From 4e8ceb0d13dbfd10e9bd11498e6c47bd3b3d0d92 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 11 Nov 2022 14:18:26 +0200 Subject: [PATCH 001/121] Modified PA_Excavator - edited physics collisions --- .../ExcavatorParts/PhysicsAssets/PA_Excavator.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator.uasset index fdf74b2..c5912d0 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d21b69cc973ed45339758cefb853327cef767d7ab43bc1e9abb80912f751efe -size 15630 +oid sha256:51f81ecb3f45182d7eb2c374dafea0ab20de945c17ff52a4e41009f9c254992d +size 15947 -- GitLab From 5ee1467a95537ce56eafa8348ba710446d566185 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 11 Nov 2022 14:18:58 +0200 Subject: [PATCH 002/121] Modified skeleton sockets --- .../ExcavatorParts/Skeletons/Skeleton_Excavator.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset index d594535..4205a4a 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:379da578c2e0a7f0beda8f9552f8e5caf9d48c0b4c4d1cff68bd0fab08d1cb3f -size 12619 +oid sha256:b7654aad213ff12bd08cc13bd1840d7ef79dd33a2ff80b7ea1435ba78978c3fb +size 13362 -- GitLab From 55f1539ad2c5e8f949a65ac789a685ce6f2f4b93 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 11 Nov 2022 14:19:22 +0200 Subject: [PATCH 003/121] Add CheckCollisionFunc - do not delete before this fucktion is working in the character --- .../Content/Blueprints/CheckCollisionFunc.txt | 395 ++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 ExcavatorSimulator/Content/Blueprints/CheckCollisionFunc.txt diff --git a/ExcavatorSimulator/Content/Blueprints/CheckCollisionFunc.txt b/ExcavatorSimulator/Content/Blueprints/CheckCollisionFunc.txt new file mode 100644 index 0000000..f264295 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/CheckCollisionFunc.txt @@ -0,0 +1,395 @@ +Begin Object Class=/Script/BlueprintGraph.K2Node_FunctionEntry Name="K2Node_FunctionEntry_1" + ExtraFlags=201457664 + FunctionReference=(MemberName="CheckCollisions") + bIsEditable=True + NodePosX=-3706 + NodePosY=-866 + NodeGuid=AAE5965E4F3C58B20265F195F4F06161 + CustomProperties Pin (PinId=C4AA568945D85AF169DD7B979393F75A,PinName="then",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_11 88374B394F67AD80FAF39984868A5B67,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_14" + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="LineTraceSingle") + NodePosX=-416 + NodePosY=-864 + AdvancedPinDisplay=Hidden + NodeGuid=263976644A18DB57208645ACD965F999 + CustomProperties Pin (PinId=88374B394F67AD80FAF39984868A5B67,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_10 FA9DAB154E36D74B9170EB9E56791318,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7A54ECF14A12CC9FA0D586B7B6F8C318,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_18 86070F4D439CEE856CE3E7BC1CCD048C,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=590B3D2443CFC1CE84FBAFA609734A79,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=45F5A0C24A4DFC2F6B7B96B2256E64E5,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A56C47504B461B9003F61C8411ADE92E,PinName="Start",PinToolTip="Start\nVector\n\nStart of line segment.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_CallFunction_15 AB3916304BFB8BBDAD5805ABF634A5C6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3AF2B8AB48AF78B2FD18A3BFF9A6041F,PinName="End",PinToolTip="End\nVector\n\nEnd of line segment.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_PromotableOperator_6 B91D15734D34ACBEF99D57B86E678BBF,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A579CE114BEB301E7CCC31BE96AC4F10,PinName="TraceChannel",PinToolTip="Trace Channel\nETraceTypeQuery Enum",PinType.PinCategory="byte",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Enum'"/Script/Engine.ETraceTypeQuery"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="TraceTypeQuery1",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3C3B21864D025C7BE11506A530E7C562,PinName="bTraceComplex",PinToolTip="Trace Complex\nBoolean\n\nTrue to test against complex collision, false to test against simplified collision.",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=98617BDB4EDD490EE4F474AFB16F879F,PinName="ActorsToIgnore",PinToolTip="Actors to Ignore\nArray of Actor Object References",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.Actor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=Array,PinType.bIsReference=True,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=True,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A0E36E29404904D9DE5664B9DF0D51D6,PinName="DrawDebugType",PinToolTip="Draw Debug Type\nEDrawDebugTrace Enum",PinType.PinCategory="byte",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Enum'"/Script/Engine.EDrawDebugTrace"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="ForDuration",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=87CB386A473A8D90801B059AC3A10F58,PinName="OutHit",PinToolTip="Out Hit\nHit Result Structure\n\nProperties of the trace hit.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/Engine.HitResult"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7969F1B44E799A40D047B690D81BEE33,PinName="bIgnoreSelf",PinToolTip="Ignore Self\nBoolean",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=2B3A2FCB426952739104A3B782AFA3F0,PinName="TraceColor",PinToolTip="Trace Color\nLinear Color Structure",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=1.000000,G=0.000000,B=0.000000,A=1.000000)",AutogeneratedDefaultValue="(R=1.000000,G=0.000000,B=0.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=9956FC9F4AFB76A421336A8BB19A9899,PinName="TraceHitColor",PinToolTip="Trace Hit Color\nLinear Color Structure",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=0.000000,G=1.000000,B=0.000000,A=1.000000)",AutogeneratedDefaultValue="(R=0.000000,G=1.000000,B=0.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=2F0C3EFB4A552CDB51FA2E827250A9F3,PinName="DrawTime",PinToolTip="Draw Time\nFloat (single-precision)",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="5.000000",AutogeneratedDefaultValue="5.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=D852482F4170D0A865C004819CA354EB,PinName="ReturnValue",PinToolTip="Return Value\nBoolean\n\nTrue if there was a hit, false otherwise.",Direction="EGPD_Output",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_CallFunction_17 E347A20D4746153663FF308273AF4A49,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet Name="K2Node_VariableGet_3" + VariableReference=(MemberName="Mesh",bSelfContext=True) + NodePosX=-1344 + NodePosY=-720 + NodeGuid=2D83A75241E98B39124D83AFF349769C + CustomProperties Pin (PinId=54AA5F1A4C27ACC19D5CCFB5F9223693,PinName="Mesh",PinFriendlyName=NSLOCTEXT("UObjectDisplayNames", "Character:Mesh", "Mesh"),Direction="EGPD_Output",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SkeletalMeshComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=True,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_15 E4FAACF54734EBAF0D3E61917F9B73AD,K2Node_CallFunction_25 3503585C48F7316605C2B88CD833D55A,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=C23F7ABC44991B5C1222A190B389BDEB,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.Character"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_15" + bIsPureFunc=True + bIsConstFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.SceneComponent"',MemberName="GetSocketLocation") + NodePosX=-1056 + NodePosY=-752 + NodeGuid=95A54212431B6160D0D657A1C5E2453C + CustomProperties Pin (PinId=E4FAACF54734EBAF0D3E61917F9B73AD,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nScene Component Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SceneComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableGet_3 54AA5F1A4C27ACC19D5CCFB5F9223693,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=D909FFFD40E37B275C9E69BAA3338215,PinName="InSocketName",PinToolTip="In Socket Name\nName\n\nName of the socket or the bone to get the transform",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="sockect_beamBottomMiddle",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=AB3916304BFB8BBDAD5805ABF634A5C6,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nSocket transform in world space if socket if found. Otherwise it will return component\'s transform in world space.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_CallFunction_14 A56C47504B461B9003F61C8411ADE92E,K2Node_PromotableOperator_6 44B7291E4DDF3FBAED271EB28A957EBB,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_PromotableOperator Name="K2Node_PromotableOperator_6" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetMathLibrary"',MemberName="Add_VectorVector") + NodePosX=-624 + NodePosY=-592 + NodeGuid=9F07F5FC467A70C4E6BC7D85B4B1E905 + CustomProperties Pin (PinId=44B7291E4DDF3FBAED271EB28A957EBB,PinName="A",PinToolTip="A\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_15 AB3916304BFB8BBDAD5805ABF634A5C6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=5B3D3AAC4DE35E98A1BAC2A9A250CBCC,PinName="B",PinToolTip="B\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_PromotableOperator_10 2A4068484E9B49DFE2A056A6D824323B,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B91D15734D34ACBEF99D57B86E678BBF,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nVector addition",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_14 3AF2B8AB48AF78B2FD18A3BFF9A6041F,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator Name="K2Node_CommutativeAssociativeBinaryOperator_1" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Concat_StrStr") + NodePosX=128 + NodePosY=-736 + NodeGuid=FBA10FF243D4A48163295ABFD8C13C7D + CustomProperties Pin (PinId=9F03CD94440691EA72DA6DB2625C8B7B,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet String Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=244142074CB799D05BD968B82B5CEE90,PinName="A",PinToolTip="A\nString\n\nThe original string",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="Hit result backward ",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=0D86873F41888915CF2B1E8A02988192,PinName="B",PinToolTip="B\nString\n\nThe string to append to A",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_17 3645C84544AC3BBC26FF2181E1CF4EF5,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=003C23AE4B36A3DB1A4CBD872F9A4305,PinName="ReturnValue",PinToolTip="Return Value\nString\n\nA new string which is the concatenation of A+B",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_18 B7FF50A94BBA4225CC0EE79101CA2BA6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_17" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Conv_BoolToString") + NodePosX=-32 + NodePosY=-704 + NodeGuid=8C4DE6CD4E0D8E89F8EC55B5A1599893 + CustomProperties Pin (PinId=76F1BB0C406D6B4F4BD679B3675E78D5,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet String Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=E347A20D4746153663FF308273AF4A49,PinName="InBool",PinToolTip="In Bool\nBoolean",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_CallFunction_14 D852482F4170D0A865C004819CA354EB,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3645C84544AC3BBC26FF2181E1CF4EF5,PinName="ReturnValue",PinToolTip="Return Value\nString\n\nConverts a boolean value to a string, either \'true\' or \'false\'",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CommutativeAssociativeBinaryOperator_1 0D86873F41888915CF2B1E8A02988192,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_18" + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="PrintString") + NodePosX=416 + NodePosY=-864 + AdvancedPinDisplay=Hidden + EnabledState=DevelopmentOnly + NodeGuid=7202DA5F46E00C128BDFE285EF7B1B95 + CustomProperties Pin (PinId=86070F4D439CEE856CE3E7BC1CCD048C,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_14 7A54ECF14A12CC9FA0D586B7B6F8C318,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=FA9DAB154E36D74B9170EB9E56791318,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=11BAD7D942198E77649597A6E4B67A6C,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=9453662C494F49EB886035B07450A539,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B7FF50A94BBA4225CC0EE79101CA2BA6,PinName="InString",PinToolTip="In String\nString\n\nThe string to log out",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="Hello",AutogeneratedDefaultValue="Hello",LinkedTo=(K2Node_CommutativeAssociativeBinaryOperator_1 003C23AE4B36A3DB1A4CBD872F9A4305,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=93755A8C44150C756F0383BB9653E6F6,PinName="bPrintToScreen",PinToolTip="Print to Screen\nBoolean\n\nWhether or not to print the output to the screen",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=6427A0BF4B604426563A35A5619CE723,PinName="bPrintToLog",PinToolTip="Print to Log\nBoolean\n\nWhether or not to print the output to the log",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=E75F30AD467412420557E88B653AFEBE,PinName="TextColor",PinToolTip="Text Color\nLinear Color Structure\n\nThe color of the text to display",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=0.000000,G=0.660000,B=1.000000,A=1.000000)",AutogeneratedDefaultValue="(R=0.000000,G=0.660000,B=1.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=87D93F3F48F1470F9741DF8F7370148E,PinName="Duration",PinToolTip="Duration\nFloat (single-precision)\n\nThe display duration (if Print to Screen is True). Using negative number will result in loading the duration time from the config.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="2.000000",AutogeneratedDefaultValue="2.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=10908E904F5830BF56FEEE93CCF4921F,PinName="Key",PinToolTip="Key\nName\n\nIf a non-empty key is provided, the message will replace any existing on-screen messages with the same key.",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="None",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/UnrealEd.EdGraphNode_Comment Name="EdGraphNode_Comment_0" + bCommentBubbleVisible_InDetailsPanel=False + NodePosX=-1392 + NodePosY=-912 + NodeWidth=1952 + NodeHeight=528 + bCommentBubblePinned=False + bCommentBubbleVisible=False + NodeComment="Check if beam middle should be able to move" + NodeGuid=B372CAF44B1D98C2FA56B8859659F0F3 +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_19" + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="LineTraceSingle") + NodePosX=1664 + NodePosY=-864 + AdvancedPinDisplay=Hidden + NodeGuid=BA02FE534E7BCDC231E18E8FD122CEF4 + CustomProperties Pin (PinId=88374B394F67AD80FAF39984868A5B67,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7A54ECF14A12CC9FA0D586B7B6F8C318,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_23 86070F4D439CEE856CE3E7BC1CCD048C,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=590B3D2443CFC1CE84FBAFA609734A79,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=45F5A0C24A4DFC2F6B7B96B2256E64E5,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A56C47504B461B9003F61C8411ADE92E,PinName="Start",PinToolTip="Start\nVector\n\nStart of line segment.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_CallFunction_20 AB3916304BFB8BBDAD5805ABF634A5C6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3AF2B8AB48AF78B2FD18A3BFF9A6041F,PinName="End",PinToolTip="End\nVector\n\nEnd of line segment.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_PromotableOperator_8 B91D15734D34ACBEF99D57B86E678BBF,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A579CE114BEB301E7CCC31BE96AC4F10,PinName="TraceChannel",PinToolTip="Trace Channel\nETraceTypeQuery Enum",PinType.PinCategory="byte",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Enum'"/Script/Engine.ETraceTypeQuery"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="TraceTypeQuery1",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3C3B21864D025C7BE11506A530E7C562,PinName="bTraceComplex",PinToolTip="Trace Complex\nBoolean\n\nTrue to test against complex collision, false to test against simplified collision.",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=98617BDB4EDD490EE4F474AFB16F879F,PinName="ActorsToIgnore",PinToolTip="Actors to Ignore\nArray of Actor Object References",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.Actor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=Array,PinType.bIsReference=True,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=True,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A0E36E29404904D9DE5664B9DF0D51D6,PinName="DrawDebugType",PinToolTip="Draw Debug Type\nEDrawDebugTrace Enum",PinType.PinCategory="byte",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Enum'"/Script/Engine.EDrawDebugTrace"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="ForDuration",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=87CB386A473A8D90801B059AC3A10F58,PinName="OutHit",PinToolTip="Out Hit\nHit Result Structure\n\nProperties of the trace hit.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/Engine.HitResult"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7969F1B44E799A40D047B690D81BEE33,PinName="bIgnoreSelf",PinToolTip="Ignore Self\nBoolean",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=2B3A2FCB426952739104A3B782AFA3F0,PinName="TraceColor",PinToolTip="Trace Color\nLinear Color Structure",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=1.000000,G=0.000000,B=0.000000,A=1.000000)",AutogeneratedDefaultValue="(R=1.000000,G=0.000000,B=0.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=9956FC9F4AFB76A421336A8BB19A9899,PinName="TraceHitColor",PinToolTip="Trace Hit Color\nLinear Color Structure",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=0.000000,G=1.000000,B=0.000000,A=1.000000)",AutogeneratedDefaultValue="(R=0.000000,G=1.000000,B=0.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=2F0C3EFB4A552CDB51FA2E827250A9F3,PinName="DrawTime",PinToolTip="Draw Time\nFloat (single-precision)",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="5.000000",AutogeneratedDefaultValue="5.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=D852482F4170D0A865C004819CA354EB,PinName="ReturnValue",PinToolTip="Return Value\nBoolean\n\nTrue if there was a hit, false otherwise.",Direction="EGPD_Output",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_CallFunction_22 E347A20D4746153663FF308273AF4A49,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet Name="K2Node_VariableGet_4" + VariableReference=(MemberName="Mesh",bSelfContext=True) + NodePosX=736 + NodePosY=-720 + NodeGuid=6EF999B5417C0DC1E1A09C9509911B43 + CustomProperties Pin (PinId=54AA5F1A4C27ACC19D5CCFB5F9223693,PinName="Mesh",PinFriendlyName=NSLOCTEXT("UObjectDisplayNames", "Character:Mesh", "Mesh"),Direction="EGPD_Output",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SkeletalMeshComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=True,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_20 E4FAACF54734EBAF0D3E61917F9B73AD,K2Node_CallFunction_21 A964C284410EA7317497EE95A110BCD7,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=C23F7ABC44991B5C1222A190B389BDEB,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.Character"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_20" + bIsPureFunc=True + bIsConstFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.SceneComponent"',MemberName="GetSocketLocation") + NodePosX=1024 + NodePosY=-752 + NodeGuid=B23244A94D950C3AF5C93B91754B7C5F + CustomProperties Pin (PinId=E4FAACF54734EBAF0D3E61917F9B73AD,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nScene Component Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SceneComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableGet_4 54AA5F1A4C27ACC19D5CCFB5F9223693,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=D909FFFD40E37B275C9E69BAA3338215,PinName="InSocketName",PinToolTip="In Socket Name\nName\n\nName of the socket or the bone to get the transform",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="socket_bucketMiddle",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=AB3916304BFB8BBDAD5805ABF634A5C6,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nSocket transform in world space if socket if found. Otherwise it will return component\'s transform in world space.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_CallFunction_19 A56C47504B461B9003F61C8411ADE92E,K2Node_PromotableOperator_8 44B7291E4DDF3FBAED271EB28A957EBB,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_PromotableOperator Name="K2Node_PromotableOperator_8" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetMathLibrary"',MemberName="Add_VectorVector") + NodePosX=1456 + NodePosY=-592 + NodeGuid=C4CA0ECC4571732AAFDB9CBCB6E7E94B + CustomProperties Pin (PinId=44B7291E4DDF3FBAED271EB28A957EBB,PinName="A",PinToolTip="A\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_20 AB3916304BFB8BBDAD5805ABF634A5C6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=5B3D3AAC4DE35E98A1BAC2A9A250CBCC,PinName="B",PinToolTip="B\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_PromotableOperator_9 87ED08A341E13D6224A3A081611F81B0,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B91D15734D34ACBEF99D57B86E678BBF,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nVector addition",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_19 3AF2B8AB48AF78B2FD18A3BFF9A6041F,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_21" + bIsPureFunc=True + bIsConstFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.SceneComponent"',MemberName="GetForwardVector") + NodePosX=1072 + NodePosY=-624 + NodeGuid=CEBA250F46FEFDCA598E3186B57F2D9D + CustomProperties Pin (PinId=A964C284410EA7317497EE95A110BCD7,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nScene Component Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SceneComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableGet_4 54AA5F1A4C27ACC19D5CCFB5F9223693,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=D4C4F08E4BE95AEC08B26790758F00CE,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nGet the forward (X) unit direction vector from this component, in world space.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_PromotableOperator_9 2ADA2F4848F9BC2A8AE24AA689AE984D,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_PromotableOperator Name="K2Node_PromotableOperator_9" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetMathLibrary"',MemberName="Multiply_VectorVector") + NodePosX=1296 + NodePosY=-512 + NodeGuid=778FD8B849A0FD0230B67DAEE81B5AFB + CustomProperties Pin (PinId=2ADA2F4848F9BC2A8AE24AA689AE984D,PinName="A",PinToolTip="A\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_21 D4C4F08E4BE95AEC08B26790758F00CE,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=312F08BF441F2A39122519BA44B0BEF4,PinName="B",PinToolTip="B\nFloat (single-precision)",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="-160.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=87ED08A341E13D6224A3A081611F81B0,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nElement-wise Vector multiplication (Result = {A.x*B.x, A.y*B.y, A.z*B.z})",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_PromotableOperator_8 5B3D3AAC4DE35E98A1BAC2A9A250CBCC,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator Name="K2Node_CommutativeAssociativeBinaryOperator_2" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Concat_StrStr") + NodePosX=2208 + NodePosY=-736 + NodeGuid=5C448C824D1533F68176169C64ABEEC0 + CustomProperties Pin (PinId=9F03CD94440691EA72DA6DB2625C8B7B,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet String Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=244142074CB799D05BD968B82B5CEE90,PinName="A",PinToolTip="A\nString\n\nThe original string",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="Hit result forward ",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=0D86873F41888915CF2B1E8A02988192,PinName="B",PinToolTip="B\nString\n\nThe string to append to A",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_22 3645C84544AC3BBC26FF2181E1CF4EF5,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=003C23AE4B36A3DB1A4CBD872F9A4305,PinName="ReturnValue",PinToolTip="Return Value\nString\n\nA new string which is the concatenation of A+B",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_23 B7FF50A94BBA4225CC0EE79101CA2BA6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_22" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Conv_BoolToString") + NodePosX=2048 + NodePosY=-704 + NodeGuid=7918160E43F0AD7C6414728CA4B8EA2E + CustomProperties Pin (PinId=76F1BB0C406D6B4F4BD679B3675E78D5,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=E347A20D4746153663FF308273AF4A49,PinName="InBool",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_CallFunction_19 D852482F4170D0A865C004819CA354EB,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3645C84544AC3BBC26FF2181E1CF4EF5,PinName="ReturnValue",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CommutativeAssociativeBinaryOperator_2 0D86873F41888915CF2B1E8A02988192,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_23" + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="PrintString") + NodePosX=2496 + NodePosY=-864 + AdvancedPinDisplay=Hidden + EnabledState=DevelopmentOnly + NodeGuid=09F1A05448FCC820221E4AA7E52B3AE0 + CustomProperties Pin (PinId=86070F4D439CEE856CE3E7BC1CCD048C,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_19 7A54ECF14A12CC9FA0D586B7B6F8C318,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=FA9DAB154E36D74B9170EB9E56791318,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=11BAD7D942198E77649597A6E4B67A6C,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=9453662C494F49EB886035B07450A539,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B7FF50A94BBA4225CC0EE79101CA2BA6,PinName="InString",PinToolTip="In String\nString\n\nThe string to log out",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="Hello",AutogeneratedDefaultValue="Hello",LinkedTo=(K2Node_CommutativeAssociativeBinaryOperator_2 003C23AE4B36A3DB1A4CBD872F9A4305,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=93755A8C44150C756F0383BB9653E6F6,PinName="bPrintToScreen",PinToolTip="Print to Screen\nBoolean\n\nWhether or not to print the output to the screen",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=6427A0BF4B604426563A35A5619CE723,PinName="bPrintToLog",PinToolTip="Print to Log\nBoolean\n\nWhether or not to print the output to the log",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=E75F30AD467412420557E88B653AFEBE,PinName="TextColor",PinToolTip="Text Color\nLinear Color Structure\n\nThe color of the text to display",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=0.000000,G=0.660000,B=1.000000,A=1.000000)",AutogeneratedDefaultValue="(R=0.000000,G=0.660000,B=1.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=87D93F3F48F1470F9741DF8F7370148E,PinName="Duration",PinToolTip="Duration\nFloat (single-precision)\n\nThe display duration (if Print to Screen is True). Using negative number will result in loading the duration time from the config.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="2.000000",AutogeneratedDefaultValue="2.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=10908E904F5830BF56FEEE93CCF4921F,PinName="Key",PinToolTip="Key\nName\n\nIf a non-empty key is provided, the message will replace any existing on-screen messages with the same key.",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="None",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/UnrealEd.EdGraphNode_Comment Name="EdGraphNode_Comment_2" + bCommentBubbleVisible_InDetailsPanel=False + NodePosX=688 + NodePosY=-912 + NodeWidth=1994 + NodeHeight=514 + bCommentBubblePinned=False + bCommentBubbleVisible=False + NodeComment="Check bucket collision forward" + NodeGuid=446C46834950E56F98B025AE5A12865F +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_11" + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="LineTraceSingle") + NodePosX=-2458 + NodePosY=-866 + AdvancedPinDisplay=Hidden + NodeGuid=2885A6AE4245F2BF351D398BFAF15026 + CustomProperties Pin (PinId=88374B394F67AD80FAF39984868A5B67,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_FunctionEntry_1 C4AA568945D85AF169DD7B979393F75A,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7A54ECF14A12CC9FA0D586B7B6F8C318,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableSet_0 887195C240E0F5BEA47FD8B4EA703D67,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=590B3D2443CFC1CE84FBAFA609734A79,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=45F5A0C24A4DFC2F6B7B96B2256E64E5,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A56C47504B461B9003F61C8411ADE92E,PinName="Start",PinToolTip="Start\nVector\n\nStart of line segment.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_CallFunction_12 AB3916304BFB8BBDAD5805ABF634A5C6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3AF2B8AB48AF78B2FD18A3BFF9A6041F,PinName="End",PinToolTip="End\nVector\n\nEnd of line segment.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_PromotableOperator_4 B91D15734D34ACBEF99D57B86E678BBF,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A579CE114BEB301E7CCC31BE96AC4F10,PinName="TraceChannel",PinToolTip="Trace Channel\nETraceTypeQuery Enum",PinType.PinCategory="byte",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Enum'"/Script/Engine.ETraceTypeQuery"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="TraceTypeQuery1",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3C3B21864D025C7BE11506A530E7C562,PinName="bTraceComplex",PinToolTip="Trace Complex\nBoolean\n\nTrue to test against complex collision, false to test against simplified collision.",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=98617BDB4EDD490EE4F474AFB16F879F,PinName="ActorsToIgnore",PinToolTip="Actors to Ignore\nArray of Actor Object References",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.Actor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=Array,PinType.bIsReference=True,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=True,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A0E36E29404904D9DE5664B9DF0D51D6,PinName="DrawDebugType",PinToolTip="Draw Debug Type\nEDrawDebugTrace Enum",PinType.PinCategory="byte",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Enum'"/Script/Engine.EDrawDebugTrace"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="ForDuration",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=87CB386A473A8D90801B059AC3A10F58,PinName="OutHit",PinToolTip="Out Hit\nHit Result Structure\n\nProperties of the trace hit.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/Engine.HitResult"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7969F1B44E799A40D047B690D81BEE33,PinName="bIgnoreSelf",PinToolTip="Ignore Self\nBoolean",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=2B3A2FCB426952739104A3B782AFA3F0,PinName="TraceColor",PinToolTip="Trace Color\nLinear Color Structure",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=1.000000,G=0.000000,B=0.000000,A=1.000000)",AutogeneratedDefaultValue="(R=1.000000,G=0.000000,B=0.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=9956FC9F4AFB76A421336A8BB19A9899,PinName="TraceHitColor",PinToolTip="Trace Hit Color\nLinear Color Structure",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=0.000000,G=1.000000,B=0.000000,A=1.000000)",AutogeneratedDefaultValue="(R=0.000000,G=1.000000,B=0.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=2F0C3EFB4A552CDB51FA2E827250A9F3,PinName="DrawTime",PinToolTip="Draw Time\nFloat (single-precision)",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="5.000000",AutogeneratedDefaultValue="5.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=D852482F4170D0A865C004819CA354EB,PinName="ReturnValue",PinToolTip="Return Value\nBoolean\n\nTrue if there was a hit, false otherwise.",Direction="EGPD_Output",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_CallFunction_9 E347A20D4746153663FF308273AF4A49,K2Node_VariableSet_0 7D6D192C4886AB50C1A3BC821FAB2EA6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet Name="K2Node_VariableGet_2" + VariableReference=(MemberName="Mesh",bSelfContext=True) + NodePosX=-3386 + NodePosY=-722 + NodeGuid=38403EB2425743BED9313D94F7449D5B + CustomProperties Pin (PinId=54AA5F1A4C27ACC19D5CCFB5F9223693,PinName="Mesh",PinFriendlyName=NSLOCTEXT("UObjectDisplayNames", "Character:Mesh", "Mesh"),Direction="EGPD_Output",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SkeletalMeshComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=True,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_12 E4FAACF54734EBAF0D3E61917F9B73AD,K2Node_CallFunction_24 8901467346C3E5E2567102B86D729B24,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=C23F7ABC44991B5C1222A190B389BDEB,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.Character"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_12" + bIsPureFunc=True + bIsConstFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.SceneComponent"',MemberName="GetSocketLocation") + NodePosX=-3098 + NodePosY=-754 + NodeGuid=4E1845F940E8150192A848AD704783D9 + CustomProperties Pin (PinId=E4FAACF54734EBAF0D3E61917F9B73AD,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nScene Component Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SceneComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableGet_2 54AA5F1A4C27ACC19D5CCFB5F9223693,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=D909FFFD40E37B275C9E69BAA3338215,PinName="InSocketName",PinToolTip="In Socket Name\nName\n\nName of the socket or the bone to get the transform",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="socket_bucketMiddle",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=AB3916304BFB8BBDAD5805ABF634A5C6,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nSocket transform in world space if socket if found. Otherwise it will return component\'s transform in world space.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_CallFunction_11 A56C47504B461B9003F61C8411ADE92E,K2Node_PromotableOperator_4 44B7291E4DDF3FBAED271EB28A957EBB,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_PromotableOperator Name="K2Node_PromotableOperator_4" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetMathLibrary"',MemberName="Add_VectorVector") + NodePosX=-2666 + NodePosY=-594 + NodeGuid=DF5BEB6C430C1DB472BA8AAFFC518E23 + CustomProperties Pin (PinId=44B7291E4DDF3FBAED271EB28A957EBB,PinName="A",PinToolTip="A\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_12 AB3916304BFB8BBDAD5805ABF634A5C6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=5B3D3AAC4DE35E98A1BAC2A9A250CBCC,PinName="B",PinToolTip="B\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_PromotableOperator_5 87ED08A341E13D6224A3A081611F81B0,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B91D15734D34ACBEF99D57B86E678BBF,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nVector addition",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_11 3AF2B8AB48AF78B2FD18A3BFF9A6041F,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_PromotableOperator Name="K2Node_PromotableOperator_5" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetMathLibrary"',MemberName="Multiply_VectorVector") + NodePosX=-2826 + NodePosY=-514 + NodeGuid=B3D9F6EB4FDFFCA89D30C9B114B30D73 + CustomProperties Pin (PinId=2ADA2F4848F9BC2A8AE24AA689AE984D,PinName="A",PinToolTip="A\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_24 0657003B4E133A1CEC844EA64ADE4E64,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=312F08BF441F2A39122519BA44B0BEF4,PinName="B",PinToolTip="B\nFloat (single-precision)",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="-160.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=87ED08A341E13D6224A3A081611F81B0,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nElement-wise Vector multiplication (Result = {A.x*B.x, A.y*B.y, A.z*B.z})",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_PromotableOperator_4 5B3D3AAC4DE35E98A1BAC2A9A250CBCC,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator Name="K2Node_CommutativeAssociativeBinaryOperator_0" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Concat_StrStr") + NodePosX=-1914 + NodePosY=-738 + NodeGuid=CD7C7CC145DDB4588981B0B0C5EA9157 + CustomProperties Pin (PinId=9F03CD94440691EA72DA6DB2625C8B7B,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet String Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=244142074CB799D05BD968B82B5CEE90,PinName="A",PinToolTip="A\nString\n\nThe original string",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="Hit result down ",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=0D86873F41888915CF2B1E8A02988192,PinName="B",PinToolTip="B\nString\n\nThe string to append to A",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_9 3645C84544AC3BBC26FF2181E1CF4EF5,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=003C23AE4B36A3DB1A4CBD872F9A4305,PinName="ReturnValue",PinToolTip="Return Value\nString\n\nA new string which is the concatenation of A+B",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_10 B7FF50A94BBA4225CC0EE79101CA2BA6,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_9" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetStringLibrary"',MemberName="Conv_BoolToString") + NodePosX=-2074 + NodePosY=-706 + NodeGuid=1E228786488A3D6635B98FA419638FD8 + CustomProperties Pin (PinId=76F1BB0C406D6B4F4BD679B3675E78D5,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet String Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetStringLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetStringLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=E347A20D4746153663FF308273AF4A49,PinName="InBool",PinToolTip="In Bool\nBoolean",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_CallFunction_11 D852482F4170D0A865C004819CA354EB,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3645C84544AC3BBC26FF2181E1CF4EF5,PinName="ReturnValue",PinToolTip="Return Value\nString\n\nConverts a boolean value to a string, either \'true\' or \'false\'",Direction="EGPD_Output",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CommutativeAssociativeBinaryOperator_0 0D86873F41888915CF2B1E8A02988192,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_10" + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="PrintString") + NodePosX=-1626 + NodePosY=-866 + AdvancedPinDisplay=Hidden + EnabledState=DevelopmentOnly + NodeGuid=461E8BDD41FEAE8B61D845930A789E82 + CustomProperties Pin (PinId=86070F4D439CEE856CE3E7BC1CCD048C,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableSet_0 638868A44C03BE4F6298CA8AFC77383A,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=FA9DAB154E36D74B9170EB9E56791318,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_14 88374B394F67AD80FAF39984868A5B67,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=11BAD7D942198E77649597A6E4B67A6C,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=9453662C494F49EB886035B07450A539,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B7FF50A94BBA4225CC0EE79101CA2BA6,PinName="InString",PinToolTip="In String\nString\n\nThe string to log out",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="Hello",AutogeneratedDefaultValue="Hello",LinkedTo=(K2Node_CommutativeAssociativeBinaryOperator_0 003C23AE4B36A3DB1A4CBD872F9A4305,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=93755A8C44150C756F0383BB9653E6F6,PinName="bPrintToScreen",PinToolTip="Print to Screen\nBoolean\n\nWhether or not to print the output to the screen",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=6427A0BF4B604426563A35A5619CE723,PinName="bPrintToLog",PinToolTip="Print to Log\nBoolean\n\nWhether or not to print the output to the log",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=E75F30AD467412420557E88B653AFEBE,PinName="TextColor",PinToolTip="Text Color\nLinear Color Structure\n\nThe color of the text to display",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(R=0.000000,G=0.660000,B=1.000000,A=1.000000)",AutogeneratedDefaultValue="(R=0.000000,G=0.660000,B=1.000000,A=1.000000)",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=87D93F3F48F1470F9741DF8F7370148E,PinName="Duration",PinToolTip="Duration\nFloat (single-precision)\n\nThe display duration (if Print to Screen is True). Using negative number will result in loading the duration time from the config.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="2.000000",AutogeneratedDefaultValue="2.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=10908E904F5830BF56FEEE93CCF4921F,PinName="Key",PinToolTip="Key\nName\n\nIf a non-empty key is provided, the message will replace any existing on-screen messages with the same key.",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="None",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/UnrealEd.EdGraphNode_Comment Name="EdGraphNode_Comment_1" + bCommentBubbleVisible_InDetailsPanel=False + NodePosX=-3440 + NodePosY=-912 + NodeWidth=2000 + NodeHeight=512 + bCommentBubblePinned=False + bCommentBubbleVisible=False + NodeComment="Check bucket collision down" + NodeGuid=D5FABAA94B730AE502E1A69461570BCA +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_24" + bIsPureFunc=True + bIsConstFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.SceneComponent"',MemberName="GetUpVector") + NodePosX=-3098 + NodePosY=-594 + NodeGuid=4C9AFC04407F5154D72AF5A26031046F + CustomProperties Pin (PinId=8901467346C3E5E2567102B86D729B24,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nScene Component Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SceneComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableGet_2 54AA5F1A4C27ACC19D5CCFB5F9223693,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=0657003B4E133A1CEC844EA64ADE4E64,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nGet the up (Z) unit direction vector from this component, in world space.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_PromotableOperator_5 2ADA2F4848F9BC2A8AE24AA689AE984D,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_VariableSet Name="K2Node_VariableSet_0" + VariableReference=(MemberName="BucketCollidingFromDown",MemberGuid=765C72894646C32C672772AD79EE84C6,bSelfContext=True) + NodePosX=-2048 + NodePosY=-848 + NodeGuid=DC8A413742D74130CB331FBC421BAB14 + CustomProperties Pin (PinId=887195C240E0F5BEA47FD8B4EA703D67,PinName="execute",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_11 7A54ECF14A12CC9FA0D586B7B6F8C318,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=638868A44C03BE4F6298CA8AFC77383A,PinName="then",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_10 86070F4D439CEE856CE3E7BC1CCD048C,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7D6D192C4886AB50C1A3BC821FAB2EA6,PinName="BucketCollidingFromDown",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_CallFunction_11 D852482F4170D0A865C004819CA354EB,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=4A811F2142491C320044A99B138C9C60,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=BlueprintGeneratedClass'"/Game/Blueprints/BP_ExcavatorCharacter.BP_ExcavatorCharacter_C"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=72DE34FC46D53B87224128BF99EA56D7,PinName="Output_Get",PinToolTip="Retrieves the value of the variable, can use instead of a separate Get node",Direction="EGPD_Output",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_25" + bIsPureFunc=True + bIsConstFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.SceneComponent"',MemberName="GetUpVector") + NodePosX=-1008 + NodePosY=-528 + NodeGuid=C8FEDB344BF2A49E5B50E3BD3656AAE9 + CustomProperties Pin (PinId=3503585C48F7316605C2B88CD833D55A,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nScene Component Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.SceneComponent"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_VariableGet_3 54AA5F1A4C27ACC19D5CCFB5F9223693,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=EFFA1F4E48743E7B023FC48269963D89,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nGet the up (Z) unit direction vector from this component, in world space.",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0, 0, 0",AutogeneratedDefaultValue="0, 0, 0",LinkedTo=(K2Node_PromotableOperator_10 ECECD7CA4CB1177E2DAFDE875B24091D,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_PromotableOperator Name="K2Node_PromotableOperator_10" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetMathLibrary"',MemberName="Multiply_VectorVector") + NodePosX=-752 + NodePosY=-464 + NodeGuid=6B44715D48D5DC805D20EEA47491FBA8 + CustomProperties Pin (PinId=ECECD7CA4CB1177E2DAFDE875B24091D,PinName="A",PinToolTip="A\nVector",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_25 EFFA1F4E48743E7B023FC48269963D89,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=FAB747E041C227E290C8E98652727571,PinName="B",PinToolTip="B\nFloat (single-precision)",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="-420.000000",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=2A4068484E9B49DFE2A056A6D824323B,PinName="ReturnValue",PinToolTip="Return Value\nVector\n\nElement-wise Vector multiplication (Result = {A.x*B.x, A.y*B.y, A.z*B.z})",Direction="EGPD_Output",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.Vector"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_PromotableOperator_6 5B3D3AAC4DE35E98A1BAC2A9A250CBCC,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/UnrealEd.EdGraphNode_Comment Name="EdGraphNode_Comment_4" + bCommentBubbleVisible_InDetailsPanel=False + NodePosX=-2512 + NodePosY=-1056 + bCommentBubblePinned=False + bCommentBubbleVisible=False + NodeComment="Add check here that if the colliding object is it self it will ignore the collision" + NodeGuid=2D1C45E349AAB5F6193A3BA839A47966 +End Object -- GitLab From 81a838e6eaa1af3dd6e57610930145ff6f498191 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Fri, 11 Nov 2022 14:50:53 +0200 Subject: [PATCH 004/121] FINALLY GRAB MECHANICS WORK IN CABIN --- ExcavatorSimulator/Config/DefaultEngine.ini | 2 +- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset | 4 ++-- .../Content/Blueprints/VR/VRInteractionBPI.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ExcavatorSimulator/Config/DefaultEngine.ini b/ExcavatorSimulator/Config/DefaultEngine.ini index 333df35..65b5b1c 100644 --- a/ExcavatorSimulator/Config/DefaultEngine.ini +++ b/ExcavatorSimulator/Config/DefaultEngine.ini @@ -22,7 +22,7 @@ DefaultGraphicsPerformance=Scalable AppliedDefaultGraphicsPerformance=Scalable [/Script/WindowsTargetPlatform.WindowsTargetSettings] -DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +DefaultGraphicsRHI=DefaultGraphicsRHI_DX11 [/Script/Engine.RendererSettings] r.Mobile.DisableVertexFog=False diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index 90f2f77..76016c8 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b7343c71378fc70382beb43f69399fbdeb758c825829d9f43e02cfb87f1ed11 -size 233119 +oid sha256:f1dc3c67a113bd85189f83dd0cf6e1c73c44603cd99d626b6f55ffd8dbf7f64c +size 241480 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index c4953d8..f437654 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81b229d104f484f6226466d5f67d14b5099c32bbc5e66c7952345cbec69f7b55 -size 42454 +oid sha256:43b8be1f324da1d9a362aab8bfe3df48d2365690dfbd0089583177dced5fff65 +size 73206 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset b/ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset index 7b2cc75..082d889 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e399566f0930dbf15d619810b58fc0c9a7905248d3c510b9f75325e49e251c32 -size 268434 +oid sha256:6e78c3acf57c6860bdda78240b23483817f096c988c0ad3dbc1de15bb710c13c +size 291859 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset b/ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset index af053a4..9067e75 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba8814a8554e0754b582c06c5981f9f95611d94c4f593ce8b68e24dfe78487e4 -size 18497 +oid sha256:2b4ac4acbd6d21c6777b4c56ecba29c445df5975ef194c72d161473067393131 +size 19687 diff --git a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap index e4afa3e..70f7f0b 100644 --- a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap +++ b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e8731ac9023688a391141d54be79f4e702efbce63c61aaabc45a6f31b44d50c -size 74844 +oid sha256:8d6bcd17d4368fb15fbcb9073cf362337bc34e3920402df19ded1b2212aeac91 +size 74263 -- GitLab From fe2a926877016ca77004230503fff552d75851c8 Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Fri, 11 Nov 2022 15:28:52 +0200 Subject: [PATCH 005/121] sprites for tutorial --- .../Selfmade/TutorialSprites/excavator_rock_tutorial.uasset | 3 +++ .../Selfmade/TutorialSprites/excavator_rock_tutorial2.uasset | 3 +++ .../Assets/Selfmade/TutorialSprites/excavator_tutorial1.uasset | 3 +++ .../Selfmade/TutorialSprites/excavator_tutorial1_Sprite.uasset | 3 +++ .../Assets/Selfmade/TutorialSprites/excavator_tutorial2.uasset | 3 +++ .../Selfmade/TutorialSprites/excavator_tutorial2_Sprite.uasset | 3 +++ .../Assets/Selfmade/TutorialSprites/excavator_tutorial3.uasset | 3 +++ .../Selfmade/TutorialSprites/excavator_tutorial3_Sprite.uasset | 3 +++ .../Assets/Selfmade/TutorialSprites/excavator_tutorial4.uasset | 3 +++ .../Selfmade/TutorialSprites/excavator_tutorial4_Sprite.uasset | 3 +++ .../Assets/Selfmade/TutorialSprites/excavator_tutorial5.uasset | 3 +++ .../Selfmade/TutorialSprites/excavator_tutorial5_Sprite.uasset | 3 +++ 12 files changed, 36 insertions(+) create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial2.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1_Sprite.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2_Sprite.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3_Sprite.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4_Sprite.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5.uasset create mode 100644 ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5_Sprite.uasset diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial.uasset new file mode 100644 index 0000000..e3b8c82 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6012f95d395947bfd15020ecbf8a3020ef3605daf4787c42e318c6954d4b80b +size 12747 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial2.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial2.uasset new file mode 100644 index 0000000..5fe9778 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_rock_tutorial2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c5b71863bec905fc90e719cd7d239b0e1a9ec20b35fa85309718c5ef4b589b6 +size 12306 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1.uasset new file mode 100644 index 0000000..29ab535 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cea88c0103ad9d821f696b6eda0ff069b8006379fdf3cfa38a87d9617090b14c +size 52596 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1_Sprite.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1_Sprite.uasset new file mode 100644 index 0000000..d783e99 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial1_Sprite.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d68f0178109c9ff6be26a086a14b1d9e7e93e46a5e0d5c9f97be10ec38c725a1 +size 15426 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2.uasset new file mode 100644 index 0000000..88e07d2 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fba0946862af982150b9e95c144e33b50e2a0ea849a2329486d363174865e4f4 +size 33703 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2_Sprite.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2_Sprite.uasset new file mode 100644 index 0000000..1143d70 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial2_Sprite.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c49c175de7d5cf90156719fb504204405d0e88a910c05915da789de401d5b1f9 +size 16147 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3.uasset new file mode 100644 index 0000000..8934ebb --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4e80a1726c2c31c58c0d6cf3f083dd2af4a5de3874b387217fbf78c63243413 +size 33436 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3_Sprite.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3_Sprite.uasset new file mode 100644 index 0000000..764ff88 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial3_Sprite.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:469f4e544ce0b739ca7b59d6ecfbcb39f5549c8c0a7a8eed2290bb201d6fa2f3 +size 15952 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4.uasset new file mode 100644 index 0000000..b62c940 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afd1a2eddfd9ac1611ae175c00701711b5d18a65662caa69c6c817d6d03ff000 +size 34007 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4_Sprite.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4_Sprite.uasset new file mode 100644 index 0000000..053b03a --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial4_Sprite.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7171e7d2d6fc890fd168093a6708703ac95e9b8cdcb2beed30785e3f89b3a7f6 +size 16007 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5.uasset new file mode 100644 index 0000000..81e6362 --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ac7bd784cf7283de23768cea762b510b474e0f74e1360736a9cf7a66b3b2545 +size 33973 diff --git a/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5_Sprite.uasset b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5_Sprite.uasset new file mode 100644 index 0000000..7f4c46e --- /dev/null +++ b/ExcavatorSimulator/Content/Assets/Selfmade/TutorialSprites/excavator_tutorial5_Sprite.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5a91536032c991d5581c944cf1c9447989304286d806504685883f08af8b30e +size 16204 -- GitLab From b6ed13ad3ac20fba3c6d560e813daf9920ae64f1 Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Fri, 11 Nov 2022 15:30:32 +0200 Subject: [PATCH 006/121] tutorial for picking up rocks --- .../Content/Blueprints/BP_RockStartPoint.uasset | 4 ++-- .../Content/Blueprints/Test_BP_Excavator.uasset | 3 +++ ExcavatorSimulator/Content/Levels/FirstLevel.umap | 4 ++-- ExcavatorSimulator/Content/Levels/TestLevel_Markka.umap | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset b/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset index b6c5baf..9bb7d8a 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f10ec04d1816f51c9bf99d3cbfa26464b319dc482afe04baf0f7c9b3a0cadcd -size 255693 +oid sha256:a10a07673d56cdb9ef77d3229c0a71d77ae5fec1fd2b253088efea26cbcd35b7 +size 253168 diff --git a/ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset new file mode 100644 index 0000000..0d04902 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b09bae25b01c0ad04695defca8d076843846929f4896093f680a51bb632d9b9 +size 1117086 diff --git a/ExcavatorSimulator/Content/Levels/FirstLevel.umap b/ExcavatorSimulator/Content/Levels/FirstLevel.umap index 2643d78..85c3c2d 100644 --- a/ExcavatorSimulator/Content/Levels/FirstLevel.umap +++ b/ExcavatorSimulator/Content/Levels/FirstLevel.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ce8bb89d43e282b1452ed49ca79b9ab02f74ad42e31d6513740430ff7636a33 -size 2620118 +oid sha256:16d3b0f392b3c52d10ae8bd3691504115a439b9ef17979c378f020237e4a6c95 +size 2631472 diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Markka.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Markka.umap index 804a1c1..8354de2 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Markka.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Markka.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0090df893ef82e1f7194aa60510d6cc1db862f8adf7a5a37c6139e0cb118f777 -size 63348 +oid sha256:cb5ea3a9a150a8b733896ef82bcc84c994dfdeae25dca97e2aac014e27753477 +size 52318 -- GitLab From 4570282a3cecac284ce62c456bef18ed2110d3d9 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Fri, 11 Nov 2022 15:56:20 +0200 Subject: [PATCH 007/121] Comment stuff --- .../Content/Levels/TestLevel_Niko.umap | 4 ++-- .../GroundDeformerComponent.cpp | 22 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap index 2ba2595..cea4286 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:409f5e5524a09e651cd4d812f5201a2a5f8ab846ee91cea5e5a50ccede24affe -size 32560 +oid sha256:fbf361588ce83603fcfcb2de9edc4bd4ba3fd7c8e3269c0cd0ac6495205d2913 +size 31555 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp index 79d2177..a3d6ad8 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp @@ -5,6 +5,7 @@ #include <Voxel/Public/VoxelTools/VoxelSurfaceTools.h> #include <Voxel/Public/VoxelDebug/VoxelDebugUtilities.h> #include <Voxel/Public/VoxelTools/Gen/VoxelSurfaceEditTools.h> +#include "WorldGenerator.h" #include "GroundDeformerComponent.h" @@ -29,21 +30,22 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) //GetComponentLocation() is WorldLocation, GetForwardVctor() is ForwardVector. FVector EndVector = Mesh->GetComponentLocation() + (Mesh->GetForwardVector() * ForwardVectorMultiplier); - /*FVoxelIntBox IntBox = UVoxelBlueprintLibrary::MakeIntBoxFromGlobalPositionAndRadius(VoxelWorldREF, Mesh->GetComponentLocation(), 100.0f); + //FVoxelIntBox IntBox = UVoxelBlueprintLibrary::MakeIntBoxFromGlobalPositionAndRadius(VoxelWorldREF, Mesh->GetComponentLocation(), 100.0f); + //UVoxelDebugUtilities::DrawDebugIntBox(VoxelWorldREF, IntBox, IntBox.); - UVoxelBoxTools::RemoveBox(VoxelWorldREF, IntBox); + //UVoxelBoxTools::RemoveBox(VoxelWorldREF, IntBox); - FVoxelSurfaceEditsVoxels Voxels; - UVoxelSurfaceTools::FindSurfaceVoxelsFromDistanceField(Voxels, VoxelWorldREF, IntBox); + //FVoxelSurfaceEditsVoxels Voxels; + //UVoxelSurfaceTools::FindSurfaceVoxelsFromDistanceField(Voxels, VoxelWorldREF, IntBox); - FVoxelSurfaceEditsStack Stack; - Stack.Add(UVoxelSurfaceTools::ApplyFalloff(VoxelWorldREF, EVoxelFalloff::Linear, Mesh->GetComponentLocation(), 100.0f, 0.5f)); - Stack.Add(UVoxelSurfaceTools::ApplyConstantStrength(-0.8f)); + //FVoxelSurfaceEditsStack Stack; + //Stack.Add(UVoxelSurfaceTools::ApplyFalloff(VoxelWorldREF, EVoxelFalloff::Linear, Mesh->GetComponentLocation(), 100.0f, 0.5f)); + //Stack.Add(UVoxelSurfaceTools::ApplyConstantStrength(-0.8f)); - FVoxelSurfaceEditsProcessedVoxels PVoxels = Stack.Execute(Voxels); - UVoxelSurfaceEditTools::EditVoxelValues(VoxelWorldREF, PVoxels); + //FVoxelSurfaceEditsProcessedVoxels PVoxels = Stack.Execute(Voxels); + //UVoxelSurfaceEditTools::EditVoxelValues(VoxelWorldREF, PVoxels); - UVoxelSphereTools::SmoothSphere(VoxelWorldREF, Mesh->GetComponentLocation(), 140.0f, 0.5f);*/ + //UVoxelSphereTools::SmoothSphere(VoxelWorldREF, Mesh->GetComponentLocation(), 140.0f, 0.5f); if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) -- GitLab From 5aba048cc113ba3a04ac56203484ec3555dd6e60 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Fri, 11 Nov 2022 15:56:22 +0200 Subject: [PATCH 008/121] Added functionality to grab in cabin --- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index 76016c8..8b1f5e2 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1dc3c67a113bd85189f83dd0cf6e1c73c44603cd99d626b6f55ffd8dbf7f64c -size 241480 +oid sha256:4bf372a3a918709abcfc99b42add64b9f8bee2070192224018229caef5765a06 +size 383786 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 120fd03..718ded0 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e4ec64cdeea4a48ceb2e714c94ecf63b2c1f32e5f4f672e997924c09f558f59 -size 1053346 +oid sha256:f02ce8ce246c979b97888d85072e26009cec9a6da1f1391fa0b3dedeed39a321 +size 1052867 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index f437654..5560bcc 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43b8be1f324da1d9a362aab8bfe3df48d2365690dfbd0089583177dced5fff65 -size 73206 +oid sha256:5406912e0d178d86acb6fc9e65d272aeadd795374efcddf40767eb9bdb5db299 +size 64756 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index a400f44..e902811 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a74fb18aeafd81baac5d39192b5bf76451e1c58dee957df9eeabfed02be16c90 -size 770630 +oid sha256:d0714454bf7f8506e256c2970c7dd660168f8211a2bf12bfc1f4ba2911d1f466 +size 728148 -- GitLab From 582f98f9d7eecfb6426d8006f2ac5cb5c5c86cfb Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Fri, 11 Nov 2022 15:57:21 +0200 Subject: [PATCH 009/121] Add worldgenerator --- .../Blueprints/BP_WorldGenerator.uasset | 3 + .../ExcavatorSimulator/WorldGenerator.cpp | 92 +++++++++++++++++++ .../ExcavatorSimulator/WorldGenerator.h | 50 ++++++++++ 3 files changed, 145 insertions(+) create mode 100644 ExcavatorSimulator/Content/Blueprints/BP_WorldGenerator.uasset create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.cpp create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.h diff --git a/ExcavatorSimulator/Content/Blueprints/BP_WorldGenerator.uasset b/ExcavatorSimulator/Content/Blueprints/BP_WorldGenerator.uasset new file mode 100644 index 0000000..60ec87c --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/BP_WorldGenerator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5e0991d28b86884a32acac281fded90521b60a4560022f7c9c507e0a6e582eb +size 6071 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.cpp new file mode 100644 index 0000000..1be3160 --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.cpp @@ -0,0 +1,92 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include <Voxel/Public/VoxelMaterialBuilder.h> +#include <Voxel/Public/FastNoise/VoxelFastNoise.inl> +#include "WorldGenerator.h" + +UWorldGenerator::UWorldGenerator() +{ +} + +UWorldGenerator ::~UWorldGenerator() +{ +} + +TVoxelSharedRef<FVoxelGeneratorInstance> UWorldGenerator::GetInstance() +{ + return MakeVoxelShared<FWorldGeneratorInstance>(*this); +} + +FWorldGeneratorInstance::FWorldGeneratorInstance(const UWorldGenerator& MyGenerator) + : Super(&MyGenerator) + , NoiseHeight(MyGenerator.NoiseHeight) + , Seed(MyGenerator.Seed) +{ +} + +void FWorldGeneratorInstance::Init(const FVoxelGeneratorInit& InitStruct) +{ + Noise.SetSeed(Seed); +} + +v_flt FWorldGeneratorInstance::GetValueImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack& Items) const +{ + const float Height = Noise.GetPerlin_2D(X, Y, 0.01f) * NoiseHeight; + + // Positive value -> empty voxel + // Negative value -> full voxel + // Value positive when Z > Height, and negative Z < Height + float Value = Z - Height; + + // The voxel value is clamped between -1 and 1. That can result in a bad gradient/normal. To solve that we divide it + Value /= 5; + + return Value; +} + +FVoxelMaterial FWorldGeneratorInstance::GetMaterialImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack& Items) const +{ + FVoxelMaterialBuilder Builder; + + // RGB + Builder.SetMaterialConfig(EVoxelMaterialConfig::RGB); + Builder.SetColor(FColor::Red); + + // Single index + //Builder.SetMaterialConfig(EVoxelMaterialConfig::SingleIndex); + //Builder.SetSingleIndex(0); + + // Multi index + //Builder.SetMaterialConfig(EVoxelMaterialConfig::MultiIndex); + //Builder.AddMultiIndex(0, 0.5f); + //Builder.AddMultiIndex(1, 0.5f); + + return Builder.Build(); +} + +TVoxelRange<v_flt> FWorldGeneratorInstance::GetValueRangeImpl(const FVoxelIntBox& Bounds, int32 LOD, const FVoxelItemStack& Items) const +{ + // Return the values that GetValueImpl can return in Bounds + // Used to skip chunks where the value does not change + // Be careful, if wrong your world will have holes! + // By default return infinite range to be safe + return TVoxelRange<v_flt>::Infinite(); + + // Example for the GetValueImpl above + + // Noise is between -1 and 1 + const TVoxelRange<v_flt> Height = TVoxelRange<v_flt>(-1, 1) * NoiseHeight; + + // Z can go from min to max + TVoxelRange<v_flt> Value = TVoxelRange<v_flt>(Bounds.Min.Z, Bounds.Max.Z) - Height; + + Value /= 5; + + return Value; +} + +FVector FWorldGeneratorInstance::GetUpVector(v_flt X, v_flt Y, v_flt Z) const +{ + // Used by spawners + return FVector::UpVector; +} diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.h b/ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.h new file mode 100644 index 0000000..77d2650 --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/WorldGenerator.h @@ -0,0 +1,50 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include <Voxel/Public/FastNoise/VoxelFastNoise.h> +#include <Voxel/Public/VoxelGenerators/VoxelGeneratorHelpers.h> +#include "WorldGenerator.generated.h" + +/** + * + */ +UCLASS(Blueprintable) +class EXCAVATORSIMULATOR_API UWorldGenerator : public UVoxelGenerator +{ + GENERATED_BODY() +public: + UWorldGenerator(); + ~UWorldGenerator(); + virtual TVoxelSharedRef<FVoxelGeneratorInstance> GetInstance() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generator") + float NoiseHeight = 10.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generator") + int32 Seed = 1337; +}; + +class FWorldGeneratorInstance : public TVoxelGeneratorInstanceHelper<FWorldGeneratorInstance, UWorldGenerator > +{ +public: + + using Super = TVoxelGeneratorInstanceHelper<FWorldGeneratorInstance, UWorldGenerator >; + + explicit FWorldGeneratorInstance(const UWorldGenerator& MyGenerator); + + virtual void Init(const FVoxelGeneratorInit& InitStruct) override; + + v_flt GetValueImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack& Items) const; + + FVoxelMaterial GetMaterialImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack& Items) const; + + TVoxelRange<v_flt> GetValueRangeImpl(const FVoxelIntBox& Bounds, int32 LOD, const FVoxelItemStack& Items) const; + + virtual FVector GetUpVector(v_flt X, v_flt Y, v_flt Z) const override final; +private: + const float NoiseHeight; + const int32 Seed; + FVoxelFastNoise Noise; +}; -- GitLab From 6201ddc2920bd5f7d7c8b926b37267aee97a1cbc Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Mon, 14 Nov 2022 11:41:21 +0200 Subject: [PATCH 010/121] modified first FirstLevel --- ExcavatorSimulator/Content/Levels/FirstLevel.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/FirstLevel.umap b/ExcavatorSimulator/Content/Levels/FirstLevel.umap index 85c3c2d..ae97114 100644 --- a/ExcavatorSimulator/Content/Levels/FirstLevel.umap +++ b/ExcavatorSimulator/Content/Levels/FirstLevel.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16d3b0f392b3c52d10ae8bd3691504115a439b9ef17979c378f020237e4a6c95 -size 2631472 +oid sha256:b5375d84990ec8aee4a3b11a6147144a8471b6ccf2a32b2bff4c0923cf3902c0 +size 2672536 -- GitLab From 297ae8f42ece45562f6588b7bc5ca2169638739c Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 09:34:24 +0200 Subject: [PATCH 011/121] Add collision check c++ --- .../CollisionCheckComponent.cpp | 53 +++++++++++++++++++ .../CollisionCheckComponent.h | 39 ++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp new file mode 100644 index 0000000..e34a5a7 --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp @@ -0,0 +1,53 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "CollisionCheckComponent.h" +#include "Engine/World.h" + + +// Sets default values for this component's properties +UCollisionCheckComponent::UCollisionCheckComponent() +{ + // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features + // off to improve performance if you don't need them. + PrimaryComponentTick.bCanEverTick = true; +} + + +// Called when the game starts +void UCollisionCheckComponent::BeginPlay() +{ + Super::BeginPlay(); + excavator = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);; + mesh = parent->GetMesh(); +} + +void UCollisionCheckComponent::CollisionCheck() +{ + FVector socketLocation = mesh->GetSocketLocation("socket_bucketMiddle"); + FVector MeshUpVector = mesh->GetUpVector(); + FVector MeshForwardVector = mesh->GetForwardVector(); + float lengthDown = -160.0f; + float lengthForward = -220.0f; + FVector lineDown = MeshUpVector * lengthDown; + FVector lineFront = MeshForwardVector * lengthForward; + FVector endDown = socketLocation + lineDown; + FVector endForward = socketLocation + lineFront; + FHitResult OutHit = FHitResult(ForceInit); + FCollisionQueryParams collisionParams; + collisionParams.AddIgnoredActor(parent); + BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endDown, ECC_Visibility, collisionParams); + BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endForward, ECC_Visibility, collisionParams); + DrawDebugLine(GetWorld(), socketLocation, endDown, FColor::Red); + DrawDebugLine(GetWorld(), socketLocation, endForward, FColor::Red); +} + + +// Called every frame +void UCollisionCheckComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + CollisionCheck(); +} + diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h new file mode 100644 index 0000000..4a95ab8 --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h @@ -0,0 +1,39 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/SceneComponent.h" +#include "GameFramework/Actor.h" +#include "Components/SkeletalMeshComponent.h" +#include "ExcavatorCharacter.h" +#include "CollisionCheckComponent.generated.h" + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class EXCAVATORSIMULATOR_API UCollisionCheckComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UCollisionCheckComponent(); + +protected: + // Called when the game starts + virtual void BeginPlay() override; + + void CollisionCheck(); + +public: + // Called every frame + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")); + bool BucketCollidingFromDown; + + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Mesh", meta = (AllowPrivateAccess = "true")) + USkeletalMeshComponent* mesh; + + AExcavatorCharacter* excavator; + AActor* parent; +}; -- GitLab From bb2c86535c667614fcaa3c62501a0f5e747801f9 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 15 Nov 2022 12:39:59 +0200 Subject: [PATCH 012/121] Removed grab component and restructuring --- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_DiggingGround.uasset | 4 ++-- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- .../Content/Blueprints/BP_RockStartPoint.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- .../Content/Blueprints/BP_VrMainMenuCamera.uasset | 3 --- .../Content/Blueprints/Test_BP_Excavator.uasset | 4 ++-- .../Content/Blueprints/VR/BP_TestVRCharacter.uasset | 3 --- ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset | 3 +++ ExcavatorSimulator/Content/Blueprints/VR/Enum_Hands.uasset | 3 +++ ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset | 3 --- ExcavatorSimulator/Content/Blueprints/VR/GrabType.uasset | 3 --- .../Content/Blueprints/VR/Grabbable_SmallCube.uasset | 3 --- .../Blueprints/VR/HandAnimations/BPA_HandAnimations.uasset | 3 --- .../VR/HandAnimations/BPA_HandAnimationsLeft.uasset | 3 +++ .../VR/HandAnimations/BPA_HandAnimationsRight.uasset | 3 +++ .../Content/Blueprints/VR/HandModel/Enum_HandPosition.uasset | 3 --- .../Blueprints/VR/HandModel/Enum_HandPosition_Left.uasset | 3 +++ .../Blueprints/VR/HandModel/Enum_HandPosition_Right.uasset | 3 +++ .../Content/Blueprints/VR/HandModel/QK_CustomHand.uasset | 3 --- .../Content/Blueprints/VR/HandModel/QK_CustomHandLeft.uasset | 3 +++ .../Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset | 3 +++ .../Blueprints/VR/HandModel/QK_CustomHand_PhysicsAsset.uasset | 4 ++-- .../Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset | 4 ++-- .../Content/Blueprints/VR/VRInteractionBPI.uasset | 3 --- ExcavatorSimulator/Content/Data/BP_Savegame.uasset | 4 ++-- ExcavatorSimulator/Content/Data/Struct_SaveData.uasset | 2 +- 28 files changed, 45 insertions(+), 48 deletions(-) delete mode 100644 ExcavatorSimulator/Content/Blueprints/BP_VrMainMenuCamera.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/BP_TestVRCharacter.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/Enum_Hands.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/GrabType.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/Grabbable_SmallCube.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimations.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsLeft.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsRight.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Left.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Right.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandLeft.uasset create mode 100644 ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset delete mode 100644 ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index 8b1f5e2..40d08fb 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4bf372a3a918709abcfc99b42add64b9f8bee2070192224018229caef5765a06 -size 383786 +oid sha256:8be53bfa65cd710c1269dcb6bad7f485074f329d30157c8e2d5c6e14cae5777d +size 27817 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_DiggingGround.uasset b/ExcavatorSimulator/Content/Blueprints/BP_DiggingGround.uasset index 28cd084..5cdfc4b 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_DiggingGround.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_DiggingGround.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2590e5268a97d58adffa398d8cd36d19a620cd3e473456a79d10fdab96757ad -size 197150 +oid sha256:b745f8edd0aa9a39814fe504f6b3a49708e4f81f439f6539802d0f4380f97b05 +size 195912 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 718ded0..6f4c413 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f02ce8ce246c979b97888d85072e26009cec9a6da1f1391fa0b3dedeed39a321 -size 1052867 +oid sha256:d46ddc9d92118eaacbaf1bddd0b402f8b24522ebd525624a3a79aef36388b510 +size 1044933 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index 5560bcc..d84e209 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5406912e0d178d86acb6fc9e65d272aeadd795374efcddf40767eb9bdb5db299 -size 64756 +oid sha256:aa65285069ed405707fe9b7cf105a79571e9026298173c6115f52c664d938656 +size 41135 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset b/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset index 9bb7d8a..03aea72 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a10a07673d56cdb9ef77d3229c0a71d77ae5fec1fd2b253088efea26cbcd35b7 -size 253168 +oid sha256:01f9b34a88f9cd0ed3ef6cd4989bf6c8d2ddb8431d6fc9884f83f4e03167759d +size 218124 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index e902811..1109edc 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0714454bf7f8506e256c2970c7dd660168f8211a2bf12bfc1f4ba2911d1f466 -size 728148 +oid sha256:5aa57d290a4ce4967e118f8e33107ce84ea217d512a6df2eb3ec3d7c5e51bf11 +size 641126 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VrMainMenuCamera.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VrMainMenuCamera.uasset deleted file mode 100644 index 53b517a..0000000 --- a/ExcavatorSimulator/Content/Blueprints/BP_VrMainMenuCamera.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b8513465c88ae2e6565b4b2cb72aad74cd24f46fc6aebcb2815524085059be8 -size 123167 diff --git a/ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset index 0d04902..36518cb 100644 --- a/ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Blueprints/Test_BP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b09bae25b01c0ad04695defca8d076843846929f4896093f680a51bb632d9b9 -size 1117086 +oid sha256:9d23d9700e97b75d80b1311b765300689fa3d009e193a74783bf620d0345d34c +size 1115709 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/BP_TestVRCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/VR/BP_TestVRCharacter.uasset deleted file mode 100644 index a04afe0..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/BP_TestVRCharacter.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:829e1bc7a568635f94f1e740fe854b89a5a1ca1cdd147c65603a519fbfbccddd -size 75576 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset b/ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset new file mode 100644 index 0000000..c2287fd --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d732383275d381847cfecffa10721a3ee389aa207d398941742a6ed84b2ba8e9 +size 51262 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/Enum_Hands.uasset b/ExcavatorSimulator/Content/Blueprints/VR/Enum_Hands.uasset new file mode 100644 index 0000000..a3841a8 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/Enum_Hands.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c08e6050fc17f3d9893ae4039d292b2588147f9b7432e2f4a255a7a23d1cf256 +size 2200 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset b/ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset deleted file mode 100644 index 082d889..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/GrabComponent.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e78c3acf57c6860bdda78240b23483817f096c988c0ad3dbc1de15bb710c13c -size 291859 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/GrabType.uasset b/ExcavatorSimulator/Content/Blueprints/VR/GrabType.uasset deleted file mode 100644 index b762bae..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/GrabType.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e201c862405abb855a9a158cba148f289b5997d78956f3d7a2e36029ef3d8c17 -size 2840 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/Grabbable_SmallCube.uasset b/ExcavatorSimulator/Content/Blueprints/VR/Grabbable_SmallCube.uasset deleted file mode 100644 index 46a928d..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/Grabbable_SmallCube.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c3d004812f29e801a988a158a8c41fb4bac415030d2dd1c5200461e3124223fd -size 20178 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimations.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimations.uasset deleted file mode 100644 index 34124e2..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimations.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f4ffc9cc5019496bcf511878741ad1557bfd17a723c93a28fda6dd73fd24fe0 -size 178473 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsLeft.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsLeft.uasset new file mode 100644 index 0000000..af9d919 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d042407ded7d4e956ad6fb6383b9e2aee6d9b8d36eb429b81c67520133491bd +size 178702 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsRight.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsRight.uasset new file mode 100644 index 0000000..635b6e0 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandAnimations/BPA_HandAnimationsRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7df1540fc1bb579f1e81eb27d069169fe69d13eb2a11abd18042fcdd89dbcff8 +size 174040 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition.uasset deleted file mode 100644 index 3f6389c..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ae10429122916d1ff96f5107abe3d2443ec5fbd224290564e567e201e221038a -size 3442 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Left.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Left.uasset new file mode 100644 index 0000000..b0787b2 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Left.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c4fc4cce0c531ad73c751374482ed32e4eaf3bb2f65f37d7bfc95a717b8250c +size 3555 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Right.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Right.uasset new file mode 100644 index 0000000..2f93e5e --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/Enum_HandPosition_Right.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:122717e52036e0d96e46f91b40100984381b421a6f08ed410c47e37aaf2ed1cf +size 3528 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand.uasset deleted file mode 100644 index 85f143f..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1dc617f1ef187c84916b25881bcd75503ba44d270fab5ee845bec63d70ec31d9 -size 2127876 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandLeft.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandLeft.uasset new file mode 100644 index 0000000..59d29c6 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandLeft.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f15b8c53254768f52033fc50bbc900fb61aaf3120eced10d398e54c928af7be7 +size 2127924 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset new file mode 100644 index 0000000..a17eeea --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8e0331a5b51cce1d292ce7d8eba11c6828087e0a2ec2be4fde288b5e7f87926 +size 2127823 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_PhysicsAsset.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_PhysicsAsset.uasset index 0a5391c..4b014bd 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_PhysicsAsset.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_PhysicsAsset.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b64adcfa045e8daeab8c2b675399ff97d4aceb2ba4bf9d68fd76c8861c9c1169 -size 7563 +oid sha256:71ce0b40a577c70f73d6fef4a822dd91e19e7e84756f1a336a4ae7f9777f1859 +size 7570 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset index a123922..45ff7a0 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3813298c8800caf2bdafb219fb3450f95ae7b9bc05099bca951da693dc035126 -size 11519 +oid sha256:09cffddc1b29231cd6951656eaea99c24ef955c12be39fd520b394347f22f4a8 +size 11539 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset b/ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset deleted file mode 100644 index 9067e75..0000000 --- a/ExcavatorSimulator/Content/Blueprints/VR/VRInteractionBPI.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2b4ac4acbd6d21c6777b4c56ecba29c445df5975ef194c72d161473067393131 -size 19687 diff --git a/ExcavatorSimulator/Content/Data/BP_Savegame.uasset b/ExcavatorSimulator/Content/Data/BP_Savegame.uasset index a73166c..cda71a8 100644 --- a/ExcavatorSimulator/Content/Data/BP_Savegame.uasset +++ b/ExcavatorSimulator/Content/Data/BP_Savegame.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b336a70020768610bee0fa05a170c42b61eee31054e02265f22519793517af5 -size 13576 +oid sha256:6fc37cd25c6c8aea42eb4099ca2a74baff38e868e882e722959c536a9b5d48d1 +size 13945 diff --git a/ExcavatorSimulator/Content/Data/Struct_SaveData.uasset b/ExcavatorSimulator/Content/Data/Struct_SaveData.uasset index 5c6ce6e..67505a6 100644 --- a/ExcavatorSimulator/Content/Data/Struct_SaveData.uasset +++ b/ExcavatorSimulator/Content/Data/Struct_SaveData.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e14b4b59a383132d6da4cdd1d69e9a5a1c85c08e22e64ef448843a48860f0f9 +oid sha256:ba1eb30c2a728447e78ffc0b0076ed117f28cc0e65db1ff3885f19484706a22c size 10536 -- GitLab From 32ec6773325c0d48a75d472d8248871bf43014af Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 12:58:58 +0200 Subject: [PATCH 013/121] Add socket to Skeleton --- .../ExcavatorParts/Skeletons/Skeleton_Excavator.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset index 4205a4a..7075c66 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7654aad213ff12bd08cc13bd1840d7ef79dd33a2ff80b7ea1435ba78978c3fb -size 13362 +oid sha256:7740d685808e5b284ca37a4e221a8091958af9fd681b7f3a22c2a9040fafcd0b +size 13650 -- GitLab From ead424c0920ca4c2a6f1150675d78026b0b5d106 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 12:59:23 +0200 Subject: [PATCH 014/121] Modified TestLevel_Tero - Added things to collide with to the level --- ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap index 3915c53..cbe335d 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16590491b1739569e355d5380e21642c7c8afec120a385f98721e7bffab69b33 -size 46098 +oid sha256:d90649002af974dfc247a08a7ea395bce2ce40b7e599fbeb7c2538c22e58c40a +size 55030 -- GitLab From 376b6f00384551b4db60230351a8d092b1b7eb0b Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 13:00:15 +0200 Subject: [PATCH 015/121] Modified CollisionCheckComponent - Now checks collisions from bucket: - Front - Left - Right - Down --- .../CollisionCheckComponent.cpp | 78 ++++++++++++++++--- .../CollisionCheckComponent.h | 39 +++++++--- 2 files changed, 93 insertions(+), 24 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp index e34a5a7..9e0028f 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp @@ -3,7 +3,7 @@ #include "CollisionCheckComponent.h" #include "Engine/World.h" - +#include "ExcavatorCharacter.h" // Sets default values for this component's properties UCollisionCheckComponent::UCollisionCheckComponent() @@ -11,37 +11,91 @@ UCollisionCheckComponent::UCollisionCheckComponent() // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; + BucketCollidingFromDown = false; + BucketCollidingFromFront = false; + BucketCollidingFromLeft = false; + BucketCollidingFromRight = false; + character = nullptr; + mesh = nullptr; } - // Called when the game starts void UCollisionCheckComponent::BeginPlay() { Super::BeginPlay(); - excavator = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);; - mesh = parent->GetMesh(); + // @TODO: Modify this to make it possible to be any character + //character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); + //mesh = character->GetMesh(); + character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); + mesh = character->GetMesh(); +} + +void UCollisionCheckComponent::IsColliding(EOutputs& OutputPins, bool rotatingRight = false, bool moveUp = false) +{ + if (rotatingRight) + { + if (BucketCollidingFromLeft || BucketCollidingFromRight) + { + OutputPins = EOutputs::Colliding; + return; + } + OutputPins = EOutputs::NotColliding; + return; + } + else if (moveUp) + { + if (BucketCollidingFromDown || BucketCollidingFromFront) + { + OutputPins = EOutputs::Colliding; + return; + } + } + OutputPins = EOutputs::NotColliding; + return; } void UCollisionCheckComponent::CollisionCheck() { + FHitResult OutHit = FHitResult(ForceInit); + FCollisionQueryParams collisionParams; + collisionParams.AddIgnoredActor(character); FVector socketLocation = mesh->GetSocketLocation("socket_bucketMiddle"); + + // Line trace down FVector MeshUpVector = mesh->GetUpVector(); - FVector MeshForwardVector = mesh->GetForwardVector(); float lengthDown = -160.0f; - float lengthForward = -220.0f; FVector lineDown = MeshUpVector * lengthDown; - FVector lineFront = MeshForwardVector * lengthForward; FVector endDown = socketLocation + lineDown; - FVector endForward = socketLocation + lineFront; - FHitResult OutHit = FHitResult(ForceInit); - FCollisionQueryParams collisionParams; - collisionParams.AddIgnoredActor(parent); BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endDown, ECC_Visibility, collisionParams); - BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endForward, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endDown, FColor::Red); + + // Line trace forward + FVector MeshForwardVector = mesh->GetForwardVector(); + float lengthForward = -220.0f; + FVector lineFront = MeshForwardVector * lengthForward; + FVector endForward = socketLocation + lineFront; + BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endForward, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endForward, FColor::Red); + + // Line trace Left + FVector MeshRightVector = mesh->GetRightVector(); + FVector lineLeft = MeshRightVector * lengthDown * (-1); + FVector endLeft = socketLocation + lineLeft; + BucketCollidingFromLeft = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endLeft, ECC_Visibility, collisionParams); + DrawDebugLine(GetWorld(), socketLocation, endLeft, FColor::Red); + + // Line trace Right + FVector lineRight = MeshRightVector * lengthDown; + FVector endRight = socketLocation + lineRight; + BucketCollidingFromRight = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endRight, ECC_Visibility, collisionParams); + DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Red); } +//void UCollisionCheckComponent::Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront) +//{ +// +//} + // Called every frame void UCollisionCheckComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h index 4a95ab8..9974b45 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h @@ -4,36 +4,51 @@ #include "CoreMinimal.h" #include "Components/SceneComponent.h" -#include "GameFramework/Actor.h" #include "Components/SkeletalMeshComponent.h" -#include "ExcavatorCharacter.h" +#include "GameFramework/Actor.h" #include "CollisionCheckComponent.generated.h" +UENUM(BlueprintType) +enum class EOutputs : uint8 +{ + Colliding, + NotColliding +}; + UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class EXCAVATORSIMULATOR_API UCollisionCheckComponent : public USceneComponent { GENERATED_BODY() -public: - // Sets default values for this component's properties - UCollisionCheckComponent(); - protected: // Called when the game starts virtual void BeginPlay() override; +public: + // Sets default values for this component's properties + UCollisionCheckComponent(); + void CollisionCheck(); + //void Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront); + UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) + void IsColliding(EOutputs& OutputPins, bool rotatingRight, bool moveUp); -public: +private: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) bool BucketCollidingFromDown; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) + bool BucketCollidingFromFront; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) + bool BucketCollidingFromLeft; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) + bool BucketCollidingFromRight; - //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Mesh", meta = (AllowPrivateAccess = "true")) USkeletalMeshComponent* mesh; - - AExcavatorCharacter* excavator; - AActor* parent; + //TArray<FString> socketNames; + ACharacter* character; + //AActor* parent; + //FHitResult OutHit; }; -- GitLab From c3d42ffe4b6e5df848d0e0191c075454a9ca46e0 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 13:16:00 +0200 Subject: [PATCH 016/121] Modified DefaultInput - Add zoom out of character when pressing F --- ExcavatorSimulator/Config/DefaultInput.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/ExcavatorSimulator/Config/DefaultInput.ini b/ExcavatorSimulator/Config/DefaultInput.ini index d0e3054..2f9cb36 100644 --- a/ExcavatorSimulator/Config/DefaultInput.ini +++ b/ExcavatorSimulator/Config/DefaultInput.ini @@ -116,6 +116,7 @@ DoubleClickTime=0.200000 +ActionMappings=(ActionName="PauseGame",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=P) +ActionMappings=(ActionName="RestartLevel",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=R) +ActionMappings=(ActionName="RestartLevel",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=SpaceBar) ++ActionMappings=(ActionName="ZoomOut",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=F) +AxisMappings=(AxisName="Oculus Touch (L) X-Axis",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_X) +AxisMappings=(AxisName="GrabAxisLeft",Scale=1.000000,Key=OculusTouch_Left_Grip_Axis) +AxisMappings=(AxisName="Oculus Touch (L) Y-Axis",Scale=-1.000000,Key=OculusTouch_Left_Thumbstick_Y) -- GitLab From 219946e58563b63f2897b276c4d44291b4798269 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 13:17:04 +0200 Subject: [PATCH 017/121] Modified BP_ExcavatorCharacter - Added collision component to character --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 6f4c413..9e0cb08 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d46ddc9d92118eaacbaf1bddd0b402f8b24522ebd525624a3a79aef36388b510 -size 1044933 +oid sha256:7d05d643f4e910772bf731b453c9489d2294269ba360930c84512435c6afb0a6 +size 1083834 -- GitLab From 65fe298b717d1d36877d79c307da93641c3dd6a0 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 13:17:44 +0200 Subject: [PATCH 018/121] Added F input to Controller --- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 1109edc..bb4de23 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5aa57d290a4ce4967e118f8e33107ce84ea217d512a6df2eb3ec3d7c5e51bf11 -size 641126 +oid sha256:a758add4580a3ca78501c43e67d91cc27b9cb29fa220c289f7120653483187d3 +size 656410 -- GitLab From 499b000f0c26edf55c942ea9947c8ec1138ee08d Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 15 Nov 2022 15:37:56 +0200 Subject: [PATCH 019/121] Added grab handlers --- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset | 4 ++-- .../Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index 40d08fb..f311ac9 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8be53bfa65cd710c1269dcb6bad7f485074f329d30157c8e2d5c6e14cae5777d -size 27817 +oid sha256:21341e9de9651a82f1eacc56bd81ab7cdb68a76e57c88239aa9dcdfda042d32b +size 24873 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 9e0cb08..6c2b592 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d05d643f4e910772bf731b453c9489d2294269ba360930c84512435c6afb0a6 -size 1083834 +oid sha256:0c1c2b109c85b6c99db5b0cc4bd912eda4cf0fc772d1699513c283d5e870c881 +size 1088363 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index d84e209..9d84f14 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa65285069ed405707fe9b7cf105a79571e9026298173c6115f52c664d938656 -size 41135 +oid sha256:a35c44ebc754764d6533c8e9bad99408f7a61d40e410edd5138e44f195875463 +size 119717 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index bb4de23..9526966 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a758add4580a3ca78501c43e67d91cc27b9cb29fa220c289f7120653483187d3 -size 656410 +oid sha256:156a0ad0acb3d57ac99748fda72b01c0f5c01460a4730c3d7183cdb03a04bdb7 +size 822513 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset b/ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset index c2287fd..23c2f83 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/BP_VRHands.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d732383275d381847cfecffa10721a3ee389aa207d398941742a6ed84b2ba8e9 -size 51262 +oid sha256:d17e9f3326699177848ee3aab1e2eacae21de22353eb4414550639c215fe3fea +size 53479 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset index a17eeea..3b4bc4b 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHandRight.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8e0331a5b51cce1d292ce7d8eba11c6828087e0a2ec2be4fde288b5e7f87926 +oid sha256:0c425506467fa340b5c1b8852a9ab23cb17862ac802db1dea15243dc171387c2 size 2127823 -- GitLab From 83c482d5c314a8204e38cb0afa822421a36149df Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 15:47:03 +0200 Subject: [PATCH 020/121] Modified CollisionCheckComponent --- .../CollisionCheckComponent.cpp | 35 ++++++++++++------- .../CollisionCheckComponent.h | 7 ++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp index 9e0028f..84609e2 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp @@ -24,25 +24,28 @@ void UCollisionCheckComponent::BeginPlay() { Super::BeginPlay(); // @TODO: Modify this to make it possible to be any character - //character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); - //mesh = character->GetMesh(); character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); mesh = character->GetMesh(); + + //TArray< AActor* > tempChildActors; + //character->GetAllChildActors(tempChildActors, true); + //tempChildActors.FindItemByClass<>() } -void UCollisionCheckComponent::IsColliding(EOutputs& OutputPins, bool rotatingRight = false, bool moveUp = false) +void UCollisionCheckComponent::IsCollidingLR(EOutputs& OutputPins, bool rotating = false) { - if (rotatingRight) + if (BucketCollidingFromLeft || BucketCollidingFromRight) { - if (BucketCollidingFromLeft || BucketCollidingFromRight) - { - OutputPins = EOutputs::Colliding; - return; - } - OutputPins = EOutputs::NotColliding; + OutputPins = EOutputs::Colliding; return; } - else if (moveUp) + OutputPins = EOutputs::NotColliding; + return; +} + +void UCollisionCheckComponent::IsCollidingFB(EOutputs& OutputPins, bool movingForward = false) +{ + if (movingForward) { if (BucketCollidingFromDown || BucketCollidingFromFront) { @@ -54,6 +57,7 @@ void UCollisionCheckComponent::IsColliding(EOutputs& OutputPins, bool rotatingRi return; } +//@TODO: ota anim instance skeletal meshistä void UCollisionCheckComponent::CollisionCheck() { FHitResult OutHit = FHitResult(ForceInit); @@ -62,9 +66,8 @@ void UCollisionCheckComponent::CollisionCheck() FVector socketLocation = mesh->GetSocketLocation("socket_bucketMiddle"); // Line trace down - FVector MeshUpVector = mesh->GetUpVector(); float lengthDown = -160.0f; - FVector lineDown = MeshUpVector * lengthDown; + FVector lineDown = GetUpVector() * lengthDown; FVector endDown = socketLocation + lineDown; BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endDown, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endDown, FColor::Red); @@ -77,6 +80,12 @@ void UCollisionCheckComponent::CollisionCheck() BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endForward, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endForward, FColor::Red); + // Line trace backward + /*FVector lineBack = MeshForwardVector * lengthForward * (-1); + FVector endBack = socketLocation + lineBack; + BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endBack, ECC_Visibility, collisionParams); + DrawDebugLine(GetWorld(), socketLocation, endBack, FColor::Red);*/ + // Line trace Left FVector MeshRightVector = mesh->GetRightVector(); FVector lineLeft = MeshRightVector * lengthDown * (-1); diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h index 9974b45..7c6e308 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h @@ -31,7 +31,9 @@ public: void CollisionCheck(); //void Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront); UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) - void IsColliding(EOutputs& OutputPins, bool rotatingRight, bool moveUp); + void IsCollidingLR(EOutputs& OutputPins, bool rotating); + UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) + void IsCollidingFB(EOutputs& OutputPins, bool moving); private: // Called every frame @@ -47,8 +49,5 @@ private: bool BucketCollidingFromRight; USkeletalMeshComponent* mesh; - //TArray<FString> socketNames; ACharacter* character; - //AActor* parent; - //FHitResult OutHit; }; -- GitLab From 8118103d4440ad1d36b09a419b25ab79db824c2c Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 15 Nov 2022 15:47:30 +0200 Subject: [PATCH 021/121] Modified ExcavatorCharacter --- .../Source/ExcavatorSimulator/ExcavatorCharacter.cpp | 1 + .../Source/ExcavatorSimulator/ExcavatorCharacter.h | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 6c707bf..c7ecc8b 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -8,6 +8,7 @@ AExcavatorCharacter::AExcavatorCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; + collisionComponent = CreateDefaultSubobject<UCollisionCheckComponent>(TEXT("Collision Component")); } // Called every frame diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 1cbe147..7efe5d4 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -6,6 +6,7 @@ #include "GameFramework/Character.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/InputSettings.h" +#include "CollisionCheckComponent.h" #include "ExcavatorCharacter.generated.h" UCLASS() @@ -17,6 +18,8 @@ public: /** Sets default values for this character's properties. */ AExcavatorCharacter(); + UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly) + class UCollisionCheckComponent* collisionComponent; /** Called every frame. */ virtual void Tick(float DeltaTime) override; @@ -30,7 +33,6 @@ protected: virtual void BeginPlay() override; private: - UInputSettings* Inputsettings = UInputSettings::GetInputSettings(); - + }; -- GitLab From b9aa882b41d3df9656eeb2067ec31cc12db31a0c Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 15 Nov 2022 15:51:48 +0200 Subject: [PATCH 022/121] Delete thirdparty things, marching cubes component --- .../Binaries/Win64/libnoise.dll | 3 - .../Content/Levels/TestLevel_Niko.umap | 4 +- .../ExcavatorSimulator.Build.cs | 29 - .../GroundDeformerComponent.cpp | 28 +- .../ExcavatorSimulator/MCubeComponent.cpp | 236 -------- .../ExcavatorSimulator/MCubeComponent.h | 569 ------------------ .../PerlinNoiseComponent.cpp | 161 ----- .../ExcavatorSimulator/PerlinNoiseComponent.h | 103 ---- .../ExcavatorSimulator/ProceduralCube.cpp | 62 -- .../ExcavatorSimulator/ProceduralCube.h | 82 --- .../thirdParty/LibNoise/Includes/basictypes.h | 66 -- .../thirdParty/LibNoise/Includes/exception.h | 74 --- .../thirdParty/LibNoise/Includes/interp.h | 112 ---- .../thirdParty/LibNoise/Includes/latlon.h | 52 -- .../thirdParty/LibNoise/Includes/libnoise.h | 74 --- .../thirdParty/LibNoise/Includes/mathconsts.h | 60 -- .../thirdParty/LibNoise/Includes/misc.h | 97 --- .../LibNoise/Includes/model/cylinder.h | 131 ---- .../thirdParty/LibNoise/Includes/model/line.h | 198 ------ .../LibNoise/Includes/model/model.h | 31 - .../LibNoise/Includes/model/plane.h | 121 ---- .../LibNoise/Includes/model/sphere.h | 131 ---- .../thirdParty/LibNoise/Includes/module/abs.h | 76 --- .../thirdParty/LibNoise/Includes/module/add.h | 77 --- .../LibNoise/Includes/module/billow.h | 277 --------- .../LibNoise/Includes/module/blend.h | 144 ----- .../LibNoise/Includes/module/cache.h | 118 ---- .../LibNoise/Includes/module/checkerboard.h | 81 --- .../LibNoise/Includes/module/clamp.h | 152 ----- .../LibNoise/Includes/module/const.h | 111 ---- .../LibNoise/Includes/module/curve.h | 197 ------ .../LibNoise/Includes/module/cylinders.h | 129 ---- .../LibNoise/Includes/module/displace.h | 259 -------- .../LibNoise/Includes/module/exponent.h | 119 ---- .../LibNoise/Includes/module/invert.h | 75 --- .../thirdParty/LibNoise/Includes/module/max.h | 76 --- .../thirdParty/LibNoise/Includes/module/min.h | 76 --- .../LibNoise/Includes/module/module.h | 55 -- .../LibNoise/Includes/module/modulebase.h | 366 ----------- .../LibNoise/Includes/module/multiply.h | 76 --- .../LibNoise/Includes/module/perlin.h | 359 ----------- .../LibNoise/Includes/module/power.h | 80 --- .../LibNoise/Includes/module/ridgedmulti.h | 313 ---------- .../LibNoise/Includes/module/rotatepoint.h | 233 ------- .../LibNoise/Includes/module/scalebias.h | 151 ----- .../LibNoise/Includes/module/scalepoint.h | 212 ------- .../LibNoise/Includes/module/select.h | 267 -------- .../LibNoise/Includes/module/spheres.h | 127 ---- .../LibNoise/Includes/module/terrace.h | 242 -------- .../LibNoise/Includes/module/translatepoint.h | 223 ------- .../LibNoise/Includes/module/turbulence.h | 269 --------- .../LibNoise/Includes/module/voronoi.h | 246 -------- .../thirdParty/LibNoise/Includes/noisegen.h | 208 ------- .../LibNoise/Includes/vectortable.h | 290 --------- .../LibNoise/Libraries/libnoise.x64.lib | Bin 121304 -> 0 bytes .../LibNoise/Libraries/libnoise.x86.lib | Bin 121304 -> 0 bytes 56 files changed, 11 insertions(+), 8097 deletions(-) delete mode 100644 ExcavatorSimulator/Binaries/Win64/libnoise.dll delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.cpp delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.h delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.cpp delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.h delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.cpp delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/basictypes.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/exception.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/interp.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/latlon.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/libnoise.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/mathconsts.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/misc.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/model/cylinder.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/model/line.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/model/model.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/model/plane.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/model/sphere.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/abs.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/add.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/billow.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/blend.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cache.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/checkerboard.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/clamp.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/const.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/curve.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cylinders.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/displace.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/exponent.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/invert.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/max.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/min.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/module.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/modulebase.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/multiply.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/perlin.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/power.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/ridgedmulti.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/rotatepoint.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalebias.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalepoint.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/select.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/spheres.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/terrace.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/translatepoint.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/turbulence.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/module/voronoi.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/noisegen.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Includes/vectortable.h delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Libraries/libnoise.x64.lib delete mode 100644 ExcavatorSimulator/thirdParty/LibNoise/Libraries/libnoise.x86.lib diff --git a/ExcavatorSimulator/Binaries/Win64/libnoise.dll b/ExcavatorSimulator/Binaries/Win64/libnoise.dll deleted file mode 100644 index 8f7baf9..0000000 --- a/ExcavatorSimulator/Binaries/Win64/libnoise.dll +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8492972cf116d1fbf850049fb6d058fc3e7cb0d158d8ce13df7e99f07cdbb7ba -size 1354752 diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap index cea4286..e53ff32 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbf361588ce83603fcfcb2de9edc4bd4ba3fd7c8e3269c0cd0ac6495205d2913 -size 31555 +oid sha256:2ae3b6f1b7728efd1683d309b6f3086b0857d1ac5a09eec038aa1c74d58f58aa +size 32376 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorSimulator.Build.cs b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorSimulator.Build.cs index f7993f8..d646c09 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorSimulator.Build.cs +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorSimulator.Build.cs @@ -24,34 +24,5 @@ public class ExcavatorSimulator : ModuleRules // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true // All the default constructor stuff - LoadLib(Target, "LibNoise", "LIB_NOISE"); // LibNoise is the name of the folder where you copied the files previously } - - public bool LoadLib(ReadOnlyTargetRules Target, string libPath, string libName) - { - bool isLibrarySupported = false; - - if (Target.Platform == UnrealTargetPlatform.Win64) - { - isLibrarySupported = true; - string PlatformString = "x64"; - string LibrariesPath = Path.Combine(ThirdPartyPath, libPath, "Libraries"); - - PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "LibNoise." + PlatformString + ".lib")); - PublicIncludePaths.Add(Path.Combine(ThirdPartyPath, libPath, "Includes")); - } - PublicDefinitions.Add(string.Format("WITH_" + libName + "_BINDING={0}", isLibrarySupported ? 1 : 0)); - return isLibrarySupported; - } - - private string ThirdPartyPath - { - get { return Path.GetFullPath(Path.Combine(ModulePath, "../../ThirdParty/")); } - } - - private string ModulePath - { - get { return ModuleDirectory; } - } - } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp index a3d6ad8..d3a6dad 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp @@ -5,6 +5,7 @@ #include <Voxel/Public/VoxelTools/VoxelSurfaceTools.h> #include <Voxel/Public/VoxelDebug/VoxelDebugUtilities.h> #include <Voxel/Public/VoxelTools/Gen/VoxelSurfaceEditTools.h> +#include <Voxel/Public/VoxelTools/VoxelProjectionTools.h> #include "WorldGenerator.h" #include "GroundDeformerComponent.h" @@ -15,7 +16,7 @@ UGroundDeformerComponent::UGroundDeformerComponent() PrimaryComponentTick.bCanEverTick = false; RemoveSphereRadius = 100.0f; - ForwardVectorMultiplier = 50.0f; + ForwardVectorMultiplier = 250.0f; } void UGroundDeformerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) @@ -26,7 +27,8 @@ void UGroundDeformerComponent::TickComponent(float DeltaTime, ELevelTick TickTyp void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) { FHitResult hit = FHitResult(ForceInit); - + TArray<FVoxelProjectionHit> voxelHit; + //GetComponentLocation() is WorldLocation, GetForwardVctor() is ForwardVector. FVector EndVector = Mesh->GetComponentLocation() + (Mesh->GetForwardVector() * ForwardVectorMultiplier); @@ -48,23 +50,11 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) //UVoxelSphereTools::SmoothSphere(VoxelWorldREF, Mesh->GetComponentLocation(), 140.0f, 0.5f); - if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) - { - DrawDebugLine(GetWorld(), Mesh->GetComponentLocation(), EndVector, FColor::Red, false, 5.0f, ECC_WorldStatic, 1.0f); - - if (hit.GetActor() == UGameplayStatics::GetActorOfClass(GetWorld(), ClassToFind)) - { - //UVoxelSphereTools::RemoveSphere(VoxelWorldREF, hit.ImpactPoint, RemoveSphereRadius); - - FVoxelIntBox BoundsToDoEditsIn = UVoxelBlueprintLibrary::MakeIntBoxFromGlobalPositionAndRadius(VoxelWorldREF, hit.ImpactPoint, RemoveSphereRadius * 1.6f); - //UVoxelBoxTools::RemoveBox(VoxelWorldREF, BoundsToDoEditsIn); - - if (GEngine) - { - GEngine->AddOnScreenDebugMessage(1, 2.0f, FColor::Red, FString::Printf(TEXT("Jumala"))); - } - } - } + //if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) + //{ + ///* DrawDebugLine(GetWorld(), Mesh->GetComponentLocation(), EndVector, FColor::Red, false, 5.0f, ECC_WorldStatic, 1.0f); + // UVoxelProjectionTools::FindProjectionVoxels(voxelHit, VoxelWorldREF,Parameters,hit.ImpactPoint,Mesh->GetForwardVector(), 10.0f);*/ + //} } void UGroundDeformerComponent::BeginPlay() diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.cpp deleted file mode 100644 index ec926f5..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.cpp +++ /dev/null @@ -1,236 +0,0 @@ -#include "MCubeComponent.h" - - -UMCubeComponent::UMCubeComponent() -{ - PrimaryComponentTick.bCanEverTick = true; - - BoxLength = 50.f; - Threshold = 0.5f; - Scale = 200.f; - - XScale = 30; - ZScale = 30; - YScale = 30; -} - - -void UMCubeComponent::GenerateMarchingCubeMesh(int X_Iterations, int Y_Iterations, int Z_Iterations, float _BoxLength) -{ - - ///* Making sure we wouldn't crash the game */ - //if (Mesh != nullptr) - //{ - // return; - //} - - /* Setting the variables */ - XScale = X_Iterations; - YScale = Y_Iterations; - ZScale = Z_Iterations; - BoxLength = _BoxLength; - - /* Generating all the arrays */ - GenerateMeshData(); - /* Generating the mesh based on the arrays */ - GenerateMesh(); -} - -void UMCubeComponent::GenerateMeshData() -{ - for (int32 y = 0; y < YScale; y++) - { - for (int32 x = 0; x < XScale; x++) - { - for (int32 z = 0; z < ZScale; z++) - { - /* Creating an array for vertices of the current cube */ - FVector4 Points[8]; - /* Filling this array with empty values to not crash the game just in case */ - for (int i = 0; i < 8; i++) - { - Points[i] = FVector4(); - } - /* Creates the index for the triangulation table and filling in Points array*/ - const int Index = GetTriangulationIndexForCube(x, y, z, Points); - - /* Cube is entirely in/out of the surface */ - if (edgeTable[Index] != 0) - { - for (int32 i = 0; triangulation[Index][i] != -1; i += 3) - { - /* Gets the vertices for the configuration */ - const int A0 = cornerIndexAFromEdge[triangulation[Index][i]]; - const int B0 = cornerIndexBFromEdge[triangulation[Index][i]]; - - const int A1 = cornerIndexAFromEdge[triangulation[Index][i + 1]]; - const int B1 = cornerIndexBFromEdge[triangulation[Index][i + 1]]; - - const int A2 = cornerIndexAFromEdge[triangulation[Index][i + 2]]; - const int B2 = cornerIndexBFromEdge[triangulation[Index][i + 2]]; - - /* Creates actual vertices on the edges of the cube*/ - FVector FirstVertice = InterpolateVerts(Points[A2], Points[B2]); - FVector SecondVertice = InterpolateVerts(Points[A1], Points[B1]); - FVector ThirdVertice = InterpolateVerts(Points[A0], Points[B0]); - - /* Creates indexes for triangles array */ - int FirstVerticeIndex = Vertices.Add(FirstVertice); - int SecondVerticeIndex = Vertices.Add(SecondVertice); - int ThirdVerticeIndex = Vertices.Add(ThirdVertice); - - /* Triangles array */ - Triangles.Add(FirstVerticeIndex); - Triangles.Add(SecondVerticeIndex); - Triangles.Add(ThirdVerticeIndex); - - /* Creates basic UVs */ - /* We do it 3 times cuz it should be correlate wth indexes of Vertices array */ - UV.Add(FVector2D(x, y)); - UV.Add(FVector2D(x, y)); - UV.Add(FVector2D(x, y)); - - /* Creates basic normals */ - /* If you want to smooth the mesh out you would probably need to find an average of adjacent normals or something like that I dunno, google it */ - /* We do it 3 times cuz it should be correlate wth indexes of Vertices array */ - const FVector Normal = FVector::CrossProduct(FirstVertice - ThirdVertice, - SecondVertice - ThirdVertice).GetSafeNormal(); - /*Normals.Add(Normal); - Normals.Add(Normal); - Normals.Add(Normal);*/ - - /* Basic tangets */ - /* We do it 3 times cuz it should be correlate wth indexes of Vertices array */ - /*const FVector Tanget = (FirstVertice - SecondVertice).GetSafeNormal2D(); - Tangents.Add(Tanget); - Tangents.Add(Tanget); - Tangents.Add(Tanget);*/ - } - } - } - } - } -} - -FVector UMCubeComponent::InterpolateVerts(FVector4& FirstCorner, FVector4& SecondCorner) -{ - /* Math shit */ - const float t = (Threshold - FirstCorner.W) / (SecondCorner.W - FirstCorner.W); - return FVector(FirstCorner + t * (SecondCorner - FirstCorner)) * BoxLength; -} - -int UMCubeComponent::GetTriangulationIndexForCube(int x, int y, int z, FVector4(&PointsOut)[8]) -{ - int Index = 0; - // Iterating through each vertice(clockwise aka 0-X-XY-Y-Z-XZ-XYZ-YZ) of the cube - // XYZ are the indexes(not world/relative position!!) of the first point of the cube (0) - // For each vertice we find the Noise Value - // In case if the vertice is valid (NoiseValue < Threshold) setting 1 to the bit of the vertice to Index(8 bits) - // EX: 3 and 6 vertices are valid ---> Index = 01001000 - // 4--------5 - // /| /| - // / | / | Z - // 7--------6 | | - // | | | | | - // | 0-----|--1 []-------X - // | / | / / - // |/ |/ / - // 3--------2 Y - - // 0 - float Value = GetNoiseValueForCoordinates(x, y, z); - PointsOut[0] = FVector4(x, y, z, Value); - if (Value < Threshold) - { - Index |= 1; - } - // 1 - Value = GetNoiseValueForCoordinates(x + 1, y, z); - PointsOut[1] = FVector4(x + 1, y, z, Value); - if (Value < Threshold) - { - Index |= 2; - } - // 2 - Value = GetNoiseValueForCoordinates(x + 1, y + 1, z); - PointsOut[2] = FVector4(x + 1, y + 1, z, Value); - if (Value < Threshold) - { - Index |= 4; - } - // 3 - Value = GetNoiseValueForCoordinates(x, y + 1, z); - PointsOut[3] = FVector4(x, y + 1, z, Value); - if (Value < Threshold) - { - Index |= 8; - } - // 4 - Value = GetNoiseValueForCoordinates(x, y, z + 1); - PointsOut[4] = FVector4(x, y, z + 1, Value); - if (Value < Threshold) - { - Index |= 16; - } - // 5 - Value = GetNoiseValueForCoordinates(x + 1, y, z + 1); - PointsOut[5] = FVector4(x + 1, y, z + 1, Value); - if (Value < Threshold) - { - Index |= 32; - } - // 6 - Value = GetNoiseValueForCoordinates(x + 1, y + 1, z + 1); - PointsOut[6] = FVector4(x + 1, y + 1, z + 1, Value); - if (Value < Threshold) - { - Index |= 64; - } - // 7 - Value = GetNoiseValueForCoordinates(x, y + 1, z + 1); - PointsOut[7] = FVector4(x, y + 1, z + 1, Value); - if (Value < Threshold) - { - Index |= 128; - } - - return Index; -} - -void UMCubeComponent::GenerateMesh() -{ - /* Making sure it wouldn't crash the game */ - if (Mesh == nullptr) - { - return; - } - Mesh->CreateMeshSection(0,Vertices,Triangles,Normals,UV,VertexColors,Tangents,true); -} - - -float UMCubeComponent::GetNoiseValueForCoordinates(int x, int y, int z) -{ - /* Filling in the borders (or the border would be empty and it would look odd as shit) */ - if (x == 0 || x >= XScale || y == 0 || y >= YScale || z == 0 || z >= ZScale) - { - return 0.f; - } - - float Res = Noise->GetValue( - (x * NoiseInputScale), - (y * NoiseInputScale), - (z * NoiseInputScale) - ); - - return Res * Scale; -} - -void UMCubeComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); -} - -void UMCubeComponent::BeginPlay() -{ - Super::BeginPlay(); -} diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.h deleted file mode 100644 index dd68c2b..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/MCubeComponent.h +++ /dev/null @@ -1,569 +0,0 @@ -#pragma once - -#include "GameFramework/Actor.h" -#include "CoreMinimal.h" -#include "Components/ActorComponent.h" -#include "PerlinNoiseComponent.h" -#include "ProceduralMeshComponent.h" -#include "Components/ActorComponent.h" -#include "MCubeComponent.generated.h" - -/** - * Component to creates a rectangular shape object filled of marching cubes - * @warning requires UPerlinNoiseComponent and URuntimeMeshComponentStatic (Noise and RuntimeMesh) to be set before using this component - */ -UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) -class UMCubeComponent : public UActorComponent -{ - GENERATED_BODY() - -public: - UMCubeComponent(); - - // ----------------------------------------------------------------------------------------------------------- - // NEEDED COMPONENTS - // ----------------------------------------------------------------------------------------------------------- - /** - * Noise component to get values for each 3D point - * @warning should be set in order for this component to work - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - UPerlinNoiseComponent* Noise; - - /** - * Runtime mesh component for creating a mesh - * @warning should be set in order for this component to work - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - UProceduralMeshComponent* Mesh; - - // ----------------------------------------------------------------------------------------------------------- - // MAIN METHOD - // ----------------------------------------------------------------------------------------------------------- - - /** - * Main method to generate a 3D perlin noise via marching cubes - * It will create a rectangular shape object filled of marching cubes - * Before calling this method make sure that you configure the PerlinNoise and called SetRandomSeed() by it - * The volume of the mesh is (X * Y * Z) * BoxLength - * @param X_Iterations - amount of cubes along x axis - * @param Y_Iterations - amount of cubes along y axis - * @param Y_Iterations - amount of cubes along z axis - * @param _BoxLength - Length of each cube's edges - * @warning This method requires UPerlinNoiseComponent and URuntimeMeshComponentStatic to be set as Noise and RuntimeMesh accordingly - */ - UFUNCTION(BlueprintCallable) - void GenerateMarchingCubeMesh(int X_Iterations, int Y_Iterations, int Z_Iterations, float _BoxLength); - - // ----------------------------------------------------------------------------------------------------------- - // MESH GENERATION - // ----------------------------------------------------------------------------------------------------------- - - /** - * Main method to generate mesh related data - * GENERATES : VERTICES, TRIANGLES, UVS, NORMALS, TANGENTS - * Itterates through all XYZ scales to calculate every cube - * Creates index for the cube(by GetTriangulationIndexForCube() method) - * If the index is valid, gets the configuration and using it to create vertices, triangles, uvs, normals and tangetns for this cube - */ - UFUNCTION(BlueprintCallable) - void GenerateMeshData(); - - /** - * Last method to invoke - * Generates mesh in RuntimeMesh by using following arrays: Vertices, Triangles, Normals, Tangents, UV, VertesColors - * @warning requires URuntimeMeshComponentStatic - * @warning currently(07/01/2021) there is a bug of replacing the mesh in RuntimeMeshComponent see (https://github.com/TriAxis-Games/RuntimeMeshComponent/issues/194) - */ - UFUNCTION(BlueprintCallable) - void GenerateMesh(); - - // ----------------------------------------------------------------------------------------------------------- - // CUBE GENERATION - // ----------------------------------------------------------------------------------------------------------- - - /** - * Okay this one is a lil bit difficult - * Read this article first - http://paulbourke.net/geometry/polygonise/ - * There is a table triangulation of all posible configuration of the cube (2^8 or 256 in total) - * To get the configuration for a cube we need to iterate among all of 8 vertices of the cube and get the noise value for each one(to get a noise value we use GetNoiseValueForCoordinates()) - * If noise value is below Threshold than we consider this vertice as valid - * If the vertice is valid, we should update the TriangulationIndex to get the right configuration for the cube - * The index is in range 0-255, or we can represent it as 8 bits - * If one of the vertices of the cube is considered to be valued(its Noise value < Threshold), we replace the corresponding bit 0 by 1. By doing that we can easely create the index to get the configration from the table later. It's really dope - * Also filling PointOut array for each vertice of the cube (Position + Noise Value) - * It's not necessary that those vertices are added to Vertices array. See InterpolateVerts() and GenerateMeshData() methods - * @param x - x index value of the cube. It's not the world/relative positiion! It's an index position from 0 to XScale - * @param y - y index value of the cube. It's not the world/relative positiion! It's an index position from 0 to YScale - * @param z - z index value of the cube. It's not the world/relative positiion! It's an index position from 0 to ZScale - * @param PointsOut - filling array of vertices for the current cube. First three values are the index values xyz. The fourth value is the noise value. - * @return - index of for the cube to use in a triangulation table to get the configuration for this cube. - */ - int GetTriangulationIndexForCube(int x, int y, int z, FVector4(&PointsOut)[8]); - - /** - * Getting noise value for coordinates - * @return noise value - * @warning - XYZ are indexes, not world/relative positions! - */ - UFUNCTION(BlueprintCallable) - float GetNoiseValueForCoordinates(int x, int y, int z); - - /** - * Intrepolate point beetwen two vertices of the cube - * If two vertices are valid, we need to find a point on the edge where we should create an actual vertice - * @param FirstCorner - contains XYZ index and NoiseValue - * @param SecondCorner - contains XYZ index and NoiseValue - * @return - actual vetice to add to the Vertices (now its relative position, not the indexes!) - */ - UFUNCTION(BlueprintCallable) - FVector InterpolateVerts(FVector4& FirstCorner, FVector4& SecondCorner); - - /** Called every frame. */ - virtual void TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) override; - - // ----------------------------------------------------------------------------------------------------------- - // TABLES - // ----------------------------------------------------------------------------------------------------------- - - /** Triagnluation table of configuration for cubes */ - int triangulation[256][16] = { - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, - {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, - {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, - {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, - {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, - {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, - {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, - {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, - {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, - {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, - {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, - {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, - {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, - {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, - {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, - {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, - {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, - {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, - {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, - {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, - {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, - {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, - {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, - {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, - {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, - {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, - {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, - {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, - {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, - {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, - {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, - {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, - {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, - {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, - {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, - {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, - {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, - {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, - {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, - {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, - {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, - {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, - {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, - {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, - {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, - {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, - {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, - {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, - {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, - {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, - {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, - {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, - {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, - {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, - {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, - {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, - {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, - {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, - {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, - {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, - {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, - {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, - {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, - {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, - {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, - {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, - {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, - {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, - {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, - {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, - {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, - {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, - {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, - {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, - {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, - {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, - {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, - {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, - {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, - {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, - {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, - {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, - {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, - {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, - {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, - {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, - {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, - {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, - {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, - {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, - {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, - {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, - {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, - {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, - {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, - {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, - {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, - {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, - {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, - {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, - {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, - {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, - {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, - {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, - {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, - {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, - {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, - {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, - {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, - {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, - {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, - {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, - {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, - {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, - {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, - {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, - {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, - {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, - {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, - {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, - {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, - {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, - {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, - {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, - {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, - {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, - {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, - {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, - {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, - {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, - {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, - {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, - {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, - {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, - {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, - {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, - {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, - {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, - {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, - {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, - {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, - {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, - {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, - {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, - {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, - {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, - {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, - {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, - {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, - {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, - {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, - {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, - {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, - {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, - {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, - {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} - }; - - int edgeTable[256] = { - 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, - 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, - 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, - 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, - 0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c, - 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, - 0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac, - 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, - 0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c, - 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, - 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc, - 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, - 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c, - 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, - 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, - 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, - 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, - 0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, - 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, - 0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, - 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, - 0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, - 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, - 0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460, - 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, - 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0, - 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, - 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230, - 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, - 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190, - 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, - 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 - }; - - int cornerIndexAFromEdge[12] = { - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 0, - 1, - 2, - 3 - }; - - int cornerIndexBFromEdge[12] = { - 1, - 2, - 3, - 0, - 5, - 6, - 7, - 4, - 4, - 5, - 6, - 7 - }; - - // ----------------------------------------------------------------------------------------------------------- - // NOISE/MESH PARAMS - // ----------------------------------------------------------------------------------------------------------- - - /** - * Amout of cubes along x axis - * Sets this value in GenerateMarchingCubeMesh() method - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - int XScale; - - /** - * Amout of cubes along y axis - * Sets this value in GenerateMarchingCubeMesh() method - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - int YScale; - - /** - * Amout of cubes along z axis - * Sets this value in GenerateMarchingCubeMesh() method - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - int ZScale; - - /** - * The length of the cube's edge - * Sets this value in GenerateMarchingCubeMesh() method - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float BoxLength; - - - /** Scale value for the noise value for each 3D point (Noise value will be multiplied by it) */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float Scale; - - /** - * Reducing the valeue among each axis while getting the noise value - * Making this smaller will "stretch" the perlin noise terrain - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float NoiseInputScale = 0.05; //default was 0.05 - - /** - * Threshold value to calculate if the vertice if valid - * It is considered valid if the Noise Value of the vertice < Threshold - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite) - float Threshold; - - // ----------------------------------------------------------------------------------------------------------- - // MESH DATA ARRAYS - // ----------------------------------------------------------------------------------------------------------- - - /** - * Array of Vertices of the mesh - * Sadly we cannot know the amount of vertices before generationg the mesh so yeah that stinks - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray<FVector> Vertices; - - /** - * Array of triangles - * Length must be a multiple of 3 - * Each elemet represents the index of a vertice in order to make a full triangle - * EX: Triangles[0], Triangles[1], Triangles[2] will be used to create a triangle based on Vertices[0], Vertices[1] and Vertices[2] accordingly - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray<int32> Triangles; - - /** - * OPTIONAL - * Normals for each VERTICE(not triangle) - * Being set in GenerateMesh - * @warning - must be same length as Vertices array - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray<FVector> Normals; - - /** - * OPTIONAL - * Tangets for each VERTICE(not triangle) - * Being set in GenerateMesh - * @warning - must be same length as Vertices array - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray<FProcMeshTangent> Tangents; - - /** - * OPTIONAL - * Normals for each VERTICE(not triangle) - * Being set in GenerateMesh - * @warning - must be same length as Vertices array - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray<FVector2D> UV; - - /** - * OPTIONAL - * Normals for each VERTICE(not triangle) - * Being set in GenerateMesh - * @warning - must be same length as Vertices array - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray<FColor> VertexColors; -protected: - - /** Called when the game starts. */ - virtual void BeginPlay() override; - -private: - - -}; \ No newline at end of file diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.cpp deleted file mode 100644 index 1840a64..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "PerlinNoiseComponent.h" - -// Sets default values for this component's properties -UPerlinNoiseComponent::UPerlinNoiseComponent() -{ - // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features - // off to improve performance if you don't need them. - PrimaryComponentTick.bCanEverTick = true; - UpdateOptions(); - // ... -} - -float UPerlinNoiseComponent::GetFrequency() const -{ - return perlinNoise.GetFrequency(); -} - -float UPerlinNoiseComponent::GetLacunarity() const -{ - return perlinNoise.GetLacunarity(); -} - -qualities::PerlinNoiseQuality UPerlinNoiseComponent::GetNoiseQuality() const -{ - return convertBPNoiseQuality(perlinNoise.GetNoiseQuality()); -} - -int UPerlinNoiseComponent::GetOctaveCount() const -{ - return perlinNoise.GetOctaveCount(); -} - -float UPerlinNoiseComponent::GetPersistence() const -{ - return perlinNoise.GetPersistence(); -} - -int UPerlinNoiseComponent::GetSeed() const -{ - return perlinNoise.GetSeed(); -} - -int UPerlinNoiseComponent::GetSourceModuleCount() const -{ - return perlinNoise.GetSourceModuleCount(); -} - -float UPerlinNoiseComponent::GetValue(float x, float y, float z) const -{ - float value = perlinNoise.GetValue(x, y, z); - // UE_LOG(LogTemp, Warning, TEXT("Perlin value for: %f,%f,%f: %f"), x, y, z, value); - return value; -} - -void UPerlinNoiseComponent::SetFrequency(float frequency) -{ - return perlinNoise.SetFrequency(frequency); -} - -void UPerlinNoiseComponent::SetLacunarity(float lacunarity) -{ - return perlinNoise.SetLacunarity(lacunarity); -} - -void UPerlinNoiseComponent::SetNoiseQuality(qualities::PerlinNoiseQuality noiseQuality) -{ - return perlinNoise.SetNoiseQuality(convertBPNoiseQuality(noiseQuality)); -} - -void UPerlinNoiseComponent::SetOctaveCount(int octaveCount) -{ - perlinNoise.SetOctaveCount(octaveCount); -} - -void UPerlinNoiseComponent::SetPersistence(float persistence) -{ - perlinNoise.SetPersistence(persistence); -} - -void UPerlinNoiseComponent::SetSeed(int seed) -{ - perlinNoise.SetSeed(seed); -} - -void UPerlinNoiseComponent::SetRandomSeed() -{ - SetSeed(FMath::RandRange(0, 1000000)); -} - -// Called when the game starts -void UPerlinNoiseComponent::BeginPlay() -{ - Super::BeginPlay(); - // ... -} - - -// Called every frame -void UPerlinNoiseComponent::TickComponent(float DeltaTime, ELevelTick TickType, - FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - - // ... -} - -void UPerlinNoiseComponent::SetupOptions(float Frequency_, float Lacunarity_, - qualities::PerlinNoiseQuality NoiseQuality_, int OctaveCount_, - float Persistence_, int Seed_) -{ - SetFrequency(Frequency_); - SetLacunarity(Lacunarity_); - SetNoiseQuality(NoiseQuality_); - SetOctaveCount(OctaveCount_); - SetPersistence(Persistence_); - SetSeed(Seed_); -} - -noise::NoiseQuality UPerlinNoiseComponent::convertBPNoiseQuality(qualities::PerlinNoiseQuality quality) const -{ - switch (quality) - { - case qualities::QUALITY_FAST: - return noise::QUALITY_FAST; - break; - break; - case qualities::QUALITY_STD: - return noise::QUALITY_STD; - break; - case qualities::QUALITY_BEST: - return noise::QUALITY_BEST; - break; - default: - return noise::QUALITY_STD; - } -} - -qualities::PerlinNoiseQuality UPerlinNoiseComponent::convertBPNoiseQuality(noise::NoiseQuality quality) const -{ - switch (quality) - { - case noise::QUALITY_FAST: - return qualities::QUALITY_FAST; - break; - case noise::QUALITY_STD: - return qualities::QUALITY_STD; - break; - case noise::QUALITY_BEST: - return qualities::QUALITY_BEST; - break; - default: - return qualities::QUALITY_STD; - } -} - -void UPerlinNoiseComponent::UpdateOptions() -{ - SetupOptions(Frequency, Lacunarity, NoiseQuality, OctaveCount, Persistence, Seed); -} diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.h deleted file mode 100644 index 74dd1ff..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/PerlinNoiseComponent.h +++ /dev/null @@ -1,103 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "../../ThirdParty/LibNoise/Includes/libnoise.h" - -#include "CoreMinimal.h" -#include "Components/ActorComponent.h" -#include "PerlinNoiseComponent.generated.h" - -/** UE4 compatible enum that corresponds to perlin noise qualities. */ -UENUM(BluePrintType) -namespace qualities { - enum PerlinNoiseQuality { - QUALITY_FAST UMETA(DisplayName = "Fast Quality"), - QUALITY_STD UMETA(DisplayName = "Standard Quality"), - QUALITY_BEST UMETA(DisplayName = "Best Quailty") - }; -} - -// Change UNDERWATER_API to whatever your project API name is -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) -class UPerlinNoiseComponent : public UActorComponent -{ - GENERATED_BODY() - -public: - /** Sets default values for this component's properties. */ - UPerlinNoiseComponent(); - - /** The main function to call to get noise output. */ - UFUNCTION(BluePrintCallable, meta = (Keywords = "noise perlin"), Category = "Perlin") - float GetValue(float x, float y, float z) const; - - /** Easy setup function to set all the options at once. */ - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetupOptions( - float Frequency_, - float Lacunarity_, - qualities::PerlinNoiseQuality NoiseQuality_, - int OctaveCount_, - float Persistence_, - int Seed_ - ); - - /** Setters/getters for all the individual noise properties. */ - UFUNCTION(BluePrintCallable, Category = "Perlin") - float GetFrequency() const; - UFUNCTION(BluePrintCallable, Category = "Perlin") - float GetLacunarity() const; - UFUNCTION(BluePrintCallable, Category = "Perlin") - qualities::PerlinNoiseQuality GetNoiseQuality() const; - UFUNCTION(BluePrintCallable, Category = "Perlin") - int GetOctaveCount() const; - UFUNCTION(BluePrintCallable, Category = "Perlin") - float GetPersistence() const; - UFUNCTION(BluePrintCallable, Category = "Perlin") - int GetSeed() const; - UFUNCTION(BluePrintCallable, Category = "Perlin") - int GetSourceModuleCount() const; - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetFrequency(float frequency); - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetLacunarity(float lacunarity); - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetNoiseQuality(qualities::PerlinNoiseQuality noiseQuality); - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetOctaveCount(int octaveCount); - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetPersistence(float persistence); - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetSeed(int seed); - UFUNCTION(BluePrintCallable, Category = "Perlin") - void SetRandomSeed(); - -protected: - - /** Called when the game starts. */ - virtual void BeginPlay() override; - /** Called every frame. */ - virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - -private: - - noise::module::Perlin perlinNoise; - noise::NoiseQuality convertBPNoiseQuality(qualities::PerlinNoiseQuality quality) const; - qualities::PerlinNoiseQuality convertBPNoiseQuality(noise::NoiseQuality quality) const; - void UpdateOptions(); - - /** Those are private because they shouldn't be modified at runtime, but can be edited in the editor before running the game. */ - UPROPERTY(EditAnywhere, Category = "Perlin") - float Frequency = 1.0; //1.0 default - UPROPERTY(EditAnywhere, Category = "Perlin") - float Lacunarity = 10.0; // 2.0 default - UPROPERTY(EditAnywhere, Category = "Perlin") - TEnumAsByte<qualities::PerlinNoiseQuality> NoiseQuality = qualities::QUALITY_STD; - UPROPERTY(EditAnywhere, Category = "Perlin") - int OctaveCount = 6; // 6 default - UPROPERTY(EditAnywhere, Category = "Perlin") - float Persistence = 0.5; // 0.5 default - UPROPERTY(EditAnywhere, Category = "Perlin") - int Seed = 0; -}; diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.cpp deleted file mode 100644 index 7cc5fac..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "ProceduralCube.h" - -// Sets default values -AProceduralCube::AProceduralCube() -{ - // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. - PrimaryActorTick.bCanEverTick = false; - - Mesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("Procedural Mesh Component")); - Mesh->SetupAttachment(GetRootComponent()); - - /* Setting the attachment rules */ - const FAttachmentTransformRules AttachmentTransformRules = FAttachmentTransformRules(EAttachmentRule::KeepRelative, true); - - /* Creates Perlin Noise Component */ - Noise = CreateDefaultSubobject<UPerlinNoiseComponent>(TEXT("Perlin Noise Component")); - - /* Creates Marching Cube Component */ - MCube = CreateDefaultSubobject<UMCubeComponent>(TEXT("Marching cube Component")); - MCube->Noise = Noise; - MCube->Mesh = Mesh; - - /* Sets default box length*/ - BoxLength = 50.f; - - XLength = 30; - YLength = 30; - ZLength = 30; - - /* Sets default noise parameters */ - Frequency = 1.f; - Lacunarity = 2.f; - NoiseQuality = qualities::QUALITY_STD; - OctaveCount = 6; - Persistence = 0.5f; - Seed = 0; - Noise->SetupOptions(Frequency, Lacunarity, NoiseQuality, OctaveCount, Persistence, Seed); - -} - -// Called every frame -void AProceduralCube::Tick(float DeltaSeconds) -{ - Super::Tick(DeltaSeconds); -} -void AProceduralCube::GenerateMesh() -{ - /* Updates the noises seed to get new noise values */ - //Noise->SetRandomSeed(); - - MCube->GenerateMarchingCubeMesh(XLength, YLength, ZLength, BoxLength); -} -// Called when the game starts or when spawned -void AProceduralCube::BeginPlay() -{ - Super::BeginPlay(); - //GenerateMesh(); -} - diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.h deleted file mode 100644 index 0ecceb7..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ProceduralCube.h +++ /dev/null @@ -1,82 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/Actor.h" -#include "Math/IntVector.h" -#include "PerlinNoiseComponent.h" -#include "ProceduralMeshComponent.h" -#include "MCubeComponent.h" -#include "ProceduralCube.generated.h" - -UCLASS() -class EXCAVATORSIMULATOR_API AProceduralCube : public AActor -{ - GENERATED_BODY() - -public: - /** Constructor. Sets default values for this actor's properties. */ - AProceduralCube(); - - /** Called every frame. */ - virtual void Tick(float Deltaseconds) override; - - - UFUNCTION(BlueprintCallable) - void GenerateMesh(); - - UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly) - class UProceduralMeshComponent* Mesh; - - UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly) - class UPerlinNoiseComponent* Noise; - - UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly) - class UMCubeComponent* MCube; - - // ----------------------------------------------------------------------------------------------------------- - // Meshes size attributes - // ----------------------------------------------------------------------------------------------------------- - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube") - float BoxLength; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube") - int XLength; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube") - int YLength; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube") - int ZLength; - - // ----------------------------------------------------------------------------------------------------------- - // Noise attributes - // ----------------------------------------------------------------------------------------------------------- - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube|Noise") - float Frequency; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube|Noise") - float Lacunarity; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube|Noise") - TEnumAsByte<qualities::PerlinNoiseQuality> NoiseQuality; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube|Noise") - int OctaveCount; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube|Noise") - float Persistence; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Marching cube|Noise") - int Seed; -protected: - /** Called when the game starts or when spawned. */ - virtual void BeginPlay() override; - -private: - - -}; diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/basictypes.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/basictypes.h deleted file mode 100644 index e06f666..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/basictypes.h +++ /dev/null @@ -1,66 +0,0 @@ -// basictypes.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_BASICTYPES_H -#define NOISE_BASICTYPES_H - -// You may need to modify these constants for your compiler or platform. -#ifdef DLLEXPORT -#define DLL __declspec(dllexport) -#else -#define DLL __declspec(dllimport) -#endif - - -namespace noise -{ - - /// @defgroup libnoise libnoise - /// @addtogroup libnoise - /// @{ - - /// Unsigned integer type. - typedef unsigned int uint; - - /// 32-bit unsigned integer type. - typedef unsigned int uint32; - - /// 16-bit unsigned integer type. - typedef unsigned short uint16; - - /// 8-bit unsigned integer type. - typedef unsigned char uint8; - - /// 32-bit signed integer type. - typedef int int32; - - /// 16-bit signed integer type. - typedef short int16; - - /// 8-bit signed integer type. - typedef char int8; - - /// @} - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/exception.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/exception.h deleted file mode 100644 index a55bdfb..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/exception.h +++ /dev/null @@ -1,74 +0,0 @@ -// exception.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_EXCEPTION_H -#define NOISE_EXCEPTION_H - -namespace noise -{ - - /// @addtogroup libnoise - /// @{ - - /// Abstract base class for libnoise exceptions - class Exception - { - }; - - /// Invalid parameter exception - /// - /// An invalid parameter was passed to a libnoise function or method. - class ExceptionInvalidParam: public Exception - { - }; - - /// No module exception - /// - /// Could not retrieve a source module from a noise module. - /// - /// @note If one or more required source modules were not connected to a - /// specific noise module, and its GetValue() method was called, that - /// method will raise a debug assertion instead of this exception. This - /// is done for performance reasons. - class ExceptionNoModule: public Exception - { - }; - - /// Out of memory exception - /// - /// There was not enough memory to perform an action. - class ExceptionOutOfMemory: public Exception - { - }; - - /// Unknown exception - /// - /// libnoise raised an unknown exception. - class ExceptionUnknown: public Exception - { - }; - - /// @} - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/interp.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/interp.h deleted file mode 100644 index 3b0cf90..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/interp.h +++ /dev/null @@ -1,112 +0,0 @@ -// interp.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_INTERP_H -#define NOISE_INTERP_H - -namespace noise -{ - - /// @addtogroup libnoise - /// @{ - - /// Performs cubic interpolation between two values bound between two other - /// values. - /// - /// @param n0 The value before the first value. - /// @param n1 The first value. - /// @param n2 The second value. - /// @param n3 The value after the second value. - /// @param a The alpha value. - /// - /// @returns The interpolated value. - /// - /// The alpha value should range from 0.0 to 1.0. If the alpha value is - /// 0.0, this function returns @a n1. If the alpha value is 1.0, this - /// function returns @a n2. - inline double CubicInterp (double n0, double n1, double n2, double n3, - double a) - { - double p = (n3 - n2) - (n0 - n1); - double q = (n0 - n1) - p; - double r = n2 - n0; - double s = n1; - return p * a * a * a + q * a * a + r * a + s; - } - - /// Performs linear interpolation between two values. - /// - /// @param n0 The first value. - /// @param n1 The second value. - /// @param a The alpha value. - /// - /// @returns The interpolated value. - /// - /// The alpha value should range from 0.0 to 1.0. If the alpha value is - /// 0.0, this function returns @a n0. If the alpha value is 1.0, this - /// function returns @a n1. - inline double LinearInterp (double n0, double n1, double a) - { - return ((1.0 - a) * n0) + (a * n1); - } - - /// Maps a value onto a cubic S-curve. - /// - /// @param a The value to map onto a cubic S-curve. - /// - /// @returns The mapped value. - /// - /// @a a should range from 0.0 to 1.0. - /// - /// The derivitive of a cubic S-curve is zero at @a a = 0.0 and @a a = - /// 1.0 - inline double SCurve3 (double a) - { - return (a * a * (3.0 - 2.0 * a)); - } - - /// Maps a value onto a quintic S-curve. - /// - /// @param a The value to map onto a quintic S-curve. - /// - /// @returns The mapped value. - /// - /// @a a should range from 0.0 to 1.0. - /// - /// The first derivitive of a quintic S-curve is zero at @a a = 0.0 and - /// @a a = 1.0 - /// - /// The second derivitive of a quintic S-curve is zero at @a a = 0.0 and - /// @a a = 1.0 - inline double SCurve5 (double a) - { - double a3 = a * a * a; - double a4 = a3 * a; - double a5 = a4 * a; - return (6.0 * a5) - (15.0 * a4) + (10.0 * a3); - } - - // @} - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/latlon.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/latlon.h deleted file mode 100644 index 7e0aab1..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/latlon.h +++ /dev/null @@ -1,52 +0,0 @@ -// latlon.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_LATLON_H -#define NOISE_LATLON_H - -#include <math.h> -#include "mathconsts.h" - -namespace noise -{ - - /// @addtogroup libnoise - /// @{ - - /// Converts latitude/longitude coordinates on a unit sphere into 3D - /// Cartesian coordinates. - /// - /// @param lat The latitude, in degrees. - /// @param lon The longitude, in degrees. - /// @param x On exit, this parameter contains the @a x coordinate. - /// @param y On exit, this parameter contains the @a y coordinate. - /// @param z On exit, this parameter contains the @a z coordinate. - /// - /// @pre lat must range from @b -90 to @b +90. - /// @pre lon must range from @b -180 to @b +180. - DLL void LatLonToXYZ (double lat, double lon, double& x, double& y, double& z); - - /// @} - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/libnoise.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/libnoise.h deleted file mode 100644 index a60c8e6..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/libnoise.h +++ /dev/null @@ -1,74 +0,0 @@ -// noise.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef LIBNOISE_H -#define LIBNOISE_H - -/// @mainpage libnoise -/// -/// @section intro Introduction -/// -/// libnoise is a portable C++ library that is used to generate <i>coherent -/// noise</i>, a type of smoothly-changing noise. libnoise can generate Perlin -/// noise, ridged multifractal noise, and other types of coherent noise. -/// -/// Coherent noise is often used by graphics programmers to generate -/// natural-looking textures, planetary terrain, and other things. It can -/// also be used to move critters in a realistic way. -/// -/// libnoise is known to compile using the following compilers on the -/// following platforms: -/// - Microsoft Visual C++ 5.0 under Microsoft Windows 2000 Service Pack 4 -/// - gcc 3.3.4 under Gentoo Linux 10.0 (x86) -/// -/// It is not known if libnoise will compile on 64-bit platforms, although -/// there is a good change that it will. -/// -/// @section noise Noise Modules -/// -/// In libnoise, coherent-noise generators are encapsulated in classes called -/// <i>noise modules</i>. There are many different types of noise modules. -/// Some noise modules can combine or modify the outputs of other noise -/// modules in various ways; you can join these modules together to generate -/// very complex coherent noise. -/// -/// A noise module receives a 3-dimensional input value from the application, -/// computes the noise value given that input value, and returns the resulting -/// value back to the application. -/// -/// If the application passes the same input value to a noise module, the -/// noise module returns the same output value. -/// -/// All noise modules are derived from the noise::module::Module abstract -/// base class. -/// -/// @section contact Contact -/// -/// Contact jas for questions about libnoise. The spam-resistant email -/// address is jlbezigvins@gmzigail.com (For great email, take off every -/// <a href=http://www.planettribes.com/allyourbase/story.shtml>zig</a>.) - -#include "module/module.h" -#include "model/model.h" -#include "misc.h" - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/mathconsts.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/mathconsts.h deleted file mode 100644 index 4b126bc..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/mathconsts.h +++ /dev/null @@ -1,60 +0,0 @@ -// mathconsts.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MATHCONSTS_H -#define NOISE_MATHCONSTS_H - -// For whatever reason, I can't find the basic math consts in the MSVC version -// of math.h. -#ifdef DLLEXPORT -#define DLL __declspec(dllexport) -#else -#define DLL __declspec(dllimport) -#endif - - -namespace noise -{ - - /// @addtogroup libnoise - /// @{ - - /// Pi. - const double PI = 3.1415926535897932385; - - /// Square root of 2. - const double SQRT_2 = 1.4142135623730950488; - - /// Square root of 3. - const double SQRT_3 = 1.7320508075688772935; - - /// Converts an angle from degrees to radians. - const double DEG_TO_RAD = PI / 180.0; - - /// Converts an angle from radians to degrees. - const double RAD_TO_DEG = 1.0 / DEG_TO_RAD; - - /// @} - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/misc.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/misc.h deleted file mode 100644 index 116d39f..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/misc.h +++ /dev/null @@ -1,97 +0,0 @@ -// misc.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MISC_H -#define NOISE_MISC_H - -namespace noise -{ - - /// Clamps a value onto a clamping range. - /// - /// @param value The value to clamp. - /// @param lowerBound The lower bound of the clamping range. - /// @param upperBound The upper bound of the clamping range. - /// - /// @returns - /// - @a value if @a value lies between @a lowerBound and @a upperBound. - /// - @a lowerBound if @a value is less than @a lowerBound. - /// - @a upperBound if @a value is greater than @a upperBound. - /// - /// This function does not modify any parameters. - inline int ClampValue (int value, int lowerBound, int upperBound) - { - if (value < lowerBound) { - return lowerBound; - } else if (value > upperBound) { - return upperBound; - } else { - return value; - } - } - - /// @addtogroup libnoise - /// @{ - - /// Returns the maximum of two values. - /// - /// @param a The first value. - /// @param b The second value. - /// - /// @returns The maximum of the two values. - template <class T> - T GetMax (const T& a, const T& b) - { - return (a > b? a: b); - } - - /// Returns the minimum of two values. - /// - /// @param a The first value. - /// @param b The second value. - /// - /// @returns The minimum of the two values. - template <class T> - T GetMin (const T& a, const T& b) - { - return (a < b? a: b); - } - - /// Swaps two values. - /// - /// @param a A variable containing the first value. - /// @param b A variable containing the second value. - /// - /// @post The values within the the two variables are swapped. - template <class T> - void SwapValues (T& a, T& b) - { - T c = a; - a = b; - b = c; - } - - /// @} - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/cylinder.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/cylinder.h deleted file mode 100644 index 074f495..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/cylinder.h +++ /dev/null @@ -1,131 +0,0 @@ -// cylinder.h -// -// Copyright 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODEL_CYLINDER_H -#define NOISE_MODEL_CYLINDER_H - -#include <assert.h> -#include <math.h> -#include <stdlib.h> -#include "../module/modulebase.h" - -namespace noise -{ - - namespace model - { - - /// @addtogroup libnoise - /// @{ - - /// @defgroup models Models - /// @addtogroup models - /// @{ - - /// Model that defines the surface of a cylinder. - /// - /// @image html modelcylinder.png - /// - /// This model returns an output value from a noise module given the - /// coordinates of an input value located on the surface of a cylinder. - /// - /// To generate an output value, pass the (angle, height) coordinates of - /// an input value to the GetValue() method. - /// - /// This model is useful for creating: - /// - seamless textures that can be mapped onto a cylinder - /// - /// This cylinder has a radius of 1.0 unit and has infinite height. It is - /// oriented along the @a y axis. Its center is located at the origin. - class DLL Cylinder - { - - public: - - /// Constructor. - Cylinder (); - - /// Constructor - /// - /// @param module The noise module that is used to generate the output - /// values. - Cylinder (const module::Module& module); - - /// Returns the noise module that is used to generate the output - /// values. - /// - /// @returns A reference to the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - const module::Module& GetModule () const - { - assert (m_pModule != NULL); - return *m_pModule; - } - - /// Returns the output value from the noise module given the - /// (angle, height) coordinates of the specified input value located - /// on the surface of the cylinder. - /// - /// @param angle The angle around the cylinder's center, in degrees. - /// @param height The height along the @a y axis. - /// - /// @returns The output value from the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - /// - /// This output value is generated by the noise module passed to the - /// SetModule() method. - /// - /// This cylinder has a radius of 1.0 unit and has infinite height. - /// It is oriented along the @a y axis. Its center is located at the - /// origin. - double GetValue (double angle, double height) const; - - /// Sets the noise module that is used to generate the output values. - /// - /// @param module The noise module that is used to generate the output - /// values. - /// - /// This noise module must exist for the lifetime of this object, - /// until you pass a new noise module to this method. - void SetModule (const module::Module& module) - { - m_pModule = &module; - } - - private: - - /// A pointer to the noise module used to generate the output values. - const module::Module* m_pModule; - - }; - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/line.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/line.h deleted file mode 100644 index 449c5e1..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/line.h +++ /dev/null @@ -1,198 +0,0 @@ -// line.h -// -// Copyright (C) 2004 Keith Davies -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// - -#ifndef NOISE_MODEL_LINE_H -#define NOISE_MODEL_LINE_H - -#include <assert.h> -#include <math.h> -#include <stdlib.h> -#include "../module/modulebase.h" - -namespace noise -{ - - namespace model - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup models - /// @{ - - /// Model that defines the displacement of a line segment. - /// - /// This model returns an output value from a noise module given the - /// one-dimensional coordinate of an input value located on a line - /// segment, which can be used as displacements. - /// - /// This class is useful for creating: - /// - roads and rivers - /// - disaffected college students - /// - /// To generate an output value, pass an input value between 0.0 and 1.0 - /// to the GetValue() method. 0.0 represents the start position of the - /// line segment and 1.0 represents the end position of the line segment. - class DLL Line - { - - public: - - /// Constructor. - Line (); - - /// Constructor - /// - /// @param module The noise module that is used to generate the output - /// values. - Line (const module::Module& module); - - /// Returns a flag indicating whether the output value is to be - /// attenuated (moved toward 0.0) as the ends of the line segment are - /// approached by the input value. - /// - /// @returns - /// - @a true if the value is to be attenuated - /// - @a false if not. - bool GetAttenuate () const - { - return m_attenuate; - } - - /// Returns the noise module that is used to generate the output - /// values. - /// - /// @returns A reference to the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - const module::Module& GetModule () const - { - assert (m_pModule != NULL); - return *m_pModule; - } - - /// Returns the output value from the noise module given the - /// one-dimensional coordinate of the specified input value located - /// on the line segment. - /// - /// @param p The distance along the line segment (ranges from 0.0 - /// to 1.0) - /// - /// @returns The output value from the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - /// @pre The start and end points of the line segment were specified. - /// - /// The output value is generated by the noise module passed to the - /// SetModule() method. This value may be attenuated (moved toward - /// 0.0) as @a p approaches either end of the line segment; this is - /// the default behavior. - /// - /// If the value is not to be attenuated, @a p can safely range - /// outside the 0.0 to 1.0 range; the output value will be - /// extrapolated along the line that this segment is part of. - double GetValue (double p) const; - - /// Sets a flag indicating that the output value is to be attenuated - /// (moved toward 0.0) as the ends of the line segment are approached. - /// - /// @param att A flag that specifies whether the output value is to be - /// attenuated. - void SetAttenuate (bool att) - { - m_attenuate = att; - } - - /// Sets the position ( @a x, @a y, @a z ) of the end of the line - /// segment to choose values along. - /// - /// @param x x coordinate of the end position. - /// @param y y coordinate of the end position. - /// @param z z coordinate of the end position. - void SetEndPoint (double x, double y, double z) - { - m_x1 = x; - m_y1 = y; - m_z1 = z; - } - - /// Sets the noise module that is used to generate the output values. - /// - /// @param module The noise module that is used to generate the output - /// values. - /// - /// This noise module must exist for the lifetime of this object, - /// until you pass a new noise module to this method. - void SetModule (const module::Module& module) - { - m_pModule = &module; - } - - /// Sets the position ( @a x, @a y, @a z ) of the start of the line - /// segment to choose values along. - /// - /// @param x x coordinate of the start position. - /// @param y y coordinate of the start position. - /// @param z z coordinate of the start position. - void SetStartPoint (double x, double y, double z) - { - m_x0 = x; - m_y0 = y; - m_z0 = z; - } - - private: - - /// A flag that specifies whether the value is to be attenuated - /// (moved toward 0.0) as the ends of the line segment are approached. - bool m_attenuate; - - /// A pointer to the noise module used to generate the output values. - const module::Module* m_pModule; - - /// @a x coordinate of the start of the line segment. - double m_x0; - - /// @a x coordinate of the end of the line segment. - double m_x1; - - /// @a y coordinate of the start of the line segment. - double m_y0; - - /// @a y coordinate of the end of the line segment. - double m_y1; - - /// @a z coordinate of the start of the line segment. - double m_z0; - - /// @a z coordinate of the end of the line segment. - double m_z1; - - }; - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/model.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/model.h deleted file mode 100644 index 52308aa..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/model.h +++ /dev/null @@ -1,31 +0,0 @@ -// model.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODEL_H -#define NOISE_MODEL_H - -#include "cylinder.h" -#include "line.h" -#include "plane.h" -#include "sphere.h" - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/plane.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/plane.h deleted file mode 100644 index 380962f..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/plane.h +++ /dev/null @@ -1,121 +0,0 @@ -// plane.h -// -// Copyright (C) 2004 Owen Jacobson -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is ojacobson@lionsanctuary.net -// - -#ifndef NOISE_MODEL_PLANE_H -#define NOISE_MODEL_PLANE_H - -#include <assert.h> -#include "../module/modulebase.h" - -namespace noise -{ - - namespace model - { - /// @addtogroup libnoise - /// @{ - - /// @addtogroup models - /// @{ - - /// Model that defines the surface of a plane. - /// - /// This model returns an output value from a noise module given the - /// coordinates of an input value located on the surface of an ( @a x, - /// @a z ) plane. - /// - /// To generate an output value, pass the ( @a x, @a z ) coordinates of - /// an input value to the GetValue() method. - /// - /// This model is useful for creating: - /// - two-dimensional textures - /// - terrain height maps for local areas - /// - /// This plane extends infinitely in both directions. - class DLL Plane - { - - public: - - /// Constructor. - Plane (); - - /// Constructor - /// - /// @param module The noise module that is used to generate the output - /// values. - Plane (const module::Module& module); - - /// Returns the noise module that is used to generate the output - /// values. - /// - /// @returns A reference to the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - const module::Module& GetModule () const - { - assert (m_pModule != NULL); - return *m_pModule; - } - - /// Returns the output value from the noise module given the - /// ( @a x, @a z ) coordinates of the specified input value located - /// on the surface of the plane. - /// - /// @param x The @a x coordinate of the input value. - /// @param z The @a z coordinate of the input value. - /// - /// @returns The output value from the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - /// - /// This output value is generated by the noise module passed to the - /// SetModule() method. - double GetValue (double x, double z) const; - - /// Sets the noise module that is used to generate the output values. - /// - /// @param module The noise module that is used to generate the output - /// values. - /// - /// This noise module must exist for the lifetime of this object, - /// until you pass a new noise module to this method. - void SetModule (const module::Module& module) - { - m_pModule = &module; - } - - private: - - /// A pointer to the noise module used to generate the output values. - const module::Module* m_pModule; - - }; - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/sphere.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/sphere.h deleted file mode 100644 index 0afcecd..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/model/sphere.h +++ /dev/null @@ -1,131 +0,0 @@ -// sphere.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODEL_SPHERE_H -#define NOISE_MODEL_SPHERE_H - -#include <assert.h> -#include "../module/modulebase.h" - -namespace noise -{ - - namespace model - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup models - /// @{ - - /// Model that defines the surface of a sphere. - /// - /// @image html modelsphere.png - /// - /// This model returns an output value from a noise module given the - /// coordinates of an input value located on the surface of a sphere. - /// - /// To generate an output value, pass the (latitude, longitude) - /// coordinates of an input value to the GetValue() method. - /// - /// This model is useful for creating: - /// - seamless textures that can be mapped onto a sphere - /// - terrain height maps for entire planets - /// - /// This sphere has a radius of 1.0 unit and its center is located at - /// the origin. - class DLL Sphere - { - - public: - - /// Constructor. - Sphere (); - - /// Constructor - /// - /// @param module The noise module that is used to generate the output - /// values. - Sphere (const module::Module& module); - - /// Returns the noise module that is used to generate the output - /// values. - /// - /// @returns A reference to the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - const module::Module& GetModule () const - { - assert (m_pModule != NULL); - return *m_pModule; - } - - /// Returns the output value from the noise module given the - /// (latitude, longitude) coordinates of the specified input value - /// located on the surface of the sphere. - /// - /// @param lat The latitude of the input value, in degrees. - /// @param lon The longitude of the input value, in degrees. - /// - /// @returns The output value from the noise module. - /// - /// @pre A noise module was passed to the SetModule() method. - /// - /// This output value is generated by the noise module passed to the - /// SetModule() method. - /// - /// Use a negative latitude if the input value is located on the - /// southern hemisphere. - /// - /// Use a negative longitude if the input value is located on the - /// western hemisphere. - double GetValue (double lat, double lon) const; - - /// Sets the noise module that is used to generate the output values. - /// - /// @param module The noise module that is used to generate the output - /// values. - /// - /// This noise module must exist for the lifetime of this object, - /// until you pass a new noise module to this method. - void SetModule (const module::Module& module) - { - m_pModule = &module; - } - - private: - - /// A pointer to the noise module used to generate the output values. - const module::Module* m_pModule; - - }; - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/abs.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/abs.h deleted file mode 100644 index 4a67d8e..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/abs.h +++ /dev/null @@ -1,76 +0,0 @@ -// abs.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_ABS_H -#define NOISE_MODULE_ABS_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @defgroup modifiermodules Modifier Modules - /// @addtogroup modifiermodules - /// @{ - - /// Noise module that outputs the absolute value of the output value from - /// a source module. - /// - /// @image html moduleabs.png - /// - /// This noise module requires one source module. - class DLL Abs : public Module - { - - public: - - /// Constructor. - Abs (); - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/add.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/add.h deleted file mode 100644 index df7f691..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/add.h +++ /dev/null @@ -1,77 +0,0 @@ -// add.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_ADD_H -#define NOISE_MODULE_ADD_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @defgroup combinermodules Combiner Modules - /// @addtogroup combinermodules - /// @{ - - /// Noise module that outputs the sum of the two output values from two - /// source modules. - /// - /// @image html moduleadd.png - /// - /// This noise module requires two source modules. - class DLL Add : public Module - { - - public: - - /// Constructor. - Add (); - - virtual int GetSourceModuleCount () const - { - return 2; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/billow.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/billow.h deleted file mode 100644 index f1d7284..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/billow.h +++ /dev/null @@ -1,277 +0,0 @@ -// billow.h -// -// Copyright (C) 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_BILLOW_H -#define NOISE_MODULE_BILLOW_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup generatormodules - /// @{ - - /// Default frequency for the noise::module::Billow noise module. - const double DEFAULT_BILLOW_FREQUENCY = 1.0; - - /// Default lacunarity for the the noise::module::Billow noise module. - const double DEFAULT_BILLOW_LACUNARITY = 2.0; - - /// Default number of octaves for the the noise::module::Billow noise - /// module. - const int DEFAULT_BILLOW_OCTAVE_COUNT = 6; - - /// Default persistence value for the the noise::module::Billow noise - /// module. - const double DEFAULT_BILLOW_PERSISTENCE = 0.5; - - /// Default noise quality for the the noise::module::Billow noise module. - const noise::NoiseQuality DEFAULT_BILLOW_QUALITY = QUALITY_STD; - - /// Default noise seed for the the noise::module::Billow noise module. - const int DEFAULT_BILLOW_SEED = 0; - - /// Maximum number of octaves for the the noise::module::Billow noise - /// module. - const int BILLOW_MAX_OCTAVE = 30; - - /// Noise module that outputs three-dimensional "billowy" noise. - /// - /// @image html modulebillow.png - /// - /// This noise module generates "billowy" noise suitable for clouds and - /// rocks. - /// - /// This noise module is nearly identical to noise::module::Perlin except - /// this noise module modifies each octave with an absolute-value - /// function. See the documentation of noise::module::Perlin for more - /// information. - class DLL Billow : public Module - { - - public: - - /// Constructor. - /// - /// The default frequency is set to - /// noise::module::DEFAULT_BILLOW_FREQUENCY. - /// - /// The default lacunarity is set to - /// noise::module::DEFAULT_BILLOW_LACUNARITY. - /// - /// The default number of octaves is set to - /// noise::module::DEFAULT_BILLOW_OCTAVE_COUNT. - /// - /// The default persistence value is set to - /// noise::module::DEFAULT_BILLOW_PERSISTENCE. - /// - /// The default seed value is set to - /// noise::module::DEFAULT_BILLOW_SEED. - Billow (); - - /// Returns the frequency of the first octave. - /// - /// @returns The frequency of the first octave. - double GetFrequency () const - { - return m_frequency; - } - - /// Returns the lacunarity of the billowy noise. - /// - /// @returns The lacunarity of the billowy noise. - /// - /// The lacunarity is the frequency multiplier between successive - /// octaves. - double GetLacunarity () const - { - return m_lacunarity; - } - - /// Returns the quality of the billowy noise. - /// - /// @returns The quality of the billowy noise. - /// - /// See noise::NoiseQuality for definitions of the various - /// coherent-noise qualities. - noise::NoiseQuality GetNoiseQuality () const - { - return m_noiseQuality; - } - - /// Returns the number of octaves that generate the billowy noise. - /// - /// @returns The number of octaves that generate the billowy noise. - /// - /// The number of octaves controls the amount of detail in the billowy - /// noise. - int GetOctaveCount () const - { - return m_octaveCount; - } - - /// Returns the persistence value of the billowy noise. - /// - /// @returns The persistence value of the billowy noise. - /// - /// The persistence value controls the roughness of the billowy noise. - double GetPersistence () const - { - return m_persistence; - } - - /// Returns the seed value used by the billowy-noise function. - /// - /// @returns The seed value. - int GetSeed () const - { - return m_seed; - } - - virtual int GetSourceModuleCount () const - { - return 0; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the frequency of the first octave. - /// - /// @param frequency The frequency of the first octave. - void SetFrequency (double frequency) - { - m_frequency = frequency; - } - - /// Sets the lacunarity of the billowy noise. - /// - /// @param lacunarity The lacunarity of the billowy noise. - /// - /// The lacunarity is the frequency multiplier between successive - /// octaves. - /// - /// For best results, set the lacunarity to a number between 1.5 and - /// 3.5. - void SetLacunarity (double lacunarity) - { - m_lacunarity = lacunarity; - } - - /// Sets the quality of the billowy noise. - /// - /// @param noiseQuality The quality of the billowy noise. - /// - /// See noise::NoiseQuality for definitions of the various - /// coherent-noise qualities. - void SetNoiseQuality (noise::NoiseQuality noiseQuality) - { - m_noiseQuality = noiseQuality; - } - - /// Sets the number of octaves that generate the billowy noise. - /// - /// @param octaveCount The number of octaves that generate the billowy - /// noise. - /// - /// @pre The number of octaves ranges from 1 to - /// noise::module::BILLOW_MAX_OCTAVE. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// The number of octaves controls the amount of detail in the billowy - /// noise. - /// - /// The larger the number of octaves, the more time required to - /// calculate the billowy-noise value. - void SetOctaveCount (int octaveCount) - { - if (octaveCount < 1 || octaveCount > BILLOW_MAX_OCTAVE) { - throw noise::ExceptionInvalidParam (); - } - m_octaveCount = octaveCount; - } - - /// Sets the persistence value of the billowy noise. - /// - /// @param persistence The persistence value of the billowy noise. - /// - /// The persistence value controls the roughness of the billowy noise. - /// - /// For best results, set the persistence value to a number between - /// 0.0 and 1.0. - void SetPersistence (double persistence) - { - m_persistence = persistence; - } - - /// Sets the seed value used by the billowy-noise function. - /// - /// @param seed The seed value. - void SetSeed (int seed) - { - m_seed = seed; - } - - protected: - - /// Frequency of the first octave. - double m_frequency; - - /// Frequency multiplier between successive octaves. - double m_lacunarity; - - /// Quality of the billowy noise. - noise::NoiseQuality m_noiseQuality; - - /// Total number of octaves that generate the billowy noise. - int m_octaveCount; - - /// Persistence value of the billowy noise. - double m_persistence; - - /// Seed value used by the billowy-noise function. - int m_seed; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/blend.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/blend.h deleted file mode 100644 index 0c59ef6..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/blend.h +++ /dev/null @@ -1,144 +0,0 @@ -// blend.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_BLEND_H -#define NOISE_MODULE_BLEND_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @defgroup selectormodules Selector Modules - /// @addtogroup selectormodules - /// @{ - - /// Noise module that outputs a weighted blend of the output values from - /// two source modules given the output value supplied by a control module. - /// - /// @image html moduleblend.png - /// - /// Unlike most other noise modules, the index value assigned to a source - /// module determines its role in the blending operation: - /// - Source module 0 (upper left in the diagram) outputs one of the - /// values to blend. - /// - Source module 1 (lower left in the diagram) outputs one of the - /// values to blend. - /// - Source module 2 (bottom of the diagram) is known as the <i>control - /// module</i>. The control module determines the weight of the - /// blending operation. Negative values weigh the blend towards the - /// output value from the source module with an index value of 0. - /// Positive values weigh the blend towards the output value from the - /// source module with an index value of 1. - /// - /// An application can pass the control module to the SetControlModule() - /// method instead of the SetSourceModule() method. This may make the - /// application code easier to read. - /// - /// This noise module uses linear interpolation to perform the blending - /// operation. - /// - /// This noise module requires three source modules. - class DLL Blend : public Module - { - - public: - - /// Constructor. - Blend (); - - /// Returns the control module. - /// - /// @returns A reference to the control module. - /// - /// @pre A control module has been added to this noise module via a - /// call to SetSourceModule() or SetControlModule(). - /// - /// @throw noise::ExceptionNoModule See the preconditions for more - /// information. - /// - /// The control module determines the weight of the blending - /// operation. Negative values weigh the blend towards the output - /// value from the source module with an index value of 0. Positive - /// values weigh the blend towards the output value from the source - /// module with an index value of 1. - const Module& GetControlModule () const - { - if (m_pSourceModule == NULL || m_pSourceModule[2] == NULL) { - throw noise::ExceptionNoModule (); - } - return *(m_pSourceModule[2]); - } - - virtual int GetSourceModuleCount () const - { - return 3; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the control module. - /// - /// @param controlModule The control module. - /// - /// The control module determines the weight of the blending - /// operation. Negative values weigh the blend towards the output - /// value from the source module with an index value of 0. Positive - /// values weigh the blend towards the output value from the source - /// module with an index value of 1. - /// - /// This method assigns the control module an index value of 2. - /// Passing the control module to this method produces the same - /// results as passing the control module to the SetSourceModule() - /// method while assigning that noise module an index value of 2. - /// - /// This control module must exist throughout the lifetime of this - /// noise module unless another control module replaces that control - /// module. - void SetControlModule (const Module& controlModule) - { - assert (m_pSourceModule != NULL); - m_pSourceModule[2] = &controlModule; - } - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cache.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cache.h deleted file mode 100644 index 8aa2c14..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cache.h +++ /dev/null @@ -1,118 +0,0 @@ -// cache.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_CACHE_H -#define NOISE_MODULE_CACHE_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @defgroup miscmodules Miscellaneous Modules - /// @addtogroup miscmodules - /// @{ - - /// Noise module that caches the last output value generated by a source - /// module. - /// - /// If an application passes an input value to the GetValue() method that - /// differs from the previously passed-in input value, this noise module - /// instructs the source module to calculate the output value. This - /// value, as well as the ( @a x, @a y, @a z ) coordinates of the input - /// value, are stored (cached) in this noise module. - /// - /// If the application passes an input value to the GetValue() method - /// that is equal to the previously passed-in input value, this noise - /// module returns the cached output value without having the source - /// module recalculate the output value. - /// - /// If an application passes a new source module to the SetSourceModule() - /// method, the cache is invalidated. - /// - /// Caching a noise module is useful if it is used as a source module for - /// multiple noise modules. If a source module is not cached, the source - /// module will redundantly calculate the same output value once for each - /// noise module in which it is included. - /// - /// This noise module requires one source module. - class DLL Cache : public Module - { - - public: - - /// Constructor. - Cache (); - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - virtual void SetSourceModule (int index, const Module& sourceModule) - { - Module::SetSourceModule (index, sourceModule); - m_isCached = false; - } - - protected: - - /// The cached output value at the cached input value. - mutable double m_cachedValue; - - /// Determines if a cached output value is stored in this noise - /// module. - mutable double m_isCached; - - /// @a x coordinate of the cached input value. - mutable double m_xCache; - - /// @a y coordinate of the cached input value. - mutable double m_yCache; - - /// @a z coordinate of the cached input value. - mutable double m_zCache; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/checkerboard.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/checkerboard.h deleted file mode 100644 index 542aa42..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/checkerboard.h +++ /dev/null @@ -1,81 +0,0 @@ -// checkerboard.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_CHECKERBOARD_H -#define NOISE_MODULE_CHECKERBOARD_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup generatormodules - /// @{ - - /// Noise module that outputs a checkerboard pattern. - /// - /// @image html modulecheckerboard.png - /// - /// This noise module outputs unit-sized blocks of alternating values. - /// The values of these blocks alternate between -1.0 and +1.0. - /// - /// This noise module is not really useful by itself, but it is often used - /// for debugging purposes. - /// - /// This noise module does not require any source modules. - class DLL Checkerboard : public Module - { - - public: - - /// Constructor. - Checkerboard (); - - virtual int GetSourceModuleCount () const - { - return 0; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/clamp.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/clamp.h deleted file mode 100644 index 207666b..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/clamp.h +++ /dev/null @@ -1,152 +0,0 @@ -// clamp.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_CLAMP_H -#define NOISE_MODULE_CLAMP_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup modifiermodules - /// @{ - - /// Default lower bound of the clamping range for the noise::module::Clamp - /// noise module. - const double DEFAULT_CLAMP_LOWER_BOUND = -1.0; - - /// Default upper bound of the clamping range for the noise::module::Clamp - /// noise module. - const double DEFAULT_CLAMP_UPPER_BOUND = 1.0; - - /// Noise module that clamps the output value from a source module to a - /// range of values. - /// - /// @image html moduleclamp.png - /// - /// The range of values in which to clamp the output value is called the - /// <i>clamping range</i>. - /// - /// If the output value from the source module is less than the lower - /// bound of the clamping range, this noise module clamps that value to - /// the lower bound. If the output value from the source module is - /// greater than the upper bound of the clamping range, this noise module - /// clamps that value to the upper bound. - /// - /// To specify the upper and lower bounds of the clamping range, call the - /// SetBounds() method. - /// - /// This noise module requires one source module. - class DLL Clamp : public Module - { - - public: - - /// Constructor. - /// - /// The default lower bound of the clamping range is set to - /// noise::module::DEFAULT_CLAMP_LOWER_BOUND. - /// - /// The default upper bound of the clamping range is set to - /// noise::module::DEFAULT_CLAMP_UPPER_BOUND. - Clamp (); - - /// Returns the lower bound of the clamping range. - /// - /// @returns The lower bound. - /// - /// If the output value from the source module is less than the lower - /// bound of the clamping range, this noise module clamps that value - /// to the lower bound. - double GetLowerBound () const - { - return m_lowerBound; - } - - virtual int GetSourceModuleCount () const - { - return 1; - } - - /// Returns the upper bound of the clamping range. - /// - /// @returns The upper bound. - /// - /// If the output value from the source module is greater than the - /// upper bound of the clamping range, this noise module clamps that - /// value to the upper bound. - double GetUpperBound () const - { - return m_upperBound; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the lower and upper bounds of the clamping range. - /// - /// @param lowerBound The lower bound. - /// @param upperBound The upper bound. - /// - /// @pre The lower bound must be less than or equal to the - /// upper bound. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// If the output value from the source module is less than the lower - /// bound of the clamping range, this noise module clamps that value - /// to the lower bound. If the output value from the source module - /// is greater than the upper bound of the clamping range, this noise - /// module clamps that value to the upper bound. - void SetBounds (double lowerBound, double upperBound); - - protected: - - /// Lower bound of the clamping range. - double m_lowerBound; - - /// Upper bound of the clamping range. - double m_upperBound; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/const.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/const.h deleted file mode 100644 index 30290c3..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/const.h +++ /dev/null @@ -1,111 +0,0 @@ -// const.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_CONST_H -#define NOISE_MODULE_CONST_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @defgroup generatormodules Generator Modules - /// @addtogroup generatormodules - /// @{ - - /// Default constant value for the noise::module::Const noise module. - const double DEFAULT_CONST_VALUE = 0.0; - - /// Noise module that outputs a constant value. - /// - /// @image html moduleconst.png - /// - /// To specify the constant value, call the SetConstValue() method. - /// - /// This noise module is not useful by itself, but it is often used as a - /// source module for other noise modules. - /// - /// This noise module does not require any source modules. - class DLL Const : public Module - { - - public: - - /// Constructor. - /// - /// The default constant value is set to - /// noise::module::DEFAULT_CONST_VALUE. - Const (); - - /// Returns the constant output value for this noise module. - /// - /// @returns The constant output value for this noise module. - double GetConstValue () const - { - return m_constValue; - } - - virtual int GetSourceModuleCount () const - { - return 0; - } - - virtual double GetValue (double x, double y, double z) const - { - return m_constValue; - } - - /// Sets the constant output value for this noise module. - /// - /// @param constValue The constant output value for this noise module. - void SetConstValue (double constValue) - { - m_constValue = constValue; - } - - protected: - - /// Constant value. - double m_constValue; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/curve.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/curve.h deleted file mode 100644 index 37c840c..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/curve.h +++ /dev/null @@ -1,197 +0,0 @@ -// curve.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_CURVE_H -#define NOISE_MODULE_CURVE_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// This structure defines a control point. - /// - /// Control points are used for defining splines. - struct ControlPoint - { - - /// The input value. - double inputValue; - - /// The output value that is mapped from the input value. - double outputValue; - - }; - - /// @addtogroup modules - /// @{ - - /// @addtogroup modifiermodules - /// @{ - - /// Noise module that maps the output value from a source module onto an - /// arbitrary function curve. - /// - /// @image html modulecurve.png - /// - /// This noise module maps the output value from the source module onto an - /// application-defined curve. This curve is defined by a number of - /// <i>control points</i>; each control point has an <i>input value</i> - /// that maps to an <i>output value</i>. Refer to the following - /// illustration: - /// - /// @image html curve.png - /// - /// To add the control points to this curve, call the AddControlPoint() - /// method. - /// - /// Since this curve is a cubic spline, an application must add a minimum - /// of four control points to the curve. If this is not done, the - /// GetValue() method fails. Each control point can have any input and - /// output value, although no two control points can have the same input - /// value. There is no limit to the number of control points that can be - /// added to the curve. - /// - /// This noise module requires one source module. - class DLL Curve : public Module - { - - public: - - /// Constructor. - Curve (); - - /// Destructor. - ~Curve (); - - /// Adds a control point to the curve. - /// - /// @param inputValue The input value stored in the control point. - /// @param outputValue The output value stored in the control point. - /// - /// @pre No two control points have the same input value. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// It does not matter which order these points are added. - void AddControlPoint (double inputValue, double outputValue); - - /// Deletes all the control points on the curve. - /// - /// @post All points on the curve are deleted. - void ClearAllControlPoints (); - - /// Returns a pointer to the array of control points on the curve. - /// - /// @returns A pointer to the array of control points. - /// - /// Before calling this method, call GetControlPointCount() to - /// determine the number of control points in this array. - /// - /// It is recommended that an application does not store this pointer - /// for later use since the pointer to the array may change if the - /// application calls another method of this object. - const ControlPoint* GetControlPointArray () const - { - return m_pControlPoints; - } - - /// Returns the number of control points on the curve. - /// - /// @returns The number of control points on the curve. - int GetControlPointCount () const - { - return m_controlPointCount; - } - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - protected: - - /// Determines the array index in which to insert the control point - /// into the internal control point array. - /// - /// @param inputValue The input value of the control point. - /// - /// @returns The array index in which to insert the control point. - /// - /// @pre No two control points have the same input value. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// By inserting the control point at the returned array index, this - /// class ensures that the control point array is sorted by input - /// value. The code that maps a value onto the curve requires a - /// sorted control point array. - int FindInsertionPos (double inputValue); - - /// Inserts the control point at the specified position in the - /// internal control point array. - /// - /// @param insertionPos The zero-based array position in which to - /// insert the control point. - /// @param inputValue The input value stored in the control point. - /// @param outputValue The output value stored in the control point. - /// - /// To make room for this new control point, this method reallocates - /// the control point array and shifts all control points occurring - /// after the insertion position up by one. - /// - /// Because the curve mapping algorithm used by this noise module - /// requires that all control points in the array must be sorted by - /// input value, the new control point should be inserted at the - /// position in which the order is still preserved. - void InsertAtPos (int insertionPos, double inputValue, - double outputValue); - - /// Number of control points on the curve. - int m_controlPointCount; - - /// Array that stores the control points. - ControlPoint* m_pControlPoints; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cylinders.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cylinders.h deleted file mode 100644 index 759722e..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/cylinders.h +++ /dev/null @@ -1,129 +0,0 @@ -// cylinders.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_CYLINDERS_H -#define NOISE_MODULE_CYLINDERS_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup generatormodules - /// @{ - - /// Default frequency value for the noise::module::Cylinders noise module. - const double DEFAULT_CYLINDERS_FREQUENCY = 1.0; - - /// Noise module that outputs concentric cylinders. - /// - /// @image html modulecylinders.png - /// - /// This noise module outputs concentric cylinders centered on the origin. - /// These cylinders are oriented along the @a y axis similar to the - /// concentric rings of a tree. Each cylinder extends infinitely along - /// the @a y axis. - /// - /// The first cylinder has a radius of 1.0. Each subsequent cylinder has - /// a radius that is 1.0 unit larger than the previous cylinder. - /// - /// The output value from this noise module is determined by the distance - /// between the input value and the the nearest cylinder surface. The - /// input values that are located on a cylinder surface are given the - /// output value 1.0 and the input values that are equidistant from two - /// cylinder surfaces are given the output value -1.0. - /// - /// An application can change the frequency of the concentric cylinders. - /// Increasing the frequency reduces the distances between cylinders. To - /// specify the frequency, call the SetFrequency() method. - /// - /// This noise module, modified with some low-frequency, low-power - /// turbulence, is useful for generating wood-like textures. - /// - /// This noise module does not require any source modules. - class DLL Cylinders : public Module - { - - public: - - /// Constructor. - /// - /// The default frequency is set to - /// noise::module::DEFAULT_CYLINDERS_FREQUENCY. - Cylinders (); - - /// Returns the frequency of the concentric cylinders. - /// - /// @returns The frequency of the concentric cylinders. - /// - /// Increasing the frequency increases the density of the concentric - /// cylinders, reducing the distances between them. - double GetFrequency () const - { - return m_frequency; - } - - virtual int GetSourceModuleCount () const - { - return 0; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the frequenct of the concentric cylinders. - /// - /// @param frequency The frequency of the concentric cylinders. - /// - /// Increasing the frequency increases the density of the concentric - /// cylinders, reducing the distances between them. - void SetFrequency (double frequency) - { - m_frequency = frequency; - } - - protected: - - /// Frequency of the concentric cylinders. - double m_frequency; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/displace.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/displace.h deleted file mode 100644 index 2481674..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/displace.h +++ /dev/null @@ -1,259 +0,0 @@ -// displace.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_DISPLACE_H -#define NOISE_MODULE_DISPLACE_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @defgroup transformermodules Transformer Modules - /// @addtogroup transformermodules - /// @{ - - /// Noise module that uses three source modules to displace each - /// coordinate of the input value before returning the output value from - /// a source module. - /// - /// @image html moduledisplace.png - /// - /// Unlike most other noise modules, the index value assigned to a source - /// module determines its role in the displacement operation: - /// - Source module 0 (left in the diagram) outputs a value. - /// - Source module 1 (lower left in the diagram) specifies the offset to - /// apply to the @a x coordinate of the input value. - /// - Source module 2 (lower center in the diagram) specifies the - /// offset to apply to the @a y coordinate of the input value. - /// - Source module 3 (lower right in the diagram) specifies the offset - /// to apply to the @a z coordinate of the input value. - /// - /// The GetValue() method modifies the ( @a x, @a y, @a z ) coordinates of - /// the input value using the output values from the three displacement - /// modules before retrieving the output value from the source module. - /// - /// The noise::module::Turbulence noise module is a special case of the - /// displacement module; internally, there are three Perlin-noise modules - /// that perform the displacement operation. - /// - /// This noise module requires four source modules. - class DLL Displace : public Module - { - - public: - - /// Constructor. - Displace (); - - virtual int GetSourceModuleCount () const - { - return 4; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Returns the @a x displacement module. - /// - /// @returns A reference to the @a x displacement module. - /// - /// @pre This displacement module has been added to this noise module - /// via a call to SetSourceModule() or SetXDisplaceModule(). - /// - /// @throw noise::ExceptionNoModule See the preconditions for more - /// information. - /// - /// The GetValue() method displaces the input value by adding the output - /// value from this displacement module to the @a x coordinate of the - /// input value before returning the output value from the source - /// module. - const Module& GetXDisplaceModule () const - { - if (m_pSourceModule == NULL || m_pSourceModule[1] == NULL) { - throw noise::ExceptionNoModule (); - } - return *(m_pSourceModule[1]); - } - - /// Returns the @a y displacement module. - /// - /// @returns A reference to the @a y displacement module. - /// - /// @pre This displacement module has been added to this noise module - /// via a call to SetSourceModule() or SetYDisplaceModule(). - /// - /// @throw noise::ExceptionNoModule See the preconditions for more - /// information. - /// - /// The GetValue() method displaces the input value by adding the output - /// value from this displacement module to the @a y coordinate of the - /// input value before returning the output value from the source - /// module. - const Module& GetYDisplaceModule () const - { - if (m_pSourceModule == NULL || m_pSourceModule[2] == NULL) { - throw noise::ExceptionNoModule (); - } - return *(m_pSourceModule[2]); - } - - /// Returns the @a z displacement module. - /// - /// @returns A reference to the @a z displacement module. - /// - /// @pre This displacement module has been added to this noise module - /// via a call to SetSourceModule() or SetZDisplaceModule(). - /// - /// @throw noise::ExceptionNoModule See the preconditions for more - /// information. - /// - /// The GetValue() method displaces the input value by adding the output - /// value from this displacement module to the @a z coordinate of the - /// input value before returning the output value from the source - /// module. - const Module& GetZDisplaceModule () const - { - if (m_pSourceModule == NULL || m_pSourceModule[3] == NULL) { - throw noise::ExceptionNoModule (); - } - return *(m_pSourceModule[3]); - } - - /// Sets the @a x, @a y, and @a z displacement modules. - /// - /// @param xDisplaceModule Displacement module that displaces the @a x - /// coordinate of the input value. - /// @param yDisplaceModule Displacement module that displaces the @a y - /// coordinate of the input value. - /// @param zDisplaceModule Displacement module that displaces the @a z - /// coordinate of the input value. - /// - /// The GetValue() method displaces the input value by adding the output - /// value from each of the displacement modules to the corresponding - /// coordinates of the input value before returning the output value - /// from the source module. - /// - /// This method assigns an index value of 1 to the @a x displacement - /// module, an index value of 2 to the @a y displacement module, and an - /// index value of 3 to the @a z displacement module. - /// - /// These displacement modules must exist throughout the lifetime of - /// this noise module unless another displacement module replaces it. - void SetDisplaceModules (const Module& xDisplaceModule, - const Module& yDisplaceModule, const Module& zDisplaceModule) - { - SetXDisplaceModule (xDisplaceModule); - SetYDisplaceModule (yDisplaceModule); - SetZDisplaceModule (zDisplaceModule); - } - - /// Sets the @a x displacement module. - /// - /// @param xDisplaceModule Displacement module that displaces the @a x - /// coordinate. - /// - /// The GetValue() method displaces the input value by adding the output - /// value from this displacement module to the @a x coordinate of the - /// input value before returning the output value from the source - /// module. - /// - /// This method assigns an index value of 1 to the @a x displacement - /// module. Passing this displacement module to this method produces - /// the same results as passing this displacement module to the - /// SetSourceModule() method while assigning it an index value of 1. - /// - /// This displacement module must exist throughout the lifetime of this - /// noise module unless another displacement module replaces it. - void SetXDisplaceModule (const Module& xDisplaceModule) - { - assert (m_pSourceModule != NULL); - m_pSourceModule[1] = &xDisplaceModule; - } - - /// Sets the @a y displacement module. - /// - /// @param yDisplaceModule Displacement module that displaces the @a y - /// coordinate. - /// - /// The GetValue() method displaces the input value by adding the output - /// value from this displacement module to the @a y coordinate of the - /// input value before returning the output value from the source - /// module. - /// - /// This method assigns an index value of 2 to the @a y displacement - /// module. Passing this displacement module to this method produces - /// the same results as passing this displacement module to the - /// SetSourceModule() method while assigning it an index value of 2. - /// - /// This displacement module must exist throughout the lifetime of this - /// noise module unless another displacement module replaces it. - void SetYDisplaceModule (const Module& yDisplaceModule) - { - assert (m_pSourceModule != NULL); - m_pSourceModule[2] = &yDisplaceModule; - } - - /// Sets the @a z displacement module. - /// - /// @param zDisplaceModule Displacement module that displaces the @a z - /// coordinate. - /// - /// The GetValue() method displaces the input value by adding the output - /// value from this displacement module to the @a z coordinate of the - /// input value before returning the output value from the source - /// module. - /// - /// This method assigns an index value of 3 to the @a z displacement - /// module. Passing this displacement module to this method produces - /// the same results as passing this displacement module to the - /// SetSourceModule() method while assigning it an index value of 3. - /// - /// This displacement module must exist throughout the lifetime of this - /// noise module unless another displacement module replaces it. - void SetZDisplaceModule (const Module& zDisplaceModule) - { - assert (m_pSourceModule != NULL); - m_pSourceModule[3] = &zDisplaceModule; - } - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/exponent.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/exponent.h deleted file mode 100644 index fed4f21..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/exponent.h +++ /dev/null @@ -1,119 +0,0 @@ -// exponent.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_EXPONENT_H -#define NOISE_MODULE_EXPONENT_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup modifiermodules - /// @{ - - /// Default exponent for the noise::module::Exponent noise module. - const double DEFAULT_EXPONENT = 1.0; - - /// Noise module that maps the output value from a source module onto an - /// exponential curve. - /// - /// @image html moduleexponent.png - /// - /// Because most noise modules will output values that range from -1.0 to - /// +1.0, this noise module first normalizes this output value (the range - /// becomes 0.0 to 1.0), maps that value onto an exponential curve, then - /// rescales that value back to the original range. - /// - /// This noise module requires one source module. - class DLL Exponent : public Module - { - - public: - - /// Constructor. - /// - /// The default exponent is set to noise::module::DEFAULT_EXPONENT. - Exponent (); - - /// Returns the exponent value to apply to the output value from the - /// source module. - /// - /// @returns The exponent value. - /// - /// Because most noise modules will output values that range from -1.0 - /// to +1.0, this noise module first normalizes this output value (the - /// range becomes 0.0 to 1.0), maps that value onto an exponential - /// curve, then rescales that value back to the original range. - double GetExponent () const - { - return m_exponent; - } - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the exponent value to apply to the output value from the - /// source module. - /// - /// @param exponent The exponent value. - /// - /// Because most noise modules will output values that range from -1.0 - /// to +1.0, this noise module first normalizes this output value (the - /// range becomes 0.0 to 1.0), maps that value onto an exponential - /// curve, then rescales that value back to the original range. - void SetExponent (double exponent) - { - m_exponent = exponent; - } - - protected: - - /// Exponent to apply to the output value from the source module. - double m_exponent; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/invert.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/invert.h deleted file mode 100644 index f6be5a5..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/invert.h +++ /dev/null @@ -1,75 +0,0 @@ -// invert.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_INVERT_H -#define NOISE_MODULE_INVERT_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup modifiermodules - /// @{ - - /// Noise module that inverts the output value from a source module. - /// - /// @image html moduleinvert.png - /// - /// This noise module requires one source module. - class DLL Invert : public Module - { - - public: - - /// Constructor. - Invert (); - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/max.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/max.h deleted file mode 100644 index 7c9f039..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/max.h +++ /dev/null @@ -1,76 +0,0 @@ -// max.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_MAX_H -#define NOISE_MODULE_MAX_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup combinermodules - /// @{ - - /// Noise module that outputs the larger of the two output values from two - /// source modules. - /// - /// @image html modulemax.png - /// - /// This noise module requires two source modules. - class DLL Max : public Module - { - - public: - - /// Constructor. - Max (); - - virtual int GetSourceModuleCount () const - { - return 2; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/min.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/min.h deleted file mode 100644 index 1778ae2..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/min.h +++ /dev/null @@ -1,76 +0,0 @@ -// min.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_MIN_H -#define NOISE_MODULE_MIN_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup combinermodules - /// @{ - - /// Noise module that outputs the smaller of the two output values from - /// two source modules. - /// - /// @image html modulemin.png - /// - /// This noise module requires two source modules. - class DLL Min : public Module - { - - public: - - /// Constructor. - Min (); - - virtual int GetSourceModuleCount () const - { - return 2; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/module.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/module.h deleted file mode 100644 index 5994095..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/module.h +++ /dev/null @@ -1,55 +0,0 @@ -// module.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_H -#define NOISE_MODULE_H - -#include "add.h" -#include "abs.h" -#include "billow.h" -#include "blend.h" -#include "cache.h" -#include "checkerboard.h" -#include "clamp.h" -#include "const.h" -#include "curve.h" -#include "cylinders.h" -#include "displace.h" -#include "exponent.h" -#include "invert.h" -#include "max.h" -#include "min.h" -#include "multiply.h" -#include "perlin.h" -#include "power.h" -#include "ridgedmulti.h" -#include "rotatepoint.h" -#include "scalebias.h" -#include "scalepoint.h" -#include "select.h" -#include "spheres.h" -#include "terrace.h" -#include "translatepoint.h" -#include "turbulence.h" -#include "voronoi.h" - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/modulebase.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/modulebase.h deleted file mode 100644 index 25b84e8..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/modulebase.h +++ /dev/null @@ -1,366 +0,0 @@ -// modulebase.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULEBASE_H -#define NOISE_MODULEBASE_H - -#include <stdlib.h> -#include <assert.h> -#include <math.h> -#include "../basictypes.h" -#include "../exception.h" -#include "../noisegen.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @defgroup modules Noise Modules - /// @addtogroup modules - /// @{ - - /// Abstract base class for noise modules. - /// - /// A <i>noise module</i> is an object that calculates and outputs a value - /// given a three-dimensional input value. - /// - /// Each type of noise module uses a specific method to calculate an - /// output value. Some of these methods include: - /// - /// - Calculating a value using a coherent-noise function or some other - /// mathematical function. - /// - Mathematically changing the output value from another noise module - /// in various ways. - /// - Combining the output values from two noise modules in various ways. - /// - /// An application can use the output values from these noise modules in - /// the following ways: - /// - /// - It can be used as an elevation value for a terrain height map - /// - It can be used as a grayscale (or an RGB-channel) value for a - /// procedural texture - /// - It can be used as a position value for controlling the movement of a - /// simulated lifeform. - /// - /// A noise module defines a near-infinite 3-dimensional texture. Each - /// position in this "texture" has a specific value. - /// - /// <b>Combining noise modules</b> - /// - /// Noise modules can be combined with other noise modules to generate - /// complex output values. A noise module that is used as a source of - /// output values for another noise module is called a <i>source - /// module</i>. Each of these source modules may be connected to other - /// source modules, and so on. - /// - /// There is no limit to the number of noise modules that can be connected - /// together in this way. However, each connected noise module increases - /// the time required to calculate an output value. - /// - /// <b>Noise-module categories</b> - /// - /// The noise module classes that are included in libnoise can be roughly - /// divided into five categories. - /// - /// <i>Generator Modules</i> - /// - /// A generator module outputs a value generated by a coherent-noise - /// function or some other mathematical function. - /// - /// Examples of generator modules include: - /// - noise::module::Const: Outputs a constant value. - /// - noise::module::Perlin: Outputs a value generated by a Perlin-noise - /// function. - /// - noise::module::Voronoi: Outputs a value generated by a Voronoi-cell - /// function. - /// - /// <i>Modifier Modules</i> - /// - /// A modifer module mathematically modifies the output value from a - /// source module. - /// - /// Examples of modifier modules include: - /// - noise::module::Curve: Maps the output value from the source module - /// onto an arbitrary function curve. - /// - noise::module::Invert: Inverts the output value from the source - /// module. - /// - /// <i>Combiner Modules</i> - /// - /// A combiner module mathematically combines the output values from two - /// or more source modules together. - /// - /// Examples of combiner modules include: - /// - noise::module::Add: Adds the two output values from two source - /// modules. - /// - noise::module::Max: Outputs the larger of the two output values from - /// two source modules. - /// - /// <i>Selector Modules</i> - /// - /// A selector module uses the output value from a <i>control module</i> - /// to specify how to combine the output values from its source modules. - /// - /// Examples of selector modules include: - /// - noise::module::Blend: Outputs a value that is linearly interpolated - /// between the output values from two source modules; the interpolation - /// weight is determined by the output value from the control module. - /// - noise::module::Select: Outputs the value selected from one of two - /// source modules chosen by the output value from a control module. - /// - /// <i>Transformer Modules</i> - /// - /// A transformer module applies a transformation to the coordinates of - /// the input value before retrieving the output value from the source - /// module. A transformer module does not modify the output value. - /// - /// Examples of transformer modules include: - /// - RotatePoint: Rotates the coordinates of the input value around the - /// origin before retrieving the output value from the source module. - /// - ScalePoint: Multiplies each coordinate of the input value by a - /// constant value before retrieving the output value from the source - /// module. - /// - /// <b>Connecting source modules to a noise module</b> - /// - /// An application connects a source module to a noise module by passing - /// the source module to the SetSourceModule() method. - /// - /// The application must also pass an <i>index value</i> to - /// SetSourceModule() as well. An index value is a numeric identifier for - /// that source module. Index values are consecutively numbered starting - /// at zero. - /// - /// To retrieve a reference to a source module, pass its index value to - /// the GetSourceModule() method. - /// - /// Each noise module requires the attachment of a certain number of - /// source modules before it can output a value. For example, the - /// noise::module::Add module requires two source modules, while the - /// noise::module::Perlin module requires none. Call the - /// GetSourceModuleCount() method to retrieve the number of source modules - /// required by that module. - /// - /// For non-selector modules, it usually does not matter which index value - /// an application assigns to a particular source module, but for selector - /// modules, the purpose of a source module is defined by its index value. - /// For example, consider the noise::module::Select noise module, which - /// requires three source modules. The control module is the source - /// module assigned an index value of 2. The control module determines - /// whether the noise module will output the value from the source module - /// assigned an index value of 0 or the output value from the source - /// module assigned an index value of 1. - /// - /// <b>Generating output values with a noise module</b> - /// - /// Once an application has connected all required source modules to a - /// noise module, the application can now begin to generate output values - /// with that noise module. - /// - /// To generate an output value, pass the ( @a x, @a y, @a z ) coordinates - /// of an input value to the GetValue() method. - /// - /// <b>Using a noise module to generate terrain height maps or textures</b> - /// - /// One way to generate a terrain height map or a texture is to first - /// allocate a 2-dimensional array of floating-point values. For each - /// array element, pass the array subscripts as @a x and @a y coordinates - /// to the GetValue() method (leaving the @a z coordinate set to zero) and - /// place the resulting output value into the array element. - /// - /// <b>Creating your own noise modules</b> - /// - /// Create a class that publicly derives from noise::module::Module. - /// - /// In the constructor, call the base class' constructor while passing the - /// return value from GetSourceModuleCount() to it. - /// - /// Override the GetSourceModuleCount() pure virtual method. From this - /// method, return the number of source modules required by your noise - /// module. - /// - /// Override the GetValue() pure virtual method. For generator modules, - /// calculate and output a value given the coordinates of the input value. - /// For other modules, retrieve the output values from each source module - /// referenced in the protected @a m_pSourceModule array, mathematically - /// combine those values, and return the combined value. - /// - /// When developing a noise module, you must ensure that your noise module - /// does not modify any source module or control module connected to it; a - /// noise module can only modify the output value from those source - /// modules. You must also ensure that if an application fails to connect - /// all required source modules via the SetSourceModule() method and then - /// attempts to call the GetValue() method, your module will raise an - /// assertion. - /// - /// It shouldn't be too difficult to create your own noise module. If you - /// still have some problems, take a look at the source code for - /// noise::module::Add, which is a very simple noise module. -class DLL Module - { - - public: - - /// Constructor. - Module (int sourceModuleCount); - - /// Destructor. - virtual ~Module (); - - /// Returns a reference to a source module connected to this noise - /// module. - /// - /// @param index The index value assigned to the source module. - /// - /// @returns A reference to the source module. - /// - /// @pre The index value ranges from 0 to one less than the number of - /// source modules required by this noise module. - /// @pre A source module with the specified index value has been added - /// to this noise module via a call to SetSourceModule(). - /// - /// @throw noise::ExceptionNoModule See the preconditions for more - /// information. - /// - /// Each noise module requires the attachment of a certain number of - /// source modules before an application can call the GetValue() - /// method. - virtual const Module& GetSourceModule (int index) const - { - assert (m_pSourceModule != NULL); - - // The following fix was provided by Will Hawkins: - // - // m_pSourceModule[index] != NULL - // - // was incorrect; it should be: - // - // m_pSourceModule[index] == NULL - if (index >= GetSourceModuleCount () || index < 0 - || m_pSourceModule[index] == NULL) { - throw noise::ExceptionNoModule (); - } - return *(m_pSourceModule[index]); - } - - /// Returns the number of source modules required by this noise - /// module. - /// - /// @returns The number of source modules required by this noise - /// module. - virtual int GetSourceModuleCount () const = 0; - - /// Generates an output value given the coordinates of the specified - /// input value. - /// - /// @param x The @a x coordinate of the input value. - /// @param y The @a y coordinate of the input value. - /// @param z The @a z coordinate of the input value. - /// - /// @returns The output value. - /// - /// @pre All source modules required by this noise module have been - /// passed to the SetSourceModule() method. - /// - /// Before an application can call this method, it must first connect - /// all required source modules via the SetSourceModule() method. If - /// these source modules are not connected to this noise module, this - /// method raises a debug assertion. - /// - /// To determine the number of source modules required by this noise - /// module, call the GetSourceModuleCount() method. - virtual double GetValue (double x, double y, double z) const = 0; - - /// Connects a source module to this noise module. - /// - /// @param index An index value to assign to this source module. - /// @param sourceModule The source module to attach. - /// - /// @pre The index value ranges from 0 to one less than the number of - /// source modules required by this noise module. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// A noise module mathematically combines the output values from the - /// source modules to generate the value returned by GetValue(). - /// - /// The index value to assign a source module is a unique identifier - /// for that source module. If an index value has already been - /// assigned to a source module, this noise module replaces the old - /// source module with the new source module. - /// - /// Before an application can call the GetValue() method, it must - /// first connect all required source modules. To determine the - /// number of source modules required by this noise module, call the - /// GetSourceModuleCount() method. - /// - /// This source module must exist throughout the lifetime of this - /// noise module unless another source module replaces that source - /// module. - /// - /// A noise module does not modify a source module; it only modifies - /// its output values. - virtual void SetSourceModule (int index, const Module& sourceModule) - { - assert (m_pSourceModule != NULL); - if (index >= GetSourceModuleCount () || index < 0) { - throw noise::ExceptionInvalidParam (); - } - m_pSourceModule[index] = &sourceModule; - } - - protected: - - /// An array containing the pointers to each source module required by - /// this noise module. - const Module** m_pSourceModule; - - private: - - /// Assignment operator. - /// - /// This assignment operator does nothing and cannot be overridden. - /// This restriction is necessary because if this object was copied, - /// all source modules assigned to this noise module would need to be - /// copied as well. - const Module& operator= (const Module& m) - { - return *this; - } - - }; - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/multiply.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/multiply.h deleted file mode 100644 index da04eac..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/multiply.h +++ /dev/null @@ -1,76 +0,0 @@ -// multiply.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_MULTIPLY_H -#define NOISE_MODULE_MULTIPLY_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup combinermodules - /// @{ - - /// Noise module that outputs the product of the two output values from - /// two source modules. - /// - /// @image html modulemultiply.png - /// - /// This noise module requires two source modules. - class DLL Multiply : public Module - { - - public: - - /// Constructor. - Multiply (); - - virtual int GetSourceModuleCount () const - { - return 2; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/perlin.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/perlin.h deleted file mode 100644 index 3fac5e3..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/perlin.h +++ /dev/null @@ -1,359 +0,0 @@ -// perlin.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_PERLIN_H -#define NOISE_MODULE_PERLIN_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup generatormodules - /// @{ - - /// Default frequency for the noise::module::Perlin noise module. - const double DEFAULT_PERLIN_FREQUENCY = 1.0; - - /// Default lacunarity for the noise::module::Perlin noise module. - const double DEFAULT_PERLIN_LACUNARITY = 2.0; - - /// Default number of octaves for the noise::module::Perlin noise module. - const int DEFAULT_PERLIN_OCTAVE_COUNT = 6; - - /// Default persistence value for the noise::module::Perlin noise module. - const double DEFAULT_PERLIN_PERSISTENCE = 0.5; - - /// Default noise quality for the noise::module::Perlin noise module. - const noise::NoiseQuality DEFAULT_PERLIN_QUALITY = QUALITY_STD; - - /// Default noise seed for the noise::module::Perlin noise module. - const int DEFAULT_PERLIN_SEED = 0; - - /// Maximum number of octaves for the noise::module::Perlin noise module. - const int PERLIN_MAX_OCTAVE = 30; - - /// Noise module that outputs 3-dimensional Perlin noise. - /// - /// @image html moduleperlin.png - /// - /// Perlin noise is the sum of several coherent-noise functions of - /// ever-increasing frequencies and ever-decreasing amplitudes. - /// - /// An important property of Perlin noise is that a small change in the - /// input value will produce a small change in the output value, while a - /// large change in the input value will produce a random change in the - /// output value. - /// - /// This noise module outputs Perlin-noise values that usually range from - /// -1.0 to +1.0, but there are no guarantees that all output values will - /// exist within that range. - /// - /// For a better description of Perlin noise, see the links in the - /// <i>References and Acknowledgments</i> section. - /// - /// This noise module does not require any source modules. - /// - /// <b>Octaves</b> - /// - /// The number of octaves control the <i>amount of detail</i> of the - /// Perlin noise. Adding more octaves increases the detail of the Perlin - /// noise, but with the drawback of increasing the calculation time. - /// - /// An octave is one of the coherent-noise functions in a series of - /// coherent-noise functions that are added together to form Perlin - /// noise. - /// - /// An application may specify the frequency of the first octave by - /// calling the SetFrequency() method. - /// - /// An application may specify the number of octaves that generate Perlin - /// noise by calling the SetOctaveCount() method. - /// - /// These coherent-noise functions are called octaves because each octave - /// has, by default, double the frequency of the previous octave. Musical - /// tones have this property as well; a musical C tone that is one octave - /// higher than the previous C tone has double its frequency. - /// - /// <b>Frequency</b> - /// - /// An application may specify the frequency of the first octave by - /// calling the SetFrequency() method. - /// - /// <b>Persistence</b> - /// - /// The persistence value controls the <i>roughness</i> of the Perlin - /// noise. Larger values produce rougher noise. - /// - /// The persistence value determines how quickly the amplitudes diminish - /// for successive octaves. The amplitude of the first octave is 1.0. - /// The amplitude of each subsequent octave is equal to the product of the - /// previous octave's amplitude and the persistence value. So a - /// persistence value of 0.5 sets the amplitude of the first octave to - /// 1.0; the second, 0.5; the third, 0.25; etc. - /// - /// An application may specify the persistence value by calling the - /// SetPersistence() method. - /// - /// <b>Lacunarity</b> - /// - /// The lacunarity specifies the frequency multipler between successive - /// octaves. - /// - /// The effect of modifying the lacunarity is subtle; you may need to play - /// with the lacunarity value to determine the effects. For best results, - /// set the lacunarity to a number between 1.5 and 3.5. - /// - /// <b>References & acknowledgments</b> - /// - /// <a href=http://www.noisemachine.com/talk1/>The Noise Machine</a> - - /// From the master, Ken Perlin himself. This page contains a - /// presentation that describes Perlin noise and some of its variants. - /// He won an Oscar for creating the Perlin noise algorithm! - /// - /// <a - /// href=http://freespace.virgin.net/hugo.elias/models/m_perlin.htm> - /// Perlin Noise</a> - Hugo Elias's webpage contains a very good - /// description of Perlin noise and describes its many applications. This - /// page gave me the inspiration to create libnoise in the first place. - /// Now that I know how to generate Perlin noise, I will never again use - /// cheesy subdivision algorithms to create terrain (unless I absolutely - /// need the speed.) - /// - /// <a - /// href=http://www.robo-murito.net/code/perlin-noise-math-faq.html>The - /// Perlin noise math FAQ</a> - A good page that describes Perlin noise in - /// plain English with only a minor amount of math. During development of - /// libnoise, I noticed that my coherent-noise function generated terrain - /// with some "regularity" to the terrain features. This page describes a - /// better coherent-noise function called <i>gradient noise</i>. This - /// version of noise::module::Perlin uses gradient coherent noise to - /// generate Perlin noise. - class DLL Perlin : public Module - { - - public: - - /// Constructor. - /// - /// The default frequency is set to - /// noise::module::DEFAULT_PERLIN_FREQUENCY. - /// - /// The default lacunarity is set to - /// noise::module::DEFAULT_PERLIN_LACUNARITY. - /// - /// The default number of octaves is set to - /// noise::module::DEFAULT_PERLIN_OCTAVE_COUNT. - /// - /// The default persistence value is set to - /// noise::module::DEFAULT_PERLIN_PERSISTENCE. - /// - /// The default seed value is set to - /// noise::module::DEFAULT_PERLIN_SEED. - Perlin (); - - /// Returns the frequency of the first octave. - /// - /// @returns The frequency of the first octave. - double GetFrequency () const - { - return m_frequency; - } - - /// Returns the lacunarity of the Perlin noise. - /// - /// @returns The lacunarity of the Perlin noise. - /// - /// The lacunarity is the frequency multiplier between successive - /// octaves. - double GetLacunarity () const - { - return m_lacunarity; - } - - /// Returns the quality of the Perlin noise. - /// - /// @returns The quality of the Perlin noise. - /// - /// See noise::NoiseQuality for definitions of the various - /// coherent-noise qualities. - noise::NoiseQuality GetNoiseQuality () const - { - return m_noiseQuality; - } - - /// Returns the number of octaves that generate the Perlin noise. - /// - /// @returns The number of octaves that generate the Perlin noise. - /// - /// The number of octaves controls the amount of detail in the Perlin - /// noise. - int GetOctaveCount () const - { - return m_octaveCount; - } - - /// Returns the persistence value of the Perlin noise. - /// - /// @returns The persistence value of the Perlin noise. - /// - /// The persistence value controls the roughness of the Perlin noise. - double GetPersistence () const - { - return m_persistence; - } - - /// Returns the seed value used by the Perlin-noise function. - /// - /// @returns The seed value. - int GetSeed () const - { - return m_seed; - } - - virtual int GetSourceModuleCount () const - { - return 0; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the frequency of the first octave. - /// - /// @param frequency The frequency of the first octave. - void SetFrequency (double frequency) - { - m_frequency = frequency; - } - - /// Sets the lacunarity of the Perlin noise. - /// - /// @param lacunarity The lacunarity of the Perlin noise. - /// - /// The lacunarity is the frequency multiplier between successive - /// octaves. - /// - /// For best results, set the lacunarity to a number between 1.5 and - /// 3.5. - void SetLacunarity (double lacunarity) - { - m_lacunarity = lacunarity; - } - - /// Sets the quality of the Perlin noise. - /// - /// @param noiseQuality The quality of the Perlin noise. - /// - /// See noise::NoiseQuality for definitions of the various - /// coherent-noise qualities. - void SetNoiseQuality (noise::NoiseQuality noiseQuality) - { - m_noiseQuality = noiseQuality; - } - - /// Sets the number of octaves that generate the Perlin noise. - /// - /// @param octaveCount The number of octaves that generate the Perlin - /// noise. - /// - /// @pre The number of octaves ranges from 1 to - /// noise::module::PERLIN_MAX_OCTAVE. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// The number of octaves controls the amount of detail in the Perlin - /// noise. - /// - /// The larger the number of octaves, the more time required to - /// calculate the Perlin-noise value. - void SetOctaveCount (int octaveCount) - { - if (octaveCount < 1 || octaveCount > PERLIN_MAX_OCTAVE) { - throw noise::ExceptionInvalidParam (); - } - m_octaveCount = octaveCount; - } - - /// Sets the persistence value of the Perlin noise. - /// - /// @param persistence The persistence value of the Perlin noise. - /// - /// The persistence value controls the roughness of the Perlin noise. - /// - /// For best results, set the persistence to a number between 0.0 and - /// 1.0. - void SetPersistence (double persistence) - { - m_persistence = persistence; - } - - /// Sets the seed value used by the Perlin-noise function. - /// - /// @param seed The seed value. - void SetSeed (int seed) - { - m_seed = seed; - } - - protected: - - /// Frequency of the first octave. - double m_frequency; - - /// Frequency multiplier between successive octaves. - double m_lacunarity; - - /// Quality of the Perlin noise. - noise::NoiseQuality m_noiseQuality; - - /// Total number of octaves that generate the Perlin noise. - int m_octaveCount; - - /// Persistence of the Perlin noise. - double m_persistence; - - /// Seed value used by the Perlin-noise function. - int m_seed; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/power.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/power.h deleted file mode 100644 index c4c38f6..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/power.h +++ /dev/null @@ -1,80 +0,0 @@ -// power.h -// -// Copyright (C) 2004 Owen Jacobson -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is angstrom@lionsanctuary.net -// - -#ifndef NOISE_MODULE_POWER_H -#define NOISE_MODULE_POWER_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @defgroup combinermodules Combiner Modules - /// @addtogroup combinermodules - /// @{ - - /// Noise module that raises the output value from a first source module - /// to the power of the output value from a second source module. - /// - /// @image html modulepower.png - /// - /// The first source module must have an index value of 0. - /// - /// The second source module must have an index value of 1. - /// - /// This noise module requires two source modules. - class DLL Power : public Module - { - - public: - - /// Constructor. - Power (); - - virtual int GetSourceModuleCount () const - { - return 2; - } - - virtual double GetValue (double x, double y, double z) const; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/ridgedmulti.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/ridgedmulti.h deleted file mode 100644 index ddcad0b..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/ridgedmulti.h +++ /dev/null @@ -1,313 +0,0 @@ -// ridgedmulti.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_RIDGEDMULTI_H -#define NOISE_MODULE_RIDGEDMULTI_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup generatormodules - /// @{ - - /// Default frequency for the noise::module::RidgedMulti noise module. - const double DEFAULT_RIDGED_FREQUENCY = 1.0; - - /// Default lacunarity for the noise::module::RidgedMulti noise module. - const double DEFAULT_RIDGED_LACUNARITY = 2.0; - - /// Default number of octaves for the noise::module::RidgedMulti noise - /// module. - const int DEFAULT_RIDGED_OCTAVE_COUNT = 6; - - /// Default noise quality for the noise::module::RidgedMulti noise - /// module. - const noise::NoiseQuality DEFAULT_RIDGED_QUALITY = QUALITY_STD; - - /// Default noise seed for the noise::module::RidgedMulti noise module. - const int DEFAULT_RIDGED_SEED = 0; - - /// Maximum number of octaves for the noise::module::RidgedMulti noise - /// module. - const int RIDGED_MAX_OCTAVE = 30; - - /// Noise module that outputs 3-dimensional ridged-multifractal noise. - /// - /// @image html moduleridgedmulti.png - /// - /// This noise module, heavily based on the Perlin-noise module, generates - /// ridged-multifractal noise. Ridged-multifractal noise is generated in - /// much of the same way as Perlin noise, except the output of each octave - /// is modified by an absolute-value function. Modifying the octave - /// values in this way produces ridge-like formations. - /// - /// Ridged-multifractal noise does not use a persistence value. This is - /// because the persistence values of the octaves are based on the values - /// generated from from previous octaves, creating a feedback loop (or - /// that's what it looks like after reading the code.) - /// - /// This noise module outputs ridged-multifractal-noise values that - /// usually range from -1.0 to +1.0, but there are no guarantees that all - /// output values will exist within that range. - /// - /// @note For ridged-multifractal noise generated with only one octave, - /// the output value ranges from -1.0 to 0.0. - /// - /// Ridged-multifractal noise is often used to generate craggy mountainous - /// terrain or marble-like textures. - /// - /// This noise module does not require any source modules. - /// - /// <b>Octaves</b> - /// - /// The number of octaves control the <i>amount of detail</i> of the - /// ridged-multifractal noise. Adding more octaves increases the detail - /// of the ridged-multifractal noise, but with the drawback of increasing - /// the calculation time. - /// - /// An application may specify the number of octaves that generate - /// ridged-multifractal noise by calling the SetOctaveCount() method. - /// - /// <b>Frequency</b> - /// - /// An application may specify the frequency of the first octave by - /// calling the SetFrequency() method. - /// - /// <b>Lacunarity</b> - /// - /// The lacunarity specifies the frequency multipler between successive - /// octaves. - /// - /// The effect of modifying the lacunarity is subtle; you may need to play - /// with the lacunarity value to determine the effects. For best results, - /// set the lacunarity to a number between 1.5 and 3.5. - /// - /// <b>References & Acknowledgments</b> - /// - /// <a href=http://www.texturingandmodeling.com/Musgrave.html>F. - /// Kenton "Doc Mojo" Musgrave's texturing page</a> - This page contains - /// links to source code that generates ridged-multfractal noise, among - /// other types of noise. The source file <a - /// href=http://www.texturingandmodeling.com/CODE/MUSGRAVE/CLOUD/fractal.c> - /// fractal.c</a> contains the code I used in my ridged-multifractal class - /// (see the @a RidgedMultifractal() function.) This code was written by F. - /// Kenton Musgrave, the person who created - /// <a href=http://www.pandromeda.com/>MojoWorld</a>. He is also one of - /// the authors in <i>Texturing and Modeling: A Procedural Approach</i> - /// (Morgan Kaufmann, 2002. ISBN 1-55860-848-6.) - class DLL RidgedMulti : public Module - { - - public: - - /// Constructor. - /// - /// The default number of octaves is set to - /// noise::module::DEFAULT_RIDGED_OCTAVE_COUNT. - /// - /// The default frequency is set to - /// noise::module::DEFAULT_RIDGED_FREQUENCY. - /// - /// The default lacunarity is set to - /// noise::module::DEFAULT_RIDGED_LACUNARITY. - /// - /// The default seed value is set to - /// noise::module::DEFAULT_RIDGED_SEED. - RidgedMulti (); - - /// Returns the frequency of the first octave. - /// - /// @returns The frequency of the first octave. - double GetFrequency () const - { - return m_frequency; - } - - /// Returns the lacunarity of the ridged-multifractal noise. - /// - /// @returns The lacunarity of the ridged-multifractal noise. - /// - /// The lacunarity is the frequency multiplier between successive - /// octaves. - double GetLacunarity () const - { - return m_lacunarity; - } - - /// Returns the quality of the ridged-multifractal noise. - /// - /// @returns The quality of the ridged-multifractal noise. - /// - /// See noise::NoiseQuality for definitions of the various - /// coherent-noise qualities. - noise::NoiseQuality GetNoiseQuality () const - { - return m_noiseQuality; - } - - /// Returns the number of octaves that generate the - /// ridged-multifractal noise. - /// - /// @returns The number of octaves that generate the - /// ridged-multifractal noise. - /// - /// The number of octaves controls the amount of detail in the - /// ridged-multifractal noise. - int GetOctaveCount () const - { - return m_octaveCount; - } - - /// Returns the seed value used by the ridged-multifractal-noise - /// function. - /// - /// @returns The seed value. - int GetSeed () const - { - return m_seed; - } - - virtual int GetSourceModuleCount () const - { - return 0; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the frequency of the first octave. - /// - /// @param frequency The frequency of the first octave. - void SetFrequency (double frequency) - { - m_frequency = frequency; - } - - /// Sets the lacunarity of the ridged-multifractal noise. - /// - /// @param lacunarity The lacunarity of the ridged-multifractal noise. - /// - /// The lacunarity is the frequency multiplier between successive - /// octaves. - /// - /// For best results, set the lacunarity to a number between 1.5 and - /// 3.5. - void SetLacunarity (double lacunarity) - { - m_lacunarity = lacunarity; - CalcSpectralWeights (); - } - - /// Sets the quality of the ridged-multifractal noise. - /// - /// @param noiseQuality The quality of the ridged-multifractal noise. - /// - /// See noise::NoiseQuality for definitions of the various - /// coherent-noise qualities. - void SetNoiseQuality (noise::NoiseQuality noiseQuality) - { - m_noiseQuality = noiseQuality; - } - - /// Sets the number of octaves that generate the ridged-multifractal - /// noise. - /// - /// @param octaveCount The number of octaves that generate the - /// ridged-multifractal noise. - /// - /// @pre The number of octaves ranges from 1 to - /// noise::module::RIDGED_MAX_OCTAVE. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// The number of octaves controls the amount of detail in the - /// ridged-multifractal noise. - /// - /// The larger the number of octaves, the more time required to - /// calculate the ridged-multifractal-noise value. - void SetOctaveCount (int octaveCount) - { - if (octaveCount > RIDGED_MAX_OCTAVE) { - throw noise::ExceptionInvalidParam (); - } - m_octaveCount = octaveCount; - } - - /// Sets the seed value used by the ridged-multifractal-noise - /// function. - /// - /// @param seed The seed value. - void SetSeed (int seed) - { - m_seed = seed; - } - - protected: - - /// Calculates the spectral weights for each octave. - /// - /// This method is called when the lacunarity changes. - void CalcSpectralWeights (); - - /// Frequency of the first octave. - double m_frequency; - - /// Frequency multiplier between successive octaves. - double m_lacunarity; - - /// Quality of the ridged-multifractal noise. - noise::NoiseQuality m_noiseQuality; - - /// Total number of octaves that generate the ridged-multifractal - /// noise. - int m_octaveCount; - - /// Contains the spectral weights for each octave. - double m_pSpectralWeights[RIDGED_MAX_OCTAVE]; - - /// Seed value used by the ridged-multfractal-noise function. - int m_seed; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/rotatepoint.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/rotatepoint.h deleted file mode 100644 index f87d01b..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/rotatepoint.h +++ /dev/null @@ -1,233 +0,0 @@ -// rotatepoint.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_ROTATEPOINT_H -#define NOISE_MODULE_ROTATEPOINT_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup transformermodules - /// @{ - - /// Default @a x rotation angle for the noise::module::RotatePoint noise - /// module. - const double DEFAULT_ROTATE_X = 0.0; - - /// Default @a y rotation angle for the noise::module::RotatePoint noise - /// module. - const double DEFAULT_ROTATE_Y = 0.0; - - /// Default @a z rotation angle for the noise::module::RotatePoint noise - /// module. - const double DEFAULT_ROTATE_Z = 0.0; - - /// Noise module that rotates the input value around the origin before - /// returning the output value from a source module. - /// - /// @image html modulerotatepoint.png - /// - /// The GetValue() method rotates the coordinates of the input value - /// around the origin before returning the output value from the source - /// module. To set the rotation angles, call the SetAngles() method. To - /// set the rotation angle around the individual @a x, @a y, or @a z axes, - /// call the SetXAngle(), SetYAngle() or SetZAngle() methods, - /// respectively. - /// - /// The coordinate system of the input value is assumed to be - /// "left-handed" (@a x increases to the right, @a y increases upward, - /// and @a z increases inward.) - /// - /// This noise module requires one source module. - class DLL RotatePoint : public Module - { - - public: - - /// Constructor. - /// - /// The default rotation angle around the @a x axis, in degrees, is - /// set to noise::module::DEFAULT_ROTATE_X. - /// - /// The default rotation angle around the @a y axis, in degrees, is - /// set to noise::module::DEFAULT_ROTATE_Y. - /// - /// The default rotation angle around the @a z axis, in degrees, is - /// set to noise::module::DEFAULT_ROTATE_Z. - RotatePoint (); - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Returns the rotation angle around the @a x axis to apply to the - /// input value. - /// - /// @returns The rotation angle around the @a x axis, in degrees. - double GetXAngle () const - { - return m_xAngle; - } - - /// Returns the rotation angle around the @a y axis to apply to the - /// input value. - /// - /// @returns The rotation angle around the @a y axis, in degrees. - double GetYAngle () const - { - return m_yAngle; - } - - /// Returns the rotation angle around the @a z axis to apply to the - /// input value. - /// - /// @returns The rotation angle around the @a z axis, in degrees. - double GetZAngle () const - { - return m_zAngle; - } - - /// Sets the rotation angles around all three axes to apply to the - /// input value. - /// - /// @param xAngle The rotation angle around the @a x axis, in degrees. - /// @param yAngle The rotation angle around the @a y axis, in degrees. - /// @param zAngle The rotation angle around the @a z axis, in degrees. - /// - /// The GetValue() method rotates the coordinates of the input value - /// around the origin before returning the output value from the - /// source module. - void SetAngles (double xAngle, double yAngle, double zAngle); - - /// Sets the rotation angle around the @a x axis to apply to the input - /// value. - /// - /// @param xAngle The rotation angle around the @a x axis, in degrees. - /// - /// The GetValue() method rotates the coordinates of the input value - /// around the origin before returning the output value from the - /// source module. - void SetXAngle (double xAngle) - { - SetAngles (xAngle, m_yAngle, m_zAngle); - } - - /// Sets the rotation angle around the @a y axis to apply to the input - /// value. - /// - /// @param yAngle The rotation angle around the @a y axis, in degrees. - /// - /// The GetValue() method rotates the coordinates of the input value - /// around the origin before returning the output value from the - /// source module. - void SetYAngle (double yAngle) - { - SetAngles (m_xAngle, yAngle, m_zAngle); - } - - /// Sets the rotation angle around the @a z axis to apply to the input - /// value. - /// - /// @param zAngle The rotation angle around the @a z axis, in degrees. - /// - /// The GetValue() method rotates the coordinates of the input value - /// around the origin before returning the output value from the - /// source module. - void SetZAngle (double zAngle) - { - SetAngles (m_xAngle, m_yAngle, zAngle); - } - - protected: - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_x1Matrix; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_x2Matrix; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_x3Matrix; - - /// @a x rotation angle applied to the input value, in degrees. - double m_xAngle; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_y1Matrix; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_y2Matrix; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_y3Matrix; - - /// @a y rotation angle applied to the input value, in degrees. - double m_yAngle; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_z1Matrix; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_z2Matrix; - - /// An entry within the 3x3 rotation matrix used for rotating the - /// input value. - double m_z3Matrix; - - /// @a z rotation angle applied to the input value, in degrees. - double m_zAngle; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalebias.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalebias.h deleted file mode 100644 index 39eaf83..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalebias.h +++ /dev/null @@ -1,151 +0,0 @@ -// scalebias.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_SCALEBIAS_H -#define NOISE_MODULE_SCALEBIAS_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup modifiermodules - /// @{ - - /// Default bias for the noise::module::ScaleBias noise module. - const double DEFAULT_BIAS = 0.0; - - /// Default scale for the noise::module::ScaleBias noise module. - const double DEFAULT_SCALE = 1.0; - - /// Noise module that applies a scaling factor and a bias to the output - /// value from a source module. - /// - /// @image html modulescalebias.png - /// - /// The GetValue() method retrieves the output value from the source - /// module, multiplies it with a scaling factor, adds a bias to it, then - /// outputs the value. - /// - /// This noise module requires one source module. - class DLL ScaleBias : public Module - { - - public: - - /// Constructor. - /// - /// The default bias is set to noise::module::DEFAULT_BIAS. - /// - /// The default scaling factor is set to noise::module::DEFAULT_SCALE. - ScaleBias (); - - /// Returns the bias to apply to the scaled output value from the - /// source module. - /// - /// @returns The bias to apply. - /// - /// The GetValue() method retrieves the output value from the source - /// module, multiplies it with the scaling factor, adds the bias to - /// it, then outputs the value. - double GetBias () const - { - return m_bias; - } - - /// Returns the scaling factor to apply to the output value from the - /// source module. - /// - /// @returns The scaling factor to apply. - /// - /// The GetValue() method retrieves the output value from the source - /// module, multiplies it with the scaling factor, adds the bias to - /// it, then outputs the value. - double GetScale () const - { - return m_scale; - } - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the bias to apply to the scaled output value from the source - /// module. - /// - /// @param bias The bias to apply. - /// - /// The GetValue() method retrieves the output value from the source - /// module, multiplies it with the scaling factor, adds the bias to - /// it, then outputs the value. - void SetBias (double bias) - { - m_bias = bias; - } - - /// Sets the scaling factor to apply to the output value from the - /// source module. - /// - /// @param scale The scaling factor to apply. - /// - /// The GetValue() method retrieves the output value from the source - /// module, multiplies it with the scaling factor, adds the bias to - /// it, then outputs the value. - void SetScale (double scale) - { - m_scale = scale; - } - - protected: - - /// Bias to apply to the scaled output value from the source module. - double m_bias; - - /// Scaling factor to apply to the output value from the source - /// module. - double m_scale; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalepoint.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalepoint.h deleted file mode 100644 index 1b7adfd..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/scalepoint.h +++ /dev/null @@ -1,212 +0,0 @@ -// scalepoint.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_SCALEPOINT_H -#define NOISE_MODULE_SCALEPOINT_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup transformermodules - /// @{ - - /// Default scaling factor applied to the @a x coordinate for the - /// noise::module::ScalePoint noise module. - const double DEFAULT_SCALE_POINT_X = 1.0; - - /// Default scaling factor applied to the @a y coordinate for the - /// noise::module::ScalePoint noise module. - const double DEFAULT_SCALE_POINT_Y = 1.0; - - /// Default scaling factor applied to the @a z coordinate for the - /// noise::module::ScalePoint noise module. - const double DEFAULT_SCALE_POINT_Z = 1.0; - - /// Noise module that scales the coordinates of the input value before - /// returning the output value from a source module. - /// - /// @image html modulescalepoint.png - /// - /// The GetValue() method multiplies the ( @a x, @a y, @a z ) coordinates - /// of the input value with a scaling factor before returning the output - /// value from the source module. To set the scaling factor, call the - /// SetScale() method. To set the scaling factor to apply to the - /// individual @a x, @a y, or @a z coordinates, call the SetXScale(), - /// SetYScale() or SetZScale() methods, respectively. - /// - /// This noise module requires one source module. - class DLL ScalePoint : public Module - { - - public: - - /// Constructor. - /// - /// The default scaling factor applied to the @a x coordinate is set - /// to noise::module::DEFAULT_SCALE_POINT_X. - /// - /// The default scaling factor applied to the @a y coordinate is set - /// to noise::module::DEFAULT_SCALE_POINT_Y. - /// - /// The default scaling factor applied to the @a z coordinate is set - /// to noise::module::DEFAULT_SCALE_POINT_Z. - ScalePoint (); - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Returns the scaling factor applied to the @a x coordinate of the - /// input value. - /// - /// @returns The scaling factor applied to the @a x coordinate. - double GetXScale () const - { - return m_xScale; - } - - /// Returns the scaling factor applied to the @a y coordinate of the - /// input value. - /// - /// @returns The scaling factor applied to the @a y coordinate. - double GetYScale () const - { - return m_yScale; - } - - /// Returns the scaling factor applied to the @a z coordinate of the - /// input value. - /// - /// @returns The scaling factor applied to the @a z coordinate. - double GetZScale () const - { - return m_zScale; - } - - /// Sets the scaling factor to apply to the input value. - /// - /// @param scale The scaling factor to apply. - /// - /// The GetValue() method multiplies the ( @a x, @a y, @a z ) - /// coordinates of the input value with a scaling factor before - /// returning the output value from the source module. - void SetScale (double scale) - { - m_xScale = scale; - m_yScale = scale; - m_zScale = scale; - } - - /// Sets the scaling factor to apply to the ( @a x, @a y, @a z ) - /// coordinates of the input value. - /// - /// @param xScale The scaling factor to apply to the @a x coordinate. - /// @param yScale The scaling factor to apply to the @a y coordinate. - /// @param zScale The scaling factor to apply to the @a z coordinate. - /// - /// The GetValue() method multiplies the ( @a x, @a y, @a z ) - /// coordinates of the input value with a scaling factor before - /// returning the output value from the source module. - void SetScale (double xScale, double yScale, double zScale) - { - m_xScale = xScale; - m_yScale = yScale; - m_zScale = zScale; - } - - /// Sets the scaling factor to apply to the @a x coordinate of the - /// input value. - /// - /// @param xScale The scaling factor to apply to the @a x coordinate. - /// - /// The GetValue() method multiplies the ( @a x, @a y, @a z ) - /// coordinates of the input value with a scaling factor before - /// returning the output value from the source module. - void SetXScale (double xScale) - { - m_xScale = xScale; - } - - /// Sets the scaling factor to apply to the @a y coordinate of the - /// input value. - /// - /// @param yScale The scaling factor to apply to the @a y coordinate. - /// - /// The GetValue() method multiplies the ( @a x, @a y, @a z ) - /// coordinates of the input value with a scaling factor before - /// returning the output value from the source module. - void SetYScale (double yScale) - { - m_yScale = yScale; - } - - /// Sets the scaling factor to apply to the @a z coordinate of the - /// input value. - /// - /// @param zScale The scaling factor to apply to the @a z coordinate. - /// - /// The GetValue() method multiplies the ( @a x, @a y, @a z ) - /// coordinates of the input value with a scaling factor before - /// returning the output value from the source module. - void SetZScale (double zScale) - { - m_zScale = zScale; - } - - protected: - - /// Scaling factor applied to the @a x coordinate of the input value. - double m_xScale; - - /// Scaling factor applied to the @a y coordinate of the input value. - double m_yScale; - - /// Scaling factor applied to the @a z coordinate of the input value. - double m_zScale; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/select.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/select.h deleted file mode 100644 index 54bdc8b..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/select.h +++ /dev/null @@ -1,267 +0,0 @@ -// select.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_SELECT_H -#define NOISE_MODULE_SELECT_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup selectormodules - /// @{ - - /// Default edge-falloff value for the noise::module::Select noise module. - const double DEFAULT_SELECT_EDGE_FALLOFF = 0.0; - - /// Default lower bound of the selection range for the - /// noise::module::Select noise module. - const double DEFAULT_SELECT_LOWER_BOUND = -1.0; - - /// Default upper bound of the selection range for the - /// noise::module::Select noise module. - const double DEFAULT_SELECT_UPPER_BOUND = 1.0; - - /// Noise module that outputs the value selected from one of two source - /// modules chosen by the output value from a control module. - /// - /// @image html moduleselect.png - /// - /// Unlike most other noise modules, the index value assigned to a source - /// module determines its role in the selection operation: - /// - Source module 0 (upper left in the diagram) outputs a value. - /// - Source module 1 (lower left in the diagram) outputs a value. - /// - Source module 2 (bottom of the diagram) is known as the <i>control - /// module</i>. The control module determines the value to select. If - /// the output value from the control module is within a range of values - /// known as the <i>selection range</i>, this noise module outputs the - /// value from the source module with an index value of 1. Otherwise, - /// this noise module outputs the value from the source module with an - /// index value of 0. - /// - /// To specify the bounds of the selection range, call the SetBounds() - /// method. - /// - /// An application can pass the control module to the SetControlModule() - /// method instead of the SetSourceModule() method. This may make the - /// application code easier to read. - /// - /// By default, there is an abrupt transition between the output values - /// from the two source modules at the selection-range boundary. To - /// smooth the transition, pass a non-zero value to the SetEdgeFalloff() - /// method. Higher values result in a smoother transition. - /// - /// This noise module requires three source modules. - class DLL Select : public Module - { - - public: - - /// Constructor. - /// - /// The default falloff value at the edge transition is set to - /// noise::module::DEFAULT_SELECT_EDGE_FALLOFF. - /// - /// The default lower bound of the selection range is set to - /// noise::module::DEFAULT_SELECT_LOWER_BOUND. - /// - /// The default upper bound of the selection range is set to - /// noise::module::DEFAULT_SELECT_UPPER_BOUND. - Select (); - - /// Returns the control module. - /// - /// @returns A reference to the control module. - /// - /// @pre A control module has been added to this noise module via a - /// call to SetSourceModule() or SetControlModule(). - /// - /// @throw noise::ExceptionNoModule See the preconditions for more - /// information. - /// - /// The control module determines the output value to select. If the - /// output value from the control module is within a range of values - /// known as the <i>selection range</i>, the GetValue() method outputs - /// the value from the source module with an index value of 1. - /// Otherwise, this method outputs the value from the source module - /// with an index value of 0. - const Module& GetControlModule () const - { - if (m_pSourceModule == NULL || m_pSourceModule[2] == NULL) { - throw noise::ExceptionNoModule (); - } - return *(m_pSourceModule[2]); - } - - /// Returns the falloff value at the edge transition. - /// - /// @returns The falloff value at the edge transition. - /// - /// The falloff value is the width of the edge transition at either - /// edge of the selection range. - /// - /// By default, there is an abrupt transition between the output - /// values from the two source modules at the selection-range - /// boundary. - double GetEdgeFalloff () const - { - return m_edgeFalloff; - } - - /// Returns the lower bound of the selection range. - /// - /// @returns The lower bound of the selection range. - /// - /// If the output value from the control module is within the - /// selection range, the GetValue() method outputs the value from the - /// source module with an index value of 1. Otherwise, this method - /// outputs the value from the source module with an index value of 0. - double GetLowerBound () const - { - return m_lowerBound; - } - - virtual int GetSourceModuleCount () const - { - return 3; - } - - /// Returns the upper bound of the selection range. - /// - /// @returns The upper bound of the selection range. - /// - /// If the output value from the control module is within the - /// selection range, the GetValue() method outputs the value from the - /// source module with an index value of 1. Otherwise, this method - /// outputs the value from the source module with an index value of 0. - double GetUpperBound () const - { - return m_upperBound; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the lower and upper bounds of the selection range. - /// - /// @param lowerBound The lower bound. - /// @param upperBound The upper bound. - /// - /// @pre The lower bound must be less than or equal to the upper - /// bound. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// If the output value from the control module is within the - /// selection range, the GetValue() method outputs the value from the - /// source module with an index value of 1. Otherwise, this method - /// outputs the value from the source module with an index value of 0. - void SetBounds (double lowerBound, double upperBound); - - /// Sets the control module. - /// - /// @param controlModule The control module. - /// - /// The control module determines the output value to select. If the - /// output value from the control module is within a range of values - /// known as the <i>selection range</i>, the GetValue() method outputs - /// the value from the source module with an index value of 1. - /// Otherwise, this method outputs the value from the source module - /// with an index value of 0. - /// - /// This method assigns the control module an index value of 2. - /// Passing the control module to this method produces the same - /// results as passing the control module to the SetSourceModule() - /// method while assigning that noise module an index value of 2. - /// - /// This control module must exist throughout the lifetime of this - /// noise module unless another control module replaces that control - /// module. - void SetControlModule (const Module& controlModule) - { - assert (m_pSourceModule != NULL); - m_pSourceModule[2] = &controlModule; - } - - /// Sets the falloff value at the edge transition. - /// - /// @param edgeFalloff The falloff value at the edge transition. - /// - /// The falloff value is the width of the edge transition at either - /// edge of the selection range. - /// - /// By default, there is an abrupt transition between the values from - /// the two source modules at the boundaries of the selection range. - /// - /// For example, if the selection range is 0.5 to 0.8, and the edge - /// falloff value is 0.1, then the GetValue() method outputs: - /// - the output value from the source module with an index value of 0 - /// if the output value from the control module is less than 0.4 - /// ( = 0.5 - 0.1). - /// - a linear blend between the two output values from the two source - /// modules if the output value from the control module is between - /// 0.4 ( = 0.5 - 0.1) and 0.6 ( = 0.5 + 0.1). - /// - the output value from the source module with an index value of 1 - /// if the output value from the control module is between 0.6 - /// ( = 0.5 + 0.1) and 0.7 ( = 0.8 - 0.1). - /// - a linear blend between the output values from the two source - /// modules if the output value from the control module is between - /// 0.7 ( = 0.8 - 0.1 ) and 0.9 ( = 0.8 + 0.1). - /// - the output value from the source module with an index value of 0 - /// if the output value from the control module is greater than 0.9 - /// ( = 0.8 + 0.1). - void SetEdgeFalloff (double edgeFalloff); - - protected: - - /// Edge-falloff value. - double m_edgeFalloff; - - /// Lower bound of the selection range. - double m_lowerBound; - - /// Upper bound of the selection range. - double m_upperBound; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/spheres.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/spheres.h deleted file mode 100644 index 32b1c04..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/spheres.h +++ /dev/null @@ -1,127 +0,0 @@ -// spheres.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_SPHERES_H -#define NOISE_MODULE_SPHERES_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup generatormodules - /// @{ - - /// Default frequency value for the noise::module::Spheres noise module. - const double DEFAULT_SPHERES_FREQUENCY = 1.0; - - /// Noise module that outputs concentric spheres. - /// - /// @image html modulespheres.png - /// - /// This noise module outputs concentric spheres centered on the origin - /// like the concentric rings of an onion. - /// - /// The first sphere has a radius of 1.0. Each subsequent sphere has a - /// radius that is 1.0 unit larger than the previous sphere. - /// - /// The output value from this noise module is determined by the distance - /// between the input value and the the nearest spherical surface. The - /// input values that are located on a spherical surface are given the - /// output value 1.0 and the input values that are equidistant from two - /// spherical surfaces are given the output value -1.0. - /// - /// An application can change the frequency of the concentric spheres. - /// Increasing the frequency reduces the distances between spheres. To - /// specify the frequency, call the SetFrequency() method. - /// - /// This noise module, modified with some low-frequency, low-power - /// turbulence, is useful for generating agate-like textures. - /// - /// This noise module does not require any source modules. - class DLL Spheres : public Module - { - - public: - - /// Constructor. - /// - /// The default frequency is set to - /// noise::module::DEFAULT_SPHERES_FREQUENCY. - Spheres (); - - /// Returns the frequency of the concentric spheres. - /// - /// @returns The frequency of the concentric spheres. - /// - /// Increasing the frequency increases the density of the concentric - /// spheres, reducing the distances between them. - double GetFrequency () const - { - return m_frequency; - } - - virtual int GetSourceModuleCount () const - { - return 0; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the frequenct of the concentric spheres. - /// - /// @param frequency The frequency of the concentric spheres. - /// - /// Increasing the frequency increases the density of the concentric - /// spheres, reducing the distances between them. - void SetFrequency (double frequency) - { - m_frequency = frequency; - } - - protected: - - /// Frequency of the concentric spheres. - double m_frequency; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/terrace.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/terrace.h deleted file mode 100644 index 92c595d..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/terrace.h +++ /dev/null @@ -1,242 +0,0 @@ -// terrace.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_TERRACE_H -#define NOISE_MODULE_TERRACE_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup modifiermodules - /// @{ - - /// Noise module that maps the output value from a source module onto a - /// terrace-forming curve. - /// - /// @image html moduleterrace.png - /// - /// This noise module maps the output value from the source module onto a - /// terrace-forming curve. The start of this curve has a slope of zero; - /// its slope then smoothly increases. This curve also contains - /// <i>control points</i> which resets the slope to zero at that point, - /// producing a "terracing" effect. Refer to the following illustration: - /// - /// @image html terrace.png - /// - /// To add a control point to this noise module, call the - /// AddControlPoint() method. - /// - /// An application must add a minimum of two control points to the curve. - /// If this is not done, the GetValue() method fails. The control points - /// can have any value, although no two control points can have the same - /// value. There is no limit to the number of control points that can be - /// added to the curve. - /// - /// This noise module clamps the output value from the source module if - /// that value is less than the value of the lowest control point or - /// greater than the value of the highest control point. - /// - /// This noise module is often used to generate terrain features such as - /// your stereotypical desert canyon. - /// - /// This noise module requires one source module. - class DLL Terrace : public Module - { - - public: - - /// Constructor. - Terrace (); - - /// Destructor. - ~Terrace (); - - /// Adds a control point to the terrace-forming curve. - /// - /// @param value The value of the control point to add. - /// - /// @pre No two control points have the same value. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// Two or more control points define the terrace-forming curve. The - /// start of this curve has a slope of zero; its slope then smoothly - /// increases. At the control points, its slope resets to zero. - /// - /// It does not matter which order these points are added. - void AddControlPoint (double value); - - /// Deletes all the control points on the terrace-forming curve. - /// - /// @post All control points on the terrace-forming curve are deleted. - void ClearAllControlPoints (); - - /// Returns a pointer to the array of control points on the - /// terrace-forming curve. - /// - /// @returns A pointer to the array of control points in this noise - /// module. - /// - /// Two or more control points define the terrace-forming curve. The - /// start of this curve has a slope of zero; its slope then smoothly - /// increases. At the control points, its slope resets to zero. - /// - /// Before calling this method, call GetControlPointCount() to - /// determine the number of control points in this array. - /// - /// It is recommended that an application does not store this pointer - /// for later use since the pointer to the array may change if the - /// application calls another method of this object. - const double* GetControlPointArray () const - { - return m_pControlPoints; - } - - /// Returns the number of control points on the terrace-forming curve. - /// - /// @returns The number of control points on the terrace-forming - /// curve. - int GetControlPointCount () const - { - return m_controlPointCount; - } - - virtual int GetSourceModuleCount () const - { - return 1; - } - - /// Enables or disables the inversion of the terrace-forming curve - /// between the control points. - /// - /// @param invert Specifies whether to invert the curve between the - /// control points. - void InvertTerraces (bool invert = true) - { - m_invertTerraces = invert; - } - - /// Determines if the terrace-forming curve between the control - /// points is inverted. - /// - /// @returns - /// - @a true if the curve between the control points is inverted. - /// - @a false if the curve between the control points is not - /// inverted. - bool IsTerracesInverted () const - { - return m_invertTerraces; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Creates a number of equally-spaced control points that range from - /// -1 to +1. - /// - /// @param controlPointCount The number of control points to generate. - /// - /// @pre The number of control points must be greater than or equal to - /// 2. - /// - /// @post The previous control points on the terrace-forming curve are - /// deleted. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// Two or more control points define the terrace-forming curve. The - /// start of this curve has a slope of zero; its slope then smoothly - /// increases. At the control points, its slope resets to zero. - void MakeControlPoints (int controlPointCount); - - protected: - - /// Determines the array index in which to insert the control point - /// into the internal control point array. - /// - /// @param value The value of the control point. - /// - /// @returns The array index in which to insert the control point. - /// - /// @pre No two control points have the same value. - /// - /// @throw noise::ExceptionInvalidParam An invalid parameter was - /// specified; see the preconditions for more information. - /// - /// By inserting the control point at the returned array index, this - /// class ensures that the control point array is sorted by value. - /// The code that maps a value onto the curve requires a sorted - /// control point array. - int FindInsertionPos (double value); - - /// Inserts the control point at the specified position in the - /// internal control point array. - /// - /// @param insertionPos The zero-based array position in which to - /// insert the control point. - /// @param value The value of the control point. - /// - /// To make room for this new control point, this method reallocates - /// the control point array and shifts all control points occurring - /// after the insertion position up by one. - /// - /// Because the curve mapping algorithm in this noise module requires - /// that all control points in the array be sorted by value, the new - /// control point should be inserted at the position in which the - /// order is still preserved. - void InsertAtPos (int insertionPos, double value); - - /// Number of control points stored in this noise module. - int m_controlPointCount; - - /// Determines if the terrace-forming curve between all control points - /// is inverted. - bool m_invertTerraces; - - /// Array that stores the control points. - double* m_pControlPoints; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/translatepoint.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/translatepoint.h deleted file mode 100644 index b483dd9..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/translatepoint.h +++ /dev/null @@ -1,223 +0,0 @@ -// translatepoint.h -// -// Copyright (C) 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_TRANSLATEPOINT_H -#define NOISE_MODULE_TRANSLATEPOINT_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup transformermodules - /// @{ - - /// Default translation factor applied to the @a x coordinate for the - /// noise::module::TranslatePoint noise module. - const double DEFAULT_TRANSLATE_POINT_X = 0.0; - - /// Default translation factor applied to the @a y coordinate for the - /// noise::module::TranslatePoint noise module. - const double DEFAULT_TRANSLATE_POINT_Y = 0.0; - - /// Default translation factor applied to the @a z coordinate for the - /// noise::module::TranslatePoint noise module. - const double DEFAULT_TRANSLATE_POINT_Z = 0.0; - - /// Noise module that moves the coordinates of the input value before - /// returning the output value from a source module. - /// - /// @image html moduletranslatepoint.png - /// - /// The GetValue() method moves the ( @a x, @a y, @a z ) coordinates of - /// the input value by a translation amount before returning the output - /// value from the source module. To set the translation amount, call - /// the SetTranslation() method. To set the translation amount to - /// apply to the individual @a x, @a y, or @a z coordinates, call the - /// SetXTranslation(), SetYTranslation() or SetZTranslation() methods, - /// respectively. - /// - /// This noise module requires one source module. - class DLL TranslatePoint : public Module - { - - public: - - /// Constructor. - /// - /// The default translation amount to apply to the @a x coordinate is - /// set to noise::module::DEFAULT_TRANSLATE_POINT_X. - /// - /// The default translation amount to apply to the @a y coordinate is - /// set to noise::module::DEFAULT_TRANSLATE_POINT_Y. - /// - /// The default translation amount to apply to the @a z coordinate is - /// set to noise::module::DEFAULT_TRANSLATE_POINT_Z. - TranslatePoint (); - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Returns the translation amount to apply to the @a x coordinate of - /// the input value. - /// - /// @returns The translation amount to apply to the @a x coordinate. - double GetXTranslation () const - { - return m_xTranslation; - } - - /// Returns the translation amount to apply to the @a y coordinate of - /// the input value. - /// - /// @returns The translation amount to apply to the @a y coordinate. - double GetYTranslation () const - { - return m_yTranslation; - } - - /// Returns the translation amount to apply to the @a z coordinate of - /// the input value. - /// - /// @returns The translation amount to apply to the @a z coordinate. - double GetZTranslation () const - { - return m_zTranslation; - } - - /// Sets the translation amount to apply to the input value. - /// - /// @param translation The translation amount to apply. - /// - /// The GetValue() method moves the ( @a x, @a y, @a z ) coordinates - /// of the input value by a translation amount before returning the - /// output value from the source module - void SetTranslation (double translation) - { - m_xTranslation = translation; - m_yTranslation = translation; - m_zTranslation = translation; - } - - /// Sets the translation amounts to apply to the ( @a x, @a y, @a z ) - /// coordinates of the input value. - /// - /// @param xTranslation The translation amount to apply to the @a x - /// coordinate. - /// @param yTranslation The translation amount to apply to the @a y - /// coordinate. - /// @param zTranslation The translation amount to apply to the @a z - /// coordinate. - /// - /// The GetValue() method moves the ( @a x, @a y, @a z ) coordinates - /// of the input value by a translation amount before returning the - /// output value from the source module - void SetTranslation (double xTranslation, double yTranslation, - double zTranslation) - { - m_xTranslation = xTranslation; - m_yTranslation = yTranslation; - m_zTranslation = zTranslation; - } - - /// Sets the translation amount to apply to the @a x coordinate of the - /// input value. - /// - /// @param xTranslation The translation amount to apply to the @a x - /// coordinate. - /// - /// The GetValue() method moves the ( @a x, @a y, @a z ) coordinates - /// of the input value by a translation amount before returning the - /// output value from the source module - void SetXTranslation (double xTranslation) - { - m_xTranslation = xTranslation; - } - - /// Sets the translation amount to apply to the @a y coordinate of the - /// input value. - /// - /// @param yTranslation The translation amount to apply to the @a y - /// coordinate. - /// - /// The GetValue() method moves the ( @a x, @a y, @a z ) coordinates - /// of the input value by a translation amount before returning the - /// output value from the source module - void SetYTranslation (double yTranslation) - { - m_yTranslation = yTranslation; - } - - /// Sets the translation amount to apply to the @a z coordinate of the - /// input value. - /// - /// @param zTranslation The translation amount to apply to the @a z - /// coordinate. - /// - /// The GetValue() method moves the ( @a x, @a y, @a z ) coordinates - /// of the input value by a translation amount before returning the - /// output value from the source module - void SetZTranslation (double zTranslation) - { - m_zTranslation = zTranslation; - } - - protected: - - /// Translation amount applied to the @a x coordinate of the input - /// value. - double m_xTranslation; - - /// Translation amount applied to the @a y coordinate of the input - /// value. - double m_yTranslation; - - /// Translation amount applied to the @a z coordinate of the input - /// value. - double m_zTranslation; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/turbulence.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/turbulence.h deleted file mode 100644 index d8cc252..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/turbulence.h +++ /dev/null @@ -1,269 +0,0 @@ -// turbulence.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_TURBULENCE_H -#define NOISE_MODULE_TURBULENCE_H - -#include "perlin.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup transformermodules - /// @{ - - /// Default frequency for the noise::module::Turbulence noise module. - const double DEFAULT_TURBULENCE_FREQUENCY = DEFAULT_PERLIN_FREQUENCY; - - /// Default power for the noise::module::Turbulence noise module. - const double DEFAULT_TURBULENCE_POWER = 1.0; - - /// Default roughness for the noise::module::Turbulence noise module. - const int DEFAULT_TURBULENCE_ROUGHNESS = 3; - - /// Default noise seed for the noise::module::Turbulence noise module. - const int DEFAULT_TURBULENCE_SEED = DEFAULT_PERLIN_SEED; - - /// Noise module that randomly displaces the input value before - /// returning the output value from a source module. - /// - /// @image html moduleturbulence.png - /// - /// @a Turbulence is the pseudo-random displacement of the input value. - /// The GetValue() method randomly displaces the ( @a x, @a y, @a z ) - /// coordinates of the input value before retrieving the output value from - /// the source module. To control the turbulence, an application can - /// modify its frequency, its power, and its roughness. - /// - /// The frequency of the turbulence determines how rapidly the - /// displacement amount changes. To specify the frequency, call the - /// SetFrequency() method. - /// - /// The power of the turbulence determines the scaling factor that is - /// applied to the displacement amount. To specify the power, call the - /// SetPower() method. - /// - /// The roughness of the turbulence determines the roughness of the - /// changes to the displacement amount. Low values smoothly change the - /// displacement amount. High values roughly change the displacement - /// amount, which produces more "kinky" changes. To specify the - /// roughness, call the SetRoughness() method. - /// - /// Use of this noise module may require some trial and error. Assuming - /// that you are using a generator module as the source module, you - /// should first: - /// - Set the frequency to the same frequency as the source module. - /// - Set the power to the reciprocal of the frequency. - /// - /// From these initial frequency and power values, modify these values - /// until this noise module produce the desired changes in your terrain or - /// texture. For example: - /// - Low frequency (1/8 initial frequency) and low power (1/8 initial - /// power) produces very minor, almost unnoticeable changes. - /// - Low frequency (1/8 initial frequency) and high power (8 times - /// initial power) produces "ropey" lava-like terrain or marble-like - /// textures. - /// - High frequency (8 times initial frequency) and low power (1/8 - /// initial power) produces a noisy version of the initial terrain or - /// texture. - /// - High frequency (8 times initial frequency) and high power (8 times - /// initial power) produces nearly pure noise, which isn't entirely - /// useful. - /// - /// Displacing the input values result in more realistic terrain and - /// textures. If you are generating elevations for terrain height maps, - /// you can use this noise module to produce more realistic mountain - /// ranges or terrain features that look like flowing lava rock. If you - /// are generating values for textures, you can use this noise module to - /// produce realistic marble-like or "oily" textures. - /// - /// Internally, there are three noise::module::Perlin noise modules - /// that displace the input value; one for the @a x, one for the @a y, - /// and one for the @a z coordinate. - /// - /// This noise module requires one source module. - class DLL Turbulence : public Module - { - - public: - - /// Constructor. - /// - /// The default frequency is set to - /// noise::module::DEFAULT_TURBULENCE_FREQUENCY. - /// - /// The default power is set to - /// noise::module::DEFAULT_TURBULENCE_POWER. - /// - /// The default roughness is set to - /// noise::module::DEFAULT_TURBULENCE_ROUGHNESS. - /// - /// The default seed value is set to - /// noise::module::DEFAULT_TURBULENCE_SEED. - Turbulence (); - - /// Returns the frequency of the turbulence. - /// - /// @returns The frequency of the turbulence. - /// - /// The frequency of the turbulence determines how rapidly the - /// displacement amount changes. - double GetFrequency () const; - - /// Returns the power of the turbulence. - /// - /// @returns The power of the turbulence. - /// - /// The power of the turbulence determines the scaling factor that is - /// applied to the displacement amount. - double GetPower () const - { - return m_power; - } - - /// Returns the roughness of the turbulence. - /// - /// @returns The roughness of the turbulence. - /// - /// The roughness of the turbulence determines the roughness of the - /// changes to the displacement amount. Low values smoothly change - /// the displacement amount. High values roughly change the - /// displacement amount, which produces more "kinky" changes. - int GetRoughnessCount () const - { - return m_xDistortModule.GetOctaveCount (); - } - - /// Returns the seed value of the internal Perlin-noise modules that - /// are used to displace the input values. - /// - /// @returns The seed value. - /// - /// Internally, there are three noise::module::Perlin noise modules - /// that displace the input value; one for the @a x, one for the @a y, - /// and one for the @a z coordinate. - int GetSeed () const; - - virtual int GetSourceModuleCount () const - { - return 1; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the frequency of the turbulence. - /// - /// @param frequency The frequency of the turbulence. - /// - /// The frequency of the turbulence determines how rapidly the - /// displacement amount changes. - void SetFrequency (double frequency) - { - // Set the frequency of each Perlin-noise module. - m_xDistortModule.SetFrequency (frequency); - m_yDistortModule.SetFrequency (frequency); - m_zDistortModule.SetFrequency (frequency); - } - - /// Sets the power of the turbulence. - /// - /// @param power The power of the turbulence. - /// - /// The power of the turbulence determines the scaling factor that is - /// applied to the displacement amount. - void SetPower (double power) - { - m_power = power; - } - - /// Sets the roughness of the turbulence. - /// - /// @param roughness The roughness of the turbulence. - /// - /// The roughness of the turbulence determines the roughness of the - /// changes to the displacement amount. Low values smoothly change - /// the displacement amount. High values roughly change the - /// displacement amount, which produces more "kinky" changes. - /// - /// Internally, there are three noise::module::Perlin noise modules - /// that displace the input value; one for the @a x, one for the @a y, - /// and one for the @a z coordinate. The roughness value is equal to - /// the number of octaves used by the noise::module::Perlin noise - /// modules. - void SetRoughness (int roughness) - { - // Set the octave count for each Perlin-noise module. - m_xDistortModule.SetOctaveCount (roughness); - m_yDistortModule.SetOctaveCount (roughness); - m_zDistortModule.SetOctaveCount (roughness); - } - - /// Sets the seed value of the internal noise modules that are used to - /// displace the input values. - /// - /// @param seed The seed value. - /// - /// Internally, there are three noise::module::Perlin noise modules - /// that displace the input value; one for the @a x, one for the @a y, - /// and one for the @a z coordinate. This noise module assigns the - /// following seed values to the noise::module::Perlin noise modules: - /// - It assigns the seed value (@a seed + 0) to the @a x noise module. - /// - It assigns the seed value (@a seed + 1) to the @a y noise module. - /// - It assigns the seed value (@a seed + 2) to the @a z noise module. - void SetSeed (int seed); - - protected: - - /// The power (scale) of the displacement. - double m_power; - - /// Noise module that displaces the @a x coordinate. - Perlin m_xDistortModule; - - /// Noise module that displaces the @a y coordinate. - Perlin m_yDistortModule; - - /// Noise module that displaces the @a z coordinate. - Perlin m_zDistortModule; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/voronoi.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/voronoi.h deleted file mode 100644 index c71901b..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/module/voronoi.h +++ /dev/null @@ -1,246 +0,0 @@ -// voronoi.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_MODULE_VORONOI_H -#define NOISE_MODULE_VORONOI_H - -#include "modulebase.h" - -namespace noise -{ - - namespace module - { - - /// @addtogroup libnoise - /// @{ - - /// @addtogroup modules - /// @{ - - /// @addtogroup generatormodules - /// @{ - - /// Default displacement to apply to each cell for the - /// noise::module::Voronoi noise module. - const double DEFAULT_VORONOI_DISPLACEMENT = 1.0; - - /// Default frequency of the seed points for the noise::module::Voronoi - /// noise module. - const double DEFAULT_VORONOI_FREQUENCY = 1.0; - - /// Default seed of the noise function for the noise::module::Voronoi - /// noise module. - const int DEFAULT_VORONOI_SEED = 0; - - /// Noise module that outputs Voronoi cells. - /// - /// @image html modulevoronoi.png - /// - /// In mathematics, a <i>Voronoi cell</i> is a region containing all the - /// points that are closer to a specific <i>seed point</i> than to any - /// other seed point. These cells mesh with one another, producing - /// polygon-like formations. - /// - /// By default, this noise module randomly places a seed point within - /// each unit cube. By modifying the <i>frequency</i> of the seed points, - /// an application can change the distance between seed points. The - /// higher the frequency, the closer together this noise module places - /// the seed points, which reduces the size of the cells. To specify the - /// frequency of the cells, call the SetFrequency() method. - /// - /// This noise module assigns each Voronoi cell with a random constant - /// value from a coherent-noise function. The <i>displacement value</i> - /// controls the range of random values to assign to each cell. The - /// range of random values is +/- the displacement value. Call the - /// SetDisplacement() method to specify the displacement value. - /// - /// To modify the random positions of the seed points, call the SetSeed() - /// method. - /// - /// This noise module can optionally add the distance from the nearest - /// seed to the output value. To enable this feature, call the - /// EnableDistance() method. This causes the points in the Voronoi cells - /// to increase in value the further away that point is from the nearest - /// seed point. - /// - /// Voronoi cells are often used to generate cracked-mud terrain - /// formations or crystal-like textures - /// - /// This noise module requires no source modules. - class DLL Voronoi : public Module - { - - public: - - /// Constructor. - /// - /// The default displacement value is set to - /// noise::module::DEFAULT_VORONOI_DISPLACEMENT. - /// - /// The default frequency is set to - /// noise::module::DEFAULT_VORONOI_FREQUENCY. - /// - /// The default seed value is set to - /// noise::module::DEFAULT_VORONOI_SEED. - Voronoi (); - - /// Enables or disables applying the distance from the nearest seed - /// point to the output value. - /// - /// @param enable Specifies whether to apply the distance to the - /// output value or not. - /// - /// Applying the distance from the nearest seed point to the output - /// value causes the points in the Voronoi cells to increase in value - /// the further away that point is from the nearest seed point. - /// Setting this value to @a true (and setting the displacement to a - /// near-zero value) causes this noise module to generate cracked mud - /// formations. - void EnableDistance (bool enable = true) - { - m_enableDistance = enable; - } - - /// Returns the displacement value of the Voronoi cells. - /// - /// @returns The displacement value of the Voronoi cells. - /// - /// This noise module assigns each Voronoi cell with a random constant - /// value from a coherent-noise function. The <i>displacement - /// value</i> controls the range of random values to assign to each - /// cell. The range of random values is +/- the displacement value. - double GetDisplacement () const - { - return m_displacement; - } - - /// Returns the frequency of the seed points. - /// - /// @returns The frequency of the seed points. - /// - /// The frequency determines the size of the Voronoi cells and the - /// distance between these cells. - double GetFrequency () const - { - return m_frequency; - } - - virtual int GetSourceModuleCount () const - { - return 0; - } - - /// Returns the seed value used by the Voronoi cells - /// - /// @returns The seed value. - /// - /// The positions of the seed values are calculated by a - /// coherent-noise function. By modifying the seed value, the output - /// of that function changes. - int GetSeed () const - { - return m_seed; - } - - /// Determines if the distance from the nearest seed point is applied - /// to the output value. - /// - /// @returns - /// - @a true if the distance is applied to the output value. - /// - @a false if not. - /// - /// Applying the distance from the nearest seed point to the output - /// value causes the points in the Voronoi cells to increase in value - /// the further away that point is from the nearest seed point. - bool IsDistanceEnabled () const - { - return m_enableDistance; - } - - virtual double GetValue (double x, double y, double z) const; - - /// Sets the displacement value of the Voronoi cells. - /// - /// @param displacement The displacement value of the Voronoi cells. - /// - /// This noise module assigns each Voronoi cell with a random constant - /// value from a coherent-noise function. The <i>displacement - /// value</i> controls the range of random values to assign to each - /// cell. The range of random values is +/- the displacement value. - void SetDisplacement (double displacement) - { - m_displacement = displacement; - } - - /// Sets the frequency of the seed points. - /// - /// @param frequency The frequency of the seed points. - /// - /// The frequency determines the size of the Voronoi cells and the - /// distance between these cells. - void SetFrequency (double frequency) - { - m_frequency = frequency; - } - - /// Sets the seed value used by the Voronoi cells - /// - /// @param seed The seed value. - /// - /// The positions of the seed values are calculated by a - /// coherent-noise function. By modifying the seed value, the output - /// of that function changes. - void SetSeed (int seed) - { - m_seed = seed; - } - - protected: - - /// Scale of the random displacement to apply to each Voronoi cell. - double m_displacement; - - /// Determines if the distance from the nearest seed point is applied to - /// the output value. - bool m_enableDistance; - - /// Frequency of the seed points. - double m_frequency; - - /// Seed value used by the coherent-noise function to determine the - /// positions of the seed points. - int m_seed; - - }; - - /// @} - - /// @} - - /// @} - - } - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/noisegen.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/noisegen.h deleted file mode 100644 index 5c3f4b0..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/noisegen.h +++ /dev/null @@ -1,208 +0,0 @@ -// noisegen.h -// -// Copyright (C) 2003, 2004 Jason Bevins -// -// This library is free software; you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation; either version 2.1 of the License, or (at -// your option) any later version. -// -// This library is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -// License (COPYING.txt) for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with this library; if not, write to the Free Software Foundation, -// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -// -// The developer's email is jlbezigvins@gmzigail.com (for great email, take -// off every 'zig'.) -// - -#ifndef NOISE_NOISEGEN_H -#define NOISE_NOISEGEN_H - -#include <math.h> -#include "basictypes.h" - -namespace noise -{ - - /// @addtogroup libnoise - /// @{ - - /// Enumerates the noise quality. - enum NoiseQuality - { - - /// Generates coherent noise quickly. When a coherent-noise function with - /// this quality setting is used to generate a bump-map image, there are - /// noticeable "creasing" artifacts in the resulting image. This is - /// because the derivative of that function is discontinuous at integer - /// boundaries. - QUALITY_FAST = 0, - - /// Generates standard-quality coherent noise. When a coherent-noise - /// function with this quality setting is used to generate a bump-map - /// image, there are some minor "creasing" artifacts in the resulting - /// image. This is because the second derivative of that function is - /// discontinuous at integer boundaries. - QUALITY_STD = 1, - - /// Generates the best-quality coherent noise. When a coherent-noise - /// function with this quality setting is used to generate a bump-map - /// image, there are no "creasing" artifacts in the resulting image. This - /// is because the first and second derivatives of that function are - /// continuous at integer boundaries. - QUALITY_BEST = 2 - - }; - - /// Generates a gradient-coherent-noise value from the coordinates of a - /// three-dimensional input value. - /// - /// @param x The @a x coordinate of the input value. - /// @param y The @a y coordinate of the input value. - /// @param z The @a z coordinate of the input value. - /// @param seed The random number seed. - /// @param noiseQuality The quality of the coherent-noise. - /// - /// @returns The generated gradient-coherent-noise value. - /// - /// The return value ranges from -1.0 to +1.0. - /// - /// For an explanation of the difference between <i>gradient</i> noise and - /// <i>value</i> noise, see the comments for the GradientNoise3D() function. - DLL double GradientCoherentNoise3D(double x, double y, double z, int seed = 0, - NoiseQuality noiseQuality = QUALITY_STD); - - /// Generates a gradient-noise value from the coordinates of a - /// three-dimensional input value and the integer coordinates of a - /// nearby three-dimensional value. - /// - /// @param fx The floating-point @a x coordinate of the input value. - /// @param fy The floating-point @a y coordinate of the input value. - /// @param fz The floating-point @a z coordinate of the input value. - /// @param ix The integer @a x coordinate of a nearby value. - /// @param iy The integer @a y coordinate of a nearby value. - /// @param iz The integer @a z coordinate of a nearby value. - /// @param seed The random number seed. - /// - /// @returns The generated gradient-noise value. - /// - /// @pre The difference between @a fx and @a ix must be less than or equal - /// to one. - /// - /// @pre The difference between @a fy and @a iy must be less than or equal - /// to one. - /// - /// @pre The difference between @a fz and @a iz must be less than or equal - /// to one. - /// - /// A <i>gradient</i>-noise function generates better-quality noise than a - /// <i>value</i>-noise function. Most noise modules use gradient noise for - /// this reason, although it takes much longer to calculate. - /// - /// The return value ranges from -1.0 to +1.0. - /// - /// This function generates a gradient-noise value by performing the - /// following steps: - /// - It first calculates a random normalized vector based on the - /// nearby integer value passed to this function. - /// - It then calculates a new value by adding this vector to the - /// nearby integer value passed to this function. - /// - It then calculates the dot product of the above-generated value - /// and the floating-point input value passed to this function. - /// - /// A noise function differs from a random-number generator because it - /// always returns the same output value if the same input value is passed - /// to it. - DLL double GradientNoise3D (double fx, double fy, double fz, int ix, int iy, - int iz, int seed = 0); - - /// Generates an integer-noise value from the coordinates of a - /// three-dimensional input value. - /// - /// @param x The integer @a x coordinate of the input value. - /// @param y The integer @a y coordinate of the input value. - /// @param z The integer @a z coordinate of the input value. - /// @param seed A random number seed. - /// - /// @returns The generated integer-noise value. - /// - /// The return value ranges from 0 to 2147483647. - /// - /// A noise function differs from a random-number generator because it - /// always returns the same output value if the same input value is passed - /// to it. - DLL int IntValueNoise3D(int x, int y, int z, int seed = 0); - - /// Modifies a floating-point value so that it can be stored in a - /// noise::int32 variable. - /// - /// @param n A floating-point number. - /// - /// @returns The modified floating-point number. - /// - /// This function does not modify @a n. - /// - /// In libnoise, the noise-generating algorithms are all integer-based; - /// they use variables of type noise::int32. Before calling a noise - /// function, pass the @a x, @a y, and @a z coordinates to this function to - /// ensure that these coordinates can be cast to a noise::int32 value. - /// - /// Although you could do a straight cast from double to noise::int32, the - /// resulting value may differ between platforms. By using this function, - /// you ensure that the resulting value is identical between platforms. - inline double MakeInt32Range (double n) - { - if (n >= 1073741824.0) { - return (2.0 * fmod (n, 1073741824.0)) - 1073741824.0; - } else if (n <= -1073741824.0) { - return (2.0 * fmod (n, 1073741824.0)) + 1073741824.0; - } else { - return n; - } - } - - /// Generates a value-coherent-noise value from the coordinates of a - /// three-dimensional input value. - /// - /// @param x The @a x coordinate of the input value. - /// @param y The @a y coordinate of the input value. - /// @param z The @a z coordinate of the input value. - /// @param seed The random number seed. - /// @param noiseQuality The quality of the coherent-noise. - /// - /// @returns The generated value-coherent-noise value. - /// - /// The return value ranges from -1.0 to +1.0. - /// - /// For an explanation of the difference between <i>gradient</i> noise and - /// <i>value</i> noise, see the comments for the GradientNoise3D() function. - DLL double ValueCoherentNoise3D(double x, double y, double z, int seed = 0, - NoiseQuality noiseQuality = QUALITY_STD); - - /// Generates a value-noise value from the coordinates of a - /// three-dimensional input value. - /// - /// @param x The @a x coordinate of the input value. - /// @param y The @a y coordinate of the input value. - /// @param z The @a z coordinate of the input value. - /// @param seed A random number seed. - /// - /// @returns The generated value-noise value. - /// - /// The return value ranges from -1.0 to +1.0. - /// - /// A noise function differs from a random-number generator because it - /// always returns the same output value if the same input value is passed - /// to it. - DLL double ValueNoise3D(int x, int y, int z, int seed = 0); - - /// @} - -} - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Includes/vectortable.h b/ExcavatorSimulator/thirdParty/LibNoise/Includes/vectortable.h deleted file mode 100644 index 3724610..0000000 --- a/ExcavatorSimulator/thirdParty/LibNoise/Includes/vectortable.h +++ /dev/null @@ -1,290 +0,0 @@ -// vectortable.h -// -// Written by Jason Bevins. Actually it's the output of a program written -// by me. I'm not going to copyright a bunch of random numbers (although -// you could probably do so in the States, the way things are going down -// there :-) -// -// This file is in the public domain. -// - -#ifndef NOISE_VECTORTABLE_H -#define NOISE_VECTORTABLE_H - -#ifndef DOXYGEN_SHOULD_SKIP_THIS - -namespace noise -{ - - // A table of 256 random normalized vectors. Each row is an (x, y, z, 0) - // coordinate. The 0 is used as padding so we can use bit shifts to index - // any row in the table. These vectors have an even statistical - // distribution, which improves the quality of the coherent noise - // generated by these vectors. For more information, see "GPU Gems", - // Chapter 5 - Implementing Improved Perlin Noise by Ken Perlin, - // specifically page 76. - double g_randomVectors[256 * 4] = - { - -0.763874, -0.596439, -0.246489, 0.0, - 0.396055, 0.904518, -0.158073, 0.0, - -0.499004, -0.8665, -0.0131631, 0.0, - 0.468724, -0.824756, 0.316346, 0.0, - 0.829598, 0.43195, 0.353816, 0.0, - -0.454473, 0.629497, -0.630228, 0.0, - -0.162349, -0.869962, -0.465628, 0.0, - 0.932805, 0.253451, 0.256198, 0.0, - -0.345419, 0.927299, -0.144227, 0.0, - -0.715026, -0.293698, -0.634413, 0.0, - -0.245997, 0.717467, -0.651711, 0.0, - -0.967409, -0.250435, -0.037451, 0.0, - 0.901729, 0.397108, -0.170852, 0.0, - 0.892657, -0.0720622, -0.444938, 0.0, - 0.0260084, -0.0361701, 0.999007, 0.0, - 0.949107, -0.19486, 0.247439, 0.0, - 0.471803, -0.807064, -0.355036, 0.0, - 0.879737, 0.141845, 0.453809, 0.0, - 0.570747, 0.696415, 0.435033, 0.0, - -0.141751, -0.988233, -0.0574584, 0.0, - -0.58219, -0.0303005, 0.812488, 0.0, - -0.60922, 0.239482, -0.755975, 0.0, - 0.299394, -0.197066, -0.933557, 0.0, - -0.851615, -0.220702, -0.47544, 0.0, - 0.848886, 0.341829, -0.403169, 0.0, - -0.156129, -0.687241, 0.709453, 0.0, - -0.665651, 0.626724, 0.405124, 0.0, - 0.595914, -0.674582, 0.43569, 0.0, - 0.171025, -0.509292, 0.843428, 0.0, - 0.78605, 0.536414, -0.307222, 0.0, - 0.18905, -0.791613, 0.581042, 0.0, - -0.294916, 0.844994, 0.446105, 0.0, - 0.342031, -0.58736, -0.7335, 0.0, - 0.57155, 0.7869, 0.232635, 0.0, - 0.885026, -0.408223, 0.223791, 0.0, - -0.789518, 0.571645, 0.223347, 0.0, - 0.774571, 0.31566, 0.548087, 0.0, - -0.79695, -0.0433603, -0.602487, 0.0, - -0.142425, -0.473249, -0.869339, 0.0, - -0.0698838, 0.170442, 0.982886, 0.0, - 0.687815, -0.484748, 0.540306, 0.0, - 0.543703, -0.534446, -0.647112, 0.0, - 0.97186, 0.184391, -0.146588, 0.0, - 0.707084, 0.485713, -0.513921, 0.0, - 0.942302, 0.331945, 0.043348, 0.0, - 0.499084, 0.599922, 0.625307, 0.0, - -0.289203, 0.211107, 0.9337, 0.0, - 0.412433, -0.71667, -0.56239, 0.0, - 0.87721, -0.082816, 0.47291, 0.0, - -0.420685, -0.214278, 0.881538, 0.0, - 0.752558, -0.0391579, 0.657361, 0.0, - 0.0765725, -0.996789, 0.0234082, 0.0, - -0.544312, -0.309435, -0.779727, 0.0, - -0.455358, -0.415572, 0.787368, 0.0, - -0.874586, 0.483746, 0.0330131, 0.0, - 0.245172, -0.0838623, 0.965846, 0.0, - 0.382293, -0.432813, 0.81641, 0.0, - -0.287735, -0.905514, 0.311853, 0.0, - -0.667704, 0.704955, -0.239186, 0.0, - 0.717885, -0.464002, -0.518983, 0.0, - 0.976342, -0.214895, 0.0240053, 0.0, - -0.0733096, -0.921136, 0.382276, 0.0, - -0.986284, 0.151224, -0.0661379, 0.0, - -0.899319, -0.429671, 0.0812908, 0.0, - 0.652102, -0.724625, 0.222893, 0.0, - 0.203761, 0.458023, -0.865272, 0.0, - -0.030396, 0.698724, -0.714745, 0.0, - -0.460232, 0.839138, 0.289887, 0.0, - -0.0898602, 0.837894, 0.538386, 0.0, - -0.731595, 0.0793784, 0.677102, 0.0, - -0.447236, -0.788397, 0.422386, 0.0, - 0.186481, 0.645855, -0.740335, 0.0, - -0.259006, 0.935463, 0.240467, 0.0, - 0.445839, 0.819655, -0.359712, 0.0, - 0.349962, 0.755022, -0.554499, 0.0, - -0.997078, -0.0359577, 0.0673977, 0.0, - -0.431163, -0.147516, -0.890133, 0.0, - 0.299648, -0.63914, 0.708316, 0.0, - 0.397043, 0.566526, -0.722084, 0.0, - -0.502489, 0.438308, -0.745246, 0.0, - 0.0687235, 0.354097, 0.93268, 0.0, - -0.0476651, -0.462597, 0.885286, 0.0, - -0.221934, 0.900739, -0.373383, 0.0, - -0.956107, -0.225676, 0.186893, 0.0, - -0.187627, 0.391487, -0.900852, 0.0, - -0.224209, -0.315405, 0.92209, 0.0, - -0.730807, -0.537068, 0.421283, 0.0, - -0.0353135, -0.816748, 0.575913, 0.0, - -0.941391, 0.176991, -0.287153, 0.0, - -0.154174, 0.390458, 0.90762, 0.0, - -0.283847, 0.533842, 0.796519, 0.0, - -0.482737, -0.850448, 0.209052, 0.0, - -0.649175, 0.477748, 0.591886, 0.0, - 0.885373, -0.405387, -0.227543, 0.0, - -0.147261, 0.181623, -0.972279, 0.0, - 0.0959236, -0.115847, -0.988624, 0.0, - -0.89724, -0.191348, 0.397928, 0.0, - 0.903553, -0.428461, -0.00350461, 0.0, - 0.849072, -0.295807, -0.437693, 0.0, - 0.65551, 0.741754, -0.141804, 0.0, - 0.61598, -0.178669, 0.767232, 0.0, - 0.0112967, 0.932256, -0.361623, 0.0, - -0.793031, 0.258012, 0.551845, 0.0, - 0.421933, 0.454311, 0.784585, 0.0, - -0.319993, 0.0401618, -0.946568, 0.0, - -0.81571, 0.551307, -0.175151, 0.0, - -0.377644, 0.00322313, 0.925945, 0.0, - 0.129759, -0.666581, -0.734052, 0.0, - 0.601901, -0.654237, -0.457919, 0.0, - -0.927463, -0.0343576, -0.372334, 0.0, - -0.438663, -0.868301, -0.231578, 0.0, - -0.648845, -0.749138, -0.133387, 0.0, - 0.507393, -0.588294, 0.629653, 0.0, - 0.726958, 0.623665, 0.287358, 0.0, - 0.411159, 0.367614, -0.834151, 0.0, - 0.806333, 0.585117, -0.0864016, 0.0, - 0.263935, -0.880876, 0.392932, 0.0, - 0.421546, -0.201336, 0.884174, 0.0, - -0.683198, -0.569557, -0.456996, 0.0, - -0.117116, -0.0406654, -0.992285, 0.0, - -0.643679, -0.109196, -0.757465, 0.0, - -0.561559, -0.62989, 0.536554, 0.0, - 0.0628422, 0.104677, -0.992519, 0.0, - 0.480759, -0.2867, -0.828658, 0.0, - -0.228559, -0.228965, -0.946222, 0.0, - -0.10194, -0.65706, -0.746914, 0.0, - 0.0689193, -0.678236, 0.731605, 0.0, - 0.401019, -0.754026, 0.52022, 0.0, - -0.742141, 0.547083, -0.387203, 0.0, - -0.00210603, -0.796417, -0.604745, 0.0, - 0.296725, -0.409909, -0.862513, 0.0, - -0.260932, -0.798201, 0.542945, 0.0, - -0.641628, 0.742379, 0.192838, 0.0, - -0.186009, -0.101514, 0.97729, 0.0, - 0.106711, -0.962067, 0.251079, 0.0, - -0.743499, 0.30988, -0.592607, 0.0, - -0.795853, -0.605066, -0.0226607, 0.0, - -0.828661, -0.419471, -0.370628, 0.0, - 0.0847218, -0.489815, -0.8677, 0.0, - -0.381405, 0.788019, -0.483276, 0.0, - 0.282042, -0.953394, 0.107205, 0.0, - 0.530774, 0.847413, 0.0130696, 0.0, - 0.0515397, 0.922524, 0.382484, 0.0, - -0.631467, -0.709046, 0.313852, 0.0, - 0.688248, 0.517273, 0.508668, 0.0, - 0.646689, -0.333782, -0.685845, 0.0, - -0.932528, -0.247532, -0.262906, 0.0, - 0.630609, 0.68757, -0.359973, 0.0, - 0.577805, -0.394189, 0.714673, 0.0, - -0.887833, -0.437301, -0.14325, 0.0, - 0.690982, 0.174003, 0.701617, 0.0, - -0.866701, 0.0118182, 0.498689, 0.0, - -0.482876, 0.727143, 0.487949, 0.0, - -0.577567, 0.682593, -0.447752, 0.0, - 0.373768, 0.0982991, 0.922299, 0.0, - 0.170744, 0.964243, -0.202687, 0.0, - 0.993654, -0.035791, -0.106632, 0.0, - 0.587065, 0.4143, -0.695493, 0.0, - -0.396509, 0.26509, -0.878924, 0.0, - -0.0866853, 0.83553, -0.542563, 0.0, - 0.923193, 0.133398, -0.360443, 0.0, - 0.00379108, -0.258618, 0.965972, 0.0, - 0.239144, 0.245154, -0.939526, 0.0, - 0.758731, -0.555871, 0.33961, 0.0, - 0.295355, 0.309513, 0.903862, 0.0, - 0.0531222, -0.91003, -0.411124, 0.0, - 0.270452, 0.0229439, -0.96246, 0.0, - 0.563634, 0.0324352, 0.825387, 0.0, - 0.156326, 0.147392, 0.976646, 0.0, - -0.0410141, 0.981824, 0.185309, 0.0, - -0.385562, -0.576343, -0.720535, 0.0, - 0.388281, 0.904441, 0.176702, 0.0, - 0.945561, -0.192859, -0.262146, 0.0, - 0.844504, 0.520193, 0.127325, 0.0, - 0.0330893, 0.999121, -0.0257505, 0.0, - -0.592616, -0.482475, -0.644999, 0.0, - 0.539471, 0.631024, -0.557476, 0.0, - 0.655851, -0.027319, -0.754396, 0.0, - 0.274465, 0.887659, 0.369772, 0.0, - -0.123419, 0.975177, -0.183842, 0.0, - -0.223429, 0.708045, 0.66989, 0.0, - -0.908654, 0.196302, 0.368528, 0.0, - -0.95759, -0.00863708, 0.288005, 0.0, - 0.960535, 0.030592, 0.276472, 0.0, - -0.413146, 0.907537, 0.0754161, 0.0, - -0.847992, 0.350849, -0.397259, 0.0, - 0.614736, 0.395841, 0.68221, 0.0, - -0.503504, -0.666128, -0.550234, 0.0, - -0.268833, -0.738524, -0.618314, 0.0, - 0.792737, -0.60001, -0.107502, 0.0, - -0.637582, 0.508144, -0.579032, 0.0, - 0.750105, 0.282165, -0.598101, 0.0, - -0.351199, -0.392294, -0.850155, 0.0, - 0.250126, -0.960993, -0.118025, 0.0, - -0.732341, 0.680909, -0.0063274, 0.0, - -0.760674, -0.141009, 0.633634, 0.0, - 0.222823, -0.304012, 0.926243, 0.0, - 0.209178, 0.505671, 0.836984, 0.0, - 0.757914, -0.56629, -0.323857, 0.0, - -0.782926, -0.339196, 0.52151, 0.0, - -0.462952, 0.585565, 0.665424, 0.0, - 0.61879, 0.194119, -0.761194, 0.0, - 0.741388, -0.276743, 0.611357, 0.0, - 0.707571, 0.702621, 0.0752872, 0.0, - 0.156562, 0.819977, 0.550569, 0.0, - -0.793606, 0.440216, 0.42, 0.0, - 0.234547, 0.885309, -0.401517, 0.0, - 0.132598, 0.80115, -0.58359, 0.0, - -0.377899, -0.639179, 0.669808, 0.0, - -0.865993, -0.396465, 0.304748, 0.0, - -0.624815, -0.44283, 0.643046, 0.0, - -0.485705, 0.825614, -0.287146, 0.0, - -0.971788, 0.175535, 0.157529, 0.0, - -0.456027, 0.392629, 0.798675, 0.0, - -0.0104443, 0.521623, -0.853112, 0.0, - -0.660575, -0.74519, 0.091282, 0.0, - -0.0157698, -0.307475, -0.951425, 0.0, - -0.603467, -0.250192, 0.757121, 0.0, - 0.506876, 0.25006, 0.824952, 0.0, - 0.255404, 0.966794, 0.00884498, 0.0, - 0.466764, -0.874228, -0.133625, 0.0, - 0.475077, -0.0682351, -0.877295, 0.0, - -0.224967, -0.938972, -0.260233, 0.0, - -0.377929, -0.814757, -0.439705, 0.0, - -0.305847, 0.542333, -0.782517, 0.0, - 0.26658, -0.902905, -0.337191, 0.0, - 0.0275773, 0.322158, -0.946284, 0.0, - 0.0185422, 0.716349, 0.697496, 0.0, - -0.20483, 0.978416, 0.0273371, 0.0, - -0.898276, 0.373969, 0.230752, 0.0, - -0.00909378, 0.546594, 0.837349, 0.0, - 0.6602, -0.751089, 0.000959236, 0.0, - 0.855301, -0.303056, 0.420259, 0.0, - 0.797138, 0.0623013, -0.600574, 0.0, - 0.48947, -0.866813, 0.0951509, 0.0, - 0.251142, 0.674531, 0.694216, 0.0, - -0.578422, -0.737373, -0.348867, 0.0, - -0.254689, -0.514807, 0.818601, 0.0, - 0.374972, 0.761612, 0.528529, 0.0, - 0.640303, -0.734271, -0.225517, 0.0, - -0.638076, 0.285527, 0.715075, 0.0, - 0.772956, -0.15984, -0.613995, 0.0, - 0.798217, -0.590628, 0.118356, 0.0, - -0.986276, -0.0578337, -0.154644, 0.0, - -0.312988, -0.94549, 0.0899272, 0.0, - -0.497338, 0.178325, 0.849032, 0.0, - -0.101136, -0.981014, 0.165477, 0.0, - -0.521688, 0.0553434, -0.851339, 0.0, - -0.786182, -0.583814, 0.202678, 0.0, - -0.565191, 0.821858, -0.0714658, 0.0, - 0.437895, 0.152598, -0.885981, 0.0, - -0.92394, 0.353436, -0.14635, 0.0, - 0.212189, -0.815162, -0.538969, 0.0, - -0.859262, 0.143405, -0.491024, 0.0, - 0.991353, 0.112814, 0.0670273, 0.0, - 0.0337884, -0.979891, -0.196654, 0.0 - }; - -} - -#endif - -#endif diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Libraries/libnoise.x64.lib b/ExcavatorSimulator/thirdParty/LibNoise/Libraries/libnoise.x64.lib deleted file mode 100644 index db92ad7b862fcc532d44e12dce4b50f1db4a3050..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121304 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 S{BH1s8dTaLw1LziwEqJ&V4mdw diff --git a/ExcavatorSimulator/thirdParty/LibNoise/Libraries/libnoise.x86.lib b/ExcavatorSimulator/thirdParty/LibNoise/Libraries/libnoise.x86.lib deleted file mode 100644 index db92ad7b862fcc532d44e12dce4b50f1db4a3050..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121304 zcmeHQd3+T`^6$oTS(jxM6%i2?6%jF<0Yvf=u7KPG45)-a9uNo#n*<gV@mR$R@B6}I z74g1rbh$+ZM8pdf6<H6!3l%TKU)A(X-pq7WPfy;<x_|uqe99zDRoAzxtE;=KdwLG* zR$Mu5V)y#THxK@IT-)H^-d|bnFYP<FZ`Z;3eN25J>OCc7qlH2qutmtmhYR^+Jw+EJ zghYC6zM_%yghXoBMA6Yhg+w}Pk)l}*ghaaa3PnqxOr(>4acL6#1R6I-$TqjTv@iSw zDqp8))&eL4==Sc4o&r9kg`E^F9U&yr+OCR5HUdmQg%>L7v`R>ay1`GNGoc)$J2xm= zx((!kMnRn&Y6U-ma(gODJRu~~nd=qJzez}>W#=h6WsQ&yCEzDe6Zm(e3xFT#2ABFa z780om*b?cW*@{jbC1lgx6<s)1NTeGJ74^LeWPlDnS5cE)APaOV*yfPAil%{Wk!C_U zNVCfno!w7Jq;tMfblzP;B3<~6q8r}_9Y8PSD_RWLkd`-9v|@*lNGs<lS_S1I_1j0$ zA$x*;pr%j{q|-pxp^X$x9WNx(MQ1CzsYFPmXF(TIzl#+$1KbYPho3;FEfcai*cR#H zhKg?50<u7_fnKDOKqpf30~8$!Wgw02F67}^iY^BKK)QLVqLaXWNQXiF9Rk_IfJQ$f z<Pk3_I(?^*NSAa{bo~+`k#63l=;=p<MCuQ^k&fu9=<p{%Cs0vmA&=~-sA8j#NSCZo zbjx`{B0U4Tkop4_q!x=@g0flw6+H(12FgI{ze~}iQ9>e>?XBqVHwcMT34Vt(cZ8x_ z!G1_npiW4SLfJ@1x-_P>kS#YUn%-DQq`99eng_oly#RiNG~jeaEkQ5R(Q6frnJ;9k zYDJd<57N9l72P*lNTdhuQuGkC8PapJ6s-eZr1eV_y$x8A2K=Jvm~BEjG!}jW6}J$w zHP{(xnoE~K`y<^3bwOGPwnQ2T<suzBTT!d;0V7cH?Lr=RjiSrufh^E%PbnG*dXbK6 zps4j6&;>LW{6DL?qRZO|iF7;I5NXg1MaM%q4mE+FKx3~HvMuOBx_qIcJD?4b25nc= zwi!GJ%DPF>IMCY;_>l&eDe90VB+?cA6y32B$^aS+c0_8wz$GZBJy3}dvg3=2ri0y) zW^Pt=7T6Z)?DdMy1?))Yw^Ve+`=A5p&Y_B494#c$VrWmK<<BU3>0BX^UT&pm)i)3a zfQC#`)Uh+@2kP*MqLNucc7`&Mu7r9a{R_%P8uGcK>@`9<ln+0FN_Gg@1#lz%1KJho z^m0XKEr5Ceodfbn=Rct6%AHU)(7!+i>BY|#Edg1i6$Oe`f}N38S1XzfKaqx(DC#l_ z^a6DTOi0P8LY@G1K)UKGMR&o!A>F%P(F4%tNDqxs^whpECIP+vm7?{Xz{i2!nyqN) zr=Sa{>vosM!%v{(H$tA+P*Fu8#0;SGnku@wrI1K>K^>8vhToB10gOn)CMxQ7p^!)? z>{2uyaCV=s=<0<~2GHGe6x|2qAU&9;=wXmUdUb?LAHh$cHySH?6Y7Wb_5wx2J_nsZ z-J2>p@e05WG#=`mfchd$1sq7#vlN|io{&h_3|4eQKNu5%=7YVEo&g!8XE!N&{yrg* z3MMJac|=HuTEb7D31vd&LH&{b@tmS-05j4(%@zH7x{yc(fE6h(Us3KN7}J3!d@5v* zb&7^VJ0MMhe?uy}K~ZH_h_66ZqZM6SDJ0V41&W@4-;wTFpy)~HBS_^v6+H%ZMC#FB z(ZsPr_9|C&-R?pnEyz-I{~RHa9$cm9k$r_kTDwfq>);niZ``Tq%`MO_K<~6uG<-AE z0jMX~z#*`8PoRk#h3r#b(Ttu#BK@<TOBcgWpgA2BU3Uv$0$Kp}L0Tjfz5EpPd!Sbm zidIA0B8`}&sQ1}IBK7)SQRx^V^KVi#r4@`tKvmx;nz0rz09~J@XwE{=4fOaXMfZZO zke+X<XwhYW0cZ`_25H1LMfr`Oe4sva6_r9gADOS{(e5zr0X?=}(K{=^MnLbLid0n8 zcfjC5Lko&}_8Qh>Xy3sFgN7EBm5wd1D6LA0qN0Jr`}fy5#91DTVkpQTKJcWXp1B3N zBBy_Gb^nU;f{MbCqY_RjiNwg<!hr*Gd*u$y%1VraLQAJiEsEyL>65G;Rxzz|Tyj7~ z$+WU$!u>Ht{pFTDyjNasZm+x%ZQHd^<flrnm2A#1JB_)yh54S&l*-x(=j3!C>jma= z>Xp^DT>^FC&y=osLOD5Eq>dq=gDTvP8%pglv#hkdBw3k&Hc6Iw&FvIhSkur^Z02&X z?m_SYJD4CaqUcv{i5!0AQbD!t6R9etQ37foQCv1Hnc$TV95^u5)Ykmyy%F^S$JLF} z-se~86ak*hRS8`=*KuLWm19X+X=%{{d{FB$(RxU^qU@j}xPhlrUP6HD_Af0@Mz}xE z!prN;6m)y0OhycyF1NZmSw5|}I+>tk^Ky#@2K?KK-Pe`qLUlZJH;N^l>O!;_*?SDE z*m2C&g?>}y>yp;|SaPrOlED?F<<+Fwq2NGgx90bCcB*DEI(j^D)H|%YxUxD{oopqb zbJK84;lo-YvUIZhM<5EwKz|-wRvZ}+3_OMs^u81f30$d+oUiYalJ;X@Jn>ZTW#j@K zqGt3nGW&W1$rqzL#FPi0VkAs1YcCRAe!v0UxpgF{fDriHu&EQ1l~LoEiOEz3<A5Gk zUO6M^^IRH|z3ax5Px&q<NIS2Y+vmMBV+{9=TLAbMCt`Bo7%~<b0^aDkMI^3(6!>56 z*eaZe;Y!h+qG<~eo&{Uhzu4?iF%5=nogbH*`GJX29I2H5&?STS_@)^(vCqhylhc-Z z4lBD9OcM)lIqZI+WhZB3A1hy8puxOm=1L@)i?a2qy7Tbdl9I^QXIYrm*Z8ekpOx~p zKFPzizKI!IpLiZ_eU=4VOMA%X1)^E`^2i3|l#|TR`WjTX_47*0$}0X5<p4%5Y@Obf z$-hramXDj6U|92VgR3M<0W7<J@wjQ_#g(Pi)`i<jVCe%fUpHi0ahb9FoZOQ;1UN&h zvA{Fd1av8gV$iti;^|2hJ6Z8y__P+m@&_j?t4gb29Xl>*+d|eNSpKkNvV<=<2t1nC zDbbg}G}y%^u#g7z1W}htE3s@$c!}c2*-50E<2p8#vWg*rrAPa@R=l+cmT%+Lph4>z zfeNKj2x72sbAaGWV8@a{4Hk~BnbBSbHJChJ18>0$V;AFmq2bHO1tSjmoE^lhxQ(Tf zqmuyz$zPOB0`Ug4vSfMWgeK3xgaM&PMR|2)MVXtvq`3oYCD=nrO&V)o02BBKv|jjf zXM-4m^?+%R^MyTzi;H?@c64_e>w(+*7O7yqK+LFZ3D_}6eBpj!U_u8b9`n7zg4t9i z>Ek>Ek{!APfx_((J&MOobTSmC1)hnC_DV{^#w$&7DX-+X7_W?AcY?65e<WyrjKv18 z^!KnhC4g6Q`g~Et%mf}pmBD(Y4YR3C(#?1XBzsXdg~Id7iOF%3la*sDiYp_-b&qIX z#z%QUD_4yc_j#<DnR8q#DfA!{V<@~?@M7EZCGu`%FJqyB^qNRqq!joX56yGAvf?RI zqn5fo3{2SA{VV>FtjvQXS4o0q4X<=91sDe=&{%n{K@K$ypE@;OEi46m-GM>C^#e0I z25`sdRgBefh8|o<S+QfsA~lD5lz|Byop{XmAq!?xnWX>o5J-0DE(8j%pH!4rRY&#{ z0~2}yOyR1$^BN>;xSv=GF#Uv3VbxCzaz0;XxHw-nv%6Ddvvts)!&71_3G`@Ef7qi9 zOz3^YW8R}Jm`!DpF3Lk7*}<a;6rM*<tDGJ^1vW6DRf)&L@f=uc;b&&bfg~(o#D%3g z13;CCHV#CxctSZn%96#Exn*U3MG!Ly?2e`s_V|KsFR!UjZe?ZhOh#2+?%-Z|!~LKV z%#ztLO|6Ow<55{dy6WM*C_l1jIVJ`XMz)GU20330Vz{^%WM<Fl4aX1rmRBV!t4k}& z2UpN8(ifaD-xr4B3FWBrbK%4bUwmP{f7p&8f$@Z>BOjw;FheXMxV>279^H)GGxbi* zag5lusr)d{9Uds{Wf(tYDq!WS5wbpiHE?o18!dukr%nB};WLI1#(EY>INnDvqL(r( zyr)_UM3qerlV&)i2=oR9k=rABmR3zID~6q=h(iPse6+VRLN?ACA)4}410&<A(GpmC zp~vPXlD$7QLm6|)>6C&fMtW+~LXl1>h+<SmP^41|#*QF}s)YBq)*=u<f|)@78yM*q zgjlTGRl^ck7^GDU;;?R6Gmf;1K^)d?YsQgQF%bJ1m(ZsA5quDx84fn^Tk=PgOvZ2{ z7E+=pn}Op7{a!PsR+J~<s8>YLkKn`5P{UI5OSsJN6sU%=<`-H5OZR8()zqF5vKdk$ zL{XnQOkiMShLmUt>?Ac@HNS*w4!y%SWsf5Q&d-#pIe!EnxG2NHI=>}<M9HKp8?lfQ zMcE7-*ZKRFPluILl<OP0XxAh~Y#h@lm~u;&jd98-esCFvB{WesazUe#&sk@&;x?8_ zHoE}@$zPOB0&$&WK=F(y58+v8_t5!me1n%tIR?$cc!iM}T!MNY@&}#;noWPmdO|cS zUmn?}oN|&G-f+_z*=zmM^2pX_S(w(>_^n!>mGZSd$-}k2i5Xj;cph$jmIYf&d&swb zBr9JY*`S<qk{McG)9<yuI!>>T&Hh)dO)nSYfi|=yRgN|q8@bQ|)5@w#r<To(bVxHF z<CV0Km1~+seg4Taa!wj4f!5`i!tQG3i?Fcoh=0x5eMWQG(IZMGz0%m)qy+deT|w8G z;jm+w=I9R8)b;_qzh~mYrc9;a7}oGC-%<d}_8n`rEZkNCOAj1jWy_~ecc%{MrBe!` z@SlWbK?ILlTZ>@%P-e{;akl*6DSm4aEZ;fU$`%|19?k2N=sVJ-mUd+EHlfHUh`Q9W zj%8!QP!vBd=_1`+ThZA{VCm6*t`%=Bg5}$IHE4)&Mx3-6h8waF#9-m(AkCi<=QTRR zFlYddsJ@|@(Ow4YVDfkkyah81Z)CN!$%By#1|afzc%Z5o*sQpXrIJIG0R_olluZKh z1~fdw6*U<eY+z!74<TU{dkk{E(8F+Xk;lxA?m=>dramSnY(}CC-Zw0mO=XfT%|jsB z!7&IFu3ro-Et!xkaVA4i-Vx1;Etd+^CVqb26D@^WeDOM3AT=L}C5Czeq28=1)?BDI zMIj!4*f*CEoLt0{LrqS+jibU+z$sgs#PEu2qDizgYE{!2GcOZatb}k#v-5s!+>9lL zdMq7TQxs1QHCgun&=q6G!&}sP+7nR(KliZ^teKuz@Veo}o6uND<n$;m8#iofa$I#~ zaoNeq(g_o*s}j5%o=GZ9tvhQc45J^fL7O2(v%;vwGqK~Ly-0NV<X{&CiWDl!=5cxB zWJNVxhoufN5-F}!>wsAzS(zBf%2~z0NNHaTH1cvW&_)P#6y}ysfO|DeW(W`e)*@K` z$oS=3i(vVq;+JnNf^o$u9{M|LPbUIGD+JCw#F0WR7ADn32YVURU}02EgS`xDurRBp z!CnR<BHIRfM2uv`h{ZDT5z$&Cx_mOYMS&uPAR@B3+=w`ATya@4ue6vu>shltj$*_< z=aj=UhU0}5JC^GdnXoU|j^8O+o5ONiY@&CRENWEqR}&`_xh+Jnw?!CL>uGlUSh54a z&<a}7mIBa&`MQNx^-+w_yEqoM@7fAPl}&a*GaOQ+D4Rp%@3U5u7^ff;r=br}V%D9& zBpm7m9IL4tM983qLZ|M^T`Be<aIIHxPrz0JOCK4#bXy55eN^nyZ6!kem^O^5BO4{G zOApP8BCCst92wLQ-A-BstVOW==$6vTw-&+jquWX=-&(|rrBpL%lOf0KCQdKnQ9R~w zY9kR@IvE{RKxNA(W!#8L33(wkSqAI)sNCr=6Bl+|_iZfqSOdduJTqV05>R_DNCNjR zhHKHrk590=gE7xq1k3lm5N=t#tpt{?A9p2{SPel`J9&br%lA?^sYD+fY$dRCcZ$b> z;0Cq`eheM%tKkf0CrSpEn|AzIvX$R~g5bLa0cEu=0`*%sq81hHWiTw#<_*s;O<WkT zsONkrwBa_EN`^B73X&hf9|gn<ea=O|(d$1W8@hce{jKhb)r$<zxNIeu@IWH+x_N55 zhzKl3NnaOc`M5qDC4jCTU2DzLtwqq$84O`hH?pBqk_^1BTd|wVCB4@KgcJaGC$YHx zUXX;>y`$gxGqZu6X$EbQWoFY}t*6=1>i(A_!td5cNHH(h8>CX1aQgLnvnci;cQkP3 zT<WcOPR4n~J~%C@g>YD>I#$K&4DPJ@71~JB5sB7j6Oqi4@IpGiw&5~WC1`HPN&FwJ zh$)rRx60F?B;jUMPE+vXA@K!MF_zYVHe+IJq(IJq;>mC$ZR)-XqrrR|jIrhP%Lr(2 zk=GGKhy^Yr;CbOng4Be}e<MMZ2t;L?g^l*{*o(4helH?d7ME9**>21g*l;ub(<Exu zO>GMB`F0dL=i-)fsHxDi2V94d@P22uIK%U=__C;Rq^G488sp2N#!+c#j4z9Gv=|D5 zBXg}}YoRfeL5qnul}n2$n2Ik@(KznoS_x|!#|Jdy)<PI{#_<A8XDx(rerWE*q3O64 zH^ymGseHJOBW0z9oOxTIBjsRxFah+2iD{K%;U-wT2|nWNMnM!O29?yB%D^6um@$M< zhdQzuqkKyNEPH6hv<VZ-lT}sje3h?r_|B-flBmr&7ig?Va6)az@06@<!Ln+shG@<X zo-pcE89JjLZfG_Y@5*4nv2qkG(!`NMEs^e`X|R>R(rf%A!CJ~$bNHsvF{MyTl&3%y zf(k4JFa-0B6%M~qoEQ{29zN{aN<@}UhRp~>qzr^w4wM^aM^sc+lvk8S#aJ_2POtLf zv1LiPiKDt012)ec_{?HVDeTJLWgk=UN)Jv=IE=>-LLI4YX{*3if)0SR7B7IAGMi>e zM;m_dgl5BC4<d@2nIUW7wX$kGQ|bOMj>eWl^eS-Eh|oSv)Q%LuKxE|<T0o5>g<7IL zLsMZbQWH+;`Vzs~iKP+T5J+hbE!SyggCIvUu<_H5-CQmi1x-Lmfud|0ixV^ZR2G+% zLip)XfuB??SBLG}_w>F=H`4LS{Lr!bw!RNX)V_cmPt-Z!uj$Os_YIvktdMC^eM~D4 zit-m0$(Q4F)modRmeNYA27X`=yi+*KbbRyRf5){A{_Xvh<^IyXWBV@coZrXPpDcw~ z*hq>k2S{;vV=3xADMjJ}DdxW>#k`SHG-)Tr(4(bTbd(egW=V0ytx_ypBE>H!OEC#( z&NwM<ZzILNK<mn-SO7fTZ-;+-N{UVkr5LePimq#=Xar?lSSZD+PEvFO>VBpa8}5{1 z8$5q%loYLidge;;L_&)7@a(4fQk=I;iZ!PI9-#A@z`p~o^*2b-xG&&@zh@sL#i&!I z*uAL~V=t7V@J1=Ff@kL*EX6K(z7C$vJp?dM1HPG3l+Bi+-`P@pb&eEwohQXNAoD)R z<i7xAE|#L{aw&GKfO@Y4-c?fU(@%;$L4G!r-|sZ2!=X})pDM-K7fDfa6UaR)#l^sL zMKdYt11$rdo0?0}@M5qhJl_XwxaUby9031z1;}<EEk)MhU>kTo^=7~c&+a-5o&!At z&t5!2ik+u}-7b-0$@NeN;CbX}D5F2v5S~2&vYm^-PDe_yu|kR!z<1s)AomQE1w4yd z03Xm}$OG-Q3;u1?Bq{bTlj4TIgDoqeJ>~+|TS4Cx;C~bd%4$7EicKxS2GgbZ6rMMp z2ex}biqi*xJpB7wX#e@ZQ{4*w?NTZ3gy*C01MCk#Js*-{)^qUh>j3k5DEn>j4UpS* z473SQi(<gv8VK58pUXhkZGd+n@DBvL9xKK7Q0DExbIoyJcX<93JpTorHGse8fLznD zQZ&z!qRr)i^>!&{3<AF6!7qWXgJ-w51%HHRJ@1fWJ3MO!f8PZB4aUJU@ZGY(VDk>p zepg7b5_q=2-wWCUACM>kEFGa9Q18v)AN9`y|2i9VoGV4k^T9VjX6T(z*B7DAi@|2# zALqUVzWOrw&MNT#A<!Nm{|J;n3(6YW8T4HV{Qr{Tb9lBU8+-(42Rz%{1?v6}DaxUJ z7l5A8=ScAY=-3JTn~@)Mv|b`b0r+17@W1NSK$F4GhC&;F>>4P0Y7+FE06u;d{C*c; zy%+2NZ7}8`=zmXvU%w8%18p$-EoeuO+ujxY73dp~Yj`5`j|#Boc~G~jK`+R+d>YDq zMT&{Tpl@~qe}S^6j)%7H4mJgzId=mev~%8r@cd!$omZv!2&nNJpz}?r$J>A#WSav2 z6;S?HAloSc<xB;f)!>_FNHO>tus!tIedoh7@Y7AuXYP9*Y*zqw%K;2PWfK5Lo)pi4 zeLe#I=J!Z3{ohbF@Z`h4FUp1b0MEJ}P@m!O`y}w^GVt|Euw#`JmDhsZ9*3V#049(< z_et=Fa?tyj6#YSF>_o78FNhP@fiEn8dfg9k2<o)&BhY8pf?dFFcS4=EK%LsX1MLAi zKI#eBfi{9{{XS6M42U`Zg#HfHVU85HKv|2REU^fD7W^RrcH9MRHfaRJhu+}Vpl?hm z_+mcz%M@q>z_}L6$hsbE3-X%)Z+~dNreKT9po}?dpwED8BjBG4`Zj{>e8AWJQD|qV z#|pr8>bsz;o)E{2Hlmg2Bsz+=qOZsooyAZwM4Ti>iIJi}6pJz9R54jh65~X*s1j4e z*<!k=Fa9Jh5*Lbd#r5Jkaf!HH+$L@m_lWu8PO-1}n|Me(DDD+ciYLS)VsG)Zcu~9{ zo)xc)wPKMtL^Kueh_}TXVvG1fY!E+*AH_DYo2(~)5&O!&$vtFqd8lk8TZkjXF|wsR zTy~bn%09BU>?%jd;j*tx$`V;9j}sk4A2C=IiXu@eD#bs<1>#yUPuwjY5RZ#z#6t0! zcuRaPb_gjO%4YIt*;@9J1#+DHt7sq&7EQ!n;&5@8I7YM-GsJQ7Gci-Nkt5}7(Mj$n zeih}Szt~@%E{>OHh%7lu9w2WLUBo}dV)33hLiUrV$gX0Ecwdx=1LdDZBhgG8DNYbe z#RsCBSSB`#6UB0IlI$;citb{C_)rXxr^!9Uv7(*G5d*|<ahgbq3NcHZC+3Qq#lOUT z;!*LOcuA}mABiu;Z(>i`ShkRD<q2}2JXMYt2g%l=y-0|GVuTniCWxuxOtFtRUtB6~ z5qF9G#r@(j@w`|m4i#^TkHuGFm)J`-kw=LmWe3?!4w6OkYjK&_Tb?Mt5toZU$?o!7 zafPTabL0d$QJy8TWvOf@N6YqdchOO97YB&n#YAzi{JR(=n#wE17<q_1P~0kV#T@an z*d&gUgJrSI6R(I*#9u^X(OeuYdWcuWX3<lu68{ms#A-1_Lg0{pmix%wVvYDz43lH! z$zo4&kVwc}`JK2*{6*%;@5R-kf$SlF5Z8#m%ARtPoGhV#{J&>{XJ(0P(N7E$r--ql zOiUB!h>OJy;tsJuJS?6P4aL93I`OX9Dt;Eb%l+hGvX$&2^X18My!?Nk{$KOy-QZ?0 ztFJ%vY`#0GcqTZ#f)BM%2RjSR!1|773n`+@Qi{m0I+D70&Y(J;&(%nLG>=xuF^2SU zY)T#VH8fHk%NSJ0vbpN`4`R_;BlttgBe-1*#8-xB<s4&3E63)lBR&B`tK%3$>Nqx6 z9r2Y2M_s5Cj?FE_y<Hzu#mO=+-BDAHdkWs&t80h(4(M*)mHdQIuvGo{DCF$Jo!2Xk zWDZq^`4)hKSE=@Lc~tH@tl7jHRBYl@dYL<WNe3|T2KAaam0t1^SW+v`8Pv)1DUJP0 z%bnd@Yj<G;RB8GhrX+ZVEr^_9auw0H2!?$^zg5%06H-K%<tn1C&7=xp`7q%J-Hf>y zk+u(gi6W)ez^N)g-d;s&<vD});`v;SWyLXlYA_?=mPcH(L|1@g4B_Y4lsf*-3Q}nV zdr)-*vkQQIMVtm;W)A@{Gb;ejYiQ1E$i%UZa2GQ&2bCLHeX6O~!pN#S1+v_Pa+cTC zPQ8^uYi2n^T3J3<qjNBx7<#c7dc0O==?M{kiRAFBH63{>->Gk?7CfU;N}nbrN-{9I zC7I40kxgMdsYF4D)ZilFw&iIQM(&_F*$scEWTl<n-6B;RSc7^EjE>T_xnskA<LxNI zmZQFU^F1WjNB-x;ddBydT7UQ-^D6{D7cryvKPG12{>Q}B+W(lDtom4(X{C>YnE?7Y z$myEzxjrfKKh!2N{)f~A#s8e1N%%O(ao_)(7*~Cd`LWN>#EwrsCT<)Fvry6A&qKv# z|8p|x`55S6=4UX48b2QwJbXNK=S?wm@K#>>$dKDBcSKg(cHy;}g+w5JbfBmHT<K=+ zV9CLeuDqhODw#+?!MPrr_tTk6cVOm=9Fhq3VA<U8$`u~P?=L<4mKgPINDT4nv#oJo zMXTlYcg(wJEH)e}YV{6U+_PjczY(gj)7`V?)gL^)lv`b$ET0B1x+ExXb>FE%#)wOK z)NwXsgx8$RA6E{r;umjS-_IjPNId_A{^PQ!@7%gte4^yb#8I+NaLmZpaV=ja^x<}N zt5edno2g@Ov;^I(-cdP}@djdx6Nt!e@z#2((Q1b9nqQ`<fz=IKhy!1p#DY`D#K`Fp z&FY2m@Jx*6CdXu&l^=g;e)^qVn%6L+rOZK(W&Wal7pg|u&c_>R4j-?bJ$x5tP~W)^ zV8;26bHRi8vJZ3(w?})89Uij--QIPu_V{^lpDZ1*eBpwTBS%9Uh4e^glyr`C4VhM4 zR$4tX!8Rm)#WPklAkQ|grM$Vp`9l1cJhOGcW4egg&&&d*n9b-s2`IAW>j5klFaG$M zSu8co&g|W)BiFo!VELj8+Qpf(8}6!0g$d^bEL%6v5DphxsQ{OqWusnVNDWEGBf35| z-)l4px4*kx(j6=Cd^GmgwlJ_eSz@%4PfD4WT9ngTTz+k5)H|o{w1ZYYY3c|OVy<GA zpcnY2YV<Iz`Hg~P8o($)W(JHBWU|00!R8N4Qr=7k#|L=R&fC#5+nSk~n1z_cg9#j! zi!kyN=?No0mys|Dks-lcb@c{T91HUi#jJ_H{M9VSCp=81V6)>9%ILI+Q4^CXG0D)= zcr!F(sy9NEPqCO#(lb@RD&IY1Q4>TTE4yU#hGX?!4ITTvw;Ex%aM{R^ud%U_Ugu;2 zyvE4Ib)C^1&UJn!itCI_@YWfbn5{E1rzl{GqF&RlvAZ{Y53kkGKeS>Ev0URdgm8_Q zjocb1y<!N}iH_=8S^Ca8S!rh1o8)Cfx2Bv~!T=xU2Wy+!5X=6J5N8<NyS!<(YF zR<<!{>)c$d);XOJt*<kIF<>)#)laq;AHg;4k%3#6FvV+)n-9}IR*P%+Q>&0due-W4 zckj>8Obplmz9n}mLl=F)txuO{9_#yC(8n$A{7<PE>EW+sb)xUn_35frbu2zGkWw)! z&FVy7-|N#=i|W++{X?tQ2LrcHQ3+J%%QpF{XUDpBD*7ctHQKmc-*}IgbMaaHmyv66 z=~+(yVz>&oyr80R<S1{6G!i#aV9#b?Ru;y$_&15co569v4PxF+fNdRR5-2%hJHdV7 z7~dXtFN~$9mNh#?DMn^u>F12xqVTDAZ*6m<2E9iC?7<L-1_wkzSsoZE9PHp#3GZ($ z{%YD)gM)rg#JCOMtE<I6fS<d(MC~tAvnu3M-4AfoOy+8S39dEOj-P9^$UOregcL6~ z5oL-dXIcy|KR03o=otjVY{)l~6{QFIK;ga=Ke^!Hn4^v2E7{U2Zol!GCp`UvD*Bw? z^@{e+xoq$oSD5jyXhCw8Y^9RU^qWNkse0b<p_lG!DjN{zo5D&wqH(w~{Hc4s0#2e9 zTF1}L<E6MG;CUiYXjH1-*G2r?JRE1P58&(S(jn)*3;ckQA=B>z?JRaoUj&KETN86l zn+Q;-XJgYYLYU36*`oFl!o?aQS`A8H&OSkBT(DGMM1XaOJD&LW6S!RnPZ;+hJhAHH zO7N0WxWb2tKdJF8#{3vGc`g`Js-|TvR&hQp*2_4MA?+8vj`M4aSt-}p7ArZQ66>WL zSEcB+oL^h*s${X6^C_`j&T&<WUeEcp)uKvub^A%*0fPq(Ehy^QYgmt=eFql|8d_9V zI@Z}96h%b?hxhMaq;bH5(kWAm@I^`OxGv6Zg2()#n9=F+Y#_K|6f5j-r&zR%vq?40 zOsb|?{i@lanW;F>PXXY$0~m<>xWNG2x(SXr>egqO{Q#&@zyN?{rvR|b0RVhH--IJV zii8s(6F^1m8kpd4>?973IRJpzjW7Y=*hv5!a{vIb%i#bZOXQdXB{JI~5NWVXca`EU zUV?#yU-#fyrkY^lv4yu$Oon5&)A_2J1pHvUtO1c|U;>Oug0i}a-;czQE?|(DBq$^% zem@d&i^<SBJU@kk=k{Zur=gK9$cXt%)_1%NRb!Z`dNHg4)!LB=lWLloR86x6R8#MR zu-zxv%iZ%!4mU1RYI-uW-Dj#4eaM3cV&L~zk6i;ZU5DqV?8|cpFfgyRVu?Y62E!(k zxCz8eYH!nEW|p0-9q}TMQ?|cy=H)PB<p?1POoT`P61y*Jund1DhxNe-5kDku@7Dmx zC_-7tD4>B+i+Xps_IKpK+o3f*+9{lsVd1b60X)?1waGFpKZ%3o4qzbn#VzJGT#hu* zOZi+l(?JXZWF5(aSB?q&m85s-S^qY$2TC=)-DYS;Z~4u96UDWCcu^WH6~M+FA28uE z%24Jv?p_2OfzR?Wj$ja=kQmr~W-+|r>ved;YrZus<Qmy~6EVd_tdTV)YOSd=@hd=` zmsq9gOvFk`XCf9yIuo<3&={FXzsAQ*$u&N5(yVdmQ&62nn=0xo)KpRDqGx6rA31l@ zxrjN4#>CGJbXIn5ps{k}Da}ZYbUG(B7U^7MG}qYZ@T{|$!n4lJ1wW0G4mDvmi<gVZ z35SIo5&z=6n(PIr2pLLCaMJEho|xB^Yt%5adGdW?TOkS@Hz6@AijpEU6=my*XS{aZ z0}s!&EN<{QIe>&%h|wd+K@A-t4u0GSF%pADh=Uk0LL9_!5#nIRiXay=Pz0HnQ6k7h z4iP~XeS8Q#*9M2sb82J=vCzXpkck`<LM+695ai%TgAgA(6a@LWae&~W27wS8H3EcK z$nYQJp<{oD#}xQOtX$L&ve6;m#}n^j12<5Fh3LBBgx&w8-5$kd<AzO5j;pRLE;~6{ zI$>gURf2DM=Okom229^uKpmlU)J6#!5T`_AUVGpgN4w2{0$FpH0VhNQ42!{GV)N0( z;KyZwJ>vYLIy~!w0`|xghlGIdegk?(j5v55(C90GIeTbRg445Wbxnc>##a>GE_j%H zaIZXk1&9`ozB0ii$1BCxWmA&g2xL4r8E!F)!;_lGjHwmn_>m}PH1poAPa#VkcqC^K zCINnrDGV#6gdCMjO87WrlHzhcJTODvffu5KxDL#B%F)uwJ_$>c`6S}l%<?jZl*9N0 z*ccG(ZtfJ{bC>Xm%|OU-lc>(c3@M#%3Dm}uy)?OKuotIi^%n92@#>{oUf7T_L7)`Y zCFmO$m{QDc5>=G{uqNx&!6@h#VXMH4nDGYXp!lehp5boo7h%x_Jv;KuXIMwvYaUW9 z=ggc9M?k}xOs6%~+qu57@pE}>m4k+L%8k<!QHM84FnnMQq46K%BvT3anjJ2<y$d0H zwf4zIT?=89(pKiElgFuakzK}0kXXgr$Wd#1D>;6}ZzWFT0j%VR?0}UVku$K8V=@Ug zvfk`2biTz#l$od5h?2Po8$mroVI`&IEv%&IRE3Qw8IY_5iL{1|93R!Jq}ddRjWm}S zv6iLMC04>z62(f8Os&{R(u*c5NmClessJuIV<SutO1h-)P73-R!|+(`6R@08TU=$4 z8nP@dv<Ne^qm%pYx5m#5N(?(U7IEz6A&8S=Mjnox8E`muW}M;J-Qh)F{D45}xECaL zRBL&QrSSPp^fpF?VH9E{41*6N!Hzi$KfPijYfO(yOu5FZGQ!TuoS-w9k{x;&ILy)# zxQV8v&W#zcm`5cB5PzJ?pvg1_F?t*kZs_4;xDkircLo~UjzedD47KJ*BL<K0)Qm}y zY3yNi@k5R#VDS=Hc-*mmlPi$rP#1EfI$8AVT$*0bgw~gHq_kPQ*cDc1{dSkJn$fR# zX?oMBrp3!%VRhDTd>N}5{o0qNx7O9H^_3o;JGccnxiRy1!TncuaN9nG>Us~iaK8i{ zBit)NQXxL_s*jqYrzb0`-9n=;{P3izV1%zOE+3av+g~LKIyzwbxyYBN>RaV$QlmHI zdg%Iu1tYHjae>9Y2*j0xm!{)f2jY`5UNH`Po6e;mDgKD9wcutbpt8Ck#8-U9wDOWF z?t)@0-8T(@;v={2G(f>?Kj^(X5W+l{n*>v@9j7n`OJG(R<|4fLrP2`l`j8YBuOQ=Q zm}Ucu-7sCo>OzqevYH8T%`Z~|mxP8l&Y>|=TU+Fs$tb~1@{K~|+|($+&mfIr^i0Yq zV%S8}+9qW<E{tg+zAY|WWBWvG4d)YyWjVL!d5s#LutIHo{nPa;=W>9Q|G7m_tEL#@ z<Z2{_JiXIpQ$S#oif<z7wvdz&0)_C67Nd}eW;B=N-?ZYZ<a=ccf`VU$uK_O;0XQfK z3Zk~UpzVS~kY9!e7?N?)s&Tq_&2Cr1EETLz)J~UH8J_^P6eS{-l|*RNHf^W@q);6U zJ8Cp@tR|1zQ=?TF;%o@LZ)Ysd<tV8>T#GAGe8QF&rKBWctUxsHi76ZM3X4^@rqO!Y z4U)bgBRr*xUU%!7V^%h`w1J>zvGUdwS}(oLRgGSI>zdQ4s>SMCQ)s>XHdi%z{jF=R zbyaJ51&iD99BF;+oe%$k<DBK)x}xq}Daao_@T8)ixdpkxd(ft&tnB#a&6^+B7XC;4 zm*xIjhc2Dn-^SE0nX{Yt?Y_bv8b~4b7V5t?CCRbVCbSsl{wea{m$}{_?$3m1nAfJX zq`11cMLX|NBlu+v&?fgEc7vY@k+sO>>*W5kXn?<fK93LoOo--->cP+b#Gmj_v5&t} zM~nR$?gjt2)_EkLfWyS4XY4zmbX;XcRmJ$~<}HUF)4YG*fhW1o13&hGzYqzM5W73S zHH81~;r!-T11QE-OqmLBFFixb-+%YZ0JA`R^@LaJgb)tmiQd0B^;4Q$o({+ZLZXJJ zW9nb<-w(t8x&LA5LUeWi!XNcS0sOy6zh1}U0-nryOS_JqJE6DK6Yu`AdG&J+I>aah z<t-)ZQO*B--qPOtNA#BY-XHEy^p?8u0-KgS1W>Ry8rAoEicK$T*E!hBmOip(>qgL^ z+L^YSp_g^^OohD^dzt^ggh+V3tb_OO$oH1_hx-%xGH+iS+cpCPne)l)c5VP`KBm49 z*i%d9beQ+$*Q)Zn<G0N9_kVES8hn%0-=wjlXtozleHiX~?6IX74kg4H9s|xn+*gOw zLePn@LcMPe+ujNg)H5Q0v(#y6rtE)b$&*{r{=2!FwNqUG1^W-aw$VFi|J{uk>eBvA z4qCJs?Z1Z+LE853sQdphefloX2F-ga&Fs?J&;?@0xa{^ij@@6behq`wUQsx>W{8cV z6Jd?Ei-s&e4p8hJg(BP(v9)J+&~5NsufJ`=>A;^{?Zj!0O!o#N7E~TK>MiV%^&?Pl z4dD+FHX~jq!V<g2wb|AhF#I_JL%12@>ZV%Zp!R$IfUU5P(oG$b`7gA>gjd=+t?-u! z6m{7O?{EA0J8Xpp5g5{Kh0fko(KjZx>9WUXCxY(3y1MzZM-hz?zh`#QduI8Ujeb1= z&@{9|!!=5LEQDnaeXH50I350*9hPw8#K%DN&vzEhTZ?^UUl#*$#w(&(V)s-cQk^uZ z!zUQ2_KQHlHAh?wRHVAJ@RcvHk2H$F5N?LJx~WLjyxIPrV=L_M>L!lP{TEu{Z|lGO z7F*$f2o!bM3PV17d=a+7fe{$eZUw>%M;|_AC)U5QtD88H=(Ivz*1yw;7o13aPy~i_ z>)*MP9vjD8e(cITsDBezH*>_2zj(^u6#@$ktJwpAsO656Hnjmn2S*}G*B;s4V%;}# zE!=tK2F%Aa4QdY`A<cBpB<yhDeie&41Cm25kYvm|35)#j&yt;4fTNiOj<j1OTldfj z%Mbso3+O*o>Guvkgx(X0Xps2hCb7Qw(-Y0#$JJKzXe3;N#KzHyu*j&Q+m{><I1Y=( z5pIy!`m+iBozF{tjV*Gx(r-BDk<oQ0(IU%7&HDjc<cMe_b=o5B-#qvgY>^hxIMQzs z!VU*qckmLl!;wlqdBQy7J%q>uTzJfu#h3>;DhfyDT7$4gyXEo+%mW-9g(B^?=uFHg zE_tQ#^6sF$rP5BHL*ecRPH!|N&@^nAT!p!*V~l9RhVa9%8ON#I;1iAaUxhQPR!Xz} zYC?oV#>PP8W;@jT;5Ez*9&5zFIb&P|RBpD<K1cn6_HS)OP#5;!=hVGEK>Hu(YSvyo z{4dyl$5DrFLHoBcVyH{|PkMXLpV0ot8xf>!e<BwCe#p9)F&1XInyD+PoF?F^AA79s zM9d;zc=gGZIGb)8g#x#m{SF`a!zu<4Glh%ioxTKT3hi9&^oy(!ju_hx*~FM2F6z4h zN67ZkNHW(Rghf7{z3pwBDRhX&5q8Rq*r9c^cV9(2bX5A;b88u$xe>kNYdK>x_Kr?b zI5O87gje+MI$#IR6tbgGq}>*s2^)N}r1PR2(B9eA9`9QAKxd8Z8=VL|({uA*KNj$G zG2=<w7F0HDj~6fAhS{*Lu5RM>^N29vw}(}{B4$fVjy>Z;95YXdK*2d>TntoB^0jUc zEXHhDw+Ia3W{9i1GcjM<aLehNyMgW#m2UHSN<)Z<i-pR>?78&$*D({*-3m*lT7=r+ zZ?WRiUvL(aV}+zH+NIg+&;Nq$l5i2Q*Cb@Rj}ZBwk)v+gitUnXg{3arWpdYzZ(_UT zSs_WcT__(tyx(88pe=eR1kRCY=S3;~m6uHR7Gj?F!~VJNW45rT1(Hm)NhiWP9?Tm1 z1!fC-S>Q;!MTot=!B-94h<km#efqfr?S>|aJ)Tl|vc>yN`VwcqeQdCBjS?G4C&DTl zb1q-i7LerIAPF~1Yy{N&`<ul4Z8-1j>ms1uUylf0@o^AyoSXsQEX8?ezbG7;YY$@H zx&4l28*tuvQWT1`+oChE|J!pw?z#kM@9%0S?*^!=9UlVEC!(iwUSvQ7p0F`u&oYR( zdB^b={fu#QpkH@g@AeQjcyRL*AL9IXP$Uk{G2>c;$`)LI>yN83TQE2hMcQpa*q~s0 z^F?TbAxb;<f_3miEdFZ8&RI}4*zLAXU!x6%Mxsd92Gp8xx)|~!t_g>^+D(@=WjbC` zCwYb*^UPOxlBd8H59gj4iQX@7zx7Lu-otItgqtWn3Su<fUhiK^agH&<MZw)klIixL z<``E^+59TbF;2F@lCid-<`_l4KDQj_7=<=S(r*_cmakrW<C_@EM=AvTt9pispFf;f zWf6!tcIMLDofyYPMdIKZBrXanjy<>KIw$jcN+gPKW5m`@WKtU4n&X^_KGo4~_`s82 zS^W4-Xzq5}`Oc2ZX|87NOuF|;nDjIgv$`EylAqzMZnV-&p2jAX$Bs%=-l)gtJ9c2+ zsL0h$oIfU&`^{kE@s#eH|1)Pj>K>zXQ>Sh-qC1;7Q8{N}$ya!yve?zlpEV^RWW@G+ zU0(7j)_<&vfjCh~D$j)N-|fEpO4L2h)lHlutTWw5Tyg4`sJq0~O`M*~jBX;+aQDIg z*?^gbq^p~~dlu%4X-7C>C+G1sFKx%2obgIOb1sRjete%J^gn#^&mW-v2}(b63MWJQ ziD>ue^}B7xXg5*mC(oQ@NIMbb|J0@RGThfKRoeB_1*GcO{f*H4TK4xJqvlDjW^G?R zGnxsD=g%MU8d`j^tC`wSCY5Kz-}aun^Hub>GFLmjmzyE|MD0)h^|;@#_EVI8a_2Hb z+KD;csNzFcW27s0wG;bOr1H2PN9{i}lih#CJa>iC&F{+88G>8Rx@ILJnCc>6_a8EX zfXXXh`{mm!FxvlJA>cCYnbA+I`4YE2wiH)4l|KDcUYP_CzyA^b-{JJa57GatTm)1G zHAC8owN=^7r+&b!NVTh-xwtwLV=bY5#+wg0IhkonJDnLKD<8Z65jo@EbANgXvkuc; z?WUXz2_k;`6CQZ>?0Fxf2mZrF!DSsXgn(#;qZ^*S0exVGLO`#tN%e7Cfy#{i^zUQe z#mv}DN4v%MI0N7D^dk_l+k?uCP48W>9y4R7+v4FC`?1kv6W%ds)-9VcCv}EGL!UY& zKe?HnWvHCg&|9{w!JO1A8!Q>K3U!|0-Y?(Viu>kgDkSdJaKX)4<~ykYv-tn8StC<e zPPwuD7Q`}JVPP(qi*BO0Sg3vLY}s}-?pvQ_g@tRPxJam8bn<6qZ(=VxTOr}D&5Jfm zMtV`VecHW;z33b}G@M~#W9dlr=++PZvgT;OaxUB}kdwoG+}7U)@#7k$_xwqRZa}^N z^z>>8v`%@6K&CFfUh1TDzcp`c#C6qqu3j>Irm6PV-==4FA~Hx%ulnuvV?guyo@OF- zrYVly#+2qM6Mx@`n&-Hhsl-_ZG!xmA`j?Ddjq~XXJk5OCOaqV!n>W6>$!}Qm3qAcr z%1l$75t|==M7L$A`65p<kua+p&8@$BWf5w=*waj;%QB#uSUbG(@fK(8aEYgxNtR_m zJF#}&wQlY^xOSfFYG+bqn)29wM(CdWZAa$~?@L|XOqwix-9)D7=D+^D6eHSYu5L0# zmbPXphQIj0tnC=XFLyQT=`l@h?7l_ny?NT#sP_s_ua+E3UoSOwoV51zH__5pdU~nU zn5H@t{&n|?{hVEwt31tgS}c9tgn!-hVUM4&)>pf_$&^_7nu!tfU&9vvgpuqTPcxAY z(-g<{FzVgc8-M(I8D=4`bv5(Ju)5LT=zvMiySLYQ`q@-i2J{n=d~xAkUtt#LdRIS} z2+M$WA_H(n_YSXO2H*z2b}9{~sgK>)2!9{@;o`5+-*5D^Qz@|YH4_=2CvRK#4!#+2 zlc$+Xe`%`Ydpx20z<F=Ki@I-ib<@eO^mS92=S3?nS%rDuTRh#S)RzV!cAFC(w_)^x zSJ2~b^$>7ruk^JOwm-l7#k<h<^IYw8%1cunyWOdL!NR}gZpVDVZMYWAEh%xoKB0OW z_4yS%Zlpew;WvC>rDn52qE_k0&A9m!T&3SmVWAyTp$WfK7~e9)+U3uSXZ_d{(A<G& zdK8z98#XmLuDY_g?BrzWgo)Kv34Ty;J~xx@ZZLJG_Kv+fZAt*9I~`1A$>Pe~vNC^@ zRVCOqOB)Z-Z-)2&el_0f@-H5bq21C(M9dj-ANtRG=tFlQBK7qhSO_@Z;$c6+0}kzc zh9=<9@qLJR)2YMMd8@H^-Hm8^mzI~PPZhz(v<6quO@*$LkGJ^;!1({L>RrUR)}vtH zZ+K(Cd=d-YOqoGLy@|JYZ2l*BdhH%W1K(22HGU|_{Y;YI3;_e#*n~o{eD=!!Ac_Sp ziipn-sqbl}hl1!uXC86&>v*E@UKa&)=qm5yl0@17KmLRtjY<ng7s5m4p5JOmKH#`7 zg`>K%qRh=_)88^8zEGAPE@FHv`b*v?eF4|~BrfI)Nm0OJ_wTk@p2<=-1COohy?iJj zdmxC+89j3=D~o3`;W#gMaId`K{=l73-|9=tb~V>R`)-<=Hu|)6IEp>UBBXmhIvHS0 z+s~*ywr=&BZ^u2hhX_pSlk4;#rasjkFj4GQ>`bh7ZYcZn(p<pvFpGz`5q>;AO+>`F z@$lVEUczzX5f_n{%QAk4D+5u1+Qm7v-+y-DF3zJa7K^WYWdaFt7H!ZYA3LYeAM=pR zm|9VuEU!*5={7P@#`c(OB4YRb{Qi{~u^)Glq`r_v?kA8y{9a=<ei6Gj4Yq&uD()mb zq2chpZKc0tL^MuDZ1P>(C%#9UJgH$JGe#M)3AKu7+pF2DI0Jl2!@*D3NigxPLilX^ zHLYI4UFxSbG~7qqGSNQNO0V*Tvv*=lc}7EG_~a7-Cbr)aR+;|v2XCNNp4G6>Yu~zS zpZkuu|3^gfoQuTwNjdwjoDomikho|OB6(g%V&8#cBcbjV+xp(BMYvK}s3VElg<|^( z5o`DP;ElByS6=YYU>4Z<?q{M0v7JY{N^Bg2&z`fb*-zL$FX}khDX2Dmirqe4i23jl zr!-rM^WlHHNZbj!7xKfOGYrgx-BT&;wZrPMk##1<qh<Ggv9T8*TNFe_ofE1fR9%SK zRO@@c`m#5mS{y`0o{h1C71y7-5SfbZ-#)#o51?8SL`9!Xv4a)64Z9G2K62!y)dK+6 zQWsa?BaduM%*oZh&6xJgm_k>0_~dnnZW)D+jgxhOj#>})xaZO@aXq-4LT8Sh77*il z9W|R@-23_;aW=ogMd<(jCEK{#A}13_#3dgsMkFs0NZ5u<8ws@@UH)sUS8zSLl0af^ zG!0Jd9!RxO|EJr3i<WuW#RJxe`fO+>=4nI@{Kx&ibx!cTLLgz=NE`HGdkZlODD8E? zPMih2>LGFVg6nqXO63YZdGx1GF;}q4#ZmL&RF$|e0?L555ZQ}vW1iiJcb=?v5&6ED zN?sB~xJrDlCC0PWS=-;jHze11IPk-&_8}!B7+$@lk@MY)*Zde*N6kQc5T1J5{H>p& zr>^y5h#5v=_ZY%2_FXyU4fKoG{W$8{KfM&GF9W%0%lfq)0LMB%4t|-ZHA-AtbSCCB z2OoId&K`i_4G)9=v$^i{*_|1eSCm#I6N%xF|HOm0Z6PnKU0NF3SE)S3kmUudFi)|b zz@&nS{-HTUr5PwAXkPjKsGkwdn>-rrgl>k=5a;P89QM}h2|)7}i-rs=B%s*-L#((O zpK#G?oFTl;;~`G-l5pa;7J=ux!>)QC@w~(0q0abb1`oBu+BS38DqLZ`%j4ls{*tgV z(P}-nj(Qobw1G!OBoIhA8ELg`6=$wNJn!*%i0!_*!}EK``#wcH@AG(wv%?v}LqzF| zN676Mr9a^DFei#Lgozl<FWz$cPCVhTk;UZg&4fM`LPCn|)dZqmRi`gOL?5z<nC+0v zAR?mmF_+)*Q34Qs#3CZkBxeQ>F%w;L)^}TREc=+nqn}hJ!Nl+N1ezVsKK3i3*~FvK z&M#*O4dFd^4ZHs>^qx<6H1yeG5>jk`CcJ0*T`N~60MTX^5jla+3?5=^ed)tSTQDmA zhsQ&lLnh(G_a7psdfI*$zk@l|Pgy+t;-c<QRp*v1K~$gdsMs^h8A3&jAh*pp@<SX! zK4(#JCz&&ZiO8W0zy9Hscmm@KGbZYMGYKjaeXhl<8DA#=(-s~Rb?P}Yc!;&?>U-v| z#jMI!9uIjIngkTT*Aa-W{Nj)uh-e#&h&~gN8AR0Stjo4e{svEHeaRy-otq|s#r7q_ zzaMORmUF`BD;^hjnmRL>h}QeAbIVV#^}c2?(Fq6=P;7sqPK>;E#?_lKEA)+vX!z8r zf5<Lo`>vll=Mlcuan#{1CiUf}2D3kR@<Q8PBqNH;rX>?}&SN0_m+qNZ3d3hP_k4u8 zsqcaq$V^8@Fc7}_&+3ZrFi-b=00W!fpkT!JP+}hTTdQ*CZr~q+IGDT!1tV@Wp-$wU zGUm-4cp`U400o!L$OH;1H&?Kz&lfmn|1pTdoWr2NWTa(E`t4YbHu))thRIz}Ff!3H z%ZlD|z9jr}5CxO5s4Eor@78P$+TxcW3MNmH5fnt9eR$%*%dpS>8brZ-))sbL^zaqm zGl;SGvj>*`iaUinNgUy`e>l2j#3IDZd+wKwHsk4--vUSsxe*FTe47wR#`Nv`2_o4Q zKw`*=WCjW0B{P=1zY4wN_W%+$7m^tq#QB%)N8k4~zF#M#de=gYwDh4I-BDx54q^{z z%d%11aGaF^1auaLGD9XX6z)3VW5iG|h=I(#WCR1@o4f7y>QeO0-GUhSj0*)MelH<> zbL-EitU=%0J%EJGv}6Vc;Tf%FAG8BoWse{Z>LX=j7>(^2#LOr6gEuzfn6hUO1C!;U zOc56abw2l_H}+hK=X3W8pfKll>I%#CeO7#fSoRKLF=li!frUB^v*P}bzs51{PXR2Z zJWfWC5Sh4Rw)Xf6Puthmk#N}?3Q9)$@9_uT<9w6)&p|9))+RGJh-~Ti4O(u;TMPFI z;vhfvLU!Qz)*!Olr_X%hS6p5Hg+NhfvmgS;3orimJK|^%z`<vVsAh>BONdsvb^MeK zn8W^S5Jyygh=LQpeTbfO_e~Ao!k*JGh{u!{$_y5wjh<fh!MoT-e+yvY^Fb7lxNStm zwZ)Z}IcMwkb#WBtmQN^4CWf*>HX|q7sWY{Y?{Vjwc&2tg4~aKzcTdK6e};BA5f2`b zak+>W7~}g6F=rY-W~Ot`V<Qip?|>T>c1bhEMnat;>b&!YZMeIzzlWs2?NIPOcf$KU zS$CKyFB*D9^JO?X9^hgc`G<MYzW?mI1-<A%4~geC{}W!+VB^!y8%K>jbbpu^wL5Xb z=jcTTc}P6Z`2)S^!MS%ir^1`Km`43!UR2Th+xO6m4)&1zPkGUn$uBzD^QIoUKg^3N zmwdYdz331R$$#04s2!>+ukG>^?oc&zFjW?pl)}roJt`(9E8(Z}8cF+}3HN;ENGB7O zA9`t|Z6fU~Kau~KwPNV|nEyD`kBi2WpRXPEj6K#9V_3Hf@_xj1e{)2j?f}WHHeCTy zn6D>dVvk|ec+sMFlkGTO92Q1GPg0$mY|_7vgt}v``tCD7!aLRuS4h-_7h0=8gXm2X z+Y(fc|N4ea)?kkR2&I2|va;H>K~;h_hI83aCT@x$X6!F@{p(veV{hT0se-8I+*zY; zuqk1$uAweQiEkYuCvr&3EiWek&yfm`XBm~egoFK@g(yIAdl2O<SKM>+8uXT<5KsT& z>i!kw1r>!ON2THkTuKG^HxJCp3Wt#R_8{8llO1hVV*4D8`UezGPWmtJFglDDP~zhu zPL-W|+EYKF_qIel!{GiLTs**?REE`qb8nZnjT##b_1)c<I}CXXvx&!eXpFb{Wahpl z3Wpeb$yUVC%Eb|JpAT9jZEvA=F=tQu^d-zF9_!(#m{wi_7d{bNb{V=)jk3nv##e4a zYqSocVD|YP_ekFw#OT*5@7f(W`W@%uh`iq@Bke-HIlb%c19st?(``~nB5ybXFu_%B z+BWJ!#M{n4e7n9MAUmE!#y6Q6THMInHp`nnrS3Soz3PIkg8^BVi!3-HuS$4-YuN$K zaC&Hti;o(|`;Qy55hG#S5I*BwIT`UV>I)fH&pT=hX1dzBXe@5a$qW`^k7r@0Lx0Bp z-`>MgUZRo{)Yc!OsG~XOpbI8{k8{or9*)R+dnl8{^%d%yAp^%e>g-Z?bdf~f+d~0~ zJz`S3J%64w_ch#M>7*g?u5k$Gt?8>SGhvnY)^A^q@i|+=Lhc9BK;m14*bV$(ZvHB~ zd8D(3gx?3F;l#EJak^r|*ww$I-*(Z^aCi4m?h+RX72_^=Y~%`zaa}beh8H?1FtJ;S zT0`ddJ?cxe%Ly79I*VR+E%a9Nr7t0pZZ4AO+j}UZ#BCwUCVNlI{Tz{;s3VEdiQ*!m zZZqh+`Bdlqh3-0%xSc4rw@@>ib3X396K6I#E*>`>?1j1TZ8gw9zB$blb;W-BwDUsL zTZ{c)`spitYcUZ-#*B`2f{Iw#bsl%c%XlwLZV(lDTaBf$;<qCan@>4@`G=U@%M0S7 z@2jx{7T4dq5c_$*O=-NNKj7-&;&M_*L#7p%l~&K>?jHbnq0D0jJdD8hWKPSU5nE3R z8y^=kj;(E$=V+;U{h%LazdZ!7^`fvvM2bj&u|1BOOLy%t=~tXf_jVCl+*Jb&n|{89 z!cuzcr@IhK9|8;Ckm+NgZUKMwzU)^MfF+;6647uPptwDdvd!qmzpO{w^mXxoJ)-Wb zfp*G3=7l(Ea8RG#ZzGm|1Qx!XGJu81dt5N4%kMa^KFPxpb!SaRtU}EnyG>f?<huL2 zNNR4aNtox4n!CjB*F;nt(scYXJb^L5MdRB<BX6vs%o5vgiT0`fa`AiEJ_B_e^d>$9 zB)&xmubsGa>+9&XgW@2`#A=-w=WqR_$rpGcbFhcSaE}idM01hYEkj@^xwmK?Vi@Ab zz<1yDF%aHy^OP&UL+=>s#}GHX#`g}Qzua`wwclWW8Rkb4v*X5Y6=DzM=EXm6z_We@ zejJ7+pVlm~twH5*A1Qrc6XtM-dno*O`Oq&4p_}@q?{HEn?Hn$3*ZS^XCw|2@eMeBp z++~lupmBG>@iWE>8s0M$1{J%_2rqvA*5g*A7oY55sxGdCo##@xHJe%IWFiYlot`>n z<Tc;oiG@NJNsI#v8Hu~peWgRztos(<Vjo#MOqqzl#As0Pmmi(4ERS+gS)2^ah=sZk zIk5L%o3eT!Sm+cFi<j<q{n`67I{TltcN6GVt?uHSTRPQ4=bK8{58-k98P)gC-haV+ z*!NHKkfiC}aWPTz@;=L-UWK!Y(Jm&7vw?NyN4GXSbsPFok%z@|oc{?wdiJ-$OE7yk z#zXgq`BAS+E_fOJsMtgDKj24?KG1tH`q5Yylf~)4I`g9w_M5m2{b-zr<$ubL*1kFQ zGxVbp58WT;M>ph+-j05h^pO0Q{fHWEfB*RFbvW9NM@-I3h-n@Uj6I+1MC`nGo80y{ S{BH1s8dTaLw1LziwEqJ&V4mdw -- GitLab From d507347acf5a9d40d43e810787e333edf8038dfb Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 15 Nov 2022 15:55:08 +0200 Subject: [PATCH 023/121] Add prototype digging --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 6c2b592..df0c552 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c1c2b109c85b6c99db5b0cc4bd912eda4cf0fc772d1699513c283d5e870c881 -size 1088363 +oid sha256:ec77f51d3e719d8e9ecdafcf186c4925a2c0455e06e1a76731f712ca08c02e2c +size 1127895 -- GitLab From 0c529377f00780d0b7407e9ad07a96cd5401eaa8 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Wed, 16 Nov 2022 11:53:11 +0200 Subject: [PATCH 024/121] Modified CollisionCheckComponent --- .../CollisionCheckComponent.cpp | 23 ++++++++++++++++--- .../CollisionCheckComponent.h | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp index 84609e2..d7d84f4 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp @@ -32,13 +32,25 @@ void UCollisionCheckComponent::BeginPlay() //tempChildActors.FindItemByClass<>() } -void UCollisionCheckComponent::IsCollidingLR(EOutputs& OutputPins, bool rotating = false) +// oikealle rot = -1 +// vasemmalle rot = 1 +void UCollisionCheckComponent::IsCollidingLR(EOutputs& OutputPins, float rotationDirection) { - if (BucketCollidingFromLeft || BucketCollidingFromRight) + // if we are colliding from left and trying to rotate left we cant + if (rotationDirection == 1.0f && BucketCollidingFromLeft) { OutputPins = EOutputs::Colliding; return; } + // if we are colliding from right and trying to rotate right we cant + else if (rotationDirection == -1.0f && BucketCollidingFromRight) + { + OutputPins = EOutputs::Colliding; + return; + } + // if we are not colliding form eather direction rotate freely + // or we are colliding only from one direction we can still spin to the other + // like collidingfromLeft but trying to rotate to the right that is possible OutputPins = EOutputs::NotColliding; return; } @@ -91,13 +103,18 @@ void UCollisionCheckComponent::CollisionCheck() FVector lineLeft = MeshRightVector * lengthDown * (-1); FVector endLeft = socketLocation + lineLeft; BucketCollidingFromLeft = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endLeft, ECC_Visibility, collisionParams); - DrawDebugLine(GetWorld(), socketLocation, endLeft, FColor::Red); + DrawDebugLine(GetWorld(), socketLocation, endLeft, FColor::Green); // Line trace Right FVector lineRight = MeshRightVector * lengthDown; FVector endRight = socketLocation + lineRight; BucketCollidingFromRight = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endRight, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Red); + + /* if (OutHit) + { + DrawDebugBox(GetWorld(), OutHit.ImpactPoint, FVector(5, 5, 5), FColor::Emerald, false, 2.0f); + }*/ } //void UCollisionCheckComponent::Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h index 7c6e308..ac0c94a 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h @@ -31,7 +31,7 @@ public: void CollisionCheck(); //void Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront); UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) - void IsCollidingLR(EOutputs& OutputPins, bool rotating); + void IsCollidingLR(EOutputs& OutputPins, float rotationDirection); UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) void IsCollidingFB(EOutputs& OutputPins, bool moving); -- GitLab From 55c47ee4d0c78e0366039323b40fc458a08e5b2d Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Wed, 16 Nov 2022 11:54:54 +0200 Subject: [PATCH 025/121] Modifed Character - tried to fix the Collision component --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index df0c552..28f84fb 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec77f51d3e719d8e9ecdafcf186c4925a2c0455e06e1a76731f712ca08c02e2c -size 1127895 +oid sha256:1fe7e9d1a245f78e4f2b74972b44e255d79b9d8d21a17e2feb9f21de79a4eaca +size 1157244 -- GitLab From fb80a80e8ee6238fc692e43b28f32696d3ba4f5e Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Wed, 16 Nov 2022 11:58:49 +0200 Subject: [PATCH 026/121] Modified ExcavatorCharacter.h --- .../Source/ExcavatorSimulator/ExcavatorCharacter.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 7efe5d4..fdc8e1a 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -19,7 +19,7 @@ public: AExcavatorCharacter(); UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly) - class UCollisionCheckComponent* collisionComponent; + class UCollisionCheckComponent* collisionComponent; /** Called every frame. */ virtual void Tick(float DeltaTime) override; @@ -34,5 +34,4 @@ protected: private: UInputSettings* Inputsettings = UInputSettings::GetInputSettings(); - }; -- GitLab From 331f6b69d94bbec9af0a8af211c6d26dca6be265 Mon Sep 17 00:00:00 2001 From: make <make.rangepure@gmail.com> Date: Thu, 17 Nov 2022 17:28:00 +0200 Subject: [PATCH 027/121] Cleaned up a little --- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_rock.uasset | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 9526966..f28ac3c 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:156a0ad0acb3d57ac99748fda72b01c0f5c01460a4730c3d7183cdb03a04bdb7 -size 822513 +oid sha256:d783f5396dba25ec799fdd0ea34d616b966d13947cccb496fca9f4492dd915b9 +size 848839 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index 0de2a14..1348771 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39a45234c3df783ed78da1235302c0f6a4c62fc53bfe66c2c6c9846e5713503b -size 138594 +oid sha256:c301795eb4747bfefa0f63c4df9ef5894d9fc0b0c561d763e910624c4b79b54f +size 119340 -- GitLab From 6e3a3db929d904e72182ebffdf889a5b19f86ab8 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 18 Nov 2022 09:31:37 +0200 Subject: [PATCH 028/121] Renamed CollisionCheckComponent -> CheckCollisionComponent --- ...ponent.cpp => CheckCollisionComponent.cpp} | 69 +++++++------------ ...kComponent.h => CheckCollisionComponent.h} | 41 ++++++----- 2 files changed, 49 insertions(+), 61 deletions(-) rename ExcavatorSimulator/Source/ExcavatorSimulator/{CollisionCheckComponent.cpp => CheckCollisionComponent.cpp} (73%) rename ExcavatorSimulator/Source/ExcavatorSimulator/{CollisionCheckComponent.h => CheckCollisionComponent.h} (70%) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp similarity index 73% rename from ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp rename to ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index d7d84f4..db678b2 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -1,51 +1,54 @@ // Fill out your copyright notice in the Description page of Project Settings. -#include "CollisionCheckComponent.h" -#include "Engine/World.h" +#include "CheckCollisionComponent.h" #include "ExcavatorCharacter.h" +#include "Engine/World.h" // Sets default values for this component's properties -UCollisionCheckComponent::UCollisionCheckComponent() +UCheckCollisionComponent::UCheckCollisionComponent() { // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; - BucketCollidingFromDown = false; - BucketCollidingFromFront = false; - BucketCollidingFromLeft = false; - BucketCollidingFromRight = false; - character = nullptr; - mesh = nullptr; + + BucketCollidingFromDown = false; + BucketCollidingFromFront = false; + BucketCollidingFromLeft = false; + BucketCollidingFromRight = false; + character = nullptr; + mesh = nullptr; } // Called when the game starts -void UCollisionCheckComponent::BeginPlay() +void UCheckCollisionComponent::BeginPlay() { Super::BeginPlay(); - // @TODO: Modify this to make it possible to be any character - character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); - mesh = character->GetMesh(); - //TArray< AActor* > tempChildActors; - //character->GetAllChildActors(tempChildActors, true); - //tempChildActors.FindItemByClass<>() + character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); + mesh = character->GetMesh(); +} + +// Called every frame +void UCheckCollisionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); } // oikealle rot = -1 // vasemmalle rot = 1 -void UCollisionCheckComponent::IsCollidingLR(EOutputs& OutputPins, float rotationDirection) +void UCheckCollisionComponent::IsCollidingLR(EOutputs& OutputPins, float rotationDirection) { // if we are colliding from left and trying to rotate left we cant if (rotationDirection == 1.0f && BucketCollidingFromLeft) { - OutputPins = EOutputs::Colliding; + OutputPins = EOutputs::CanRotateRight; return; } // if we are colliding from right and trying to rotate right we cant else if (rotationDirection == -1.0f && BucketCollidingFromRight) { - OutputPins = EOutputs::Colliding; + OutputPins = EOutputs::CanRotateLeft; return; } // if we are not colliding form eather direction rotate freely @@ -55,7 +58,7 @@ void UCollisionCheckComponent::IsCollidingLR(EOutputs& OutputPins, float rotatio return; } -void UCollisionCheckComponent::IsCollidingFB(EOutputs& OutputPins, bool movingForward = false) +void UCheckCollisionComponent::IsCollidingFB(EOutputs& OutputPins, bool movingForward = false) { if (movingForward) { @@ -70,16 +73,16 @@ void UCollisionCheckComponent::IsCollidingFB(EOutputs& OutputPins, bool movingFo } //@TODO: ota anim instance skeletal meshistä -void UCollisionCheckComponent::CollisionCheck() +void UCheckCollisionComponent::CollisionCheck() { FHitResult OutHit = FHitResult(ForceInit); FCollisionQueryParams collisionParams; collisionParams.AddIgnoredActor(character); FVector socketLocation = mesh->GetSocketLocation("socket_bucketMiddle"); - + // Line trace down float lengthDown = -160.0f; - FVector lineDown = GetUpVector() * lengthDown; + FVector lineDown = mesh->GetUpVector() * lengthDown; FVector endDown = socketLocation + lineDown; BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endDown, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endDown, FColor::Red); @@ -110,24 +113,4 @@ void UCollisionCheckComponent::CollisionCheck() FVector endRight = socketLocation + lineRight; BucketCollidingFromRight = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endRight, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Red); - - /* if (OutHit) - { - DrawDebugBox(GetWorld(), OutHit.ImpactPoint, FVector(5, 5, 5), FColor::Emerald, false, 2.0f); - }*/ -} - -//void UCollisionCheckComponent::Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront) -//{ -// -//} - - -// Called every frame -void UCollisionCheckComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) -{ - Super::TickComponent(DeltaTime, TickType, ThisTickFunction); - - CollisionCheck(); } - diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h similarity index 70% rename from ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h rename to ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index ac0c94a..b86de55 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CollisionCheckComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -3,50 +3,55 @@ #pragma once #include "CoreMinimal.h" -#include "Components/SceneComponent.h" -#include "Components/SkeletalMeshComponent.h" -#include "GameFramework/Actor.h" -#include "CollisionCheckComponent.generated.h" +#include "Components/ActorComponent.h" +#include "CheckCollisionComponent.generated.h" UENUM(BlueprintType) enum class EOutputs : uint8 { Colliding, - NotColliding + NotColliding, + CanRotateRight, + CanRotateLeft }; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) -class EXCAVATORSIMULATOR_API UCollisionCheckComponent : public USceneComponent +class EXCAVATORSIMULATOR_API UCheckCollisionComponent : public UActorComponent { GENERATED_BODY() -protected: - // Called when the game starts - virtual void BeginPlay() override; - public: // Sets default values for this component's properties - UCollisionCheckComponent(); + UCheckCollisionComponent(); void CollisionCheck(); + //void Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront); UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) - void IsCollidingLR(EOutputs& OutputPins, float rotationDirection); + void IsCollidingLR(EOutputs& OutputPins, float rotationDirection); + UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) - void IsCollidingFB(EOutputs& OutputPins, bool moving); + void IsCollidingFB(EOutputs& OutputPins, bool moving); -private: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; +protected: + // Called when the game starts + virtual void BeginPlay() override; + +private: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) - bool BucketCollidingFromDown; + bool BucketCollidingFromDown; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) - bool BucketCollidingFromFront; + bool BucketCollidingFromFront; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) - bool BucketCollidingFromLeft; + bool BucketCollidingFromLeft; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) - bool BucketCollidingFromRight; + bool BucketCollidingFromRight; USkeletalMeshComponent* mesh; ACharacter* character; -- GitLab From 748bb88cf9006fd016aec4188d7d6310955c1156 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 18 Nov 2022 09:32:20 +0200 Subject: [PATCH 029/121] modified ExcavatorCharacter --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- .../Source/ExcavatorSimulator/ExcavatorCharacter.cpp | 4 +++- .../Source/ExcavatorSimulator/ExcavatorCharacter.h | 9 ++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 28f84fb..eb1f0e6 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fe7e9d1a245f78e4f2b74972b44e255d79b9d8d21a17e2feb9f21de79a4eaca -size 1157244 +oid sha256:993b3067e40382c3b37bc4222af4d475ef2d1cb4848971e759cc858e5240c0ee +size 1157323 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index c7ecc8b..5f0b246 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -8,7 +8,9 @@ AExcavatorCharacter::AExcavatorCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; - collisionComponent = CreateDefaultSubobject<UCollisionCheckComponent>(TEXT("Collision Component")); + //collisionComponent = CreateDefaultSubobject<UCollisionCheckComponent>(TEXT("Collision Component")); + //collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); + //collisionComponent->RegisterComponent(); } // Called every frame diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index fdc8e1a..3af27ca 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -6,7 +6,8 @@ #include "GameFramework/Character.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/InputSettings.h" -#include "CollisionCheckComponent.h" +//#include "CollisionCheckComponent.h" +#include "CheckCollisionComponent.h" #include "ExcavatorCharacter.generated.h" UCLASS() @@ -18,8 +19,10 @@ public: /** Sets default values for this character's properties. */ AExcavatorCharacter(); - UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly) - class UCollisionCheckComponent* collisionComponent; + /*UPROPERTY(VisibleAnywhere, BlueprintReadOnly) + class UCollisionCheckComponent* collisionComponent;*/ + + //class UCheckCollisionComponent* collisionComponent; /** Called every frame. */ virtual void Tick(float DeltaTime) override; -- GitLab From 3da533a8868aa03c7b3a43b508f926cd707150dc Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 18 Nov 2022 09:51:30 +0200 Subject: [PATCH 030/121] Fixed beamTopRotation --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index eb1f0e6..0c94983 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:993b3067e40382c3b37bc4222af4d475ef2d1cb4848971e759cc858e5240c0ee -size 1157323 +oid sha256:9f73bbed41878eafe913089d4acc3cf790b23ca8b26e06b2e527e534900aaf6c +size 1137855 -- GitLab From 7638bb4f9f59b5c65a721a65285346b517e14926 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Fri, 18 Nov 2022 15:37:38 +0200 Subject: [PATCH 031/121] Added new plugin to save the world --- .../Content/Blueprints/BP_Cabin.uasset | 4 +- .../Blueprints/BP_ExcavatorCharacter.uasset | 4 +- .../Content/Blueprints/BP_JoyStick.uasset | 4 +- .../Content/Blueprints/BP_VRController.uasset | 4 +- .../Plugins/VRExpansionPlugin/.gitattributes | 2 + .../Plugins/VRExpansionPlugin/.gitignore | 10 + .../Plugins/VRExpansionPlugin/LICENSE.txt | 19 + .../Config/FilterPlugin.ini | 8 + .../OpenVRExpansionPlugin.uplugin | 30 + .../Resources/Icon128.png | 3 + .../OpenVRExpansionPlugin.Build.cs | 62 + .../OpenVRExpansionFunctionLibrary.cpp | 1417 +++ .../Private/OpenVRExpansionPlugin.cpp | 101 + .../Private/OpenVRExpansionPluginPrivatePCH.h | 9 + .../Private/SteamVRKeyboardComponent.cpp | 170 + .../Public/OpenVRExpansionFunctionLibrary.h | 743 ++ .../Public/OpenVRExpansionPlugin.h | 24 + .../Public/SteamVRKeyboardComponent.h | 270 + .../Config/FilterPlugin.ini | 8 + .../OpenXRExpansionPlugin.uplugin | 42 + .../Resources/Icon128.png | 3 + .../OpenXRExpansionEditor.Build.cs | 61 + .../AnimGraphNode_ApplyOpenXRHandPose.cpp | 33 + .../Private/OpenXRExpansionEditor.cpp | 16 + .../AnimGraphNode_ApplyOpenXRHandPose.h | 34 + .../Public/OpenXRExpansionEditor.h | 13 + .../OpenXRExpansionPlugin.Build.cs | 69 + .../Private/AnimNode_ApplyOpenXRHandPose.cpp | 370 + .../OpenXRExpansionFunctionLibrary.cpp | 152 + .../Private/OpenXRExpansionPlugin.cpp | 24 + .../Private/OpenXRHandPoseComponent.cpp | 827 ++ .../Public/AnimNode_ApplyOpenXRHandPose.h | 105 + .../Public/OpenXRExpansionFunctionLibrary.h | 374 + .../Public/OpenXRExpansionPlugin.h | 18 + .../Public/OpenXRExpansionTypes.h | 324 + .../Public/OpenXRHandPoseComponent.h | 351 + .../Plugins/VRExpansionPlugin/README.md | 48 + .../VRExpansionPlugin/Config/FilterPlugin.ini | 8 + .../VRExpansionPlugin/Resources/Icon128.png | 3 + .../Private/HandSocketComponentDetails.cpp | 862 ++ .../Private/HandSocketVisualizer.cpp | 448 + .../Private/VRExpansionEditor.cpp | 64 + .../Public/HandSocketComponentDetails.h | 83 + .../Public/HandSocketVisualizer.h | 95 + .../Public/VRExpansionEditor.h | 19 + .../VRExpansionEditor.Build.cs | 62 + .../Private/CharacterMovementCompTypes.cpp | 464 + .../Private/GripMotionControllerComponent.cpp | 7761 +++++++++++++++++ .../Private/GripScripts/GS_Default.cpp | 231 + .../Private/GripScripts/GS_GunTools.cpp | 586 ++ .../GripScripts/GS_InteractibleSettings.cpp | 134 + .../Private/GripScripts/GS_LerpToHand.cpp | 191 + .../Private/GripScripts/GS_Melee.cpp | 964 ++ .../Private/GripScripts/GS_Physics.cpp | 109 + .../Private/GripScripts/VRGripScriptBase.cpp | 355 + .../Private/Grippables/GrippableActor.cpp | 788 ++ .../Grippables/GrippableBoxComponent.cpp | 317 + .../Grippables/GrippableCapsuleComponent.cpp | 317 + .../Private/Grippables/GrippableCharacter.cpp | 29 + .../GrippablePhysicsReplication.cpp | 701 ++ .../Grippables/GrippableSkeletalMeshActor.cpp | 803 ++ .../GrippableSkeletalMeshComponent.cpp | 316 + .../Grippables/GrippableSphereComponent.cpp | 316 + .../Grippables/GrippableStaticMeshActor.cpp | 824 ++ .../GrippableStaticMeshComponent.cpp | 311 + .../Grippables/HandSocketComponent.cpp | 807 ++ .../Interactibles/VRButtonComponent.cpp | 414 + .../Private/Interactibles/VRDialComponent.cpp | 586 ++ .../VRInteractibleFunctionLibrary.cpp | 6 + .../Interactibles/VRLeverComponent.cpp | 865 ++ .../Interactibles/VRMountComponent.cpp | 582 ++ .../Interactibles/VRSliderComponent.cpp | 941 ++ .../Private/Misc/BucketUpdateSubsystem.cpp | 397 + .../Private/Misc/CollisionIgnoreSubsystem.cpp | 431 + .../Misc/OptionalRepSkeletalMeshActor.cpp | 601 ++ .../Private/Misc/VRAIPerceptionOverrides.cpp | 984 +++ .../Misc/VREPhysicalAnimationComponent.cpp | 273 + .../Private/Misc/VRFullScreenUserWidget.cpp | 1234 +++ .../Private/Misc/VRLogComponent.cpp | 199 + .../Private/Misc/VRPlayerStart.cpp | 19 + .../Private/Misc/VRRenderTargetManager.cpp | 1792 ++++ .../ParentRelativeAttachmentComponent.cpp | 174 + .../Private/ReplicatedVRCameraComponent.cpp | 399 + .../Private/SimpleChar/VRSimpleCharacter.cpp | 112 + .../VRSimpleCharacterMovementComponent.cpp | 1619 ++++ .../Private/VRAIController.cpp | 140 + .../Private/VRBPDatatypes.cpp | 217 + .../Private/VRBaseCharacter.cpp | 1107 +++ .../VRBaseCharacterMovementComponent.cpp | 2014 +++++ .../VRExpansionPlugin/Private/VRCharacter.cpp | 221 + .../Private/VRCharacterMovementComponent.cpp | 4288 +++++++++ .../Private/VRExpansionFunctionLibrary.cpp | 738 ++ .../Private/VRExpansionPlugin.cpp | 58 + .../Private/VRExpansionPluginPrivatePCH.h | 20 + .../Private/VRGestureComponent.cpp | 738 ++ .../Private/VRGlobalSettings.cpp | 445 + .../Private/VRGripInterface.cpp | 18 + .../Private/VRPathFollowingComponent.cpp | 441 + .../Private/VRPlayerController.cpp | 120 + .../Private/VRRootComponent.cpp | 1644 ++++ .../Private/VRStereoWidgetComponent.cpp | 1165 +++ .../Private/VRTrackedParentInterface.cpp | 11 + .../Public/CharacterMovementCompTypes.h | 695 ++ .../Public/GripMotionControllerComponent.h | 1533 ++++ .../Public/GripScripts/GS_Default.h | 48 + .../Public/GripScripts/GS_GunTools.h | 318 + .../GripScripts/GS_InteractibleSettings.h | 114 + .../Public/GripScripts/GS_LerpToHand.h | 69 + .../Public/GripScripts/GS_Melee.h | 315 + .../Public/GripScripts/GS_Physics.h | 38 + .../Public/GripScripts/VRGripScriptBase.h | 273 + .../Public/Grippables/GrippableActor.h | 272 + .../Public/Grippables/GrippableBoxComponent.h | 202 + .../Grippables/GrippableCapsuleComponent.h | 199 + .../Public/Grippables/GrippableCharacter.h | 28 + .../Public/Grippables/GrippableDataTypes.h | 46 + .../Grippables/GrippablePhysicsReplication.h | 122 + .../Grippables/GrippableSkeletalMeshActor.h | 296 + .../GrippableSkeletalMeshComponent.h | 201 + .../Grippables/GrippableSphereComponent.h | 201 + .../Grippables/GrippableStaticMeshActor.h | 298 + .../Grippables/GrippableStaticMeshComponent.h | 200 + .../Public/Grippables/HandSocketComponent.h | 425 + .../Public/Interactibles/VRButtonComponent.h | 189 + .../Public/Interactibles/VRDialComponent.h | 307 + .../VRInteractibleFunctionLibrary.h | 302 + .../Public/Interactibles/VRLeverComponent.h | 416 + .../Public/Interactibles/VRMountComponent.h | 254 + .../Public/Interactibles/VRSliderComponent.h | 406 + .../Public/Misc/BucketUpdateSubsystem.h | 169 + .../Public/Misc/CollisionIgnoreSubsystem.h | 181 + .../Misc/OptionalRepSkeletalMeshActor.h | 128 + .../Public/Misc/VRAIPerceptionOverrides.h | 286 + .../Misc/VREPhysicalAnimationComponent.h | 89 + .../Misc/VREPhysicsConstraintComponent.h | 180 + .../Public/Misc/VRFullScreenUserWidget.h | 424 + .../Public/Misc/VRGameViewportClient.h | 164 + .../Public/Misc/VRLogComponent.h | 242 + .../Public/Misc/VRPlayerStart.h | 150 + .../Public/Misc/VRRenderTargetManager.h | 389 + .../Public/Misc/VRVehiclePawn.h | 158 + .../Public/Misc/VRWheeledVehicle.h | 94 + .../ParentRelativeAttachmentComponent.h | 361 + .../Public/ReplicatedVRCameraComponent.h | 167 + .../Public/SimpleChar/VRSimpleCharacter.h | 51 + .../VRSimpleCharacterMovementComponent.h | 166 + .../VRExpansionPlugin/Public/VRAIController.h | 36 + .../VRExpansionPlugin/Public/VRBPDatatypes.h | 1956 +++++ .../Public/VRBaseCharacter.h | 621 ++ .../Public/VRBaseCharacterMovementComponent.h | 417 + .../VRExpansionPlugin/Public/VRCharacter.h | 46 + .../Public/VRCharacterMovementComponent.h | 327 + .../Public/VRExpansionFunctionLibrary.h | 380 + .../Public/VRExpansionPlugin.h | 19 + .../Public/VRGestureComponent.h | 367 + .../Public/VRGlobalSettings.h | 290 + .../Public/VRGripInterface.h | 158 + .../Public/VRPathFollowingComponent.h | 56 + .../Public/VRPlayerController.h | 86 + .../Public/VRRootComponent.h | 296 + .../Public/VRStereoWidgetComponent.h | 238 + .../Public/VRTrackedParentInterface.h | 101 + .../VRExpansionPlugin.Build.cs | 144 + .../VRExpansionPlugin.uplugin | 41 + 164 files changed, 65474 insertions(+), 8 deletions(-) create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitattributes create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitignore create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/LICENSE.txt create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Config/FilterPlugin.ini create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/OpenVRExpansionPlugin.uplugin create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Resources/Icon128.png create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/OpenVRExpansionPlugin.Build.cs create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionFunctionLibrary.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPlugin.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPluginPrivatePCH.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/SteamVRKeyboardComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionFunctionLibrary.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionPlugin.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/SteamVRKeyboardComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Config/FilterPlugin.ini create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/OpenXRExpansionPlugin.uplugin create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Resources/Icon128.png create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/OpenXRExpansionEditor.Build.cs create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/AnimGraphNode_ApplyOpenXRHandPose.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/OpenXRExpansionEditor.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/AnimGraphNode_ApplyOpenXRHandPose.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/OpenXRExpansionEditor.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/OpenXRExpansionPlugin.Build.cs create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/AnimNode_ApplyOpenXRHandPose.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionFunctionLibrary.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionPlugin.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRHandPoseComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/AnimNode_ApplyOpenXRHandPose.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionFunctionLibrary.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionPlugin.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionTypes.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRHandPoseComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/README.md create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Config/FilterPlugin.ini create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Resources/Icon128.png create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketComponentDetails.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketVisualizer.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRExpansionEditor.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketComponentDetails.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketVisualizer.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRExpansionEditor.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/VRExpansionEditor.Build.cs create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/CharacterMovementCompTypes.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Default.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_GunTools.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_InteractibleSettings.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_LerpToHand.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Melee.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Physics.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/VRGripScriptBase.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableActor.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableBoxComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCapsuleComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCharacter.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippablePhysicsReplication.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshActor.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSphereComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshActor.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRButtonComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRDialComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRInteractibleFunctionLibrary.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRLeverComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRMountComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRSliderComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/BucketUpdateSubsystem.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/CollisionIgnoreSubsystem.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/OptionalRepSkeletalMeshActor.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRAIPerceptionOverrides.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VREPhysicalAnimationComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRFullScreenUserWidget.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRLogComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRPlayerStart.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRRenderTargetManager.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ParentRelativeAttachmentComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ReplicatedVRCameraComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacter.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacterMovementComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRAIController.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBPDatatypes.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacter.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacterMovementComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacter.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacterMovementComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionFunctionLibrary.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPlugin.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPluginPrivatePCH.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGestureComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGlobalSettings.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGripInterface.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPathFollowingComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPlayerController.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRRootComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRStereoWidgetComponent.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRTrackedParentInterface.cpp create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/CharacterMovementCompTypes.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripMotionControllerComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Default.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_GunTools.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_InteractibleSettings.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_LerpToHand.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Melee.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Physics.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/VRGripScriptBase.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableActor.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableBoxComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCapsuleComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCharacter.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableDataTypes.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippablePhysicsReplication.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshActor.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSphereComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshActor.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRButtonComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRDialComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRInteractibleFunctionLibrary.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRLeverComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRMountComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRSliderComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/BucketUpdateSubsystem.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/CollisionIgnoreSubsystem.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/OptionalRepSkeletalMeshActor.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRAIPerceptionOverrides.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicalAnimationComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicsConstraintComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRFullScreenUserWidget.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRGameViewportClient.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRLogComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRPlayerStart.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRRenderTargetManager.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRVehiclePawn.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRWheeledVehicle.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ParentRelativeAttachmentComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ReplicatedVRCameraComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacter.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacterMovementComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRAIController.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBPDatatypes.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacter.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacterMovementComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacter.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacterMovementComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionFunctionLibrary.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionPlugin.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGestureComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGlobalSettings.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGripInterface.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPathFollowingComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPlayerController.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRRootComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRStereoWidgetComponent.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRTrackedParentInterface.h create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/VRExpansionPlugin.Build.cs create mode 100644 ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/VRExpansionPlugin.uplugin diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index f311ac9..9858cf5 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21341e9de9651a82f1eacc56bd81ab7cdb68a76e57c88239aa9dcdfda042d32b -size 24873 +oid sha256:a2f48c4c78a244448a0f0bbac026eb2391b4081aeca09798bd9da775d8ab399c +size 26775 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 0c94983..33fd6bd 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f73bbed41878eafe913089d4acc3cf790b23ca8b26e06b2e527e534900aaf6c -size 1137855 +oid sha256:0daddead4a99bec0ce0607db86f329ee9a99334bd78c29c2a14a748247c90418 +size 1143619 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index 9d84f14..b69323c 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a35c44ebc754764d6533c8e9bad99408f7a61d40e410edd5138e44f195875463 -size 119717 +oid sha256:99cb344c28fd0b9670a9719c2eee8f0d9c18e15c4a681f372f14d63371df3eaa +size 167946 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index f28ac3c..63465c6 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d783f5396dba25ec799fdd0ea34d616b966d13947cccb496fca9f4492dd915b9 -size 848839 +oid sha256:b5f1d1baf3dfdf3ab91b1ab796919be3abcefb0238e14166e92c74c6b5913196 +size 848576 diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitattributes b/ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitattributes new file mode 100644 index 0000000..3373152 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.bat eol=crlf \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitignore b/ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitignore new file mode 100644 index 0000000..3cdb673 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/.gitignore @@ -0,0 +1,10 @@ + +.hg/ +binaries/ +deriveddatacache/ +.vs/ +build/ +intermediate/ +PACKPLUGIN/ +saved/ +*.orig \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/LICENSE.txt b/ExcavatorSimulator/Plugins/VRExpansionPlugin/LICENSE.txt new file mode 100644 index 0000000..986c999 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright Joshua Statzer + +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. \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Config/FilterPlugin.ini b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/OpenVRExpansionPlugin.uplugin b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/OpenVRExpansionPlugin.uplugin new file mode 100644 index 0000000..3387018 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/OpenVRExpansionPlugin.uplugin @@ -0,0 +1,30 @@ +{ + "FileVersion": 3, + "Version": 5.0, + "VersionName": "5.0", + "FriendlyName": "OpenVRExpansionPlugin", + "Description": "Adds several new Steam/Open VR features & components to UE4", + "Category": "VRExpansion", + "CreatedBy": "Joshua (MordenTral) Statzer", + "CreatedByURL": "http://www.vreue4.com", + "DocsURL": "http://www.vreue4.com", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": true, + "Modules": [ + { + "Name": "OpenVRExpansionPlugin", + "Type": "RunTime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "ProceduralMeshComponent", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Resources/Icon128.png b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Resources/Icon128.png new file mode 100644 index 0000000..23fec31 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8f329acc9d0c2815d28349d5a4144ebd337f7e2a93819cf1a7c3cc9b06ecea +size 16085 diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/OpenVRExpansionPlugin.Build.cs b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/OpenVRExpansionPlugin.Build.cs new file mode 100644 index 0000000..3eb3072 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/OpenVRExpansionPlugin.Build.cs @@ -0,0 +1,62 @@ +// Some copyright should be here... +using System.IO; +using UnrealBuildTool; + +public class OpenVRExpansionPlugin : ModuleRules +{ + private string PluginsPath + { + get { return Path.GetFullPath(Target.RelativeEnginePath) + "Plugins/Runtime/"; } + } + + public OpenVRExpansionPlugin(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + //bEnforceIWYU = true; + + PublicDefinitions.Add("WITH_OPEN_VR_EXPANSION=1"); + + + PublicIncludePaths.AddRange( + new string[] { + //"OpenVRExpansionPlugin/Public", + //"HeadMountedDisplay/Public", + "Runtime/Engine/Private/PhysicsEngine" + + // ... add public include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "HeadMountedDisplay", + "RHI", + "RenderCore", + // "ShaderCore", + "ProceduralMeshComponent", + // "VRExpansionPlugin" + // "EngineSettings" + }); + + + if ( + Target.Platform == UnrealTargetPlatform.Win64 || + Target.Platform == UnrealTargetPlatform.Mac || + (Target.Platform == UnrealTargetPlatform.Linux && Target.Architecture.StartsWith("x86_64")) + ) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "OpenVR" + }); + } + + } +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionFunctionLibrary.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionFunctionLibrary.cpp new file mode 100644 index 0000000..cd50497 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionFunctionLibrary.cpp @@ -0,0 +1,1417 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "OpenVRExpansionFunctionLibrary.h" +//#include "EngineMinimal.h" +//#include "Engine/Engine.h" +#include "CoreMinimal.h" +#include "Engine/Texture.h" +#include "Engine/Texture2D.h" +#include "Rendering/Texture2DResource.h" +#include "RenderUtils.h" +#include "IXRTrackingSystem.h" +#include "IHeadMountedDisplay.h" +#include "ProceduralMeshComponent.h" +#include "HeadMountedDisplayFunctionLibrary.h" + +#if WITH_EDITOR +#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h" +#endif + +//General Log +DEFINE_LOG_CATEGORY(OpenVRExpansionFunctionLibraryLog); + +#if STEAMVR_SUPPORTED_PLATFORM + +//pVRGetGenericInterface UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn = nullptr; +FBPOpenVRCameraHandle UOpenVRExpansionFunctionLibrary::OpenCamera = FBPOpenVRCameraHandle(); +#endif + +UOpenVRExpansionFunctionLibrary::UOpenVRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +//============================================================================= +UOpenVRExpansionFunctionLibrary::~UOpenVRExpansionFunctionLibrary() +{ +#if STEAMVR_SUPPORTED_PLATFORM + //if(VRGetGenericInterfaceFn) + // UnloadOpenVRModule(); +#endif +} + +EBPOpenVRHMDDeviceType UOpenVRExpansionFunctionLibrary::GetOpenVRHMDType() +{ + + EBPOpenVRHMDDeviceType DeviceType = EBPOpenVRHMDDeviceType::DT_Unknown; + + if (GEngine && GEngine->XRSystem.IsValid()) + { + // Already declared in some of our includes here + //static const FName SteamVRSysName(TEXT("SteamVR")); + static const FName OculusSystemName(TEXT("OculusHMD")); + //static const FName OSVRSystemName(TEXT("OSVR")); + + FName DeviceName(NAME_None); + DeviceName = GEngine->XRSystem->GetSystemName(); + + + if (DeviceName == SteamVRSystemName) + DeviceType = EBPOpenVRHMDDeviceType::DT_SteamVR; + else if (DeviceName == OculusSystemName) + DeviceType = EBPOpenVRHMDDeviceType::DT_OculusHMD; + //else if (DeviceName == OSVRSystemName) + //DeviceType = EBPOpenVRHMDDeviceType::DT_OSVR; + } + +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Get OpenVRHMDType returning default value as this is not a steamvr supported platform!!!")); + return DeviceType; +#else + + // If not, will early out with the type (OSVR / OculusHMD) + if (DeviceType == EBPOpenVRHMDDeviceType::DT_SteamVR) + { + FString DeviceModelNumber; + EBPOVRResultSwitch Result; + + // Using index 0 as it is always HMD + UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyString(EVRDeviceProperty_String::Prop_ModelNumber_String_1001, vr::k_unTrackedDeviceIndex_Hmd, DeviceModelNumber, Result); + if (Result == EBPOVRResultSwitch::OnSucceeded) + { + //UE_LOG(OpenVRExpansionFunctionLibraryLog, Display, TEXT("OpenVRDeviceType - Prop_ModelNumber_String_1001: %s"), *DeviceModelNumber); + + //#TODO: Screw this eventually, need it to be actual string value comparisons for each model + // This is the hacky workaround for now + /* + "Vive MV"; + "Vive Cosmos" + "VIVE_Pro MV" + "Vive Focus" + "Oculus Rift CV1"; + "Lenovo Explorer"; + "HP Windows Mixed Reality Headset"; + "Samsung Windows Mixed Reality 800ZAA"; + "Acer AH100"; + */ + + // Manufacturer name + /*WindowsMR*/ + + if (DeviceModelNumber.Find("index", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_ValveIndex; + } + else if (DeviceModelNumber.Find("vive_cosmos", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_ViveCosmos; + } + else if (DeviceModelNumber.Find("vive", ESearchCase::IgnoreCase) != INDEX_NONE) + { + if (DeviceModelNumber.Find("focus3", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_ViveFocus3; + } + else if (DeviceModelNumber.Find("focus", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_ViveFocus; + } + else if (DeviceModelNumber.Find("Pico Neo3", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_PicoNeo3; + } + else + { + DeviceType = EBPOpenVRHMDDeviceType::DT_Vive; + } + } + else if ((DeviceModelNumber.Find("oculus quest", ESearchCase::IgnoreCase) != INDEX_NONE) || + (DeviceModelNumber.Find("miramar", ESearchCase::IgnoreCase) != INDEX_NONE)) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_OculusQuestHMD; + } + else if (DeviceModelNumber.Find("oculus", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_OculusHMD; + } + else if ( + DeviceModelNumber.Find("Mixed Reality", ESearchCase::IgnoreCase) != INDEX_NONE || + DeviceModelNumber.Find("Acer", ESearchCase::IgnoreCase) != INDEX_NONE || + DeviceModelNumber.Find("Lenovo", ESearchCase::IgnoreCase) != INDEX_NONE + ) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_WindowsMR; + } + else + { + // Check for manufacturer name for windows MR + UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyString(EVRDeviceProperty_String::Prop_ManufacturerName_String_1005, vr::k_unTrackedDeviceIndex_Hmd, DeviceModelNumber, Result); + if (Result == EBPOVRResultSwitch::OnSucceeded) + { + if (DeviceModelNumber.Find("WindowsMR", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRHMDDeviceType::DT_WindowsMR; + } + } + else + { + DeviceType = EBPOpenVRHMDDeviceType::DT_Unknown; +#if WITH_EDITOR + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Tell VRE about unhandled HMD model type: %s !!!"), *DeviceModelNumber); +#endif + } + } + } + else + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Get OpenVRHMDType failed to get the OpenVR property string!!!")); + } + } + + return DeviceType; +#endif +} + +EBPOpenVRControllerDeviceType UOpenVRExpansionFunctionLibrary::GetOpenVRControllerType() +{ + + EBPOpenVRControllerDeviceType DeviceType = EBPOpenVRControllerDeviceType::DT_UnknownController; + +#if !STEAMVR_SUPPORTED_PLATFORM + return DeviceType; +#else + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + return DeviceType; + } + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + return DeviceType; + } + + int32 DeviceIndexOut = INDEX_NONE; + int32 FallbackIndex = INDEX_NONE; + + for (uint32 DeviceIndex = 0; DeviceIndex < vr::k_unMaxTrackedDeviceCount; ++DeviceIndex) + { + const vr::ETrackedDeviceClass DeviceClass = VRSystem->GetTrackedDeviceClass(DeviceIndex); + if (DeviceClass == vr::ETrackedDeviceClass::TrackedDeviceClass_Controller) + { + // NOTE: GetControllerRoleForTrackedDeviceIndex() only seems to return a valid role if the device is on and being tracked + const vr::ETrackedControllerRole ControllerRole = VRSystem->GetControllerRoleForTrackedDeviceIndex(DeviceIndex); + if (ControllerRole != vr::TrackedControllerRole_LeftHand && ControllerRole != vr::TrackedControllerRole_RightHand) + { + continue; + } + + DeviceIndexOut = DeviceIndex; + break; + } + } + + if (DeviceIndexOut == INDEX_NONE) + { + return DeviceType; + } + + EBPOVRResultSwitch Result; + + FString DeviceModelNumber; + UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyString(EVRDeviceProperty_String::Prop_ModelNumber_String_1001, DeviceIndexOut, DeviceModelNumber, Result); + if (Result == EBPOVRResultSwitch::OnSucceeded) + { + if (DeviceModelNumber.Find("knuckles", ESearchCase::IgnoreCase) != INDEX_NONE || DeviceModelNumber.Find("index", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRControllerDeviceType::DT_IndexController; + } + else if (DeviceModelNumber.Find("cosmos", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenVRControllerDeviceType::DT_CosmosController; + } + else if (DeviceModelNumber.Find("VIVE", ESearchCase::IgnoreCase) != INDEX_NONE) // Vive Wand + { + DeviceType = EBPOpenVRControllerDeviceType::DT_ViveController; + } + else if ((DeviceModelNumber.Find("oculus quest", ESearchCase::IgnoreCase) != INDEX_NONE) || + (DeviceModelNumber.Find("miramar", ESearchCase::IgnoreCase) != INDEX_NONE)) + { + DeviceType = EBPOpenVRControllerDeviceType::DT_QuestController; + } + else if (DeviceModelNumber.Find("oculus rift cv1", ESearchCase::IgnoreCase) != INDEX_NONE) // Oculus Rift CV1 + { + DeviceType = EBPOpenVRControllerDeviceType::DT_RiftController; + } + else if (DeviceModelNumber.Find("oculus rift s", ESearchCase::IgnoreCase) != INDEX_NONE) // Oculus Rift CV1 + { + DeviceType = EBPOpenVRControllerDeviceType::DT_RiftSController; + } + else if (DeviceModelNumber.Find("windowsmr", ESearchCase::IgnoreCase) != INDEX_NONE) // Oculus Rift CV1 + { + DeviceType = EBPOpenVRControllerDeviceType::DT_WMRController; + } + else if (DeviceModelNumber.Find("Pico Neo3 Controller", ESearchCase::IgnoreCase) != INDEX_NONE) // PicoNeo3 + { + DeviceType = EBPOpenVRControllerDeviceType::DT_PicoNeo3Controller; + } + else + { +#if WITH_EDITOR + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Tell VRE about unhandled controller model type: %s !!!"), *DeviceModelNumber); + // Add other controllers here +#endif + } + } + + return DeviceType; +#endif +} + +bool UOpenVRExpansionFunctionLibrary::HasVRCamera(EOpenVRCameraFrameType FrameType, int32 &Width, int32 &Height) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + return false; +#else + Width = 0; + Height = 0; + + //if (!VRGetGenericInterfaceFn) + // return false; + + // Don't run anything if no HMD and if the HMD is not a steam type + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + return false; + + vr::IVRTrackedCamera* VRCamera = vr::VRTrackedCamera(); + + + if (!VRCamera) + return false; + + bool pHasCamera; + vr::EVRTrackedCameraError CamError = VRCamera->HasCamera(vr::k_unTrackedDeviceIndex_Hmd, &pHasCamera); + + if (CamError != vr::EVRTrackedCameraError::VRTrackedCameraError_None) + return false; + + uint32 WidthOut; + uint32 HeightOut; + uint32 FrameBufferSize; + CamError = VRCamera->GetCameraFrameSize(vr::k_unTrackedDeviceIndex_Hmd, (vr::EVRTrackedCameraFrameType)FrameType, &WidthOut, &HeightOut, &FrameBufferSize); + + Width = WidthOut; + Height = HeightOut; + + return pHasCamera; + +#endif +} + +void UOpenVRExpansionFunctionLibrary::AcquireVRCamera(FBPOpenVRCameraHandle & CameraHandle, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + // Don't run anything if no HMD and if the HMD is not a steam type + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + // If already have a valid camera handle + if (OpenCamera.IsValid()) + { + CameraHandle = OpenCamera; + Result = EBPOVRResultSwitch::OnSucceeded; + return; + } + + vr::IVRTrackedCamera* VRCamera = vr::VRTrackedCamera(); + + if (!VRCamera) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::EVRTrackedCameraError CamError = VRCamera->AcquireVideoStreamingService(vr::k_unTrackedDeviceIndex_Hmd, &CameraHandle.pCameraHandle); + + if (CamError != vr::EVRTrackedCameraError::VRTrackedCameraError_None) + CameraHandle.pCameraHandle = INVALID_TRACKED_CAMERA_HANDLE; + + if (CameraHandle.pCameraHandle == INVALID_TRACKED_CAMERA_HANDLE) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + OpenCamera = CameraHandle; + Result = EBPOVRResultSwitch::OnSucceeded; + return; +#endif +} + +void UOpenVRExpansionFunctionLibrary::ReleaseVRCamera(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + // Don't run anything if no HMD and if the HMD is not a steam type + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + if (!CameraHandle.IsValid()) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + + vr::IVRTrackedCamera* VRCamera = vr::VRTrackedCamera(); + + if (!VRCamera) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::EVRTrackedCameraError CamError = VRCamera->ReleaseVideoStreamingService(CameraHandle.pCameraHandle); + CameraHandle.pCameraHandle = INVALID_TRACKED_CAMERA_HANDLE; + + OpenCamera = CameraHandle; + Result = EBPOVRResultSwitch::OnSucceeded; + return; + +#endif +} + +bool UOpenVRExpansionFunctionLibrary::IsValid(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle) +{ + return (CameraHandle.IsValid()); +} + + +UTexture2D * UOpenVRExpansionFunctionLibrary::CreateCameraTexture2D(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle, EOpenVRCameraFrameType FrameType, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return nullptr; +#else + + // Don't run anything if no HMD and if the HMD is not a steam type + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return nullptr; + } + + if (!CameraHandle.IsValid() || !FApp::CanEverRender()) + { + Result = EBPOVRResultSwitch::OnFailed; + return nullptr; + } + + vr::IVRTrackedCamera* VRCamera = vr::VRTrackedCamera(); + + if (!VRCamera) + { + Result = EBPOVRResultSwitch::OnFailed; + return nullptr; + } + + uint32 Width; + uint32 Height; + uint32 FrameBufferSize; + vr::EVRTrackedCameraError CamError = VRCamera->GetCameraFrameSize(vr::k_unTrackedDeviceIndex_Hmd, (vr::EVRTrackedCameraFrameType)FrameType, &Width, &Height, &FrameBufferSize); + + if (Width > 0 && Height > 0) + { + + UTexture2D * NewRenderTarget2D = UTexture2D::CreateTransient(Width, Height, EPixelFormat::PF_R8G8B8A8);//NewObject<UTexture2D>(GetWorld()); + check(NewRenderTarget2D); + + //Setting some Parameters for the Texture and finally returning it + NewRenderTarget2D->GetPlatformData()->SetNumSlices(1); + NewRenderTarget2D->NeverStream = true; + NewRenderTarget2D->UpdateResource(); + + Result = EBPOVRResultSwitch::OnSucceeded; + return NewRenderTarget2D; + } + + Result = EBPOVRResultSwitch::OnFailed; + return nullptr; +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetVRCameraFrame(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle, EOpenVRCameraFrameType FrameType, EBPOVRResultSwitch & Result, UTexture2D * TargetRenderTarget) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + // Don't run anything if no HMD and if the HMD is not a steam type + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + if (!TargetRenderTarget || !CameraHandle.IsValid()) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVRTrackedCamera* VRCamera = vr::VRTrackedCamera(); + + if (!VRCamera) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + uint32 Width; + uint32 Height; + uint32 FrameBufferSize; + vr::EVRTrackedCameraError CamError = VRCamera->GetCameraFrameSize(vr::k_unTrackedDeviceIndex_Hmd, (vr::EVRTrackedCameraFrameType)FrameType, &Width, &Height, &FrameBufferSize); + + if (CamError != vr::EVRTrackedCameraError::VRTrackedCameraError_None || Width <= 0 || Height <= 0) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + // Make sure formats are correct + check(FrameBufferSize == (Width * Height * GPixelFormats[EPixelFormat::PF_R8G8B8A8].BlockBytes)); + + + // Need to bring this back after moving from render target to this + // Update the format if required, this is in case someone made a new render target NOT with my custom function + // Enforces correct buffer size for the camera feed + if (TargetRenderTarget->GetSizeX() != Width || TargetRenderTarget->GetSizeY() != Height || TargetRenderTarget->GetPixelFormat() != EPixelFormat::PF_R8G8B8A8) + { + if (FTexturePlatformData* PlatformData = TargetRenderTarget->GetPlatformData()) + { + PlatformData->SizeX = Width; + PlatformData->SizeY = Height; + PlatformData->PixelFormat = EPixelFormat::PF_R8G8B8A8; + + // Allocate first mipmap. + int32 NumBlocksX = Width / GPixelFormats[EPixelFormat::PF_R8G8B8A8].BlockSizeX; + int32 NumBlocksY = Height / GPixelFormats[EPixelFormat::PF_R8G8B8A8].BlockSizeY; + + PlatformData->Mips[0].SizeX = Width; + PlatformData->Mips[0].SizeY = Height; + PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + PlatformData->Mips[0].BulkData.Realloc(NumBlocksX * NumBlocksY * GPixelFormats[EPixelFormat::PF_R8G8B8A8].BlockBytes); + PlatformData->Mips[0].BulkData.Unlock(); + } + } + + vr::CameraVideoStreamFrameHeader_t CamHeader; + uint8* pData = new uint8[FrameBufferSize]; + + CamError = VRCamera->GetVideoStreamFrameBuffer(CameraHandle.pCameraHandle, (vr::EVRTrackedCameraFrameType)FrameType, pData, FrameBufferSize, &CamHeader, sizeof(vr::CameraVideoStreamFrameHeader_t)); + + // No frame available = still on spin / wake up + if (CamError != vr::EVRTrackedCameraError::VRTrackedCameraError_None || CamError == vr::EVRTrackedCameraError::VRTrackedCameraError_NoFrameAvailable) + { + delete[] pData; + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + /*ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( + UpdateDynamicTextureCode, + UTexture2D*, pTexture, TargetRenderTarget, + const uint8*, pData, pData, + { + FUpdateTextureRegion2D region; + region.SrcX = 0; + region.SrcY = 0; + region.DestX = 0; + region.DestY = 0; + region.Width = pTexture->GetSizeX();// TEX_WIDTH; + region.Height = pTexture->GetSizeY();//TEX_HEIGHT; + + FTexture2DResource* resource = (FTexture2DResource*)pTexture->Resource; + RHIUpdateTexture2D(resource->GetTexture2DRHI(), 0, region, region.Width * GPixelFormats[pTexture->GetPixelFormat()].BlockBytes, pData); + delete[] pData; + });*/ + + UTexture2D* TexturePtr = TargetRenderTarget; + const uint8* TextureData = pData; + ENQUEUE_RENDER_COMMAND(OpenVRExpansionPlugin_GetVRCameraFrame)( + [TexturePtr, TextureData](FRHICommandList& RHICmdList) + { + FUpdateTextureRegion2D region; + region.SrcX = 0; + region.SrcY = 0; + region.DestX = 0; + region.DestY = 0; + region.Width = TexturePtr->GetSizeX();// TEX_WIDTH; + region.Height = TexturePtr->GetSizeY();//TEX_HEIGHT; + + FTexture2DResource* resource = (FTexture2DResource*)TexturePtr->GetResource(); + RHIUpdateTexture2D(resource->GetTexture2DRHI(), 0, region, region.Width * GPixelFormats[TexturePtr->GetPixelFormat()].BlockBytes/*TEX_PIXEL_SIZE_IN_BYTES*/, TextureData); + delete[] TextureData; + }); + + // Letting the enqueued command free the memory + Result = EBPOVRResultSwitch::OnSucceeded; + return; +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyString(EVRDeviceProperty_String PropertyToRetrieve, int32 DeviceID, FString & StringValue, EBPOVRResultSwitch & Result) +{ + +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::TrackedPropertyError pError = vr::TrackedPropertyError::TrackedProp_Success; + + vr::ETrackedDeviceProperty EnumPropertyValue = VREnumToString(TEXT("EVRDeviceProperty_String"), static_cast<uint8>(PropertyToRetrieve)); + if (EnumPropertyValue == vr::ETrackedDeviceProperty::Prop_Invalid) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + char charvalue[vr::k_unMaxPropertyStringSize]; + uint32_t buffersize = vr::k_unMaxPropertyStringSize; + uint32_t ret = VRSystem->GetStringTrackedDeviceProperty(DeviceID, EnumPropertyValue, charvalue, buffersize, &pError); + + if (pError != vr::TrackedPropertyError::TrackedProp_Success) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + StringValue = FString(ANSI_TO_TCHAR(charvalue)); + Result = EBPOVRResultSwitch::OnSucceeded; + return; + +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyBool(EVRDeviceProperty_Bool PropertyToRetrieve, int32 DeviceID, bool & BoolValue, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::TrackedPropertyError pError = vr::TrackedPropertyError::TrackedProp_Success; + + vr::ETrackedDeviceProperty EnumPropertyValue = VREnumToString(TEXT("EVRDeviceProperty_Bool"), static_cast<uint8>(PropertyToRetrieve)); + if (EnumPropertyValue == vr::ETrackedDeviceProperty::Prop_Invalid) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + bool ret = VRSystem->GetBoolTrackedDeviceProperty(DeviceID, EnumPropertyValue, &pError); + + if (pError != vr::TrackedPropertyError::TrackedProp_Success) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + BoolValue = ret; + Result = EBPOVRResultSwitch::OnSucceeded; + return; + +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyFloat(EVRDeviceProperty_Float PropertyToRetrieve, int32 DeviceID, float & FloatValue, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::TrackedPropertyError pError = vr::TrackedPropertyError::TrackedProp_Success; + + vr::ETrackedDeviceProperty EnumPropertyValue = VREnumToString(TEXT("EVRDeviceProperty_Float"), static_cast<uint8>(PropertyToRetrieve)); + if (EnumPropertyValue == vr::ETrackedDeviceProperty::Prop_Invalid) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + float ret = VRSystem->GetFloatTrackedDeviceProperty(DeviceID, EnumPropertyValue, &pError); + + if (pError != vr::TrackedPropertyError::TrackedProp_Success) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + FloatValue = ret; + Result = EBPOVRResultSwitch::OnSucceeded; + return; + +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyInt32(EVRDeviceProperty_Int32 PropertyToRetrieve, int32 DeviceID, int32 & IntValue, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::TrackedPropertyError pError = vr::TrackedPropertyError::TrackedProp_Success; + + vr::ETrackedDeviceProperty EnumPropertyValue = VREnumToString(TEXT("EVRDeviceProperty_Int32"), static_cast<uint8>(PropertyToRetrieve)); + if (EnumPropertyValue == vr::ETrackedDeviceProperty::Prop_Invalid) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + int32 ret = VRSystem->GetInt32TrackedDeviceProperty(DeviceID, EnumPropertyValue, &pError); + + if (pError != vr::TrackedPropertyError::TrackedProp_Success) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + IntValue = ret; + Result = EBPOVRResultSwitch::OnSucceeded; + return; + +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyUInt64(EVRDeviceProperty_UInt64 PropertyToRetrieve, int32 DeviceID, FString & UInt64Value, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::TrackedPropertyError pError = vr::TrackedPropertyError::TrackedProp_Success; + + vr::ETrackedDeviceProperty EnumPropertyValue = VREnumToString(TEXT("EVRDeviceProperty_UInt64"), static_cast<uint8>(PropertyToRetrieve)); + if (EnumPropertyValue == vr::ETrackedDeviceProperty::Prop_Invalid) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + uint64 ret = VRSystem->GetUint64TrackedDeviceProperty(DeviceID, EnumPropertyValue, &pError); + + if (pError != vr::TrackedPropertyError::TrackedProp_Success) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + UInt64Value = FString::Printf(TEXT("%llu"), ret); + Result = EBPOVRResultSwitch::OnSucceeded; + return; + +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetVRDevicePropertyMatrix34AsTransform(EVRDeviceProperty_Matrix34 PropertyToRetrieve, int32 DeviceID, FTransform & TransformValue, EBPOVRResultSwitch & Result) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::TrackedPropertyError pError = vr::TrackedPropertyError::TrackedProp_Success; + + vr::ETrackedDeviceProperty EnumPropertyValue = VREnumToString(TEXT("EVRDeviceProperty_Matrix34"), static_cast<uint8>(PropertyToRetrieve)); + if (EnumPropertyValue == vr::ETrackedDeviceProperty::Prop_Invalid) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::HmdMatrix34_t ret = VRSystem->GetMatrix34TrackedDeviceProperty(DeviceID, EnumPropertyValue, &pError); + + if (pError != vr::TrackedPropertyError::TrackedProp_Success) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + TransformValue = FTransform(ToFMatrix(ret)); + Result = EBPOVRResultSwitch::OnSucceeded; + return; + +#endif +} + + +EBPOpenVRTrackedDeviceClass UOpenVRExpansionFunctionLibrary::GetOpenVRDeviceType(int32 OpenVRDeviceIndex) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + return EBPOpenVRTrackedDeviceClass::TrackedDeviceClass_Invalid; +#else + + if (OpenVRDeviceIndex < 0 || OpenVRDeviceIndex > (vr::k_unMaxTrackedDeviceCount - 1)) + return EBPOpenVRTrackedDeviceClass::TrackedDeviceClass_Invalid; + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VRSystem InterfaceErrored in GetOpenVRDevices")); + return EBPOpenVRTrackedDeviceClass::TrackedDeviceClass_Invalid; + } + + return (EBPOpenVRTrackedDeviceClass)VRSystem->GetTrackedDeviceClass(OpenVRDeviceIndex); +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetOpenVRDevices(TArray<EBPOpenVRTrackedDeviceClass> &FoundDevices) +{ +#if !STEAMVR_SUPPORTED_PLATFORM +#else + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VRSystem InterfaceErrored in GetOpenVRDevices")); + return; + } + + vr::ETrackedDeviceClass DeviceClass = vr::ETrackedDeviceClass::TrackedDeviceClass_Invalid; + for (vr::TrackedDeviceIndex_t deviceIndex = vr::k_unTrackedDeviceIndex_Hmd; deviceIndex < vr::k_unMaxTrackedDeviceCount; ++deviceIndex) + { + DeviceClass = VRSystem->GetTrackedDeviceClass(deviceIndex); + + if (VRSystem->GetTrackedDeviceClass(deviceIndex) != vr::ETrackedDeviceClass::TrackedDeviceClass_Invalid) + FoundDevices.Add((EBPOpenVRTrackedDeviceClass)DeviceClass); + } +#endif +} + +void UOpenVRExpansionFunctionLibrary::GetOpenVRDevicesByType(EBPOpenVRTrackedDeviceClass TypeToRetreive, TArray<int32> &FoundIndexs) +{ +#if !STEAMVR_SUPPORTED_PLATFORM +#else + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VRSystem InterfaceErrored in GetOpenVRDevices")); + return; + } + + for (vr::TrackedDeviceIndex_t deviceIndex = vr::k_unTrackedDeviceIndex_Hmd; deviceIndex < vr::k_unMaxTrackedDeviceCount; ++deviceIndex) + { + if (VRSystem->GetTrackedDeviceClass(deviceIndex) == (vr::ETrackedDeviceClass)TypeToRetreive) + FoundIndexs.Add(deviceIndex); + } +#endif +} + +bool UOpenVRExpansionFunctionLibrary::IsOpenVRDeviceConnected(int32 OpenVRDeviceIndex) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + return false; +#else + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VRSystem InterfaceErrored in IsOpenVRDeviceConnected")); + return false; + } + + return VRSystem->IsTrackedDeviceConnected(OpenVRDeviceIndex); + +#endif +} + +UTexture2D * UOpenVRExpansionFunctionLibrary::GetVRDeviceModelAndTexture(UObject* WorldContextObject, FString RenderModelNameOverride, FString& RenderModelNameOut, EBPOpenVRTrackedDeviceClass DeviceType, TArray<UProceduralMeshComponent *> ProceduralMeshComponentsToFill, bool bCreateCollision, EAsyncBlueprintResultSwitch &Result, int32 OverrideDeviceID) +{ + +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + return NULL; +#else + + vr::IVRSystem* VRSystem = vr::VRSystem(); + + if (!VRSystem) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VRSystem InterfaceErrored")); + } + + vr::IVRRenderModels* VRRenderModels = vr::VRRenderModels(); + + if (!VRRenderModels) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Render Models Errored")); + } + + + if (!VRSystem || !VRRenderModels) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Couldn't Get Interfaces!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + return nullptr; + } + + char RenderModelName[vr::k_unMaxPropertyStringSize]; + FMemory::Memzero(&RenderModelName, vr::k_unMaxPropertyStringSize); + + if (!RenderModelNameOverride.IsEmpty()) + { + int len = RenderModelNameOverride.Len(); + FMemory::Memcpy(&RenderModelName, TCHAR_TO_ANSI(*RenderModelNameOverride), len > vr::k_unMaxPropertyStringSize ? vr::k_unMaxPropertyStringSize : len); + RenderModelNameOut = RenderModelNameOverride; + } + else + { + int32 DeviceID = 0; + if (OverrideDeviceID != -1) + { + DeviceID = (uint32)OverrideDeviceID; + if (OverrideDeviceID > (vr::k_unMaxTrackedDeviceCount - 1) || VRSystem->GetTrackedDeviceClass(DeviceID) == vr::k_unTrackedDeviceIndexInvalid) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Override Tracked Device Was Missing!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + return nullptr; + } + } + else + { + TArray<int32> FoundIDs; + UOpenVRExpansionFunctionLibrary::GetOpenVRDevicesByType(DeviceType, FoundIDs); + + if (FoundIDs.Num() == 0) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Couldn't Get Tracked Devices!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + return nullptr; + } + + DeviceID = FoundIDs[0]; + } + + vr::TrackedPropertyError pError = vr::TrackedPropertyError::TrackedProp_Success; + + uint32_t buffersize = vr::k_unMaxPropertyStringSize; + uint32_t ret = VRSystem->GetStringTrackedDeviceProperty(DeviceID, vr::ETrackedDeviceProperty::Prop_RenderModelName_String, RenderModelName, buffersize, &pError); + + if (pError != vr::TrackedPropertyError::TrackedProp_Success) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Couldn't Get Render Model Name String!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + return nullptr; + + } + + RenderModelNameOut = FString(ANSI_TO_TCHAR(RenderModelName)); + } + + //uint32_t numComponents = VRRenderModels->GetComponentCount("vr_controller_vive_1_5"); + //UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("NumComponents: %i"), (int32)numComponents); + // if numComponents > 0 load each, otherwise load the main one only + + vr::RenderModel_t *RenderModel = NULL; + + //VRRenderModels->LoadRenderModel() + vr::EVRRenderModelError ModelErrorCode = VRRenderModels->LoadRenderModel_Async(RenderModelName, &RenderModel); + + if (ModelErrorCode != vr::EVRRenderModelError::VRRenderModelError_None) + { + if (ModelErrorCode != vr::EVRRenderModelError::VRRenderModelError_Loading) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Couldn't Load Model!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + } + else + Result = EAsyncBlueprintResultSwitch::AsyncLoading; + + return nullptr; + } + + if (!RenderModel) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Couldn't Load Model!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + return nullptr; + } + + vr::TextureID_t texID = RenderModel->diffuseTextureId; + vr::RenderModel_TextureMap_t * texture = NULL; + UTexture2D* OutTexture = nullptr; + + if (texID != vr::INVALID_TEXTURE_ID) + { + //UTexture2DDynamic * OutTexture = nullptr; + vr::EVRRenderModelError TextureErrorCode = VRRenderModels->LoadTexture_Async(texID, &texture); + + if (TextureErrorCode != vr::EVRRenderModelError::VRRenderModelError_None) + { + if (TextureErrorCode != vr::EVRRenderModelError::VRRenderModelError_Loading) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Couldn't Load Texture!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + } + else + Result = EAsyncBlueprintResultSwitch::AsyncLoading; + + return nullptr; + } + + if (!texture) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Couldn't Load Texture!!")); + Result = EAsyncBlueprintResultSwitch::OnFailure; + return nullptr; + } + } + + if (ProceduralMeshComponentsToFill.Num() > 0) + { + TArray<FVector> vertices; + TArray<int32> triangles; + TArray<FVector> normals; + TArray<FVector2D> UV0; + TArray<FColor> vertexColors; + TArray<FProcMeshTangent> tangents; + + vr::HmdVector3_t vPosition; + vr::HmdVector3_t vNormal; + + vertices.Reserve(RenderModel->unVertexCount); + normals.Reserve(RenderModel->unVertexCount); + UV0.Reserve(RenderModel->unVertexCount); + + for (uint32_t i = 0; i < RenderModel->unVertexCount; ++i) + { + vPosition = RenderModel->rVertexData[i].vPosition; + // OpenVR y+ Up, +x Right, -z Going away + // UE4 z+ up, +y right, +x forward + + vertices.Add(FVector(-vPosition.v[2], vPosition.v[0], vPosition.v[1])); + + vNormal = RenderModel->rVertexData[i].vNormal; + + normals.Add(FVector(-vNormal.v[2], vNormal.v[0], vNormal.v[1])); + + UV0.Add(FVector2D(RenderModel->rVertexData[i].rfTextureCoord[0], RenderModel->rVertexData[i].rfTextureCoord[1])); + } + + triangles.Reserve(RenderModel->unTriangleCount); + for (uint32_t i = 0; i < RenderModel->unTriangleCount * 3; i += 3) + { + triangles.Add(RenderModel->rIndexData[i]); + triangles.Add(RenderModel->rIndexData[i + 1]); + triangles.Add(RenderModel->rIndexData[i + 2]); + } + + float scale = UHeadMountedDisplayFunctionLibrary::GetWorldToMetersScale(WorldContextObject); + for (int i = 0; i < ProceduralMeshComponentsToFill.Num(); ++i) + { + ProceduralMeshComponentsToFill[i]->ClearAllMeshSections(); + ProceduralMeshComponentsToFill[i]->CreateMeshSection(0, vertices, triangles, normals, UV0, vertexColors, tangents, bCreateCollision); + ProceduralMeshComponentsToFill[i]->SetMeshSectionVisible(0, true); + ProceduralMeshComponentsToFill[i]->SetWorldScale3D(FVector(scale, scale, scale)); + } + } + + if (texture != nullptr) + { + uint32 Width = texture->unWidth; + uint32 Height = texture->unHeight; + + + //OutTexture = UTexture2DDynamic::Create(Width, Height, PF_R8G8B8A8); + OutTexture = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8); + + + //FTexture2DDynamicResource * TexResource = static_cast<FTexture2DDynamicResource*>(OutTexture->Resource); + //FTexture2DRHIParamRef TextureRHI = TexResource->GetTexture2DRHI(); + + //uint32 DestStride = 0; + //uint8* MipData = reinterpret_cast<uint8*>(RHILockTexture2D(TextureRHI, 0, RLM_WriteOnly, DestStride, false, false)); + + if (FTexturePlatformData* PlatformData = OutTexture->GetPlatformData()) + { + uint8* MipData = (uint8*)PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(MipData, (void*)texture->rubTextureMapData, Height * Width * 4); + PlatformData->Mips[0].BulkData.Unlock(); + //RHIUnlockTexture2D(TextureRHI, 0, false, false); + + + //Setting some Parameters for the Texture and finally returning it + PlatformData->SetNumSlices(1); + OutTexture->NeverStream = true; + OutTexture->UpdateResource(); + } + + /*if (bReturnRawTexture) + { + OutRawTexture.AddUninitialized(Height * Width * 4); + FMemory::Memcpy(OutRawTexture.GetData(), (void*)texture->rubTextureMapData, Height * Width * 4); + }*/ + + Result = EAsyncBlueprintResultSwitch::OnSuccess; + VRRenderModels->FreeTexture(texture); + } + + VRRenderModels->FreeRenderModel(RenderModel); + return OutTexture; +#endif +} + + +bool UOpenVRExpansionFunctionLibrary::SetSkyboxOverride_LatLongStereoPair(UTexture2D * LatLongSkyboxL, UTexture2D * LatLongSkyboxR) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + + if (!LatLongSkyboxL || !LatLongSkyboxR) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Bad texture passed in to SetSkyBoxOverride")); + return false; + } + + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + vr::Texture_t TextureArray[2]; + + TextureArray[0] = CreateOpenVRTexture_t(LatLongSkyboxL); + TextureArray[1] = CreateOpenVRTexture_t(LatLongSkyboxR); + + vr::EVRCompositorError CompositorError; + CompositorError = VRCompositor->SetSkyboxOverride(TextureArray, 2); + + if (CompositorError != vr::VRCompositorError_None) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor Error %i"), (int32)CompositorError); + return false; + } + + return true; + +#endif +} + +bool UOpenVRExpansionFunctionLibrary::SetSkyboxOverride_LatLong(UTexture2D * LatLongSkybox) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + if (!LatLongSkybox) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Bad texture passed in to SetSkyBoxOverride")); + return false; + } + + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + vr::Texture_t Texture; + + Texture = CreateOpenVRTexture_t(LatLongSkybox); + + vr::EVRCompositorError CompositorError; + CompositorError = VRCompositor->SetSkyboxOverride(&Texture, 1); + + if (CompositorError != vr::VRCompositorError_None) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor Error %i"), (int32)CompositorError); + return false; + } + + return true; +#endif +} + + +bool UOpenVRExpansionFunctionLibrary::SetSkyboxOverride(UTexture * tFront, UTexture2D * tBack, UTexture * tLeft, UTexture * tRight, UTexture * tTop, UTexture * tBottom) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + if (!tFront || !tBack || !tLeft || !tRight || !tTop || !tBottom) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Bad texture passed in to SetSkyBoxOverride")); + return false; + } + + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + vr::Texture_t TextureArray[6]; + + TextureArray[0] = CreateOpenVRTexture_t(tFront); + TextureArray[1] = CreateOpenVRTexture_t(tBack); + TextureArray[2] = CreateOpenVRTexture_t(tLeft); + TextureArray[3] = CreateOpenVRTexture_t(tRight); + TextureArray[4] = CreateOpenVRTexture_t(tTop); + TextureArray[5] = CreateOpenVRTexture_t(tBottom); + + vr::EVRCompositorError CompositorError; + CompositorError = VRCompositor->SetSkyboxOverride(TextureArray, 6); + + if (CompositorError != vr::VRCompositorError_None) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor Error %i"), (int32)CompositorError); + return false; + } + + return true; +#endif +} + +bool UOpenVRExpansionFunctionLibrary::ClearSkyboxOverride() +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + VRCompositor->ClearSkyboxOverride(); + + return true; + +#endif +} + +bool UOpenVRExpansionFunctionLibrary::FadeHMDToColor(float fSeconds, FColor Color, bool bBackground) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + VRCompositor->FadeToColor(fSeconds, Color.R, Color.G, Color.B, Color.A, bBackground); + + return true; + +#endif +} + +bool UOpenVRExpansionFunctionLibrary::GetCurrentHMDFadeColor(FColor & ColorOut, bool bBackground) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + vr::HmdColor_t HMDColor = VRCompositor->GetCurrentFadeColor(bBackground); + + ColorOut = FColor(HMDColor.r, HMDColor.g, HMDColor.b, HMDColor.a); + return true; + +#endif +} + +bool UOpenVRExpansionFunctionLibrary::FadeVRGrid(float fSeconds, bool bFadeIn) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + VRCompositor->FadeGrid(fSeconds, bFadeIn); + return true; + +#endif +} + +bool UOpenVRExpansionFunctionLibrary::GetCurrentVRGridAlpha(float & VRGridAlpha) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + VRGridAlpha = VRCompositor->GetCurrentGridAlpha(); + return true; + +#endif +} + +bool UOpenVRExpansionFunctionLibrary::SetSuspendRendering(bool bSuspendRendering) +{ +#if !STEAMVR_SUPPORTED_PLATFORM + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Not SteamVR Supported Platform!!")); + return false; +#else + vr::IVRCompositor* VRCompositor = vr::VRCompositor(); + + if (!VRCompositor) + { + UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("VR Compositor InterfaceErrored")); + return false; + } + + VRCompositor->SuspendRendering(bSuspendRendering); + return true; + +#endif +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPlugin.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPlugin.cpp new file mode 100644 index 0000000..dc8db36 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPlugin.cpp @@ -0,0 +1,101 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "OpenVRExpansionPlugin.h" +#include "OpenVRExpansionFunctionLibrary.h" + +#define LOCTEXT_NAMESPACE "FVRExpansionPluginModule" + +void FOpenVRExpansionPluginModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + //LoadOpenVRModule(); +} + +void FOpenVRExpansionPluginModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +// UnloadOpenVRModule(); +} + +/*bool FOpenVRExpansionPluginModule::LoadOpenVRModule() +{ +#if !STEAMVR_SUPPORTED_PLATFORM + return false; +#else +#if PLATFORM_WINDOWS +#if PLATFORM_64BITS + + FString RootOpenVRPath; + TCHAR VROverridePath[MAX_PATH]; + FPlatformMisc::GetEnvironmentVariable(TEXT("VR_OVERRIDE"), VROverridePath, MAX_PATH); + + if (FCString::Strlen(VROverridePath) > 0) + { + RootOpenVRPath = FString::Printf(TEXT("%s\\bin\\win64\\"), VROverridePath); + } + else + { + RootOpenVRPath = FPaths::EngineDir() / FString::Printf(TEXT("Binaries/ThirdParty/OpenVR/%s/Win64/"), OPENVR_SDK_VER); + } + + FPlatformProcess::PushDllDirectory(*RootOpenVRPath); + OpenVRDLLHandle = FPlatformProcess::GetDllHandle(*(RootOpenVRPath + "openvr_api.dll")); + FPlatformProcess::PopDllDirectory(*RootOpenVRPath); +#else + FString RootOpenVRPath = FPaths::EngineDir() / FString::Printf(TEXT("Binaries/ThirdParty/OpenVR/%s/Win32/"), OPENVR_SDK_VER); + FPlatformProcess::PushDllDirectory(*RootOpenVRPath); + OpenVRDLLHandle = FPlatformProcess::GetDllHandle(*(RootOpenVRPath + "openvr_api.dll")); + FPlatformProcess::PopDllDirectory(*RootOpenVRPath); +#endif +#elif PLATFORM_MAC + OpenVRDLLHandle = FPlatformProcess::GetDllHandle(TEXT("libopenvr_api.dylib")); + +#elif PLATFORM_LINUX + FString RootOpenVRPath = FPaths::EngineDir() / FString::Printf(TEXT("Binaries/ThirdParty/OpenVR/%s/linux64/"), OPENVR_SDK_VER); + OpenVRDLLHandle = FPlatformProcess::GetDllHandle(*(RootOpenVRPath + "libopenvr_api.so")); +#else + #error "SteamVRHMD is not supported for this platform." +#endif //PLATFORM_WINDOWS + + if (!OpenVRDLLHandle) + { + //UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Failed to load OpenVR library.")); + return false; + } + + //@todo steamvr: Remove GetProcAddress() workaround once we update to Steamworks 1.33 or higher + //VRInitFn = (pVRInit)FPlatformProcess::GetDllExport(OpenVRDLLHandle, TEXT("VR_Init")); + //VRShutdownFn = (pVRShutdown)FPlatformProcess::GetDllExport(OpenVRDLLHandle, TEXT("VR_Shutdown")); + //VRIsHmdPresentFn = (pVRIsHmdPresent)FPlatformProcess::GetDllExport(OpenVRDLLHandle, TEXT("VR_IsHmdPresent")); + //VRGetStringForHmdErrorFn = (pVRGetStringForHmdError)FPlatformProcess::GetDllExport(OpenVRDLLHandle, TEXT("VR_GetStringForHmdError")); + UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn = (pVRGetGenericInterface)FPlatformProcess::GetDllExport(OpenVRDLLHandle, TEXT("VR_GetGenericInterface")); + + if (!UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn) + { + //UE_LOG(OpenVRExpansionFunctionLibraryLog, Warning, TEXT("Failed to GetProcAddress() on openvr_api.dll")); + UnloadOpenVRModule(); + return false; + } + + return true; +#endif +}*/ + +/*void FOpenVRExpansionPluginModule::UnloadOpenVRModule() +{ +#if STEAMVR_SUPPORTED_PLATFORM + if (OpenVRDLLHandle != nullptr) + { + FPlatformProcess::FreeDllHandle(OpenVRDLLHandle); + OpenVRDLLHandle = nullptr; + //(*VRShutdownFn)(); + } + + UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn = nullptr; +#endif +}*/ + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FOpenVRExpansionPluginModule, OpenVRExpansionPlugin) \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPluginPrivatePCH.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPluginPrivatePCH.h new file mode 100644 index 0000000..8bd46ff --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/OpenVRExpansionPluginPrivatePCH.h @@ -0,0 +1,9 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +//#include "OpenVRExpansionPlugin.h" +//#include "OpenVRExpansionFunctionLibrary.h" +//#include "GripSteamVRTrackedDevice.h" +//#include "Runtime/Launch/Resources/Version.h" + +// You should place include statements to your module's private header files here. You only need to +// add includes for headers that are used in most of your module's source files though. \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/SteamVRKeyboardComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/SteamVRKeyboardComponent.cpp new file mode 100644 index 0000000..5f09f6c --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Private/SteamVRKeyboardComponent.cpp @@ -0,0 +1,170 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "SteamVRKeyboardComponent.h" +//#include "Engine/Engine.h" +#include "OpenVRExpansionFunctionLibrary.h" +#include "HeadMountedDisplayFunctionLibrary.h" +#include "GameFramework/PlayerController.h" +#include "Engine/LocalPlayer.h" +//#include "GripMotionControllerComponent.h" + + +//============================================================================= +USteamVRKeyboardComponent::USteamVRKeyboardComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + PrimaryComponentTick.bStartWithTickEnabled = false; +} + +//============================================================================= +USteamVRKeyboardComponent::~USteamVRKeyboardComponent() +{ +} + +void USteamVRKeyboardComponent::OnUnregister() +{ +#if STEAMVR_SUPPORTED_PLATFORM + if (KeyboardHandle.IsValid()) + { + EBPOVRResultSwitch Result; + CloseVRKeyboard(Result); + } +#endif + + Super::OnUnregister(); +} + + +void USteamVRKeyboardComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ +Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + +#if !STEAMVR_SUPPORTED_PLATFORM +return; +#else + if (/*!UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn ||*/ !KeyboardHandle.IsValid()) + { + return; + } + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + return; + } + + vr::IVROverlay* VROverlay = vr::VROverlay(); + + + vr::IVRInput* VRInput = vr::VRInput(); + + if (!VROverlay) + { + return; + } + + FTransform PlayerTransform = FTransform::Identity; + + // Get first local player controller + /*APlayerController* PC = nullptr; + for (FConstPlayerControllerIterator Iterator = GetWorld()->GetPlayerControllerIterator(); Iterator; ++Iterator) + { + if (Iterator->Get()->IsLocalPlayerController()) + { + PC = Iterator->Get(); + break; + } + }*/ + APlayerController* PC = nullptr; + if (UWorld * CurWorld = GetWorld()) + { + const ULocalPlayer* FirstPlayer = GEngine->GetFirstGamePlayer(CurWorld); + PC = FirstPlayer ? FirstPlayer->GetPlayerController(CurWorld) : nullptr; + } + + if (PC) + { + APawn * mpawn = PC->GetPawnOrSpectator(); + //bTextureNeedsUpdate = true; + if (mpawn) + { + // Set transform to this relative transform + PlayerTransform = mpawn->GetTransform(); + } + } + + float WorldToMetersScale = UHeadMountedDisplayFunctionLibrary::GetWorldToMetersScale(GetWorld()); + + // HMD Matrix + FTransform RelTransform = this->GetComponentTransform(); + RelTransform = RelTransform.GetRelativeTransform(PlayerTransform); + + FQuat Rot = RelTransform.GetRotation(); + RelTransform.SetRotation(FQuat(Rot.Y, Rot.Z, -Rot.X, -Rot.W)); + + FVector pos = RelTransform.GetTranslation(); + RelTransform.SetTranslation(FVector(pos.Y, pos.Z, -pos.X) / WorldToMetersScale); + + FVector scale = RelTransform.GetScale3D(); + RelTransform.SetScale3D(FVector(scale.Y, scale.Z, scale.X) / WorldToMetersScale); + + vr::HmdMatrix34_t NewTransform = UOpenVRExpansionFunctionLibrary::ToHmdMatrix34(RelTransform.ToMatrixNoScale()); + VROverlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &NewTransform); + + // Poll SteamVR events + vr::VREvent_t VREvent; + + while (KeyboardHandle.IsValid() && VROverlay->PollNextOverlayEvent(KeyboardHandle.VRKeyboardHandle, &VREvent, sizeof(VREvent))) + { + + //VRKeyboardEvent_None = 0, + //VRKeyboardEvent_OverlayFocusChanged = 307, // data is overlay, global event + //VRKeyboardEvent_OverlayShown = 500, + //VRKeyboardEvent_OverlayHidden = 501, + //VRKeyboardEvent_ShowKeyboard = 509, // Sent to keyboard renderer in the dashboard to invoke it + //VRKeyboardEvent_HideKeyboard = 510, // Sent to keyboard renderer in the dashboard to hide it + //VRKeyboardEvent_OverlayGamepadFocusGained = 511, // Sent to an overlay when IVROverlay::SetFocusOverlay is called on it + //VRKeyboardEvent_OverlayGamepadFocusLost = 512, // Send to an overlay when it previously had focus and IVROverlay::SetFocusOverlay is called on something else + //VRKeyboardEvent_OverlaySharedTextureChanged = 513, + //VRKeyboardEvent_KeyboardClosed = 1200, + //VRKeyboardEvent_KeyboardCharInput = 1201, + //VRKeyboardEvent_KeyboardDone = 1202, // Sent when DONE button clicked on keyboard + + switch (VREvent.eventType) + { + case vr::VREvent_KeyboardCharInput: + { + char OutString[512]; + uint32 TextLen = VROverlay->GetKeyboardText((char*)&OutString, 512); + OnKeyboardCharInput.Broadcast(FString(ANSI_TO_TCHAR(OutString))); + }break; + case vr::VREvent_KeyboardClosed: + { + if (KeyboardHandle.IsValid()) + { + VROverlay->DestroyOverlay(KeyboardHandle.VRKeyboardHandle); + KeyboardHandle.VRKeyboardHandle = vr::k_ulOverlayHandleInvalid; + } + OnKeyboardClosed.Broadcast(); + }break; + case vr::VREvent_KeyboardDone: + { + char OutString[512]; + uint32 TextLen = VROverlay->GetKeyboardText((char*)&OutString, 512); + OnKeyboardDone.Broadcast(FString(ANSI_TO_TCHAR(OutString))); + + if (KeyboardHandle.IsValid()) + { + VROverlay->HideKeyboard(); + VROverlay->DestroyOverlay(KeyboardHandle.VRKeyboardHandle); + KeyboardHandle.VRKeyboardHandle = vr::k_ulOverlayHandleInvalid; + } + }break; + + default:break; + } + } + +#endif +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionFunctionLibrary.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionFunctionLibrary.h new file mode 100644 index 0000000..094829a --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionFunctionLibrary.h @@ -0,0 +1,743 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +class UTexture; + +//Re-defined here as I can't load ISteamVRPlugin on non windows platforms +// Make sure to update if it changes + +// #TODO: 4.19 check this +#define STEAMVR_SUPPORTED_PLATFORM (PLATFORM_MAC || (PLATFORM_LINUX && PLATFORM_CPU_X86_FAMILY && PLATFORM_64BITS) || (PLATFORM_WINDOWS && WINVER > 0x0502)) + +// #TODO: Check this over time for when they make it global +// @TODO: hardcoded to match FSteamVRHMD::GetSystemName(), which we should turn into +static FName SteamVRSystemName(TEXT("SteamVR")); + + +#if STEAMVR_SUPPORTED_PLATFORM +#include "openvr.h" +//#include "ISteamVRPlugin.h" +//#include "SteamVRFunctionLibrary.h" + +#endif // STEAMVR_SUPPORTED_PLATFORM + +#include "OpenVRExpansionFunctionLibrary.generated.h" + +class UProceduralMeshComponent; + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(OpenVRExpansionFunctionLibraryLog, Log, All); + + +UENUM() +enum class EBPOpenVRTrackedDeviceClass : uint8 +{ + // #TODO: Keep up to date + //enum ETrackedDeviceClass - copied from valves enum +TrackedDeviceClass_Invalid = 0, // the ID was not valid. +TrackedDeviceClass_HMD = 1, // Head-Mounted Displays +TrackedDeviceClass_Controller = 2, // Tracked controllers +TrackedDeviceClass_GenericTracker = 3, // Generic trackers, similar to controllers +TrackedDeviceClass_TrackingReference = 4, // Camera and base stations that serve as tracking reference points +TrackedDeviceClass_DisplayRedirect = 5, // Accessories that aren't necessarily tracked themselves, but may redirect video output from other tracked devices +}; + +// This makes a lot of the blueprint functions cleaner +/*UENUM() +enum class EBPVRDeviceIndex : uint8 +{ + // On Success + HMD = 0, + FirstTracking_Reference = 1, + SecondTracking_Reference = 2, + FirstController = 3, + SecondController = 4, + TrackedDevice1 = 5, + TrackedDevice2 = 6, + TrackedDevice3 = 7, + TrackedDevice4 = 8, + TrackedDevice5 = 9, + TrackedDevice6 = 10, + TrackedDevice7 = 11, + TrackedDevice8 = 12, + TrackedDevice9 = 13, + TrackedDevice10 = 14, + TrackedDevice11 = 15, + None = 255 +};*/ + +// This makes a lot of the blueprint functions cleaner +UENUM() +enum class EBPOVRResultSwitch : uint8 +{ + // On Success + OnSucceeded, + // On Failure + OnFailed +}; + +//USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR") +struct OPENVREXPANSIONPLUGIN_API FBPOpenVRKeyboardHandle +{ + //GENERATED_BODY() +public: + + uint64_t VRKeyboardHandle; + + FBPOpenVRKeyboardHandle() + { +#if STEAMVR_SUPPORTED_PLATFORM + //static const VROverlayHandle_t k_ulOverlayHandleInvalid = 0; + VRKeyboardHandle = vr::k_ulOverlayHandleInvalid; +#endif + } + const bool IsValid() + { +#if STEAMVR_SUPPORTED_PLATFORM + return VRKeyboardHandle != vr::k_ulOverlayHandleInvalid; +#else + return false; +#endif + } + + //This is here for the Find() and Remove() functions from TArray + FORCEINLINE bool operator==(const FBPOpenVRKeyboardHandle &Other) const + { + if (VRKeyboardHandle == Other.VRKeyboardHandle) + return true; + + return false; + } + //#define INVALID_TRACKED_CAMERA_HANDLE +}; + + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|VRCamera") +struct OPENVREXPANSIONPLUGIN_API FBPOpenVRCameraHandle +{ + GENERATED_BODY() +public: + + uint64_t pCameraHandle; + + FBPOpenVRCameraHandle() + { +#if STEAMVR_SUPPORTED_PLATFORM + pCameraHandle = INVALID_TRACKED_CAMERA_HANDLE; +#endif + } + const bool IsValid() + { +#if STEAMVR_SUPPORTED_PLATFORM + return pCameraHandle != INVALID_TRACKED_CAMERA_HANDLE; +#else + return false; +#endif + } + + //This is here for the Find() and Remove() functions from TArray + FORCEINLINE bool operator==(const FBPOpenVRCameraHandle &Other) const + { + if (pCameraHandle == Other.pCameraHandle) + return true; + + return false; + } + //#define INVALID_TRACKED_CAMERA_HANDLE +}; + +UENUM(BlueprintType) +enum class EOpenVRCameraFrameType : uint8 +{ + VRFrameType_Distorted = 0, + VRFrameType_Undistorted, + VRFrameType_MaximumUndistorted +}; + +// This will make using the load model as async easier to understand +UENUM() +enum class EAsyncBlueprintResultSwitch : uint8 +{ + // On Success + OnSuccess, + // On still loading async + AsyncLoading, + // On Failure + OnFailure +}; + +// Redefined here so that non windows packages can compile +/** Defines the class of tracked devices in SteamVR*/ +// #TODO: Update these +UENUM(BlueprintType) +enum class EBPSteamVRTrackedDeviceType : uint8 +{ + /** Represents a Steam VR Controller */ + Controller, + + /** Represents a static tracking reference device, such as a Lighthouse or tracking camera */ + TrackingReference, + + /** Misc. device types, for future expansion */ + Other, + + /** DeviceId is invalid */ + Invalid +}; + +#if STEAMVR_SUPPORTED_PLATFORM +static vr::ETrackedDeviceProperty VREnumToString(const FString& enumName, uint8 value) +{ + const UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, *enumName, true); + + if (!EnumPtr) + return vr::ETrackedDeviceProperty::Prop_Invalid; + + FString EnumName = EnumPtr->GetNameStringByIndex(value).Right(4); + + if (EnumName.IsEmpty() || EnumName.Len() < 4) + return vr::ETrackedDeviceProperty::Prop_Invalid; + + return static_cast<vr::ETrackedDeviceProperty>(FCString::Atoi(*EnumName)); +} +#endif + + +/* +// Not implementing currently + +// Properties that are unique to TrackedDeviceClass_HMD +Prop_DisplayMCImageData_Binary = 2041, + +Prop_IconPathName_String = 5000, // DEPRECATED. Value not referenced. Now expected to be part of icon path properties. + + +// Not implemented because very little use, and names are huuggge..... +Prop_NamedIconPathControllerLeftDeviceOff_String_2051 +Prop_NamedIconPAthControllerRightDeviceOff_String_2052 +Prop_NamedIconPathTrackingReferenceDeviceOff_String_2053 + + +// Properties that are used by helpers, but are opaque to applications +Prop_DisplayHiddenArea_Binary_Start = 5100, +Prop_DisplayHiddenArea_Binary_End = 5150, +Prop_ParentContainer = 5151 + +// Vendors are free to expose private debug data in this reserved region +Prop_VendorSpecific_Reserved_Start = 10000, +Prop_VendorSpecific_Reserved_End = 10999, + +Prop_ImuFactoryGyroBias_Vector3 = 2064, +Prop_ImuFactoryGyroScale_Vector3 = 2065, +Prop_ImuFactoryAccelerometerBias_Vector3 = 2066, +Prop_ImuFactoryAccelerometerScale_Vector3 = 2067, +// reserved 2068 + +// Driver requested mura correction properties +Prop_DriverRequestedMuraCorrectionMode_Int32 = 2200, +Prop_DriverRequestedMuraFeather_InnerLeft_Int32 = 2201, +Prop_DriverRequestedMuraFeather_InnerRight_Int32 = 2202, +Prop_DriverRequestedMuraFeather_InnerTop_Int32 = 2203, +Prop_DriverRequestedMuraFeather_InnerBottom_Int32 = 2204, +Prop_DriverRequestedMuraFeather_OuterLeft_Int32 = 2205, +Prop_DriverRequestedMuraFeather_OuterRight_Int32 = 2206, +Prop_DriverRequestedMuraFeather_OuterTop_Int32 = 2207, +Prop_DriverRequestedMuraFeather_OuterBottom_Int32 = 2208, + +Prop_TrackedDeviceProperty_Max = 1000000, + + +Prop_CameraWhiteBalance_Vector4_Array = 2071, // Prop_NumCameras_Int32-sized array of float[4] RGBG white balance calibration data (max size is vr::k_unMaxCameras) +Prop_CameraDistortionFunction_Int32_Array = 2072, // Prop_NumCameras_Int32-sized array of vr::EVRDistortionFunctionType values (max size is vr::k_unMaxCameras) +Prop_CameraDistortionCoefficients_Float_Array = 2073, // Prop_NumCameras_Int32-sized array of double[vr::k_unMaxDistortionFunctionParameters] (max size is vr::k_unMaxCameras) +Prop_DisplayAvailableFrameRates_Float_Array = 2080, // populated by compositor from actual EDID list when available from GPU driver +*/ + + +// #TODO: Update these +UENUM(BlueprintType) +enum class EVRDeviceProperty_String : uint8 +{ + + // No prefix = 1000 series + Prop_TrackingSystemName_String_1000 UMETA(DisplayName = "Prop_TrackingSystemName_String"), + Prop_ModelNumber_String_1001 UMETA(DisplayName = "Prop_ModelNumber_String"), + Prop_SerialNumber_String_1002 UMETA(DisplayName = "Prop_SerialNumber_String"), + Prop_RenderModelName_String_1003 UMETA(DisplayName = "Prop_RenderModelName_String"), + Prop_ManufacturerName_String_1005 UMETA(DisplayName = "Prop_ManufacturerName_String"), + Prop_TrackingFirmwareVersion_String_1006 UMETA(DisplayName = "Prop_TrackingFirmwareVersion_String"), + Prop_HardwareRevision_String_1007 UMETA(DisplayName = "Prop_HardwareRevision_String"), + Prop_AllWirelessDongleDescriptions_String_1008 UMETA(DisplayName = "Prop_AllWirelessDongleDescriptions_String"), + Prop_ConnectedWirelessDongle_String_1009 UMETA(DisplayName = "Prop_ConnectedWirelessDongle_String"), + Prop_Firmware_ManualUpdateURL_String_1016 UMETA(DisplayName = "Prop_Firmware_ManualUpdateURL_String"), + Prop_Firmware_ProgrammingTarget_String_1028 UMETA(DisplayName = "Prop_Firmware_ProgrammingTarget_String"), + Prop_DriverVersion_String_1031 UMETA(DisplayName = "Prop_DriverVersion_String"), + Prop_ResourceRoot_String_1035 UMETA(DisplayName = "Prop_ResourceRoot_String"), + Prop_RegisteredDeviceType_String_1036 UMETA(DisplayName = "Prop_RegisteredDeviceType_String"), + Prop_InputProfileName_String_1037 UMETA(DisplayName = "Prop_InputProfileName_String"), + + Prop_AdditionalDeviceSettingsPath_String_1042 UMETA(DisplayName = "Prop_AdditionalDeviceSettingsPath_String"), + Prop_AdditionalSystemReportData_String_1045 UMETA(DisplayName = "Prop_AdditionalSystemReportData_String"), + Prop_CompositeFirmwareVersion_String_1046 UMETA(DisplayName = "Prop_CompositeFirmwareVersion_String"), + + // 1 prefix = 2000 series + HMDProp_DisplayMCImageLeft_String_2012 UMETA(DisplayName = "HMDProp_DisplayMCImageLeft_String"), + HMDProp_DisplayMCImageRight_String_2013 UMETA(DisplayName = "HMDProp_DisplayMCImageRight_String"), + HMDProp_DisplayGCImage_String_2021 UMETA(DisplayName = "HMDProp_DisplayGCImage_String"), + HMDProp_CameraFirmwareDescription_String_2028 UMETA(DisplayName = "HMDProp_CameraFirmwareDescription_String"), + HMDProp_DriverProvidedChaperonePath_String_2048 UMETA(DisplayName = "HMDProp_DriverProvidedChaperonePath_String"), + + HMDProp_ExpectedControllerType_String_2074 UMETA(DisplayName = "HMDProp_ExpectedControllerType_String"), + HMDProp_DashboardLayoutPathName_String_2090 UMETA(DisplayName = "HMDProp_DashboardLayoutPathName_String"), + + // 2 prefix = 3000 series + ControllerProp_AttachedDeviceId_String_3000 UMETA(DisplayName = "ControllerProp_AttachedDeviceId_String"), + + // 3 prefix = 4000 series + TrackRefProp_ModeLabel_String_4006 UMETA(DisplayName = "TrackRefProp_ModeLabel_String"), + + // 4 prefix = 5000 series + UIProp_NamedIconPathDeviceOff_String_5001 UMETA(DisplayName = "UIProp_NamedIconPathDeviceOff_String"), + UIProp_NamedIconPathDeviceSearching_String_5002 UMETA(DisplayName = "UIProp_NamedIconPathDeviceSearching_String"), + UIProp_NamedIconPathDeviceSearchingAlert_String_5003 UMETA(DisplayName = "UIProp_NamedIconPathDeviceSearchingAlert_String_"), + UIProp_NamedIconPathDeviceReady_String_5004 UMETA(DisplayName = "UIProp_NamedIconPathDeviceReady_String"), + UIProp_NamedIconPathDeviceReadyAlert_String_5005 UMETA(DisplayName = "UIProp_NamedIconPathDeviceReadyAlert_String"), + UIProp_NamedIconPathDeviceNotReady_String_5006 UMETA(DisplayName = "UIProp_NamedIconPathDeviceNotReady_String"), + UIProp_NamedIconPathDeviceStandby_String_5007 UMETA(DisplayName = "UIProp_NamedIconPathDeviceStandby_String"), + UIProp_NamedIconPathDeviceAlertLow_String_5008 UMETA(DisplayName = "UIProp_NamedIconPathDeviceAlertLow_String"), + + // 5 prefix = 6000 series + DriverProp_UserConfigPath_String_6000 UMETA(DisplayName = "DriverProp_UserConfigPath_String"), + DriverProp_InstallPath_String_6001 UMETA(DisplayName = "DriverProp_InstallPath_String"), + + // Properties that are set internally based on other information provided by drivers + DriverProp_ControllerType_String_7000 UMETA(DisplayName = "DriverProp_ControllerType_String"), + //DriveerProp_LegacyInputProfile_String_7001 UMETA(DisplayName = "DriveerProp_LegacyInputProfile_String") // Deprecated + +}; + +UENUM(BlueprintType) +enum class EVRDeviceProperty_Bool : uint8 +{ + // No prefix = 1000 series + Prop_WillDriftInYaw_Bool_1004 UMETA(DisplayName = "Prop_WillDriftInYaw_Bool"), + Prop_DeviceIsWireless_Bool_1010 UMETA(DisplayName = "Prop_DeviceIsWireless_Bool"), + Prop_DeviceIsCharging_Bool_1011 UMETA(DisplayName = "Prop_DeviceIsCharging_Bool"), + Prop_Firmware_UpdateAvailable_Bool_1014 UMETA(DisplayName = "Prop_Firmware_UpdateAvailable_Bool"), + Prop_Firmware_ManualUpdate_Bool_1015 UMETA(DisplayName = "Prop_Firmware_ManualUpdate_Bool"), + Prop_BlockServerShutdown_Bool_1023 UMETA(DisplayName = "Prop_BlockServerShutdown_Bool"), + Prop_CanUnifyCoordinateSystemWithHmd_Bool_1024 UMETA(DisplayName = "Prop_CanUnifyCoordinateSystemWithHmd_Bool"), + Prop_ContainsProximitySensor_Bool_1025 UMETA(DisplayName = "Prop_ContainsProximitySensor_Bool"), + Prop_DeviceProvidesBatteryStatus_Bool_1026 UMETA(DisplayName = "Prop_DeviceProvidesBatteryStatus_Bool"), + Prop_DeviceCanPowerOff_Bool_1027 UMETA(DisplayName = "Prop_DeviceCanPowerOff_Bool"), + Prop_HasCamera_Bool_1030 UMETA(DisplayName = "Prop_HasCamera_Bool"), + Prop_Firmware_ForceUpdateRequired_Bool_1032 UMETA(DisplayName = "Prop_Firmware_ForceUpdateRequired_Bool"), + Prop_ViveSystemButtonFixRequired_Bool_1033 UMETA(DisplayName = "Prop_ViveSystemButtonFixRequired_Bool"), + Prop_NeverTracked_Bool_1038 UMETA(DisplayName = "Prop_NeverTracked_Bool"), + Prop_Identifiable_Bool_1043 UMETA(DisplayName = "Prop_Identifiable_Bool"), + Prop_Firmware_RemindUpdate_Bool_1047 UMETA(DisplayName = "Prop_Firmware_RemindUpdate_Bool"), + + // 1 prefix = 2000 series + HMDProp_ReportsTimeSinceVSync_Bool_2000 UMETA(DisplayName = "HMDProp_ReportsTimeSinceVSync_Bool"), + HMDProp_IsOnDesktop_Bool_2007 UMETA(DisplayName = "HMDProp_IsOnDesktop_Bool"), + HMDProp_DisplaySuppressed_Bool_2036 UMETA(DisplayName = "HMDProp_DisplaySuppressed_Bool"), + HMDProp_DisplayAllowNightMode_Bool_2037 UMETA(DisplayName = "HMDProp_DisplayAllowNightMode_Bool"), + HMDProp_DriverDirectModeSendsVsyncEvents_Bool_2043 UMETA(DisplayName = "HMDProp_DriverDirectModeSendsVsyncEvents_Bool"), + HMDProp_DisplayDebugMode_Bool_2044 UMETA(DisplayName = "HMDProp_DisplayDebugMode_Bool"), + HMDProp_DoNotApplyPrediction_Bool_2054 UMETA(DisplayName = "HMDProp_DoNotApplyPrediction_Bool"), + + HMDProp_DriverIsDrawingControllers_Bool_2057 UMETA(DisplayName = "HMDProp_DriverIsDrawingControllers_Bool"), + HMDProp_DriverRequestsApplicationPause_Bool_2058 UMETA(DisplayName = "HMDProp_DriverRequestsApplicationPause_Bool"), + HMDProp_DriverRequestsReducedRendering_Bool_2059 UMETA(DisplayName = "HMDProp_DriverRequestsReducedRendering_Bool"), + HMDProp_ConfigurationIncludesLighthouse20Features_Bool_2069 UMETA(DisplayName = "HMDProp_ConfigurationIncludesLighthouse20Features_Bool"), + + HMDProp_DriverProvidedChaperoneVisibility_Bool_2076 UMETA(DisplayName = "HMDProp_DriverProvidedChaperoneVisibility_Bool"), + HMDProp_DisplaySupportsMultipleFramerates_Bool_2081 UMETA(DisplayName = "HMDProp_DisplaySupportsMultipleFramerates_Bool"), + + // Tracked devices + TrackRefProp_CanWirelessIdentify_Bool_4007 UMETA(DisplayName = "TrackRefProp_CanWirelessIdentify_Bool"), + + + // 5 prefix = 6000 series + DriverProp_HasDisplayComponent_Bool_6002 UMETA(DisplayName = "DriverProp_HasDisplayComponent_Bool"), + DriverProp_HasControllerComponent_Bool_6003 UMETA(DisplayName = "DriverProp_HasControllerComponent_Bool"), + DriverProp_HasCameraComponent_Bool_6004 UMETA(DisplayName = "DriverProp_HasCameraComponent_Bool"), + DriverProp_HasDriverDirectModeComponent_Bool_6005 UMETA(DisplayName = "DriverProp_HasDriverDirectModeComponent_Bool"), + DriverProp_HasVirtualDisplayComponent_Bool_6006 UMETA(DisplayName = "DriverProp_HasVirtualDisplayComponent_Bool"), + DriverProp_HasSpatialAnchorsSupport_Bool_6007 UMETA(DisplayName = "DriverProp_HasSpatialAnchorsSupport_Bool") + +}; + +UENUM(BlueprintType) +enum class EVRDeviceProperty_Float : uint8 +{ + // No Prefix = 1000 series + Prop_DeviceBatteryPercentage_Float_1012 UMETA(DisplayName = "Prop_DeviceBatteryPercentage_Float"), + + // 1 Prefix = 2000 series + HMDProp_SecondsFromVsyncToPhotons_Float_2001 UMETA(DisplayName = "HMDProp_SecondsFromVsyncToPhotons_Float"), + HMDProp_DisplayFrequency_Float_2002 UMETA(DisplayName = "HMDProp_DisplayFrequency_Float"), + HMDProp_UserIpdMeters_Float_2003 UMETA(DisplayName = "HMDProp_UserIpdMeters_Float"), + HMDProp_DisplayMCOffset_Float_2009 UMETA(DisplayName = "HMDProp_DisplayMCOffset_Float"), + HMDProp_DisplayMCScale_Float_2010 UMETA(DisplayName = "HMDProp_DisplayMCScale_Float"), + HMDProp_DisplayGCBlackClamp_Float_2014 UMETA(DisplayName = "HMDProp_DisplayGCBlackClamp_Float"), + HMDProp_DisplayGCOffset_Float_2018 UMETA(DisplayName = "HMDProp_DisplayGCOffset_Float"), + HMDProp_DisplayGCScale_Float_2019 UMETA(DisplayName = "HMDProp_DisplayGCScale_Float"), + HMDProp_DisplayGCPrescale_Float_2020 UMETA(DisplayName = "HMDProp_DisplayGCPrescale_Float"), + HMDProp_LensCenterLeftU_Float_2022 UMETA(DisplayName = "HMDProp_LensCenterLeftU_Float"), + HMDProp_LensCenterLeftV_Float_2023 UMETA(DisplayName = "HMDProp_LensCenterLeftV_Float"), + HMDProp_LensCenterRightU_Float_2024 UMETA(DisplayName = "HMDProp_LensCenterRightU_Float"), + HMDProp_LensCenterRightV_Float_2025 UMETA(DisplayName = "HMDProp_LensCenterRightV_Float"), + HMDProp_UserHeadToEyeDepthMeters_Float_2026 UMETA(DisplayName = "HMDProp_UserHeadToEyeDepthMeters_Float"), + HMDProp_ScreenshotHorizontalFieldOfViewDegrees_Float_2034 UMETA(DisplayName = "HMDProp_ScreenshotHorizontalFieldOfViewDegrees_Float"), + HMDProp_ScreenshotVerticalFieldOfViewDegrees_Float_2035 UMETA(DisplayName = "HMDProp_ScreenshotVerticalFieldOfViewDegrees_Float"), + HMDProp_SecondsFromPhotonsToVblank_Float_2042 UMETA(DisplayName = "HMDProp_SecondsFromPhotonsToVblank_Float"), + HMDProp_MinimumIpdStepMeters_Float_2060 UMETA(DisplayName = "HMDProp_MinimumIpdStepMeters_Float"), + + // 3 Prefix = 4000 series + TrackRefProp_FieldOfViewLeftDegrees_Float_4000 UMETA(DisplayName = "TrackRefProp_FieldOfViewLeftDegrees_Float"), + TrackRefProp_FieldOfViewRightDegrees_Float_4001 UMETA(DisplayName = "TrackRefProp_FieldOfViewRightDegrees_Float"), + TrackRefProp_FieldOfViewTopDegrees_Float_4002 UMETA(DisplayName = "TrackRefProp_FieldOfViewTopDegrees_Float"), + TrackRefProp_FieldOfViewBottomDegrees_Float_4003 UMETA(DisplayName = "TrackRefProp_FieldOfViewBottomDegrees_Float"), + TrackRefProp_TrackingRangeMinimumMeters_Float_4004 UMETA(DisplayName = "TrackRefProp_TrackingRangeMinimumMeters_Float"), + TrackRefProp_TrackingRangeMaximumMeters_Float_4005 UMETA(DisplayName = "TrackRefProp_TrackingRangeMaximumMeters_Float") +}; + +UENUM(BlueprintType) +enum class EVRDeviceProperty_Int32 : uint8 +{ + // No prefix = 1000 series + Prop_DeviceClass_Int32_1029 UMETA(DisplayName = "Prop_DeviceClass_Int32"), + Prop_NumCameras_Int32_1039 UMETA(DisplayName = "Prop_NumCameras_Int32"), + Prop_CameraFrameLayout_Int32_1040 UMETA(DisplayName = "Prop_CameraFrameLayout_Int32"), // EVRTrackedCameraFrameLayout value + Prop_CameraStreamFormat_Int32_1041 UMETA(DisplayName = "Prop_CameraStreamFormat_Int32"), + + // 1 Prefix = 2000 series + HMDProp_DisplayMCType_Int32_2008 UMETA(DisplayName = "HMDProp_DisplayMCType_Int32"), + HMDProp_EdidVendorID_Int32_2011 UMETA(DisplayName = "HMDProp_EdidVendorID_Int32"), + HMDProp_EdidProductID_Int32_2015 UMETA(DisplayName = "HMDProp_EdidProductID_Int32"), + HMDProp_DisplayGCType_Int32_2017 UMETA(DisplayName = "HMDProp_DisplayGCType_Int32"), + HMDProp_CameraCompatibilityMode_Int32_2033 UMETA(DisplayName = "HMDProp_CameraCompatibilityMode_Int32"), + HMDProp_DisplayMCImageWidth_Int32_2038 UMETA(DisplayName = "HMDProp_DisplayMCImageWidth_Int32"), + HMDProp_DisplayMCImageHeight_Int32_2039 UMETA(DisplayName = "HMDProp_DisplayMCImageHeight_Int32"), + HMDProp_DisplayMCImageNumChannels_Int32_2040 UMETA(DisplayName = "HMDProp_DisplayMCImageNumChannels_Int32"), + HMDProp_ExpectedTrackingReferenceCount_Int32_2049 UMETA(DisplayName = "HMDProp_ExpectedTrackingReferenceCount_Int32"), + HMDProp_ExpectedControllerCount_Int32_2050 UMETA(DisplayName = "HMDProp_ExpectedControllerCount_Int32"), + HMDProp_DistortionMeshResolution_Int32_2056 UMETA(DisplayName = "HMDProp_DistortionMeshResolution_Int32"), + + HMDProp_HmdTrackingStyle_Int32_2075 UMETA(DisplayName = "HMDProp_HmdTrackingStyle_Int32"), + + // 2 Prefix = 3000 series + ControllerProp_Axis0Type_Int32_3002 UMETA(DisplayName = "ControllerProp_Axis0Type_Int32"), + ControllerPropProp_Axis1Type_Int32_3003 UMETA(DisplayName = "ControllerPropProp_Axis1Type_Int32"), + ControllerPropProp_Axis2Type_Int32_3004 UMETA(DisplayName = "ControllerPropProp_Axis2Type_Int32"), + ControllerPropProp_Axis3Type_Int32_3005 UMETA(DisplayName = "ControllerPropProp_Axis3Type_Int32"), + ControllerPropProp_Axis4Type_Int32_3006 UMETA(DisplayName = "ControllerPropProp_Axis4Type_Int32"), + ControllerProp_ControllerRoleHint_Int32_3007 UMETA(DisplayName = "ControllerProp_ControllerRoleHint_Int32"), + + // Tracked device props + TrackRefProp_Nonce_Int32_4008 UMETA(DisplayName = "TrackRefProp_Nonce_Int32"), + + // Driver Props + DriverProp_ControllerHandSelectionPriority_Int32_7002 UMETA(DisplayName = "DriverProp_ControllerHandSelectionPriority_Int32") +}; + +UENUM(BlueprintType) +enum class EVRDeviceProperty_UInt64 : uint8 +{ + // No prefix = 1000 series + Prop_HardwareRevision_Uint64_1017 UMETA(DisplayName = "Prop_HardwareRevision_Uint64"), + Prop_FirmwareVersion_Uint64_1018 UMETA(DisplayName = "Prop_FirmwareVersion_Uint64"), + Prop_FPGAVersion_Uint64_1019 UMETA(DisplayName = "Prop_FPGAVersion_Uint64"), + Prop_VRCVersion_Uint64_1020 UMETA(DisplayName = "Prop_VRCVersion_Uint64"), + Prop_RadioVersion_Uint64_1021 UMETA(DisplayName = "Prop_RadioVersion_Uint64"), + Prop_DongleVersion_Uint64_1022 UMETA(DisplayName = "Prop_DongleVersion_Uint64"), + Prop_ParentDriver_Uint64_1034 UMETA(DisplayName = "Prop_ParentDriver_Uint64"), + Prop_BootloaderVersion_Uint64_1044 UMETA(DisplayName = "Prop_BootloaderVersion_Uint64"), + + // 1 Prefix = 2000 series + HMDProp_CurrentUniverseId_Uint64_2004 UMETA(DisplayName = "HMDProp_CurrentUniverseId_Uint64"), + HMDProp_PreviousUniverseId_Uint64_2005 UMETA(DisplayName = "HMDProp_PreviousUniverseId_Uint64"), + HMDProp_DisplayFirmwareVersion_Uint64_2006 UMETA(DisplayName = "HMDProp_DisplayFirmwareVersion_Uint64"), + HMDProp_CameraFirmwareVersion_Uint64_2027 UMETA(DisplayName = "HMDProp_CameraFirmwareVersion_Uint64"), + HMDProp_DisplayFPGAVersion_Uint64_2029 UMETA(DisplayName = "HMDProp_DisplayFPGAVersion_Uint64"), + HMDProp_DisplayBootloaderVersion_Uint64_2030 UMETA(DisplayName = "HMDProp_DisplayBootloaderVersion_Uint64"), + HMDProp_DisplayHardwareVersion_Uint64_2031 UMETA(DisplayName = "HMDProp_DisplayHardwareVersion_Uint64"), + HMDProp_AudioFirmwareVersion_Uint64_2032 UMETA(DisplayName = "HMDProp_AudioFirmwareVersion_Uint64"), + HMDProp_GraphicsAdapterLuid_Uint64_2045 UMETA(DisplayName = "HMDProp_GraphicsAdapterLuid_Uint64"), + HMDProp_AudioBridgeFirmwareVersion_Uint64_2061 UMETA(DisplayName = "HMDProp_AudioBridgeFirmwareVersion_Uint64"), + HMDProp_ImageBridgeFirmwareVersion_Uint64_2062 UMETA(DisplayName = "HMDProp_ImageBridgeFirmwareVersion_Uint64"), + HMDProp_AdditionalRadioFeatures_Uint64_2070 UMETA(DisplayName = "HMDProp_AdditionalRadioFeatures_Uint64"), + + // 2 Prefix = 3000 series + ControllerProp_SupportedButtons_Uint64_3001 UMETA(DisplayName = "ControllerProp_SupportedButtons_Uint64") +}; + +UENUM(BlueprintType) +enum class EVRDeviceProperty_Matrix34 : uint8 +{ + // No prefix = 1000 series + Prop_StatusDisplayTransform_Matrix34_1013 UMETA(DisplayName = "Prop_StatusDisplayTransform_Matrix34"), + + // 1 Prefix = 2000 series + HMDProp_CameraToHeadTransform_Matrix34_2016 UMETA(DisplayName = "HMDProp_CameraToHeadTransform_Matrix34"), + HMDProp_CameraToHeadTransforms_Matrix34_2055 UMETA(DisplayName = "HMDProp_CameraToHeadTransforms_Matrix34"), + HMDProp_ImuToHeadTransform_Matrix34_2063 UMETA(DisplayName = "HMDProp_ImToHeadTransform_Matrix34") +}; + +// This needs to be updated as the original gets changed, that or hope they make the original blueprint accessible. +UENUM(Blueprintable) +enum class EBPOpenVRHMDDeviceType : uint8 +{ + DT_SteamVR, + DT_ValveIndex, + DT_Vive, + DT_ViveCosmos, + DT_ViveFocus, + DT_ViveFocus3, + DT_OculusQuestHMD, + DT_OculusHMD, + DT_WindowsMR, + DT_PicoNeo3, + //DT_OSVR, + DT_Unknown +}; + +// This needs to be updated as the original gets changed, that or hope they make the original blueprint accessible. +UENUM(Blueprintable) +enum class EBPOpenVRControllerDeviceType : uint8 +{ + DT_IndexController, + DT_ViveController, + DT_CosmosController, + DT_RiftController, + DT_RiftSController, + DT_QuestController, + DT_WMRController, + DT_PicoNeo3Controller, + DT_UnknownController +}; + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) +class OPENVREXPANSIONPLUGIN_API UOpenVRExpansionFunctionLibrary : public UBlueprintFunctionLibrary +{ + //GENERATED_BODY() + GENERATED_BODY() + +public: + UOpenVRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer); + + ~UOpenVRExpansionFunctionLibrary(); +public: + +#if STEAMVR_SUPPORTED_PLATFORM + static FBPOpenVRCameraHandle OpenCamera; + + static FORCEINLINE FMatrix ToFMatrix(const vr::HmdMatrix34_t& tm) + { + // Rows and columns are swapped between vr::HmdMatrix34_t and FMatrix + return FMatrix( + FPlane(tm.m[0][0], tm.m[1][0], tm.m[2][0], 0.0f), + FPlane(tm.m[0][1], tm.m[1][1], tm.m[2][1], 0.0f), + FPlane(tm.m[0][2], tm.m[1][2], tm.m[2][2], 0.0f), + FPlane(tm.m[0][3], tm.m[1][3], tm.m[2][3], 1.0f)); + } + + static FORCEINLINE FMatrix ToFMatrix(const vr::HmdMatrix44_t& tm) + { + // Rows and columns are swapped between vr::HmdMatrix44_t and FMatrix + return FMatrix( + FPlane(tm.m[0][0], tm.m[1][0], tm.m[2][0], tm.m[3][0]), + FPlane(tm.m[0][1], tm.m[1][1], tm.m[2][1], tm.m[3][1]), + FPlane(tm.m[0][2], tm.m[1][2], tm.m[2][2], tm.m[3][2]), + FPlane(tm.m[0][3], tm.m[1][3], tm.m[2][3], tm.m[3][3])); + } + + static FORCEINLINE vr::HmdMatrix34_t ToHmdMatrix34(const FMatrix& tm) + { + // Rows and columns are swapped between vr::HmdMatrix34_t and FMatrix + vr::HmdMatrix34_t out; + + out.m[0][0] = tm.M[0][0]; + out.m[1][0] = tm.M[0][1]; + out.m[2][0] = tm.M[0][2]; + + out.m[0][1] = tm.M[1][0]; + out.m[1][1] = tm.M[1][1]; + out.m[2][1] = tm.M[1][2]; + + out.m[0][2] = tm.M[2][0]; + out.m[1][2] = tm.M[2][1]; + out.m[2][2] = tm.M[2][2]; + + out.m[0][3] = tm.M[3][0]; + out.m[1][3] = tm.M[3][1]; + out.m[2][3] = tm.M[3][2]; + + return out; + } + +#endif + + // Gets whether an HMD device is connected, this is an expanded version for SteamVR + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetOpenVRHMDType")) + static EBPOpenVRHMDDeviceType GetOpenVRHMDType(); + + // Gets what type of controller is plugged in + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetOpenVRControllerType")) + static EBPOpenVRControllerDeviceType GetOpenVRControllerType(); + + // Checks if a specific OpenVR device is connected, index names are assumed, they may not be exact + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true")) + static bool IsOpenVRDeviceConnected(int32 DeviceIndex); + + // Get what type a specific openVR device index is + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true")) + static EBPOpenVRTrackedDeviceClass GetOpenVRDeviceType(int32 DeviceIndex); + + // Get a list of all currently tracked devices and their types, index in the array is their device index + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true")) + static void GetOpenVRDevices(TArray<EBPOpenVRTrackedDeviceClass> &FoundDevices); + + // Get a list of all currently tracked devices of a specific type + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true")) + static void GetOpenVRDevicesByType(EBPOpenVRTrackedDeviceClass TypeToRetreive, TArray<int32> &FoundIndexs); + + // Gets the model / texture of a SteamVR Device, can use to fill procedural mesh components or just get the texture of them to apply to a pre-made model. + // If the render model name override is empty then the render model name will be automatically retrieved from SteamVR and RenderModelNameOut will be filled with it. + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", DisplayName = "GetVRDeviceModelAndTexture", ExpandEnumAsExecs = "Result", AdvancedDisplay = "OverrideDeviceID")) + static UTexture2D * GetVRDeviceModelAndTexture(UObject* WorldContextObject, FString RenderModelNameOverride, FString & RenderModelNameOut, EBPOpenVRTrackedDeviceClass DeviceType, TArray<UProceduralMeshComponent *> ProceduralMeshComponentsToFill, bool bCreateCollision, EAsyncBlueprintResultSwitch &Result, int32 OverrideDeviceID = -1); + + // Gets a String device property + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetVRDevicePropertyString", ExpandEnumAsExecs = "Result")) + static void GetVRDevicePropertyString(EVRDeviceProperty_String PropertyToRetrieve, int32 DeviceID, FString & StringValue, EBPOVRResultSwitch & Result); + + // Gets a Bool device property + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetVRDevicePropertyBool", ExpandEnumAsExecs = "Result")) + static void GetVRDevicePropertyBool(EVRDeviceProperty_Bool PropertyToRetrieve, int32 DeviceID, bool & BoolValue, EBPOVRResultSwitch & Result); + + // Gets a Float device property + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetVRDevicePropertyFloat", ExpandEnumAsExecs = "Result")) + static void GetVRDevicePropertyFloat(EVRDeviceProperty_Float PropertyToRetrieve, int32 DeviceID, float & FloatValue, EBPOVRResultSwitch & Result); + + // Gets a Int32 device property + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetVRDevicePropertyInt32", ExpandEnumAsExecs = "Result")) + static void GetVRDevicePropertyInt32(EVRDeviceProperty_Int32 PropertyToRetrieve, int32 DeviceID, int32 & IntValue, EBPOVRResultSwitch & Result); + + // Gets a UInt64 device property as a string (Blueprints do not support int64) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetVRDevicePropertyUInt64", ExpandEnumAsExecs = "Result")) + static void GetVRDevicePropertyUInt64(EVRDeviceProperty_UInt64 PropertyToRetrieve, int32 DeviceID, FString & UInt64Value, EBPOVRResultSwitch & Result); + + // Gets a Matrix34 device property as a Transform + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", DisplayName = "GetVRDevicePropertyMatrix34AsTransform", ExpandEnumAsExecs = "Result")) + static void GetVRDevicePropertyMatrix34AsTransform(EVRDeviceProperty_Matrix34 PropertyToRetrieve, int32 DeviceID, FTransform & TransformValue, EBPOVRResultSwitch & Result); + + // VR Camera options + + // Returns if there is a VR camera and what its pixel height / width is + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions|SteamVR|VRCamera", meta = (bIgnoreSelf = "true", DisplayName = "HasVRCamera")) + static bool HasVRCamera(EOpenVRCameraFrameType FrameType, int32 &Width, int32 &Height); + + // Gets a screen cap from the HMD camera if there is one + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|VRCamera", meta = (bIgnoreSelf = "true", DisplayName = "GetVRCameraFrame", ExpandEnumAsExecs = "Result")) + static void GetVRCameraFrame(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle, EOpenVRCameraFrameType FrameType, EBPOVRResultSwitch & Result, UTexture2D * TargetRenderTarget = nullptr); + + // Create Camera Render Target, automatically pulls the correct texture size and format + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|VRCamera", meta = (bIgnoreSelf = "true", DisplayName = "CreateCameraTexture2D", ExpandEnumAsExecs = "Result")) + static UTexture2D * CreateCameraTexture2D(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle, EOpenVRCameraFrameType FrameType, EBPOVRResultSwitch & Result); + + // Acquire the vr camera for access (wakes it up) and returns a handle to use for functions regarding it (MUST RELEASE CAMERA WHEN DONE) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|VRCamera", meta = (bIgnoreSelf = "true", DisplayName = "AcquireVRCamera", ExpandEnumAsExecs = "Result")) + static void AcquireVRCamera(FBPOpenVRCameraHandle & CameraHandle, EBPOVRResultSwitch & Result); + + // Releases the vr camera from access - you MUST call this when done with the camera + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|VRCamera", meta = (bIgnoreSelf = "true", DisplayName = "ReleaseVRCamera", ExpandEnumAsExecs = "Result")) + static void ReleaseVRCamera(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle, EBPOVRResultSwitch & Result); + + // Checks if a camera handle is valid + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|VRCamera", meta = (bIgnoreSelf = "true", DisplayName = "IsValid")) + static bool IsValid(UPARAM(ref) FBPOpenVRCameraHandle & CameraHandle); + + // VR compositor + + // Override the standard skybox texture in steamVR - LatLong format - need to call ClearSkyboxOverride when finished + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool SetSkyboxOverride_LatLong(UTexture2D * LatLongSkybox); + + // Override the standard skybox texture in steamVR - LatLong stereo pair - need to call ClearSkyboxOverride when finished + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool SetSkyboxOverride_LatLongStereoPair(UTexture2D * LatLongSkyboxL, UTexture2D * LatLongSkyboxR); + + // Override the standard skybox texture in steamVR - 6 cardinal textures - need to call ClearSkyboxOverride when finished + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool SetSkyboxOverride(UTexture * tFront, UTexture2D * tBack, UTexture * tLeft, UTexture * tRight, UTexture * tTop, UTexture * tBottom); + + // Remove skybox override in steamVR + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool ClearSkyboxOverride(); + + /** Fades the view on the HMD to the specified color. The fade will take fSeconds, and the color values are between + * 0.0 and 1.0. This color is faded on top of the scene based on the alpha parameter. Removing the fade color instantly + * would be FadeToColor( 0.0, 0.0, 0.0, 0.0, 0.0 ). Values are in un-premultiplied alpha space. */ + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool FadeHMDToColor(float fSeconds, FColor Color, bool bBackground = false); + + + /** Get current fade color value. */ + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool GetCurrentHMDFadeColor(FColor & ColorOut, bool bBackground = false); + + /** Fading the Grid in or out in fSeconds */ + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool FadeVRGrid(float fSeconds, bool bFadeIn); + + /** Get current alpha value of grid. */ + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool GetCurrentVRGridAlpha(float & VRGridAlpha); + + // Sets whether the compositor is allows to render or not (reverts to base compositor / grid when active) + // Useful to place players out of the app during frame drops/hitches/loading and into the vr skybox. + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR|Compositor", meta = (bIgnoreSelf = "true")) + static bool SetSuspendRendering(bool bSuspendRendering); + +#if STEAMVR_SUPPORTED_PLATFORM + static vr::Texture_t CreateOpenVRTexture_t(UTexture * Texture) + { + vr::Texture_t VRTexture; + + if (Texture) + VRTexture.handle = Texture->GetResource()->TextureRHI->GetNativeResource(); + else + VRTexture.handle = NULL; + + VRTexture.eColorSpace = vr::EColorSpace::ColorSpace_Auto; + + VRTexture.eType = vr::ETextureType::TextureType_OpenGL; +#if PLATFORM_MAC + VRTexture.eType = vr::ETextureType::TextureType_IOSurface; +#else + if (IsPCPlatform(GMaxRHIShaderPlatform)) + { + if (IsVulkanPlatform(GMaxRHIShaderPlatform)) + { + VRTexture.eType = vr::ETextureType::TextureType_Vulkan; + } + else if (IsOpenGLPlatform(GMaxRHIShaderPlatform)) + { + VRTexture.eType = vr::ETextureType::TextureType_OpenGL; + } +#if PLATFORM_WINDOWS + else + { + VRTexture.eType = vr::ETextureType::TextureType_DirectX; + } +#endif + } +#endif + return VRTexture; + } +#endif +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionPlugin.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionPlugin.h new file mode 100644 index 0000000..20f4ae4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/OpenVRExpansionPlugin.h @@ -0,0 +1,24 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" + +class FOpenVRExpansionPluginModule : public IModuleInterface +{ +public: + + FOpenVRExpansionPluginModule() + { + //OpenVRDLLHandle = nullptr; + } + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + //bool LoadOpenVRModule(); + //void UnloadOpenVRModule(); + + //void* OpenVRDLLHandle; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/SteamVRKeyboardComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/SteamVRKeyboardComponent.h new file mode 100644 index 0000000..e4b63ec --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenVRExpansionPlugin/Source/OpenVRExpansionPlugin/Public/SteamVRKeyboardComponent.h @@ -0,0 +1,270 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "EngineMinimal.h" +//#include "VRBPDatatypes.h" +#include "OpenVRExpansionFunctionLibrary.h" +//#include "GripMotionControllerComponent.h" +//#include "Engine/Engine.h" + +#include "IXRTrackingSystem.h" +#include "IHeadMountedDisplay.h" + +#include "SteamVRKeyboardComponent.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRKeyboardStringCallbackSignature, FString, Text); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRKeyboardNullCallbackSignature); + +/** +* Allows displaying / hiding and sending input to and from the SteamVR keyboard. Has events for keyboard inputs +* Generally outdated with the data table based keyboards I added, but still useful. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) +class OPENVREXPANSIONPLUGIN_API USteamVRKeyboardComponent : public USceneComponent +{ + +public: + + GENERATED_BODY() + +public: + USteamVRKeyboardComponent(const FObjectInitializer& ObjectInitializer); + + ~USteamVRKeyboardComponent(); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void OnUnregister() override; + // Keyboard Functions // + +#if STEAMVR_SUPPORTED_PLATFORM + FBPOpenVRKeyboardHandle KeyboardHandle; +#endif + + UPROPERTY(BlueprintAssignable, Category = "VRExpansionFunctions|SteamVR") + FVRKeyboardStringCallbackSignature OnKeyboardDone; + + UPROPERTY(BlueprintAssignable, Category = "VRExpansionFunctions|SteamVR") + FVRKeyboardNullCallbackSignature OnKeyboardClosed; + + UPROPERTY(BlueprintAssignable, Category = "VRExpansionFunctions|SteamVR") + FVRKeyboardStringCallbackSignature OnKeyboardCharInput; + + + // Opens the vrkeyboard, can fail if already open or in use + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", ExpandEnumAsExecs = "Result")) + void OpenVRKeyboard(bool bIsForPassword, bool bIsMultiline, bool bUseMinimalMode, bool bIsRightHand, int32 MaxCharacters, FString Description, FString StartingString, EBPOVRResultSwitch & Result) + { +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + if (/*!UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn ||*/ KeyboardHandle.IsValid()) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + if (KeyboardHandle.IsValid()) + { + Result = EBPOVRResultSwitch::OnSucceeded; + return; + } + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVROverlay* VROverlay = vr::VROverlay(); + + if (!VROverlay) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::EVROverlayError OverlayError; + OverlayError = VROverlay->CreateOverlay("KeyboardOverlay", "Keyboard Overlay", &KeyboardHandle.VRKeyboardHandle); + + if (OverlayError != vr::EVROverlayError::VROverlayError_None || !KeyboardHandle.IsValid()) + { + KeyboardHandle.VRKeyboardHandle = vr::k_ulOverlayHandleInvalid; + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::EGamepadTextInputMode Inputmode = bIsForPassword ? vr::EGamepadTextInputMode::k_EGamepadTextInputModePassword : vr::EGamepadTextInputMode::k_EGamepadTextInputModeNormal; + vr::EGamepadTextInputLineMode LineInputMode = bIsMultiline ? vr::EGamepadTextInputLineMode::k_EGamepadTextInputLineModeMultipleLines : vr::EGamepadTextInputLineMode::k_EGamepadTextInputLineModeSingleLine; + uint32 HandInteracting = bIsRightHand ? 0 : 1; + + if (bIsForPassword) + OverlayError = VROverlay->ShowKeyboardForOverlay(KeyboardHandle.VRKeyboardHandle, Inputmode, LineInputMode, TCHAR_TO_ANSI(*Description), MaxCharacters, TCHAR_TO_ANSI(*StartingString), bUseMinimalMode, HandInteracting); + else + OverlayError = VROverlay->ShowKeyboardForOverlay(KeyboardHandle.VRKeyboardHandle, Inputmode, LineInputMode, TCHAR_TO_ANSI(*Description), MaxCharacters, TCHAR_TO_ANSI(*StartingString), bUseMinimalMode, HandInteracting); + + if (OverlayError != vr::EVROverlayError::VROverlayError_None) + { + VROverlay->DestroyOverlay(KeyboardHandle.VRKeyboardHandle); + KeyboardHandle.VRKeyboardHandle = vr::k_ulOverlayHandleInvalid; + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + //VROverlay->SetOverlayAlpha(KeyboardHandle.VRKeyboardHandle, 0.0f); // Might need to remove this, keyboard would be invis? + VROverlay->ShowOverlay(KeyboardHandle.VRKeyboardHandle); + + // // Set the position of the keyboard in world space + //virtual void SetKeyboardTransformAbsolute(ETrackingUniverseOrigin eTrackingOrigin, const HmdMatrix34_t *pmatTrackingOriginToKeyboardTransform) = 0; + + + // // Set the position of the keyboard in overlay space by telling it to avoid a rectangle in the overlay. Rectangle coords have (0,0) in the bottom left + //virtual void SetKeyboardPositionForOverlay(VROverlayHandle_t ulOverlayHandle, HmdRect2_t avoidRect) = 0; + //VROverlay->SetOverlayTransformAbsolute(,ETrackingUniverseOrigin::TrackingUniverseStanding,HMDMatrix) + + //const float WorldToMeterScale = FMath::Max(GetWorldToMetersScale(), 0.1f); + //OVR_VERIFY(VROverlay->SetOverlayWidthInMeters(Layer.OverlayHandle, Layer.LayerDesc.QuadSize.X / WorldToMeterScale)); + //OVR_VERIFY(VROverlay->SetOverlayTexelAspect(Layer.OverlayHandle, Layer.LayerDesc.QuadSize.X / Layer.LayerDesc.QuadSize.Y)); + //OVR_VERIFY(VROverlay->SetOverlaySortOrder(Layer.OverlayHandle, Layer.LayerDesc.Priority)); + + this->SetComponentTickEnabled(true); + Result = EBPOVRResultSwitch::OnSucceeded; +#endif + } + + + // Closes the vrkeyboard, can fail if not already open + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", ExpandEnumAsExecs = "Result")) + void CloseVRKeyboard(EBPOVRResultSwitch & Result) + { +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + if (/*!UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn ||*/ !KeyboardHandle.IsValid()) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVROverlay* VROverlay = vr::VROverlay(); + + if (!VROverlay) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + VROverlay->HideKeyboard(); + VROverlay->HideOverlay(KeyboardHandle.VRKeyboardHandle); + + vr::EVROverlayError OverlayError; + OverlayError = VROverlay->DestroyOverlay(KeyboardHandle.VRKeyboardHandle); + KeyboardHandle.VRKeyboardHandle = vr::k_ulOverlayHandleInvalid; + this->SetComponentTickEnabled(false); + Result = EBPOVRResultSwitch::OnSucceeded; +#endif + } + + + // Re-Opens the vr keyboard that is currently active, can be used for switching interacting hands and the like. + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", ExpandEnumAsExecs = "Result")) + void ReOpenVRKeyboardForUser(bool bIsForPassword, bool bIsMultiline, bool bUseMinimalMode, bool bIsRightHand, int32 MaxCharacters, FString Description, FString StartingString, EBPOVRResultSwitch & Result) + { +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + if (/*!UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn ||*/ !KeyboardHandle.IsValid()) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVROverlay* VROverlay = vr::VROverlay(); + + if (!VROverlay) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::EVROverlayError OverlayError; + + VROverlay->HideKeyboard(); + + vr::EGamepadTextInputMode Inputmode = bIsForPassword ? vr::EGamepadTextInputMode::k_EGamepadTextInputModePassword : vr::EGamepadTextInputMode::k_EGamepadTextInputModeNormal; + vr::EGamepadTextInputLineMode LineInputMode = bIsMultiline ? vr::EGamepadTextInputLineMode::k_EGamepadTextInputLineModeMultipleLines : vr::EGamepadTextInputLineMode::k_EGamepadTextInputLineModeSingleLine; + uint32 HandInteracting = bIsRightHand ? 0 : 1; + + if (bIsForPassword) + OverlayError = VROverlay->ShowKeyboardForOverlay(KeyboardHandle.VRKeyboardHandle, Inputmode, LineInputMode, TCHAR_TO_ANSI(*Description), MaxCharacters, TCHAR_TO_ANSI(*StartingString), bUseMinimalMode, HandInteracting); + else + OverlayError = VROverlay->ShowKeyboardForOverlay(KeyboardHandle.VRKeyboardHandle, Inputmode, LineInputMode, TCHAR_TO_ANSI(*Description), MaxCharacters, TCHAR_TO_ANSI(*StartingString), bUseMinimalMode, HandInteracting); + + if (OverlayError != vr::EVROverlayError::VROverlayError_None) + { + VROverlay->DestroyOverlay(KeyboardHandle.VRKeyboardHandle); + KeyboardHandle.VRKeyboardHandle = vr::k_ulOverlayHandleInvalid; + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + Result = EBPOVRResultSwitch::OnSucceeded; +#endif + } + + + // Closes the vrkeyboard, can fail if not already open + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|SteamVR", meta = (bIgnoreSelf = "true", ExpandEnumAsExecs = "Result")) + void GetVRKeyboardText(FString & Text, EBPOVRResultSwitch & Result) + { +#if !STEAMVR_SUPPORTED_PLATFORM + Result = EBPOVRResultSwitch::OnFailed; + return; +#else + if (/*!UOpenVRExpansionFunctionLibrary::VRGetGenericInterfaceFn ||*/ !KeyboardHandle.IsValid()) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + if (!GEngine->XRSystem.IsValid() || (GEngine->XRSystem->GetSystemName() != SteamVRSystemName)) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + vr::IVROverlay* VROverlay = vr::VROverlay(); + + if (!VROverlay) + { + Result = EBPOVRResultSwitch::OnFailed; + return; + } + + char OutString[512]; + uint32 TextLen = VROverlay->GetKeyboardText((char*)&OutString, 512); + + Text = FString(ANSI_TO_TCHAR(OutString)); + Result = EBPOVRResultSwitch::OnSucceeded; +#endif + } + + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Config/FilterPlugin.ini b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/OpenXRExpansionPlugin.uplugin b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/OpenXRExpansionPlugin.uplugin new file mode 100644 index 0000000..7555a7b --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/OpenXRExpansionPlugin.uplugin @@ -0,0 +1,42 @@ +{ + "FileVersion": 3, + "Version": 5.0, + "VersionName": "5.0", + "FriendlyName": "OpenXRExpansionPlugin", + "Description": "An set of utility functions for OpenXR", + "Category": "Virtual Reality", + "CreatedBy": "Joshua (MordenTral) Statzer", + "CreatedByURL": "http://www.vreue4.com", + "DocsURL": "http://www.vreue4.com", + "MarketplaceURL": "", + "SupportURL": "http://www.vreue4.com", + "EnabledByDefault": false, + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android", + "HoloLens" + ], + "Modules": [ + { + "Name": "OpenXRExpansionPlugin", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit" + } + , + { + "Name": "OpenXRExpansionEditor", + "Type": "UnCookedOnly", + "LoadingPhase": "PostEngineInit" + } + ], + "Plugins": [ + { + "Name": "OpenXR", + "Enabled": true + } + ] +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Resources/Icon128.png b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Resources/Icon128.png new file mode 100644 index 0000000..23fec31 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8f329acc9d0c2815d28349d5a4144ebd337f7e2a93819cf1a7c3cc9b06ecea +size 16085 diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/OpenXRExpansionEditor.Build.cs b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/OpenXRExpansionEditor.Build.cs new file mode 100644 index 0000000..36288b9 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/OpenXRExpansionEditor.Build.cs @@ -0,0 +1,61 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class OpenXRExpansionEditor : ModuleRules + { + + public OpenXRExpansionEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + // ... add other public dependencies that you statically link with here ... + "Engine", + "Core", + "CoreUObject" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "BlueprintGraph", + "AnimGraph", + "AnimGraphRuntime", + "SlateCore", + "Slate", + "InputCore", + "Engine", + "EditorStyle", + "AssetRegistry", + "OpenXRExpansionPlugin" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/AnimGraphNode_ApplyOpenXRHandPose.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/AnimGraphNode_ApplyOpenXRHandPose.cpp new file mode 100644 index 0000000..666bd39 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/AnimGraphNode_ApplyOpenXRHandPose.cpp @@ -0,0 +1,33 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "AnimGraphNode_ApplyOpenXRHandPose.h" + +///////////////////////////////////////////////////// +// UAnimGraphNode_ModifyBSHand + +UAnimGraphNode_ApplyOpenXRHandPose::UAnimGraphNode_ApplyOpenXRHandPose(const FObjectInitializer& Initializer) + : Super(Initializer) +{ +} + +//Title Color! +FLinearColor UAnimGraphNode_ApplyOpenXRHandPose::GetNodeTitleColor() const +{ + return FLinearColor(12, 12, 0, 1); +} + +//Node Category +FString UAnimGraphNode_ApplyOpenXRHandPose::GetNodeCategory() const +{ + return FString("OpenXR"); +} +FText UAnimGraphNode_ApplyOpenXRHandPose::GetControllerDescription() const +{ + return FText::FromString("Apply OpenXR Hand Pose"); +} + +FText UAnimGraphNode_ApplyOpenXRHandPose::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + FText Result = GetControllerDescription(); + return Result; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/OpenXRExpansionEditor.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/OpenXRExpansionEditor.cpp new file mode 100644 index 0000000..f22c5fc --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Private/OpenXRExpansionEditor.cpp @@ -0,0 +1,16 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "OpenXRExpansionEditor.h" +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" + + +IMPLEMENT_MODULE(FOpenXRExpansionEditorModule, OpenXRExpansionEditor); + +void FOpenXRExpansionEditorModule::StartupModule() +{ +} + +void FOpenXRExpansionEditorModule::ShutdownModule() +{ +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/AnimGraphNode_ApplyOpenXRHandPose.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/AnimGraphNode_ApplyOpenXRHandPose.h new file mode 100644 index 0000000..b9d5db3 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/AnimGraphNode_ApplyOpenXRHandPose.h @@ -0,0 +1,34 @@ +#pragma once + +#include "AnimGraphDefinitions.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Editor/AnimGraph/Public/AnimGraphNode_SkeletalControlBase.h" + +#include "AnimNode_ApplyOpenXRHandPose.h" +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "AnimGraphNode_ApplyOpenXRHandPose.generated.h" + +UCLASS(MinimalAPI) +class UAnimGraphNode_ApplyOpenXRHandPose : public UAnimGraphNode_SkeletalControlBase +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditAnywhere, Category = Settings) + FAnimNode_ApplyOpenXRHandPose Node; + +public: + // UEdGraphNode interface + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual FLinearColor GetNodeTitleColor() const override; + virtual FString GetNodeCategory() const override; + // End of UEdGraphNode interface + +protected: + + // UAnimGraphNode_SkeletalControlBase protected interface + virtual FText GetControllerDescription() const; + virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; } + // End of UAnimGraphNode_SkeletalControlBase protected interface + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/OpenXRExpansionEditor.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/OpenXRExpansionEditor.h new file mode 100644 index 0000000..92ed6bc --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionEditor/Public/OpenXRExpansionEditor.h @@ -0,0 +1,13 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Runtime/Core/Public/Modules/ModuleInterface.h" + +class FOpenXRExpansionEditorModule : public IModuleInterface +{ +public: + + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/OpenXRExpansionPlugin.Build.cs b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/OpenXRExpansionPlugin.Build.cs new file mode 100644 index 0000000..1fc95b7 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/OpenXRExpansionPlugin.Build.cs @@ -0,0 +1,69 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class OpenXRExpansionPlugin: ModuleRules + { + public OpenXRExpansionPlugin(ReadOnlyTargetRules Target) + : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + //"InputDevice", + //"LiveLink", + //"LiveLinkInterface" + } + ); + + var EngineDir = Path.GetFullPath(Target.RelativeEnginePath); + PrivateIncludePaths.AddRange( + new string[] { + EngineDir + "Plugins/Runtime/OpenXR/Source/OpenXRHMD/Private", + EngineDir + "/Source/ThirdParty/OpenXR/include", + // ... add other private include paths required here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "NetCore", + "CoreUObject", + //"ApplicationCore", + "Engine", + //"InputDevice", + "InputCore", + "Slate", + "HeadMountedDisplay", + //"AnimGraph", + "AnimGraphRuntime", + "SlateCore", + //"LiveLink", + //"LiveLinkInterface", + } + ); + + if (Target.Platform != UnrealTargetPlatform.Mac) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "OpenXRHMD" + } + ); + PrivateDefinitions.AddRange(new string[] { "OPENXR_SUPPORTED" }); + AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenXR"); + } + + // if (Target.bBuildEditor == true) + // { + // PrivateDependencyModuleNames.Add("UnrealEd"); + // } + } + } +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/AnimNode_ApplyOpenXRHandPose.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/AnimNode_ApplyOpenXRHandPose.cpp new file mode 100644 index 0000000..2c0b052 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/AnimNode_ApplyOpenXRHandPose.cpp @@ -0,0 +1,370 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "AnimNode_ApplyOpenXRHandPose.h" +//#include "EngineMinimal.h" +//#include "Engine/Engine.h" +//#include "CoreMinimal.h" +#include "OpenXRExpansionFunctionLibrary.h" +#include "AnimNode_ApplyOpenXRHandPose.h" +#include "AnimationRuntime.h" +#include "DrawDebugHelpers.h" +#include "OpenXRHandPoseComponent.h" +#include "Runtime/Engine/Public/Animation/AnimInstanceProxy.h" +#include "BoneControllers/AnimNode_SkeletalControlBase.h" + +FAnimNode_ApplyOpenXRHandPose::FAnimNode_ApplyOpenXRHandPose() + : FAnimNode_SkeletalControlBase() +{ + WorldIsGame = false; + Alpha = 1.f; + SkeletonType = EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Right; + bIsOpenInputAnimationInstance = false; + bSkipRootBone = false; + bOnlyApplyWristTransform = false; + //WristAdjustment = FQuat::Identity; +} + +void FAnimNode_ApplyOpenXRHandPose::OnInitializeAnimInstance(const FAnimInstanceProxy* InProxy, const UAnimInstance* InAnimInstance) +{ + Super::OnInitializeAnimInstance(InProxy, InAnimInstance); + + if (const UOpenXRAnimInstance * animInst = Cast<UOpenXRAnimInstance>(InAnimInstance)) + { + bIsOpenInputAnimationInstance = true; + } +} + +void FAnimNode_ApplyOpenXRHandPose::Initialize_AnyThread(const FAnimationInitializeContext& Context) +{ + Super::Initialize_AnyThread(Context); +} + +void FAnimNode_ApplyOpenXRHandPose::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) +{ + Super::CacheBones_AnyThread(Context); +} + +void FAnimNode_ApplyOpenXRHandPose::InitializeBoneReferences(const FBoneContainer& RequiredBones) +{ + UObject* OwningAsset = RequiredBones.GetAsset(); + if (!OwningAsset) + return; + + if (!MappedBonePairs.bInitialized || OwningAsset->GetFName() != MappedBonePairs.LastInitializedName) + { + MappedBonePairs.LastInitializedName = OwningAsset->GetFName(); + MappedBonePairs.bInitialized = false; + + USkeleton* AssetSkeleton = RequiredBones.GetSkeletonAsset(); + if (AssetSkeleton) + { + // If our bone pairs are empty, then setup our sane defaults + if(!MappedBonePairs.BonePairs.Num()) + MappedBonePairs.ConstructDefaultMappings(SkeletonType, bSkipRootBone); + + FBPOpenXRSkeletalPair WristPair; + FBPOpenXRSkeletalPair IndexPair; + FBPOpenXRSkeletalPair PinkyPair; + + TArray<FTransform> RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose(); + TArray<FMeshBoneInfo> RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo(); + + for (FBPOpenXRSkeletalPair& BonePair : MappedBonePairs.BonePairs) + { + // Fill in the bone name for the reference + BonePair.ReferenceToConstruct.BoneName = BonePair.BoneToTarget; + + // Init the reference + BonePair.ReferenceToConstruct.Initialize(AssetSkeleton); + + BonePair.ReferenceToConstruct.CachedCompactPoseIndex = BonePair.ReferenceToConstruct.GetCompactPoseIndex(RequiredBones); + + if ((BonePair.ReferenceToConstruct.CachedCompactPoseIndex != INDEX_NONE)) + { + // Get our parent bones index + BonePair.ParentReference = RequiredBones.GetParentBoneIndex(BonePair.ReferenceToConstruct.CachedCompactPoseIndex); + + /*FTransform WristPose = GetRefBoneInCS(RefBones, RefBonesInfo, BonePair.ReferenceToConstruct.BoneIndex); + + FVector WristForward = WristPose.GetRotation().GetForwardVector(); + FVector WristUpward = WristPose.GetRotation().GetForwardVector(); + FQuat ForwardFixup = FQuat::FindBetweenNormals(FVector::ForwardVector, WristForward); + FQuat UpFixup = FQuat::FindBetweenNormals(ForwardFixup.RotateVector(FVector::UpVector), WristUpward); + + FQuat rotFix = UpFixup * ForwardFixup; + rotFix.Normalize(); + //MappedBonePairs.AdjustmentQuat = rotFix; + BonePair.RetargetRot = rotFix;*/ + } + + if (BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT) + { + WristPair = BonePair; + } + else if (BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT) + { + IndexPair = BonePair; + } + else if (BonePair.OpenXRBone == EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT) + { + PinkyPair = BonePair; + } + } + + MappedBonePairs.bInitialized = true; + + if (WristPair.ReferenceToConstruct.HasValidSetup() && IndexPair.ReferenceToConstruct.HasValidSetup() && PinkyPair.ReferenceToConstruct.HasValidSetup()) + { + //TArray<FTransform> RefBones = AssetSkeleton->GetReferenceSkeleton().GetRefBonePose(); + //TArray<FMeshBoneInfo> RefBonesInfo = AssetSkeleton->GetReferenceSkeleton().GetRefBoneInfo(); + + FTransform WristPose = GetRefBoneInCS(RefBones, RefBonesInfo, WristPair.ReferenceToConstruct.BoneIndex); + FTransform MiddleFingerPose = GetRefBoneInCS(RefBones, RefBonesInfo, PinkyPair.ReferenceToConstruct.BoneIndex); + + FVector BoneForwardVector = MiddleFingerPose.GetTranslation() - WristPose.GetTranslation(); + SetVectorToMaxElement(BoneForwardVector); + BoneForwardVector.Normalize(); + + FTransform IndexFingerPose = GetRefBoneInCS(RefBones, RefBonesInfo, IndexPair.ReferenceToConstruct.BoneIndex); + FTransform PinkyFingerPose = GetRefBoneInCS(RefBones, RefBonesInfo, PinkyPair.ReferenceToConstruct.BoneIndex); + FVector BoneUpVector = IndexFingerPose.GetTranslation() - PinkyFingerPose.GetTranslation(); + SetVectorToMaxElement(BoneUpVector); + BoneUpVector.Normalize(); + + FVector BoneRightVector = FVector::CrossProduct(BoneUpVector, BoneForwardVector); + BoneRightVector.Normalize(); + + FQuat ForwardAdjustment = FQuat::FindBetweenNormals(FVector::ForwardVector, BoneForwardVector); + + FVector NewRightVector = ForwardAdjustment * FVector::RightVector; + NewRightVector.Normalize(); + + FQuat TwistAdjustment = FQuat::FindBetweenNormals(NewRightVector, BoneRightVector); + MappedBonePairs.AdjustmentQuat = TwistAdjustment * ForwardAdjustment; + MappedBonePairs.AdjustmentQuat.Normalize(); + } + + } + } +} + +void FAnimNode_ApplyOpenXRHandPose::ConvertHandTransformsSpace(TArray<FTransform>& OutTransforms, TArray<FTransform>& WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones) +{ + // Fail if the count is too low + if (WorldTransforms.Num() < EHandKeypointCount) + return; + + if (OutTransforms.Num() < WorldTransforms.Num()) + { + OutTransforms.Empty(WorldTransforms.Num()); + OutTransforms.AddUninitialized(WorldTransforms.Num()); + } + + // Bone/Parent map + int32 BoneParents[26] = + { + // Manually build the parent hierarchy starting at the wrist which has no parent (-1) + 1, // Palm -> Wrist + -1, // Wrist -> None + 1, // ThumbMetacarpal -> Wrist + 2, // ThumbProximal -> ThumbMetacarpal + 3, // ThumbDistal -> ThumbProximal + 4, // ThumbTip -> ThumbDistal + + 1, // IndexMetacarpal -> Wrist + 6, // IndexProximal -> IndexMetacarpal + 7, // IndexIntermediate -> IndexProximal + 8, // IndexDistal -> IndexIntermediate + 9, // IndexTip -> IndexDistal + + 1, // MiddleMetacarpal -> Wrist + 11, // MiddleProximal -> MiddleMetacarpal + 12, // MiddleIntermediate -> MiddleProximal + 13, // MiddleDistal -> MiddleIntermediate + 14, // MiddleTip -> MiddleDistal + + 1, // RingMetacarpal -> Wrist + 16, // RingProximal -> RingMetacarpal + 17, // RingIntermediate -> RingProximal + 18, // RingDistal -> RingIntermediate + 19, // RingTip -> RingDistal + + 1, // LittleMetacarpal -> Wrist + 21, // LittleProximal -> LittleMetacarpal + 22, // LittleIntermediate -> LittleProximal + 23, // LittleDistal -> LittleIntermediate + 24, // LittleTip -> LittleDistal + }; + + // Convert transforms to parent space + // The hand tracking transforms are in world space. + + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + WorldTransforms[Index].NormalizeRotation(); + + if (bMirrorLeftRight) + { + WorldTransforms[Index].Mirror(EAxis::Y, EAxis::Y); + } + + WorldTransforms[Index].ConcatenateRotation(AddTrans.GetRotation()); + } + + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + FTransform& BoneTransform = WorldTransforms[Index]; + int32 ParentIndex = BoneParents[Index]; + int32 ParentParent = -1; + + // Thumb keeps the metacarpal intact, we don't skip it + if (bMergeMissingUE4Bones) + { + if (Index != (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT && ParentIndex > 0) + { + ParentParent = BoneParents[ParentIndex]; + } + } + + if (ParentIndex < 0) + { + // We are at the root, so use it. + OutTransforms[Index] = BoneTransform; + } + else + { + // Merging missing metacarpal bone into the transform + if (bMergeMissingUE4Bones && ParentParent == 1) // Wrist + { + OutTransforms[Index] = BoneTransform.GetRelativeTransform(WorldTransforms[ParentParent]); + } + else + { + OutTransforms[Index] = BoneTransform.GetRelativeTransform(WorldTransforms[ParentIndex]); + } + } + } +} + +void FAnimNode_ApplyOpenXRHandPose::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms) +{ + if (!MappedBonePairs.bInitialized) + return; + + /*const */FBPOpenXRActionSkeletalData *StoredActionInfoPtr = nullptr; + if (bIsOpenInputAnimationInstance) + { + /*const*/ FOpenXRAnimInstanceProxy* OpenXRAnimInstance = (FOpenXRAnimInstanceProxy*)Output.AnimInstanceProxy; + if (OpenXRAnimInstance->HandSkeletalActionData.Num()) + { + for (int i = 0; i <OpenXRAnimInstance->HandSkeletalActionData.Num(); ++i) + { + EVRSkeletalHandIndex TargetHand = OpenXRAnimInstance->HandSkeletalActionData[i].TargetHand; + + if (OpenXRAnimInstance->HandSkeletalActionData[i].bMirrorLeftRight) + { + TargetHand = (TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + } + + if (TargetHand == MappedBonePairs.TargetHand) + { + StoredActionInfoPtr = &OpenXRAnimInstance->HandSkeletalActionData[i]; + break; + } + } + } + } + + // If we have an empty hand pose but have a passed in custom one then use that + if ((StoredActionInfoPtr == nullptr || !StoredActionInfoPtr->SkeletalTransforms.Num()) && OptionalStoredActionInfo.SkeletalTransforms.Num()) + { + StoredActionInfoPtr = &OptionalStoredActionInfo; + } + + //MappedBonePairs.AdjustmentQuat = WristAdjustment; + + // Currently not blending correctly + const float BlendWeight = FMath::Clamp<float>(ActualAlpha, 0.f, 1.f); + const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer(); + uint8 BoneTransIndex = 0; + uint8 NumBones = StoredActionInfoPtr ? StoredActionInfoPtr->SkeletalTransforms.Num() : 0; + + if (NumBones < 1) + { + // Early out, we don't have a valid data to work with + return; + } + + FTransform trans = FTransform::Identity; + OutBoneTransforms.Reserve(MappedBonePairs.BonePairs.Num()); + TArray<FBoneTransform> TransBones; + FTransform AdditionTransform = StoredActionInfoPtr->AdditionTransform; + + FTransform TempTrans = FTransform::Identity; + FTransform ParentTrans = FTransform::Identity; + FTransform * ParentTransPtr = nullptr; + + //AdditionTransform.SetRotation(MappedBonePairs.AdjustmentQuat); + + TArray<FTransform> HandTransforms; + ConvertHandTransformsSpace(HandTransforms, StoredActionInfoPtr->SkeletalTransforms, AdditionTransform, StoredActionInfoPtr->bMirrorLeftRight, MappedBonePairs.bMergeMissingBonesUE4); + + for (const FBPOpenXRSkeletalPair& BonePair : MappedBonePairs.BonePairs) + { + BoneTransIndex = (int8)BonePair.OpenXRBone; + ParentTrans = FTransform::Identity; + + if (BoneTransIndex >= NumBones || BonePair.ReferenceToConstruct.CachedCompactPoseIndex == INDEX_NONE) + continue; + + if (!BonePair.ReferenceToConstruct.IsValidToEvaluate(BoneContainer)) + { + continue; + } + + trans = Output.Pose.GetComponentSpaceTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex); + + if (BonePair.ParentReference != INDEX_NONE) + { + ParentTrans = Output.Pose.GetComponentSpaceTransform(BonePair.ParentReference); + ParentTrans.SetScale3D(FVector(1.f)); + } + + EXRHandJointType CurrentBone = (EXRHandJointType)BoneTransIndex; + TempTrans = (HandTransforms[BoneTransIndex]); + + /*if (StoredActionInfoPtr->bMirrorHand) + { + FMatrix M = TempTrans.ToMatrixWithScale(); + M.Mirror(EAxis::Z, EAxis::X); + M.Mirror(EAxis::X, EAxis::Z); + TempTrans.SetFromMatrix(M); + }*/ + + TempTrans = TempTrans * ParentTrans; + + if (StoredActionInfoPtr->bAllowDeformingMesh || bOnlyApplyWristTransform) + trans.SetTranslation(TempTrans.GetTranslation()); + + trans.SetRotation(TempTrans.GetRotation()); + + TransBones.Add(FBoneTransform(BonePair.ReferenceToConstruct.CachedCompactPoseIndex, trans)); + + // Need to do it per bone so future bones are correct + // Only if in parent space though, can do it all at the end in component space + if (TransBones.Num()) + { + Output.Pose.LocalBlendCSBoneTransforms(TransBones, BlendWeight); + TransBones.Reset(); + } + + if (bOnlyApplyWristTransform && CurrentBone == EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT) + { + break; // Early out of the loop, we only wanted to apply the wrist + } + } +} + +bool FAnimNode_ApplyOpenXRHandPose::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) +{ + return(/*MappedBonePairs.bInitialized && */MappedBonePairs.BonePairs.Num() > 0); +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionFunctionLibrary.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionFunctionLibrary.cpp new file mode 100644 index 0000000..85008ae --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionFunctionLibrary.cpp @@ -0,0 +1,152 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "OpenXRExpansionFunctionLibrary.h" +//#include "EngineMinimal.h" +#include "Engine/Engine.h" +#include <openxr/openxr.h> +#include "CoreMinimal.h" +#include "IXRTrackingSystem.h" + +//General Log +DEFINE_LOG_CATEGORY(OpenXRExpansionFunctionLibraryLog); + +UOpenXRExpansionFunctionLibrary::UOpenXRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +//============================================================================= +UOpenXRExpansionFunctionLibrary::~UOpenXRExpansionFunctionLibrary() +{ + +} + +void UOpenXRExpansionFunctionLibrary::GetXRMotionControllerType(FString& TrackingSystemName, EBPOpenXRControllerDeviceType& DeviceType, EBPXRResultSwitch& Result) +{ +#if defined(OPENXR_SUPPORTED) + DeviceType = EBPOpenXRControllerDeviceType::DT_UnknownController; + Result = EBPXRResultSwitch::OnFailed; + + if (FOpenXRHMD* pOpenXRHMD = GetOpenXRHMD()) + { + XrInstance XRInstance = pOpenXRHMD->GetInstance(); + XrSystemId XRSysID = pOpenXRHMD->GetSystem(); + + if (XRSysID && XRInstance) + { + XrSystemProperties systemProperties{ XR_TYPE_SYSTEM_PROPERTIES }; + systemProperties.next = nullptr; + + if (xrGetSystemProperties(XRInstance, XRSysID, &systemProperties) == XR_SUCCESS) + { + XrSession XRSesh = pOpenXRHMD->GetSession(); + + if (XRSesh) + { + XrPath myPath; + XrResult PathResult = xrStringToPath(XRInstance, "/user/hand/left", &myPath); + XrInteractionProfileState interactionProfile{ XR_TYPE_INTERACTION_PROFILE_STATE }; + interactionProfile.next = nullptr; + + XrResult QueryResult = xrGetCurrentInteractionProfile(XRSesh, myPath, &interactionProfile); + if (QueryResult == XR_SUCCESS) + { + char myPathy[XR_MAX_SYSTEM_NAME_SIZE]; + uint32_t outputsize; + xrPathToString(XRInstance, interactionProfile.interactionProfile, XR_MAX_SYSTEM_NAME_SIZE, &outputsize, myPathy); + + if (outputsize < 1) + return; + + FString InteractionName(ANSI_TO_TCHAR(myPathy)); + if (InteractionName.Len() < 1) + return; + + /* + * Interaction profile paths [6.4] + An interaction profile identifies a collection of buttons and + other input sources, and is of the form: + /interaction_profiles/<vendor_name>/<type_name> + Paths supported in the core 1.0 release + /interaction_profiles/khr/simple_control + /interaction_profiles/khr/simple_controller + /interaction_profiles/google/daydream_controller + /interaction_profiles/htc/vive_controller + /interaction_profiles/htc/vive_pro + /interaction_profiles/microsoft/motion_controller + /interaction_profiles/microsoft/xbox_controller + /interaction_profiles/oculus/go_controller + /interaction_profiles/oculus/touch_controller + /interaction_profiles/valve/index_controller + */ + + // Not working currently? + /*XrInputSourceLocalizedNameGetInfo InputSourceInfo{ XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO }; + InputSourceInfo.next = nullptr; + InputSourceInfo.whichComponents = XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT; + InputSourceInfo.sourcePath = interactionProfile.interactionProfile; + + char buffer[XR_MAX_SYSTEM_NAME_SIZE]; + uint32_t usedBufferCount = 0; + if (xrGetInputSourceLocalizedName(XRSesh, &InputSourceInfo, XR_MAX_SYSTEM_NAME_SIZE, &usedBufferCount, (char*)&buffer) == XR_SUCCESS) + { + int g = 0; + }*/ + + + if (InteractionName.Find("touch_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_OculusTouchController; + } + else if (InteractionName.Contains("index_controller", ESearchCase::IgnoreCase)) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_ValveIndexController; + } + else if (InteractionName.Find("vive_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_ViveController; + } + else if (InteractionName.Find("vive_pro", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_ViveProController; + } + else if (InteractionName.Find("simple_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_SimpleController; + } + else if (InteractionName.Find("daydream_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_DaydreamController; + } + else if (InteractionName.Find("motion_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_MicrosoftMotionController; + } + else if (InteractionName.Find("xbox_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_MicrosoftXboxController; + } + else if (InteractionName.Find("go_controller", ESearchCase::IgnoreCase) != INDEX_NONE) + { + DeviceType = EBPOpenXRControllerDeviceType::DT_OculusGoController; + } + else + { + UE_LOG(OpenXRExpansionFunctionLibraryLog, Warning, TEXT("UNKNOWN OpenXR Interaction profile detected!!!: %s"), *InteractionName); + DeviceType = EBPOpenXRControllerDeviceType::DT_UnknownController; + } + + Result = EBPXRResultSwitch::OnSucceeded; + } + + TrackingSystemName = FString(ANSI_TO_TCHAR(systemProperties.systemName));// , XR_MAX_SYSTEM_NAME_SIZE); + //VendorID = systemProperties.vendorId; + return; + } + } + } + } +#endif + + TrackingSystemName.Empty(); + return; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionPlugin.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionPlugin.cpp new file mode 100644 index 0000000..47fac71 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRExpansionPlugin.cpp @@ -0,0 +1,24 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "OpenXRExpansionPlugin.h" +#include "OpenXRExpansionFunctionLibrary.h" + +#define LOCTEXT_NAMESPACE "FXRExpansionPluginModule" + +void FOpenXRExpansionPluginModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + //LoadOpenVRModule(); +} + +void FOpenXRExpansionPluginModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +// UnloadOpenVRModule(); +} + + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FOpenXRExpansionPluginModule, OpenXRExpansionPlugin) \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRHandPoseComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRHandPoseComponent.cpp new file mode 100644 index 0000000..43dbd8f --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Private/OpenXRHandPoseComponent.cpp @@ -0,0 +1,827 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "OpenXRHandPoseComponent.h" +#include "Net/UnrealNetwork.h" +#include "MotionControllerComponent.h" +#include "OpenXRExpansionFunctionLibrary.h" +#include "Engine/NetSerialization.h" + +#include "XRMotionControllerBase.h" // for GetHandEnumForSourceName() +//#include "EngineMinimal.h" + +UOpenXRHandPoseComponent::UOpenXRHandPoseComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + + ReplicationRateForSkeletalAnimations = 10.f; + bReplicateSkeletalData = false; + bSmoothReplicatedSkeletalData = true; + SkeletalNetUpdateCount = 0.f; + bDetectGestures = true; + SetIsReplicatedByDefault(true); + bGetMockUpPoseForDebugging = false; +} + +void UOpenXRHandPoseComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + // Skipping the owner with this as the owner will use the controllers location directly + DOREPLIFETIME_CONDITION(UOpenXRHandPoseComponent, LeftHandRep, COND_SkipOwner); + DOREPLIFETIME_CONDITION(UOpenXRHandPoseComponent, RightHandRep, COND_SkipOwner); +} + +void UOpenXRHandPoseComponent::Server_SendSkeletalTransforms_Implementation(const FBPXRSkeletalRepContainer& SkeletalInfo) +{ + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandSkeletalActions[i].TargetHand == SkeletalInfo.TargetHand) + { + HandSkeletalActions[i].OldSkeletalTransforms = HandSkeletalActions[i].SkeletalTransforms; + + FBPXRSkeletalRepContainer::CopyReplicatedTo(SkeletalInfo, HandSkeletalActions[i]); + + if (SkeletalInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + LeftHandRep = SkeletalInfo; + if (bSmoothReplicatedSkeletalData) + LeftHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations); + } + else + { + RightHandRep = SkeletalInfo; + if (bSmoothReplicatedSkeletalData) + RightHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations); + } + + break; + } + } +} + +bool UOpenXRHandPoseComponent::Server_SendSkeletalTransforms_Validate(const FBPXRSkeletalRepContainer& SkeletalInfo) +{ + return true; +} + +void FOpenXRAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) +{ + Super::PreUpdate(InAnimInstance, DeltaSeconds); + + if (UOpenXRAnimInstance* OwningInstance = Cast<UOpenXRAnimInstance>(InAnimInstance)) + { + if (OwningInstance->OwningPoseComp) + { + if (HandSkeletalActionData.Num() != OwningInstance->OwningPoseComp->HandSkeletalActions.Num()) + { + HandSkeletalActionData.Empty(OwningInstance->OwningPoseComp->HandSkeletalActions.Num()); + + for(FBPOpenXRActionSkeletalData& actionInfo : OwningInstance->OwningPoseComp->HandSkeletalActions) + { + HandSkeletalActionData.Add(actionInfo); + } + } + else + { + for (int i = 0; i < OwningInstance->OwningPoseComp->HandSkeletalActions.Num(); ++i) + { + HandSkeletalActionData[i] = OwningInstance->OwningPoseComp->HandSkeletalActions[i]; + } + } + } + } +} + +FOpenXRAnimInstanceProxy::FOpenXRAnimInstanceProxy(UAnimInstance* InAnimInstance) + : FAnimInstanceProxy(InAnimInstance) +{ +} + +void UOpenXRHandPoseComponent::BeginPlay() +{ + /*if (UMotionControllerComponent * MotionParent = Cast<UMotionControllerComponent>(GetAttachParent())) + { + EControllerHand HandType; + if (!FXRMotionControllerBase::GetHandEnumForSourceName(MotionParent->MotionSource, HandType)) + { + HandType = EControllerHand::Left; + } + + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandType == EControllerHand::Left || HandType == EControllerHand::AnyHand) + HandSkeletalActions[i].SkeletalData.TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Left; + else + HandSkeletalActions[i].SkeletalData.TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right; + } + + }*/ + + Super::BeginPlay(); +} + +void UOpenXRHandPoseComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!IsLocallyControlled()) + { + if (bReplicateSkeletalData) + { + // Handle bone lerping here if we are replicating + for (FBPOpenXRActionSkeletalData& actionInfo : HandSkeletalActions) + { + if (bSmoothReplicatedSkeletalData) + { + if (actionInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + LeftHandRepManager.UpdateManager(DeltaTime, actionInfo); + } + else + { + RightHandRepManager.UpdateManager(DeltaTime, actionInfo); + } + } + } + + + } + } + else // Get data and process + { + bool bGetCompressedTransforms = false; + if (bReplicateSkeletalData && HandSkeletalActions.Num() > 0) + { + SkeletalNetUpdateCount += DeltaTime; + if (SkeletalNetUpdateCount >= (1.0f / ReplicationRateForSkeletalAnimations)) + { + SkeletalNetUpdateCount = 0.0f; + bGetCompressedTransforms = true; + } + } + + for (FBPOpenXRActionSkeletalData& actionInfo : HandSkeletalActions) + { + if (UOpenXRExpansionFunctionLibrary::GetOpenXRHandPose(actionInfo, this, bGetMockUpPoseForDebugging)) + { + if (bGetCompressedTransforms) + { + if (GetNetMode() == NM_Client) + { + if (actionInfo.bHasValidData) + { + FBPXRSkeletalRepContainer ContainerSend; + ContainerSend.CopyForReplication(actionInfo); + Server_SendSkeletalTransforms(ContainerSend); + } + } + else + { + if (actionInfo.bHasValidData) + { + if (actionInfo.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + LeftHandRep.CopyForReplication(actionInfo); + else + RightHandRep.CopyForReplication(actionInfo); + } + } + } + } + + if (bDetectGestures && actionInfo.bHasValidData && actionInfo.SkeletalTransforms.Num() > 0 && GesturesDB != nullptr && GesturesDB->Gestures.Num() > 0) + { + DetectCurrentPose(actionInfo); + } + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + +bool UOpenXRHandPoseComponent::SaveCurrentPose(FName RecordingName, EVRSkeletalHandIndex HandToSave) +{ + + if (!HandSkeletalActions.Num()) + return false; + + // Default to the first hand element so that single length arrays work as is. + FBPOpenXRActionSkeletalData* HandSkeletalAction = nullptr; + + // Now check for the specific passed in hand if this is a multi hand + for (int i = 0; i < HandSkeletalActions.Num(); ++i) + { + if (HandSkeletalActions[i].TargetHand == HandToSave) + { + HandSkeletalAction = &HandSkeletalActions[i]; + break; + } + } + + if (!HandSkeletalAction || !HandSkeletalAction->bHasValidData || HandSkeletalAction->SkeletalTransforms.Num() < EHandKeypointCount) + return false; + + if (GesturesDB) + { + FOpenXRGesture NewGesture; + + int32 FingerMap[5] = + { + (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT + }; + + FVector WristLoc = FVector::ZeroVector; + + if (HandToSave == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + WristLoc = HandSkeletalAction->SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector); + } + else + { + WristLoc = HandSkeletalAction->SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation(); + } + + for (int i = 0; i < 5; ++i) + { + if (HandToSave == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + NewGesture.FingerValues[i] = FOpenXRGestureFingerPosition(HandSkeletalAction->SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc, (EXRHandJointType)FingerMap[i]); + } + else + { + NewGesture.FingerValues[i] = FOpenXRGestureFingerPosition(HandSkeletalAction->SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc, (EXRHandJointType)FingerMap[i]); + } + } + + NewGesture.Name = RecordingName; + GesturesDB->Gestures.Add(NewGesture); + + return true; + } + + return false; +} + + +bool UOpenXRHandPoseComponent::K2_DetectCurrentPose(UPARAM(ref) FBPOpenXRActionSkeletalData& SkeletalAction, FOpenXRGesture & GestureOut) +{ + if (!GesturesDB || GesturesDB->Gestures.Num() < 1) + return false; + + int32 FingerMap[5] = + { + (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT + }; + + FVector WristLoc = FVector::ZeroVector; + + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector); + } + else + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation(); + } + + // Early fill in an array to keep from performing math for each gesture + TArray<FVector> CurrentTips; + CurrentTips.AddUninitialized(5); + for (int i = 0; i < 5; ++i) + { + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc; + } + else + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc; + } + } + + for (const FOpenXRGesture& Gesture : GesturesDB->Gestures) + { + // If not enough indexs to match curl values, or if this gesture requires finger splay and the controller can't do it + if (Gesture.FingerValues.Num() < 5 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount) + continue; + + bool bDetectedPose = true; + for (int i = 0; i < 5; ++i) + { + FVector GestureV = Gesture.FingerValues[i].Value; + FVector CurrentV = CurrentTips[i]; + FVector Difference = GestureV - CurrentV; + + if (!Gesture.FingerValues[i].Value.Equals(CurrentTips[i], Gesture.FingerValues[i].Threshold)) + { + bDetectedPose = false; + break; + } + } + + if (bDetectedPose) + { + GestureOut = Gesture; + return true; + } + } + + return false; +} + +bool UOpenXRHandPoseComponent::DetectCurrentPose(FBPOpenXRActionSkeletalData &SkeletalAction) +{ + if (!GesturesDB || GesturesDB->Gestures.Num() < 1 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount) + return false; + + FTransform BoneTransform = FTransform::Identity; + + int32 FingerMap[5] = + { + (int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, + (int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT + }; + + FVector WristLoc = FVector::ZeroVector; + + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation().MirrorByVector(FVector::RightVector); + } + else + { + WristLoc = SkeletalAction.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT].GetLocation(); + } + + // Early fill in an array to keep from performing math for each gesture + TArray<FVector> CurrentTips; + CurrentTips.AddUninitialized(5); + for (int i = 0; i < 5; ++i) + { + if (SkeletalAction.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left) + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation().MirrorByVector(FVector::RightVector) - WristLoc; + } + else + { + CurrentTips[i] = SkeletalAction.SkeletalTransforms[FingerMap[i]].GetLocation() - WristLoc; + } + } + + for (auto GestureIterator = GesturesDB->Gestures.CreateConstIterator(); GestureIterator; ++GestureIterator) + { + const FOpenXRGesture &Gesture = *GestureIterator; + + // If not enough indexs to match curl values, or if this gesture requires finger splay and the controller can't do it + if (Gesture.FingerValues.Num() < 5 || SkeletalAction.SkeletalTransforms.Num() < EHandKeypointCount) + continue; + + bool bDetectedPose = true; + for (int i = 0; i < 5; ++i) + { + if (Gesture.FingerValues[i].Threshold <= 0.0f) + continue; + + if (!Gesture.FingerValues[i].Value.Equals(CurrentTips[i], Gesture.FingerValues[i].Threshold)) + { + bDetectedPose = false; + break; + } + } + + if (bDetectedPose) + { + if (SkeletalAction.LastHandGesture != Gesture.Name) + { + if (SkeletalAction.LastHandGesture != NAME_None) + OnGestureEnded.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand); + + SkeletalAction.LastHandGesture = Gesture.Name; + SkeletalAction.LastHandGestureIndex = GestureIterator.GetIndex(); + OnNewGestureDetected.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand); + + return true; + } + else + return false; // Same gesture + } + } + + if (SkeletalAction.LastHandGesture != NAME_None) + { + OnGestureEnded.Broadcast(SkeletalAction.LastHandGesture, SkeletalAction.LastHandGestureIndex, SkeletalAction.TargetHand); + SkeletalAction.LastHandGesture = NAME_None; + SkeletalAction.LastHandGestureIndex = INDEX_NONE; + } + + return false; +} + +UOpenXRHandPoseComponent::FTransformLerpManager::FTransformLerpManager() +{ + bReplicatedOnce = false; + bLerping = false; + UpdateCount = 0.0f; + UpdateRate = 0.0f; +} + +void UOpenXRHandPoseComponent::FTransformLerpManager::NotifyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate) +{ + UpdateRate = (1.0f / NetUpdateRate); + if (bReplicatedOnce) + { + bLerping = true; + UpdateCount = 0.0f; + NewTransforms = ActionInfo.SkeletalTransforms; + } + else + { + bReplicatedOnce = true; + } +} + +void UOpenXRHandPoseComponent::FTransformLerpManager::UpdateManager(float DeltaTime, FBPOpenXRActionSkeletalData& ActionInfo) +{ + if (!ActionInfo.bHasValidData) + return; + + if (bLerping) + { + UpdateCount += DeltaTime; + float LerpVal = FMath::Clamp(UpdateCount / UpdateRate, 0.0f, 1.0f); + + if (LerpVal >= 1.0f) + { + bLerping = false; + UpdateCount = 0.0f; + ActionInfo.SkeletalTransforms = NewTransforms; + } + else + { + int32 BoneCountAdjustment = 5 + (ActionInfo.bEnableUE4HandRepSavings ? 4 : 0); + if ((NewTransforms.Num() < (EHandKeypointCount - BoneCountAdjustment)) || (NewTransforms.Num() != ActionInfo.SkeletalTransforms.Num() || NewTransforms.Num() != ActionInfo.OldSkeletalTransforms.Num())) + { + return; + } + + ActionInfo.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_PALM_EXT] = FTransform::Identity; + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, ActionInfo, LerpVal); + + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, ActionInfo, LerpVal); + //BlendBone((uint8)EVROpenXRBones::eBone_Thumb3, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT, ActionInfo, LerpVal); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, ActionInfo, LerpVal); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT, ActionInfo, LerpVal); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, ActionInfo, LerpVal); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT, ActionInfo, LerpVal); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, ActionInfo, LerpVal); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + if (!ActionInfo.bEnableUE4HandRepSavings) + { + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT, ActionInfo, LerpVal); + } + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, ActionInfo, LerpVal); + BlendBone((int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, ActionInfo, LerpVal); + //BlendBone((uint8)EVROpenXRBones::eBone_IndexFinger4, ActionInfo, LerpVal); // Technically can be projected instead of blended + + // These are copied from the 3rd joints as they use the same transform but a different root + // Don't want to waste cpu time blending these + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_Thumb] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Thumb2]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_IndexFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_IndexFinger3]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_MiddleFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_MiddleFinger3]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_RingFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_RingFinger3]; + //ActionInfo.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_Aux_PinkyFinger] = ActionInfo.SkeletalData.SkeletalTransforms[(uint8)EVROpenXRBones::eBone_PinkyFinger3]; + } + } +} + +void FBPXRSkeletalRepContainer::CopyForReplication(FBPOpenXRActionSkeletalData& Other) +{ + TargetHand = Other.TargetHand; + + if (!Other.bHasValidData) + return; + + bAllowDeformingMesh = Other.bAllowDeformingMesh; + bEnableUE4HandRepSavings = Other.bEnableUE4HandRepSavings; + + // Instead of doing this, we likely need to lerp but this is for testing + //SkeletalTransforms = Other.SkeletalData.SkeletalTransforms; + + if (Other.SkeletalTransforms.Num() < EHandKeypointCount) + { + SkeletalTransforms.Empty(); + return; + } + + int32 BoneCountAdjustment = 5 + (bEnableUE4HandRepSavings ? 4 : 0); + + if (SkeletalTransforms.Num() != EHandKeypointCount - BoneCountAdjustment) + { + SkeletalTransforms.Reset(EHandKeypointCount - BoneCountAdjustment); // Minus bones we don't need + SkeletalTransforms.AddUninitialized(EHandKeypointCount - BoneCountAdjustment); + } + + int32 idx = 0; + // Root is always identity + //SkeletalTransforms[0] = Other.SkeletalData.SkeletalTransforms[(uint8)EVROpenInputBones::eBone_Root]; // This has no pos right? Need to skip pos on it + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT]; + + if (!bEnableUE4HandRepSavings) + { + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT]; + } + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT]; + SkeletalTransforms[idx++] = Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT]; +} + +void FBPXRSkeletalRepContainer::CopyReplicatedTo(const FBPXRSkeletalRepContainer& Container, FBPOpenXRActionSkeletalData& Other) +{ + int32 BoneCountAdjustment = 5 + (Container.bEnableUE4HandRepSavings ? 4 : 0); + if (Container.SkeletalTransforms.Num() < (EHandKeypointCount - BoneCountAdjustment)) + { + Other.SkeletalTransforms.Empty(); + Other.bHasValidData = false; + return; + } + + Other.bAllowDeformingMesh = Container.bAllowDeformingMesh; + Other.bEnableUE4HandRepSavings = Container.bEnableUE4HandRepSavings; + + // Instead of doing this, we likely need to lerp but this is for testing + //Other.SkeletalData.SkeletalTransforms = Container.SkeletalTransforms; + + if (Other.SkeletalTransforms.Num() != EHandKeypointCount) + Other.SkeletalTransforms.Reset(EHandKeypointCount); + { + Other.SkeletalTransforms.AddUninitialized(EHandKeypointCount); + } + + int32 idx = 0; + + // Only fill in the ones that we care about + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_PALM_EXT] = FTransform::Identity; // Always identity + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT] = Container.SkeletalTransforms[idx++]; + + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + + if (!Container.bEnableUE4HandRepSavings) + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT] = Container.SkeletalTransforms[idx++]; + } + else + { + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT] = FTransform::Identity; + } + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT] = Container.SkeletalTransforms[idx++]; + Other.SkeletalTransforms[(int32)EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT] = Container.SkeletalTransforms[idx++]; + + Other.bHasValidData = true; +} + +bool FBPXRSkeletalRepContainer::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + Ar.SerializeBits(&TargetHand, 1); + Ar.SerializeBits(&bAllowDeformingMesh, 1); + Ar.SerializeBits(&bEnableUE4HandRepSavings, 1); + + int32 BoneCountAdjustment = 5 + (bEnableUE4HandRepSavings ? 4 : 0); + uint8 TransformCount = EHandKeypointCount - BoneCountAdjustment; + + //Ar << TransformCount; + + if (Ar.IsLoading()) + { + SkeletalTransforms.Reset(TransformCount); + } + + FVector Position = FVector::ZeroVector; + FRotator Rot = FRotator::ZeroRotator; + + for (int i = 0; i < TransformCount; i++) + { + if (Ar.IsSaving()) + { + if (bAllowDeformingMesh) + Position = SkeletalTransforms[i].GetLocation(); + + Rot = SkeletalTransforms[i].Rotator(); + } + + if (bAllowDeformingMesh) + bOutSuccess &= SerializePackedVector<10, 11>(Position, Ar); + + Rot.SerializeCompressed(Ar); // Short? 10 bit? + + if (Ar.IsLoading()) + { + if (bAllowDeformingMesh) + SkeletalTransforms.Add(FTransform(Rot, Position)); + else + SkeletalTransforms.Add(FTransform(Rot)); + } + } + + return bOutSuccess; +} + +void UOpenXRAnimInstance::NativeBeginPlay() +{ + Super::NativeBeginPlay(); + + AActor* Owner = GetOwningComponent()->GetOwner(); + UActorComponent* HandPoseComp = nullptr; + + if (Owner) + { + HandPoseComp = Owner->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + + if (!HandPoseComp) + { + // We are also checking owner->owner in case hand mesh is in a sub actor + if (Owner->GetOwner()) + { + HandPoseComp = Owner->GetOwner()->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + } + } + } + + if (!HandPoseComp) + { + return; + } + + if (UOpenXRHandPoseComponent* HandComp = Cast<UOpenXRHandPoseComponent>(HandPoseComp)) + { + OwningPoseComp = HandComp; + } +} + +/*void UOpenXRAnimInstance::NativeInitializeAnimation() +{ + Super::NativeInitializeAnimation(); + + AActor* Owner = GetOwningComponent()->GetOwner(); + UActorComponent* HandPoseComp = nullptr; + + if (Owner) + { + HandPoseComp = Owner->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + + if (!HandPoseComp) + { + // We are also checking owner->owner in case hand mesh is in a sub actor + if (Owner->GetOwner()) + { + HandPoseComp = Owner->GetOwner()->GetComponentByClass(UOpenXRHandPoseComponent::StaticClass()); + } + } + } + + if (!HandPoseComp) + { + return; + } + + if (UOpenXRHandPoseComponent* HandComp = Cast<UOpenXRHandPoseComponent>(HandPoseComp)) + { + OwningPoseComp = HandComp; + } +}*/ + +void UOpenXRAnimInstance::InitializeCustomBoneMapping(UPARAM(ref) FBPOpenXRSkeletalMappingData& SkeletalMappingData) +{ + USkeleton* AssetSkeleton = this->CurrentSkeleton;//RequiredBones.GetSkeletonAsset(); + + if (AssetSkeleton) + { + FBoneContainer& RequiredBones = this->GetRequiredBones(); + for (FBPOpenXRSkeletalPair& BonePair : SkeletalMappingData.BonePairs) + { + // Fill in the bone name for the reference + BonePair.ReferenceToConstruct.BoneName = BonePair.BoneToTarget; + + // Init the reference + BonePair.ReferenceToConstruct.Initialize(AssetSkeleton); + BonePair.ReferenceToConstruct.CachedCompactPoseIndex = BonePair.ReferenceToConstruct.GetCompactPoseIndex(RequiredBones); + + if ((BonePair.ReferenceToConstruct.CachedCompactPoseIndex != INDEX_NONE)) + { + // Get our parent bones index + BonePair.ParentReference = RequiredBones.GetParentBoneIndex(BonePair.ReferenceToConstruct.CachedCompactPoseIndex); + } + } + + if (UObject* OwningAsset = RequiredBones.GetAsset()) + { + SkeletalMappingData.LastInitializedName = OwningAsset->GetFName(); + } + + SkeletalMappingData.bInitialized = true; + return; + } + + SkeletalMappingData.bInitialized = false; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/AnimNode_ApplyOpenXRHandPose.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/AnimNode_ApplyOpenXRHandPose.h new file mode 100644 index 0000000..eb49cd7 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/AnimNode_ApplyOpenXRHandPose.h @@ -0,0 +1,105 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Runtime/AnimGraphRuntime/Public/BoneControllers/AnimNode_SkeletalControlBase.h" +#include "OpenXRExpansionTypes.h" +//#include "Skeleton/BodyStateSkeleton.h" +//#include "BodyStateAnimInstance.h" + +#include "AnimNode_ApplyOpenXRHandPose.generated.h" + + +USTRUCT() +struct OPENXREXPANSIONPLUGIN_API FAnimNode_ApplyOpenXRHandPose : public FAnimNode_SkeletalControlBase +{ + GENERATED_USTRUCT_BODY() + +public: + + // Generally used when not passing in custom bone mappings, defines the auto mapping style + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + EVROpenXRSkeletonType SkeletonType; + + // If your hand is part of a full body or arm skeleton and you don't have a proxy bone to retain the position enable this + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + bool bSkipRootBone; + + // If you only want to use the wrist transform part of this + // This will also automatically add the deform to the wrist as it doesn't make much sense without it + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + bool bOnlyApplyWristTransform; + + // Generally used when not passing in custom bone mappings, defines the auto mapping style + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinShownByDefault)) + FBPOpenXRActionSkeletalData OptionalStoredActionInfo; + + // MappedBonePairs, if you leave it blank then they will auto generate based off of the SkeletonType + // Otherwise, fill out yourself. + UPROPERTY(EditAnywhere, Category = Skeletal, meta = (PinHiddenByDefault)) + FBPOpenXRSkeletalMappingData MappedBonePairs; + + bool bIsOpenInputAnimationInstance; + + void ConvertHandTransformsSpace(TArray<FTransform>& OutTransforms, TArray<FTransform>& WorldTransforms, FTransform AddTrans, bool bMirrorLeftRight, bool bMergeMissingUE4Bones); + + FTransform GetRefBoneInCS(TArray<FTransform>& RefBones, TArray<FMeshBoneInfo>& RefBonesInfo, int32 BoneIndex) + { + FTransform BoneTransform; + + if (BoneIndex >= 0) + { + BoneTransform = RefBones[BoneIndex]; + if (RefBonesInfo[BoneIndex].ParentIndex >= 0) + { + BoneTransform *= GetRefBoneInCS(RefBones, RefBonesInfo, RefBonesInfo[BoneIndex].ParentIndex); + } + } + + return BoneTransform; + } + + void SetVectorToMaxElement(FVector& vec) + { + float aX = FMath::Abs(vec.X); + float aY = FMath::Abs(vec.Y); + + if (aY < aX) + { + vec.Y = 0.f; + if (FMath::Abs(vec.Z) < aX) + vec.Z = 0.f; + else + vec.X = 0.f; + } + else + { + vec.X = 0.f; + if (FMath::Abs(vec.Z) < aY) + vec.Z = 0; + else + vec.Y = 0; + } + } + + // FAnimNode_SkeletalControlBase interface + //virtual void UpdateInternal(const FAnimationUpdateContext& Context) override; + virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms) override; + virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override; + // End of FAnimNode_SkeletalControlBase interface + virtual void OnInitializeAnimInstance(const FAnimInstanceProxy* InProxy, const UAnimInstance* InAnimInstance) override; + virtual bool NeedsOnInitializeAnimInstance() const override { return true; } + virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override; + + virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override; + virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override; + + // Constructor + FAnimNode_ApplyOpenXRHandPose(); + +protected: + bool WorldIsGame; + AActor* OwningActor; + +private: +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionFunctionLibrary.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionFunctionLibrary.h new file mode 100644 index 0000000..afdf59d --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionFunctionLibrary.h @@ -0,0 +1,374 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "HeadMountedDisplayTypes.h" +#if defined(OPENXR_SUPPORTED) +#include "OpenXRCore.h" +#include "OpenXRHMD.h" +#endif +#include "OpenXRExpansionTypes.h" +#include "HeadMountedDisplayFunctionLibrary.h" + +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" + +#include "OpenXRExpansionFunctionLibrary.generated.h" + +#if !defined(OPENXR_SUPPORTED) +class FOpenXRHMD; +#endif + +DECLARE_LOG_CATEGORY_EXTERN(OpenXRExpansionFunctionLibraryLog, Log, All); + +// This needs to be updated as the original gets changed, that or hope they make the original blueprint accessible. +UENUM(Blueprintable) +enum class EBPOpenXRControllerDeviceType : uint8 +{ + DT_SimpleController, + DT_ValveIndexController, + DT_ViveController, + DT_ViveProController, + //DT_CosmosController, + DT_DaydreamController, + DT_OculusTouchController, + DT_OculusGoController, + DT_MicrosoftMotionController, + DT_MicrosoftXboxController, + DT_UnknownController +}; + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) +class OPENXREXPANSIONPLUGIN_API UOpenXRExpansionFunctionLibrary : public UBlueprintFunctionLibrary +{ + //GENERATED_BODY() + GENERATED_BODY() + +public: + UOpenXRExpansionFunctionLibrary(const FObjectInitializer& ObjectInitializer); + + ~UOpenXRExpansionFunctionLibrary(); +public: + + static FOpenXRHMD* GetOpenXRHMD() + { +#if defined(OPENXR_SUPPORTED) + static FName SystemName(TEXT("OpenXR")); + if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName)) + { + return static_cast<FOpenXRHMD*>(GEngine->XRSystem.Get()); + } +#endif + + return nullptr; + } + + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true")) + static bool GetOpenXRHandPose(FBPOpenXRActionSkeletalData& HandPoseContainer, UOpenXRHandPoseComponent* HandPoseComponent, bool bGetMockUpPose = false) + { + FXRMotionControllerData MotionControllerData; + + if (bGetMockUpPose) + { + GetMockUpControllerData(MotionControllerData, HandPoseContainer); + return true; + } + + UHeadMountedDisplayFunctionLibrary::GetMotionControllerData((UObject*)HandPoseComponent, HandPoseContainer.TargetHand == EVRSkeletalHandIndex::EActionHandIndex_Left ? EControllerHand::Left : EControllerHand::Right, MotionControllerData); + + if (MotionControllerData.bValid) + { + HandPoseContainer.SkeletalTransforms.Empty(MotionControllerData.HandKeyPositions.Num()); + FTransform ParentTrans = FTransform::Identity; + + if (MotionControllerData.DeviceVisualType == EXRVisualType::Controller) + { + ParentTrans = FTransform(MotionControllerData.GripRotation, MotionControllerData.GripPosition, FVector(1.f)); + } + else // EXRVisualType::Hand visual type + { + ParentTrans = FTransform(MotionControllerData.HandKeyRotations[(uint8)EHandKeypoint::Palm], MotionControllerData.HandKeyPositions[(uint8)EHandKeypoint::Palm], FVector(1.f)); + } + + for (int i = 0; i < MotionControllerData.HandKeyPositions.Num(); i++) + { + // Convert to component space, we convert then to parent space later when applying it + HandPoseContainer.SkeletalTransforms.Add(FTransform(MotionControllerData.HandKeyRotations[i].GetNormalized(), MotionControllerData.HandKeyPositions[i], FVector(1.f)).GetRelativeTransform(ParentTrans)); + } + + HandPoseContainer.bHasValidData = true; + return true; + } + + HandPoseContainer.bHasValidData = false; + return false; + } + + //UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true")) + static void ConvertHandTransformsSpaceAndBack(TArray<FTransform>& OutTransforms, const TArray<FTransform>& WorldTransforms) + { + // Fail if the count is too low + if (WorldTransforms.Num() < EHandKeypointCount) + return; + + if (OutTransforms.Num() < WorldTransforms.Num()) + { + OutTransforms.Empty(WorldTransforms.Num()); + OutTransforms.AddUninitialized(WorldTransforms.Num()); + } + + // Bone/Parent map + int32 BoneParents[26] = + { + // Manually build the parent hierarchy starting at the wrist which has no parent (-1) + 1, // Palm -> Wrist + -1, // Wrist -> None + 1, // ThumbMetacarpal -> Wrist + 2, // ThumbProximal -> ThumbMetacarpal + 3, // ThumbDistal -> ThumbProximal + 4, // ThumbTip -> ThumbDistal + + 1, // IndexMetacarpal -> Wrist + 6, // IndexProximal -> IndexMetacarpal + 7, // IndexIntermediate -> IndexProximal + 8, // IndexDistal -> IndexIntermediate + 9, // IndexTip -> IndexDistal + + 1, // MiddleMetacarpal -> Wrist + 11, // MiddleProximal -> MiddleMetacarpal + 12, // MiddleIntermediate -> MiddleProximal + 13, // MiddleDistal -> MiddleIntermediate + 14, // MiddleTip -> MiddleDistal + + 1, // RingMetacarpal -> Wrist + 16, // RingProximal -> RingMetacarpal + 17, // RingIntermediate -> RingProximal + 18, // RingDistal -> RingIntermediate + 19, // RingTip -> RingDistal + + 1, // LittleMetacarpal -> Wrist + 21, // LittleProximal -> LittleMetacarpal + 22, // LittleIntermediate -> LittleProximal + 23, // LittleDistal -> LittleIntermediate + 24, // LittleTip -> LittleDistal + }; + + // Convert transforms to parent space + // The hand tracking transforms are in world space. + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + const FTransform& BoneTransform = WorldTransforms[Index]; + int32 ParentIndex = BoneParents[Index]; + int32 ParentParent = -1; + + if (ParentIndex > 0) + { + ParentParent = BoneParents[ParentIndex]; + } + + if (ParentIndex < 0) + { + // We are at the root, so use it. + OutTransforms[Index] = BoneTransform; + } + else + { + // Merging missing metacarpal bone into the transform + if (ParentParent == 1) // Wrist + { + OutTransforms[Index] = BoneTransform.GetRelativeTransform(WorldTransforms[ParentParent]); + } + else + { + OutTransforms[Index] = BoneTransform.GetRelativeTransform(WorldTransforms[ParentIndex]); + } + } + } + + // Check on the easy component space conversion first + { + for (int32 Index = 0; Index < EHandKeypointCount; ++Index) + { + const FTransform& BoneTransform = WorldTransforms[Index]; + int32 ParentIndex = BoneParents[Index]; + int32 ParentParent = -1; + + if (ParentIndex > 0) + { + ParentParent = BoneParents[ParentIndex]; + } + + if (ParentIndex > 0) + { + if (ParentParent == 1) + { + OutTransforms[Index] = OutTransforms[Index] * OutTransforms[ParentParent]; + } + else + { + OutTransforms[Index] = OutTransforms[Index] * OutTransforms[ParentIndex]; + } + } + } + } + } + + //UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true")) + static void GetMockUpControllerData(FXRMotionControllerData& MotionControllerData, FBPOpenXRActionSkeletalData& SkeletalMappingData, bool bOpenHand = false) + { + + + TArray<FQuat> HandRotationsClosed = { + // Closed palm + FQuat(0.419283837f,-0.547548413f,-0.704691410f,-0.166739136f), + FQuat(-0.494952023f,0.567746222f,0.647768855f,0.114382625f), + FQuat(0.105539620f,-0.079821065f,-0.934957743f,0.329157114f), + FQuat(0.309810340f,0.005135804f,-0.887668610f,0.340640306f), + FQuat(-0.292820454f,0.003018156f,0.892185807f,-0.343877673f), + FQuat(-0.292820454f,0.003018156f,0.892185807f,-0.343877673f), + FQuat(0.351302803f,-0.693441451f,-0.594165504f,-0.206626847f), + FQuat(0.510899961f,-0.668595433f,-0.531049490f,-0.099752367f), + FQuat(0.582462251f,-0.656170130f,-0.478819191f,0.030230284f), + FQuat(0.631917894f,-0.637212157f,-0.435243726f,0.072160020f), + FQuat(0.631917894f,-0.637212157f,-0.435243726f,0.072160020f), + FQuat(0.419282734f,-0.547549129f,-0.704691410f,-0.166739583f), + FQuat(0.514688492f,-0.678421855f,-0.501057625f,-0.154207960f), + FQuat(0.603475809f,-0.696219206f,-0.388458073f,0.014001161f), + FQuat(0.665590405f,-0.681470037f,-0.267755210f,0.144555300f), + FQuat(0.670854926f,-0.691665173f,-0.240195125f,0.117731705f), + FQuat(0.468501151f,-0.566464663f,-0.638139248f,-0.228914157f), + FQuat(0.541896224f,-0.702563524f,-0.417507589f,-0.196058303f), + FQuat(0.619513929f,-0.728625834f,-0.289327621f,-0.039927930f), + FQuat(0.676032484f,-0.712538362f,-0.151063532f,0.111561134f), + FQuat(0.676032484f,-0.712538362f,-0.151063532f,0.111561134f), + FQuat(0.541268349f,-0.622995615f,-0.511540473f,-0.239230901f), + FQuat(0.545463741f,-0.719190478f,-0.361613214f,-0.233387768f), + FQuat(0.613924086f,-0.754614115f,-0.223099023f,-0.062293097f), + FQuat(0.682628751f,-0.717284977f,-0.112533726f,0.082796305f), + FQuat(0.682628751f,-0.717284977f,-0.112533726f,0.082796305f) + }; + TArray<FQuat> HandRotationsOpen = { + // Open Hand + FQuat(0.167000905f,-0.670308471f,0.304047525f,-0.656011939f), + FQuat(-0.129862994f,0.736467659f,-0.315623045f,0.584065497f), + FQuat(-0.030090153f,0.121532254f,-0.840178490f,0.527659237f), + FQuat(-0.126470163f,-0.262596279f,0.816956878f,-0.497623593f), + FQuat(-0.102322638f,-0.249194950f,0.821705163f,-0.502227187f), + FQuat(-0.102322638f,-0.249194950f,0.821705163f,-0.502227187f), + FQuat(-0.277370781f,0.686735749f,-0.258646101f,0.620130479f), + FQuat(0.193366051f,-0.808131576f,0.260262072f,-0.491728455f), + FQuat(0.145547777f,-0.854364336f,0.317562312f,-0.384749293f), + FQuat(0.107193023f,-0.879853010f,0.321882188f,-0.332806766f), + FQuat(0.107193023f,-0.879853010f,0.321882188f,-0.332806766f), + FQuat(0.166999936f,-0.670307159f,0.304047883f,-0.656013489f), + FQuat(0.206125781f,-0.815250278f,0.203173012f,-0.501596987f), + FQuat(0.164493740f,-0.890369833f,0.257293820f,-0.337613612f), + FQuat(0.114019498f,-0.937856555f,0.283619940f,-0.164267495f), + FQuat(0.107336335f,-0.925720870f,0.321023613f,-0.168710276f), + FQuat(0.156629071f,-0.719596088f,0.210793152f,-0.642817795f), + FQuat(0.194258988f,-0.858762920f,0.127883837f,-0.456546605f), + FQuat(0.166189745f,-0.930981100f,0.161785051f,-0.281922638f), + FQuat(0.119936436f,-0.970744252f,0.189072192f,-0.086731322f), + FQuat(0.119936436f,-0.970744252f,0.189072192f,-0.086731322f), + FQuat(0.160288095f,-0.812664807f,0.100923792f,-0.551087677f), + FQuat(0.207311243f,-0.870556056f,0.056644741f,-0.442656904f), + FQuat(0.191506147f,-0.944826961f,0.096772499f,-0.247511998f), + FQuat(0.116890728f,-0.981477261f,0.138804480f,-0.061412390f), + FQuat(0.116890728f,-0.981477261f,0.138804480f,-0.061412390f) + }; + + MotionControllerData.HandKeyRotations = SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left ? HandRotationsOpen : HandRotationsClosed; + + TArray<FVector> HandPositionsClosed = { + // Closed palm - Left + FVector(-1203.819f,-516.869f,286.028f), + FVector(-1201.705f,-515.876f,287.796), + FVector(-1204.748f,-519.489f,288.397), + FVector(-1207.823f,-522.044f,287.811), + FVector(-1209.696f,-524.000f,286.012), + FVector(-1210.690f,-525.034f,285.137), + FVector(-1204.366f,-517.319f,288.299), + FVector(-1209.332f,-519.116f,283.063), + FVector(-1211.344f,-521.651f,280.094), + FVector(-1212.261f,-523.927f,278.608), + FVector(-1212.474f,-524.895f,278.097), + FVector(-1203.111f,-516.601f,286.951), + FVector(-1207.318f,-518.192f,281.462), + FVector(-1209.140f,-520.536f,278.337), + FVector(-1210.042f,-523.368f,276.842), + FVector(-1210.136f,-524.640f,276.636), + FVector(-1202.057f,-516.294f,286.121), + FVector(-1205.064f,-517.866f,280.471), + FVector(-1206.431f,-520.299f,277.509), + FVector(-1207.089f,-522.828f,276.311), + FVector(-1207.154f,-523.888f,276.263), + FVector(-1200.966f,-516.092f,285.475), + FVector(-1202.852f,-518.796f,280.113), + FVector(-1203.746f,-520.657f,277.907), + FVector(-1204.180f,-522.291f,277.237), + FVector(-1204.222f,-523.061f,277.211) + }; + + // Open Hand + TArray<FVector> HandPositionsOpen = { + FVector(-1014.001f,-478.278f,212.902f), + FVector(-1013.516f,-476.006f,214.688f), + FVector(-1016.362f,-479.642f,215.119f), + FVector(-1018.145f,-483.254f,214.805f), + FVector(-1019.682f,-485.682f,213.284f), + FVector(-1020.480f,-486.982f,212.581f), + FVector(-1014.360f,-478.927f,215.169f), + FVector(-1014.932f,-484.146f,209.902f), + FVector(-1016.872f,-486.643f,206.852f), + FVector(-1018.771f,-488.058f,205.231f), + FVector(-1019.613f,-488.507f,204.655f), + FVector(-1013.901f,-477.534f,213.831f), + FVector(-1014.494f,-481.954f,208.310f), + FVector(-1016.269f,-484.282f,205.146f), + FVector(-1018.657f,-485.834f,203.427f), + FVector(-1019.846f,-486.231f,203.113f), + FVector(-1013.816f,-476.436f,213.006f), + FVector(-1014.637f,-479.707f,207.344f), + FVector(-1016.703f,-481.540f,204.355f), + FVector(-1018.962f,-482.692f,203.000f), + FVector(-1019.978f,-482.975f,202.870f), + FVector(-1013.845f,-475.325f,212.363f), + FVector(-1015.993f,-477.665f,206.928f), + FVector(-1017.571f,-478.907f,204.670f), + FVector(-1019.033f,-479.652f,203.887f), + FVector(-1019.778f,-479.842f,203.819f) + }; + + MotionControllerData.HandKeyPositions = SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left ? HandPositionsOpen : HandPositionsClosed; + + if (SkeletalMappingData.TargetHand != EVRSkeletalHandIndex::EActionHandIndex_Left) + { + MotionControllerData.GripPosition = FVector(-1018.305f, -478.019f, 209.872f); + MotionControllerData.GripRotation = FQuat(-0.116352126f, 0.039430488f, -0.757644236f, 0.641001403f); + } + else + { + MotionControllerData.GripPosition = FVector(-1202.619f, -521.077f, 283.076f); + MotionControllerData.GripRotation = FQuat(0.040843058f, 0.116659224f, 0.980030060f, -0.155767411f); + } + + MotionControllerData.DeviceName = TEXT("OpenXR"); + + SkeletalMappingData.bHasValidData = true; + SkeletalMappingData.SkeletalTransforms.Empty(SkeletalMappingData.SkeletalTransforms.Num()); + FTransform ParentTrans = FTransform(MotionControllerData.GripRotation, MotionControllerData.GripPosition, FVector(1.f)); + for (int i = 0; i < MotionControllerData.HandKeyPositions.Num(); i++) + { + SkeletalMappingData.SkeletalTransforms.Add(FTransform(MotionControllerData.HandKeyRotations[i], MotionControllerData.HandKeyPositions[i], FVector(1.f)).GetRelativeTransform(ParentTrans)); + } + } + + // Get a list of all currently tracked devices and their types, index in the array is their device index + // Returns failed if the openXR query failed (no interaction profile yet or openXR is not running) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|OpenXR", meta = (bIgnoreSelf = "true", ExpandEnumAsExecs = "Result")) + static void GetXRMotionControllerType(FString& TrackingSystemName, EBPOpenXRControllerDeviceType& DeviceType, EBPXRResultSwitch &Result); +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionPlugin.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionPlugin.h new file mode 100644 index 0000000..23d057c --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionPlugin.h @@ -0,0 +1,18 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" + +class FOpenXRExpansionPluginModule : public IModuleInterface +{ +public: + + FOpenXRExpansionPluginModule() + { + } + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionTypes.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionTypes.h new file mode 100644 index 0000000..9c58723 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRExpansionTypes.h @@ -0,0 +1,324 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" + +#include "OpenXRExpansionTypes.generated.h" + +// This makes a lot of the blueprint functions cleaner +UENUM() +enum class EBPXRResultSwitch : uint8 +{ + // On Success + OnSucceeded, + // On Failure + OnFailed +}; + +UENUM(BlueprintType) +enum class EVRSkeletalHandIndex : uint8 +{ + EActionHandIndex_Left = 0, + EActionHandIndex_Right +}; + +UENUM(BlueprintType) +enum class EXRHandJointType : uint8 +{ + OXR_HAND_JOINT_PALM_EXT = 0, + OXR_HAND_JOINT_WRIST_EXT = 1, + OXR_HAND_JOINT_THUMB_METACARPAL_EXT = 2, + OXR_HAND_JOINT_THUMB_PROXIMAL_EXT = 3, + OXR_HAND_JOINT_THUMB_DISTAL_EXT = 4, + OXR_HAND_JOINT_THUMB_TIP_EXT = 5, + OXR_HAND_JOINT_INDEX_METACARPAL_EXT = 6, + OXR_HAND_JOINT_INDEX_PROXIMAL_EXT = 7, + OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT = 8, + OXR_HAND_JOINT_INDEX_DISTAL_EXT = 9, + OXR_HAND_JOINT_INDEX_TIP_EXT = 10, + OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT = 11, + OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT = 12, + OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT = 13, + OXR_HAND_JOINT_MIDDLE_DISTAL_EXT = 14, + OXR_HAND_JOINT_MIDDLE_TIP_EXT = 15, + OXR_HAND_JOINT_RING_METACARPAL_EXT = 16, + OXR_HAND_JOINT_RING_PROXIMAL_EXT = 17, + OXR_HAND_JOINT_RING_INTERMEDIATE_EXT = 18, + OXR_HAND_JOINT_RING_DISTAL_EXT = 19, + OXR_HAND_JOINT_RING_TIP_EXT = 20, + OXR_HAND_JOINT_LITTLE_METACARPAL_EXT = 21, + OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT = 22, + OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT = 23, + OXR_HAND_JOINT_LITTLE_DISTAL_EXT = 24, + OXR_HAND_JOINT_LITTLE_TIP_EXT = 25, + OXR_HAND_JOINT_MAX_ENUM_EXT = 0xFF +}; + +UENUM(BlueprintType) +enum class EVROpenXRSkeletonType : uint8 +{ + OXR_SkeletonType_UE4Default_Right, + OXR_SkeletonType_UE4Default_Left, + OXR_SkeletonType_OpenVRDefault_Right, + OXR_SkeletonType_OpenVRDefault_Left, + OXR_SkeletonType_Custom +}; + + + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|OpenXR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPOpenXRActionSkeletalData +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + EVRSkeletalHandIndex TargetHand; + + // A world scale override that will replace the engines current value and force into the tracked data if non zero + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + float WorldScaleOverride; + + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + bool bAllowDeformingMesh; + + // If true then the bones will be mirrored from left/right, to allow you to swap a hand mesh or apply to a full body mesh + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + bool bMirrorLeftRight; + + UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default) + TArray<FTransform> SkeletalTransforms; + + // If true we will assume that the target skeleton does not have the metacarpal bones and we will not replicate them + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + bool bEnableUE4HandRepSavings; + + UPROPERTY(BlueprintReadOnly, NotReplicated, Transient, Category = Default) + TArray<FTransform> OldSkeletalTransforms; + + // The rotation required to rotate the finger bones back to X+ + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadWrite, Category = Default) + FTransform AdditionTransform; + + UPROPERTY(NotReplicated, BlueprintReadOnly, Category = Default) + bool bHasValidData; + + FName LastHandGesture; + int32 LastHandGestureIndex; + + FBPOpenXRActionSkeletalData() + { + //bGetTransformsInParentSpace = false; + AdditionTransform = FTransform(FRotator(180.f, 0.f, -90.f), FVector::ZeroVector, FVector(1.f));//FTransform(FRotator(0.f, 90.f, 90.f), FVector::ZeroVector, FVector(1.f)); + WorldScaleOverride = 0.0f; + bAllowDeformingMesh = true; + bMirrorLeftRight = false; + bEnableUE4HandRepSavings = true; + TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right; + bHasValidData = false; + LastHandGestureIndex = INDEX_NONE; + LastHandGesture = NAME_None; + } +}; + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPOpenXRSkeletalPair +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + EXRHandJointType OpenXRBone; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + FName BoneToTarget; + + FBoneReference ReferenceToConstruct; + FCompactPoseBoneIndex ParentReference; + FQuat RetargetRot; + + FBPOpenXRSkeletalPair() : + ParentReference(INDEX_NONE) + { + OpenXRBone = EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT; + BoneToTarget = NAME_None; + RetargetRot = FQuat::Identity; + } + + FBPOpenXRSkeletalPair(EXRHandJointType Bone, FString TargetBone) : + ParentReference(INDEX_NONE) + { + OpenXRBone = Bone; + BoneToTarget = FName(*TargetBone); + ReferenceToConstruct.BoneName = BoneToTarget; + RetargetRot = FQuat::Identity; + } + + FORCEINLINE bool operator==(const int32& Other) const + { + return ReferenceToConstruct.CachedCompactPoseIndex.GetInt() == Other; + //return ReferenceToConstruct.BoneIndex == Other; + } +}; + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|SteamVR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPOpenXRSkeletalMappingData +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + TArray<FBPOpenXRSkeletalPair> BonePairs; + + // Merge the transforms of bones that are missing from the OpenVR skeleton to the UE4 one. + // This should be always enabled for UE4 skeletons generally. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + bool bMergeMissingBonesUE4; + + // The hand data to get, if not using a custom bone mapping then this value will be auto filled + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Default") + EVRSkeletalHandIndex TargetHand; + + FQuat AdjustmentQuat; + bool bInitialized; + + FName LastInitializedName; + + void ConstructDefaultMappings(EVROpenXRSkeletonType SkeletonType, bool bSkipRootBone) + { + switch (SkeletonType) + { + + case EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left: + case EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Right: + { + bMergeMissingBonesUE4 = false; + SetDefaultOpenVRInputs(SkeletonType, bSkipRootBone); + }break; + case EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Left: + case EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Right: + { + bMergeMissingBonesUE4 = true; + SetDefaultUE4Inputs(SkeletonType, bSkipRootBone); + }break; + } + } + + void SetDefaultOpenVRInputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone) + { + // Don't map anything if the end user already has + if (BonePairs.Num()) + return; + + bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_OpenVRDefault_Left; + FString HandDelimiterS = !bIsRightHand ? "l" : "r"; + const TCHAR* HandDelimiter = *HandDelimiterS; + + TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + + // Default OpenVR bones mapping + //if (!bSkipRootBone) + //{ + //BonePairs.Add(FBPOpenVRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_PALM_EXT, FString::Printf(TEXT("Root"), HandDelimiter))); + //} + + if (!bSkipRootBone) + { + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("wrist_%s"), HandDelimiter))); + } + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("finger_thumb_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("finger_thumb_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("finger_thumb_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT, FString::Printf(TEXT("finger_thumb_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_METACARPAL_EXT, FString::Printf(TEXT("finger_index_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("finger_index_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_index_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("finger_index_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT, FString::Printf(TEXT("finger_index_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_METACARPAL_EXT, FString::Printf(TEXT("finger_middle_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("finger_middle_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_middle_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("finger_middle_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT, FString::Printf(TEXT("finger_middle_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_METACARPAL_EXT, FString::Printf(TEXT("finger_ring_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("finger_ring_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_ring_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("finger_ring_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT, FString::Printf(TEXT("finger_ring_%s_end"), HandDelimiter))); + + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_METACARPAL_EXT, FString::Printf(TEXT("finger_pinky_meta_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("finger_pinky_0_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("finger_pinky_1_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("finger_pinky_2_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT, FString::Printf(TEXT("finger_pinky_%s_end"), HandDelimiter))); + + // Aux bones share the final knuckles location / rotation + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("finger_thumb_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("finger_index_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("finger_middle_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("finger_ring_%s_aux"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("finger_pinky_%s_aux"), HandDelimiter))); + } + + void SetDefaultUE4Inputs(EVROpenXRSkeletonType cSkeletonType, bool bSkipRootBone) + { + // Don't map anything if the end user already has + if (BonePairs.Num()) + return; + + bool bIsRightHand = cSkeletonType != EVROpenXRSkeletonType::OXR_SkeletonType_UE4Default_Left; + FString HandDelimiterS = !bIsRightHand ? "l" : "r"; + const TCHAR* HandDelimiter = *HandDelimiterS; + + TargetHand = bIsRightHand ? EVRSkeletalHandIndex::EActionHandIndex_Right : EVRSkeletalHandIndex::EActionHandIndex_Left; + + // Default ue4 skeleton hand to the OpenVR bones, skipping the extra joint and the aux joints + if (!bSkipRootBone) + { + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_WRIST_EXT, FString::Printf(TEXT("hand_%s"), HandDelimiter))); + } + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_PROXIMAL_EXT, FString::Printf(TEXT("index_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_INTERMEDIATE_EXT, FString::Printf(TEXT("index_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_INDEX_DISTAL_EXT, FString::Printf(TEXT("index_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_PROXIMAL_EXT, FString::Printf(TEXT("middle_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT, FString::Printf(TEXT("middle_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_MIDDLE_DISTAL_EXT, FString::Printf(TEXT("middle_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_PROXIMAL_EXT, FString::Printf(TEXT("pinky_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT, FString::Printf(TEXT("pinky_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_LITTLE_DISTAL_EXT, FString::Printf(TEXT("pinky_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_PROXIMAL_EXT, FString::Printf(TEXT("ring_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_INTERMEDIATE_EXT, FString::Printf(TEXT("ring_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_RING_DISTAL_EXT, FString::Printf(TEXT("ring_03_%s"), HandDelimiter))); + + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_METACARPAL_EXT, FString::Printf(TEXT("thumb_01_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_PROXIMAL_EXT, FString::Printf(TEXT("thumb_02_%s"), HandDelimiter))); + BonePairs.Add(FBPOpenXRSkeletalPair(EXRHandJointType::OXR_HAND_JOINT_THUMB_DISTAL_EXT, FString::Printf(TEXT("thumb_03_%s"), HandDelimiter))); + + } + + FBPOpenXRSkeletalMappingData() + { + AdjustmentQuat = FQuat::Identity; + bInitialized = false; + bMergeMissingBonesUE4 = false; + TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Right; + LastInitializedName = NAME_None; + } +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRHandPoseComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRHandPoseComponent.h new file mode 100644 index 0000000..2f9b265 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/OpenXRExpansionPlugin/Source/OpenXRExpansionPlugin/Public/OpenXRHandPoseComponent.h @@ -0,0 +1,351 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Engine/Texture.h" +#include "Engine/EngineTypes.h" +#include "HeadMountedDisplayTypes.h" +//#include "Runtime/Launch/Resources/Version.h" + +#include "Animation/AnimInstanceProxy.h" +#include "OpenXRExpansionTypes.h" +#include "Engine/DataAsset.h" + +#include "OpenXRHandPoseComponent.generated.h" + +USTRUCT(BlueprintType, Category = "VRExpansionFunctions|OpenXR|HandSkeleton") +struct OPENXREXPANSIONPLUGIN_API FBPXRSkeletalRepContainer +{ + GENERATED_BODY() +public: + + UPROPERTY(Transient, NotReplicated) + EVRSkeletalHandIndex TargetHand; + + UPROPERTY(Transient, NotReplicated) + bool bAllowDeformingMesh; + + // If true we will skip sending the 4 metacarpal bones that ue4 doesn't need, (STEAMVR skeletons need this disabled!) + UPROPERTY(Transient, NotReplicated) + bool bEnableUE4HandRepSavings; + + UPROPERTY(Transient, NotReplicated) + TArray<FTransform> SkeletalTransforms; + + UPROPERTY(Transient, NotReplicated) + uint8 BoneCount; + + + FBPXRSkeletalRepContainer() + { + TargetHand = EVRSkeletalHandIndex::EActionHandIndex_Left; + bAllowDeformingMesh = false; + bEnableUE4HandRepSavings = true; + BoneCount = 0; + } + + bool bHasValidData() + { + return SkeletalTransforms.Num() > 0; + } + + void CopyForReplication(FBPOpenXRActionSkeletalData& Other); + static void CopyReplicatedTo(const FBPXRSkeletalRepContainer& Container, FBPOpenXRActionSkeletalData& Other); + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); +}; + +template<> +struct TStructOpsTypeTraits< FBPXRSkeletalRepContainer > : public TStructOpsTypeTraitsBase2<FBPXRSkeletalRepContainer> +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct OPENXREXPANSIONPLUGIN_API FOpenXRGestureFingerPosition +{ + GENERATED_BODY() +public: + + // The Finger index, not editable + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "VRGesture") + EXRHandJointType IndexType; + + // The locational value of this element 0.f - 1.f + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FVector Value; + + // The threshold within which this finger value will be detected as matching (1.0 would be always matching, IE: finger doesn't count) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture", meta = (ClampMin = "0.0", ClampMax = "100.0", UIMin = "0.0", UIMax = "100.0")) + float Threshold; + + FOpenXRGestureFingerPosition(FVector TipLoc, EXRHandJointType Type) + { + IndexType = Type; + Value = TipLoc; + Threshold = 5.0f; + } + FOpenXRGestureFingerPosition() + { + IndexType = EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT; + Value = FVector(0.f); + Threshold = 5.0f; + } +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct OPENXREXPANSIONPLUGIN_API FOpenXRGesture +{ + GENERATED_BODY() +public: + + // Name of the recorded gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FName Name; + + // Samples in the recorded gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + TArray<FOpenXRGestureFingerPosition> FingerValues; + + FOpenXRGesture() + { + InitPoseValues(); + Name = NAME_None; + } + + void InitPoseValues() + { + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_THUMB_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_INDEX_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_MIDDLE_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_RING_TIP_EXT)); + FingerValues.Add(FOpenXRGestureFingerPosition(FVector::ZeroVector, EXRHandJointType::OXR_HAND_JOINT_LITTLE_TIP_EXT)); + } +}; + +/** +* Items Database DataAsset, here we can save all of our game items +*/ +UCLASS(BlueprintType, Category = "VRGestures") +class OPENXREXPANSIONPLUGIN_API UOpenXRGestureDatabase : public UDataAsset +{ + GENERATED_BODY() +public: + + // Gestures in this database + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TArray <FOpenXRGesture> Gestures; + + UOpenXRGestureDatabase() + { + } +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOpenXRGestureDetected, const FName &, GestureDetected, int32, GestureIndex, EVRSkeletalHandIndex, ActionHandType); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOpenXRGestureEnded, const FName &, GestureEnded, int32, GestureIndex, EVRSkeletalHandIndex, ActionHandType); + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent)) +class OPENXREXPANSIONPLUGIN_API UOpenXRHandPoseComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UOpenXRHandPoseComponent(const FObjectInitializer& ObjectInitializer); + + // Says whether we should run gesture detection + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + bool bDetectGestures; + + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void SetDetectGestures(bool bNewDetectGestures) + { + bDetectGestures = bNewDetectGestures; + } + + UPROPERTY(BlueprintAssignable, Category = "VRGestures") + FOpenXRGestureDetected OnNewGestureDetected; + + UPROPERTY(BlueprintAssignable, Category = "VRGestures") + FOpenXRGestureEnded OnGestureEnded; + + // Known sequences + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + UOpenXRGestureDatabase *GesturesDB; + + UFUNCTION(BlueprintCallable, Category = "VRGestures") + bool SaveCurrentPose(FName RecordingName, EVRSkeletalHandIndex HandToSave = EVRSkeletalHandIndex::EActionHandIndex_Right); + + UFUNCTION(BlueprintCallable, Category = "VRGestures", meta = (DisplayName = "DetectCurrentPose")) + bool K2_DetectCurrentPose(UPARAM(ref) FBPOpenXRActionSkeletalData& SkeletalAction, FOpenXRGesture & GestureOut); + + // This version throws events + bool DetectCurrentPose(FBPOpenXRActionSkeletalData& SkeletalAction); + + // Need this as I can't think of another way for an actor component to make sure it isn't on the server + inline bool IsLocallyControlled() const + { +#if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 22 + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); +#else + // I like epics new authority check more than mine + const AActor* MyOwner = GetOwner(); + const APawn* MyPawn = Cast<APawn>(MyOwner); + return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner && MyOwner->GetLocalRole() == ENetRole::ROLE_Authority); +#endif + } + + // Using tick and not timers because skeletal components tick anyway, kind of a waste to make another tick by adding a timer over that + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + + //virtual void OnUnregister() override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SkeletalData|Actions") + bool bGetMockUpPoseForDebugging; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SkeletalData|Actions") + TArray<FBPOpenXRActionSkeletalData> HandSkeletalActions; + + UPROPERTY(Replicated, Transient, ReplicatedUsing = OnRep_SkeletalTransformLeft) + FBPXRSkeletalRepContainer LeftHandRep; + + UPROPERTY(Replicated, Transient, ReplicatedUsing = OnRep_SkeletalTransformRight) + FBPXRSkeletalRepContainer RightHandRep; + + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendSkeletalTransforms(const FBPXRSkeletalRepContainer& SkeletalInfo); + + bool bLerpingPositionLeft; + bool bReppedOnceLeft; + + bool bLerpingPositionRight; + bool bReppedOnceRight; + + struct FTransformLerpManager + { + bool bReplicatedOnce; + bool bLerping; + float UpdateCount; + float UpdateRate; + TArray<FTransform> NewTransforms; + + FTransformLerpManager(); + void NotifyNewData(FBPOpenXRActionSkeletalData& ActionInfo, int NetUpdateRate); + + FORCEINLINE void BlendBone(uint8 BoneToBlend, FBPOpenXRActionSkeletalData& ActionInfo, float & LerpVal) + { + ActionInfo.SkeletalTransforms[BoneToBlend].Blend(ActionInfo.OldSkeletalTransforms[BoneToBlend], NewTransforms[BoneToBlend], LerpVal); + } + + void UpdateManager(float DeltaTime, FBPOpenXRActionSkeletalData& ActionInfo); + + }; + + FTransformLerpManager LeftHandRepManager; + FTransformLerpManager RightHandRepManager; + + UFUNCTION() + virtual void OnRep_SkeletalTransformLeft() + { + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandSkeletalActions[i].TargetHand == LeftHandRep.TargetHand) + { + HandSkeletalActions[i].OldSkeletalTransforms = HandSkeletalActions[i].SkeletalTransforms; + + FBPXRSkeletalRepContainer::CopyReplicatedTo(LeftHandRep, HandSkeletalActions[i]); + + if(bSmoothReplicatedSkeletalData) + LeftHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations); + break; + } + } + } + + UFUNCTION() + virtual void OnRep_SkeletalTransformRight() + { + for (int i = 0; i < HandSkeletalActions.Num(); i++) + { + if (HandSkeletalActions[i].TargetHand == RightHandRep.TargetHand) + { + HandSkeletalActions[i].OldSkeletalTransforms = HandSkeletalActions[i].SkeletalTransforms; + + FBPXRSkeletalRepContainer::CopyReplicatedTo(RightHandRep, HandSkeletalActions[i]); + + if (bSmoothReplicatedSkeletalData) + RightHandRepManager.NotifyNewData(HandSkeletalActions[i], ReplicationRateForSkeletalAnimations); + break; + } + } + } + + // If we should replicate the skeletal transform data + UPROPERTY(EditAnywhere, Category = SkeletalData) + bool bReplicateSkeletalData; + + // If true we will lerp between updates of the skeletal mesh transforms and smooth the result + UPROPERTY(EditAnywhere, Category = SkeletalData) + bool bSmoothReplicatedSkeletalData; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SkeletalData) + float ReplicationRateForSkeletalAnimations; + + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position + float SkeletalNetUpdateCount; + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position + float SkeletalUpdateCount; +}; + +USTRUCT() +struct OPENXREXPANSIONPLUGIN_API FOpenXRAnimInstanceProxy : public FAnimInstanceProxy +{ +public: + GENERATED_BODY() + + FOpenXRAnimInstanceProxy() {} + FOpenXRAnimInstanceProxy(UAnimInstance* InAnimInstance); + + /** Called before update so we can copy any data we need */ + virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override; + +public: + + EVRSkeletalHandIndex TargetHand; + TArray<FBPOpenXRActionSkeletalData> HandSkeletalActionData; + +}; + +UCLASS(transient, Blueprintable, hideCategories = AnimInstance, BlueprintType) +class OPENXREXPANSIONPLUGIN_API UOpenXRAnimInstance : public UAnimInstance +{ + GENERATED_BODY() + +public: + + UPROPERTY(transient) + UOpenXRHandPoseComponent * OwningPoseComp; + + FOpenXRAnimInstanceProxy AnimInstanceProxy; + + virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override + { + return new FOpenXRAnimInstanceProxy(this); + //return &AnimInstanceProxy; + } + + virtual void NativeBeginPlay() override; + + //virtual void NativeInitializeAnimation() override; + + UFUNCTION(BlueprintCallable, Category = "BoneMappings") + void InitializeCustomBoneMapping(UPARAM(ref) FBPOpenXRSkeletalMappingData & SkeletalMappingData); + + +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/README.md b/ExcavatorSimulator/Plugins/VRExpansionPlugin/README.md new file mode 100644 index 0000000..7ffbbb5 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/README.md @@ -0,0 +1,48 @@ +UE4 Forums Thread +https://forums.unrealengine.com/development-discussion/vr-ar-development/89050-vr-openvr-expansion-plugin + +Example Template Project +https://github.com/mordentral/VRExpPluginExample + +Website: +www.vreue4.com + +*** + +### Use Of This Plugin ### + +This Plugin is intended to add additional functionality to Open/SteamVR/(All VR now) in UE4. + +### Plugin Website ### +[VREUE4.com](https://vreue4.com) + +### How do I install it? ### + +https://vreue4.com/documentation?section=installation + +**Guides for migrating between different engine versions of the plugin:** + +View the patch notes at www.vreue4.com for migration guides as well. + +**Option 1:** + +Go to www.vreue4.com and downloaded the pre-built binary version for the engine version you are using (not updated with every daily change, only weekly or with large patches). + +Install it into your Engine/Plugins directoy or ProjectName/Plugins Directory. + +**Option 2 (More up to date - preferred if possible):** + +* Clone Or Download Zip and extract this repository to a folder named "VRExpansionPlugin" in your "ProjectName/Plugins" directory, create this directory if it is missing. + +* Add the VRExpansionPlugin to your projects PublicDependencyModuleNames in the projects build.cs if you have c++ code included. + +* IF you do not have c++ code, use the Add New button in the editor and add a blank c++ class to your project. + +* Open up the generated project .SLN file and build the project from the build menu. + +You need to have visual studio installed and follow the UE4 setup guide for it: https://docs.unrealengine.com/latest/INT/Programming/Development/VisualStudioSetup/ + +### How do I use it? ### +### How do I VR? ### + +The template project contains use examples of most of the features of the plugin as well as locomotion modes, interaction methods, and basic multiplayer. diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Config/FilterPlugin.ini b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Config/FilterPlugin.ini new file mode 100644 index 0000000..ccebca2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Resources/Icon128.png b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Resources/Icon128.png new file mode 100644 index 0000000..23fec31 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Resources/Icon128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb8f329acc9d0c2815d28349d5a4144ebd337f7e2a93819cf1a7c3cc9b06ecea +size 16085 diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketComponentDetails.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketComponentDetails.cpp new file mode 100644 index 0000000..8efc8ed --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketComponentDetails.cpp @@ -0,0 +1,862 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "HandSocketComponentDetails.h" +#include "HandSocketVisualizer.h" +//#include "PropertyEditing.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SButton.h" +#include "PropertyHandle.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "DetailCategoryBuilder.h" +#include "IDetailsView.h" + +#include "Developer/AssetTools/Public/IAssetTools.h" +#include "Developer/AssetTools/Public/AssetToolsModule.h" +#include "Editor/ContentBrowser/Public/IContentBrowserSingleton.h" +#include "Editor/ContentBrowser/Public/ContentBrowserModule.h" +#include "AnimationUtils.h" +#include "AssetRegistryModule.h" +#include "UObject/SavePackage.h" +#include "Misc/MessageDialog.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Editor.h" +#include "EditorStyleSet.h" +#include "Styling/CoreStyle.h" + +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" + +#define LOCTEXT_NAMESPACE "HandSocketComponentDetails" + +FText SCreateHandAnimationDlg::LastUsedAssetPath; + +static bool PromptUserForAssetPath(FString& AssetPath, FString& AssetName) +{ + TSharedRef<SCreateHandAnimationDlg> NewAnimDlg = SNew(SCreateHandAnimationDlg); + if (NewAnimDlg->ShowModal() != EAppReturnType::Cancel) + { + AssetPath = NewAnimDlg->GetFullAssetPath(); + AssetName = NewAnimDlg->GetAssetName(); + return true; + } + + return false; +} + +TWeakObjectPtr<UAnimSequence> FHandSocketComponentDetails::SaveAnimationAsset(const FString& InAssetPath, const FString& InAssetName) +{ + + TWeakObjectPtr<UAnimSequence> FinalAnimation; + + // Replace when this moves to custom display + if (!HandSocketComponent.IsValid()) + return FinalAnimation; + + /*if (!HandSocketComponent->HandVisualizerComponent)// || !HandSocketComponent->HandVisualizerComponent->SkeletalMesh || !HandSocketComponent->HandVisualizerComponent->SkeletalMesh->Skeleton) + { + return false; + }*/ + + if (!HandSocketComponent->HandTargetAnimation && (!HandSocketComponent->VisualizationMesh || !HandSocketComponent->VisualizationMesh->GetSkeleton())) + { + return FinalAnimation; + } + + // create the asset + FText InvalidPathReason; + bool const bValidPackageName = FPackageName::IsValidLongPackageName(InAssetPath, false, &InvalidPathReason); + if (bValidPackageName == false) + { + UE_LOG(LogAnimation, Log, TEXT("%s is an invalid asset path, prompting user for new asset path. Reason: %s"), *InAssetPath, *InvalidPathReason.ToString()); + } + + FString ValidatedAssetPath = InAssetPath; + FString ValidatedAssetName = InAssetName; + + UObject* Parent = bValidPackageName ? CreatePackage(*ValidatedAssetPath) : nullptr; + if (Parent == nullptr) + { + // bad or no path passed in, do the popup + if (PromptUserForAssetPath(ValidatedAssetPath, ValidatedAssetName) == false) + { + return FinalAnimation; + } + + Parent = CreatePackage(*ValidatedAssetPath); + } + + UObject* const Object = LoadObject<UObject>(Parent, *ValidatedAssetName, nullptr, LOAD_Quiet, nullptr); + // if object with same name exists, warn user + if (Object) + { + EAppReturnType::Type ReturnValue = FMessageDialog::Open(EAppMsgType::YesNo, NSLOCTEXT("UnrealEd", "Error_AssetExist", "Asset with same name exists. Do you wish to overwrite it?")); + if (ReturnValue == EAppReturnType::No) + { + return FinalAnimation; // failed + } + } + + UAnimSequence* BaseAnimation = HandSocketComponent->HandTargetAnimation; + TArray<FTransform> LocalPoses; + + if (!BaseAnimation) + { + LocalPoses = HandSocketComponent->VisualizationMesh->GetSkeleton()->GetRefLocalPoses(); + } + + // If not, create new one now. + UAnimSequence* const NewSeq = NewObject<UAnimSequence>(Parent, *ValidatedAssetName, RF_Public | RF_Standalone); + if (NewSeq) + { + // set skeleton + if (BaseAnimation) + { + NewSeq->SetSkeleton(BaseAnimation->GetSkeleton()); + } + else + { + NewSeq->SetSkeleton(HandSocketComponent->VisualizationMesh->GetSkeleton()); + } + + // Notify the asset registry + FAssetRegistryModule::AssetCreated(NewSeq); + //StartRecord(Component, NewSeq); + + //return true; + UAnimSequence* AnimationObject = NewSeq; + + IAnimationDataController& AnimController = AnimationObject->GetController(); + { + IAnimationDataController::FScopedBracket ScopedBracket(AnimController, LOCTEXT("SaveAnimationAsset_VRE", "Creating Animation Sequence based on hand pose")); + AnimationObject->ResetAnimation(); + if (BaseAnimation) + { + AnimationObject->BoneCompressionSettings = BaseAnimation->BoneCompressionSettings; + } + else + { + AnimationObject->BoneCompressionSettings = FAnimationUtils::GetDefaultAnimationBoneCompressionSettings(); + } + + + //AnimationObject->SetSequenceLength(4.f); + AnimController.SetPlayLength(4.f); + //AnimationObject->SetRawNumberOfFrame(1); + AnimController.SetFrameRate(FFrameRate(1.f / 4.f, 1)); + + TArray<FName> TrackNames; + UAnimDataModel* BaseDataModel = nullptr; + + if (BaseAnimation) + { + BaseDataModel = BaseAnimation->GetController().GetModel(); + if (BaseDataModel) + { + BaseDataModel->GetBoneTrackNames(TrackNames); + for (FName TrackName : TrackNames) + { + AnimController.AddBoneTrack(TrackName); + } + } + else + { + return FinalAnimation; + } + } + else + { + for (int i = 0; i < LocalPoses.Num(); i++) + { + AnimController.AddBoneTrack(HandSocketComponent->VisualizationMesh->GetRefSkeleton().GetBoneName(i)); + } + } + + if (BaseAnimation) + { + AnimationObject->RetargetSource = BaseAnimation->RetargetSource; + } + else + { + AnimationObject->RetargetSource = HandSocketComponent->VisualizationMesh ? HandSocketComponent->VisualizationMesh->GetSkeleton()->GetRetargetSourceForMesh(HandSocketComponent->VisualizationMesh) : NAME_None; + } + + UAnimDataModel* DataModel = AnimController.GetModel(); + + /// SAVE POSE + if (BaseAnimation && DataModel && BaseDataModel) + { + for (int32 TrackIndex = 0; TrackIndex < DataModel->GetBoneAnimationTracks().Num(); ++TrackIndex) + { + const FRawAnimSequenceTrack& RawTrack = BaseDataModel->GetBoneTrackByIndex(TrackIndex).InternalTrackData; + + bool bHadLoc = false; + bool bHadRot = false; + bool bHadScale = false; + FVector Loc = FVector::ZeroVector; + FQuat Rot = FQuat::Identity; + FVector Scale = FVector::ZeroVector; + + if (RawTrack.PosKeys.Num()) + { + Loc.X = RawTrack.PosKeys[0].X; + Loc.Y = RawTrack.PosKeys[0].Y; + Loc.Z = RawTrack.PosKeys[0].Z; + //Loc = RawTrack.PosKeys[0]; + bHadLoc = true; + } + + if (RawTrack.RotKeys.Num()) + { + Rot.X = RawTrack.RotKeys[0].X; + Rot.Y = RawTrack.RotKeys[0].Y; + Rot.Z = RawTrack.RotKeys[0].Z; + Rot.W = RawTrack.RotKeys[0].W; + //Rot = RawTrack.RotKeys[0]; + bHadRot = true; + } + + if (RawTrack.ScaleKeys.Num()) + { + Scale.X = RawTrack.ScaleKeys[0].X; + Scale.Y = RawTrack.ScaleKeys[0].Y; + Scale.Z = RawTrack.ScaleKeys[0].Z; + //Scale = RawTrack.ScaleKeys[0]; + bHadScale = true; + } + + FTransform FinalTrans(Rot, Loc, Scale); + + FName TrackName = TrackIndex < TrackNames.Num() ? TrackNames[TrackIndex] : NAME_None; + + FQuat DeltaQuat = FQuat::Identity; + for (FBPVRHandPoseBonePair& HandPair : HandSocketComponent->CustomPoseDeltas) + { + if (HandPair.BoneName == TrackName) + { + DeltaQuat = HandPair.DeltaPose; + bHadRot = true; + break; + } + } + + FinalTrans.ConcatenateRotation(DeltaQuat); + FinalTrans.NormalizeRotation(); + + //FRawAnimSequenceTrack& RawNewTrack = DataModel->GetBoneTrackByIndex(TrackIndex).InternalTrackData; + + AnimController.SetBoneTrackKeys(TrackName, { FinalTrans.GetTranslation() }, { FinalTrans.GetRotation() }, { FinalTrans.GetScale3D() }); + } + } + else + { + USkeletalMesh* SkeletalMesh = HandSocketComponent->VisualizationMesh; + USkeleton* AnimSkeleton = SkeletalMesh->GetSkeleton(); + for (int32 TrackIndex = 0; TrackIndex < DataModel->GetBoneAnimationTracks().Num(); ++TrackIndex) + { + // verify if this bone exists in skeleton + int32 BoneTreeIndex = DataModel->GetBoneTrackByIndex(TrackIndex).BoneTreeIndex; + if (BoneTreeIndex != INDEX_NONE) + { + int32 BoneIndex = AnimSkeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkeletalMesh, BoneTreeIndex); + //int32 ParentIndex = SkeletalMesh->RefSkeleton.GetParentIndex(BoneIndex); + FTransform LocalTransform = LocalPoses[BoneIndex]; + + + FName BoneName = AnimSkeleton->GetReferenceSkeleton().GetBoneName(BoneIndex); + + FQuat DeltaQuat = FQuat::Identity; + for (FBPVRHandPoseBonePair& HandPair : HandSocketComponent->CustomPoseDeltas) + { + if (HandPair.BoneName == BoneName) + { + DeltaQuat = HandPair.DeltaPose; + } + } + + LocalTransform.ConcatenateRotation(DeltaQuat); + LocalTransform.NormalizeRotation(); + + AnimController.SetBoneTrackKeys(BoneName, { LocalTransform.GetTranslation() }, { LocalTransform.GetRotation() }, { LocalTransform.GetScale3D() }); + } + } + } + + AnimController.NotifyPopulated(); + } + /// END SAVE POSE + /// + /// + /// + + // init notifies + + AnimationObject->InitializeNotifyTrack(); + PRAGMA_DISABLE_DEPRECATION_WARNINGS + AnimationObject->PostProcessSequence(); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + AnimationObject->MarkPackageDirty(); + + //if (bAutoSaveAsset) + { + UPackage* const Package = AnimationObject->GetOutermost(); + FString const PackageName = Package->GetName(); + FString const PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()); + + double StartTime = FPlatformTime::Seconds(); + + FSavePackageArgs PackageArguments; + PackageArguments.SaveFlags = RF_Standalone; + PackageArguments.SaveFlags = SAVE_NoError; + UPackage::SavePackage(Package, NULL, *PackageFileName, PackageArguments); + //UPackage::SavePackage(Package, NULL, RF_Standalone, *PackageFileName, GError, nullptr, false, true, SAVE_NoError); + + double ElapsedTime = FPlatformTime::Seconds() - StartTime; + UE_LOG(LogAnimation, Log, TEXT("Animation Recorder saved %s in %0.2f seconds"), *PackageName, ElapsedTime); + } + + FinalAnimation = AnimationObject; + return FinalAnimation; + } + + return FinalAnimation; +} +TSharedRef< IDetailCustomization > FHandSocketComponentDetails::MakeInstance() +{ + return MakeShareable(new FHandSocketComponentDetails); +} + +void FHandSocketComponentDetails::OnHandRelativeUpdated(IDetailLayoutBuilder* LayoutBuilder) +{ + + if (!HandSocketComponent.IsValid()) + { + return; + } + + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + } + } + + FComponentVisualizer::NotifyPropertyModified(HandSocketComponent.Get(), FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); +} + +void FHandSocketComponentDetails::OnLeftDominantUpdated(IDetailLayoutBuilder* LayoutBuilder) +{ + + if (!HandSocketComponent.IsValid()) + { + return; + } + + // Default to always flipping this + //if (HandSocketComponent->bFlipForLeftHand) + { + FTransform relTrans = HandSocketComponent->GetRelativeTransform(); + FTransform HandPlacement = HandSocketComponent->GetHandRelativePlacement(); + + if (HandSocketComponent->bDecoupleMeshPlacement) + { + relTrans = FTransform::Identity; + } + + FTransform ReturnTrans = (HandPlacement * relTrans); + + HandSocketComponent->MirrorHandTransform(ReturnTrans, relTrans); + + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + ReturnTrans = ReturnTrans.GetRelativeTransform(relTrans); + HandSocketComponent->HandRelativePlacement = ReturnTrans; + + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + //FComponentVisualizer::NotifyPropertyModified(RefHand, FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } + } + + FComponentVisualizer::NotifyPropertyModified(HandSocketComponent.Get(), FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } +} + +void FHandSocketComponentDetails::OnLockedStateUpdated(IDetailLayoutBuilder* LayoutBuilder) +{ + + if (!HandSocketComponent.IsValid()) + { + return; + } + + if (HandSocketComponent->bDecoupleMeshPlacement) + { + //FTransform RelTrans = HandSocketComponent->GetRelativeTransform(); + //FTransform WorldTrans = HandSocketComponent->GetComponentTransform(); + //if (USceneComponent* ParentComp = HandSocketComponent->GetAttachParent()) + { + + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + + HandSocketComponent->HandRelativePlacement = HandSocketComponent->HandRelativePlacement * HandSocketComponent->GetRelativeTransform();// HandSocketComponent->GetComponentTransform(); + HandSocketComponent->bDecoupled = true; + + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + RefHand->bDecoupled = true; + //FComponentVisualizer::NotifyPropertyModified(RefHand, FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } + } + } + } + else + { + //if (USceneComponent* ParentComp = HandSocketComponent->GetAttachParent()) + { + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + HandSocketComponent->HandRelativePlacement = HandSocketComponent->HandRelativePlacement.GetRelativeTransform(HandSocketComponent->GetRelativeTransform()); + HandSocketComponent->bDecoupled = false; + + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandRelativePlacement = HandSocketComponent->HandRelativePlacement; + RefHand->bDecoupled = false; + //FComponentVisualizer::NotifyPropertyModified(RefHand, FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + } + } + } + } + + TArray<FProperty*> PropertiesToModify; + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bDecoupled))); + FComponentVisualizer::NotifyPropertiesModified(HandSocketComponent.Get(), PropertiesToModify); +} + +void FHandSocketComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + // Hide the SplineCurves property + //TSharedPtr<IPropertyHandle> HandPlacementProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement)); + //HandPlacementProperty->MarkHiddenByCustomization(); + + + TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized; + DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); + + if (ObjectsBeingCustomized.Num() == 1) + { + UHandSocketComponent* CurrentHandSocket = Cast<UHandSocketComponent>(ObjectsBeingCustomized[0]); + if (CurrentHandSocket != NULL) + { + if (HandSocketComponent != CurrentHandSocket) + { + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(CurrentHandSocket->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + HandVisualizer->CurrentlySelectedBoneIdx = INDEX_NONE; + HandVisualizer->CurrentlySelectedBone = NAME_None; + HandVisualizer->HandPropertyPath = FComponentPropertyPath(); + //HandVisualizer->OldHandSocketComp = CurrentHandSocket; + } + + HandSocketComponent = CurrentHandSocket; + } + } + } + + /*const TArray< TWeakObjectPtr<UObject> >& SelectedObjects = DetailBuilder.GetSelectedObjects(); + for (int32 ObjectIndex = 0; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex) + { + const TWeakObjectPtr<UObject>& CurrentObject = SelectedObjects[ObjectIndex]; + if (CurrentObject.IsValid()) + { + UHandSocketComponent* CurrentHandSocket = Cast<UHandSocketComponent>(CurrentObject.Get()); + if (CurrentHandSocket != NULL) + { + if (HandSocketComponent != CurrentHandSocket) + { + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(CurrentHandSocket->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + HandVisualizer->CurrentlySelectedBoneIdx = INDEX_NONE; + HandVisualizer->CurrentlySelectedBone = NAME_None; + HandVisualizer->HandPropertyPath = FComponentPropertyPath(); + //HandVisualizer->OldHandSocketComp = CurrentHandSocket; + } + + HandSocketComponent = CurrentHandSocket; + } + break; + } + } + }*/ + + DetailBuilder.HideCategory(FName("ComponentTick")); + DetailBuilder.HideCategory(FName("GameplayTags")); + DetailBuilder.HideCategory(FName("VRGripInterface")); + DetailBuilder.HideCategory(FName("VRGripInterface|Replication")); + DetailBuilder.HideCategory(FName("Tags")); + DetailBuilder.HideCategory(FName("AssetUserData")); + DetailBuilder.HideCategory(FName("Events")); + DetailBuilder.HideCategory(FName("Activation")); + DetailBuilder.HideCategory(FName("Cooking")); + DetailBuilder.HideCategory(FName("ComponentReplication")); + + TSharedPtr<IPropertyHandle> LockedLocationProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bDecoupleMeshPlacement)); + TSharedPtr<IPropertyHandle> HandRelativePlacementProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement)); + TSharedPtr<IPropertyHandle> LeftHandDominateProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bLeftHandDominant)); + + FSimpleDelegate OnHandRelativeChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnHandRelativeUpdated, &DetailBuilder); + HandRelativePlacementProperty->SetOnPropertyValueChanged(OnHandRelativeChangedDelegate); + + FSimpleDelegate OnLockedStateChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnLockedStateUpdated, &DetailBuilder); + LockedLocationProperty->SetOnPropertyValueChanged(OnLockedStateChangedDelegate); + + FSimpleDelegate OnLeftDominateChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnLeftDominantUpdated, &DetailBuilder); + LeftHandDominateProperty->SetOnPropertyValueChanged(OnLeftDominateChangedDelegate); + + TSharedPtr<IPropertyHandle> ShowVisualizationProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bShowVisualizationMesh)); + + FSimpleDelegate OnShowVisChangedDelegate = FSimpleDelegate::CreateSP(this, &FHandSocketComponentDetails::OnUpdateShowMesh, &DetailBuilder); + ShowVisualizationProperty->SetOnPropertyValueChanged(OnShowVisChangedDelegate); + + DetailBuilder.EditCategory("Hand Animation") + .AddCustomRow(NSLOCTEXT("HandSocketDetails", "UpdateHandSocket", "Save Current Pose")) + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(NSLOCTEXT("HandSocketDetails", "UpdateHandSocket", "Save Current Pose")) + ] + .ValueContent() + .MaxDesiredWidth(125.f) + .MinDesiredWidth(125.f) + [ + SNew(SButton) + .ContentPadding(2) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked(this, &FHandSocketComponentDetails::OnUpdateSavePose) + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(NSLOCTEXT("HandSocketDetails", "UpdateHandSocket", "Save")) + ] + ]; +} + +void FHandSocketComponentDetails::OnUpdateShowMesh(IDetailLayoutBuilder* LayoutBuilder) +{ + if (!HandSocketComponent.IsValid()) + return; + + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + HandVisualizer->CurrentlySelectedBoneIdx = INDEX_NONE; + HandVisualizer->CurrentlySelectedBone = NAME_None; + HandVisualizer->HandPropertyPath = FComponentPropertyPath(); + } +} + +FReply FHandSocketComponentDetails::OnUpdateSavePose() +{ + if (HandSocketComponent.IsValid() && HandSocketComponent->CustomPoseDeltas.Num() > 0) + { + if (HandSocketComponent->HandTargetAnimation || HandSocketComponent->VisualizationMesh) + { + // Save Animation Pose here + FString AssetPath; + FString AssetName; + PromptUserForAssetPath(AssetPath, AssetName); + TWeakObjectPtr<UAnimSequence> NewAnim = SaveAnimationAsset(AssetPath, AssetName); + + // Finally remove the deltas + if (NewAnim.IsValid()) + { + HandSocketComponent->Modify(); + if (AActor* Owner = HandSocketComponent->GetOwner()) + { + Owner->Modify(); + } + + HandSocketComponent->HandTargetAnimation = NewAnim.Get(); + HandSocketComponent->CustomPoseDeltas.Empty(); + HandSocketComponent->bUseCustomPoseDeltas = false; + + TSharedPtr<FComponentVisualizer> Visualizer = GUnrealEd->FindComponentVisualizer(HandSocketComponent->GetClass()); + FHandSocketVisualizer* HandVisualizer = (FHandSocketVisualizer*)Visualizer.Get(); + + if (HandVisualizer) + { + if (UHandSocketComponent* RefHand = HandVisualizer->GetCurrentlyEditingComponent()) + { + RefHand->HandTargetAnimation = NewAnim.Get(); + RefHand->CustomPoseDeltas.Empty(); + RefHand->bUseCustomPoseDeltas = false; + /*TArray<FProperty*> PropertiesToModify; + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandTargetAnimation))); + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bUseCustomPoseDeltas))); + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas))); + FComponentVisualizer::NotifyPropertiesModified(RefHand, PropertiesToModify);*/ + } + } + + // Modify all of the properties at once + TArray<FProperty*> PropertiesToModify; + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandTargetAnimation))); + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bUseCustomPoseDeltas))); + PropertiesToModify.Add(FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas))); + FComponentVisualizer::NotifyPropertiesModified(HandSocketComponent.Get(), PropertiesToModify); + } + } + } + + return FReply::Handled(); +} + +void SCreateHandAnimationDlg::Construct(const FArguments& InArgs) +{ + AssetPath = FText::FromString(FPackageName::GetLongPackagePath(InArgs._DefaultAssetPath.ToString())); + AssetName = FText::FromString(FPackageName::GetLongPackageAssetName(InArgs._DefaultAssetPath.ToString())); + + if (AssetPath.IsEmpty()) + { + AssetPath = LastUsedAssetPath; + // still empty? + if (AssetPath.IsEmpty()) + { + AssetPath = FText::FromString(TEXT("/Game")); + } + } + else + { + LastUsedAssetPath = AssetPath; + } + + if (AssetName.IsEmpty()) + { + // find default name for them + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools"); + FString OutPackageName, OutAssetName; + FString PackageName = AssetPath.ToString() + TEXT("/NewAnimation"); + + AssetToolsModule.Get().CreateUniqueAssetName(PackageName, TEXT(""), OutPackageName, OutAssetName); + AssetName = FText::FromString(OutAssetName); + } + + FPathPickerConfig PathPickerConfig; + PathPickerConfig.DefaultPath = AssetPath.ToString(); + PathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &SCreateHandAnimationDlg::OnPathChange); + PathPickerConfig.bAddDefaultPath = true; + + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser"); + + SWindow::Construct(SWindow::FArguments() + .Title(LOCTEXT("SCreateHandAnimationDlg_Title", "Create New Animation Object")) + .SupportsMinimize(false) + .SupportsMaximize(false) + //.SizingRule( ESizingRule::Autosized ) + .ClientSize(FVector2D(450, 450)) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() // Add user input block + .Padding(2) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SVerticalBox) + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("SelectPath", "Select Path to create animation")) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 14)) + ] + + + SVerticalBox::Slot() + .FillHeight(1) + .Padding(3) + [ + ContentBrowserModule.Get().CreatePathPicker(PathPickerConfig) + ] + + + SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SSeparator) + ] + + + SVerticalBox::Slot() + .AutoHeight() + .Padding(3) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0, 0, 10, 0) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("AnimationName", "Animation Name")) + ] + + + SHorizontalBox::Slot() + [ + SNew(SEditableTextBox) + .Text(AssetName) + .OnTextCommitted(this, &SCreateHandAnimationDlg::OnNameChange) + .MinDesiredWidth(250) + ] + ] + ] + ] + + + SVerticalBox::Slot() + .AutoHeight() + .HAlign(HAlign_Right) + .Padding(5) + [ + SNew(SUniformGridPanel) + .SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding")) + .MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")) + .MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + .Text(LOCTEXT("OK", "OK")) + .OnClicked(this, &SCreateHandAnimationDlg::OnButtonClick, EAppReturnType::Ok) + ] + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) + .Text(LOCTEXT("Cancel", "Cancel")) + .OnClicked(this, &SCreateHandAnimationDlg::OnButtonClick, EAppReturnType::Cancel) + ] + ] + ]); +} + +void SCreateHandAnimationDlg::OnNameChange(const FText& NewName, ETextCommit::Type CommitInfo) +{ + AssetName = NewName; +} + +void SCreateHandAnimationDlg::OnPathChange(const FString& NewPath) +{ + AssetPath = FText::FromString(NewPath); + LastUsedAssetPath = AssetPath; +} + +FReply SCreateHandAnimationDlg::OnButtonClick(EAppReturnType::Type ButtonID) +{ + UserResponse = ButtonID; + + if (ButtonID != EAppReturnType::Cancel) + { + if (!ValidatePackage()) + { + // reject the request + return FReply::Handled(); + } + } + + RequestDestroyWindow(); + + return FReply::Handled(); +} + +/** Ensures supplied package name information is valid */ +bool SCreateHandAnimationDlg::ValidatePackage() +{ + FText Reason; + FString FullPath = GetFullAssetPath(); + + if (!FPackageName::IsValidLongPackageName(FullPath, false, &Reason) + || !FName(*AssetName.ToString()).IsValidObjectName(Reason)) + { + FMessageDialog::Open(EAppMsgType::Ok, Reason); + return false; + } + + return true; +} + +EAppReturnType::Type SCreateHandAnimationDlg::ShowModal() +{ + GEditor->EditorAddModalWindow(SharedThis(this)); + return UserResponse; +} + +FString SCreateHandAnimationDlg::GetAssetPath() +{ + return AssetPath.ToString(); +} + +FString SCreateHandAnimationDlg::GetAssetName() +{ + return AssetName.ToString(); +} + +FString SCreateHandAnimationDlg::GetFullAssetPath() +{ + return AssetPath.ToString() + "/" + AssetName.ToString(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketVisualizer.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketVisualizer.cpp new file mode 100644 index 0000000..80326b9 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/HandSocketVisualizer.cpp @@ -0,0 +1,448 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "HandSocketVisualizer.h" +#include "CanvasItem.h" +#include "CanvasTypes.h" +#include "SceneManagement.h" +//#include "UObject/Field.h" +#include "VRBPDatatypes.h" +#include "ScopedTransaction.h" +#include "Modules/ModuleManager.h" +#include "EditorViewportClient.h" +#include "Components/PoseableMeshComponent.h" +#include "Misc/PackageName.h" +//#include "Persona.h" + +IMPLEMENT_HIT_PROXY(HHandSocketVisProxy, HComponentVisProxy); +#define LOCTEXT_NAMESPACE "HandSocketVisualizer" + +bool FHandSocketVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ + bool bEditing = false; + if (VisProxy && VisProxy->Component.IsValid()) + { + bEditing = true; + if (VisProxy->IsA(HHandSocketVisProxy::StaticGetType())) + { + + if( const UHandSocketComponent * HandComp = UpdateSelectedHandComponent(VisProxy)) + { + HHandSocketVisProxy* Proxy = (HHandSocketVisProxy*)VisProxy; + if (Proxy) + { + CurrentlySelectedBone = Proxy->TargetBoneName; + CurrentlySelectedBoneIdx = Proxy->BoneIdx; + TargetViewport = InViewportClient->Viewport; + } + } + } + } + + return bEditing; +} + + +bool FHandSocketVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const +{ + if (TargetViewport == nullptr || TargetViewport != ViewportClient->Viewport) + { + return false; + } + + if (HandPropertyPath.IsValid() && CurrentlySelectedBone != NAME_None/* && CurrentlySelectedBone != "HandSocket"*/) + { + if (CurrentlySelectedBone == "HandSocket") + { + UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent(); + if (CurrentlyEditingComponent) + { + if (CurrentlyEditingComponent->bMirrorVisualizationMesh) + { + FTransform NewTrans = CurrentlyEditingComponent->GetRelativeTransform(); + NewTrans.Mirror(CurrentlyEditingComponent->GetAsEAxis(CurrentlyEditingComponent->MirrorAxis), CurrentlyEditingComponent->GetAsEAxis(CurrentlyEditingComponent->FlipAxis)); + + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + NewTrans = NewTrans * ParentComp->GetComponentTransform(); + } + + OutMatrix = FRotationMatrix::Make(NewTrans.GetRotation()); + } + } + + return false; + } + else if (CurrentlySelectedBone == "Visualizer") + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + + FTransform newTrans = FTransform::Identity; + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + newTrans = CurrentlyEditingComponent->HandRelativePlacement * ParentComp->GetComponentTransform(); + } + } + else + { + newTrans = CurrentlyEditingComponent->GetHandRelativePlacement() * CurrentlyEditingComponent->GetComponentTransform(); + } + + OutMatrix = FRotationMatrix::Make(newTrans.GetRotation()); + } + } + else + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + FTransform newTrans = CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx); + OutMatrix = FRotationMatrix::Make(newTrans.GetRotation()); + } + } + + return true; + } + + return false; +} + +bool FHandSocketVisualizer::IsVisualizingArchetype() const +{ + return (HandPropertyPath.IsValid() && HandPropertyPath.GetParentOwningActor() && FActorEditorUtils::IsAPreviewOrInactiveActor(HandPropertyPath.GetParentOwningActor())); +} + +void FHandSocketVisualizer::DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) +{ + if (TargetViewport == nullptr || TargetViewport != Viewport) + { + return; + } + + if (const UHandSocketComponent* HandComp = Cast<const UHandSocketComponent>(Component)) + { + if (CurrentlySelectedBone != NAME_None) + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + if (!CurrentlyEditingComponent->HandVisualizerComponent) + { + return; + } + + int32 XL; + int32 YL; + const FIntRect CanvasRect = Canvas->GetViewRect(); + + FPlane location = View->Project(CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx).GetLocation()); + StringSize(GEngine->GetLargeFont(), XL, YL, *CurrentlySelectedBone.ToString()); + //const float DrawPositionX = location.X - XL; + //const float DrawPositionY = location.Y - YL; + const float DrawPositionX = FMath::FloorToFloat(CanvasRect.Min.X + (CanvasRect.Width() - XL) * 0.5f); + const float DrawPositionY = CanvasRect.Min.Y + 50.0f; + Canvas->DrawShadowedString(DrawPositionX, DrawPositionY, *CurrentlySelectedBone.ToString(), GEngine->GetLargeFont(), FLinearColor::Yellow); + } + } + } +} + +void FHandSocketVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) +{ + //UWorld* World = Component->GetWorld(); + //return World && (World->WorldType == EWorldType::EditorPreview || World->WorldType == EWorldType::Inactive); + + //cast the component into the expected component type + if (const UHandSocketComponent* HandComponent = Cast<UHandSocketComponent>(Component)) + { + if (!HandComponent->HandVisualizerComponent) + return; + + //This is an editor only uproperty of our targeting component, that way we can change the colors if we can't see them against the background + const FLinearColor SelectedColor = FLinearColor::Yellow;//TargetingComponent->EditorSelectedColor; + const FLinearColor UnselectedColor = FLinearColor::White;//TargetingComponent->EditorUnselectedColor; + const FVector Location = HandComponent->HandVisualizerComponent->GetComponentLocation(); + float BoneScale = 1.0f - ((View->ViewLocation - Location).SizeSquared() / FMath::Square(100.0f)); + BoneScale = FMath::Clamp(BoneScale, 0.2f, 1.0f); + HHandSocketVisProxy* newHitProxy = new HHandSocketVisProxy(Component); + newHitProxy->TargetBoneName = "Visualizer"; + PDI->SetHitProxy(newHitProxy); + PDI->DrawPoint(Location, CurrentlySelectedBone == newHitProxy->TargetBoneName ? SelectedColor : FLinearColor::Red, 20.f * BoneScale, SDPG_Foreground); + PDI->SetHitProxy(NULL); + newHitProxy = nullptr; + + newHitProxy = new HHandSocketVisProxy(Component); + newHitProxy->TargetBoneName = "HandSocket"; + BoneScale = 1.0f - ((View->ViewLocation - HandComponent->GetComponentLocation()).SizeSquared() / FMath::Square(100.0f)); + BoneScale = FMath::Clamp(BoneScale, 0.2f, 1.0f); + PDI->SetHitProxy(newHitProxy); + PDI->DrawPoint(HandComponent->GetComponentLocation(), FLinearColor::Green, 20.f * BoneScale, SDPG_Foreground); + PDI->SetHitProxy(NULL); + newHitProxy = nullptr; + + if (HandComponent->bUseCustomPoseDeltas) + { + TArray<FTransform> BoneTransforms = HandComponent->HandVisualizerComponent->GetBoneSpaceTransforms(); + FTransform ParentTrans = HandComponent->HandVisualizerComponent->GetComponentTransform(); + // We skip root bone, moving the visualizer itself handles that + for (int i = 1; i < HandComponent->HandVisualizerComponent->GetNumBones(); i++) + { + FName BoneName = HandComponent->HandVisualizerComponent->GetBoneName(i); + FTransform BoneTransform = HandComponent->HandVisualizerComponent->GetBoneTransform(i); + FVector BoneLoc = BoneTransform.GetLocation(); + BoneScale = 1.0f - ((View->ViewLocation - BoneLoc).SizeSquared() / FMath::Square(100.0f)); + BoneScale = FMath::Clamp(BoneScale, 0.1f, 0.9f); + newHitProxy = new HHandSocketVisProxy(Component); + newHitProxy->TargetBoneName = BoneName; + newHitProxy->BoneIdx = i; + PDI->SetHitProxy(newHitProxy); + PDI->DrawPoint(BoneLoc, CurrentlySelectedBone == newHitProxy->TargetBoneName ? SelectedColor : UnselectedColor, 20.f * BoneScale, SDPG_Foreground); + PDI->SetHitProxy(NULL); + newHitProxy = nullptr; + } + } + + if (HandComponent->bShowRangeVisualization) + { + float RangeVisualization = HandComponent->OverrideDistance; + + if (RangeVisualization <= 0.0f) + { + if (USceneComponent* Parent = Cast<USceneComponent>(HandComponent->GetAttachParent())) + { + FStructProperty* ObjectProperty = CastField<FStructProperty>(Parent->GetClass()->FindPropertyByName("VRGripInterfaceSettings")); + + AActor* ParentsActor = nullptr; + if (!ObjectProperty) + { + ParentsActor = Parent->GetOwner(); + if (ParentsActor) + { + ObjectProperty = CastField<FStructProperty>(Parent->GetOwner()->GetClass()->FindPropertyByName("VRGripInterfaceSettings")); + } + } + + if (ObjectProperty) + { + UObject* Target = ParentsActor; + + if (Target == nullptr) + { + Target = Parent; + } + + if (const FBPInterfaceProperties* Curve = ObjectProperty->ContainerPtrToValuePtr<FBPInterfaceProperties>(Target)) + { + if (HandComponent->SlotPrefix == "VRGripS") + { + RangeVisualization = Curve->SecondarySlotRange; + } + else + { + RangeVisualization = Curve->PrimarySlotRange; + } + } + } + } + } + + // Scale into our parents space as that is actually what the range is based on + FBox BoxToDraw = FBox::BuildAABB(FVector::ZeroVector, FVector(RangeVisualization) * HandComponent->GetAttachParent()->GetComponentScale()); + BoxToDraw.Min += HandComponent->GetComponentLocation(); + BoxToDraw.Max += HandComponent->GetComponentLocation(); + + DrawWireBox(PDI, BoxToDraw, FColor::Green, 0.0f); + } + } +} + +bool FHandSocketVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const +{ + if (TargetViewport == nullptr || TargetViewport != ViewportClient->Viewport) + { + return false; + } + + if (HandPropertyPath.IsValid() && CurrentlySelectedBone != NAME_None && CurrentlySelectedBone != "HandSocket") + { + if (CurrentlySelectedBone == "HandSocket") + { + return false; + } + else if (CurrentlySelectedBone == "Visualizer") + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + FTransform newTrans = FTransform::Identity; + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + newTrans = CurrentlyEditingComponent->HandRelativePlacement * ParentComp->GetComponentTransform(); + } + } + else + { + newTrans = CurrentlyEditingComponent->GetHandRelativePlacement() * CurrentlyEditingComponent->GetComponentTransform(); + } + + OutLocation = newTrans.GetLocation(); + } + } + else + { + if (UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent()) + { + OutLocation = CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx).GetLocation(); + } + } + + return true; + } + + return false; +} + +bool FHandSocketVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) +{ + + if (TargetViewport == nullptr || TargetViewport != Viewport) + { + return false; + } + + bool bHandled = false; + + if (HandPropertyPath.IsValid()) + { + if (CurrentlySelectedBone == "HandSocket" || CurrentlySelectedBone == NAME_None) + { + bHandled = false; + } + else if (CurrentlySelectedBone == "Visualizer") + { + const FScopedTransaction Transaction(LOCTEXT("ChangingComp", "ChangingComp")); + + UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent(); + if (!CurrentlyEditingComponent) + { + return false; + } + + CurrentlyEditingComponent->Modify(); + if (AActor* Owner = CurrentlyEditingComponent->GetOwner()) + { + Owner->Modify(); + } + bool bLevelEdit = ViewportClient->IsLevelEditorClient(); + + FTransform CurrentTrans = FTransform::Identity; + + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + CurrentTrans = CurrentlyEditingComponent->HandRelativePlacement * ParentComp->GetComponentTransform(); + } + } + else + { + CurrentTrans = CurrentlyEditingComponent->GetHandRelativePlacement() * CurrentlyEditingComponent->GetComponentTransform(); + } + + if (!DeltaTranslate.IsNearlyZero()) + { + CurrentTrans.AddToTranslation(DeltaTranslate); + } + + if (!DeltaRotate.IsNearlyZero()) + { + CurrentTrans.SetRotation(DeltaRotate.Quaternion() * CurrentTrans.GetRotation()); + } + + if (!DeltaScale.IsNearlyZero()) + { + CurrentTrans.MultiplyScale3D(DeltaScale); + } + + if (CurrentlyEditingComponent->bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = CurrentlyEditingComponent->GetAttachParent()) + { + CurrentlyEditingComponent->HandRelativePlacement = CurrentTrans.GetRelativeTransform(ParentComp->GetComponentTransform()); + } + } + else + { + CurrentlyEditingComponent->HandRelativePlacement = CurrentTrans.GetRelativeTransform(CurrentlyEditingComponent->GetComponentTransform()); + } + + NotifyPropertyModified(CurrentlyEditingComponent, FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement))); + //GEditor->RedrawLevelEditingViewports(true); + bHandled = true; + + } + else + { + UHandSocketComponent* CurrentlyEditingComponent = GetCurrentlyEditingComponent(); + if (!CurrentlyEditingComponent || !CurrentlyEditingComponent->HandVisualizerComponent) + { + return false; + } + + const FScopedTransaction Transaction(LOCTEXT("ChangingComp", "ChangingComp")); + + CurrentlyEditingComponent->Modify(); + if (AActor* Owner = CurrentlyEditingComponent->GetOwner()) + { + Owner->Modify(); + } + bool bLevelEdit = ViewportClient->IsLevelEditorClient(); + + FTransform BoneTrans = CurrentlyEditingComponent->HandVisualizerComponent->GetBoneTransform(CurrentlySelectedBoneIdx); + FTransform NewTrans = BoneTrans; + NewTrans.SetRotation(DeltaRotate.Quaternion() * NewTrans.GetRotation()); + + FQuat DeltaRotateMod = NewTrans.GetRelativeTransform(BoneTrans).GetRotation(); + bool bFoundBone = false; + for (FBPVRHandPoseBonePair& BonePair : CurrentlyEditingComponent->CustomPoseDeltas) + { + if (BonePair.BoneName == CurrentlySelectedBone) + { + bFoundBone = true; + BonePair.DeltaPose *= DeltaRotateMod; + break; + } + } + + if (!bFoundBone) + { + FBPVRHandPoseBonePair newBonePair; + newBonePair.BoneName = CurrentlySelectedBone; + newBonePair.DeltaPose *= DeltaRotateMod; + CurrentlyEditingComponent->CustomPoseDeltas.Add(newBonePair); + bFoundBone = true; + } + + if (bFoundBone) + { + NotifyPropertyModified(CurrentlyEditingComponent, FindFProperty<FProperty>(UHandSocketComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas))); + } + + //GEditor->RedrawLevelEditingViewports(true); + bHandled = true; + } + } + + return bHandled; +} + +void FHandSocketVisualizer::EndEditing() +{ + HandPropertyPath = FComponentPropertyPath(); + CurrentlySelectedBone = NAME_None; + CurrentlySelectedBoneIdx = INDEX_NONE; + TargetViewport = nullptr; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRExpansionEditor.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRExpansionEditor.cpp new file mode 100644 index 0000000..7f6cdeb --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Private/VRExpansionEditor.cpp @@ -0,0 +1,64 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "VRExpansionEditor.h" +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" +#include "Grippables/HandSocketComponent.h" +#include "PropertyEditorModule.h" +#include "HandSocketVisualizer.h" +#include "HandSocketComponentDetails.h" + + +IMPLEMENT_MODULE(FVRExpansionEditorModule, VRExpansionEditor); + +void FVRExpansionEditorModule::StartupModule() +{ + RegisterComponentVisualizer(UHandSocketComponent::StaticClass()->GetFName(), MakeShareable(new FHandSocketVisualizer)); + + // Register detail customizations + { + auto& PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Register our customization to be used by a class 'UMyClass' or 'AMyClass'. Note the prefix must be dropped. + PropertyModule.RegisterCustomClassLayout( + UHandSocketComponent::StaticClass()->GetFName(), + FOnGetDetailCustomizationInstance::CreateStatic(&FHandSocketComponentDetails::MakeInstance) + ); + + PropertyModule.NotifyCustomizationModuleChanged(); + } +} + +void FVRExpansionEditorModule::ShutdownModule() +{ + if (GUnrealEd != NULL) + { + // Iterate over all class names we registered for + for (FName ClassName : RegisteredComponentClassNames) + { + GUnrealEd->UnregisterComponentVisualizer(ClassName); + } + } + + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + auto& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); + + PropertyModule.UnregisterCustomClassLayout(UHandSocketComponent::StaticClass()->GetFName()); + } +} + +void FVRExpansionEditorModule::RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr<FComponentVisualizer> Visualizer) +{ + if (GUnrealEd != NULL) + { + GUnrealEd->RegisterComponentVisualizer(ComponentClassName, Visualizer); + } + + RegisteredComponentClassNames.Add(ComponentClassName); + + if (Visualizer.IsValid()) + { + Visualizer->OnRegister(); + } +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketComponentDetails.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketComponentDetails.h new file mode 100644 index 0000000..603abd8 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketComponentDetails.h @@ -0,0 +1,83 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Grippables/HandSocketComponent.h" +#include "IDetailCustomization.h" +#include "Input/Reply.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SWindow.h" + +class IDetailLayoutBuilder; + +class FHandSocketComponentDetails : 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; + //void SortCategories(const TMap<FName, IDetailCategoryBuilder*>& AllCategoryMap); + + // The selected hand component + TWeakObjectPtr<UHandSocketComponent> HandSocketComponent; + FReply OnUpdateSavePose(); + TWeakObjectPtr<UAnimSequence> SaveAnimationAsset(const FString& InAssetPath, const FString& InAssetName); + + void OnLockedStateUpdated(IDetailLayoutBuilder* LayoutBuilder); + void OnLeftDominantUpdated(IDetailLayoutBuilder* LayoutBuilder); + void OnHandRelativeUpdated(IDetailLayoutBuilder* LayoutBuilder); + void OnUpdateShowMesh(IDetailLayoutBuilder* LayoutBuilder); + + FHandSocketComponentDetails() + { + } +}; + +class SCreateHandAnimationDlg : public SWindow +{ +public: + SLATE_BEGIN_ARGS(SCreateHandAnimationDlg) + { + } + + SLATE_ARGUMENT(FText, DefaultAssetPath) + SLATE_END_ARGS() + + SCreateHandAnimationDlg() + : UserResponse(EAppReturnType::Cancel) + { + } + + void Construct(const FArguments& InArgs); + +public: + /** Displays the dialog in a blocking fashion */ + EAppReturnType::Type ShowModal(); + + /** Gets the resulting asset path */ + FString GetAssetPath(); + + /** Gets the resulting asset name */ + FString GetAssetName(); + + /** Gets the resulting full asset path (path+'/'+name) */ + FString GetFullAssetPath(); + +protected: + void OnPathChange(const FString& NewPath); + void OnNameChange(const FText& NewName, ETextCommit::Type CommitInfo); + FReply OnButtonClick(EAppReturnType::Type ButtonID); + + bool ValidatePackage(); + + EAppReturnType::Type UserResponse; + FText AssetPath; + FText AssetName; + + static FText LastUsedAssetPath; + +}; + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketVisualizer.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketVisualizer.h new file mode 100644 index 0000000..d4fb707 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/HandSocketVisualizer.h @@ -0,0 +1,95 @@ +#pragma once + +#include "ComponentVisualizer.h" +#include "Grippables/HandSocketComponent.h" +#include "ActorEditorUtils.h" + +class FEditorViewportClient; + +/**Base class for clickable targeting editing proxies*/ +struct VREXPANSIONEDITOR_API HHandSocketVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + + HHandSocketVisProxy(const UActorComponent* InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) + { + BoneIdx = 0; + TargetBoneName = NAME_None; + } + + uint32 BoneIdx; + FName TargetBoneName; +}; + +class VREXPANSIONEDITOR_API FHandSocketVisualizer : public FComponentVisualizer +{ +public: + FHandSocketVisualizer() + { + CurrentlySelectedBone = NAME_None; + CurrentlySelectedBoneIdx = INDEX_NONE; + HandPropertyPath = FComponentPropertyPath(); + TargetViewport = nullptr; + } + + virtual ~FHandSocketVisualizer() + { + + } + + UPROPERTY() + FComponentPropertyPath HandPropertyPath; + + FName CurrentlySelectedBone; + uint32 CurrentlySelectedBoneIdx; + + UPROPERTY() + FViewport* TargetViewport; + + UHandSocketComponent* GetCurrentlyEditingComponent() const + { + return Cast<UHandSocketComponent>(HandPropertyPath.GetComponent());; + } + + const UHandSocketComponent* UpdateSelectedHandComponent(HComponentVisProxy* VisProxy) + { + const UHandSocketComponent* HandComp = CastChecked<const UHandSocketComponent>(VisProxy->Component.Get()); + UHandSocketComponent* OldHandComp = Cast<UHandSocketComponent>(HandPropertyPath.GetComponent()); + AActor* OldOwningActor = HandPropertyPath.GetParentOwningActor(); + HandPropertyPath = FComponentPropertyPath(HandComp); + AActor* NewOwningActor = HandPropertyPath.GetParentOwningActor(); + + if (HandPropertyPath.IsValid()) + { + if (OldOwningActor != NewOwningActor || OldHandComp != HandComp) + { + // Reset selection state if we are selecting a different actor to the one previously selected + CurrentlySelectedBoneIdx = INDEX_NONE; + CurrentlySelectedBone = NAME_None; + } + + return HandComp; + } + + HandPropertyPath = FComponentPropertyPath(); + return nullptr; + } + + bool SaveAnimationAsset(const FString& InAssetPath, const FString& InAssetName); + + + bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override; + + bool IsVisualizingArchetype() const override; + + virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override; + virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override; + virtual void EndEditing() override; + +private: + +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRExpansionEditor.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRExpansionEditor.h new file mode 100644 index 0000000..caf41a3 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/Public/VRExpansionEditor.h @@ -0,0 +1,19 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Runtime/Core/Public/Modules/ModuleInterface.h" + +#include "ComponentVisualizer.h" + +class FVRExpansionEditorModule : public IModuleInterface +{ +public: + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + void RegisterComponentVisualizer(FName ComponentClassName, TSharedPtr<FComponentVisualizer> Visualizer); + + /** Array of component class names we have registered, so we know what to unregister afterwards */ + TArray<FName> RegisteredComponentClassNames; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/VRExpansionEditor.Build.cs b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/VRExpansionEditor.Build.cs new file mode 100644 index 0000000..92b9300 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionEditor/VRExpansionEditor.Build.cs @@ -0,0 +1,62 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class VRExpansionEditor : ModuleRules + { + + public VRExpansionEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + // ... add other public dependencies that you statically link with here ... + "Engine", + "Core", + "CoreUObject", + "VRExpansionPlugin", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "BlueprintGraph", + "AnimGraph", + "AnimGraphRuntime", + "SlateCore", + "Slate", + "InputCore", + "Engine", + "UnrealEd", + "EditorStyle", + "AssetRegistry" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/CharacterMovementCompTypes.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/CharacterMovementCompTypes.cpp new file mode 100644 index 0000000..33ec1c6 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/CharacterMovementCompTypes.cpp @@ -0,0 +1,464 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Movement.cpp: Character movement implementation + +=============================================================================*/ + +#include "CharacterMovementCompTypes.h" +#include "VRBaseCharacterMovementComponent.h" +#include "VRBPDatatypes.h" +#include "VRBaseCharacter.h" +#include "VRRootComponent.h" +#include "VRPlayerController.h" + +FSavedMove_VRBaseCharacter::FSavedMove_VRBaseCharacter() : FSavedMove_Character() +{ + VRCapsuleLocation = FVector::ZeroVector; + LFDiff = FVector::ZeroVector; + VRCapsuleRotation = FRotator::ZeroRotator; + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;// _None; +} + +uint8 FSavedMove_VRBaseCharacter::GetCompressedFlags() const +{ + // Fills in 01 and 02 for Jump / Crouch + uint8 Result = FSavedMove_Character::GetCompressedFlags(); + + // Not supporting custom movement mode directly at this time by replicating custom index + // We use 4 bits for this so a maximum of 16 elements + //Result |= (uint8)VRReplicatedMovementMode << 2; + + // This takes up custom_2 + /*if (bWantsToSnapTurn) + { + Result |= FLAG_SnapTurn; + }*/ + + // Reserved_1, and Reserved_2, Flag_Custom_0 and Flag_Custom_1 are used up + // By the VRReplicatedMovementMode packing + + + // only custom_2 and custom_3 are left currently + return Result; +} + +bool FSavedMove_VRBaseCharacter::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const +{ + FSavedMove_VRBaseCharacter* nMove = (FSavedMove_VRBaseCharacter*)NewMove.Get(); + + + if (!nMove || (VRReplicatedMovementMode != nMove->VRReplicatedMovementMode)) + return false; + + if (ConditionalValues.MoveActionArray.MoveActions.Num() > 0 || nMove->ConditionalValues.MoveActionArray.MoveActions.Num() > 0) + return false; + + if (!ConditionalValues.CustomVRInputVector.IsZero() || !nMove->ConditionalValues.CustomVRInputVector.IsZero()) + return false; + + if (!ConditionalValues.RequestedVelocity.IsZero() || !nMove->ConditionalValues.RequestedVelocity.IsZero()) + return false; + + // Hate this but we really can't combine if I am sending a new capsule height + if (!FMath::IsNearlyEqual(LFDiff.Z, nMove->LFDiff.Z)) + return false; + + if (!FVector2D(LFDiff.X, LFDiff.Y).IsZero() && !FVector2D(nMove->LFDiff.X, nMove->LFDiff.Y).IsZero() && !FVector::Coincident(LFDiff.GetSafeNormal2D(), nMove->LFDiff.GetSafeNormal2D(), AccelDotThresholdCombine)) + return false; + + return FSavedMove_Character::CanCombineWith(NewMove, Character, MaxDelta); +} + + +bool FSavedMove_VRBaseCharacter::IsImportantMove(const FSavedMovePtr& LastAckedMove) const +{ + // Auto important if toggled climbing + if (VRReplicatedMovementMode != EVRConjoinedMovementModes::C_MOVE_MAX)//_None) + return true; + + if (!ConditionalValues.CustomVRInputVector.IsZero()) + return true; + + if (!ConditionalValues.RequestedVelocity.IsZero()) + return true; + + if (ConditionalValues.MoveActionArray.MoveActions.Num() > 0) + return true; + + // #TODO: What to do here? + // This is debatable, however it will ALWAYS be non zero realistically and only really effects step ups for the most part + //if (!LFDiff.IsNearlyZero()) + //return true; + + // Else check parent class + return FSavedMove_Character::IsImportantMove(LastAckedMove); +} + +void FSavedMove_VRBaseCharacter::SetInitialPosition(ACharacter* C) +{ + // See if we can get the VR capsule location + //if (AVRBaseCharacter * VRC = Cast<AVRBaseCharacter>(C)) + //{ + if (UVRBaseCharacterMovementComponent* moveComp = Cast<UVRBaseCharacterMovementComponent>(C->GetMovementComponent())) + { + + // Saving this out early because it will be wiped before the PostUpdate gets the values + //ConditionalValues.MoveAction.MoveAction = moveComp->MoveAction.MoveAction; + + VRReplicatedMovementMode = moveComp->VRReplicatedMovementMode; + + if (moveComp->HasRequestedVelocity()) + ConditionalValues.RequestedVelocity = moveComp->RequestedVelocity; + else + ConditionalValues.RequestedVelocity = FVector::ZeroVector; + + // Throw out the Z value of the headset, its not used anyway for movement + // Instead, re-purpose it to be the capsule half height + if (AVRBaseCharacter* BaseChar = Cast<AVRBaseCharacter>(C)) + { + if (BaseChar->VRReplicateCapsuleHeight) + LFDiff.Z = BaseChar->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(); + else + LFDiff.Z = 0.0f; + } + else + LFDiff.Z = 0.0f; + } + else + { + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;//None; + ConditionalValues.CustomVRInputVector = FVector::ZeroVector; + ConditionalValues.RequestedVelocity = FVector::ZeroVector; + } + //} + //else + //{ + // VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;//None; + // ConditionalValues.CustomVRInputVector = FVector::ZeroVector; + //} + + FSavedMove_Character::SetInitialPosition(C); +} + +void FSavedMove_VRBaseCharacter::CombineWith(const FSavedMove_Character* OldMove, ACharacter* InCharacter, APlayerController* PC, const FVector& OldStartLocation) +{ + UCharacterMovementComponent* CharMovement = InCharacter->GetCharacterMovement(); + + // to combine move, first revert pawn position to PendingMove start position, before playing combined move on client + CharMovement->UpdatedComponent->SetWorldLocationAndRotation(OldStartLocation, OldMove->StartRotation, false, nullptr, CharMovement->GetTeleportType()); + CharMovement->Velocity = OldMove->StartVelocity; + + CharMovement->SetBase(OldMove->StartBase.Get(), OldMove->StartBoneName); + CharMovement->CurrentFloor = OldMove->StartFloor; + + // Now that we have reverted to the old position, prepare a new move from that position, + // using our current velocity, acceleration, and rotation, but applied over the combined time from the old and new move. + + // Combine times for both moves + DeltaTime += OldMove->DeltaTime; + + //FSavedMove_VRBaseCharacter * BaseSavedMove = (FSavedMove_VRBaseCharacter *)NewMove.Get(); + FSavedMove_VRBaseCharacter* BaseSavedMovePending = (FSavedMove_VRBaseCharacter*)OldMove; + + if (/*BaseSavedMove && */BaseSavedMovePending) + { + LFDiff.X += BaseSavedMovePending->LFDiff.X; + LFDiff.Y += BaseSavedMovePending->LFDiff.Y; + } + + // Roll back jump force counters. SetInitialPosition() below will copy them to the saved move. + // Changes in certain counters like JumpCurrentCount don't allow move combining, so no need to roll those back (they are the same). + InCharacter->JumpForceTimeRemaining = OldMove->JumpForceTimeRemaining; + InCharacter->JumpKeyHoldTime = OldMove->JumpKeyHoldTime; +} + +void FSavedMove_VRBaseCharacter::PostUpdate(ACharacter* C, EPostUpdateMode PostUpdateMode) +{ + FSavedMove_Character::PostUpdate(C, PostUpdateMode); + + // See if we can get the VR capsule location + //if (AVRBaseCharacter * VRC = Cast<AVRBaseCharacter>(C)) + //{ + if (UVRBaseCharacterMovementComponent* moveComp = Cast<UVRBaseCharacterMovementComponent>(C->GetMovementComponent())) + { + ConditionalValues.CustomVRInputVector = moveComp->CustomVRInputVector; + ConditionalValues.MoveActionArray = moveComp->MoveActionArray; + moveComp->MoveActionArray.Clear(); + } + //} + /*if (ConditionalValues.MoveAction.MoveAction != EVRMoveAction::VRMOVEACTION_None) + { + // See if we can get the VR capsule location + if (AVRBaseCharacter * VRC = Cast<AVRBaseCharacter>(C)) + { + if (UVRBaseCharacterMovementComponent * moveComp = Cast<UVRBaseCharacterMovementComponent>(VRC->GetMovementComponent())) + { + // This is cleared out in perform movement so I need to save it before applying below + EVRMoveAction tempAction = ConditionalValues.MoveAction.MoveAction; + ConditionalValues.MoveAction = moveComp->MoveAction; + ConditionalValues.MoveAction.MoveAction = tempAction; + } + else + { + ConditionalValues.MoveAction.Clear(); + } + } + else + { + ConditionalValues.MoveAction.Clear(); + } + }*/ +} + +void FSavedMove_VRBaseCharacter::Clear() +{ + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;// None; + + VRCapsuleLocation = FVector::ZeroVector; + VRCapsuleRotation = FRotator::ZeroRotator; + LFDiff = FVector::ZeroVector; + + ConditionalValues.CustomVRInputVector = FVector::ZeroVector; + ConditionalValues.RequestedVelocity = FVector::ZeroVector; + ConditionalValues.MoveActionArray.Clear(); + //ConditionalValues.MoveAction.Clear(); + + FSavedMove_Character::Clear(); +} + +void FSavedMove_VRBaseCharacter::PrepMoveFor(ACharacter* Character) +{ + UVRBaseCharacterMovementComponent* BaseCharMove = Cast<UVRBaseCharacterMovementComponent>(Character->GetCharacterMovement()); + + if (BaseCharMove) + { + BaseCharMove->MoveActionArray = ConditionalValues.MoveActionArray; + //BaseCharMove->MoveAction = ConditionalValues.MoveAction; + BaseCharMove->CustomVRInputVector = ConditionalValues.CustomVRInputVector;//this->CustomVRInputVector; + BaseCharMove->VRReplicatedMovementMode = this->VRReplicatedMovementMode; + } + + if (!ConditionalValues.RequestedVelocity.IsZero()) + { + BaseCharMove->RequestedVelocity = ConditionalValues.RequestedVelocity; + BaseCharMove->SetHasRequestedVelocity(true); + } + else + { + BaseCharMove->SetHasRequestedVelocity(false); + } + + FSavedMove_Character::PrepMoveFor(Character); +} + +FVRCharacterScopedMovementUpdate::FVRCharacterScopedMovementUpdate(USceneComponent* Component, EScopedUpdate::Type ScopeBehavior, bool bRequireOverlapsEventFlagToQueueOverlaps) + : FScopedMovementUpdate(Component, ScopeBehavior, bRequireOverlapsEventFlagToQueueOverlaps) +{ + UVRRootComponent* RootComponent = Cast<UVRRootComponent>(Owner); + if (RootComponent) + { + InitialVRTransform = RootComponent->OffsetComponentToWorld; + } +} + +void FVRCharacterScopedMovementUpdate::RevertMove() +{ + bool bTransformIsDirty = IsTransformDirty(); + + FScopedMovementUpdate::RevertMove(); + + UVRRootComponent* RootComponent = Cast<UVRRootComponent>(Owner); + if (RootComponent) + { + // If the base class was going to miss bad overlaps, ie: the offsetcomponent to world is different but the transform isn't + if (!bTransformIsDirty && !IsDeferringUpdates() && !InitialVRTransform.Equals(RootComponent->OffsetComponentToWorld)) + { + RootComponent->UpdateOverlaps(); + } + + // Fix offset + RootComponent->GenerateOffsetToWorld(); + } +} + + +FVRCharacterNetworkMoveData::FVRCharacterNetworkMoveData() : FCharacterNetworkMoveData() +{ + VRCapsuleLocation = FVector::ZeroVector; + LFDiff = FVector::ZeroVector; + VRCapsuleRotation = 0; + ReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX; +} + +FVRCharacterNetworkMoveData::~FVRCharacterNetworkMoveData() +{ +} + +void FVRCharacterNetworkMoveData::ClientFillNetworkMoveData(const FSavedMove_Character& ClientMove, ENetworkMoveType MoveType) +{ + // Handles the movement base itself now + FCharacterNetworkMoveData::ClientFillNetworkMoveData(ClientMove, MoveType); + + // I know that we overloaded this, so it should be our base type + if (const FSavedMove_VRBaseCharacter* SavedMove = (const FSavedMove_VRBaseCharacter*)(&ClientMove)) + { + ReplicatedMovementMode = SavedMove->VRReplicatedMovementMode; + ConditionalMoveReps = SavedMove->ConditionalValues; + + // #TODO: Roll these into the conditionals + VRCapsuleLocation = SavedMove->VRCapsuleLocation; + LFDiff = SavedMove->LFDiff; + VRCapsuleRotation = FRotator::CompressAxisToShort(SavedMove->VRCapsuleRotation.Yaw); + } +} + +bool FVRCharacterNetworkMoveData::Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap, ENetworkMoveType MoveType) +{ + NetworkMoveType = MoveType; + + bool bLocalSuccess = true; + const bool bIsSaving = Ar.IsSaving(); + + Ar << TimeStamp; + + // Handle switching the acceleration rep + // Can't use SerializeOptionalValue here as I don't want to bitwise compare floats + bool bRepAccel = bIsSaving ? !Acceleration.IsNearlyZero() : false; + Ar.SerializeBits(&bRepAccel, 1); + + if (bRepAccel) + { + Acceleration.NetSerialize(Ar, PackageMap, bLocalSuccess); + } + else + { + if (!bIsSaving) + { + Acceleration = FVector::ZeroVector; + } + } + + //Location.NetSerialize(Ar, PackageMap, bLocalSuccess); + + uint16 Yaw = bIsSaving ? FRotator::CompressAxisToShort(ControlRotation.Yaw) : 0; + uint16 Pitch = bIsSaving ? FRotator::CompressAxisToShort(ControlRotation.Pitch) : 0; + uint16 Roll = bIsSaving ? FRotator::CompressAxisToShort(ControlRotation.Roll) : 0; + bool bRepYaw = Yaw != 0; + + ACharacter* CharacterOwner = CharacterMovement.GetCharacterOwner(); + + bool bCanRepRollAndPitch = (CharacterOwner && (CharacterOwner->bUseControllerRotationRoll || CharacterOwner->bUseControllerRotationPitch)); + bool bRepRollAndPitch = bCanRepRollAndPitch && (Roll != 0 || Pitch != 0); + Ar.SerializeBits(&bRepRollAndPitch, 1); + + if (bRepRollAndPitch) + { + // Reversed the order of these + uint32 Rotation32 = 0; + uint32 Yaw32 = bIsSaving ? Yaw : 0; + + if (bIsSaving) + { + Rotation32 = (((uint32)Roll) << 16) | ((uint32)Pitch); + Ar.SerializeIntPacked(Rotation32); + } + else + { + Ar.SerializeIntPacked(Rotation32); + + // Reversed the order of these so it costs less to replicate + Pitch = (Rotation32 & 65535); + Roll = (Rotation32 >> 16); + } + } + + uint32 Yaw32 = bIsSaving ? Yaw : 0; + + Ar.SerializeBits(&bRepYaw, 1); + if (bRepYaw) + { + Ar.SerializeIntPacked(Yaw32); + Yaw = (uint16)Yaw32; + } + + if (!bIsSaving) + { + ControlRotation.Yaw = bRepYaw ? FRotator::DecompressAxisFromShort(Yaw) : 0; + ControlRotation.Pitch = bRepRollAndPitch ? FRotator::DecompressAxisFromShort(Pitch) : 0; + ControlRotation.Roll = bRepRollAndPitch ? FRotator::DecompressAxisFromShort(Roll) : 0; + } + + // ControlRotation : FRotator handles each component zero/non-zero test; it uses a single signal bit for zero/non-zero, and uses 16 bits per component if non-zero. + //ControlRotation.NetSerialize(Ar, PackageMap, bLocalSuccess); + + SerializeOptionalValue<uint8>(bIsSaving, Ar, CompressedMoveFlags, 0); + SerializeOptionalValue<uint8>(bIsSaving, Ar, MovementMode, MOVE_Walking); + VRCapsuleLocation.NetSerialize(Ar, PackageMap, bLocalSuccess); + Ar << VRCapsuleRotation; + + if (MoveType == ENetworkMoveType::NewMove) + { + Location.NetSerialize(Ar, PackageMap, bLocalSuccess); + + // Location, relative movement base, and ending movement mode is only used for error checking, so only save for the final move. + SerializeOptionalValue<UPrimitiveComponent*>(bIsSaving, Ar, MovementBase, nullptr); + SerializeOptionalValue<FName>(bIsSaving, Ar, MovementBaseBoneName, NAME_None); + //SerializeOptionalValue<uint8>(bIsSaving, Ar, MovementMode, MOVE_Walking); // Epic has this like this too, but it is bugged and killing movements + } + + + bool bHasReplicatedMovementMode = ReplicatedMovementMode != EVRConjoinedMovementModes::C_MOVE_MAX; + Ar.SerializeBits(&bHasReplicatedMovementMode, 1); + + if (bHasReplicatedMovementMode) + { + // Increased to 6 bits for 64 total elements instead of 16 + Ar.SerializeBits(&ReplicatedMovementMode, 6); + } + else if(!bIsSaving) + { + ReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX; + } + + // Rep out our custom move settings + ConditionalMoveReps.NetSerialize(Ar, PackageMap, bLocalSuccess); + + //VRCapsuleLocation.NetSerialize(Ar, PackageMap, bLocalSuccess); + LFDiff.NetSerialize(Ar, PackageMap, bLocalSuccess); + //Ar << VRCapsuleRotation; + + return !Ar.IsError(); +} + + +void FVRCharacterMoveResponseDataContainer::ServerFillResponseData(const UCharacterMovementComponent& CharacterMovement, const FClientAdjustment& PendingAdjustment) +{ + FCharacterMoveResponseDataContainer::ServerFillResponseData(CharacterMovement, PendingAdjustment); + + if (const UVRBaseCharacterMovementComponent* BaseMovecomp = Cast<const UVRBaseCharacterMovementComponent>(&CharacterMovement)) + { + bHasRotation = !BaseMovecomp->bUseClientControlRotation; + } +} + +FScopedMeshBoneUpdateOverrideVR::FScopedMeshBoneUpdateOverrideVR(USkeletalMeshComponent* Mesh, EKinematicBonesUpdateToPhysics::Type OverrideSetting) + : MeshRef(Mesh) +{ + if (MeshRef) + { + // Save current state. + SavedUpdateSetting = MeshRef->KinematicBonesUpdateType; + // Override bone update setting. + MeshRef->KinematicBonesUpdateType = OverrideSetting; + } +} + +FScopedMeshBoneUpdateOverrideVR::~FScopedMeshBoneUpdateOverrideVR() +{ + if (MeshRef) + { + // Restore bone update flag. + MeshRef->KinematicBonesUpdateType = SavedUpdateSetting; + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp new file mode 100644 index 0000000..296c7ba --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp @@ -0,0 +1,7761 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "IHeadMountedDisplay.h" +#include "HeadMountedDisplayTypes.h" +#include "Misc/ScopeLock.h" +#include "Net/UnrealNetwork.h" +#include "PrimitiveSceneInfo.h" +#include "Engine/World.h" +#include "GameFramework/WorldSettings.h" +#include "IXRSystemAssets.h" +#include "Components/StaticMeshComponent.h" +#include "MotionDelayBuffer.h" +#include "UObject/VRObjectVersion.h" +#include "UObject/UObjectGlobals.h" // for FindObject<> +#include "IXRTrackingSystem.h" +#include "IXRSystemAssets.h" +#include "DrawDebugHelpers.h" +#include "TimerManager.h" +#include "VRBaseCharacter.h" +#include "VRGlobalSettings.h" +#include "Math/DualQuat.h" +#include "IIdentifiableXRDevice.h" // for FXRDeviceId +#include "XRMotionControllerBase.h" // for GetHandEnumForSourceName() + +#include "GripScripts/GS_Default.h" +#include "GripScripts/GS_LerpToHand.h" + +#include "PhysicsPublic.h" +#include "PhysicsEngine/BodySetup.h" +#include "PhysicsEngine/ConstraintDrives.h" +#include "PhysicsReplication.h" + +#if WITH_CHAOS +#include "Chaos/ParticleHandle.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "Chaos/PBDJointConstraintTypes.h" +#include "Chaos/PBDJointConstraintData.h" +#include "Chaos/Sphere.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "Chaos/ChaosConstraintSettings.h" +#endif + +#include "Misc/CollisionIgnoreSubsystem.h" + +#include "Features/IModularFeatures.h" + +DEFINE_LOG_CATEGORY(LogVRMotionController); +//For UE4 Profiler ~ Stat +DECLARE_CYCLE_STAT(TEXT("TickGrip ~ TickingGrip"), STAT_TickGrip, STATGROUP_TickGrip); +DECLARE_CYCLE_STAT(TEXT("GetGripWorldTransform ~ GettingTransform"), STAT_GetGripTransform, STATGROUP_TickGrip); + +// MAGIC NUMBERS +// Constraint multipliers for angular, to avoid having to have two sets of stiffness/damping variables +const float ANGULAR_STIFFNESS_MULTIPLIER = 1.5f; +const float ANGULAR_DAMPING_MULTIPLIER = 1.4f; + +// Multiplier for the Interactive Hybrid With Physics grip - When not colliding increases stiffness by this value +const float HYBRID_PHYSICS_GRIP_MULTIPLIER = 10.0f; + +namespace { + /** This is to prevent destruction of motion controller components while they are + in the middle of being accessed by the render thread */ + FCriticalSection CritSect; + +} // anonymous namespace + + // CVars +namespace GripMotionControllerCvars +{ + static int32 DrawDebugGripCOM = 0; + FAutoConsoleVariableRef CVarDrawCOMDebugSpheres( + TEXT("vr.DrawDebugCenterOfMassForGrips"), + DrawDebugGripCOM, + TEXT("When on, will draw debug speheres for physics grips COM.\n") + TEXT("0: Disable, 1: Enable"), + ECVF_Default); +} + + //============================================================================= +UGripMotionControllerComponent::UGripMotionControllerComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + PrimaryComponentTick.bTickEvenWhenPaused = true; + + PlayerIndex = 0; + MotionSource = FXRMotionControllerBase::LeftHandSourceId; + //Hand = EControllerHand::Left; + bDisableLowLatencyUpdate = false; + bHasAuthority = false; + bUseWithoutTracking = false; + ClientAuthConflictResolutionMethod = EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_First; + bAlwaysSendTickGrip = false; + bAutoActivate = true; + + SetIsReplicatedByDefault(true); + + // Default 100 htz update rate, same as the 100htz update rate of rep_notify, will be capped to 90/45 though because of vsync on HMD + //bReplicateControllerTransform = true; + ControllerNetUpdateRate = 100.0f; // 100 htz is default + ControllerNetUpdateCount = 0.0f; + bReplicateWithoutTracking = false; + bLerpingPosition = false; + bSmoothReplicatedMotion = false; + bReppedOnce = false; + bScaleTracking = false; + TrackingScaler = FVector(1.0f); + bLimitMinHeight = false; + MinimumHeight = 0.0f; + bLimitMaxHeight = false; + MaximumHeight = 240.0f; + bOffsetByHMD = false; + bLeashToHMD = false; + LeashRange = 300.0f; + bConstrainToPivot = false; + + bSmoothHandTracking = false; + bWasSmoothingHand = false; + bSmoothWithEuroLowPassFunction = true; + LastSmoothRelativeTransform = FTransform::Identity; + SmoothingSpeed = 20.0f; + EuroSmoothingParams.MinCutoff = 0.1f; + EuroSmoothingParams.DeltaCutoff = 10.f; + EuroSmoothingParams.CutoffSlope = 10.f; + + bIsPostTeleport = false; + + GripIDIncrementer = INVALID_VRGRIP_ID; + + // Pivot Variables + CustomPivotComponentSocketName = NAME_None; + bSkipPivotTransformAdjustment = false; + + bOffsetByControllerProfile = true; + GripRenderThreadProfileTransform = FTransform::Identity; + CurrentControllerProfileTransform = FTransform::Identity; + + DefaultGripScript = nullptr; + DefaultGripScriptClass = UGS_Default::StaticClass(); + + VelocityCalculationType = EVRVelocityType::VRLOCITY_Default; + LastRelativePosition = FTransform::Identity; + bSampleVelocityInWorldSpace = false; + VelocitySamples = 30.f; + + bProjectNonSimulatingGrips = false; + EndPhysicsTickFunction.TickGroup = TG_EndPhysics; + EndPhysicsTickFunction.bCanEverTick = true; + EndPhysicsTickFunction.bStartWithTickEnabled = false; +} + +void UGripMotionControllerComponent::RegisterEndPhysicsTick(bool bRegister) +{ + if (bRegister != EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + if (bRegister) + { + if (SetupActorComponentTickFunction(&EndPhysicsTickFunction)) + { + EndPhysicsTickFunction.Target = this; + // Make sure our EndPhysicsTick gets called after physics simulation is finished + UWorld* World = GetWorld(); + if (World != nullptr) + { + EndPhysicsTickFunction.AddPrerequisite(World, World->EndPhysicsTickFunction); + } + } + } + else + { + EndPhysicsTickFunction.UnRegisterTickFunction(); + } + } +} + +void UGripMotionControllerComponent::EndPhysicsTickComponent(FGripComponentEndPhysicsTickFunction& ThisTickFunction) +{ + + if (!IsValid(this)) + return; + + // Now check if we should turn off any post physics ticking + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform().Inverse(); + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (!LocallyGrippedObjects[i].GrippedObject || !IsValid(LocallyGrippedObjects[i].GrippedObject)) + continue; // Skip, don't process this + + if (LocallyGrippedObjects[i].GrippedObject && IsValid(LocallyGrippedObjects[i].GrippedObject) && LocallyGrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bool bSampleRelativeTransform = bProjectNonSimulatingGrips; + + if (!bSampleRelativeTransform) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(LocallyGrippedObjects[i].GrippedObject); + bSampleRelativeTransform = TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation; + } + + if (bSampleRelativeTransform) + { + switch(LocallyGrippedObjects[i].GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor* Actor = Cast<AActor>(LocallyGrippedObjects[i].GrippedObject)) + { + if (UPrimitiveComponent* root = Cast<UPrimitiveComponent>(Actor->GetRootComponent())) + { + LocallyGrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + LocallyGrippedObjects[i].bSetLastWorldTransform = true; + } + } + }break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent* root = Cast<UPrimitiveComponent>(LocallyGrippedObjects[i].GrippedObject)) + { + LocallyGrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + LocallyGrippedObjects[i].bSetLastWorldTransform = true; + } + }break; + } + } + } + } + + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (!GrippedObjects[i].GrippedObject || !IsValid(GrippedObjects[i].GrippedObject)) + continue; // Skip, don't process this + + if (GrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bool bSampleRelativeTransform = bProjectNonSimulatingGrips; + + if (!bSampleRelativeTransform) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(GrippedObjects[i].GrippedObject); + bSampleRelativeTransform = TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation; + } + + if (bSampleRelativeTransform) + { + switch (GrippedObjects[i].GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor* Actor = Cast<AActor>(GrippedObjects[i].GrippedObject)) + { + if (UPrimitiveComponent* root = Cast<UPrimitiveComponent>(Actor->GetRootComponent())) + { + GrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + GrippedObjects[i].bSetLastWorldTransform = true; + } + } + }break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent* root = Cast<UPrimitiveComponent>(GrippedObjects[i].GrippedObject)) + { + GrippedObjects[i].LastWorldTransform = root->GetComponentTransform() * baseTrans; + GrippedObjects[i].bSetLastWorldTransform = true; + } + }break; + } + } + } + } +} + +void FGripComponentEndPhysicsTickFunction::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) +{ + QUICK_SCOPE_CYCLE_COUNTER(FGripComponentEndPhysicsTickFunction_ExecuteTick); + CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Physics); + + if (Target && IsValid(Target)) + { + FActorComponentTickFunction::ExecuteTickHelper(Target, /*bTickInEditor=*/ false, DeltaTime, TickType, [this](float DilatedTime) + { + Target->EndPhysicsTickComponent(*this); + }); + } +} + +FString FGripComponentEndPhysicsTickFunction::DiagnosticMessage() +{ + return TEXT("GripComponentEndPhysicsTickFunction"); +} + +FName FGripComponentEndPhysicsTickFunction::DiagnosticContext(bool bDetailed) +{ + return FName(TEXT("GripComponentEndPhysicsTick")); +} + + +//============================================================================= +UGripMotionControllerComponent::~UGripMotionControllerComponent() +{ + // Moved view extension destruction to BeginDestroy like the new controllers + // Epic had it listed as a crash in the private bug tracker I guess. +} + +void UGripMotionControllerComponent::NewControllerProfileLoaded() +{ + GetCurrentProfileTransform(false); +} + +void UGripMotionControllerComponent::GetCurrentProfileTransform(bool bBindToNoticationDelegate) +{ + if (bOffsetByControllerProfile) + { + UVRGlobalSettings* VRSettings = GetMutableDefault<UVRGlobalSettings>(); + + if (VRSettings == nullptr) + return; + + EControllerHand HandType; + this->GetHandType(HandType); + + FTransform NewControllerProfileTransform = FTransform::Identity; + + if (HandType == EControllerHand::Left || HandType == EControllerHand::AnyHand || !VRSettings->bUseSeperateHandTransforms) + { + NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransform; + } + else if (HandType == EControllerHand::Right) + { + NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransformRight; + } + + if (bBindToNoticationDelegate && !NewControllerProfileEvent_Handle.IsValid()) + { + NewControllerProfileEvent_Handle = VRSettings->OnControllerProfileChangedEvent.AddUObject(this, &UGripMotionControllerComponent::NewControllerProfileLoaded); + } + + if (!NewControllerProfileTransform.Equals(CurrentControllerProfileTransform)) + { + FTransform OriginalControllerProfileTransform = CurrentControllerProfileTransform; + CurrentControllerProfileTransform = NewControllerProfileTransform; + + // Auto adjust for FPS testing pawns + if (!bTracked && bUseWithoutTracking) + { + this->SetRelativeTransform(CurrentControllerProfileTransform * (OriginalControllerProfileTransform.Inverse() * this->GetRelativeTransform())); + } + + OnControllerProfileTransformChanged.Broadcast(CurrentControllerProfileTransform.Inverse() * OriginalControllerProfileTransform, CurrentControllerProfileTransform); + } + } +} + +void UGripMotionControllerComponent::InitializeComponent() +{ + Super::InitializeComponent(); + + if (!DefaultGripScript && DefaultGripScriptClass) + DefaultGripScript = DefaultGripScriptClass.GetDefaultObject(); + else + DefaultGripScript = GetMutableDefault<UGS_Default>(); +} + +void UGripMotionControllerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + // Cancel end physics tick + RegisterEndPhysicsTick(false); + + if (NewControllerProfileEvent_Handle.IsValid()) + { + UVRGlobalSettings* VRSettings = GetMutableDefault<UVRGlobalSettings>(); + if (VRSettings != nullptr) + { + VRSettings->OnControllerProfileChangedEvent.Remove(NewControllerProfileEvent_Handle); + NewControllerProfileEvent_Handle.Reset(); + } + } + + for (int i = 0; i < GrippedObjects.Num(); i++) + { + DestroyPhysicsHandle(GrippedObjects[i]); + + if (/*HasGripAuthority(GrippedObjects[i]) || */IsServer()) + { + DropObjectByInterface(nullptr, GrippedObjects[i].GripID); + } + else + { + if (IsValid(GrippedObjects[i].GrippedObject)) + { + bool bSimulateOnDrop = true; + if (GrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(GrippedObjects[i].GrippedObject); + } + + NotifyDrop(GrippedObjects[i], bSimulateOnDrop); + } + } + } + GrippedObjects.Empty(); + + for (int i = 0; i < LocallyGrippedObjects.Num(); i++) + { + DestroyPhysicsHandle(LocallyGrippedObjects[i]); + + if (/*HasGripAuthority(LocallyGrippedObjects[i]) || */IsServer()) + { + DropObjectByInterface(nullptr, LocallyGrippedObjects[i].GripID); + } + else + { + if (IsValid(LocallyGrippedObjects[i].GrippedObject)) + { + bool bSimulateOnDrop = true; + if (LocallyGrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(LocallyGrippedObjects[i].GrippedObject); + } + + NotifyDrop(LocallyGrippedObjects[i], bSimulateOnDrop); + } + } + } + LocallyGrippedObjects.Empty(); + + for (int i = 0; i < PhysicsGrips.Num(); i++) + { + DestroyPhysicsHandle(&PhysicsGrips[i]); + } + PhysicsGrips.Empty(); + + // Clear any timers that we are managing + if (UWorld * myWorld = GetWorld()) + { + myWorld->GetTimerManager().ClearAllTimersForObject(this); + } + + ObjectsWaitingForSocketUpdate.Empty(); +} + +void UGripMotionControllerComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void UGripMotionControllerComponent::BeginDestroy() +{ + Super::BeginDestroy(); + + if (GripViewExtension.IsValid()) + { + { + // This component could be getting accessed from the render thread so it needs to wait + // before clearing MotionControllerComponent and allowing the destructor to continue + FScopeLock ScopeLock(&CritSect); + GripViewExtension->MotionControllerComponent = NULL; + } + + GripViewExtension.Reset(); + } +} + +void UGripMotionControllerComponent::BeginPlay() +{ + Super::BeginPlay(); +} + +void UGripMotionControllerComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) +{ + + // Don't bother updating this stuff if we aren't local or using them + if (bHasAuthority && !bDisableLowLatencyUpdate && IsActive()) + { + GripRenderThreadRelativeTransform = GetRelativeTransform(); + GripRenderThreadComponentScale = GetComponentScale(); + GripRenderThreadProfileTransform = CurrentControllerProfileTransform; + GripRenderThreadLastLocationForLateUpdate = LastLocationForLateUpdate; + } + + Super::Super::CreateRenderState_Concurrent(Context); +} + +void UGripMotionControllerComponent::SendRenderTransform_Concurrent() +{ + // Don't bother updating this stuff if we aren't local or using them + if (bHasAuthority && !bDisableLowLatencyUpdate && IsActive()) + { + struct FPrimitiveUpdateRenderThreadRelativeTransformParams + { + FTransform RenderThreadRelativeTransform; + FVector RenderThreadComponentScale; + FTransform RenderThreadProfileTransform; + FVector GripRenderThreadLastLocationForLateUpdate; + }; + + FPrimitiveUpdateRenderThreadRelativeTransformParams UpdateParams; + UpdateParams.RenderThreadRelativeTransform = GetRelativeTransform(); + UpdateParams.RenderThreadComponentScale = GetComponentScale(); + UpdateParams.RenderThreadProfileTransform = CurrentControllerProfileTransform; + UpdateParams.GripRenderThreadLastLocationForLateUpdate = LastLocationForLateUpdate; + + ENQUEUE_RENDER_COMMAND(UpdateRTRelativeTransformCommand)( + [UpdateParams, this](FRHICommandListImmediate& RHICmdList) + { + GripRenderThreadRelativeTransform = UpdateParams.RenderThreadRelativeTransform; + GripRenderThreadComponentScale = UpdateParams.RenderThreadComponentScale; + GripRenderThreadProfileTransform = UpdateParams.RenderThreadProfileTransform; + GripRenderThreadLastLocationForLateUpdate = UpdateParams.GripRenderThreadLastLocationForLateUpdate; + }); + } + + // Skip bases motion controllers implementation, we don't want to double update to the render thread + Super::Super::SendRenderTransform_Concurrent(); +} + +FBPActorPhysicsHandleInformation * UGripMotionControllerComponent::GetPhysicsGrip(const FBPActorGripInformation & GripInfo) +{ + return PhysicsGrips.FindByKey(GripInfo); +} + +FBPActorPhysicsHandleInformation* UGripMotionControllerComponent::GetPhysicsGrip(const uint8 GripID) +{ + return PhysicsGrips.FindByKey(GripID); +} + +bool UGripMotionControllerComponent::GetPhysicsGripIndex(const FBPActorGripInformation & GripInfo, int & index) +{ + index = PhysicsGrips.IndexOfByKey(GripInfo); + return index != INDEX_NONE; +} + +FBPActorPhysicsHandleInformation * UGripMotionControllerComponent::CreatePhysicsGrip(const FBPActorGripInformation & GripInfo) +{ + FBPActorPhysicsHandleInformation * HandleInfo = PhysicsGrips.FindByKey(GripInfo); + + if (HandleInfo) + { + DestroyPhysicsHandle(HandleInfo); + return HandleInfo; + } + + FBPActorPhysicsHandleInformation NewInfo; + NewInfo.HandledObject = GripInfo.GrippedObject; + NewInfo.GripID = GripInfo.GripID; + + int index = PhysicsGrips.Add(NewInfo); + + return &PhysicsGrips[index]; +} + + +//============================================================================= +void UGripMotionControllerComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + // Don't ever replicate these, they are getting replaced by my custom send anyway + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeLocation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeRotation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D); + + + // Skipping the owner with this as the owner will use the controllers location directly + DOREPLIFETIME_CONDITION(UGripMotionControllerComponent, ReplicatedControllerTransform, COND_SkipOwner); + DOREPLIFETIME(UGripMotionControllerComponent, GrippedObjects); + DOREPLIFETIME(UGripMotionControllerComponent, ControllerNetUpdateRate); + DOREPLIFETIME(UGripMotionControllerComponent, bSmoothReplicatedMotion); + DOREPLIFETIME(UGripMotionControllerComponent, bReplicateWithoutTracking); + + + DOREPLIFETIME_CONDITION(UGripMotionControllerComponent, LocallyGrippedObjects, COND_SkipOwner); + DOREPLIFETIME_CONDITION(UGripMotionControllerComponent, LocalTransactionBuffer, COND_OwnerOnly); +// DOREPLIFETIME(UGripMotionControllerComponent, bReplicateControllerTransform); +} + +/*void UGripMotionControllerComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't ever replicate these, they are getting replaced by my custom send anyway + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeLocation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeRotation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeScale3D, false); +}*/ + +void UGripMotionControllerComponent::Server_SendControllerTransform_Implementation(FBPVRComponentPosRep NewTransform) +{ + // Store new transform and trigger OnRep_Function + ReplicatedControllerTransform = NewTransform; + + // Server should no longer call this RPC itself, but if is using non tracked then it will so keeping auth check + if(!bHasAuthority) + OnRep_ReplicatedControllerTransform(); +} + +bool UGripMotionControllerComponent::Server_SendControllerTransform_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +void UGripMotionControllerComponent::FGripViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily) +{ + if (!MotionControllerComponent) + { + return; + } + + // Set up the late update state for the controller component + LateUpdate.Setup(MotionControllerComponent->CalcNewComponentToWorld(FTransform()), MotionControllerComponent, false); +} + +void UGripMotionControllerComponent::GetPhysicsVelocities(const FBPActorGripInformation &Grip, FVector &AngularVelocity, FVector &LinearVelocity) +{ + UPrimitiveComponent * primComp = Grip.GetGrippedComponent();//Grip.Component; + AActor * pActor = Grip.GetGrippedActor(); + + if (!primComp && pActor) + primComp = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (!primComp) + { + AngularVelocity = FVector::ZeroVector; + LinearVelocity = FVector::ZeroVector; + return; + } + + AngularVelocity = primComp->GetPhysicsAngularVelocityInDegrees(); + LinearVelocity = primComp->GetPhysicsLinearVelocity(); +} + +bool UGripMotionControllerComponent::GetPhysicsConstraintForce(const FBPActorGripInformation& Grip, FVector& AngularForce, FVector& LinearForce) +{ + if (FBPActorPhysicsHandleInformation * PhysHandle = GetPhysicsGrip(Grip.GripID)) + { + if (PhysHandle->HandleData2.IsValid()) + { + FPhysicsInterface::GetForce(PhysHandle->HandleData2, LinearForce, AngularForce); + return true; + } + } + + return false; +} + +void UGripMotionControllerComponent::GetGripMass(const FBPActorGripInformation& Grip, float& Mass) +{ + UPrimitiveComponent* primComp = Grip.GetGrippedComponent();//Grip.Component; + AActor* pActor = Grip.GetGrippedActor(); + + if (!primComp && pActor) + primComp = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (!primComp || !primComp->IsSimulatingPhysics()) + { + Mass = 0.f; + return; + } + + Mass = primComp->GetMass(); +} + +FTransform UGripMotionControllerComponent::GetGrippedObjectTransform(const FBPActorGripInformation& Grip) +{ + FTransform returnTrans = FTransform::Identity; + + if (!IsValid(Grip.GrippedObject)) + { + return returnTrans; + } + + if (Grip.GripTargetType == EGripTargetType::ActorGrip) + { + if (AActor* GrippedActor = Cast<AActor>(Grip.GrippedObject)) + { + returnTrans = GrippedActor->GetActorTransform(); + } + } + else + { + if (UPrimitiveComponent* GrippedComp = Cast<UPrimitiveComponent>(Grip.GrippedObject)) + { + returnTrans = GrippedComp->GetComponentTransform(); + } + } + + return returnTrans; +} + +void UGripMotionControllerComponent::GetGripByActor(FBPActorGripInformation &Grip, AActor * ActorToLookForGrip, EBPVRResultSwitch &Result) +{ + if (!ActorToLookForGrip) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ActorToLookForGrip); + if(!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ActorToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::GetGripByComponent(FBPActorGripInformation &Grip, UPrimitiveComponent * ComponentToLookForGrip, EBPVRResultSwitch &Result) +{ + if (!ComponentToLookForGrip) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ComponentToLookForGrip); + if(!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ComponentToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::GetGripByObject(FBPActorGripInformation &Grip, UObject * ObjectToLookForGrip, EBPVRResultSwitch &Result) +{ + if (!ObjectToLookForGrip) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ObjectToLookForGrip); + if(!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +FBPActorGripInformation * UGripMotionControllerComponent::GetGripPtrByID(uint8 IDToLookForGrip) +{ + if (IDToLookForGrip == INVALID_VRGRIP_ID) + { + return nullptr; + } + + FBPActorGripInformation* GripInfo = GrippedObjects.FindByKey(IDToLookForGrip); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(IDToLookForGrip); + + return GripInfo; +} + +void UGripMotionControllerComponent::GetGripByID(FBPActorGripInformation &Grip, uint8 IDToLookForGrip, EBPVRResultSwitch &Result) +{ + if (IDToLookForGrip == INVALID_VRGRIP_ID) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(IDToLookForGrip); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(IDToLookForGrip); + + if (GripInfo) + { + Grip = *GripInfo;// GrippedObjects[i]; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripHybridLock(const FBPActorGripInformation& Grip, EBPVRResultSwitch& Result, bool bIsLocked) +{ + int fIndex = GrippedObjects.Find(Grip); + + FBPActorGripInformation* GripInformation = nullptr; + + if (fIndex != INDEX_NONE) + { + GripInformation = &GrippedObjects[fIndex]; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &LocallyGrippedObjects[fIndex]; + } + } + + if (GripInformation != nullptr) + { + GripInformation->bLockHybridGrip = bIsLocked; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripPaused(const FBPActorGripInformation &Grip, EBPVRResultSwitch &Result, bool bIsPaused, bool bNoConstraintWhenPaused) +{ + int fIndex = GrippedObjects.Find(Grip); + + FBPActorGripInformation * GripInformation = nullptr; + + if (fIndex != INDEX_NONE) + { + GripInformation = &GrippedObjects[fIndex]; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &LocallyGrippedObjects[fIndex]; + } + } + + if (GripInformation != nullptr) + { + if (bNoConstraintWhenPaused) + { + if (bIsPaused) + { + if (FBPActorPhysicsHandleInformation * PhysHandle = GetPhysicsGrip(*GripInformation)) + { + DestroyPhysicsHandle(*GripInformation); + } + } + else + { + ReCreateGrip(*GripInformation); + } + } + + GripInformation->bIsPaused = bIsPaused; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetPausedTransform(const FBPActorGripInformation &Grip, const FTransform & PausedTransform, bool bTeleport) +{ + + FBPActorGripInformation * GripInformation = nullptr; + + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &GrippedObjects[fIndex]; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GripInformation = &LocallyGrippedObjects[fIndex]; + } + } + + if (GripInformation != nullptr && GripInformation->GrippedObject != nullptr) + { + if (bTeleport) + { + FTransform ProxyTrans = PausedTransform; + TeleportMoveGrip_Impl(*GripInformation, true, true, ProxyTrans); + } + else + { + if (FBPActorPhysicsHandleInformation * PhysHandle = GetPhysicsGrip(GrippedObjects[fIndex])) + { + UpdatePhysicsHandleTransform(*GripInformation, PausedTransform); + } + else + { + if (GripInformation->GripTargetType == EGripTargetType::ActorGrip) + { + GripInformation->GetGrippedActor()->SetActorTransform(PausedTransform); + } + else + { + GripInformation->GetGrippedComponent()->SetWorldTransform(PausedTransform); + } + } + } + } +} + + + + +void UGripMotionControllerComponent::SetGripCollisionType(const FBPActorGripInformation &Grip, EBPVRResultSwitch &Result, EGripCollisionType NewGripCollisionType) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].GripCollisionType = NewGripCollisionType; + ReCreateGrip(GrippedObjects[fIndex]); + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].GripCollisionType = NewGripCollisionType; + + if (GetNetMode() == ENetMode::NM_Client && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + ReCreateGrip(LocallyGrippedObjects[fIndex]); + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripLateUpdateSetting(const FBPActorGripInformation &Grip, EBPVRResultSwitch &Result, EGripLateUpdateSettings NewGripLateUpdateSetting) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].GripLateUpdateSetting = NewGripLateUpdateSetting; + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].GripLateUpdateSetting = NewGripLateUpdateSetting; + + if (GetNetMode() == ENetMode::NM_Client && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripRelativeTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewRelativeTransform + ) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].RelativeTransform = NewRelativeTransform; + if (FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip)) + { + UpdatePhysicsHandle(Grip.GripID, true); + NotifyGripTransformChanged(Grip); + } + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].RelativeTransform = NewRelativeTransform; + if (FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip)) + { + UpdatePhysicsHandle(Grip.GripID, true); + NotifyGripTransformChanged(Grip); + } + + if (GetNetMode() == ENetMode::NM_Client && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripAdditionTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewAdditionTransform, bool bMakeGripRelative + ) +{ + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].AdditionTransform = CreateGripRelativeAdditionTransform(Grip, NewAdditionTransform, bMakeGripRelative); + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].AdditionTransform = CreateGripRelativeAdditionTransform(Grip, NewAdditionTransform, bMakeGripRelative); + + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + Result = EBPVRResultSwitch::OnFailed; +} + +void UGripMotionControllerComponent::SetGripStiffnessAndDamping( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + float NewStiffness, float NewDamping, bool bAlsoSetAngularValues, float OptionalAngularStiffness, float OptionalAngularDamping + ) +{ + Result = EBPVRResultSwitch::OnFailed; + int fIndex = GrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + GrippedObjects[fIndex].Stiffness = NewStiffness; + GrippedObjects[fIndex].Damping = NewDamping; + + if (bAlsoSetAngularValues) + { + GrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularStiffness = OptionalAngularStiffness; + GrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularDamping = OptionalAngularDamping; + } + + Result = EBPVRResultSwitch::OnSucceeded; + SetGripConstraintStiffnessAndDamping(&GrippedObjects[fIndex]); + //return; + } + else + { + fIndex = LocallyGrippedObjects.Find(Grip); + + if (fIndex != INDEX_NONE) + { + LocallyGrippedObjects[fIndex].Stiffness = NewStiffness; + LocallyGrippedObjects[fIndex].Damping = NewDamping; + + if (bAlsoSetAngularValues) + { + LocallyGrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularStiffness = OptionalAngularStiffness; + LocallyGrippedObjects[fIndex].AdvancedGripSettings.PhysicsSettings.AngularDamping = OptionalAngularDamping; + } + + if (GetNetMode() == ENetMode::NM_Client && !IsTornOff() && LocallyGrippedObjects[fIndex].GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[fIndex]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + + Result = EBPVRResultSwitch::OnSucceeded; + SetGripConstraintStiffnessAndDamping(&LocallyGrippedObjects[fIndex]); + // return; + } + } +} + +FTransform UGripMotionControllerComponent::CreateGripRelativeAdditionTransform_BP( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative +) +{ + return CreateGripRelativeAdditionTransform(GripToSample, AdditionTransform, bGripRelative); +} + +bool UGripMotionControllerComponent::GripObject( + UObject * ObjectToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative, + FName OptionalSnapToSocketName, + FName OptionalBoneToGripName, + EGripCollisionType GripCollisionType, + EGripLateUpdateSettings GripLateUpdateSetting, + EGripMovementReplicationSettings GripMovementReplicationSetting, + float GripStiffness, + float GripDamping, + bool bIsSlotGrip) +{ + if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(ObjectToGrip)) + { + return GripComponent(PrimComp, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, OptionalBoneToGripName, GripCollisionType,GripLateUpdateSetting,GripMovementReplicationSetting,GripStiffness,GripDamping, bIsSlotGrip); + } + else if (AActor * Actor = Cast<AActor>(ObjectToGrip)) + { + return GripActor(Actor, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, OptionalBoneToGripName, GripCollisionType, GripLateUpdateSetting, GripMovementReplicationSetting, GripStiffness, GripDamping, bIsSlotGrip); + } + + return false; +} + +bool UGripMotionControllerComponent::DropObject( + UObject * ObjectToDrop, + uint8 GripIDToDrop, + bool bSimulate, + FVector OptionalAngularVelocity, + FVector OptionalLinearVelocity) +{ + + if (ObjectToDrop != nullptr) + { + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(ObjectToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToDrop); + + if (GripInfo != nullptr) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + } + else if (GripIDToDrop != INVALID_VRGRIP_ID) + { + FBPActorGripInformation * GripInfo = GrippedObjects.FindByKey(GripIDToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(GripIDToDrop); + + if (GripInfo != nullptr) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + } + + return false; +} + +bool UGripMotionControllerComponent::GripObjectByInterface(UObject * ObjectToGrip, const FTransform &WorldOffset, bool bWorldOffsetIsRelative, FName OptionalBoneToGripName, FName OptionalSnapToSocketName, bool bIsSlotGrip) +{ + if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(ObjectToGrip)) + { + AActor * Owner = PrimComp->GetOwner(); + + if (!Owner) + return false; + + if (PrimComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(PrimComp, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(PrimComp, Stiffness, Damping); + + return GripComponent(PrimComp, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(PrimComp), + IVRGripInterface::Execute_GripMovementReplicationType(PrimComp), + Stiffness, + Damping, + bIsSlotGrip + ); + } + else if (Owner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(Owner, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(Owner, Stiffness, Damping); + + return GripActor(Owner, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(Owner), + IVRGripInterface::Execute_GripMovementReplicationType(Owner), + Stiffness, + Damping, + bIsSlotGrip + ); + /*return GripComponent(PrimComp, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(Owner), + IVRGripInterface::Execute_GripMovementReplicationType(Owner), + Stiffness, + Damping, + bIsSlotGrip + );*/ + } + else + { + // No interface, no grip + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController GripObjectByInterface was called on an object that doesn't implement the interface and doesn't have a parent that implements the interface!")); + return false; + } + } + else if (AActor * Actor = Cast<AActor>(ObjectToGrip)) + { + UPrimitiveComponent * root = Cast<UPrimitiveComponent>(Actor->GetRootComponent()); + + if (!root) + return false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(root, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(root, Stiffness, Damping); + + return GripComponent(root, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(root), + IVRGripInterface::Execute_GripMovementReplicationType(root), + Stiffness, + Damping, + bIsSlotGrip + ); + /*return GripActor(Actor, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(root), + IVRGripInterface::Execute_GripMovementReplicationType(root), + Stiffness, + Damping, + bIsSlotGrip + );*/ + } + else if (Actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripCollisionType CollisionType = IVRGripInterface::Execute_GetPrimaryGripType(Actor, bIsSlotGrip); + + float Stiffness; + float Damping; + IVRGripInterface::Execute_GetGripStiffnessAndDamping(Actor, Stiffness, Damping); + + return GripActor(Actor, WorldOffset, bWorldOffsetIsRelative, OptionalSnapToSocketName, + OptionalBoneToGripName, + CollisionType, + IVRGripInterface::Execute_GripLateUpdateSetting(Actor), + IVRGripInterface::Execute_GripMovementReplicationType(Actor), + Stiffness, + Damping, + bIsSlotGrip + ); + } + else + { + // No interface, no grip + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController GripObjectByInterface was called on an object that doesn't implement the interface and doesn't have a parent that implements the interface!")); + return false; + } + } + + return false; +} + +bool UGripMotionControllerComponent::DropObjectByInterface(UObject * ObjectToDrop, uint8 GripIDToDrop, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + FBPActorGripInformation * GripInfo = nullptr; + if (ObjectToDrop != nullptr) + { + GripInfo = GrippedObjects.FindByKey(ObjectToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToDrop); + } + else if (GripIDToDrop != INVALID_VRGRIP_ID) + { + GripInfo = GrippedObjects.FindByKey(GripIDToDrop); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(GripIDToDrop); + } + + if (GripInfo == nullptr) + { + return false; + } + + if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(GripInfo->GrippedObject)) + { + AActor * Owner = PrimComp->GetOwner(); + + if (!Owner) + return false; + + if (PrimComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(PrimComp), OptionalAngularVelocity, OptionalLinearVelocity); + //return DropComponent(PrimComp, IVRGripInterface::Execute_SimulateOnDrop(PrimComp), OptionalAngularVelocity, OptionalLinearVelocity); + } + else if (Owner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(Owner), OptionalAngularVelocity, OptionalLinearVelocity); + //return DropComponent(PrimComp, IVRGripInterface::Execute_SimulateOnDrop(Owner), OptionalAngularVelocity, OptionalLinearVelocity); + } + else + { + // Allowing for failsafe dropping here. + return DropGrip_Implementation(*GripInfo, true, OptionalAngularVelocity, OptionalLinearVelocity); + //return DropComponent(PrimComp, true, OptionalAngularVelocity, OptionalLinearVelocity); + } + } + else if (AActor * Actor = Cast<AActor>(GripInfo->GrippedObject)) + { + UPrimitiveComponent * root = Cast<UPrimitiveComponent>(Actor->GetRootComponent()); + + if (!root) + return false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(root), OptionalAngularVelocity, OptionalLinearVelocity); + } + else if (Actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + return DropGrip_Implementation(*GripInfo, IVRGripInterface::Execute_SimulateOnDrop(Actor), OptionalAngularVelocity, OptionalLinearVelocity); + } + else + { + // Failsafe drop here + return DropGrip_Implementation(*GripInfo, true, OptionalAngularVelocity, OptionalLinearVelocity); + } + } + + return false; +} + +bool UGripMotionControllerComponent::GripActor( + AActor* ActorToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative, + FName OptionalSnapToSocketName, + FName OptionalBoneToGripName, + EGripCollisionType GripCollisionType, + EGripLateUpdateSettings GripLateUpdateSetting, + EGripMovementReplicationSettings GripMovementReplicationSetting, + float GripStiffness, + float GripDamping, + bool bIsSlotGrip) +{ + bool bIsLocalGrip = (GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep); + + if (!IsServer() && !bIsLocalGrip) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was called on the client side as a replicated grip")); + return false; + } + + if (!ActorToGrip || !IsValid(ActorToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an invalid or pending kill actor")); + return false; + } + + if (GetIsObjectHeld(ActorToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an already gripped actor")); + return false; + } + + UPrimitiveComponent *root = Cast<UPrimitiveComponent>(ActorToGrip->GetRootComponent()); + + if (!root) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController tried to grip an actor without a UPrimitiveComponent Root")); + return false; // Need a primitive root + } + + // Has to be movable to work + if (root->Mobility != EComponentMobility::Movable && (GripCollisionType != EGripCollisionType::CustomGrip && GripCollisionType != EGripCollisionType::EventsOnly)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController tried to grip an actor set to static mobility not with a Custom Grip")); + return false; // It is not movable, can't grip it + } + + FBPAdvGripSettings AdvancedGripSettings; + UObject * ObjectToCheck = NULL; // Used if having to calculate the transform + //bool bIgnoreHandRotation = false; + + TArray<FBPGripPair> HoldingControllers; + bool bIsHeld = false; + bool bHadOriginalSettings = false; + bool bOriginalGravity = false; + bool bOriginalReplication = false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if(IVRGripInterface::Execute_DenyGripping(root, this)) + return false; // Interface is saying not to grip it right now + + IVRGripInterface::Execute_IsHeld(root, HoldingControllers, bIsHeld); + bool bAllowMultipleGrips = IVRGripInterface::Execute_AllowsMultipleGrips(root); + if (bIsHeld && !bAllowMultipleGrips) + { + return false; // Can't multiple grip this object + } + else if (bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation* gripInfo = HoldingControllers[0].HoldingController->GetGripPtrByID(HoldingControllers[0].GripID); + + if (gripInfo != nullptr) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo->bOriginalGravity; + bOriginalReplication = gripInfo->bOriginalReplicatesMovement; + } + } + } + + AdvancedGripSettings = IVRGripInterface::Execute_AdvancedGripSettings(root); + ObjectToCheck = root; + } + else if (ActorToGrip->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if(IVRGripInterface::Execute_DenyGripping(ActorToGrip, this)) + return false; // Interface is saying not to grip it right now + + IVRGripInterface::Execute_IsHeld(ActorToGrip, HoldingControllers, bIsHeld); + bool bAllowMultipleGrips = IVRGripInterface::Execute_AllowsMultipleGrips(ActorToGrip); + if (bIsHeld && !bAllowMultipleGrips) + { + return false; // Can't multiple grip this object + } + else if (bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation* gripInfo = HoldingControllers[0].HoldingController->GetGripPtrByID(HoldingControllers[0].GripID); + + if (gripInfo != nullptr) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo->bOriginalGravity; + bOriginalReplication = gripInfo->bOriginalReplicatesMovement; + } + } + } + + AdvancedGripSettings = IVRGripInterface::Execute_AdvancedGripSettings(ActorToGrip); + ObjectToCheck = ActorToGrip; + } + + // So that events caused by sweep and the like will trigger correctly + ActorToGrip->AddTickPrerequisiteComponent(this); + + FBPActorGripInformation newActorGrip; + newActorGrip.GripID = GetNextGripID(bIsLocalGrip); + newActorGrip.GripCollisionType = GripCollisionType; + newActorGrip.GrippedObject = ActorToGrip; + if (bHadOriginalSettings) + { + newActorGrip.bOriginalReplicatesMovement = bOriginalReplication; + newActorGrip.bOriginalGravity = bOriginalGravity; + } + else + { + newActorGrip.bOriginalReplicatesMovement = ActorToGrip->IsReplicatingMovement(); + newActorGrip.bOriginalGravity = root->IsGravityEnabled(); + } + newActorGrip.Stiffness = GripStiffness; + newActorGrip.Damping = GripDamping; + newActorGrip.AdvancedGripSettings = AdvancedGripSettings; + newActorGrip.ValueCache.bWasInitiallyRepped = true; // Set this true on authority side so we can skip a function call on tick + newActorGrip.bIsSlotGrip = bIsSlotGrip; + newActorGrip.GrippedBoneName = OptionalBoneToGripName; + newActorGrip.SlotName = OptionalSnapToSocketName; + + // Ignore late update setting if it doesn't make sense with the grip + switch(newActorGrip.GripCollisionType) + { + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + newActorGrip.GripLateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; // Late updates are bad for this grip + }break; + + default: + { + newActorGrip.GripLateUpdateSetting = GripLateUpdateSetting; + }break; + } + + if (GripMovementReplicationSetting == EGripMovementReplicationSettings::KeepOriginalMovement) + { + if (ActorToGrip->IsReplicatingMovement()) + { + newActorGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceServerSideMovement; + } + else + { + newActorGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + } + } + else + newActorGrip.GripMovementReplicationSetting = GripMovementReplicationSetting; + + newActorGrip.GripTargetType = EGripTargetType::ActorGrip; + + if (OptionalSnapToSocketName.IsValid() && WorldOffset.Equals(FTransform::Identity) && root->DoesSocketExist(OptionalSnapToSocketName)) + { + // I inverse it so that laying out the sockets makes sense + FTransform sockTrans = root->GetSocketTransform(OptionalSnapToSocketName, ERelativeTransformSpace::RTS_Component); + sockTrans.SetScale3D(FVector(1.f) / root->GetComponentScale()); // Prep this so that the inverse works correctly + newActorGrip.RelativeTransform = sockTrans.Inverse(); + newActorGrip.bIsSlotGrip = true; // Set this to a slot grip + + ObjectToCheck = NULL; // Null it back out, socketed grips don't use this + + newActorGrip.SlotName = OptionalSnapToSocketName; + } + else if (bWorldOffsetIsRelative) + { + if (bSkipPivotTransformAdjustment && IsValid(CustomPivotComponent) && !bIsSlotGrip) + { + newActorGrip.RelativeTransform = (WorldOffset * this->GetComponentTransform()).GetRelativeTransform(CustomPivotComponent->GetComponentTransform()); + } + else + { + newActorGrip.RelativeTransform = WorldOffset; + } + } + else + { + newActorGrip.RelativeTransform = WorldOffset.GetRelativeTransform(GetPivotTransform()); + } + + if (!bIsLocalGrip) + { + int32 Index = GrippedObjects.Add(newActorGrip); + if (Index != INDEX_NONE) + NotifyGrip(GrippedObjects[Index]); + //NotifyGrip(newActorGrip); + } + else + { + if (!IsLocallyControlled()) + { + LocalTransactionBuffer.Add(newActorGrip); + } + + int32 Index = LocallyGrippedObjects.Add(newActorGrip); + + if (Index != INDEX_NONE) + { + if (!IsLocallyControlled()) + { + if (!HandleGripReplication(LocallyGrippedObjects[Index])) + { + return true; + } + } + else + { + if (!NotifyGrip(LocallyGrippedObjects[Index])) + { + return true; + } + } + + if (bIsLocalGrip && GetNetMode() == ENetMode::NM_Client && !IsTornOff()) + { + Index = LocallyGrippedObjects.IndexOfByKey(newActorGrip.GripID); + if (Index != INDEX_NONE) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[Index]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + } + } + } + //NotifyGrip(newActorGrip); + + return true; +} + +bool UGripMotionControllerComponent::DropActor(AActor* ActorToDrop, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + if (!ActorToDrop) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid actor")); + return false; + } + + FBPActorGripInformation * GripToDrop = LocallyGrippedObjects.FindByKey(ActorToDrop); + + if(GripToDrop) + return DropGrip_Implementation(*GripToDrop, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was called on the client side with a replicated grip")); + return false; + } + + GripToDrop = GrippedObjects.FindByKey(ActorToDrop); + if (GripToDrop) + return DropGrip_Implementation(*GripToDrop, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + + return false; +} + +bool UGripMotionControllerComponent::GripComponent( + UPrimitiveComponent* ComponentToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative, + FName OptionalSnapToSocketName, + FName OptionalBoneToGripName, + EGripCollisionType GripCollisionType, + EGripLateUpdateSettings GripLateUpdateSetting, + EGripMovementReplicationSettings GripMovementReplicationSetting, + float GripStiffness, + float GripDamping, + bool bIsSlotGrip + ) +{ + + bool bIsLocalGrip = (GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep); + + if (!IsServer() && !bIsLocalGrip) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was called on the client side with a replicating grip")); + return false; + } + + if (!ComponentToGrip || !IsValid(ComponentToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an invalid or pending kill component")); + return false; + } + + if (GetIsObjectHeld(ComponentToGrip)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController grab function was passed an already gripped component")); + return false; + } + + // Has to be movable to work + if (ComponentToGrip->Mobility != EComponentMobility::Movable && (GripCollisionType != EGripCollisionType::CustomGrip && GripCollisionType != EGripCollisionType::EventsOnly)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController tried to grip a component set to static mobility not in CustomGrip mode")); + return false; // It is not movable, can't grip it + } + + FBPAdvGripSettings AdvancedGripSettings; + UObject * ObjectToCheck = NULL; + //bool bIgnoreHandRotation = false; + + TArray<FBPGripPair> HoldingControllers; + bool bIsHeld = false; + bool bHadOriginalSettings = false; + bool bOriginalGravity = false; + bool bOriginalReplication = false; + + if (ComponentToGrip->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if(IVRGripInterface::Execute_DenyGripping(ComponentToGrip, this)) + return false; // Interface is saying not to grip it right now + + IVRGripInterface::Execute_IsHeld(ComponentToGrip, HoldingControllers, bIsHeld); + bool bAllowMultipleGrips = IVRGripInterface::Execute_AllowsMultipleGrips(ComponentToGrip); + if (bIsHeld && !bAllowMultipleGrips) + { + return false; // Can't multiple grip this object + } + else if(bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation gripInfo; + EBPVRResultSwitch result; + HoldingControllers[0].HoldingController->GetGripByID(gripInfo, HoldingControllers[0].GripID, result); + + if (result != EBPVRResultSwitch::OnFailed) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo.bOriginalGravity; + bOriginalReplication = gripInfo.bOriginalReplicatesMovement; + } + } + } + + + AdvancedGripSettings = IVRGripInterface::Execute_AdvancedGripSettings(ComponentToGrip); + ObjectToCheck = ComponentToGrip; + } + + //ComponentToGrip->IgnoreActorWhenMoving(this->GetOwner(), true); + // So that events caused by sweep and the like will trigger correctly + + ComponentToGrip->AddTickPrerequisiteComponent(this); + + FBPActorGripInformation newComponentGrip; + newComponentGrip.GripID = GetNextGripID(bIsLocalGrip); + newComponentGrip.GripCollisionType = GripCollisionType; + newComponentGrip.GrippedObject = ComponentToGrip; + + if (bHadOriginalSettings) + { + newComponentGrip.bOriginalReplicatesMovement = bOriginalReplication; + newComponentGrip.bOriginalGravity = bOriginalGravity; + } + else + { + if (ComponentToGrip->GetOwner()) + newComponentGrip.bOriginalReplicatesMovement = ComponentToGrip->GetOwner()->IsReplicatingMovement(); + + newComponentGrip.bOriginalGravity = ComponentToGrip->IsGravityEnabled(); + } + newComponentGrip.Stiffness = GripStiffness; + newComponentGrip.Damping = GripDamping; + newComponentGrip.AdvancedGripSettings = AdvancedGripSettings; + newComponentGrip.GripTargetType = EGripTargetType::ComponentGrip; + newComponentGrip.ValueCache.bWasInitiallyRepped = true; // Set this true on authority side so we can skip a function call on tick + newComponentGrip.bIsSlotGrip = bIsSlotGrip; + newComponentGrip.GrippedBoneName = OptionalBoneToGripName; + newComponentGrip.SlotName = OptionalSnapToSocketName; + + // Ignore late update setting if it doesn't make sense with the grip + switch (newComponentGrip.GripCollisionType) + { + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + newComponentGrip.GripLateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; // Late updates are bad for this grip + }break; + + default: + { + newComponentGrip.GripLateUpdateSetting = GripLateUpdateSetting; + }break; + } + + + if (GripMovementReplicationSetting == EGripMovementReplicationSettings::KeepOriginalMovement) + { + if (ComponentToGrip->GetOwner()) + { + if (ComponentToGrip->GetOwner()->IsReplicatingMovement()) + { + newComponentGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceServerSideMovement; + } + else + { + newComponentGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + } + } + else + newComponentGrip.GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + } + else + newComponentGrip.GripMovementReplicationSetting = GripMovementReplicationSetting; + + if (OptionalSnapToSocketName.IsValid() && WorldOffset.Equals(FTransform::Identity) && ComponentToGrip->DoesSocketExist(OptionalSnapToSocketName)) + { + // I inverse it so that laying out the sockets makes sense + FTransform sockTrans = ComponentToGrip->GetSocketTransform(OptionalSnapToSocketName, ERelativeTransformSpace::RTS_Component); + sockTrans.SetScale3D(FVector(1.f) / ComponentToGrip->GetComponentScale()); // Prep this so that the inverse works correctly + newComponentGrip.RelativeTransform = sockTrans.Inverse(); + newComponentGrip.bIsSlotGrip = true; // Set this to a slot grip + + ObjectToCheck = NULL; // Null it out, socketed grips don't use this + } + else if (bWorldOffsetIsRelative) + { + if (bSkipPivotTransformAdjustment && IsValid(CustomPivotComponent) && !bIsSlotGrip) + { + newComponentGrip.RelativeTransform = (WorldOffset * this->GetComponentTransform()).GetRelativeTransform(CustomPivotComponent->GetComponentTransform()); + } + else + { + newComponentGrip.RelativeTransform = WorldOffset; + } + } + else + { + newComponentGrip.RelativeTransform = WorldOffset.GetRelativeTransform(GetPivotTransform()); + } + + if (!bIsLocalGrip) + { + int32 Index = GrippedObjects.Add(newComponentGrip); + if (Index != INDEX_NONE) + NotifyGrip(GrippedObjects[Index]); + + //NotifyGrip(newComponentGrip); + } + else + { + if (!IsLocallyControlled()) + { + LocalTransactionBuffer.Add(newComponentGrip); + } + + int32 Index = LocallyGrippedObjects.Add(newComponentGrip); + + if (Index != INDEX_NONE) + { + if (!IsLocallyControlled()) + { + if (!HandleGripReplication(LocallyGrippedObjects[Index])) + { + return true; + } + } + else + { + if (!NotifyGrip(LocallyGrippedObjects[Index])) + { + return true; + } + } + + if (bIsLocalGrip && GetNetMode() == ENetMode::NM_Client && !IsTornOff()) + { + Index = LocallyGrippedObjects.IndexOfByKey(newComponentGrip.GripID); + if (Index != INDEX_NONE) + { + FBPActorGripInformation GripInfo = LocallyGrippedObjects[Index]; + Server_NotifyLocalGripAddedOrChanged(GripInfo); + } + } + } + } + + return true; +} + +bool UGripMotionControllerComponent::DropComponent(UPrimitiveComponent * ComponentToDrop, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + + FBPActorGripInformation *GripInfo; + + // First check for it in the local grips + GripInfo = LocallyGrippedObjects.FindByKey(ComponentToDrop); + + if (GripInfo != nullptr) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + + // If we aren't the server then fail out + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was called on the client side for a replicated grip")); + return false; + } + + // Now check in the server auth gripsop) + GripInfo = GrippedObjects.FindByKey(ComponentToDrop); + + if (GripInfo != nullptr) + { + return DropGrip_Implementation(*GripInfo, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); + } + else + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid component")); + return false; + } + + //return false; +} + +bool UGripMotionControllerComponent::DropGrip(const FBPActorGripInformation& Grip, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity) +{ + return DropGrip_Implementation(Grip, bSimulate, OptionalAngularVelocity, OptionalLinearVelocity); +} + +bool UGripMotionControllerComponent::DropGrip_Implementation(const FBPActorGripInformation &Grip, bool bSimulate, FVector OptionalAngularVelocity, FVector OptionalLinearVelocity, bool bSkipNotify) +{ + int FoundIndex = 0; + bool bIsServer = IsServer(); + bool bWasLocalGrip = false; + if (!LocallyGrippedObjects.Find(Grip, FoundIndex)) // This auto checks if Actor and Component are valid in the == operator + { + if (!bIsServer) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was called on the client side for a replicated grip")); + return false; + } + + if (!GrippedObjects.Find(Grip, FoundIndex)) // This auto checks if Actor and Component are valid in the == operator + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid drop")); + return false; + } + + bWasLocalGrip = false; + } + else + bWasLocalGrip = true; + + if (bWasLocalGrip && bIsServer) + { + for (int i = LocalTransactionBuffer.Num() - 1; i >= 0; i--) + { + if (LocalTransactionBuffer[i].GripID == Grip.GripID) + LocalTransactionBuffer.RemoveAt(i); + } + } + + UPrimitiveComponent * PrimComp = nullptr; + + AActor * pActor = nullptr; + if (bWasLocalGrip) + { + PrimComp = LocallyGrippedObjects[FoundIndex].GetGrippedComponent(); + pActor = LocallyGrippedObjects[FoundIndex].GetGrippedActor(); + } + else + { + PrimComp = GrippedObjects[FoundIndex].GetGrippedComponent(); + pActor = GrippedObjects[FoundIndex].GetGrippedActor(); + } + + if (!PrimComp && pActor) + PrimComp = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if(!PrimComp) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop function was passed an invalid drop or CleanUpBadGrip wascalled")); + //return false; + } + else + { + + // Had to move in front of deletion to properly set velocity + + /*if (((bWasLocalGrip && !IsLocallyControlled()) || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceClientSideMovement) && + (!OptionalLinearVelocity.IsNearlyZero() || !OptionalAngularVelocity.IsNearlyZero())*/ + if (!OptionalLinearVelocity.IsNearlyZero() || !OptionalAngularVelocity.IsNearlyZero()) + { + PrimComp->SetPhysicsLinearVelocity(OptionalLinearVelocity); + PrimComp->SetPhysicsAngularVelocityInDegrees(OptionalAngularVelocity); + } + } + + if (bWasLocalGrip) + { + if (GetNetMode() == ENetMode::NM_Client) + { + if (!IsTornOff()) + { + FTransform_NetQuantize TransformAtDrop = FTransform::Identity; + + switch (LocallyGrippedObjects[FoundIndex].GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor * GrippedActor = LocallyGrippedObjects[FoundIndex].GetGrippedActor()) + { + TransformAtDrop = GrippedActor->GetActorTransform(); + } + }; break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent * GrippedPrim = LocallyGrippedObjects[FoundIndex].GetGrippedComponent()) + { + TransformAtDrop = GrippedPrim->GetComponentTransform(); + } + }break; + default:break; + } + + if(!bSkipNotify) + Server_NotifyLocalGripRemoved(LocallyGrippedObjects[FoundIndex].GripID, TransformAtDrop, OptionalAngularVelocity, OptionalLinearVelocity); + } + + // Have to call this ourselves + Drop_Implementation(LocallyGrippedObjects[FoundIndex], bSimulate); + } + else // Server notifyDrop it + { + NotifyDrop(LocallyGrippedObjects[FoundIndex], bSimulate); + } + } + else + NotifyDrop(GrippedObjects[FoundIndex], bSimulate); + + //GrippedObjects.RemoveAt(FoundIndex); + return true; +} + +bool UGripMotionControllerComponent::DropAndSocketObject(const FTransform_NetQuantize & RelativeTransformToParent, UObject * ObjectToDrop, uint8 GripIDToDrop, USceneComponent * SocketingParent, FName OptionalSocketName, bool bWeldBodies) +{ + if (!SocketingParent) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid socketing parent")); + return false; + } + + if (!ObjectToDrop && GripIDToDrop == INVALID_VRGRIP_ID) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid object")); + return false; + } + + bool bWasLocalGrip = false; + FBPActorGripInformation * GripInfo = nullptr; + + if (ObjectToDrop) + GripInfo = LocallyGrippedObjects.FindByKey(ObjectToDrop); + else if (GripIDToDrop != INVALID_VRGRIP_ID) + GripInfo = LocallyGrippedObjects.FindByKey(GripIDToDrop); + + if(GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = true; + } + else + { + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was called on the client side for a replicated grip")); + return false; + } + + if(ObjectToDrop) + GripInfo = GrippedObjects.FindByKey(ObjectToDrop); + else if(GripIDToDrop != INVALID_VRGRIP_ID) + GripInfo = GrippedObjects.FindByKey(GripIDToDrop); + + if(GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = false; + } + else + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid drop")); + return false; + } + } + + if(GripInfo) + return DropAndSocketGrip_Implementation(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + + return false; +} + +bool UGripMotionControllerComponent::DropAndSocketGrip(const FBPActorGripInformation& GripToDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies) +{ + return DropAndSocketGrip_Implementation(GripToDrop, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); +} + +bool UGripMotionControllerComponent::DropAndSocketGrip_Implementation(const FBPActorGripInformation & GripToDrop, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies, bool bSkipServerNotify) +{ + if (!SocketingParent || !IsValid(SocketingParent)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid socketing parent")); + return false; + } + + bool bWasLocalGrip = false; + FBPActorGripInformation * GripInfo = nullptr; + + GripInfo = LocallyGrippedObjects.FindByKey(GripToDrop); + if (GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = true; + } + else + { + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was called on the client side for a replicated grip")); + return false; + } + + GripInfo = GrippedObjects.FindByKey(GripToDrop); + + if (GripInfo) // This auto checks if Actor and Component are valid in the == operator + { + bWasLocalGrip = false; + } + else + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid drop")); + return false; + } + } + + UPrimitiveComponent * PrimComp = nullptr; + + AActor * pActor = nullptr; + + PrimComp = GripInfo->GetGrippedComponent(); + pActor = GripInfo->GetGrippedActor(); + + if (!PrimComp && pActor) + PrimComp = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (!PrimComp) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid drop or CleanUpBadGrip wascalled")); + //return false; + } + + UObject * GrippedObject = GripInfo->GrippedObject; + + if (!GrippedObject || !IsValid(GrippedObject)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController drop and socket function was passed an invalid or pending kill gripped object")); + return false; + } + + int PhysicsHandleIndex = INDEX_NONE; + GetPhysicsGripIndex(*GripInfo, PhysicsHandleIndex); + + if (bWasLocalGrip) + { + if (GetNetMode() == ENetMode::NM_Client) + { + if (!IsTornOff() && !bSkipServerNotify) + { + Server_NotifyDropAndSocketGrip(GripInfo->GripID, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + OnSocketingObject.Broadcast(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + Socket_Implementation(GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + + // Have to call this ourselves + DropAndSocket_Implementation(*GripInfo); + } + else // Server notifyDrop it + { + //Socket_Implementation(GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + NotifyDropAndSocket(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + } + else + { + //Socket_Implementation(GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + NotifyDropAndSocket(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + //GrippedObjects.RemoveAt(FoundIndex); + return true; +} + +void UGripMotionControllerComponent::SetSocketTransform(UObject* ObjectToSocket, /*USceneComponent * SocketingParent,*/ const FTransform_NetQuantize RelativeTransformToParent/*, FName OptionalSocketName, bool bWeldBodies*/) +{ + if (ObjectsWaitingForSocketUpdate.RemoveSingle(ObjectToSocket) < 1) + { + // I know that technically it should never happen that the pointers get reset with a uproperty + // But does it really hurt to add this pathway anyway? + for (int i = ObjectsWaitingForSocketUpdate.Num() - 1; i >= 0; --i) + { + if (ObjectsWaitingForSocketUpdate[i] == nullptr) + ObjectsWaitingForSocketUpdate.RemoveAt(i); + } + + return; + } + + if (!ObjectToSocket || !IsValid(ObjectToSocket)) + return; + + /*FAttachmentTransformRules TransformRule = FAttachmentTransformRules::KeepWorldTransform;//KeepWorldTransform; + TransformRule.bWeldSimulatedBodies = bWeldBodies;*/ + + if (UPrimitiveComponent * root = Cast<UPrimitiveComponent>(ObjectToSocket)) + { + //root->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + //root->SetRelativeTransform(RelativeTransformToParent); + + if(root->GetAttachParent()) + root->SetRelativeTransform(RelativeTransformToParent); + } + else if (AActor * pActor = Cast<AActor>(ObjectToSocket)) + { + //pActor->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + //pActor->SetActorRelativeTransform(RelativeTransformToParent); + + if(pActor->GetAttachParentActor()) + pActor->SetActorRelativeTransform(RelativeTransformToParent); + } +} + + +bool UGripMotionControllerComponent::Server_NotifyDropAndSocketGrip_Validate(uint8 GripID, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyDropAndSocketGrip_Implementation(uint8 GripID, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies) +{ + FBPActorGripInformation FoundGrip; + EBPVRResultSwitch Result; + + GetGripByID(FoundGrip, GripID, Result); + + if (Result == EBPVRResultSwitch::OnFailed) + return; + + int PhysicsHandleIndex = INDEX_NONE; + GetPhysicsGripIndex(FoundGrip, PhysicsHandleIndex); + + if (FoundGrip.GrippedObject) + { + OnSocketingObject.Broadcast(FoundGrip, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + Socket_Implementation(FoundGrip.GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + if (!DropAndSocketGrip_Implementation(FoundGrip, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies)) + { + DropGrip_Implementation(FoundGrip, false); + } + +} + +void UGripMotionControllerComponent::Socket_Implementation(UObject * ObjectToSocket, bool bWasSimulating, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies) +{ + // Check for valid objects + if (!SocketingParent || !SocketingParent->IsValidLowLevelFast() || !ObjectToSocket || !ObjectToSocket->IsValidLowLevelFast()) + { + if (!SocketingParent || !SocketingParent->IsValidLowLevelFast()) + { + UE_LOG(LogVRMotionController, Error, TEXT("VRGripMotionController Socket_Implementation was called with an invalid Socketing Parent object")); + } + else + { + UE_LOG(LogVRMotionController, Error, TEXT("VRGripMotionController Socket_Implementation was called with an invalid Object to Socket")); + } + return; + } + + FAttachmentTransformRules TransformRule = FAttachmentTransformRules::KeepWorldTransform;//KeepWorldTransform; + TransformRule.bWeldSimulatedBodies = bWeldBodies; + + //UPrimitiveComponent * ParentPrim = Cast<UPrimitiveComponent>(SocketingParent); + + if (UPrimitiveComponent * root = Cast<UPrimitiveComponent>(ObjectToSocket)) + { + if (FBodyInstance* rBodyInstance = root->GetBodyInstance()) + { + if (rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().RemoveAll(this); + } + } + + // Stop simulation for socketing + if (bWasSimulating || root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + bWasSimulating = true; + } + + root->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + root->SetRelativeTransform(RelativeTransformToParent); + } + else if (AActor * pActor = Cast<AActor>(ObjectToSocket)) + { + + if (UPrimitiveComponent * rootComp = Cast<UPrimitiveComponent>(pActor->GetRootComponent())) + { + if (FBodyInstance* rBodyInstance = rootComp->GetBodyInstance()) + { + if (rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().RemoveAll(this); + } + } + + if (bWasSimulating || rootComp->IsSimulatingPhysics()) + { + // Stop simulation for socketing + rootComp->SetSimulatePhysics(false); + bWasSimulating = true; + } + } + + pActor->AttachToComponent(SocketingParent, TransformRule, OptionalSocketName); + pActor->SetActorRelativeTransform(RelativeTransformToParent); + + //if (!bRetainOwnership) + //pActor->SetOwner(nullptr); + } + + // It had a physics handle or was simulating, I need to delay a tick and set the transform to ensure it skips a race condition + // I may need to consider running the entire attachment in here instead in the future + if (bWasSimulating) + { + ObjectsWaitingForSocketUpdate.Add(ObjectToSocket); + GetWorld()->GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UGripMotionControllerComponent::SetSocketTransform, ObjectToSocket, /*SocketingParent, */RelativeTransformToParent/*, OptionalSocketName, bWeldBodies*/)); + } +} + +void UGripMotionControllerComponent::NotifyDropAndSocket_Implementation(const FBPActorGripInformation &NewDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies) +{ + // Don't do this if we are the owning player on a local grip, there is no filter for multicast to not send to owner + if ((NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && + IsLocallyControlled() && + GetNetMode() == ENetMode::NM_Client) + { + + // If we still have the grip then the server is asking us to drop it even though it is locally controlled + if (FBPActorGripInformation * GripInfo = GetGripPtrByID(NewDrop.GripID)) + { + DropAndSocketGrip_Implementation(*GripInfo, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies, true); + } + return; + } + + int PhysicsHandleIndex = INDEX_NONE; + GetPhysicsGripIndex(NewDrop, PhysicsHandleIndex); + + if (NewDrop.GrippedObject) + { + OnSocketingObject.Broadcast(NewDrop, SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + Socket_Implementation(NewDrop.GrippedObject, (PhysicsHandleIndex != INDEX_NONE), SocketingParent, OptionalSocketName, RelativeTransformToParent, bWeldBodies); + } + + DropAndSocket_Implementation(NewDrop); +} + +void UGripMotionControllerComponent::DropAndSocket_Implementation(const FBPActorGripInformation &NewDrop) +{ + UGripMotionControllerComponent * HoldingController = nullptr; + bool bIsHeld = false; + + DestroyPhysicsHandle(NewDrop); + + bool bHadGripAuthority = HasGripAuthority(NewDrop); + + UPrimitiveComponent *root = NULL; + AActor * pActor = NULL; + + switch (NewDrop.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + pActor = NewDrop.GetGrippedActor(); + + if (pActor) + { + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + pActor->RemoveTickPrerequisiteComponent(this); + //this->IgnoreActorWhenMoving(pActor, false); + + if (APawn* OwningPawn = Cast<APawn>(GetOwner())) + { + OwningPawn->MoveIgnoreActorRemove(pActor); + + // Clearing owner out here + // Now I am setting the owner to the owning pawn if we are one + // This makes sure that some special replication needs are taken care of + // Only doing this for actor grips + // #TODO: Add the removal back in? + //pActor->SetOwner(nullptr); + } + + if (root) + { + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + // Attachment already handles both of these + //root->UpdateComponentToWorld(); // This fixes the late update offset + //root->SetSimulatePhysics(false); + + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + + // Stop Physics sim for socketing + root->SetSimulatePhysics(false); + } + + if (IsServer()) //&& !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(pActor, this, NewDrop.GripID, false); + + if(NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(pActor, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(pActor, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, true); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(pActor, this, NewDrop, true); + if (IVRGripInterface* GripInterface = Cast<IVRGripInterface>(pActor)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, true); + } + + } + } + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + root = NewDrop.GetGrippedComponent(); + if (root) + { + pActor = root->GetOwner(); + + root->RemoveTickPrerequisiteComponent(this); + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + // Attachment already handles both of these + //root->UpdateComponentToWorld(); + //root->SetSimulatePhysics(false); + + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + + // Stop Physics sim for socketing + root->SetSimulatePhysics(false); + + if (pActor) + { + if (IsServer() && root == pActor->GetRootComponent()) //&& !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(pActor, this, NewDrop, true); + } + } + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(root, this, NewDrop.GripID, false); + + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(root, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(root, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, true); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(root, this, NewDrop, true); + if (IVRGripInterface* GripInterface = Cast<IVRGripInterface>(root)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, true); + } + } + + // Call on child grip release on attached parent component + if (root->GetAttachParent() && root->GetAttachParent()->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(root->GetAttachParent(), this, NewDrop, true); + } + } + }break; + } + + // Copy over the information instead of working with a reference for the OnDroppedBroadcast + FBPActorGripInformation DropBroadcastData = NewDrop; + + int fIndex = 0; + if (LocallyGrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || GetNetMode() < ENetMode::NM_Client) + { + LocallyGrippedObjects.RemoveAt(fIndex); + } + else + { + LocallyGrippedObjects[fIndex].bIsPendingKill = true; + LocallyGrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + else + { + fIndex = 0; + if (GrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || GetNetMode() < ENetMode::NM_Client) + { + GrippedObjects.RemoveAt(fIndex); + } + else + { + GrippedObjects[fIndex].bIsPendingKill = true; + GrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + } + + // Broadcast a new drop + OnDroppedObject.Broadcast(DropBroadcastData, true); +} + + +// No longer an RPC, now is called from RepNotify so that joining clients also correctly set up grips +bool UGripMotionControllerComponent::NotifyGrip(FBPActorGripInformation &NewGrip, bool bIsReInit) +{ + UPrimitiveComponent *root = NULL; + AActor *pActor = NULL; + + bool bRootHasInterface = false; + bool bActorHasInterface = false; + + if (!NewGrip.GrippedObject || !NewGrip.GrippedObject->IsValidLowLevelFast()) + return false; + + if (!NewGrip.AdvancedGripSettings.bDisallowLerping && !bIsReInit && NewGrip.GripCollisionType != EGripCollisionType::EventsOnly && NewGrip.GripCollisionType != EGripCollisionType::CustomGrip) + { + // Init lerping + InitializeLerpToHand(NewGrip); + } + + switch (NewGrip.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + pActor = NewGrip.GetGrippedActor(); + + if (pActor) + { + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + // Actor grip interface is checked after component + bActorHasInterface = true; + } + + if (APawn* OwningPawn = Cast<APawn>(GetOwner())) + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + OwningPawn->MoveIgnoreActorAdd(pActor); + } + + // Now I am setting the owner to the owning pawn if we are one + // This makes sure that some special replication needs are taken care of + // Only doing this for actor grips + if (NewGrip.AdvancedGripSettings.bSetOwnerOnGrip) + { + if (GetNetMode() < ENetMode::NM_Client) + { + pActor->SetOwner(OwningPawn); + } + } + } + + if (!bIsReInit && bActorHasInterface) + { + IVRGripInterface::Execute_SetHeld(pActor, this, NewGrip.GripID, true); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(pActor, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnGrip(this, NewGrip); + } + } + } + + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnGrip(pActor, this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + + // Now check for c++ specific implementation and throw the native event if we need too + if (IVRGripInterface* GripInterface = Cast<IVRGripInterface>(pActor)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, true, NewGrip, false); + + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + } + + } + + if (root) + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + // Have to turn off gravity locally + if ((NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewGrip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(false); + } + //root->IgnoreActorWhenMoving(this->GetOwner(), true); + } + + + } + else + return false; + + if (bActorHasInterface && !EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + if (bProjectNonSimulatingGrips) + { + RegisterEndPhysicsTick(true); + } + else + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(pActor); + + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + RegisterEndPhysicsTick(true); + } + } + } + + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + root = NewGrip.GetGrippedComponent(); + + if (root) + { + pActor = root->GetOwner(); + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + // Actor grip interface is checked after component + bActorHasInterface = true; + } + + if (!bIsReInit && bRootHasInterface) + { + IVRGripInterface::Execute_SetHeld(root, this, NewGrip.GripID, true); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(root, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnGrip(this, NewGrip); + } + } + } + + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnGrip(root, this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + + // Now throw the native event if it implements the native interface + if (IVRGripInterface* GripInterface = Cast<IVRGripInterface>(root)) + { + //GripInterface->Execute_OnGrip(root, this, NewGrip); + GripInterface->Native_NotifyThrowGripDelegates(this, true, NewGrip, false); + + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + + } + + } + + if (pActor) + { + /*if (APawn* OwningPawn = Cast<APawn>(GetOwner())) + { + OwningPawn->MoveIgnoreActorAdd(root->GetOwner()); + }*/ + + if (!bIsReInit && bActorHasInterface) + { + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnChildGrip(pActor, this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + } + + } + + // Call OnChildGrip for attached grip parent + if (!bIsReInit && root->GetAttachParent() && root->GetAttachParent()->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + uint8 GripID = NewGrip.GripID; + IVRGripInterface::Execute_OnChildGrip(root->GetAttachParent(), this, NewGrip); + if (!LocallyGrippedObjects.Contains(GripID) && !GrippedObjects.Contains(GripID)) + { + return false; + } + } + + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + if ((NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewGrip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(false); + } + + //root->IgnoreActorWhenMoving(this->GetOwner(), true); + } + else + return false; + + if (bRootHasInterface && !EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + if (bProjectNonSimulatingGrips) + { + RegisterEndPhysicsTick(true); + } + else + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(root); + + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + RegisterEndPhysicsTick(true); + } + } + } + + }break; + } + + switch (NewGrip.GripMovementReplicationSetting) + { + case EGripMovementReplicationSettings::ForceClientSideMovement: + case EGripMovementReplicationSettings::ClientSide_Authoritive: + case EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep: + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (IsServer() && pActor && ((NewGrip.GripTargetType == EGripTargetType::ActorGrip) || (root && root == pActor->GetRootComponent()))) + { + pActor->SetReplicateMovement(false); + } + if (root) + { + // #TODO: This is a hack until Epic fixes their new physics replication code + // It forces the replication target to null on grip if we aren't repping movement. + + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (FPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + FBodyInstance* BI = root->GetBodyInstance(NewGrip.GrippedBoneName); + if (BI && BI->IsInstanceSimulatingPhysics()) + { + PhysicsReplication->RemoveReplicatedTarget(root); + //PhysicsReplication->SetReplicatedTarget(this, BoneName, UpdatedState); + } + } + } + } + } + } + + }break; + + case EGripMovementReplicationSettings::ForceServerSideMovement: + { + if (NewGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (IsServer() && pActor && ((NewGrip.GripTargetType == EGripTargetType::ActorGrip) || (root && root == pActor->GetRootComponent()))) + { + pActor->SetReplicateMovement(true); + } + } + }break; + + case EGripMovementReplicationSettings::KeepOriginalMovement: + default: + {}break; + } + + bool bHasMovementAuthority = HasGripMovementAuthority(NewGrip); + + switch (NewGrip.GripCollisionType) + { + case EGripCollisionType::InteractiveCollisionWithPhysics: + case EGripCollisionType::LockedConstraint: + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + if (bHasMovementAuthority) + { + SetUpPhysicsHandle(NewGrip); + } + } break; + + + case EGripCollisionType::InteractiveHybridCollisionWithPhysics: + { + if (bHasMovementAuthority) + { + SetUpPhysicsHandle(NewGrip); + } + } break; + + // Skip collision intersects with these types, they dont need it + case EGripCollisionType::EventsOnly: + case EGripCollisionType::CustomGrip: + { + // Should have never been turning off physics here, simulating is a valid custom grip state + //if (root) + //root->SetSimulatePhysics(false); + + } break; + + case EGripCollisionType::AttachmentGrip: + { + if (root) + root->SetSimulatePhysics(false); + + // Move it to the correct location automatically + if (bHasMovementAuthority) + { + if (!NewGrip.bIsLerping) + { + TeleportMoveGrip(NewGrip); + } + } + + if (bHasMovementAuthority || IsServer()) + { + FName BoneName = IsValid(CustomPivotComponent) ? CustomPivotComponentSocketName : NAME_None; + root->AttachToComponent(IsValid(CustomPivotComponent) ? CustomPivotComponent.Get() : this, FAttachmentTransformRules(EAttachmentRule::KeepWorld, true), BoneName); + } + + }break; + + case EGripCollisionType::PhysicsOnly: + case EGripCollisionType::SweepWithPhysics: + case EGripCollisionType::InteractiveHybridCollisionWithSweep: + case EGripCollisionType::InteractiveCollisionWithSweep: + default: + { + + if (root) + { + if (root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + } + + if(root->GetAttachParent()) + { + root->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + } + } + + // Move it to the correct location automatically + if (bHasMovementAuthority) + { + if (!NewGrip.bIsLerping) + { + TeleportMoveGrip(NewGrip); + } + } + } break; + + } + + if (!bIsReInit) + { + // Broadcast a new grip + OnGrippedObject.Broadcast(NewGrip); + if (!LocallyGrippedObjects.Contains(NewGrip.GripID) && !GrippedObjects.Contains(NewGrip.GripID)) + { + return false; + } + } + + return true; +} + +void UGripMotionControllerComponent::InitializeLerpToHand(FBPActorGripInformation & GripInformation) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + if (!VRSettings.bUseGlobalLerpToHand || VRSettings.LerpDuration <= 0.f) + return; + + if (VRSettings.bSkipLerpToHandIfHeld && GripInformation.GrippedObject && GripInformation.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bool bIsHeld = false; + TArray<FBPGripPair> HoldingControllers; + // Check if a different controller is holding it + IVRGripInterface::Execute_IsHeld(GripInformation.GrippedObject, HoldingControllers, bIsHeld); + + if (HoldingControllers.Num() > 0) + { + for (FBPGripPair& ControllerPair : HoldingControllers) + { + if (ControllerPair.HoldingController && ControllerPair.HoldingController != this) + { + FBPActorGripInformation Grip; + EBPVRResultSwitch Result; + ControllerPair.HoldingController->GetGripByID(Grip, ControllerPair.GripID, Result); + + if (Result != EBPVRResultSwitch::OnFailed) + { + if (Grip.IsValid()) + { + // We are skipping lerping now + return; + } + } + } + } + } + } + + EBPVRResultSwitch Result; + TSubclassOf<class UVRGripScriptBase> Class = UGS_LerpToHand::StaticClass(); + UVRGripScriptBase::GetGripScriptByClass(GripInformation.GrippedObject, Class, Result); + if (Result == EBPVRResultSwitch::OnSucceeded) + { + return; + } + + if (USceneComponent* PrimParent = Cast<USceneComponent>(GripInformation.GrippedObject)) + { + if (GripInformation.GrippedBoneName != NAME_None) + { + GripInformation.OnGripTransform = PrimParent->GetSocketTransform(GripInformation.GrippedBoneName); + } + else + { + GripInformation.OnGripTransform = PrimParent->GetComponentTransform(); + } + } + else if (AActor* ParentActor = Cast<AActor>(GripInformation.GrippedObject)) + { + GripInformation.OnGripTransform = ParentActor->GetActorTransform(); + } + + FTransform TargetTransform = GripInformation.RelativeTransform * this->GetPivotTransform(); + float Distance = FVector::Dist(GripInformation.OnGripTransform.GetLocation(), TargetTransform.GetLocation()); + if (VRSettings.MinDistanceForLerp > 0.0f && Distance < VRSettings.MinDistanceForLerp) + { + // Don't init + GripInformation.bIsLerping = false; + //OnLerpToHandFinished.Broadcast(GripInformation); + return; + } + else + { + float LerpScaler = 1.0f; + float DistanceToSpeed = Distance / VRSettings.LerpDuration; + if (DistanceToSpeed < VRSettings.MinSpeedForLerp) + { + LerpScaler = VRSettings.MinSpeedForLerp / DistanceToSpeed; + } + else if (VRSettings.MaxSpeedForLerp > 0.f && DistanceToSpeed > VRSettings.MaxSpeedForLerp) + { + LerpScaler = VRSettings.MaxSpeedForLerp / DistanceToSpeed; + } + else + { + LerpScaler = 1.0f; + } + + // Get the modified lerp speed + GripInformation.LerpSpeed = ((1.f / VRSettings.LerpDuration) * LerpScaler); + GripInformation.bIsLerping = true; + GripInformation.CurrentLerpTime = 0.0f; + } + + GripInformation.CurrentLerpTime = 0.0f; +} + +void UGripMotionControllerComponent::HandleGlobalLerpToHand(FBPActorGripInformation& GripInformation, FTransform& WorldTransform, float DeltaTime) +{ + UVRGlobalSettings* VRSettings = GetMutableDefault<UVRGlobalSettings>(); + + if (!VRSettings->bUseGlobalLerpToHand || !GripInformation.bIsLerping) + return; + + EBPVRResultSwitch Result; + TSubclassOf<class UVRGripScriptBase> Class = UGS_LerpToHand::StaticClass(); + UVRGripScriptBase * LerpScript = UVRGripScriptBase::GetGripScriptByClass(GripInformation.GrippedObject, Class, Result); + if (Result == EBPVRResultSwitch::OnSucceeded && LerpScript && LerpScript->IsScriptActive()) + { + return; + } + + if (VRSettings->LerpDuration <= 0.f) + { + GripInformation.bIsLerping = false; + GripInformation.CurrentLerpTime = 0.f; + OnLerpToHandFinished.Broadcast(GripInformation); + return; + } + + FTransform NA = GripInformation.OnGripTransform;//root->GetComponentTransform(); + float Alpha = 0.0f; + + GripInformation.CurrentLerpTime += DeltaTime * GripInformation.LerpSpeed; + float OrigAlpha = FMath::Clamp(GripInformation.CurrentLerpTime, 0.f, 1.0f); + Alpha = OrigAlpha; + + if (VRSettings->bUseCurve) + { + if (FRichCurve* richCurve = VRSettings->OptionalCurveToFollow.GetRichCurve()) + { + /*if (CurrentLerpTime > richCurve->GetLastKey().Time) + { + // Stop lerping + OnLerpToHandFinished.Broadcast(); + CurrentLerpTime = 0.0f; + bIsActive = false; + return true; + } + else*/ + { + Alpha = FMath::Clamp(richCurve->Eval(Alpha), 0.f, 1.f); + //CurrentLerpTime += DeltaTime; + } + } + } + + FTransform NB = WorldTransform; + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + if (VRSettings->LerpInterpolationMode == EVRLerpInterpolationMode::QuatInterp) + { + WorldTransform.Blend(NA, NB, Alpha); + } + + // Euler Angle interpolation + else if (VRSettings->LerpInterpolationMode == EVRLerpInterpolationMode::EulerInterp) + { + WorldTransform.SetTranslation(FMath::Lerp(NA.GetTranslation(), NB.GetTranslation(), Alpha)); + WorldTransform.SetScale3D(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + + FRotator A = NA.Rotator(); + FRotator B = NB.Rotator(); + WorldTransform.SetRotation(FQuat(A + (Alpha * (B - A)))); + } + // Dual quaternion interpolation + else + { + if ((NB.GetRotation() | NA.GetRotation()) < 0.0f) + { + NB.SetRotation(NB.GetRotation() * -1.0f); + } + WorldTransform = (FDualQuat(NA) * (1 - Alpha) + FDualQuat(NB) * Alpha).Normalized().AsFTransform(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + } + + // Turn it off if we need to + if (OrigAlpha >= 1.0f) + { + GripInformation.CurrentLerpTime = 0.0f; + GripInformation.bIsLerping = false; + + if (bConstrainToPivot) + { + DestroyPhysicsHandle(GripInformation, false); + SetUpPhysicsHandle(GripInformation); + } + + + OnLerpToHandFinished.Broadcast(GripInformation); + } +} + +void UGripMotionControllerComponent::CancelGlobalLerpToHand(uint8 GripID) +{ + FBPActorGripInformation* GripToUse = nullptr; + if (GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripID); + } + + if (GripToUse) + { + GripToUse->bIsLerping = false; + + if (bConstrainToPivot) + { + DestroyPhysicsHandle(*GripToUse, false); + SetUpPhysicsHandle(*GripToUse); + } + + GripToUse->CurrentLerpTime = 0.0f; + OnLerpToHandFinished.Broadcast(*GripToUse); + } + } +} + +void UGripMotionControllerComponent::NotifyDrop_Implementation(const FBPActorGripInformation &NewDrop, bool bSimulate) +{ + // Don't do this if we are the owning player on a local grip, there is no filter for multicast to not send to owner + if ((NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && + IsLocallyControlled() && + GetNetMode() == ENetMode::NM_Client) + { + // If we still have the grip then the server is asking us to drop it even though it is locally controlled + if (FBPActorGripInformation * GripInfo = GetGripPtrByID(NewDrop.GripID)) + { + DropGrip_Implementation(*GripInfo, bSimulate, FVector::ZeroVector, FVector::ZeroVector, true); + } + + return; + } + + Drop_Implementation(NewDrop, bSimulate); +} + +void UGripMotionControllerComponent::Drop_Implementation(const FBPActorGripInformation &NewDrop, bool bSimulate) +{ + + bool bSkipFullDrop = false; + bool bHadAnotherSelfGrip = false; + TArray<FBPGripPair> HoldingControllers; + bool bIsHeld = false; + + // Check if a different controller is holding it + if(NewDrop.GrippedObject && NewDrop.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + IVRGripInterface::Execute_IsHeld(NewDrop.GrippedObject, HoldingControllers, bIsHeld); + + if (bIsHeld && (!HoldingControllers.Contains(this) || HoldingControllers.Num() > 1)) + { + // Skip the full drop if held + bSkipFullDrop = true; + } + else // Now check for this same hand with duplicate grips on this object + { + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GrippedObject == NewDrop.GrippedObject && LocallyGrippedObjects[i].GripID != NewDrop.GripID) + { + bSkipFullDrop = true; + bHadAnotherSelfGrip = true; + } + } + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].GrippedObject == NewDrop.GrippedObject && GrippedObjects[i].GripID != NewDrop.GripID) + { + bSkipFullDrop = true; + bHadAnotherSelfGrip = true; + } + } + } + + DestroyPhysicsHandle(NewDrop, bHadAnotherSelfGrip); + + bool bHadGripAuthority = HasGripAuthority(NewDrop); + + UPrimitiveComponent *root = NULL; + AActor * pActor = NULL; + + switch (NewDrop.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + pActor = NewDrop.GetGrippedActor(); + + if (pActor) + { + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (!bSkipFullDrop) + { + + pActor->RemoveTickPrerequisiteComponent(this); + //this->IgnoreActorWhenMoving(pActor, false); + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (APawn * OwningPawn = Cast<APawn>(GetOwner())) + { + OwningPawn->MoveIgnoreActorRemove(pActor); + + // Clearing owner out here + // Now I am setting the owner to the owning pawn if we are one + // This makes sure that some special replication needs are taken care of + // Only doing this for actor grips + // #TODO: Add the removal back in? + //pActor->SetOwner(nullptr); + } + } + + if (root) + { + + if (NewDrop.GripCollisionType == EGripCollisionType::AttachmentGrip && (HasGripAuthority(NewDrop) || IsServer())) + root->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (IsServer() || bHadGripAuthority || !NewDrop.bOriginalReplicatesMovement || !pActor->GetIsReplicated()) + { + if (!NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || !NewDrop.AdvancedGripSettings.PhysicsSettings.bSkipSettingSimulating) + { + if (root->IsSimulatingPhysics() != bSimulate) + { + root->SetSimulatePhysics(bSimulate); + } + + if (bSimulate) + root->WakeAllRigidBodies(); + } + } + + root->UpdateComponentToWorld(); // This fixes the late update offset + } + + /*if (NewDrop.GrippedBoneName == NAME_None) + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + else + { + USkeletalMeshComponent * skele = Cast<USkeletalMeshComponent>(root); + if (skele) + { + skele->SetAllBodiesBelowSimulatePhysics(NewDrop.GrippedBoneName, bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + } + else + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + }*/ + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + } + } + } + + if (IsServer() && !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(pActor, this, NewDrop.GripID, false); + + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(pActor, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(pActor, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, false); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(pActor, this, NewDrop, false); + if (IVRGripInterface* GripInterface = Cast<IVRGripInterface>(pActor)) + { + //GripInterface->Execute_OnGripRelease(pActor, this, NewDrop, false); + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, false); + } + } + } + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + root = NewDrop.GetGrippedComponent(); + if (root) + { + pActor = root->GetOwner(); + + if (!bSkipFullDrop) + { + root->RemoveTickPrerequisiteComponent(this); + + /*if (APawn* OwningPawn = Cast<APawn>(GetOwner())) + { + OwningPawn->MoveIgnoreActorRemove(pActor); + }*/ + + if (NewDrop.GripCollisionType == EGripCollisionType::AttachmentGrip && (HasGripAuthority(NewDrop) || IsServer())) + root->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + + //root->IgnoreActorWhenMoving(this->GetOwner(), false); + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + // Need to set simulation in all of these cases, including if it isn't the root component (simulation isn't replicated on non roots) + if (IsServer() || bHadGripAuthority || !NewDrop.bOriginalReplicatesMovement || (pActor && (pActor->GetRootComponent() != root || !pActor->GetIsReplicated()))) + { + if (!NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || !NewDrop.AdvancedGripSettings.PhysicsSettings.bSkipSettingSimulating) + { + if (root->IsSimulatingPhysics() != bSimulate) + { + root->SetSimulatePhysics(bSimulate); + } + + if (bSimulate) + root->WakeAllRigidBodies(); + } + } + + root->UpdateComponentToWorld(); // This fixes the late update offset + } + /*if (NewDrop.GrippedBoneName == NAME_None) + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + else + { + USkeletalMeshComponent * skele = Cast<USkeletalMeshComponent>(root); + if (skele) + { + skele->SetAllBodiesBelowSimulatePhysics(NewDrop.GrippedBoneName, bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + } + else + { + root->SetSimulatePhysics(bSimulate); + root->UpdateComponentToWorld(); // This fixes the late update offset + if (bSimulate) + root->WakeAllRigidBodies(); + } + }*/ + + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if ((NewDrop.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewDrop.AdvancedGripSettings.PhysicsSettings.bTurnOffGravityDuringGrip) || + (NewDrop.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !IsServer())) + root->SetEnableGravity(NewDrop.bOriginalGravity); + } + } + + if (pActor) + { + if (IsServer() && root == pActor->GetRootComponent() && !bSkipFullDrop) + { + pActor->SetReplicateMovement(NewDrop.bOriginalReplicatesMovement); + } + + if (pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(pActor, this, NewDrop, false); + } + + } + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_SetHeld(root, this, NewDrop.GripID, false); + + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment || SecondaryGripIDs.Contains(NewDrop.GripID)) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(root, this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + OnSecondaryGripRemoved.Broadcast(NewDrop); + } + + SecondaryGripIDs.Remove(NewDrop.GripID); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(root, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + if (NewDrop.SecondaryGripInfo.bHasSecondaryAttachment) + Script->OnSecondaryGripRelease(this, NewDrop.SecondaryGripInfo.SecondaryAttachment, NewDrop); + + Script->OnGripRelease(this, NewDrop, false); + } + } + } + + IVRGripInterface::Execute_OnGripRelease(root, this, NewDrop, false); + if (IVRGripInterface* GripInterface = Cast<IVRGripInterface>(root)) + { + GripInterface->Native_NotifyThrowGripDelegates(this, false, NewDrop, false); + } + + } + + // Call on child grip release on attached parent component + if (root->GetAttachParent() && root->GetAttachParent()->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnChildGripRelease(root->GetAttachParent(), this, NewDrop, false); + } + } + }break; + } + + + switch (NewDrop.GripMovementReplicationSetting) + { + case EGripMovementReplicationSettings::ForceClientSideMovement: + case EGripMovementReplicationSettings::ClientSide_Authoritive: + case EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep: + { + if (NewDrop.GripCollisionType != EGripCollisionType::EventsOnly) + { + if (root) + { + // #TODO: This is a hack until Epic fixes their new physics replication code + // It forces the replication target to null on grip if we aren't repping movement. + if (UWorld * World = GetWorld()) + { + if (FPhysScene * PhysScene = World->GetPhysicsScene()) + { + if (FPhysicsReplication * PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + FBodyInstance* BI = root->GetBodyInstance(NewDrop.GrippedBoneName); + if (BI && BI->IsInstanceSimulatingPhysics()) + { + PhysicsReplication->RemoveReplicatedTarget(root); + //PhysicsReplication->SetReplicatedTarget(this, BoneName, UpdatedState); + } + } + } + } + } + } + + }break; + + }; + + + + + // Copy over the information instead of working with a reference for the OnDroppedBroadcast + FBPActorGripInformation DropBroadcastData = NewDrop; + + int fIndex = 0; + if (LocallyGrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || GetNetMode() < ENetMode::NM_Client) + { + LocallyGrippedObjects.RemoveAt(fIndex); + } + else + { + LocallyGrippedObjects[fIndex].bIsPendingKill = true; + LocallyGrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + else + { + fIndex = 0; + if (GrippedObjects.Find(NewDrop, fIndex)) + { + if (HasGripAuthority(NewDrop) || GetNetMode() < ENetMode::NM_Client) + { + GrippedObjects.RemoveAt(fIndex); + } + else + { + GrippedObjects[fIndex].bIsPendingKill = true; + GrippedObjects[fIndex].bIsPaused = true; // Pause it instead of dropping, dropping can corrupt the array in rare cases + } + } + } + + // Broadcast a new drop + OnDroppedObject.Broadcast(DropBroadcastData, false); + + + // Now check if we should turn off any post physics ticking + if (EndPhysicsTickFunction.IsTickFunctionRegistered()) + { + bool bNeedsPhysicsTick = false; + + if (LocallyGrippedObjects.Num() > 0 || GrippedObjects.Num() > 0) + { + if (bProjectNonSimulatingGrips) + { + bNeedsPhysicsTick = true; + } + else + { + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(LocallyGrippedObjects[i].GrippedObject); + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + bNeedsPhysicsTick = true; + break; + } + } + } + + if (!bNeedsPhysicsTick) + { + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + EGripInterfaceTeleportBehavior TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(GrippedObjects[i].GrippedObject); + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation) + { + bNeedsPhysicsTick = true; + break; + } + } + } + } + } + } + + if (!bNeedsPhysicsTick) + { + RegisterEndPhysicsTick(false); + } + } +} + +bool UGripMotionControllerComponent::BP_IsLocallyControlled() +{ + return IsLocallyControlled(); +} + +bool UGripMotionControllerComponent::BP_HasGripAuthority(const FBPActorGripInformation &Grip) +{ + return HasGripAuthority(Grip); +} + +bool UGripMotionControllerComponent::BP_HasGripAuthorityForObject(const UObject * ObjToCheck) +{ + return HasGripAuthority(ObjToCheck); +} + +bool UGripMotionControllerComponent::BP_HasGripMovementAuthority(const FBPActorGripInformation &Grip) +{ + return HasGripMovementAuthority(Grip); +} + +bool UGripMotionControllerComponent::AddSecondaryAttachmentPoint(UObject * GrippedObjectToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform & OriginalTransform, bool bTransformIsAlreadyRelative, float LerpToTime,/* float SecondarySmoothingScaler,*/ bool bIsSlotGrip, FName SecondarySlotName) +{ + if (!GrippedObjectToAddAttachment || !SecondaryPointComponent || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripToUse = nullptr; + + GripToUse = LocallyGrippedObjects.FindByKey(GrippedObjectToAddAttachment); + + // Search replicated grips if not found in local + if (!GripToUse) + { + // Replicated grips need to be called from server side + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called on the client side with a replicated grip")); + return false; + } + + GripToUse = GrippedObjects.FindByKey(GrippedObjectToAddAttachment); + } + + if (GripToUse) + { + return AddSecondaryAttachmentToGrip(*GripToUse, SecondaryPointComponent, OriginalTransform, bTransformIsAlreadyRelative, LerpToTime, bIsSlotGrip, SecondarySlotName); + } + + return false; +} + +bool UGripMotionControllerComponent::AddSecondaryAttachmentToGripByID(const uint8 GripID, USceneComponent* SecondaryPointComponent, const FTransform& OriginalTransform, bool bTransformIsAlreadyRelative, float LerpToTime, bool bIsSlotGrip, FName SecondarySlotName) +{ + FBPActorGripInformation* GripToUse = nullptr; + if (GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripID); + } + + if (GripToUse) + { + return AddSecondaryAttachmentToGrip(*GripToUse, SecondaryPointComponent, OriginalTransform, bTransformIsAlreadyRelative, LerpToTime, bIsSlotGrip, SecondarySlotName); + } + } + + return false; +} + +bool UGripMotionControllerComponent::AddSecondaryAttachmentToGrip(const FBPActorGripInformation & GripToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform &OriginalTransform, bool bTransformIsAlreadyRelative, float LerpToTime, bool bIsSlotGrip, FName SecondarySlotName) +{ + if (!SecondaryPointComponent) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called with a bad secondary component target!")); + return false; + } + + FBPActorGripInformation* GripToUse = nullptr; + bool bWasLocal = false; + if (GripToAddAttachment.GrippedObject && GripToAddAttachment.GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripToAddAttachment.GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripToAddAttachment.GripID); + bWasLocal = true; + } + } + + if (!GripToUse || GripToUse->GripID == INVALID_VRGRIP_ID) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called with a bad grip! It was not valid / found.")); + return false; + } + + if (!GripToUse->GrippedObject) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called with a bad grip (gripped object invalid)!")); + return false; + } + + // Replicated grips need to be called from server side + if (!bWasLocal && !IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called on the client side with a replicated grip")); + return false; + } + + bool bGrippedObjectIsInterfaced = GripToUse->GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()); + + if (bGrippedObjectIsInterfaced) + { + ESecondaryGripType SecondaryType = IVRGripInterface::Execute_SecondaryGripType(GripToUse->GrippedObject); + + if (SecondaryType == ESecondaryGripType::SG_None) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was called on an interface object set to SG_None!")); + return false; + } + } + + UPrimitiveComponent * root = nullptr; + + switch (GripToUse->GripTargetType) + { + case EGripTargetType::ActorGrip: + { + AActor * pActor = GripToUse->GetGrippedActor(); + + if (pActor) + { + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + } + } + break; + case EGripTargetType::ComponentGrip: + { + root = GripToUse->GetGrippedComponent(); + } + break; + } + + if (!root) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController add secondary attachment function was unable to get root component or gripped component.")); + return false; + } + + if (bTransformIsAlreadyRelative) + GripToUse->SecondaryGripInfo.SecondaryRelativeTransform = OriginalTransform; + else + GripToUse->SecondaryGripInfo.SecondaryRelativeTransform = OriginalTransform.GetRelativeTransform(root->GetComponentTransform()); + + GripToUse->SecondaryGripInfo.SecondaryAttachment = SecondaryPointComponent; + GripToUse->SecondaryGripInfo.bHasSecondaryAttachment = true; + GripToUse->SecondaryGripInfo.SecondaryGripDistance = 0.0f; + GripToUse->SecondaryGripInfo.SecondarySlotName = SecondarySlotName; + + /*const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.CutoffSlope = VRSettings.OneEuroCutoffSlope; + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.DeltaCutoff = VRSettings.OneEuroDeltaCutoff; + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.MinCutoff = VRSettings.OneEuroMinCutoff; + + GripToUse->AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.ResetSmoothingFilter();*/ + // GripToUse->SecondaryGripInfo.SecondarySmoothingScaler = FMath::Clamp(SecondarySmoothingScaler, 0.01f, 1.0f); + GripToUse->SecondaryGripInfo.bIsSlotGrip = bIsSlotGrip; + + if (GripToUse->SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + LerpToTime = 0.0f; + + if (LerpToTime > 0.0f) + { + GripToUse->SecondaryGripInfo.LerpToRate = LerpToTime; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::StartLerp; + GripToUse->SecondaryGripInfo.curLerp = LerpToTime; + } + + if (bGrippedObjectIsInterfaced) + { + SecondaryGripIDs.Add(GripToUse->GripID); + + IVRGripInterface::Execute_OnSecondaryGrip(GripToUse->GrippedObject, this, SecondaryPointComponent, *GripToUse); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(GripToUse->GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGrip(this, SecondaryPointComponent, *GripToUse); + } + } + } + } + + if (GripToUse->GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive && GetNetMode() == ENetMode::NM_Client && !IsTornOff()) + { + Server_NotifySecondaryAttachmentChanged(GripToUse->GripID, GripToUse->SecondaryGripInfo); + } + + OnSecondaryGripAdded.Broadcast(*GripToUse); + GripToUse = nullptr; + + return true; +} + +bool UGripMotionControllerComponent::RemoveSecondaryAttachmentPoint(UObject * GrippedObjectToRemoveAttachment, float LerpToTime) +{ + if (!GrippedObjectToRemoveAttachment || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripToUse = nullptr; + + // Duplicating the logic for each array for now + GripToUse = LocallyGrippedObjects.FindByKey(GrippedObjectToRemoveAttachment); + + // Check replicated grips if it wasn't found in local + if (!GripToUse) + { + if (!IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController remove secondary attachment function was called on the client side for a replicating grip")); + return false; + } + + GripToUse = GrippedObjects.FindByKey(GrippedObjectToRemoveAttachment); + } + + // Handle the grip if it was found + if (GripToUse && GripToUse->GrippedObject) + { + return RemoveSecondaryAttachmentFromGrip(*GripToUse, LerpToTime); + } + + return false; +} +bool UGripMotionControllerComponent::RemoveSecondaryAttachmentFromGripByID(const uint8 GripID, float LerpToTime) +{ + FBPActorGripInformation* GripToUse = nullptr; + if (GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripID); + } + + if (GripToUse) + { + return RemoveSecondaryAttachmentFromGrip(*GripToUse, LerpToTime); + } + } + + return false; +} + +bool UGripMotionControllerComponent::RemoveSecondaryAttachmentFromGrip(const FBPActorGripInformation & GripToRemoveAttachment, float LerpToTime) +{ + FBPActorGripInformation* GripToUse = nullptr; + bool bWasLocal = false; + if (GripToRemoveAttachment.GrippedObject && GripToRemoveAttachment.GripID != INVALID_VRGRIP_ID) + { + GripToUse = GrippedObjects.FindByKey(GripToRemoveAttachment.GripID); + if (!GripToUse) + { + GripToUse = LocallyGrippedObjects.FindByKey(GripToRemoveAttachment.GripID); + bWasLocal = true; + } + } + + if (GripToUse && !bWasLocal && !IsServer()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("VRGripMotionController remove secondary attachment function was called on the client side for a replicating grip")); + return false; + } + + // Handle the grip if it was found + if (GripToUse && GripToUse->GrippedObject && GripToUse->GripID != INVALID_VRGRIP_ID) + { + SecondaryGripIDs.Remove(GripToUse->GripID); + + if (GripToUse->SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) + LerpToTime = 0.0f; + + //if (LerpToTime > 0.0f) + //{ + UPrimitiveComponent * primComp = nullptr; + + switch (GripToUse->GripTargetType) + { + case EGripTargetType::ComponentGrip: + { + primComp = GripToUse->GetGrippedComponent(); + }break; + case EGripTargetType::ActorGrip: + { + AActor * pActor = GripToUse->GetGrippedActor(); + if (pActor) + primComp = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + } break; + } + + bool bGripObjectHasInterface = GripToUse->GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()); + + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + if (bGripObjectHasInterface) + { + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(GripToUse->GrippedObject); + //else if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling) + //LerpToTime = 0.0f; + } + + if (primComp) + { + switch (SecondaryType) + { + // All of these retain the position on release + case ESecondaryGripType::SG_FreeWithScaling_Retain: + case ESecondaryGripType::SG_SlotOnlyWithScaling_Retain: + case ESecondaryGripType::SG_Free_Retain: + case ESecondaryGripType::SG_SlotOnly_Retain: + case ESecondaryGripType::SG_ScalingOnly: + { + GripToUse->RelativeTransform = primComp->GetComponentTransform().GetRelativeTransform(GetPivotTransform()); + GripToUse->SecondaryGripInfo.LerpToRate = 0.0f; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + }break; + default: + { + if (LerpToTime > 0.0f) + { + // #TODO: This had a hitch in it just prior to lerping back, fix it eventually and allow lerping from scaling secondaries + //GripToUse->RelativeTransform.SetScale3D(GripToUse->RelativeTransform.GetScale3D() * FVector(GripToUse->SecondaryScaler)); + GripToUse->SecondaryGripInfo.LerpToRate = LerpToTime; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::EndLerp; + GripToUse->SecondaryGripInfo.curLerp = LerpToTime; + } + }break; + } + + } + else + { + GripToUse->SecondaryGripInfo.LerpToRate = 0.0f; + GripToUse->SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + if (bGripObjectHasInterface) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(GripToUse->GrippedObject, this, GripToUse->SecondaryGripInfo.SecondaryAttachment, *GripToUse); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(GripToUse->GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGripRelease(this, GripToUse->SecondaryGripInfo.SecondaryAttachment, *GripToUse); + } + } + } + } + + GripToUse->SecondaryGripInfo.SecondaryAttachment = nullptr; + GripToUse->SecondaryGripInfo.bHasSecondaryAttachment = false; + + if (GripToUse->GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive && GetNetMode() == ENetMode::NM_Client) + { + switch (SecondaryType) + { + // All of these retain the position on release + case ESecondaryGripType::SG_FreeWithScaling_Retain: + case ESecondaryGripType::SG_SlotOnlyWithScaling_Retain: + case ESecondaryGripType::SG_Free_Retain: + case ESecondaryGripType::SG_SlotOnly_Retain: + case ESecondaryGripType::SG_ScalingOnly: + { + if (!IsTornOff()) + Server_NotifySecondaryAttachmentChanged_Retain(GripToUse->GripID, GripToUse->SecondaryGripInfo, GripToUse->RelativeTransform); + }break; + default: + { + if (!IsTornOff()) + Server_NotifySecondaryAttachmentChanged(GripToUse->GripID, GripToUse->SecondaryGripInfo); + }break; + } + + } + + SecondaryGripIDs.Remove(GripToUse->GripID); + OnSecondaryGripRemoved.Broadcast(*GripToUse); + GripToUse = nullptr; + return true; + } + + return false; +} + +bool UGripMotionControllerComponent::TeleportMoveGrippedActor(AActor * GrippedActorToMove, bool bTeleportPhysicsGrips) +{ + if (!GrippedActorToMove || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(GrippedActorToMove); + if (!GripInfo) + GrippedObjects.FindByKey(GrippedActorToMove); + + if (GripInfo) + { + return TeleportMoveGrip(*GripInfo, bTeleportPhysicsGrips); + } + + return false; +} + +bool UGripMotionControllerComponent::TeleportMoveGrippedComponent(UPrimitiveComponent * ComponentToMove, bool bTeleportPhysicsGrips) +{ + if (!ComponentToMove || (!GrippedObjects.Num() && !LocallyGrippedObjects.Num())) + return false; + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(ComponentToMove); + if (!GripInfo) + GrippedObjects.FindByKey(ComponentToMove); + + if (GripInfo) + { + return TeleportMoveGrip(*GripInfo, bTeleportPhysicsGrips); + } + + return false; +} + +void UGripMotionControllerComponent::TeleportMoveGrips(bool bTeleportPhysicsGrips, bool bIsForPostTeleport) +{ + FTransform EmptyTransform = FTransform::Identity; + for (FBPActorGripInformation& GripInfo : LocallyGrippedObjects) + { + TeleportMoveGrip_Impl(GripInfo, bTeleportPhysicsGrips, bIsForPostTeleport, EmptyTransform); + } + + for (FBPActorGripInformation& GripInfo : GrippedObjects) + { + TeleportMoveGrip_Impl(GripInfo, bTeleportPhysicsGrips, bIsForPostTeleport, EmptyTransform); + } +} + +bool UGripMotionControllerComponent::TeleportMoveGrip(FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips, bool bIsForPostTeleport) +{ + FTransform EmptyTransform = FTransform::Identity; + return TeleportMoveGrip_Impl(Grip, bTeleportPhysicsGrips, bIsForPostTeleport, EmptyTransform); +} + +bool UGripMotionControllerComponent::TeleportMoveGrip_Impl(FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips, bool bIsForPostTeleport, FTransform & OptionalTransform) +{ + bool bHasMovementAuthority = HasGripMovementAuthority(Grip); + + if (!bHasMovementAuthority) + return false; + + UPrimitiveComponent * PrimComp = NULL; + AActor * actor = NULL; + + // Check if either implements the interface + bool bRootHasInterface = false; + bool bActorHasInterface = false; + + switch (Grip.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + actor = Grip.GetGrippedActor(); + if (actor) + { + PrimComp = Cast<UPrimitiveComponent>(actor->GetRootComponent()); + if (actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bActorHasInterface = true; + } + } + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + PrimComp = Grip.GetGrippedComponent(); + + if (PrimComp) + { + actor = PrimComp->GetOwner(); + if (PrimComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + } + + }break; + + } + + if (!PrimComp || !actor || !IsValid(actor) || !IsValid(PrimComp)) + return false; + + // Only use with actual teleporting + + EGripInterfaceTeleportBehavior TeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + bool bSimulateOnDrop = false; + + // Check for interaction interface + if (bRootHasInterface) + { + TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(PrimComp); + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(PrimComp); + } + else if (bActorHasInterface) + { + // Actor grip interface is checked after component + TeleportBehavior = IVRGripInterface::Execute_TeleportBehavior(actor); + bSimulateOnDrop = IVRGripInterface::Execute_SimulateOnDrop(actor); + } + + if (bIsForPostTeleport) + { + if (TeleportBehavior == EGripInterfaceTeleportBehavior::OnlyTeleportRootComponent) + { + if (AActor * owner = PrimComp->GetOwner()) + { + if (PrimComp != owner->GetRootComponent()) + { + return false; + } + } + } + else if (TeleportBehavior == EGripInterfaceTeleportBehavior::DropOnTeleport) + { + if (IsServer() || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + DropObjectByInterface(nullptr, Grip.GripID); + } + + return false; // Didn't teleport + } + else if (TeleportBehavior == EGripInterfaceTeleportBehavior::DontTeleport) + { + return false; // Didn't teleport + } + } + else + { + switch (TeleportBehavior) + { + case EGripInterfaceTeleportBehavior::DontTeleport: + case EGripInterfaceTeleportBehavior::DropOnTeleport: + { + return false; + }break; + default:break; + } + } + + FTransform WorldTransform; + FTransform ParentTransform = GetPivotTransform(); + + FBPActorGripInformation copyGrip = Grip; + + if (!OptionalTransform.Equals(FTransform::Identity)) + { + WorldTransform = OptionalTransform; + } + else + { + TArray<UVRGripScriptBase*> Scripts; + + if (bRootHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(PrimComp, Scripts); + } + else if (bActorHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(actor, Scripts); + } + + bool bForceADrop = false; + bool bHadValidWorldTransform = GetGripWorldTransform(Scripts, 0.0f, WorldTransform, ParentTransform, copyGrip, actor, PrimComp, bRootHasInterface, bActorHasInterface, true, bForceADrop); + + if (!bHadValidWorldTransform) + return false; + } + + if (!WorldTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, TeleportGrip_Impl's target transform contained NAN.")); + return false; + } + + // Saving this out prior as we are still setting our physics thread to the correct value, the delta is only applied to the object + FTransform physicsTrans = WorldTransform; + if (TeleportBehavior == EGripInterfaceTeleportBehavior::DeltaTeleportation && !Grip.LastWorldTransform.Equals(FTransform::Identity)) + { + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + WorldTransform = Grip.LastWorldTransform * baseTrans; + + //physicsTrans = WorldTransform; + + // Cancel out all other holding controllers teleport operations, we hit first + if (!Grip.bSkipNextTeleportCheck && (bRootHasInterface || bActorHasInterface)) + { + TArray<FBPGripPair> HoldingControllers; + bool bIsHeld = false; + IVRGripInterface::Execute_IsHeld(Grip.GrippedObject, HoldingControllers, bIsHeld); + + for (FBPGripPair pair : HoldingControllers) + { + if (pair.HoldingController && pair.HoldingController != this && pair.HoldingController->bIsPostTeleport) + { + FBPActorGripInformation* pGrip = pair.HoldingController->GetGripPtrByID(pair.GripID); + + if (pGrip) + { + pGrip->bSkipNextTeleportCheck = true; + } + } + } + } + } + + // Run some error checks and logging against the resulting transform + if (!WorldTransform.IsValid()) + { + if (WorldTransform.ContainsNaN()) + { + UE_LOG(LogVRMotionController, Error, TEXT("Failed to teleport grip, bad transform, NaN detected with object: %s"), *Grip.GrippedObject->GetName()); + return false; + } + else if (!WorldTransform.GetRotation().IsNormalized()) + { + WorldTransform.NormalizeRotation(); + + if (!WorldTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Error, TEXT("Failed to teleport grip, bad transform, rotation normalization issue: %s"), *Grip.GrippedObject->GetName()); + return false; + } + else + { + UE_LOG(LogVRMotionController, Error, TEXT("Error during teleport grip, rotation not normalized for object: %s"), *Grip.GrippedObject->GetName()); + } + } + } + + // Need to use WITH teleport for this function so that the velocity isn't updated and without sweep so that they don't collide + FBPActorPhysicsHandleInformation * Handle = GetPhysicsGrip(Grip); + + if (!Handle) + { + PrimComp->SetWorldTransform(WorldTransform, false, NULL, ETeleportType::TeleportPhysics); + } + else if (Handle && FPhysicsInterface::IsValid(Handle->KinActorData2) && bTeleportPhysicsGrips) + { + + // Don't try to autodrop on next tick, let the physx constraint update its local frame first + if (HasGripAuthority(Grip)) + Grip.bSkipNextConstraintLengthCheck = true; + + if (Grip.bSkipNextTeleportCheck) + { + Grip.bSkipNextTeleportCheck = false; + } + else + { + PrimComp->SetWorldTransform(WorldTransform, false, NULL, ETeleportType::TeleportPhysics); + } + + // Zero out our scale now that we are working outside of physx + physicsTrans.SetScale3D(FVector(1.0f)); + + if (Grip.bIsLerping || !bConstrainToPivot) + { + FPhysicsActorHandle ActorHandle = Handle->KinActorData2; + FTransform newTrans = Handle->COMPosition * (Handle->RootBoneRotation * physicsTrans); + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](const FPhysicsActorHandle& Actor) + { + // There is no real reason for these additional checks, the phys interface already does this and i check for validity above + // However in some rare cases the actor is somehow nulling out after here and its not an expensive call + if (FPhysicsInterface::IsValid(Actor) && FPhysicsInterface::GetCurrentScene(Actor)) + { + FPhysicsInterface::SetKinematicTarget_AssumesLocked(Actor, newTrans); + FPhysicsInterface::SetGlobalPose_AssumesLocked(Actor, newTrans); + } + }); + } + } + + return true; +} + +void UGripMotionControllerComponent::PostTeleportMoveGrippedObjects() +{ + if (!GrippedObjects.Num() && !LocallyGrippedObjects.Num()) + return; + + this->bIsPostTeleport = true; +} + + +void UGripMotionControllerComponent::Deactivate() +{ + Super::Deactivate(); + + if (IsActive() == false && GripViewExtension.IsValid()) + { + { + // This component could be getting accessed from the render thread so it needs to wait + // before clearing MotionControllerComponent + FScopeLock ScopeLock(&CritSect); + GripViewExtension->MotionControllerComponent = NULL; + } + + GripViewExtension.Reset(); + } +} + +void UGripMotionControllerComponent::OnAttachmentChanged() +{ + if (AVRBaseCharacter* CharacterOwner = Cast<AVRBaseCharacter>(this->GetOwner())) + { + AttachChar = CharacterOwner; + } + else + { + AttachChar = nullptr; + } + + Super::OnAttachmentChanged(); +} + +void UGripMotionControllerComponent::UpdateTracking(float DeltaTime) +{ + // Server/remote clients don't set the controller position in VR + // Don't call positional checks and don't create the late update scene view + if (bHasAuthority) + { + if (bOffsetByControllerProfile && !NewControllerProfileEvent_Handle.IsValid()) + { + GetCurrentProfileTransform(true); + } + + FVector Position = GetRelativeLocation(); + FRotator Orientation = GetRelativeRotation(); + + if (!bUseWithoutTracking) + { + if (!GripViewExtension.IsValid() && GEngine) + { + GripViewExtension = FSceneViewExtensions::NewExtension<FGripViewExtension>(this); + } + + float WorldToMeters = GetWorld() ? GetWorld()->GetWorldSettings()->WorldToMeters : 100.0f; + ETrackingStatus LastTrackingStatus = CurrentTrackingStatus; + const bool bNewTrackedState = GripPollControllerState(Position, Orientation, WorldToMeters); + + bTracked = bNewTrackedState && CurrentTrackingStatus != ETrackingStatus::NotTracked; + if (bTracked) + { + if (bSmoothHandTracking) + { + FTransform CalcedTransform = FTransform(Orientation, Position, this->GetRelativeScale3D()); + + if (bSmoothWithEuroLowPassFunction) + { + SetRelativeTransform(EuroSmoothingParams.RunFilterSmoothing(CalcedTransform, DeltaTime)); + } + else + { + if (SmoothingSpeed <= 0.f || LastSmoothRelativeTransform.Equals(FTransform::Identity)) + { + SetRelativeTransform(CalcedTransform); + LastSmoothRelativeTransform = CalcedTransform; + } + else + { + const float Alpha = FMath::Clamp(DeltaTime * SmoothingSpeed, 0.f, 1.f); + LastSmoothRelativeTransform.Blend(LastSmoothRelativeTransform, CalcedTransform, Alpha); + SetRelativeTransform(LastSmoothRelativeTransform); + } + } + + bWasSmoothingHand = true; + } + else + { + if (bWasSmoothingHand) + { + // Clear the smoothing information so that we start with a fresh log when its enabled again + LastSmoothRelativeTransform = FTransform::Identity; + EuroSmoothingParams.ResetSmoothingFilter(); + + bWasSmoothingHand = false; + } + + SetRelativeTransform(FTransform(Orientation, Position, this->GetRelativeScale3D())); + } + } + + // if controller tracking just changed + if (LastTrackingStatus != CurrentTrackingStatus) + { + OnTrackingChanged.Broadcast(CurrentTrackingStatus); + + if (LastTrackingStatus == ETrackingStatus::NotTracked) + { + // Handle the display component + // #TODO: Don't run if already has a display model, can't access yet + // || !DisplayComponent is private + if (bDisplayDeviceModel && DisplayModelSource != UMotionControllerComponent::CustomModelSourceId) + RefreshDisplayComponent(); + } + } + } + + if (!bTracked && !bUseWithoutTracking) + return; // Don't update anything including location + + // Don't bother with any of this if not replicating transform + if (GetIsReplicated() && (bTracked || bReplicateWithoutTracking)) + { + FVector RelLoc = GetRelativeLocation(); + FRotator RelRot = GetRelativeRotation(); + + // Don't rep if no changes + if (!RelLoc.Equals(ReplicatedControllerTransform.Position) || !RelRot.Equals(ReplicatedControllerTransform.Rotation)) + { + ControllerNetUpdateCount += DeltaTime; + if (ControllerNetUpdateCount >= (1.0f / ControllerNetUpdateRate)) + { + ControllerNetUpdateCount = 0.0f; + + // Tracked doesn't matter, already set the relative location above in that case + ReplicatedControllerTransform.Position = RelLoc; + ReplicatedControllerTransform.Rotation = RelRot; + + // I would keep the torn off check here, except this can be checked on tick if they + // Set 100 htz updates, and in the TornOff case, it actually can't hurt any besides some small + // Perf difference. + if (GetNetMode() == NM_Client/* && !IsTornOff()*/) + { + AVRBaseCharacter* OwningChar = Cast<AVRBaseCharacter>(GetOwner()); + if (OverrideSendTransform != nullptr && OwningChar != nullptr) + { + (OwningChar->* (OverrideSendTransform))(ReplicatedControllerTransform); + } + else + Server_SendControllerTransform(ReplicatedControllerTransform); + } + } + } + } + } + else + { + // Clear the view extension if active after unpossessing, just in case + if (GripViewExtension.IsValid()) + { + { + // This component could be getting accessed from the render thread so it needs to wait + // before clearing MotionControllerComponent and allowing the destructor to continue + FScopeLock ScopeLock(&CritSect); + GripViewExtension->MotionControllerComponent = NULL; + } + + GripViewExtension.Reset(); + } + + if (bLerpingPosition) + { + ControllerNetUpdateCount += DeltaTime; + float LerpVal = FMath::Clamp(ControllerNetUpdateCount / (1.0f / ControllerNetUpdateRate), 0.0f, 1.0f); + + if (LerpVal >= 1.0f) + { + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + + // Stop lerping, wait for next update if it is delayed or lost then it will hitch here + // Actual prediction might be something to consider in the future, but rough to do in VR + // considering the speed and accuracy of movements + // would like to consider sub stepping but since there is no server rollback...not sure how useful it would be + // and might be perf taxing enough to not make it worth it. + bLerpingPosition = false; + ControllerNetUpdateCount = 0.0f; + } + else + { + // Removed variables to speed this up a bit + SetRelativeLocationAndRotation( + FMath::Lerp(LastUpdatesRelativePosition, (FVector)ReplicatedControllerTransform.Position, LerpVal), + FMath::Lerp(LastUpdatesRelativeRotation, ReplicatedControllerTransform.Rotation, LerpVal) + ); + } + } + } +} + +void UGripMotionControllerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Skip motion controller tick, we override a lot of things that it does and we don't want it to perform the same functions + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (!IsActive()) + return; + + // Moved this here instead of in the polling function, it was ticking once per frame anyway so no loss of perf + // It doesn't need to be there and now I can pre-check + // Also epics implementation in the polling function didn't work anyway as it was based off of playercontroller which is not the owner of this controller + + // Cache state from the game thread for use on the render thread + // No need to check if in game thread here as tick always is + bHasAuthority = IsLocallyControlled(); + + // No longer updating in character, was a waste as it wouldn't scope this component anyway + UpdateTracking(DeltaTime); + + /*if (!bUpdateInCharacterMovement) + { + UpdateTracking(DeltaTime); + } + else if (AttachChar.IsValid()) + { + UCharacterMovementComponent* CharMove = AttachChar->GetCharacterMovement(); + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive() || (GetWorld()->IsPaused() && !AttachChar->GetCharacterMovement()->PrimaryComponentTick.bTickEvenWhenPaused)) + { + // Our character movement isn't handling our updates, lets do it ourself. + UpdateTracking(DeltaTime); + } + }*/ + + // Process the gripped actors + TickGrip(DeltaTime); +} + +bool UGripMotionControllerComponent::GetGripWorldTransform(TArray<UVRGripScriptBase*>& GripScripts, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport, bool &bForceADrop) +{ + SCOPE_CYCLE_COUNTER(STAT_GetGripTransform); + + bool bHasValidTransform = true; + + if (GripScripts.Num()) + { + bool bGetDefaultTransform = true; + + // Get grip script world transform overrides (if there are any) + for (UVRGripScriptBase* Script: GripScripts) + { + if (Script && Script->IsScriptActive() && Script->GetWorldTransformOverrideType() == EGSTransformOverrideType::OverridesWorldTransform) + { + // One of the grip scripts overrides the default transform + bGetDefaultTransform = false; + break; + } + } + + // If none of the scripts override the base transform + if (bGetDefaultTransform && DefaultGripScript) + { + bHasValidTransform = DefaultGripScript->CallCorrect_GetWorldTransform(this, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + bForceADrop = DefaultGripScript->Wants_ToForceDrop(); + } + + // Get grip script world transform modifiers (if there are any) + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->GetWorldTransformOverrideType() != EGSTransformOverrideType::None) + { + bHasValidTransform = Script->CallCorrect_GetWorldTransform(this, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + bForceADrop = Script->Wants_ToForceDrop(); + + // Early out, one of the scripts is telling us that the transform isn't valid, something went wrong or the grip is flagged for drop + if (!bHasValidTransform || bForceADrop) + break; + } + } + } + else + { + if (DefaultGripScript) + { + bHasValidTransform = DefaultGripScript->CallCorrect_GetWorldTransform(this, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + bForceADrop = DefaultGripScript->Wants_ToForceDrop(); + } + } + + HandleGlobalLerpToHand(Grip, WorldTransform, DeltaTime); + + if (bHasValidTransform && !WorldTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, GetGripWorldTransform tried to return NAN!.")); + bHasValidTransform = false; + } + + return bHasValidTransform; +} + +void UGripMotionControllerComponent::TickGrip(float DeltaTime) +{ + SCOPE_CYCLE_COUNTER(STAT_TickGrip); + + // Debug test that we aren't floating physics handles + if (PhysicsGrips.Num() > (GrippedObjects.Num() + LocallyGrippedObjects.Num())) + { + CleanUpBadPhysicsHandles(); + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, there were too many physics handles for how many grips exist! Cleaned up bad handles.")); + } + //check(PhysicsGrips.Num() <= (GrippedObjects.Num() + LocallyGrippedObjects.Num())); + + FTransform ParentTransform = GetPivotTransform(); + + // Check for floating server sided client auth grips and handle them if we need too + if(!IsServer()) + CheckTransactionBuffer(); + + bool bOriginalPostTeleport = bIsPostTeleport; + + // Split into separate functions so that I didn't have to combine arrays since I have some removal going on + HandleGripArray(GrippedObjects, ParentTransform, DeltaTime, true); + HandleGripArray(LocallyGrippedObjects, ParentTransform, DeltaTime); + + // Empty out the teleport flag, checking original state just in case the player changed it while processing bps + if (bOriginalPostTeleport) + { + if ((GrippedObjects.Num() || LocallyGrippedObjects.Num())) + { + OnTeleportedGrips.Broadcast(); + } + + bIsPostTeleport = false; + } + + // Save out the component velocity from this and last frame + + FVector newVelocitySample = ((bSampleVelocityInWorldSpace ? GetComponentLocation() : GetRelativeLocation()) - LastRelativePosition.GetTranslation()) / DeltaTime; + + switch (VelocityCalculationType) + { + case EVRVelocityType::VRLOCITY_Default: + { + ComponentVelocity = newVelocitySample; + }break; + case EVRVelocityType::VRLOCITY_RunningAverage: + { + UVRExpansionFunctionLibrary::LowPassFilter_RollingAverage(ComponentVelocity, newVelocitySample, ComponentVelocity, VelocitySamples); + }break; + case EVRVelocityType::VRLOCITY_SamplePeak: + { + if (PeakFilter.VelocitySamples != VelocitySamples) + PeakFilter.VelocitySamples = VelocitySamples; + UVRExpansionFunctionLibrary::UpdatePeakLowPassFilter(PeakFilter, newVelocitySample); + }break; + } + + // #TODO: + // Relative angular velocity too? + // Maybe add some running averaging here to make it work across frames? + // Or Valves 30 frame high point average buffer + LastRelativePosition = bSampleVelocityInWorldSpace ? this->GetComponentTransform() : this->GetRelativeTransform(); +} + +FVector UGripMotionControllerComponent::GetComponentVelocity() const +{ + if(VelocityCalculationType == EVRVelocityType::VRLOCITY_SamplePeak) + { + return PeakFilter.GetPeak(); + } + + return Super::GetComponentVelocity(); +} + +void UGripMotionControllerComponent::HandleGripArray(TArray<FBPActorGripInformation> &GrippedObjectsArray, const FTransform & ParentTransform, float DeltaTime, bool bReplicatedArray) +{ + if (GrippedObjectsArray.Num()) + { + FTransform WorldTransform; + + for (int i = GrippedObjectsArray.Num() - 1; i >= 0; --i) + { + if (!HasGripMovementAuthority(GrippedObjectsArray[i])) + continue; + + FBPActorGripInformation * Grip = &GrippedObjectsArray[i]; + + if (!Grip) // Shouldn't be possible, but why not play it safe + continue; + + // Double checking here for a failed rep due to out of order replication from a spawned actor + if (!Grip->ValueCache.bWasInitiallyRepped && !HasGripAuthority(*Grip) && !HandleGripReplication(*Grip)) + continue; // If we didn't successfully handle the replication (out of order) then continue on. + + if (Grip->IsValid()) + { + // Continue if the grip is paused + if (Grip->bIsPaused) + continue; + + if (Grip->GripCollisionType == EGripCollisionType::EventsOnly) + continue; // Earliest safe spot to continue at, we needed to check if the object is pending kill or invalid first + + UPrimitiveComponent *root = NULL; + AActor *actor = NULL; + + // Getting the correct variables depending on the grip target type + switch (Grip->GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + actor = Grip->GetGrippedActor(); + if(actor) + root = Cast<UPrimitiveComponent>(actor->GetRootComponent()); + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip : + { + root = Grip->GetGrippedComponent(); + if(root) + actor = root->GetOwner(); + }break; + + default:break; + } + + // Last check to make sure the variables are valid + if (!root || !actor || !IsValid(root) || !IsValid(actor)) + continue; + + // Keep checking for pending kill on gripped objects, and ptr removals, but don't run grip logic when seamless + // traveling, to avoid physx scene issues. + if (GetWorld()->IsInSeamlessTravel()) + { + continue; + } + + // Check if either implements the interface + bool bRootHasInterface = false; + bool bActorHasInterface = false; + + if (root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + bRootHasInterface = true; + } + else if (actor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + // Actor grip interface is checked after component + bActorHasInterface = true; + } + + if (Grip->GripCollisionType == EGripCollisionType::CustomGrip) + { + // Don't perform logic on the movement for this object, just pass in the GripTick() event with the controller difference instead + if(bRootHasInterface) + IVRGripInterface::Execute_TickGrip(root, this, *Grip, DeltaTime); + else if(bActorHasInterface) + IVRGripInterface::Execute_TickGrip(actor, this, *Grip, DeltaTime); + + continue; + } + + bool bRescalePhysicsGrips = false; + + TArray<UVRGripScriptBase*> GripScripts; + + if (bRootHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(root, GripScripts); + } + else if (bActorHasInterface) + { + IVRGripInterface::Execute_GetGripScripts(actor, GripScripts); + } + + + bool bForceADrop = false; + + // Get the world transform for this grip after handling secondary grips and interaction differences + bool bHasValidWorldTransform = GetGripWorldTransform(GripScripts, DeltaTime, WorldTransform, ParentTransform, *Grip, actor, root, bRootHasInterface, bActorHasInterface, false, bForceADrop); + + // If a script or behavior is telling us to skip this and continue on (IE: it dropped the grip) + if (bForceADrop) + { + if (HasGripAuthority(*Grip)) + { + if (bRootHasInterface) + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(root)); + else if (bActorHasInterface) + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(actor)); + else + DropGrip_Implementation(*Grip, true); + } + + continue; + } + else if (!bHasValidWorldTransform) + { + continue; + } + + if (Grip->GrippedBoneName == NAME_None && !root->GetComponentTransform().GetScale3D().Equals(WorldTransform.GetScale3D())) + bRescalePhysicsGrips = true; + + // If we just teleported, skip this update and just teleport forward + if (bIsPostTeleport) + { + + bool bSkipTeleport = false; + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->Wants_DenyTeleport(this)) + { + bSkipTeleport = true; + break; + } + } + + + if (!bSkipTeleport) + { + TeleportMoveGrip_Impl(*Grip, true, true, WorldTransform); + continue; + } + } + else + { + //Grip->LastWorldTransform = WorldTransform; + } + + // Auto drop based on distance from expected point + // Not perfect, should be done post physics or in next frame prior to changing controller location + // However I don't want to recalculate world transform + // Maybe add a grip variable of "expected loc" and use that to check next frame, but for now this will do. + if ((bRootHasInterface || bActorHasInterface) && + ( + (Grip->GripCollisionType != EGripCollisionType::AttachmentGrip) && + (Grip->GripCollisionType != EGripCollisionType::PhysicsOnly) && + (Grip->GripCollisionType != EGripCollisionType::SweepWithPhysics)) && + ((Grip->GripCollisionType != EGripCollisionType::InteractiveHybridCollisionWithSweep) || ((Grip->GripCollisionType == EGripCollisionType::InteractiveHybridCollisionWithSweep) && Grip->bColliding)) + ) + { + + // After initial teleportation the constraint local pose can be not updated yet, so lets delay a frame to let it update + // Otherwise may cause unintended auto drops + if (Grip->bSkipNextConstraintLengthCheck) + { + Grip->bSkipNextConstraintLengthCheck = false; + } + else + { + float BreakDistance = 0.0f; + if (bRootHasInterface) + { + BreakDistance = IVRGripInterface::Execute_GripBreakDistance(root); + } + else if (bActorHasInterface) + { + // Actor grip interface is checked after component + BreakDistance = IVRGripInterface::Execute_GripBreakDistance(actor); + } + + FVector CheckDistance; + if (!GetPhysicsJointLength(*Grip, root, CheckDistance)) + { + CheckDistance = (WorldTransform.GetLocation() - root->GetComponentLocation()); + } + + // Set grip distance now for people to use + Grip->GripDistance = CheckDistance.Size(); + + if (BreakDistance > 0.0f) + { + if (Grip->GripDistance >= BreakDistance) + { + bool bIgnoreDrop = false; + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->Wants_DenyAutoDrop()) + { + bIgnoreDrop = true; + break; + } + } + + if (bIgnoreDrop) + { + // Script canceled this out + } + else if (OnGripOutOfRange.IsBound()) + { + uint8 GripID = Grip->GripID; + OnGripOutOfRange.Broadcast(*Grip, Grip->GripDistance); + + // Check if we still have the grip or not + FBPActorGripInformation GripInfo; + EBPVRResultSwitch Result; + GetGripByID(GripInfo, GripID, Result); + if (Result == EBPVRResultSwitch::OnFailed) + { + // Don't bother moving it, it is dropped now + continue; + } + } + else if(HasGripAuthority(*Grip)) + { + if(bRootHasInterface) + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(root)); + else + DropGrip_Implementation(*Grip, IVRGripInterface::Execute_SimulateOnDrop(actor)); + + // Don't bother moving it, it is dropped now + continue; + } + } + } + } + } + + // Start handling the grip types and their functions + switch (Grip->GripCollisionType) + { + case EGripCollisionType::InteractiveCollisionWithPhysics: + case EGripCollisionType::LockedConstraint: + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + + + // Sweep current collision state, only used for client side late update removal + if ( + (bHasAuthority && !this->bDisableLowLatencyUpdate && + ((Grip->GripLateUpdateSetting == EGripLateUpdateSettings::NotWhenColliding) || + (Grip->GripLateUpdateSetting == EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping))) + ) + { + //TArray<FOverlapResult> Hits; + FComponentQueryParams Params(NAME_None, this->GetOwner()); + //Params.bTraceAsyncScene = root->bCheckAsyncSceneOnMove; + Params.AddIgnoredActor(actor); + Params.AddIgnoredActors(root->MoveIgnoreActors); + + actor->ForEachAttachedActors([&Params](AActor* Actor) + { + Params.AddIgnoredActor(Actor); + return true; + }); + + TArray<FHitResult> Hits; + + // Switched over to component sweep because it picks up on pivot offsets without me manually calculating it + if ( + GetWorld()->ComponentSweepMulti(Hits, root, root->GetComponentLocation(), WorldTransform.GetLocation(), WorldTransform.GetRotation(), Params) + ) + { + + // Check if the two components are ignoring collisions with each other + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem->HasCollisionIgnorePairs()) + { + // Pre-set this so it falls back to false if none of these hits are valid + Grip->bColliding = false; + + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + Grip->bColliding = true; + break; + } + } + } + else + { + Grip->bColliding = true; + } + } + else + { + Grip->bColliding = false; + } + } + + }break; + + case EGripCollisionType::InteractiveCollisionWithSweep: + { + FVector OriginalPosition(root->GetComponentLocation()); + FVector NewPosition(WorldTransform.GetTranslation()); + + if (!Grip->bIsLocked) + root->ComponentVelocity = (NewPosition - OriginalPosition) / DeltaTime; + + if (Grip->bIsLocked) + WorldTransform.SetRotation(Grip->LastLockedRotation); + + FHitResult OutHit; + // Need to use without teleport so that the physics velocity is updated for when the actor is released to throw + if (bProjectNonSimulatingGrips && !Grip->bIsLocked && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + root->SetWorldTransform(WorldTransform, true, &OutHit); + } + else + { + root->SetWorldTransform(WorldTransform, true, &OutHit); + } + + if (OutHit.bBlockingHit) + { + Grip->bColliding = true; + + if (!Grip->bIsLocked) + { + Grip->bIsLocked = true; + Grip->LastLockedRotation = root->GetComponentQuat(); + } + } + else + { + Grip->bColliding = false; + + if (Grip->bIsLocked) + Grip->bIsLocked = false; + } + }break; + + case EGripCollisionType::InteractiveHybridCollisionWithPhysics: + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + + // Always Sweep current collision state with this, used for constraint strength + //TArray<FOverlapResult> Hits; + FComponentQueryParams Params(NAME_None, this->GetOwner()); + //Params.bTraceAsyncScene = root->bCheckAsyncSceneOnMove; + Params.AddIgnoredActor(actor); + Params.AddIgnoredActors(root->MoveIgnoreActors); + + actor->ForEachAttachedActors([&Params](AActor* Actor) + { + Params.AddIgnoredActor(Actor); + return true; + }); + + TArray<FHitResult> Hits; + // Checking both current and next position for overlap using this grip type + // Switched over to component sweep because it picks up on pivot offsets without me manually calculating it + if (Grip->bLockHybridGrip) + { + if (!Grip->bColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, false); + } + + Grip->bColliding = true; + } + else if (GetWorld()->ComponentSweepMulti(Hits, root, root->GetComponentLocation(), WorldTransform.GetLocation(), WorldTransform.GetRotation(), Params)) + { + // Check if the two components are ignoring collisions with each other + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem->HasCollisionIgnorePairs()) + { + + bool bOriginalColliding = Grip->bColliding; + // Pre-set this so it falls back to false if none of these hits are valid + Grip->bColliding = false; + + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + if (!bOriginalColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, false); + } + Grip->bColliding = true; + break; + } + } + + if (!Grip->bColliding) + { + if (bOriginalColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, true); + } + } + + + } + else + { + if (!Grip->bColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, false); + } + Grip->bColliding = true; + } + } + else + { + if (Grip->bColliding) + { + SetGripConstraintStiffnessAndDamping(Grip, true); + } + + Grip->bColliding = false; + } + + }break; + + case EGripCollisionType::InteractiveHybridCollisionWithSweep: + { + + // Make sure that there is no collision on course before turning off collision and snapping to controller + FBPActorPhysicsHandleInformation * GripHandle = GetPhysicsGrip(*Grip); + + TArray<FHitResult> Hits; + FComponentQueryParams Params(NAME_None, this->GetOwner()); + //Params.bTraceAsyncScene = root->bCheckAsyncSceneOnMove; + Params.AddIgnoredActor(actor); + Params.AddIgnoredActors(root->MoveIgnoreActors); + + actor->ForEachAttachedActors([&Params](AActor* Actor) + { + Params.AddIgnoredActor(Actor); + return true; + }); + + FTransform BaseTransform = root->GetComponentTransform(); + + if (bProjectNonSimulatingGrips && !Grip->bColliding && Grip->bSetLastWorldTransform) + { + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + BaseTransform = Grip->LastWorldTransform * baseTrans; + } + + if (Grip->bLockHybridGrip) + { + Grip->bColliding = true; + } + else if (GetWorld()->ComponentSweepMulti(Hits, root, BaseTransform.GetLocation(), WorldTransform.GetLocation(), WorldTransform.GetRotation(), Params)) + { + // Check if the two components are ignoring collisions with each other + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem->HasCollisionIgnorePairs()) + { + // Pre-set this so it falls back to false if none of these hits are valid + Grip->bColliding = false; + + for (const FHitResult& Hit : Hits) + { + if (Hit.bBlockingHit && !CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(root, Hit.Component.Get())) + { + Grip->bColliding = true; + break; + } + } + } + else + { + Grip->bColliding = true; + } + } + else + { + Grip->bColliding = false; + } + + if (!Grip->bColliding) + { + if (GripHandle && !GripHandle->bIsPaused) + { + PausePhysicsHandle(GripHandle); + //DestroyPhysicsHandle(*Grip); + + switch (Grip->GripTargetType) + { + case EGripTargetType::ComponentGrip: + { + root->SetSimulatePhysics(false); + }break; + case EGripTargetType::ActorGrip: + { + actor->DisableComponentsSimulatePhysics(); + } break; + } + } + + if (bProjectNonSimulatingGrips && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + root->SetWorldTransform(WorldTransform, false);// , &OutHit); + } + else + { + root->SetWorldTransform(WorldTransform, false);// , &OutHit); + } + + if (GripHandle) + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + } + + } + else if (Grip->bColliding) + { + if (!GripHandle) + { + root->SetSimulatePhysics(true); + SetUpPhysicsHandle(*Grip, &GripScripts); + } + else if (GripHandle->bIsPaused) + { + UnPausePhysicsHandle(*Grip, GripHandle); + } + + if (GripHandle) + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + } + } + else + { + // Shouldn't be a grip handle if not server when server side moving + if (GripHandle) + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + } + } + + }break; + + case EGripCollisionType::SweepWithPhysics: + { + // Ensure physics simulation is off in case something sneaked it on + if (root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + } + + FVector OriginalPosition(root->GetComponentLocation()); + FRotator OriginalOrientation(root->GetComponentRotation()); + + FVector NewPosition(WorldTransform.GetTranslation()); + FRotator NewOrientation(WorldTransform.GetRotation()); + + root->ComponentVelocity = (NewPosition - OriginalPosition) / DeltaTime; + + // Now sweep collision separately so we can get hits but not have the location altered + if (bUseWithoutTracking || NewPosition != OriginalPosition || NewOrientation != OriginalOrientation) + { + FVector move = NewPosition - OriginalPosition; + + // ComponentSweepMulti does nothing if moving < KINDA_SMALL_NUMBER in distance, so it's important to not try to sweep distances smaller than that. + const float MinMovementDistSq = (FMath::Square(4.f*KINDA_SMALL_NUMBER)); + + if (bUseWithoutTracking || move.SizeSquared() > MinMovementDistSq || NewOrientation != OriginalOrientation) + { + if (CheckComponentWithSweep(root, move, OriginalOrientation, false)) + { + Grip->bColliding = true; + } + else + { + Grip->bColliding = false; + } + + TArray<USceneComponent* > PrimChildren; + root->GetChildrenComponents(true, PrimChildren); + for (USceneComponent * Prim : PrimChildren) + { + if (UPrimitiveComponent * primComp = Cast<UPrimitiveComponent>(Prim)) + { + CheckComponentWithSweep(primComp, move, primComp->GetComponentRotation(), false); + } + } + } + } + + if (bProjectNonSimulatingGrips && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + else + { + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + + }break; + + case EGripCollisionType::PhysicsOnly: + { + // Ensure physics simulation is off in case something sneaked it on + if (root->IsSimulatingPhysics()) + { + root->SetSimulatePhysics(false); + } + + if (bProjectNonSimulatingGrips && Grip->bSetLastWorldTransform) + { + FScopedMovementUpdate ScopedMovementUpdate(root, EScopedUpdate::DeferredUpdates); + FTransform baseTrans = this->GetAttachParent()->GetComponentTransform(); + root->SetWorldTransform(Grip->LastWorldTransform * baseTrans, false, nullptr, ETeleportType::None); + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + else + { + // Move the actor, we are not offsetting by the hit result anyway + root->SetWorldTransform(WorldTransform, false); + } + }break; + + case EGripCollisionType::AttachmentGrip: + { + FTransform RelativeTrans = WorldTransform.GetRelativeTransform(ParentTransform); + + if (!root->GetAttachParent() || root->IsSimulatingPhysics()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Attachment Grip was missing attach parent - Attempting to Re-attach")); + + if (HasGripMovementAuthority(*Grip) || IsServer()) + { + root->SetSimulatePhysics(false); + if (root->AttachToComponent(IsValid(CustomPivotComponent) ? CustomPivotComponent.Get() : this, FAttachmentTransformRules::KeepWorldTransform)) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Re-attached")); + if (!root->GetRelativeTransform().Equals(RelativeTrans)) + { + root->SetRelativeTransform(RelativeTrans); + } + } + } + } + else + { + if (!root->GetRelativeTransform().Equals(RelativeTrans)) + { + root->SetRelativeTransform(RelativeTrans); + } + } + + }break; + + case EGripCollisionType::ManipulationGrip: + case EGripCollisionType::ManipulationGripWithWristTwist: + { + UpdatePhysicsHandleTransform(*Grip, WorldTransform); + if (bRescalePhysicsGrips) + root->SetWorldScale3D(WorldTransform.GetScale3D()); + + }break; + + default: + {}break; + } + + // We only do this if specifically requested, it has a slight perf hit and isn't normally needed for non Custom Grip types + if (bAlwaysSendTickGrip) + { + // All non custom grips tick after translation, this is still pre physics so interactive grips location will be wrong, but others will be correct + if (bRootHasInterface) + { + IVRGripInterface::Execute_TickGrip(root, this, *Grip, DeltaTime); + } + + if (bActorHasInterface) + { + IVRGripInterface::Execute_TickGrip(actor, this, *Grip, DeltaTime); + } + } + } + else + { + // Object has been destroyed without notification to plugin or is pending kill + if (!Grip->bIsPendingKill) + { + CleanUpBadGrip(GrippedObjectsArray, i, bReplicatedArray); + } + } + } + } +} + + +void UGripMotionControllerComponent::CleanUpBadGrip(TArray<FBPActorGripInformation> &GrippedObjectsArray, int GripIndex, bool bReplicatedArray) +{ + // Object has been destroyed without notification to plugin + if (!DestroyPhysicsHandle(GrippedObjectsArray[GripIndex])) + { + // Clean up tailing physics handles with null objects + for (int g = PhysicsGrips.Num() - 1; g >= 0; --g) + { + if (!PhysicsGrips[g].HandledObject || PhysicsGrips[g].HandledObject == GrippedObjectsArray[GripIndex].GrippedObject || !IsValid(PhysicsGrips[g].HandledObject)) + { + // Need to delete it from the physics thread + DestroyPhysicsHandle(&PhysicsGrips[g]); + PhysicsGrips.RemoveAt(g); + } + } + } + + if (IsServer() || HasGripAuthority(GrippedObjectsArray[GripIndex])) + { + DropGrip_Implementation(GrippedObjectsArray[GripIndex], false); + UE_LOG(LogVRMotionController, Warning, TEXT("Gripped object was null or destroying, auto dropping it")); + } + else + { + GrippedObjectsArray[GripIndex].bIsPendingKill = true; + GrippedObjectsArray[GripIndex].bIsPaused = true; + } +} + +void UGripMotionControllerComponent::CleanUpBadPhysicsHandles() +{ + // Clean up tailing physics handles with null objects + for (int g = PhysicsGrips.Num() - 1; g >= 0; --g) + { + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(PhysicsGrips[g].GripID); + if(!GripInfo) + GrippedObjects.FindByKey(PhysicsGrips[g].GripID); + + if (!GripInfo) + { + // Need to delete it from the physics thread + DestroyPhysicsHandle(&PhysicsGrips[g]); + PhysicsGrips.RemoveAt(g); + } + } +} + +bool UGripMotionControllerComponent::UpdatePhysicsHandle(uint8 GripID, bool bFullyRecreate) +{ + FBPActorGripInformation* GripInfo = GrippedObjects.FindByKey(GripID); + if (!GripInfo) + GripInfo = LocallyGrippedObjects.FindByKey(GripID); + + if (!GripInfo) + return false; + + return UpdatePhysicsHandle(*GripInfo, bFullyRecreate); +} + +bool UGripMotionControllerComponent::UpdatePhysicsHandle(const FBPActorGripInformation& GripInfo, bool bFullyRecreate) +{ + int HandleIndex = 0; + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(GripInfo); + + // Don't update if the handle doesn't exist or is currently paused + if (!HandleInfo || HandleInfo->bIsPaused || !HandleInfo->bInitiallySetup) + return false; + + if (bFullyRecreate || !HandleInfo->HandleData2.IsValid() || HandleInfo->bSkipResettingCom) + { + return SetUpPhysicsHandle(GripInfo); + } + + // Not fully recreating, we just re-set important variables + UPrimitiveComponent* root = GripInfo.GetGrippedComponent(); + AActor* pActor = GripInfo.GetGrippedActor(); + + if (!root && pActor) + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (!root) + return false; + + FBodyInstance* rBodyInstance = root->GetBodyInstance(GripInfo.GrippedBoneName); + if (!rBodyInstance || !rBodyInstance->IsValidBodyInstance() || !FPhysicsInterface::IsValid(rBodyInstance->ActorHandle)) + { + return false; + } + + check(rBodyInstance->BodySetup->GetCollisionTraceFlag() != CTF_UseComplexAsSimple); + + FPhysicsCommand::ExecuteWrite(rBodyInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor) + { +#if WITH_CHAOS + if (HandleInfo) + { + if (HandleInfo->KinActorData2 && FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + { + // Make sure that the constraint particles are correct, the body instance may have changed + auto* JointConstraint = HandleInfo->HandleData2.Constraint; + if (JointConstraint) + { + JointConstraint->SetParticleProxies({ HandleInfo->KinActorData2,Actor }); + } + + // Ensure center of mass is still correct + if (HandleInfo->bSetCOM && !HandleInfo->bSkipResettingCom) + { + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); + //localCom.SetLocation(Loc); + localCom.SetLocation(HandleInfo->COMPosition.GetTranslation());//Loc); + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + } + } + } +#endif + }); + + return true; +//#endif + + return false; +} + +bool UGripMotionControllerComponent::PausePhysicsHandle(FBPActorPhysicsHandleInformation* HandleInfo) +{ + if (!HandleInfo) + return false; + + HandleInfo->bIsPaused = true; + HandleInfo->bInitiallySetup = false; + FPhysicsInterface::ReleaseConstraint(HandleInfo->HandleData2); + return true; +} + +bool UGripMotionControllerComponent::UnPausePhysicsHandle(FBPActorGripInformation& GripInfo, FBPActorPhysicsHandleInformation* HandleInfo) +{ + if (!HandleInfo) + return false; + + HandleInfo->bIsPaused = false; + SetUpPhysicsHandle(GripInfo); + + return true; +} + +bool UGripMotionControllerComponent::DestroyPhysicsHandle(FBPActorPhysicsHandleInformation* HandleInfo) +{ + if (!HandleInfo) + return false; + + FPhysicsInterface::ReleaseConstraint(HandleInfo->HandleData2); + + if (!HandleInfo->bSkipDeletingKinematicActor) + { + FPhysicsInterface::ReleaseActor(HandleInfo->KinActorData2, FPhysicsInterface::GetCurrentScene(HandleInfo->KinActorData2)); + } + + return true; +} + +bool UGripMotionControllerComponent::DestroyPhysicsHandle(const FBPActorGripInformation &Grip, bool bSkipUnregistering) +{ + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip); + + if (!HandleInfo) + { + return true; + } + + UPrimitiveComponent *root = Grip.GetGrippedComponent(); + AActor * pActor = Grip.GetGrippedActor(); + + if (!root && pActor) + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (root) + { + if (FBodyInstance * rBodyInstance = root->GetBodyInstance(Grip.GrippedBoneName)) + { + // #TODO: Should this be done on drop instead? + // Remove event registration + if (!bSkipUnregistering) + { + if (rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().RemoveAll(this); + } + } + + if (HandleInfo->bSetCOM) + { + // Reset center of mass to zero + // Get our original values + FVector vel = rBodyInstance->GetUnrealWorldVelocity(); + FVector aVel = rBodyInstance->GetUnrealWorldAngularVelocityInRadians(); + FVector originalCOM = rBodyInstance->GetCOMPosition(); + + if (rBodyInstance->IsValidBodyInstance() && rBodyInstance->BodySetup.IsValid()) + { + rBodyInstance->UpdateMassProperties(); + } + + if (rBodyInstance->IsInstanceSimulatingPhysics()) + { + // Offset the linear velocity by the new COM position and set it + vel += FVector::CrossProduct(aVel, rBodyInstance->GetCOMPosition() - originalCOM); + rBodyInstance->SetLinearVelocity(vel, false); + } + } + } + } + + DestroyPhysicsHandle(HandleInfo); + + int index; + if (GetPhysicsGripIndex(Grip, index)) + PhysicsGrips.RemoveAt(index); + + return true; +} + +void UGripMotionControllerComponent::OnGripMassUpdated(FBodyInstance* GripBodyInstance) +{ + TArray<FBPActorGripInformation> GripArray; + this->GetAllGrips(GripArray); + FBPActorGripInformation NewGrip; + + for (int i = 0; i < GripArray.Num(); i++) + { + NewGrip = GripArray[i]; + + UPrimitiveComponent *root = NewGrip.GetGrippedComponent(); + AActor * pActor = NewGrip.GetGrippedActor(); + + if (!root && pActor) + { + if (!IsValid(pActor)) + continue; + + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + } + + if (!root || root != GripBodyInstance->OwnerComponent) + continue; + + if (IsValid(root)) + { + UpdatePhysicsHandle(NewGrip, false); + } + break; + } +} + +bool UGripMotionControllerComponent::SetUpPhysicsHandle(const FBPActorGripInformation &NewGrip, TArray<UVRGripScriptBase*> * GripScripts) +{ + UPrimitiveComponent *root = NewGrip.GetGrippedComponent(); + AActor * pActor = NewGrip.GetGrippedActor(); + + if(!root && pActor) + root = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + + if (!root) + return false; + + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(NewGrip); + if (HandleInfo == nullptr) + { + HandleInfo = CreatePhysicsGrip(NewGrip); + } + + // If currently paused lets skip this step + if (HandleInfo->bIsPaused) + { + return false; + } + + HandleInfo->bSetCOM = false; // Zero this out in case it is a re-init + HandleInfo->bSkipDeletingKinematicActor = (bConstrainToPivot && !NewGrip.bIsLerping); + + // Check for grip scripts if we weren't passed in any + TArray<UVRGripScriptBase*> LocalGripScripts; + if (GripScripts == nullptr) + { + if (root && root->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if (IVRGripInterface::Execute_GetGripScripts(root, LocalGripScripts)) + { + GripScripts = &LocalGripScripts; + } + } + else if (pActor && pActor->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + if (IVRGripInterface::Execute_GetGripScripts(pActor, LocalGripScripts)) + { + GripScripts = &LocalGripScripts; + } + } + } + + // Needs to be simulating in order to run physics + if (!NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || !NewGrip.AdvancedGripSettings.PhysicsSettings.bSkipSettingSimulating) + root->SetSimulatePhysics(true); + + // Get the PxRigidDynamic that we want to grab. + FBodyInstance* rBodyInstance = root->GetBodyInstance(NewGrip.GrippedBoneName); + if (!rBodyInstance || !rBodyInstance->IsValidBodyInstance() || !FPhysicsInterface::IsValid(rBodyInstance->ActorHandle) || !rBodyInstance->BodySetup.IsValid()) + { + return false; + } + + check(rBodyInstance->BodySetup->GetCollisionTraceFlag() != CTF_UseComplexAsSimple); + + if (!HandleInfo->bSkipResettingCom && !FPhysicsInterface::IsValid(HandleInfo->KinActorData2) && !rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + // Reset the mass properties, this avoids an issue with some weird replication issues + // We only do this on initial grip + rBodyInstance->UpdateMassProperties(); + + } + + /*if (NewGrip.GrippedBoneName != NAME_None) + { + rBodyInstance->SetInstanceSimulatePhysics(true); + }*/ + + FPhysicsCommand::ExecuteWrite(rBodyInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor) + { + + FTransform KinPose; + FTransform trans = FPhysicsInterface::GetGlobalPose_AssumesLocked(Actor); + FTransform RootBoneRotation = FTransform::Identity; + + if (NewGrip.GrippedBoneName != NAME_None) + { + // Skip root bone rotation + } + else + { + // I actually don't need any of this code anymore or the HandleInfo->RootBoneRotation + // However I would have to expect people to pass in the bone transform without it. + // For now I am keeping it to keep it backwards compatible as it will adjust for root bone rotation automatically then + if (USkeletalMeshComponent * skele = Cast<USkeletalMeshComponent>(root)) + { + int32 RootBodyIndex = INDEX_NONE; + if (const UPhysicsAsset* PhysicsAsset = skele->GetPhysicsAsset()) + { + for (int32 i = 0; i < skele->GetNumBones(); i++) + { + if (PhysicsAsset->FindBodyIndex(skele->GetBoneName(i)) != INDEX_NONE) + { + RootBodyIndex = i; + break; + } + } + } + + if (RootBodyIndex != INDEX_NONE) + { + RootBoneRotation = FTransform(skele->GetBoneTransform(RootBodyIndex, FTransform::Identity)); + RootBoneRotation.SetScale3D(FVector(1.f)); + RootBoneRotation.NormalizeRotation(); + HandleInfo->RootBoneRotation = RootBoneRotation; + } + } + } + + EPhysicsGripCOMType COMType = NewGrip.AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings; + + if (!NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings || COMType == EPhysicsGripCOMType::COM_Default) + { + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGrip || NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + COMType = EPhysicsGripCOMType::COM_GripAtControllerLoc; + } + else + { + COMType = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + if (COMType == EPhysicsGripCOMType::COM_SetAndGripAt) + { + // Update the center of mass + FTransform ForwardTrans = (RootBoneRotation * NewGrip.RelativeTransform); + ForwardTrans.NormalizeRotation(); + FVector Loc = (FTransform(ForwardTrans.ToInverseMatrixWithScale())).GetLocation(); + Loc *= rBodyInstance->Scale3D; + + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); + localCom.SetLocation(Loc); + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + + FVector ComLoc = FPhysicsInterface::GetComTransform_AssumesLocked(Actor).GetLocation(); + trans.SetLocation(ComLoc); + HandleInfo->COMPosition = FTransform(rBodyInstance->GetUnrealWorldTransform().InverseTransformPosition(ComLoc)); + HandleInfo->bSetCOM = true; + } + else if (COMType == EPhysicsGripCOMType::COM_GripAtControllerLoc) + { + FVector ControllerLoc = (FTransform(NewGrip.RelativeTransform.ToInverseMatrixWithScale()) * root->GetComponentTransform()).GetLocation(); + trans.SetLocation(ControllerLoc); + HandleInfo->COMPosition = FTransform(rBodyInstance->GetUnrealWorldTransform().InverseTransformPosition(ControllerLoc)); + } + else if (COMType != EPhysicsGripCOMType::COM_AtPivot) + { + FVector ComLoc = FPhysicsInterface::GetComTransform_AssumesLocked(Actor).GetLocation(); + trans.SetLocation(ComLoc); + HandleInfo->COMPosition = FTransform(rBodyInstance->GetUnrealWorldTransform().InverseTransformPosition(ComLoc)); + } + + KinPose = trans; + bool bRecreatingConstraint = false; + + + // If using twist only, lets rotate the kinematic actor to face the controller X+ + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + FTransform PivTrans = GetPivotTransform(); + + FQuat DeltaQuat = (PivTrans.GetRotation().Inverse() * KinPose.GetRotation()).Inverse(); + + // This moves the kinematic actor to face the pivot components X+ direction + KinPose.SetRotation(KinPose.GetRotation() * DeltaQuat); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation()* DeltaQuat); + } + + if (GripScripts) + { + bool bResetCom = false; + + // Inject any alterations that the grip scripts want to make + for (UVRGripScriptBase* Script : *GripScripts) + { + if (Script && Script->IsScriptActive() && Script->InjectPrePhysicsHandle()) + { + Script->HandlePrePhysicsHandle(this, NewGrip, HandleInfo, KinPose); + bResetCom = true; + } + } + + if (HandleInfo->bSetCOM && bResetCom) + { + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); + localCom.SetLocation(HandleInfo->COMPosition.GetTranslation());//Loc); + + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + } + } + + if (!NewGrip.bIsLerping && bConstrainToPivot && IsValid(CustomPivotComponent)) + { + if (UPrimitiveComponent* PivotPrim = Cast<UPrimitiveComponent>(CustomPivotComponent)) + { + if (FBodyInstance* Bodyinst = PivotPrim->GetBodyInstance(CustomPivotComponentSocketName)) + { + HandleInfo->KinActorData2 = Bodyinst->GetPhysicsActorHandle(); + } + } + } + + if (!FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + { + // Create kinematic actor we are going to create joint with. This will be moved around with calls to SetLocation/SetRotation. + + FActorCreationParams ActorParams; + ActorParams.InitialTM = KinPose; + ActorParams.DebugName = nullptr; + ActorParams.bEnableGravity = false; + ActorParams.bQueryOnly = false;// true; // True or false? + ActorParams.bStatic = false; + ActorParams.Scene = FPhysicsInterface::GetCurrentScene(Actor); + FPhysicsInterface::CreateActor(ActorParams, HandleInfo->KinActorData2); + + if (FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + { + FPhysicsInterface::SetMass_AssumesLocked(HandleInfo->KinActorData2, 1.0f); + FPhysicsInterface::SetMassSpaceInertiaTensor_AssumesLocked(HandleInfo->KinActorData2, FVector(1.f)); + FPhysicsInterface::SetIsKinematic_AssumesLocked(HandleInfo->KinActorData2, true); + FPhysicsInterface::SetMaxDepenetrationVelocity_AssumesLocked(HandleInfo->KinActorData2, MAX_FLT); + //FPhysicsInterface::SetActorUserData_AssumesLocked(HandleInfo->KinActorData2, NULL); + } + +#if WITH_CHAOS + using namespace Chaos; + // Missing from physx, not sure how it is working for them currently. + //TArray<FPhysicsActorHandle> ActorHandles; + HandleInfo->KinActorData2->GetGameThreadAPI().SetGeometry(TUniquePtr<FImplicitObject>(new TSphere<FReal, 3>(TVector<FReal, 3>(0.f), 1000.f))); + HandleInfo->KinActorData2->GetGameThreadAPI().SetObjectState(EObjectStateType::Kinematic); + FPhysicsInterface::AddActorToSolver(HandleInfo->KinActorData2, ActorParams.Scene->GetSolver()); + //ActorHandles.Add(HandleInfo->KinActorData2); + //ActorParams.Scene->AddActorsToScene_AssumesLocked(ActorHandles); +#endif + } + + // If we don't already have a handle - make one now. + if (!HandleInfo->HandleData2.IsValid()) + { + // If this is true we will totally ignore the COM type, either we are gripping unstable or the com was set to our controller, we never accept + // Grip at COM + if (!NewGrip.bIsLerping && bConstrainToPivot) + { + FTransform TargetTrans(FTransform(NewGrip.RelativeTransform.ToMatrixNoScale().Inverse()) * HandleInfo->RootBoneRotation.Inverse()); + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2, Actor, FTransform::Identity, TargetTrans); + } + else + { + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2, Actor, FTransform::Identity, KinPose.GetRelativeTransform(FPhysicsInterface::GetGlobalPose_AssumesLocked(Actor))); + } + } + else + { + bRecreatingConstraint = true; + + /* + FTransform newTrans = HandleInfo->COMPosition * (HandleInfo->RootBoneRotation * HandleInfo->LastPhysicsTransform); + */ + +#if WITH_CHAOS + + // There isn't a direct set for the particles, so keep it as a recreation instead. + FPhysicsInterface::ReleaseConstraint(HandleInfo->HandleData2); + + if (!NewGrip.bIsLerping && bConstrainToPivot) + { + FTransform TargetTrans(NewGrip.RelativeTransform.ToMatrixNoScale().Inverse()); + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2, Actor, FTransform::Identity, TargetTrans); + } + else + { + HandleInfo->HandleData2 = FPhysicsInterface::CreateConstraint(HandleInfo->KinActorData2, Actor, FTransform::Identity, KinPose.GetRelativeTransform(FPhysicsInterface::GetGlobalPose_AssumesLocked(Actor))); + } +#endif + + } + + if (HandleInfo->HandleData2.IsValid()) + { + FPhysicsInterface::SetBreakForces_AssumesLocked(HandleInfo->HandleData2, MAX_FLT, MAX_FLT); + + + if (NewGrip.GripCollisionType == EGripCollisionType::LockedConstraint) + { + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::X, ELinearConstraintMotion::LCM_Locked); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Y, ELinearConstraintMotion::LCM_Locked); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Z, ELinearConstraintMotion::LCM_Locked); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Twist, EAngularConstraintMotion::ACM_Locked); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing1, EAngularConstraintMotion::ACM_Locked); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing2, EAngularConstraintMotion::ACM_Locked); + FPhysicsInterface::SetProjectionEnabled_AssumesLocked(HandleInfo->HandleData2, true, 0.01f, 0.01f); + } + else + { + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::X, ELinearConstraintMotion::LCM_Free); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Y, ELinearConstraintMotion::LCM_Free); + FPhysicsInterface::SetLinearMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Z, ELinearConstraintMotion::LCM_Free); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Twist, EAngularConstraintMotion::ACM_Free); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing1, EAngularConstraintMotion::ACM_Free); + FPhysicsInterface::SetAngularMotionLimitType_AssumesLocked(HandleInfo->HandleData2, PhysicsInterfaceTypes::ELimitAxis::Swing2, EAngularConstraintMotion::ACM_Free); + FPhysicsInterface::SetProjectionEnabled_AssumesLocked(HandleInfo->HandleData2, false); + } + + FPhysicsInterface::SetDrivePosition(HandleInfo->HandleData2, FVector::ZeroVector); + + bool bUseForceDrive = (NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.PhysicsConstraintType == EPhysicsGripConstraintType::ForceConstraint); + + float Stiffness = NewGrip.Stiffness; + float Damping = NewGrip.Damping; + float MaxForce; + float AngularStiffness; + float AngularDamping; + float AngularMaxForce; + + if (NewGrip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && NewGrip.AdvancedGripSettings.PhysicsSettings.bUseCustomAngularValues) + { + AngularStiffness = NewGrip.AdvancedGripSettings.PhysicsSettings.AngularStiffness; + AngularDamping = NewGrip.AdvancedGripSettings.PhysicsSettings.AngularDamping; + } + else + { + AngularStiffness = Stiffness * ANGULAR_STIFFNESS_MULTIPLIER; // Default multiplier + AngularDamping = Damping * ANGULAR_DAMPING_MULTIPLIER; // Default multiplier + } + +#if WITH_CHAOS + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + if (VRSettings.bUseChaosTranslationScalers) + { + Stiffness *= VRSettings.LinearDriveStiffnessScale; + Damping *= VRSettings.LinearDriveDampingScale; + AngularStiffness *= VRSettings.AngularDriveStiffnessScale; + AngularDamping *= VRSettings.AngularDriveDampingScale; + } +#endif + + AngularMaxForce = (float)FMath::Clamp<double>((double)AngularStiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp<double>((double)Stiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + + // Different settings for manip grip + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGrip || NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + if (!bRecreatingConstraint) + { + FConstraintDrive NewLinDrive; + NewLinDrive.bEnablePositionDrive = true; + NewLinDrive.bEnableVelocityDrive = true; + NewLinDrive.Damping = Damping; + NewLinDrive.Stiffness = Stiffness; + NewLinDrive.MaxForce = MaxForce; + + HandleInfo->LinConstraint.bEnablePositionDrive = true; + HandleInfo->LinConstraint.XDrive = NewLinDrive; + HandleInfo->LinConstraint.YDrive = NewLinDrive; + HandleInfo->LinConstraint.ZDrive = NewLinDrive; + } + + + if (NewGrip.GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + if (!bRecreatingConstraint) + { + FConstraintDrive NewAngDrive; + NewAngDrive.bEnablePositionDrive = true; + NewAngDrive.bEnableVelocityDrive = true; + NewAngDrive.Damping = AngularDamping; + NewAngDrive.Stiffness = AngularStiffness; + NewAngDrive.MaxForce = AngularMaxForce; + //NewAngDrive.MaxForce = MAX_FLT; + + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::TwistAndSwing; + //AngParams.AngularDriveMode = EAngularDriveMode::SLERP; + HandleInfo->AngConstraint.TwistDrive = NewAngDrive; + } + } + + if (GripScripts) + { + // Inject any alterations that the grip scripts want to make + for (UVRGripScriptBase* Script : *GripScripts) + { + if (Script && Script->IsScriptActive() && Script->InjectPostPhysicsHandle()) + { + Script->HandlePostPhysicsHandle(this, HandleInfo); + } + } + } + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); + } + else + { + if (NewGrip.GripCollisionType == EGripCollisionType::InteractiveHybridCollisionWithPhysics) + { + // Do not effect damping, just increase stiffness so that it is stronger + // Default multiplier + Stiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + AngularStiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + AngularMaxForce = (float)FMath::Clamp<double>((double)AngularStiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp<double>((double)Stiffness * (double)NewGrip.AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + } + + if (!bRecreatingConstraint) + { + if (NewGrip.GripCollisionType != EGripCollisionType::LockedConstraint) + { + FConstraintDrive NewLinDrive; + NewLinDrive.bEnablePositionDrive = true; + NewLinDrive.bEnableVelocityDrive = true; + NewLinDrive.Damping = Damping; + NewLinDrive.Stiffness = Stiffness; + NewLinDrive.MaxForce = MaxForce; + //NewLinDrive.MaxForce = MAX_FLT; + + FConstraintDrive NewAngDrive; + NewAngDrive.bEnablePositionDrive = true; + NewAngDrive.bEnableVelocityDrive = true; + NewAngDrive.Damping = AngularDamping; + NewAngDrive.Stiffness = AngularStiffness; + NewAngDrive.MaxForce = AngularMaxForce; + //NewAngDrive.MaxForce = MAX_FLT; + + HandleInfo->LinConstraint.bEnablePositionDrive = true; + HandleInfo->LinConstraint.XDrive = NewLinDrive; + HandleInfo->LinConstraint.YDrive = NewLinDrive; + HandleInfo->LinConstraint.ZDrive = NewLinDrive; + + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::SLERP; + HandleInfo->AngConstraint.SlerpDrive = NewAngDrive; + } + } + + if (GripScripts) + { + // Inject any alterations that the grip scripts want to make + for (UVRGripScriptBase* Script : *GripScripts) + { + if (Script && Script->IsScriptActive() && Script->InjectPostPhysicsHandle()) + { + Script->HandlePostPhysicsHandle(this, HandleInfo); + } + } + } + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); + } + + + // This is a temp workaround until epic fixes the drive creation to allow force constraints + // I wanted to use the new interface and not directly set the drive so that it is ready to delete this section + // When its fixed +#if WITH_CHAOS + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast<Chaos::FJointConstraint*>(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetLinearDriveForceMode(Chaos::EJointForceMode::Force); + Constraint->SetAngularDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } +#endif + } + }); + + HandleInfo->bInitiallySetup = true; + + // Bind to further updates in order to keep it alive + if (!rBodyInstance->OnRecalculatedMassProperties().IsBoundToObject(this)) + { + rBodyInstance->OnRecalculatedMassProperties().AddUObject(this, &UGripMotionControllerComponent::OnGripMassUpdated); + } + + return true; +} + +bool UGripMotionControllerComponent::SetGripConstraintStiffnessAndDamping(const FBPActorGripInformation* Grip, bool bUseHybridMultiplier) +{ + if (!Grip) + return false; + + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(*Grip); + + if (HandleInfo) + { + if (HandleInfo->HandleData2.IsValid()) + { + FPhysicsInterface::ExecuteOnUnbrokenConstraintReadWrite(HandleInfo->HandleData2, [&](const FPhysicsConstraintHandle& InUnbrokenConstraint) + { + bool bUseForceDrive = (Grip->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && Grip->AdvancedGripSettings.PhysicsSettings.PhysicsConstraintType == EPhysicsGripConstraintType::ForceConstraint); + + float Stiffness = Grip->Stiffness; + float Damping = Grip->Damping; + float MaxForce; + float AngularStiffness; + float AngularDamping; + float AngularMaxForce; + + if (Grip->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && Grip->AdvancedGripSettings.PhysicsSettings.bUseCustomAngularValues) + { + AngularStiffness = Grip->AdvancedGripSettings.PhysicsSettings.AngularStiffness; + AngularDamping = Grip->AdvancedGripSettings.PhysicsSettings.AngularDamping; + } + else + { + AngularStiffness = Stiffness * ANGULAR_STIFFNESS_MULTIPLIER; // Default multiplier + AngularDamping = Damping * ANGULAR_DAMPING_MULTIPLIER; // Default multiplier + } + +#if WITH_CHAOS + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + if (VRSettings.bUseChaosTranslationScalers) + { + Stiffness *= VRSettings.LinearDriveStiffnessScale; + Damping *= VRSettings.LinearDriveDampingScale; + AngularStiffness *= VRSettings.AngularDriveStiffnessScale; + AngularDamping *= VRSettings.AngularDriveDampingScale; + } +#endif + + AngularMaxForce = (float)FMath::Clamp<double>((double)AngularStiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp<double>((double)Stiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + + + // Different settings for manip grip + if (Grip->GripCollisionType == EGripCollisionType::ManipulationGrip || Grip->GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + HandleInfo->LinConstraint.XDrive.Damping = Damping; + HandleInfo->LinConstraint.XDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.XDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.YDrive.Damping = Damping; + HandleInfo->LinConstraint.YDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.YDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.ZDrive.Damping = Damping; + HandleInfo->LinConstraint.ZDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.ZDrive.MaxForce = MaxForce; + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); +#if WITH_CHAOS + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast<Chaos::FJointConstraint*>(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetLinearDriveForceMode(Chaos::EJointForceMode::Force); + Constraint->SetAngularDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } +#endif + + if (Grip->GripCollisionType == EGripCollisionType::ManipulationGripWithWristTwist) + { + HandleInfo->AngConstraint.TwistDrive.Damping = AngularDamping; + HandleInfo->AngConstraint.TwistDrive.Stiffness = AngularStiffness; + HandleInfo->AngConstraint.TwistDrive.MaxForce = AngularMaxForce; + + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); + } + + FPhysicsInterface::SetDrivePosition(HandleInfo->HandleData2, FVector::ZeroVector); + FPhysicsInterface::SetDriveOrientation(HandleInfo->HandleData2, FQuat::Identity); + } + else + { + if (Grip->GripCollisionType == EGripCollisionType::InteractiveHybridCollisionWithPhysics) + { + // Do not effect damping, just increase stiffness so that it is stronger + // Default multiplier + Stiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + AngularStiffness *= HYBRID_PHYSICS_GRIP_MULTIPLIER; + + AngularMaxForce = (float)FMath::Clamp<double>((double)AngularStiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.AngularMaxForceCoefficient, 0, (double)MAX_FLT); + MaxForce = (float)FMath::Clamp<double>((double)Stiffness * (double)Grip->AdvancedGripSettings.PhysicsSettings.LinearMaxForceCoefficient, 0, (double)MAX_FLT); + } + + HandleInfo->LinConstraint.XDrive.Damping = Damping; + HandleInfo->LinConstraint.XDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.XDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.YDrive.Damping = Damping; + HandleInfo->LinConstraint.YDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.YDrive.MaxForce = MaxForce; + HandleInfo->LinConstraint.ZDrive.Damping = Damping; + HandleInfo->LinConstraint.ZDrive.Stiffness = Stiffness; + HandleInfo->LinConstraint.ZDrive.MaxForce = MaxForce; + + FPhysicsInterface::UpdateLinearDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->LinConstraint); +#if WITH_CHAOS + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast<Chaos::FJointConstraint*>(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetLinearDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } +#endif + + + HandleInfo->AngConstraint.SlerpDrive.Damping = AngularDamping; + HandleInfo->AngConstraint.SlerpDrive.Stiffness = AngularStiffness; + HandleInfo->AngConstraint.SlerpDrive.MaxForce = AngularMaxForce; + FPhysicsInterface::UpdateAngularDrive_AssumesLocked(HandleInfo->HandleData2, HandleInfo->AngConstraint); +#if WITH_CHAOS + if (bUseForceDrive && HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint) + { + if (HandleInfo->HandleData2.IsValid() && HandleInfo->HandleData2.Constraint->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast<Chaos::FJointConstraint*>(HandleInfo->HandleData2.Constraint)) + { + Constraint->SetAngularDriveForceMode(Chaos::EJointForceMode::Force); + } + } + } +#endif + } + + }); + + } + return true; + } + + return false; +} + +bool UGripMotionControllerComponent::GetPhysicsJointLength(const FBPActorGripInformation &GrippedActor, UPrimitiveComponent * rootComp, FVector & LocOut) +{ + if (!GrippedActor.GrippedObject) + return false; + + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(GrippedActor); + + if (!HandleInfo || !FPhysicsInterface::IsValid(HandleInfo->KinActorData2)) + return false; + + if (!HandleInfo->HandleData2.IsValid()) + return false; + + // Not using com with skipping mass check as the COM can change on us + // Also skipping it on skip resetting com as we aren't gripped to the COM then + bool bUseComLoc = + ( + !HandleInfo->bSkipResettingCom && + (HandleInfo->bSetCOM || + (GrippedActor.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && GrippedActor.AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings == EPhysicsGripCOMType::COM_GripAt)) + ); + + // This is supposed to be the difference between the actor and the kinactor / constraint base + FTransform tran3 = FTransform::Identity; + + FBodyInstance* rBodyInstance = rootComp->GetBodyInstance(GrippedActor.GrippedBoneName); + + if (bUseComLoc && rBodyInstance && rBodyInstance->IsValidBodyInstance()) + { + tran3 = FTransform(rBodyInstance->GetCOMPosition()); + } + else + { + FTransform rr; + tran3 = FPhysicsInterface::GetLocalPose(HandleInfo->HandleData2, EConstraintFrame::Frame2); + + if (!rBodyInstance || !rBodyInstance->IsValidBodyInstance()) + { + rr = rootComp->GetComponentTransform(); + // Physx location throws out scale, this is where the problem was + rr.SetScale3D(FVector(1, 1, 1)); + } + else + rr = rBodyInstance->GetUnrealWorldTransform(); + + // Make the local pose global + tran3 = tran3 * rr; + } + + // Get the global pose for the kin actor + FTransform kinPose = FTransform::Identity; + FPhysicsCommand::ExecuteRead(HandleInfo->KinActorData2, [&](const FPhysicsActorHandle & Actor) + { + kinPose = FPhysicsInterface::GetGlobalPose_AssumesLocked(Actor); + }); + + // Return the difference + LocOut = FTransform::SubtractTranslations(kinPose, tran3); + + return true; +} + +void UGripMotionControllerComponent::UpdatePhysicsHandleTransform(const FBPActorGripInformation &GrippedActor, const FTransform& NewTransform) +{ + if (!GrippedActor.GrippedObject || (bConstrainToPivot && !GrippedActor.bIsLerping) || GetWorld()->IsInSeamlessTravel()) + return; + + if (!NewTransform.IsValid()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("Something went wrong, UpdatePhysicsHandeTransforms target transform contained NAN!.")); + return; + } + + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(GrippedActor); + + if (!HandleInfo || !FPhysicsInterface::IsValid(HandleInfo->KinActorData2))// || !HandleInfo->HandleData2.IsValid()) + return; + + // Don't call moveKinematic if it hasn't changed - that will stop bodies from going to sleep. + if (!HandleInfo->LastPhysicsTransform.EqualsNoScale(NewTransform)) + { + HandleInfo->LastPhysicsTransform = NewTransform; + HandleInfo->LastPhysicsTransform.SetScale3D(FVector(1.0f)); + FPhysicsActorHandle ActorHandle = HandleInfo->KinActorData2; + FTransform newTrans = HandleInfo->COMPosition * (HandleInfo->RootBoneRotation * HandleInfo->LastPhysicsTransform); + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](const FPhysicsActorHandle & Actor) + { + FPhysicsInterface::SetKinematicTarget_AssumesLocked(Actor, newTrans); + }); + } + + // Debug draw for COM movement with physics grips +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (GripMotionControllerCvars::DrawDebugGripCOM) + { + UPrimitiveComponent* me = Cast<UPrimitiveComponent>(GrippedActor.GripTargetType == EGripTargetType::ActorGrip ? GrippedActor.GetGrippedActor()->GetRootComponent() : GrippedActor.GetGrippedComponent()); + FVector curCOMPosition = me->GetBodyInstance(GrippedActor.GrippedBoneName)->GetCOMPosition(); + DrawDebugSphere(GetWorld(), curCOMPosition, 4, 32, FColor::Red, false); + FTransform TargetTransform = (HandleInfo->COMPosition * (HandleInfo->RootBoneRotation * HandleInfo->LastPhysicsTransform)); + DrawDebugSphere(GetWorld(), TargetTransform.GetLocation(), 4, 32, FColor::Cyan, false); + DrawDebugLine(GetWorld(), TargetTransform.GetTranslation(), TargetTransform.GetTranslation() + (TargetTransform.GetRotation().GetForwardVector() * 20.f), FColor::Red); + DrawDebugLine(GetWorld(), TargetTransform.GetTranslation(), TargetTransform.GetTranslation() + (TargetTransform.GetRotation().GetRightVector() * 20.f), FColor::Green); + DrawDebugLine(GetWorld(), TargetTransform.GetTranslation(), TargetTransform.GetTranslation() + (TargetTransform.GetRotation().GetUpVector() * 20.f), FColor::Blue); + } +#endif + +} + +static void PullBackHitComp(FHitResult& Hit, const FVector& Start, const FVector& End, const float Dist) +{ + const float DesiredTimeBack = FMath::Clamp(0.1f, 0.1f / Dist, 1.f / Dist) + 0.001f; + Hit.Time = FMath::Clamp(Hit.Time - DesiredTimeBack, 0.f, 1.f); +} + +bool UGripMotionControllerComponent::CheckComponentWithSweep(UPrimitiveComponent * ComponentToCheck, FVector Move, FRotator newOrientation, bool bSkipSimulatingComponents/*, bool &bHadBlockingHitOut*/) +{ + TArray<FHitResult> Hits; + // WARNING: HitResult is only partially initialized in some paths. All data is valid only if bFilledHitResult is true. + FHitResult BlockingHit(NoInit); + BlockingHit.bBlockingHit = false; + BlockingHit.Time = 1.f; + bool bFilledHitResult = false; + bool bMoved = false; + bool bIncludesOverlapsAtEnd = false; + bool bRotationOnly = false; + + UPrimitiveComponent *root = ComponentToCheck; + + if (!root || !root->IsQueryCollisionEnabled()) + return false; + + FVector start(root->GetComponentLocation()); + + const bool bCollisionEnabled = root->IsQueryCollisionEnabled(); + + if (bCollisionEnabled) + { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (!root->IsRegistered()) + { + UE_LOG(LogVRMotionController, Warning, TEXT("MovedComponent %s not initialized in grip motion controller"), *root->GetFullName()); + } +#endif + + UWorld* const MyWorld = GetWorld(); + FComponentQueryParams Params(TEXT("sweep_params"), root->GetOwner()); + + FCollisionResponseParams ResponseParam; + root->InitSweepCollisionParams(Params, ResponseParam); + + FVector end = start + Move; + bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, root, start, end, newOrientation.Quaternion(), Params); + + if (Hits.Num() > 0) + { + const float DeltaSize = FVector::Dist(start, end); + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + PullBackHitComp(Hits[HitIdx], start, end, DeltaSize); + } + } + + if (bHadBlockingHit) + { + int32 BlockingHitIndex = INDEX_NONE; + float BlockingHitNormalDotDelta = BIG_NUMBER; + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + const FHitResult& TestHit = Hits[HitIdx]; + + // Ignore the owning actor to the motion controller + if (TestHit.GetActor() == this->GetOwner() || (bSkipSimulatingComponents && TestHit.Component->IsSimulatingPhysics())) + { + if (Hits.Num() == 1) + { + //bHadBlockingHitOut = false; + return false; + } + else + continue; + } + + if (TestHit.bBlockingHit && TestHit.IsValidBlockingHit()) + { + if (TestHit.Time == 0.f) + { + // We may have multiple initial hits, and want to choose the one with the normal most opposed to our movement. + const float NormalDotDelta = (TestHit.ImpactNormal | Move); + if (NormalDotDelta < BlockingHitNormalDotDelta) + { + BlockingHitNormalDotDelta = NormalDotDelta; + BlockingHitIndex = HitIdx; + } + } + else if (BlockingHitIndex == INDEX_NONE) + { + // First non-overlapping blocking hit should be used, if an overlapping hit was not. + // This should be the only non-overlapping blocking hit, and last in the results. + BlockingHitIndex = HitIdx; + break; + } + //} + } + } + + // Update blocking hit, if there was a valid one. + if (BlockingHitIndex >= 0) + { + BlockingHit = Hits[BlockingHitIndex]; + bFilledHitResult = true; + } + } + } + + // Handle blocking hit notifications. Avoid if pending kill (which could happen after overlaps). + if (BlockingHit.bBlockingHit && IsValid(root)) + { + check(bFilledHitResult); + if (root->IsDeferringMovementUpdates()) + { + FScopedMovementUpdate* ScopedUpdate = root->GetCurrentScopedMovement(); + ScopedUpdate->AppendBlockingHitAfterMove(BlockingHit); + } + else + { + + if(root->GetOwner()) + root->DispatchBlockingHit(*root->GetOwner(), BlockingHit); + } + + return true; + } + + return false; +} + +bool UGripMotionControllerComponent::HasTrackingParameters() +{ + return bOffsetByHMD || bScaleTracking || bLeashToHMD || bLimitMinHeight || bLimitMaxHeight; +} + +void UGripMotionControllerComponent::ApplyTrackingParameters(FVector& OriginalPosition, bool bIsInGameThread) +{ + if (bScaleTracking) + { + OriginalPosition *= TrackingScaler; + } + + if (bLimitMinHeight) + { + OriginalPosition.Z = FMath::Max(OriginalPosition.Z, MinimumHeight); + } + + if (bLimitMaxHeight) + { + OriginalPosition.Z = FMath::Min(OriginalPosition.Z, MaximumHeight); + } + + if (bOffsetByHMD || bLeashToHMD) + { + if (bIsInGameThread) + { + if (IsLocallyControlled() && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + FQuat curRot; + FVector curLoc; + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curLoc)) + { + + if (IsValid(AttachChar) && AttachChar->VRReplicatedCamera) + { + AttachChar->VRReplicatedCamera->ApplyTrackingParameters(curLoc); + } + + //curLoc.Z = 0; + LastLocationForLateUpdate = curLoc; + } + } + else + { + if (IsValid(AttachChar) && AttachChar->VRReplicatedCamera) + { + // Sample camera location instead + LastLocationForLateUpdate = AttachChar->VRReplicatedCamera->GetRelativeLocation(); + } + } + } + + // #TODO: This is technically unsafe, need to use a seperate value like the transforms for the render thread + // If I ever delete the simple char then this setup can just go away anyway though + // It has a data race condition right now though + FVector CorrectLastLocation = bIsInGameThread ? LastLocationForLateUpdate : GripRenderThreadLastLocationForLateUpdate; + + if (bOffsetByHMD) + { + OriginalPosition -= FVector(CorrectLastLocation.X, CorrectLastLocation.Y, 0.0f); + } + + if (bLeashToHMD) + { + FVector DifferenceVec = bOffsetByHMD ? OriginalPosition : (OriginalPosition - CorrectLastLocation); + + if (DifferenceVec.SizeSquared() > FMath::Square(LeashRange)) + { + OriginalPosition = CorrectLastLocation + (DifferenceVec.GetSafeNormal() * LeashRange); + } + } + } +} + +//============================================================================= +bool UGripMotionControllerComponent::GripPollControllerState(FVector& Position, FRotator& Orientation , float WorldToMetersScale) +{ + // Not calling PollControllerState from the parent because its private....... + + bool bIsInGameThread = IsInGameThread(); + + if (bHasAuthority) + { + // New iteration and retrieval for 4.12 + TArray<IMotionController*> MotionControllers = IModularFeatures::Get().GetModularFeatureImplementations<IMotionController>(IMotionController::GetModularFeatureName()); + for (auto MotionController : MotionControllers) + { + if (MotionController == nullptr) + { + continue; + } + +#if !PLATFORM_PS4 + if (bIsInGameThread) + { + CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, MotionSource); + if (CurrentTrackingStatus == ETrackingStatus::NotTracked) + continue; + } +#endif + + if (MotionController->GetControllerOrientationAndPosition(PlayerIndex, MotionSource, Orientation, Position, WorldToMetersScale)) + { +#if PLATFORM_PS4 + // Moving this in here to work around a PSVR module bug + if (bIsInGameThread) + { + CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, MotionSource); + if (CurrentTrackingStatus == ETrackingStatus::NotTracked) + continue; + } +#endif + if (HasTrackingParameters()) + { + ApplyTrackingParameters(Position, bIsInGameThread); + } + + if (bOffsetByControllerProfile) + { + FTransform FinalControllerTransform(Orientation,Position); + if (bIsInGameThread) + { + FinalControllerTransform = CurrentControllerProfileTransform * FinalControllerTransform; + } + else + { + FinalControllerTransform = GripRenderThreadProfileTransform * FinalControllerTransform; + } + + Orientation = FinalControllerTransform.Rotator(); + Position = FinalControllerTransform.GetTranslation(); + } + + // Render thread also calls this, shouldn't be flagging this event in the render thread. + if (bIsInGameThread) + { + InUseMotionController = MotionController; + OnMotionControllerUpdated(); + InUseMotionController = nullptr; + } + + return true; + } +#if PLATFORM_PS4 + else if (bIsInGameThread) + { + CurrentTrackingStatus = MotionController->GetControllerTrackingStatus(PlayerIndex, MotionSource); + } +#endif + } + + // #NOTE: This was adding in 4.20, I presume to allow for HMDs as tracking sources for mixed reality. + // Skipping all of my special logic here for now + if (MotionSource == FXRMotionControllerBase::HMDSourceId) + { + IXRTrackingSystem* TrackingSys = GEngine->XRSystem.Get(); + if (TrackingSys) + { + FQuat OrientationQuat = FQuat::Identity; + if (TrackingSys->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, OrientationQuat, Position)) + { + Orientation = OrientationQuat.Rotator(); + return true; + } + } + } + } + return false; +} + +//============================================================================= +UGripMotionControllerComponent::FGripViewExtension::FGripViewExtension(const FAutoRegister& AutoRegister, UGripMotionControllerComponent* InMotionControllerComponent) + : FSceneViewExtensionBase(AutoRegister) + , MotionControllerComponent(InMotionControllerComponent) +{ + FSceneViewExtensionIsActiveFunctor IsActiveFunc; + IsActiveFunc.IsActiveFunction = [this](const ISceneViewExtension* SceneViewExtension, const FSceneViewExtensionContext& Context) + { + check(IsInGameThread()); + + static const auto CVarEnableMotionControllerLateUpdate = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.EnableMotionControllerLateUpdate")); + return MotionControllerComponent && !MotionControllerComponent->bDisableLowLatencyUpdate && MotionControllerComponent->bTracked && CVarEnableMotionControllerLateUpdate->GetValueOnGameThread(); + }; + + IsActiveThisFrameFunctions.Add(IsActiveFunc); +} + + +void UGripMotionControllerComponent::FGripViewExtension::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) +{ + FTransform OldTransform; + FTransform NewTransform; + + { + FScopeLock ScopeLock(&CritSect); + + if (!MotionControllerComponent) + return; + + // Find a view that is associated with this player. + float WorldToMetersScale = -1.0f; + for (const FSceneView* SceneView : InViewFamily.Views) + { + if (SceneView && SceneView->PlayerIndex == MotionControllerComponent->PlayerIndex) + { + WorldToMetersScale = SceneView->WorldToMetersScale; + break; + } + } + + // If there are no views associated with this player use view 0. + if (WorldToMetersScale < 0.0f) + { + check(InViewFamily.Views.Num() > 0); + WorldToMetersScale = InViewFamily.Views[0]->WorldToMetersScale; + } + + // Poll state for the most recent controller transform + FVector Position = MotionControllerComponent->GripRenderThreadRelativeTransform.GetTranslation(); + FRotator Orientation = MotionControllerComponent->GripRenderThreadRelativeTransform.GetRotation().Rotator(); + + if (!MotionControllerComponent->GripPollControllerState(Position, Orientation, WorldToMetersScale)) + { + return; + } + + OldTransform = MotionControllerComponent->GripRenderThreadRelativeTransform; + NewTransform = FTransform(Orientation, Position, MotionControllerComponent->GripRenderThreadComponentScale); + MotionControllerComponent->GripRenderThreadRelativeTransform = NewTransform; + } // Release lock on motion controller component + + // Tell the late update manager to apply the offset to the scene components + LateUpdate.Apply_RenderThread(InViewFamily.Scene, OldTransform, NewTransform); + // #TODO: UE5 is missing this pull + //LateUpdate.Apply_RenderThread(InViewFamily.Scene, InViewFamily.bLateLatchingEnabled ? InViewFamily.FrameNumber : -1, OldTransform, NewTransform); +} + +/*void UGripMotionControllerComponent::FGripViewExtension::LateLatchingViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) +{ + SCOPED_NAMED_EVENT(UMotionControllerComponent_Latch, FColor::Orange); + if (!MotionControllerComponent) + { + return; + } + + FTransform OldTransform; + FTransform NewTransform; + { + FScopeLock ScopeLock(&CritSect); + if (!MotionControllerComponent) + { + return; + } + + // Find a view that is associated with this player. + float WorldToMetersScale = -1.0f; + for (const FSceneView* SceneView : InViewFamily.Views) + { + if (SceneView && SceneView->PlayerIndex == MotionControllerComponent->PlayerIndex) + { + WorldToMetersScale = SceneView->WorldToMetersScale; + break; + } + } + // If there are no views associated with this player use view 0. + if (WorldToMetersScale < 0.0f) + { + check(InViewFamily.Views.Num() > 0); + WorldToMetersScale = InViewFamily.Views[0]->WorldToMetersScale; + } + + // Poll state for the most recent controller transform + FVector Position; + FRotator Orientation; + + if (!MotionControllerComponent->GripPollControllerState(Position, Orientation, WorldToMetersScale)) + { + return; + } + + OldTransform = MotionControllerComponent->GripRenderThreadRelativeTransform; + NewTransform = FTransform(Orientation, Position, MotionControllerComponent->GripRenderThreadComponentScale); + MotionControllerComponent->GripRenderThreadRelativeTransform = NewTransform; + + } // Release the lock on the MotionControllerComponent + + // Tell the late update manager to apply the offset to the scene components + LateUpdate.Apply_RenderThread(InViewFamily.Scene, InViewFamily.FrameNumber, OldTransform, NewTransform); +}*/ + +bool UGripMotionControllerComponent::K2_GetFirstActiveGrip(FBPActorGripInformation& FirstActiveGrip) +{ + FBPActorGripInformation* FirstGrip = GetFirstActiveGrip(); + + if (FirstGrip) + { + FirstActiveGrip = *FirstGrip; + return true; + } + + return false; +} + +FBPActorGripInformation* UGripMotionControllerComponent::GetFirstActiveGrip() +{ + for (FBPActorGripInformation& Grip : GrippedObjects) + { + if (Grip.IsValid() && !Grip.bIsPaused) + { + return &Grip; + } + } + + for (FBPActorGripInformation& LocalGrip : LocallyGrippedObjects) + { + if (LocalGrip.IsValid() && !LocalGrip.bIsPaused) + { + return &LocalGrip; + } + } + + return nullptr; +} + +void UGripMotionControllerComponent::GetAllGrips(TArray<FBPActorGripInformation> &GripArray) +{ + GripArray.Append(GrippedObjects); + GripArray.Append(LocallyGrippedObjects); +} + +void UGripMotionControllerComponent::GetGrippedObjects(TArray<UObject*> &GrippedObjectsArray) +{ + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].GrippedObject) + GrippedObjectsArray.Add(GrippedObjects[i].GrippedObject); + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GrippedObject) + GrippedObjectsArray.Add(LocallyGrippedObjects[i].GrippedObject); + } + +} + +void UGripMotionControllerComponent::GetGrippedActors(TArray<AActor*> &GrippedObjectsArray) +{ + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if(GrippedObjects[i].GetGrippedActor()) + GrippedObjectsArray.Add(GrippedObjects[i].GetGrippedActor()); + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GetGrippedActor()) + GrippedObjectsArray.Add(LocallyGrippedObjects[i].GetGrippedActor()); + } + +} + +void UGripMotionControllerComponent::GetGrippedComponents(TArray<UPrimitiveComponent*> &GrippedComponentsArray) +{ + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].GetGrippedComponent()) + GrippedComponentsArray.Add(GrippedObjects[i].GetGrippedComponent()); + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].GetGrippedComponent()) + GrippedComponentsArray.Add(LocallyGrippedObjects[i].GetGrippedComponent()); + } +} + +// Locally gripped functions +void UGripMotionControllerComponent::Client_NotifyInvalidLocalGrip_Implementation(UObject * LocallyGrippedObject, uint8 GripID, bool bWasAGripConflict) +{ + if (GripID != INVALID_VRGRIP_ID) + { + if (FBPActorGripInformation* GripInfo = GetGripPtrByID(GripID)) + { + DropObjectByInterface(nullptr, GripID); + + if (LocallyGrippedObject && bWasAGripConflict) + { + OnClientAuthGripConflict.Broadcast(LocallyGrippedObject, ClientAuthConflictResolutionMethod); + } + + return; + } + } + + FBPActorGripInformation FoundGrip; + EBPVRResultSwitch Result; + + GetGripByObject(FoundGrip, LocallyGrippedObject, Result); + + if (Result == EBPVRResultSwitch::OnFailed) + return; + + // Drop it, server told us that it was a bad grip + DropObjectByInterface(nullptr, FoundGrip.GripID); + + if (LocallyGrippedObject && bWasAGripConflict) + { + OnClientAuthGripConflict.Broadcast(LocallyGrippedObject, ClientAuthConflictResolutionMethod); + } +} + +bool UGripMotionControllerComponent::Server_NotifyHandledTransaction_Validate(uint8 GripID) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyHandledTransaction_Implementation(uint8 GripID) +{ + for (int i = LocalTransactionBuffer.Num() - 1; i >= 0; i--) + { + if(LocalTransactionBuffer[i].GripID == GripID) + LocalTransactionBuffer.RemoveAt(i); + } +} + +bool UGripMotionControllerComponent::Server_NotifyLocalGripAddedOrChanged_Validate(const FBPActorGripInformation & newGrip) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyLocalGripAddedOrChanged_Implementation(const FBPActorGripInformation & newGrip) +{ + if (!newGrip.GrippedObject || newGrip.GripMovementReplicationSetting != EGripMovementReplicationSettings::ClientSide_Authoritive) + { + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID); + return; + } + + if (!LocallyGrippedObjects.Contains(newGrip)) + { + + bool bImplementsInterface = newGrip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()); + + TArray<FBPGripPair> HoldingControllers; + bool bIsHeld = false; + if (bImplementsInterface) + { + IVRGripInterface::Execute_IsHeld(newGrip.GrippedObject, HoldingControllers, bIsHeld); + + if (bIsHeld && ClientAuthConflictResolutionMethod != EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_None) + { + // Its held and doesn't allow more than one grip at a time + if (!IVRGripInterface::Execute_AllowsMultipleGrips(newGrip.GrippedObject)) + { + // Lets see if a different owner is holding it, if so, deny this request + for (FBPGripPair& GripPair : HoldingControllers) + { + if (!GripPair.HoldingController || GripPair.GripID == INVALID_VRGRIP_ID) + { + continue; + } + + // If the controllers have different owners (if its the same then consider them as locally transacting and let it go) + if (GripPair.HoldingController->GetOwner()->GetNetOwner() != this->GetOwner()->GetNetOwner()) + { + switch (ClientAuthConflictResolutionMethod) + { + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_First: + { + // Deny the grip, another connection already came and gripped it + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID, true); + + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + return; + }break; + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_Last: + { + // Deny the old grip, another connection came and gripped it + GripPair.HoldingController->DropObjectByInterface(newGrip.GrippedObject, GripPair.GripID); + GripPair.HoldingController->Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, GripPair.GripID, true); + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + }break; + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_DropAll: + { + // Deny both grips + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID, true); + GripPair.HoldingController->DropObjectByInterface(newGrip.GrippedObject, GripPair.GripID); + GripPair.HoldingController->Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, GripPair.GripID, true); + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + return; + }break; + case EVRClientAuthConflictResolutionMode::VRGRIP_CONFLICT_None: + default: + { + OnClientAuthGripConflict.Broadcast(newGrip.GrippedObject, ClientAuthConflictResolutionMethod); + }break; + + } + } + } + } + } + } + + + UPrimitiveComponent* PrimComp = nullptr; + AActor* pActor = nullptr; + + PrimComp = newGrip.GetGrippedComponent(); + pActor = newGrip.GetGrippedActor(); + + if (!PrimComp && pActor) + { + PrimComp = Cast<UPrimitiveComponent>(pActor->GetRootComponent()); + } + else if (!pActor && PrimComp) + { + pActor = PrimComp->GetOwner(); + } + + if (!PrimComp || !pActor) + { + Client_NotifyInvalidLocalGrip(newGrip.GrippedObject, newGrip.GripID); + return; + } + + bool bHadOriginalSettings = false; + bool bOriginalGravity = false; + bool bOriginalReplication = false; + + if (bImplementsInterface) + { + //if (IVRGripInterface::Execute_DenyGripping(root)) + // return false; // Interface is saying not to grip it right now + if (bIsHeld) + { + // If we are held by multiple controllers then lets copy our original values from the first one + if (HoldingControllers.Num() > 0 && HoldingControllers[0].HoldingController != nullptr) + { + FBPActorGripInformation* gripInfo = HoldingControllers[0].HoldingController->GetGripPtrByID(HoldingControllers[0].GripID); + + if (gripInfo != nullptr) + { + bHadOriginalSettings = true; + bOriginalGravity = gripInfo->bOriginalGravity; + bOriginalReplication = gripInfo->bOriginalReplicatesMovement; + } + } + } + } + + int32 NewIndex = LocallyGrippedObjects.Add(newGrip); + + if (NewIndex != INDEX_NONE && LocallyGrippedObjects.Num() > 0) + { + if (bHadOriginalSettings) + { + LocallyGrippedObjects[NewIndex].bOriginalReplicatesMovement = bOriginalReplication; + LocallyGrippedObjects[NewIndex].bOriginalGravity = bOriginalGravity; + } + else + { + LocallyGrippedObjects[NewIndex].bOriginalReplicatesMovement = pActor->IsReplicatingMovement(); + LocallyGrippedObjects[NewIndex].bOriginalGravity = PrimComp->IsGravityEnabled(); + } + + HandleGripReplication(LocallyGrippedObjects[NewIndex]); + } + + // Initialize the differences, clients will do this themselves on the rep back, this sets up the cache + //HandleGripReplication(LocallyGrippedObjects[LocallyGrippedObjects.Num() - 1]); + } + else + { + int32 IndexFound = INDEX_NONE; + if (LocallyGrippedObjects.Find(newGrip, IndexFound) && IndexFound != INDEX_NONE) + { + FBPActorGripInformation OriginalGrip = LocallyGrippedObjects[IndexFound]; + LocallyGrippedObjects[IndexFound].RepCopy(newGrip); + HandleGripReplication(LocallyGrippedObjects[IndexFound], &OriginalGrip); + } + } + + // Server has to call this themselves + //OnRep_LocallyGrippedObjects(); +} + + +bool UGripMotionControllerComponent::Server_NotifyLocalGripRemoved_Validate(uint8 GripID, const FTransform_NetQuantize &TransformAtDrop, FVector_NetQuantize100 AngularVelocity, FVector_NetQuantize100 LinearVelocity) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifyLocalGripRemoved_Implementation(uint8 GripID, const FTransform_NetQuantize &TransformAtDrop, FVector_NetQuantize100 AngularVelocity, FVector_NetQuantize100 LinearVelocity) +{ + FBPActorGripInformation FoundGrip; + EBPVRResultSwitch Result; + GetGripByID(FoundGrip, GripID, Result); + + if (Result == EBPVRResultSwitch::OnFailed) + return; + + if (FoundGrip.GripCollisionType != EGripCollisionType::EventsOnly) + { + switch (FoundGrip.GripTargetType) + { + case EGripTargetType::ActorGrip: + { + if (AActor* DroppingActor = FoundGrip.GetGrippedActor()) + { + if (IsValid(DroppingActor) && TransformAtDrop.IsValid()) + { + DroppingActor->SetActorTransform(TransformAtDrop, false, nullptr, ETeleportType::None); + } + } + }break; + case EGripTargetType::ComponentGrip: + { + if (UPrimitiveComponent* DroppingComp = FoundGrip.GetGrippedComponent()) + { + if (IsValid(DroppingComp) && TransformAtDrop.IsValid()) + { + DroppingComp->SetWorldTransform(TransformAtDrop, false, nullptr, ETeleportType::None); + } + } + }break; + default:break; + } + } + + if (!DropObjectByInterface(nullptr, FoundGrip.GripID, AngularVelocity, LinearVelocity)) + { + DropGrip_Implementation(FoundGrip, false, AngularVelocity, LinearVelocity); + } +} + + +bool UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Validate( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Implementation( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo) +{ + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(GripID); + if (GripInfo != nullptr) + { + FBPActorGripInformation OriginalGrip = *GripInfo; + + // I override the = operator now so that it won't set the lerp components + GripInfo->SecondaryGripInfo.RepCopy(SecondaryGripInfo); + + // Initialize the differences, clients will do this themselves on the rep back + HandleGripReplication(*GripInfo, &OriginalGrip); + } + +} + +bool UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Retain_Validate( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo, const FTransform_NetQuantize & NewRelativeTransform) +{ + return true; +} + +void UGripMotionControllerComponent::Server_NotifySecondaryAttachmentChanged_Retain_Implementation( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo, const FTransform_NetQuantize & NewRelativeTransform) +{ + + FBPActorGripInformation * GripInfo = LocallyGrippedObjects.FindByKey(GripID); + if (GripInfo != nullptr) + { + FBPActorGripInformation OriginalGrip = *GripInfo; + + // I override the = operator now so that it won't set the lerp components + GripInfo->SecondaryGripInfo.RepCopy(SecondaryGripInfo); + GripInfo->RelativeTransform = NewRelativeTransform; + + // Initialize the differences, clients will do this themselves on the rep back + HandleGripReplication(*GripInfo, &OriginalGrip); + } + +} +void UGripMotionControllerComponent::GetControllerDeviceID(FXRDeviceId & DeviceID, EBPVRResultSwitch &Result, bool bCheckOpenVROnly) +{ + EControllerHand ControllerHandIndex; + if (!FXRMotionControllerBase::GetHandEnumForSourceName(MotionSource, ControllerHandIndex)) + { + Result = EBPVRResultSwitch::OnFailed; + return; + } + + TArray<IXRSystemAssets*> XRAssetSystems = IModularFeatures::Get().GetModularFeatureImplementations<IXRSystemAssets>(IXRSystemAssets::GetModularFeatureName()); + for (IXRSystemAssets* AssetSys : XRAssetSystems) + { + if (bCheckOpenVROnly && !AssetSys->GetSystemName().IsEqual(FName(TEXT("SteamVR")))) + continue; + + const int32 XRID = AssetSys->GetDeviceId(ControllerHandIndex); + + if (XRID != INDEX_NONE) + { + DeviceID = FXRDeviceId(AssetSys, XRID); + Result = EBPVRResultSwitch::OnSucceeded; + return; + } + } + + DeviceID = FXRDeviceId(); + Result = EBPVRResultSwitch::OnFailed; + return; +} + + +/* +* +* Custom late update manager implementation +* +*/ + +FExpandedLateUpdateManager::FExpandedLateUpdateManager() + : LateUpdateGameWriteIndex(0) + , LateUpdateRenderReadIndex(0) +{ +} + +void FExpandedLateUpdateManager::Setup(const FTransform& ParentToWorld, UGripMotionControllerComponent* Component, bool bSkipLateUpdate) +{ + if (!Component) + return; + + check(IsInGameThread()); + + + UpdateStates[LateUpdateGameWriteIndex].Primitives.Reset(); + UpdateStates[LateUpdateGameWriteIndex].ParentToWorld = ParentToWorld; + + //Add additional late updates registered to this controller that aren't children and aren't gripped + //This array is editable in blueprint and can be used for things like arms or the like. + for (UPrimitiveComponent* primComp : Component->AdditionalLateUpdateComponents) + { + if (primComp) + GatherLateUpdatePrimitives(primComp); + } + + ProcessGripArrayLateUpdatePrimitives(Component, Component->LocallyGrippedObjects); + ProcessGripArrayLateUpdatePrimitives(Component, Component->GrippedObjects); + + GatherLateUpdatePrimitives(Component); + //GatherLateUpdatePrimitives(Component); + + UpdateStates[LateUpdateGameWriteIndex].bSkip = bSkipLateUpdate; + ++UpdateStates[LateUpdateGameWriteIndex].TrackingNumber; + + int32 NextFrameRenderReadIndex = LateUpdateGameWriteIndex; + LateUpdateGameWriteIndex = 1 - LateUpdateGameWriteIndex; + + ENQUEUE_RENDER_COMMAND(UpdateLateUpdateRenderReadIndexCommand)( + [NextFrameRenderReadIndex, this](FRHICommandListImmediate& RHICmdList) + { + LateUpdateRenderReadIndex = NextFrameRenderReadIndex; + }); + +} + +//void FExpandedLateUpdateManager::Apply_RenderThread(FSceneInterface* Scene, const int32 FrameNumber, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform) +void FExpandedLateUpdateManager::Apply_RenderThread(FSceneInterface* Scene, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform) +{ + check(IsInRenderingThread()); + + + if (!UpdateStates[LateUpdateRenderReadIndex].Primitives.Num() || UpdateStates[LateUpdateRenderReadIndex].bSkip) + { + return; + } + + const FTransform OldCameraTransform = OldRelativeTransform * UpdateStates[LateUpdateRenderReadIndex].ParentToWorld; + const FTransform NewCameraTransform = NewRelativeTransform * UpdateStates[LateUpdateRenderReadIndex].ParentToWorld; + const FMatrix LateUpdateTransform = (OldCameraTransform.Inverse() * NewCameraTransform).ToMatrixWithScale(); + + bool bIndicesHaveChanged = false; + + /* + // Under HMD late-latching senario, Apply_RenderThread will be called twice in same frame under PreRenderViewFamily_RenderThread and + // LateLatchingViewFamily_RenderThread. We don't want to apply PrimitivePair.Value = -1 directly on UpdateStates[LateUpdateRenderReadIndex].Primitives + // because the modification will affect second Apply_RenderThread's logic under LateLatchingViewFamily_RenderThread. + // Since the list is very small(only affect stuff attaching to the controller), we just make a local copy PrimitivesLocal. + TMap<FPrimitiveSceneInfo*, int32> PrimitivesLocal = UpdateStates[LateUpdateRenderReadIndex].Primitives; + for (auto& PrimitivePair : PrimitivesLocal)*/ + for (auto& PrimitivePair : UpdateStates[LateUpdateRenderReadIndex].Primitives) + { + if (PrimitivePair.Value == -1) + continue; + + FPrimitiveSceneInfo* RetrievedSceneInfo = Scene->GetPrimitiveSceneInfo(PrimitivePair.Value); + FPrimitiveSceneInfo* CachedSceneInfo = PrimitivePair.Key; + + // If the retrieved scene info is different than our cached scene info then the scene has changed in the meantime + // and we need to search through the entire scene to make sure it still exists. + if (CachedSceneInfo != RetrievedSceneInfo) + { + bIndicesHaveChanged = true; + break; // No need to continue here, as we are going to brute force the scene primitives below anyway. + } + else if (CachedSceneInfo->Proxy) + { + CachedSceneInfo->Proxy->ApplyLateUpdateTransform(LateUpdateTransform); + PrimitivePair.Value = -1; // Set the cached index to -1 to indicate that this primitive was already processed + /*if (FrameNumber >= 0) + { + CachedSceneInfo->Proxy->SetPatchingFrameNumber(FrameNumber); + }*/ + } + } + + // Indices have changed, so we need to scan the entire scene for primitives that might still exist + if (bIndicesHaveChanged) + { + int32 Index = 0; + FPrimitiveSceneInfo* RetrievedSceneInfo = Scene->GetPrimitiveSceneInfo(Index++); + while (RetrievedSceneInfo) + { + /*int32* PrimitiveIndex = PrimitivesLocal.Find(RetrievedSceneInfo); + if (RetrievedSceneInfo->Proxy && PrimitiveIndex != nullptr && *PrimitiveIndex >= 0)*/ + if (RetrievedSceneInfo->Proxy && UpdateStates[LateUpdateRenderReadIndex].Primitives.Contains(RetrievedSceneInfo) && UpdateStates[LateUpdateRenderReadIndex].Primitives[RetrievedSceneInfo] >= 0) + { + RetrievedSceneInfo->Proxy->ApplyLateUpdateTransform(LateUpdateTransform); + /*if (FrameNumber >= 0) + { + RetrievedSceneInfo->Proxy->SetPatchingFrameNumber(FrameNumber); + }*/ + } + RetrievedSceneInfo = Scene->GetPrimitiveSceneInfo(Index++); + } + } +} + +void FExpandedLateUpdateManager::CacheSceneInfo(USceneComponent* Component) +{ + ensureMsgf(!Component->IsUsingAbsoluteLocation() && !Component->IsUsingAbsoluteRotation(), TEXT("SceneComponents that use absolute location or rotation are not supported by the LateUpdateManager")); + // If a scene proxy is present, cache it + UPrimitiveComponent* PrimitiveComponent = dynamic_cast<UPrimitiveComponent*>(Component); + if (PrimitiveComponent && PrimitiveComponent->SceneProxy) + { + FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveComponent->SceneProxy->GetPrimitiveSceneInfo(); + if (PrimitiveSceneInfo && PrimitiveSceneInfo->IsIndexValid()) + { + UpdateStates[LateUpdateGameWriteIndex].Primitives.Emplace(PrimitiveSceneInfo, PrimitiveSceneInfo->GetIndex()); + } + } +} + +void FExpandedLateUpdateManager::GatherLateUpdatePrimitives(USceneComponent* ParentComponent) +{ + CacheSceneInfo(ParentComponent); + TArray<USceneComponent*> DirectComponents; + + // Std late updates + ParentComponent->GetChildrenComponents(true, DirectComponents); + for (USceneComponent* Component : DirectComponents) + { + if (Component != nullptr) + { + CacheSceneInfo(Component); + } + } +} + +void FExpandedLateUpdateManager::ProcessGripArrayLateUpdatePrimitives(UGripMotionControllerComponent * MotionControllerComponent, TArray<FBPActorGripInformation> & GripArray) +{ + for (FBPActorGripInformation actor : GripArray) + { + // Skip actors that are colliding if turning off late updates during collision. + // Also skip turning off late updates for SweepWithPhysics, as it should always be locked to the hand + if (!actor.GrippedObject || actor.GripCollisionType == EGripCollisionType::EventsOnly) + continue; + + // Handle late updates even with attachment, we need to add it to a skip list for the primary gatherer to process + if (actor.GripCollisionType == EGripCollisionType::AttachmentGrip) + { + continue; + } + + // Don't allow late updates with server sided movement, there is no point + if (actor.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement && !MotionControllerComponent->IsServer()) + continue; + + // Don't late update paused grips + if (actor.bIsPaused) + continue; + + switch (actor.GripLateUpdateSetting) + { + case EGripLateUpdateSettings::LateUpdatesAlwaysOff: + { + continue; + }break; + case EGripLateUpdateSettings::NotWhenColliding: + { + if (actor.bColliding && actor.GripCollisionType != EGripCollisionType::SweepWithPhysics && + actor.GripCollisionType != EGripCollisionType::PhysicsOnly) + continue; + }break; + case EGripLateUpdateSettings::NotWhenDoubleGripping: + { + if (actor.SecondaryGripInfo.bHasSecondaryAttachment) + continue; + }break; + case EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping: + { + if ( + (actor.bColliding && actor.GripCollisionType != EGripCollisionType::SweepWithPhysics && actor.GripCollisionType != EGripCollisionType::PhysicsOnly) || + (actor.SecondaryGripInfo.bHasSecondaryAttachment) + ) + { + continue; + } + }break; + case EGripLateUpdateSettings::LateUpdatesAlwaysOn: + default: + {}break; + } + + // Don't run late updates if we have a grip script that denies it + if (actor.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(actor.GrippedObject, GripScripts)) + { + bool bContinueOn = false; + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsScriptActive() && Script->Wants_DenyLateUpdates()) + { + bContinueOn = true; + break; + } + } + + if (bContinueOn) + continue; + } + } + + // Get late update primitives + switch (actor.GripTargetType) + { + case EGripTargetType::ActorGrip: + //case EGripTargetType::InteractibleActorGrip: + { + AActor * pActor = actor.GetGrippedActor(); + if (pActor) + { + if (USceneComponent * rootComponent = pActor->GetRootComponent()) + { + GatherLateUpdatePrimitives(rootComponent); + } + } + + }break; + + case EGripTargetType::ComponentGrip: + //case EGripTargetType::InteractibleComponentGrip: + { + UPrimitiveComponent * cPrimComp = actor.GetGrippedComponent(); + if (cPrimComp) + { + GatherLateUpdatePrimitives(cPrimComp); + } + }break; + } + } +} + +void UGripMotionControllerComponent::GetHandType(EControllerHand& Hand) +{ + if (!FXRMotionControllerBase::GetHandEnumForSourceName(MotionSource, Hand)) + { + Hand = EControllerHand::Left; + } +} + +void UGripMotionControllerComponent::SetCustomPivotComponent(USceneComponent * NewCustomPivotComponent, FName PivotSocketName) +{ + CustomPivotComponent = NewCustomPivotComponent; + CustomPivotComponentSocketName = PivotSocketName; +} + +FTransform UGripMotionControllerComponent::GetPivotTransform_BP() +{ + return GetPivotTransform(); +} + +FVector UGripMotionControllerComponent::GetPivotLocation_BP() +{ + return GetPivotLocation(); +} + +FTransform UGripMotionControllerComponent::ConvertToControllerRelativeTransform(const FTransform & InTransform) +{ + return InTransform.GetRelativeTransform(!bSkipPivotTransformAdjustment && IsValid(CustomPivotComponent) ? CustomPivotComponent->GetSocketTransform(CustomPivotComponentSocketName) : this->GetComponentTransform()); +} + +FTransform UGripMotionControllerComponent::ConvertToGripRelativeTransform(const FTransform& GrippedActorTransform, const FTransform & InTransform) +{ + return InTransform.GetRelativeTransform(GrippedActorTransform); +} + +bool UGripMotionControllerComponent::GetIsObjectHeld(const UObject * ObjectToCheck) +{ + if (!ObjectToCheck) + return false; + + return (GrippedObjects.FindByKey(ObjectToCheck) || LocallyGrippedObjects.FindByKey(ObjectToCheck)); +} + +bool UGripMotionControllerComponent::GetIsHeld(const AActor * ActorToCheck) +{ + if (!ActorToCheck) + return false; + + return (GrippedObjects.FindByKey(ActorToCheck) || LocallyGrippedObjects.FindByKey(ActorToCheck)); +} + +bool UGripMotionControllerComponent::GetIsComponentHeld(const UPrimitiveComponent * ComponentToCheck) +{ + if (!ComponentToCheck) + return false; + + return (GrippedObjects.FindByKey(ComponentToCheck) || LocallyGrippedObjects.FindByKey(ComponentToCheck)); + + return false; +} + +bool UGripMotionControllerComponent::GetIsSecondaryAttachment(const USceneComponent * ComponentToCheck, FBPActorGripInformation & Grip) +{ + if (!ComponentToCheck) + return false; + + for (int i = 0; i < GrippedObjects.Num(); ++i) + { + if (GrippedObjects[i].SecondaryGripInfo.bHasSecondaryAttachment && GrippedObjects[i].SecondaryGripInfo.SecondaryAttachment == ComponentToCheck) + { + Grip = GrippedObjects[i]; + return true; + } + } + + for (int i = 0; i < LocallyGrippedObjects.Num(); ++i) + { + if (LocallyGrippedObjects[i].SecondaryGripInfo.bHasSecondaryAttachment && LocallyGrippedObjects[i].SecondaryGripInfo.SecondaryAttachment == ComponentToCheck) + { + Grip = LocallyGrippedObjects[i]; + return true; + } + } + + return false; +} + +bool UGripMotionControllerComponent::HasGrippedObjects() +{ + return GrippedObjects.Num() > 0 || LocallyGrippedObjects.Num() > 0; +} + +bool UGripMotionControllerComponent::SetUpPhysicsHandle_BP(const FBPActorGripInformation &Grip) +{ + return SetUpPhysicsHandle(Grip); +} + +bool UGripMotionControllerComponent::DestroyPhysicsHandle_BP(const FBPActorGripInformation &Grip) +{ + return DestroyPhysicsHandle(Grip); +} + +bool UGripMotionControllerComponent::UpdatePhysicsHandle_BP(const FBPActorGripInformation& Grip, bool bFullyRecreate) +{ + return UpdatePhysicsHandle(Grip.GripID, bFullyRecreate); +} + +bool UGripMotionControllerComponent::GetPhysicsHandleSettings(const FBPActorGripInformation& Grip, FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsOut) +{ + FBPActorPhysicsHandleInformation * HandleInfo = GetPhysicsGrip(Grip); + + if (!HandleInfo) + return false; + + PhysicsHandleSettingsOut.FillFrom(HandleInfo); + return true; +} + +bool UGripMotionControllerComponent::SetPhysicsHandleSettings(const FBPActorGripInformation& Grip, const FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsIn) +{ + FBPActorPhysicsHandleInformation* HandleInfo = GetPhysicsGrip(Grip); + + if (!HandleInfo) + return false; + + PhysicsHandleSettingsIn.FillTo(HandleInfo); + return UpdatePhysicsHandle(Grip, true); +} + + +void UGripMotionControllerComponent::UpdatePhysicsHandleTransform_BP(const FBPActorGripInformation &GrippedActor, const FTransform& NewTransform) +{ + return UpdatePhysicsHandleTransform(GrippedActor, NewTransform); +} + +bool UGripMotionControllerComponent::GetGripDistance_BP(FBPActorGripInformation &Grip, FVector ExpectedLocation, float & CurrentDistance) +{ + if (!Grip.GrippedObject) + return false; + + UPrimitiveComponent * RootComp = nullptr; + + if (Grip.GripTargetType == EGripTargetType::ActorGrip) + { + RootComp = Cast<UPrimitiveComponent>(Grip.GetGrippedActor()->GetRootComponent()); + } + else + RootComp = Grip.GetGrippedComponent(); + + if (!RootComp) + return false; + + FVector CheckDistance; + if (!GetPhysicsJointLength(Grip, RootComp, CheckDistance)) + { + CheckDistance = (ExpectedLocation - RootComp->GetComponentLocation()); + } + + // Set grip distance now for people to use + CurrentDistance = CheckDistance.Size(); + return true; +} + +bool UGripMotionControllerComponent::GripControllerIsTracked() const +{ + return bTracked; +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Default.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Default.cpp new file mode 100644 index 0000000..b496172 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Default.cpp @@ -0,0 +1,231 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_Default.h" +#include "VRGripInterface.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "GameFramework/WorldSettings.h" +#include "GripMotionControllerComponent.h" + +UGS_Default::UGS_Default(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::OverridesWorldTransform; +} + +void UGS_Default::GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform) +{ + if (Grip.SecondaryGripInfo.GripLerpState != EGripLerpState::EndLerp) + { + //float Scaler = 1.0f; + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + /*Grip.SecondaryScaler*/ Scaler = FVector(frontLoc.Size() / frontLocOrig.Size()); + //bRescalePhysicsGrips = true; // This is for the physics grips + } + } +} + +void UGS_Default::ApplySmoothingAndLerp(FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, float DeltaTime) +{ + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) // Lerp into the new grip to smooth the transition + { + /*if (Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED < 1.0f) + { + FVector SmoothedValue = Grip.AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + + frontLoc = FMath::Lerp(SmoothedValue, frontLoc, Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED); + }*/ + + frontLocOrig = FMath::Lerp(frontLocOrig, frontLoc, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + /*else if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::ConstantLerp_DEPRECATED) // If there is a frame by frame lerp + { + FVector SmoothedValue = Grip.AdvancedGripSettings.SecondaryGripSettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + + frontLoc = FMath::Lerp(SmoothedValue, frontLoc, Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED); + }*/ +} + +bool UGS_Default::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform& WorldTransform, + const FTransform& ParentTransform, + FBPActorGripInformation& Grip, + AActor* actor, + UPrimitiveComponent* root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!GrippingController) + return false; + + // Just simple transform setting + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + switch (Grip.SecondaryGripInfo.GripLerpState) + { + case EGripLerpState::StartLerp: + case EGripLerpState::EndLerp: + { + if (Grip.SecondaryGripInfo.curLerp > 0.01f) + Grip.SecondaryGripInfo.curLerp -= DeltaTime; + else + { + /*if (Grip.SecondaryGripInfo.bHasSecondaryAttachment && + Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && + Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED < 1.0f) + { + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::ConstantLerp_DEPRECATED; + } + else*/ + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + }break; + //case EGripLerpState::ConstantLerp_DEPRECATED: + case EGripLerpState::NotLerping: + default:break; + } + } + + // Handle the interp and multi grip situations, re-checking the grip situation here as it may have changed in the switch above. + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + FTransform SecondaryTransform = Grip.RelativeTransform * ParentTransform; + + // Checking secondary grip type for the scaling setting + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + + if (bRootHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(root); + else if (bActorHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(actor); + + // If the grip is a custom one, skip all of this logic we won't be changing anything + if (SecondaryType != ESecondaryGripType::SG_Custom) + { + // Variables needed for multi grip transform + FVector BasePoint = ParentTransform.GetLocation(); // Get our pivot point + const FTransform PivotToWorld = FTransform(FQuat::Identity, BasePoint); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -BasePoint); + + FVector frontLocOrig; + FVector frontLoc; + + // Ending lerp out of a multi grip + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + frontLoc = Grip.SecondaryGripInfo.LastRelativeLocation; + + frontLocOrig = FMath::Lerp(frontLoc, frontLocOrig, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + else // Is in a multi grip, might be lerping into it as well. + { + //FVector curLocation; // Current location of the secondary grip + + // Calculates the correct secondary attachment location and sets frontLoc to it + CalculateSecondaryLocation(frontLoc, BasePoint, Grip, GrippingController); + + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + + // Apply any smoothing settings and lerping in / constant lerping + ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime); + + Grip.SecondaryGripInfo.LastRelativeLocation = frontLoc; + } + + // Get any scaling addition from a scaling secondary grip type + FVector Scaler = FVector(1.0f); + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + GetAnyScaling(Scaler, Grip, frontLoc, frontLocOrig, SecondaryType, SecondaryTransform); + } + + Grip.SecondaryGripInfo.SecondaryGripDistance = FVector::Dist(frontLocOrig, frontLoc); + + /*if (Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripDistanceInfluence_DEPRECATED) + { + float rotScaler = 1.0f - FMath::Clamp((Grip.SecondaryGripInfo.SecondaryGripDistance - Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDeadZone_DEPRECATED) / FMath::Max(Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDistanceToZero_DEPRECATED, 1.0f), 0.0f, 1.0f); + frontLoc = FMath::Lerp(frontLocOrig, frontLoc, rotScaler); + }*/ + + // Skip rot val for scaling only + if (SecondaryType != ESecondaryGripType::SG_ScalingOnly) + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(frontLocOrig, frontLoc); + + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + } + return true; +} + +void UGS_Default::CalculateSecondaryLocation(FVector& frontLoc, const FVector& BasePoint, FBPActorGripInformation& Grip, UGripMotionControllerComponent* GrippingController) +{ + bool bPulledControllerLoc = false; + if (UGripMotionControllerComponent* OtherController = Cast<UGripMotionControllerComponent>(Grip.SecondaryGripInfo.SecondaryAttachment)) + { + bool bPulledCurrentTransform = false; + + if (IsValid(OtherController->CustomPivotComponent)) + { + FTransform SecondaryTrans = FTransform::Identity; + SecondaryTrans = OtherController->GetPivotTransform(); + bPulledControllerLoc = true; + frontLoc = SecondaryTrans.GetLocation() - BasePoint; + } + } + + if (!bPulledControllerLoc) + { + frontLoc = Grip.SecondaryGripInfo.SecondaryAttachment->GetComponentLocation() - BasePoint; + } +} + +void UGS_ExtendedDefault::GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform) +{ + if (Grip.SecondaryGripInfo.GripLerpState != EGripLerpState::EndLerp) + { + + //float Scaler = 1.0f; + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + /*Grip.SecondaryScaler*/ Scaler = FVector(frontLoc.Size() / frontLocOrig.Size()); + //bRescalePhysicsGrips = true; // This is for the physics grips + + if (bLimitGripScaling) + { + // Get the total scale after modification + // #TODO: convert back to singular float version? Can get Min() & Max() to convert the float to a range...think about it + FVector WorldScale = /*WorldTransform*/SecondaryTransform.GetScale3D(); + FVector CombinedScale = WorldScale * Scaler; + + // Clamp to the minimum and maximum values + CombinedScale.X = FMath::Clamp(CombinedScale.X, MinimumGripScaling.X, MaximumGripScaling.X); + CombinedScale.Y = FMath::Clamp(CombinedScale.Y, MinimumGripScaling.Y, MaximumGripScaling.Y); + CombinedScale.Z = FMath::Clamp(CombinedScale.Z, MinimumGripScaling.Z, MaximumGripScaling.Z); + + // Recreate in scaler form so that the transform chain below works as normal + Scaler = CombinedScale / WorldScale; + } + //Scaler = Grip.SecondaryScaler; + } + } +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_GunTools.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_GunTools.cpp new file mode 100644 index 0000000..ebe1e92 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_GunTools.cpp @@ -0,0 +1,586 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_GunTools.h" +#include "VRGripInterface.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "IXRTrackingSystem.h" +#include "VRGlobalSettings.h" +#include "VRBaseCharacter.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +//#include "Camera/CameraComponent.h" +#include "ReplicatedVRCameraComponent.h" +#include "DrawDebugHelpers.h" + +UGS_GunTools::UGS_GunTools(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::OverridesWorldTransform; + PivotOffset = FVector::ZeroVector; + VirtualStockComponent = nullptr; + MountWorldTransform = FTransform::Identity; + //StockSnapOffset = FVector(0.f, 0.f, 0.f); + bIsMounted = false; + + + bHasRecoil = false; + bApplyRecoilAsPhysicalForce = false; + MaxRecoilTranslation = FVector::ZeroVector; + MaxRecoilRotation = FVector::ZeroVector; + MaxRecoilScale = FVector(1.f); + bHasActiveRecoil = false; + DecayRate = 20.f; + LerpRate = 30.f; + + BackEndRecoilStorage = FTransform::Identity; + + bUseGlobalVirtualStockSettings = true; + + bUseHighQualityRemoteSimulation = false; + + bInjectPrePhysicsHandle = true; + //bInjectPostPhysicsHandle = true; + WeaponRootOrientationComponent = NAME_None; + OrientationComponentRelativeFacing = FTransform::Identity; + StoredRootOffset = FQuat::Identity; +} + +void UGS_GunTools::OnBeginPlay_Implementation(UObject* CallingOwner) +{ + // Grip base has no super of this + + if (WeaponRootOrientationComponent.IsValid()) + { + if (AActor * Owner = GetOwner()) + { + FName CurrentCompName = NAME_None; + for (UActorComponent* ChildComp : Owner->GetComponents()) + { + CurrentCompName = ChildComp->GetFName(); + if (CurrentCompName == NAME_None) + continue; + + if (CurrentCompName == WeaponRootOrientationComponent) + { + if (USceneComponent * SceneComp = Cast<USceneComponent>(ChildComp)) + { + OrientationComponentRelativeFacing = SceneComp->GetRelativeTransform(); + } + + break; + } + } + } + } +} + +void UGS_GunTools::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) +{ + if (!bIsActive) + return; + + if (WeaponRootOrientationComponent != NAME_None) + { + StoredRootOffset = HandleInfo->RootBoneRotation.GetRotation().Inverse() * OrientationComponentRelativeFacing.GetRotation(); + + // Alter to rotate to x+ if we have an orientation component + FQuat DeltaQuat = OrientationComponentRelativeFacing.GetRotation(); + + KinPose.SetRotation(KinPose.GetRotation() * StoredRootOffset); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation() * StoredRootOffset); + } + else + { + StoredRootOffset = FQuat::Identity; + } + + if (GripInfo.bIsSlotGrip && !PivotOffset.IsZero()) + { + KinPose.SetLocation(KinPose.TransformPosition(PivotOffset)); + HandleInfo->COMPosition.SetLocation(HandleInfo->COMPosition.TransformPosition(PivotOffset)); + } +} + +/*void UGS_GunTools::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) +{ +}*/ + +bool UGS_GunTools::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!GrippingController) + return false; + + bool bSkipHighQualityOperations = !bUseHighQualityRemoteSimulation && !GrippingController->bHasAuthority; + + /*if (!GunViewExtension.IsValid() && GEngine) + { + GunViewExtension = FSceneViewExtensions::NewExtension<FGunViewExtension>(GrippingController); + }*/ + + // Just simple transform setting + if (bHasRecoil && bHasActiveRecoil) + { + BackEndRecoilStorage.Blend(BackEndRecoilStorage, BackEndRecoilTarget, FMath::Clamp(LerpRate * DeltaTime, 0.f, 1.f)); + BackEndRecoilTarget.Blend(BackEndRecoilTarget, FTransform::Identity, FMath::Clamp(DecayRate * DeltaTime, 0.f, 1.f)); + bHasActiveRecoil = !BackEndRecoilTarget.Equals(FTransform::Identity); + + if (!bHasActiveRecoil) + { + BackEndRecoilStorage.SetIdentity(); + BackEndRecoilTarget.SetIdentity(); + } + } + + if (bHasActiveRecoil) + { + // Using a matrix to avoid FTransform inverse math issues + FTransform relTransform(Grip.RelativeTransform.ToInverseMatrixWithScale()); + + // Eventually may want to adjust the pivot of the recoil rotation by the PivotOffset vector... + FVector Pivot = relTransform.GetLocation() + PivotOffset; + const FTransform PivotToWorld = FTransform(FQuat::Identity, Pivot); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -Pivot); + + WorldTransform = WorldToPivot * BackEndRecoilStorage * PivotToWorld * Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + } + else + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) + { + if (!bSkipHighQualityOperations && bUseVirtualStock) + { + if (IsValid(VirtualStockComponent)) + { + FRotator PureYaw = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VirtualStockComponent->GetComponentRotation()); + MountWorldTransform = FTransform(PureYaw.Quaternion(), VirtualStockComponent->GetComponentLocation() + PureYaw.RotateVector(VirtualStockSettings.StockSnapOffset)); + } + else if (GrippingController->bHasAuthority && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + FQuat curRot = FQuat::Identity; + FVector curLoc = FVector::ZeroVector; + + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curLoc)) + { + // Translate hmd offset by the gripping controllers parent component, this should be in the same space + FRotator PureYaw = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curRot.Rotator()); + MountWorldTransform = FTransform(PureYaw.Quaternion(), curLoc + PureYaw.RotateVector(VirtualStockSettings.StockSnapOffset)) * GrippingController->GetAttachParent()->GetComponentTransform(); + } + } + else if(IsValid(CameraComponent)) + { + FRotator PureYaw = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(CameraComponent->GetComponentRotation()); + MountWorldTransform = FTransform(PureYaw.Quaternion(), CameraComponent->GetComponentLocation() + PureYaw.RotateVector(VirtualStockSettings.StockSnapOffset)); + } + + float StockSnapDistance = FMath::Square(VirtualStockSettings.StockSnapDistance); + float DistSquared = FVector::DistSquared(ParentTransform.GetTranslation(), MountWorldTransform.GetTranslation()); + + if (DistSquared <= StockSnapDistance) + { + + float StockSnapLerpThresh = FMath::Square(VirtualStockSettings.StockSnapLerpThreshold); + + if (StockSnapLerpThresh > 0.0f) + VirtualStockSettings.StockLerpValue = 1.0f - FMath::Clamp((DistSquared - (StockSnapDistance - StockSnapLerpThresh)) / StockSnapLerpThresh, 0.0f, 1.0f); + else + VirtualStockSettings.StockLerpValue = 1.0f; // Just skip lerping logic + + if (!bIsMounted) + { + VirtualStockSettings.StockHandSmoothing.ResetSmoothingFilter(); + + // Mount up + bIsMounted = true; + OnVirtualStockModeChanged.Broadcast(bIsMounted); + } + + // Adjust the mount location to follow the Z of the primary hand + if (VirtualStockSettings.bAdjustZOfStockToPrimaryHand) + { + FVector WorldTransVec = MountWorldTransform.GetTranslation(); + + if (WorldTransVec.Z >= ParentTransform.GetTranslation().Z) + { + WorldTransVec.Z = ParentTransform.GetTranslation().Z; + MountWorldTransform.SetLocation(WorldTransVec); + } + } + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (VirtualStockSettings.bDebugDrawVirtualStock) + { + DrawDebugLine(GetWorld(), ParentTransform.GetTranslation(), MountWorldTransform.GetTranslation(), FColor::Red); + DrawDebugLine(GetWorld(), Grip.SecondaryGripInfo.SecondaryAttachment->GetComponentLocation(), MountWorldTransform.GetTranslation(), FColor::Green); + DrawDebugSphere(GetWorld(), MountWorldTransform.GetTranslation(), 10.f, 32, FColor::White); + } +#endif + } + else + { + if (bIsMounted) + { + bIsMounted = false; + VirtualStockSettings.StockLerpValue = 0.0f; + OnVirtualStockModeChanged.Broadcast(bIsMounted); + } + } + } + } + else + { + if (bIsMounted) + { + bIsMounted = false; + VirtualStockSettings.StockLerpValue = 0.0f; + OnVirtualStockModeChanged.Broadcast(bIsMounted); + } + } + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + switch (Grip.SecondaryGripInfo.GripLerpState) + { + case EGripLerpState::StartLerp: + case EGripLerpState::EndLerp: + { + if (Grip.SecondaryGripInfo.curLerp > 0.01f) + Grip.SecondaryGripInfo.curLerp -= DeltaTime; + else + { + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + }break; + //case EGripLerpState::ConstantLerp_DEPRECATED: + case EGripLerpState::NotLerping: + default:break; + } + } + + // Handle the interp and multi grip situations, re-checking the grip situation here as it may have changed in the switch above. + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + FTransform NewWorldTransform = WorldTransform; + FTransform SecondaryTransform = Grip.RelativeTransform * ParentTransform; + + // Checking secondary grip type for the scaling setting + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + + if (bRootHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(root); + else if (bActorHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(actor); + + // If the grip is a custom one, skip all of this logic we won't be changing anything + if (SecondaryType != ESecondaryGripType::SG_Custom) + { + // Variables needed for multi grip transform + FVector BasePoint = ParentTransform.GetLocation(); + FVector Pivot = ParentTransform.GetLocation(); + + if (Grip.bIsSlotGrip) + { + if (FBPActorPhysicsHandleInformation * PhysHandle = GrippingController->GetPhysicsGrip(Grip)) + { + Pivot = SecondaryTransform.TransformPositionNoScale(SecondaryTransform.InverseTransformPositionNoScale(Pivot) + (StoredRootOffset * PhysHandle->RootBoneRotation.GetRotation()).RotateVector(PivotOffset)); + } + else + { + Pivot = SecondaryTransform.TransformPositionNoScale(SecondaryTransform.InverseTransformPositionNoScale(Pivot) + OrientationComponentRelativeFacing.GetRotation().RotateVector(PivotOffset)); + } + } + + // Debug draw for COM movement with physics grips +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + static const auto CVarDrawCOMDebugSpheresAccess = IConsoleManager::Get().FindConsoleVariable(TEXT("vr.DrawDebugCenterOfMassForGrips")); + if (CVarDrawCOMDebugSpheresAccess->GetInt() > 0) + { + DrawDebugSphere(GetWorld(), Pivot, 5, 32, FColor::Orange, false); + } +#endif + + const FTransform PivotToWorld = FTransform(FQuat::Identity, Pivot);//BasePoint); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -Pivot);//-BasePoint); + + FVector frontLocOrig; + FVector frontLoc; + + // Ending lerp out of a multi grip + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + WorldTransform.Blend(WorldTransform, RelativeTransOnSecondaryRelease* GrippingController->GetPivotTransform(), FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + return true; + } + else // Is in a multi grip, might be lerping into it as well. + { + //FVector curLocation; // Current location of the secondary grip + + // Calculates the correct secondary attachment location and sets frontLoc to it + CalculateSecondaryLocation(frontLoc, BasePoint, Grip, GrippingController); + + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + + // Apply any smoothing settings and lerping in / constant lerping + GunTools_ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime, bSkipHighQualityOperations); + + Grip.SecondaryGripInfo.LastRelativeLocation = frontLoc; + } + + // Get any scaling addition from a scaling secondary grip type + FVector Scaler = FVector(1.0f); + GetAnyScaling(Scaler, Grip, frontLoc, frontLocOrig, SecondaryType, SecondaryTransform); + + Grip.SecondaryGripInfo.SecondaryGripDistance = FVector::Dist(frontLocOrig, frontLoc); + + if (!bSkipHighQualityOperations && AdvSecondarySettings.bUseAdvancedSecondarySettings && AdvSecondarySettings.bUseSecondaryGripDistanceInfluence) + { + float rotScaler = 1.0f - FMath::Clamp((Grip.SecondaryGripInfo.SecondaryGripDistance - AdvSecondarySettings.GripInfluenceDeadZone) / FMath::Max(AdvSecondarySettings.GripInfluenceDistanceToZero, 1.0f), 0.0f, 1.0f); + frontLoc = FMath::Lerp(frontLocOrig, frontLoc, rotScaler); + } + + // Skip rot val for scaling only + if (SecondaryType != ESecondaryGripType::SG_ScalingOnly) + { + // Get shoulder mount addition rotation + if (!bSkipHighQualityOperations && bUseVirtualStock && bIsMounted) + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(GrippingController->GetPivotLocation() - MountWorldTransform.GetTranslation(), (frontLoc + BasePoint) - MountWorldTransform.GetTranslation()); + FQuat MountAdditionRotation = FQuat::FindBetweenVectors(frontLocOrig, GrippingController->GetPivotLocation() - MountWorldTransform.GetTranslation()); + + if (VirtualStockSettings.StockLerpValue < 1.0f) + { + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + FTransform NA = FTransform(rotVal * MountAdditionRotation, FVector::ZeroVector, Scaler); + FTransform NB = FTransform(FQuat::FindBetweenVectors(frontLocOrig, frontLoc), FVector::ZeroVector, Scaler); + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + NA.Blend(NB, NA, VirtualStockSettings.StockLerpValue); + + NewWorldTransform = WorldTransform * WorldToPivot * NA * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * MountAdditionRotation * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + else + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(frontLocOrig, frontLoc); + + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + else + { + + // Get shoulder mount addition rotation + if (!bSkipHighQualityOperations && bUseVirtualStock && bIsMounted) + { + FQuat MountAdditionRotation = FQuat::FindBetweenVectors(frontLocOrig, GrippingController->GetPivotLocation() - MountWorldTransform.GetTranslation()); + + // If it is exactly 1.0f then lets skip all of the extra logic and just set it + if (VirtualStockSettings.StockLerpValue < 1.0f) + { + FTransform NA = FTransform(MountAdditionRotation, FVector::ZeroVector, Scaler); + FTransform NB = FTransform(FQuat::Identity, FVector::ZeroVector, Scaler); + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + NA.Blend(NB, NA, VirtualStockSettings.StockLerpValue); + NewWorldTransform = WorldTransform * WorldToPivot * NA * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * MountAdditionRotation * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + NewWorldTransform = WorldTransform * WorldToPivot * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + } + + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) + { + WorldTransform.Blend(NewWorldTransform, WorldTransform, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + else + { + WorldTransform = NewWorldTransform; + } + + if (bIsMounted && VirtualStockSettings.bSmoothStockHand) + { + if (GrippingController->GetAttachParent()) + { + FTransform ParentTrans = GrippingController->GetAttachParent()->GetComponentTransform(); + FTransform ParentRel = WorldTransform * ParentTrans.Inverse(); + ParentRel.Blend(ParentRel, VirtualStockSettings.StockHandSmoothing.RunFilterSmoothing(ParentRel, DeltaTime), VirtualStockSettings.SmoothingValueForStock); + WorldTransform = ParentRel * ParentTrans; + } + } + + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + RelativeTransOnSecondaryRelease = WorldTransform.GetRelativeTransform(GrippingController->GetPivotTransform()); + } + } + + return true; +} + +void UGS_GunTools::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + + if (bUseGlobalVirtualStockSettings) + { + if (GrippingController->IsLocallyControlled()) + { + FBPVirtualStockSettings VirtualSettings; + UVRGlobalSettings::GetVirtualStockGlobalSettings(VirtualSettings); + VirtualStockSettings.CopyFrom(VirtualSettings); + } + } + + // Super doesn't do anything on grip + + // Reset smoothing filters + if (AdvSecondarySettings.bUseConstantGripScaler) + { + if (AdvSecondarySettings.bUseGlobalSmoothingSettings) + { + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + AdvSecondarySettings.SecondarySmoothing.CutoffSlope = VRSettings.OneEuroCutoffSlope; + AdvSecondarySettings.SecondarySmoothing.DeltaCutoff = VRSettings.OneEuroDeltaCutoff; + AdvSecondarySettings.SecondarySmoothing.MinCutoff = VRSettings.OneEuroMinCutoff; + } + + AdvSecondarySettings.SecondarySmoothing.ResetSmoothingFilter(); + } + + if (bUseVirtualStock) + { + ResetStockVariables(); + } + + GetVirtualStockTarget(GrippingController); +} + +void UGS_GunTools::GetVirtualStockTarget(UGripMotionControllerComponent * GrippingController) +{ + if (GrippingController && (GrippingController->bHasAuthority || bUseHighQualityRemoteSimulation)) + { + if (AVRBaseCharacter * vrOwner = Cast<AVRBaseCharacter>(GrippingController->GetOwner())) + { + CameraComponent = vrOwner->VRReplicatedCamera; + return; + } + else + { + TArray<USceneComponent*> children = GrippingController->GetOwner()->GetRootComponent()->GetAttachChildren(); + + for (int i = 0; i < children.Num(); i++) + { + if (children[i]->IsA(UCameraComponent::StaticClass())) + { + CameraComponent = children[i]; + return; + } + } + } + + CameraComponent = nullptr; + } +} + +void UGS_GunTools::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) +{ + // Super doesn't do anything on Secondary grip + + // Reset smoothing filters + if (AdvSecondarySettings.bUseConstantGripScaler) + { + if (AdvSecondarySettings.bUseGlobalSmoothingSettings) + { + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + AdvSecondarySettings.SecondarySmoothing.CutoffSlope = VRSettings.OneEuroCutoffSlope; + AdvSecondarySettings.SecondarySmoothing.DeltaCutoff = VRSettings.OneEuroDeltaCutoff; + AdvSecondarySettings.SecondarySmoothing.MinCutoff = VRSettings.OneEuroMinCutoff; + } + + AdvSecondarySettings.SecondarySmoothing.ResetSmoothingFilter(); + } + + if (bUseVirtualStock) + ResetStockVariables(); +} + +void UGS_GunTools::ResetRecoil() +{ + BackEndRecoilStorage = FTransform::Identity; + BackEndRecoilTarget = FTransform::Identity; +} + +void UGS_GunTools::AddRecoilInstance(const FTransform & RecoilAddition, FVector Optional_Location) +{ + if (!bHasRecoil) + return; + + if (bApplyRecoilAsPhysicalForce) + { + if (FBodyInstance * BodyInst = GetParentBodyInstance()) + { + BodyInst->AddImpulseAtPosition(RecoilAddition.GetLocation(), Optional_Location); + } + } + else + { + BackEndRecoilTarget += RecoilAddition; + + FVector CurVec = BackEndRecoilTarget.GetTranslation(); + CurVec.X = FMath::Clamp(CurVec.X, -FMath::Abs(MaxRecoilTranslation.X), FMath::Abs(MaxRecoilTranslation.X)); + CurVec.Y = FMath::Clamp(CurVec.Y, -FMath::Abs(MaxRecoilTranslation.Y), FMath::Abs(MaxRecoilTranslation.Y)); + CurVec.Z = FMath::Clamp(CurVec.Z, -FMath::Abs(MaxRecoilTranslation.Z), FMath::Abs(MaxRecoilTranslation.Z)); + BackEndRecoilTarget.SetTranslation(CurVec); + + FVector CurScale = BackEndRecoilTarget.GetScale3D(); + CurScale.X = FMath::Clamp(CurScale.X, -FMath::Abs(MaxRecoilScale.X), FMath::Abs(MaxRecoilScale.X)); + CurScale.Y = FMath::Clamp(CurScale.Y, -FMath::Abs(MaxRecoilScale.Y), FMath::Abs(MaxRecoilScale.Y)); + CurScale.Z = FMath::Clamp(CurScale.Z, -FMath::Abs(MaxRecoilScale.Z), FMath::Abs(MaxRecoilScale.Z)); + BackEndRecoilTarget.SetScale3D(CurScale); + + FRotator curRot = BackEndRecoilTarget.Rotator(); + curRot.Pitch = FMath::Clamp(curRot.Pitch, -FMath::Abs(MaxRecoilRotation.Y), FMath::Abs(MaxRecoilRotation.Y)); + curRot.Yaw = FMath::Clamp(curRot.Yaw, -FMath::Abs(MaxRecoilRotation.Z), FMath::Abs(MaxRecoilRotation.Z)); + curRot.Roll = FMath::Clamp(curRot.Roll, -FMath::Abs(MaxRecoilRotation.X), FMath::Abs(MaxRecoilRotation.X)); + BackEndRecoilTarget.SetRotation(curRot.Quaternion()); + + bHasActiveRecoil = !BackEndRecoilTarget.Equals(FTransform::Identity); + } +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_InteractibleSettings.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_InteractibleSettings.cpp new file mode 100644 index 0000000..ccf5399 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_InteractibleSettings.cpp @@ -0,0 +1,134 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_InteractibleSettings.h" +#include "Components/PrimitiveComponent.h" +#include "GripMotionControllerComponent.h" +#include "GameFramework/Actor.h" + +UGS_InteractibleSettings::UGS_InteractibleSettings(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::OverridesWorldTransform; +} + +void UGS_InteractibleSettings::OnBeginPlay_Implementation(UObject * CallingOwner) +{ + if (InteractionSettings.bGetInitialPositionsOnBeginPlay) + { + FTransform parentTrans = GetParentTransform(!InteractionSettings.bLimitsInLocalSpace); + + InteractionSettings.InitialAngularTranslation = parentTrans.Rotator(); + InteractionSettings.InitialLinearTranslation = parentTrans.GetTranslation(); + } +} +void UGS_InteractibleSettings::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + if (InteractionSettings.bIgnoreHandRotation && !InteractionSettings.bHasValidBaseTransform) + { + RemoveRelativeRotation(GrippingController, GripInformation); + } + +} + +void UGS_InteractibleSettings::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + InteractionSettings.bHasValidBaseTransform = false; +} + +void UGS_InteractibleSettings::RemoveRelativeRotation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + InteractionSettings.BaseTransform = GripInformation.RelativeTransform; + + // Reconstitute the controller transform relative to the object, then remove the rotation and set it back to relative to controller + // This could likely be done easier by just removing rotation that the object doesn't possess but for now this will do. + FTransform compTrans = this->GetParentTransform(true, GripInformation.GrippedBoneName); + + InteractionSettings.BaseTransform = FTransform(InteractionSettings.BaseTransform.ToInverseMatrixWithScale()) * compTrans; // Reconstitute transform + InteractionSettings.BaseTransform.SetScale3D(GrippingController->GetPivotTransform().GetScale3D()); + InteractionSettings.BaseTransform.SetRotation(FQuat::Identity); // Remove rotation + + InteractionSettings.BaseTransform = compTrans.GetRelativeTransform(InteractionSettings.BaseTransform); // Set back to relative + InteractionSettings.bHasValidBaseTransform = true; +} + +bool UGS_InteractibleSettings::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!root) + return false; + + FTransform LocalTransform; + + if (InteractionSettings.bIgnoreHandRotation) + { + if (!InteractionSettings.bHasValidBaseTransform) + { + // Removes the rotation portion of the relative grip transform + RemoveRelativeRotation(GrippingController, Grip); + } + + FTransform RotationalessTransform = ParentTransform; + RotationalessTransform.SetRotation(FQuat::Identity); + + WorldTransform = InteractionSettings.BaseTransform * Grip.AdditionTransform * RotationalessTransform; + } + else + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + + if (InteractionSettings.bLimitsInLocalSpace) + { + if (USceneComponent * parent = root->GetAttachParent()) + LocalTransform = parent->GetComponentTransform(); + else + LocalTransform = FTransform::Identity; + + WorldTransform = WorldTransform.GetRelativeTransform(LocalTransform); + } + + FVector componentLoc = WorldTransform.GetLocation(); + + // Translation settings + if (InteractionSettings.bLimitX) + componentLoc.X = FMath::Clamp(componentLoc.X, InteractionSettings.InitialLinearTranslation.X + InteractionSettings.MinLinearTranslation.X, InteractionSettings.InitialLinearTranslation.X + InteractionSettings.MaxLinearTranslation.X); + + if (InteractionSettings.bLimitY) + componentLoc.Y = FMath::Clamp(componentLoc.Y, InteractionSettings.InitialLinearTranslation.Y + InteractionSettings.MinLinearTranslation.Y, InteractionSettings.InitialLinearTranslation.Y + InteractionSettings.MaxLinearTranslation.Y); + + if (InteractionSettings.bLimitZ) + componentLoc.Z = FMath::Clamp(componentLoc.Z, InteractionSettings.InitialLinearTranslation.Z + InteractionSettings.MinLinearTranslation.Z, InteractionSettings.InitialLinearTranslation.Z + InteractionSettings.MaxLinearTranslation.Z); + + WorldTransform.SetLocation(componentLoc); + + FRotator componentRot = WorldTransform.GetRotation().Rotator(); + + // Rotation Settings + if (InteractionSettings.bLimitPitch) + componentRot.Pitch = FMath::Clamp(componentRot.Pitch, InteractionSettings.InitialAngularTranslation.Pitch + InteractionSettings.MinAngularTranslation.Pitch, InteractionSettings.InitialAngularTranslation.Pitch + InteractionSettings.MaxAngularTranslation.Pitch); + + if (InteractionSettings.bLimitYaw) + componentRot.Yaw = FMath::Clamp(componentRot.Yaw, InteractionSettings.InitialAngularTranslation.Yaw + InteractionSettings.MinAngularTranslation.Yaw, InteractionSettings.InitialAngularTranslation.Yaw + InteractionSettings.MaxAngularTranslation.Yaw); + + if (InteractionSettings.bLimitRoll) + componentRot.Roll = FMath::Clamp(componentRot.Roll, InteractionSettings.InitialAngularTranslation.Roll + InteractionSettings.MinAngularTranslation.Roll, InteractionSettings.InitialAngularTranslation.Roll + InteractionSettings.MaxAngularTranslation.Roll); + + WorldTransform.SetRotation(componentRot.Quaternion()); + + if (InteractionSettings.bLimitsInLocalSpace) + { + WorldTransform = WorldTransform * LocalTransform; + } + + return true; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_LerpToHand.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_LerpToHand.cpp new file mode 100644 index 0000000..6af8aee --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_LerpToHand.cpp @@ -0,0 +1,191 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "GripScripts/GS_LerpToHand.h" +#include "GripMotionControllerComponent.h" +#include "VRGlobalSettings.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "Math/DualQuat.h" + +UGS_LerpToHand::UGS_LerpToHand(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = false; + bDenyAutoDrop = true; // Always deny auto dropping while this script is active + WorldTransformOverrideType = EGSTransformOverrideType::ModifiesWorldTransform; + + LerpInterpolationMode = EVRLerpInterpolationMode::QuatInterp; + LerpDuration = 1.f; + LerpSpeed = 0.0f; + CurrentLerpTime = 0.0f; + OnGripTransform = FTransform::Identity; + bUseCurve = false; + MinDistanceForLerp = 0.0f; + MinSpeedForLerp = 0.f; + MaxSpeedForLerp = 0.f; + TargetGrip = INVALID_VRGRIP_ID; +} + +//void UGS_InteractibleSettings::BeginPlay_Implementation() {} +void UGS_LerpToHand::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + // Removed this, let per object scripts overide + // Dont run if the global lerping is enabled + /*if (VRSettings.bUseGlobalLerpToHand) + { + bIsActive = false; + return; + }*/ + + TargetGrip = GripInformation.GripID; + + OnGripTransform = GetParentTransform(true, GripInformation.GrippedBoneName); + UObject* ParentObj = this->GetParent(); + + FTransform TargetTransform = GripInformation.RelativeTransform * GrippingController->GetPivotTransform(); + float Distance = FVector::Dist(OnGripTransform.GetLocation(), TargetTransform.GetLocation()); + if (MinDistanceForLerp > 0.0f && Distance < MinDistanceForLerp) + { + // Don't init + OnLerpToHandFinished.Broadcast(); + return; + } + else + { + float LerpScaler = 1.0f; + float DistanceToSpeed = Distance / LerpDuration; + if (DistanceToSpeed < MinSpeedForLerp) + { + LerpScaler = MinSpeedForLerp / DistanceToSpeed; + } + else if (MaxSpeedForLerp > 0.f && DistanceToSpeed > MaxSpeedForLerp) + { + LerpScaler = MaxSpeedForLerp / DistanceToSpeed; + } + else + { + LerpScaler = 1.0f; + } + + // Get the modified lerp speed + LerpSpeed = ((1.f / LerpDuration) * LerpScaler); + + OnLerpToHandBegin.Broadcast(); + + if (FBPActorGripInformation* GripInfo = GrippingController->GetGripPtrByID(GripInformation.GripID)) + { + GripInfo->bIsLerping = true; + } + } + + + + bIsActive = true; + CurrentLerpTime = 0.0f; +} + +void UGS_LerpToHand::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + if(GripInformation.GripID == TargetGrip) + { + TargetGrip = INVALID_VRGRIP_ID; + bIsActive = false; + } +} + +bool UGS_LerpToHand::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!root) + return false; + + if (LerpDuration <= 0.f || !Grip.bIsLerping) + { + Grip.bIsLerping = false; + GrippingController->OnLerpToHandFinished.Broadcast(Grip); + bIsActive = false; + } + + FTransform NA = OnGripTransform;//root->GetComponentTransform(); + + float Alpha = 0.0f; + + CurrentLerpTime += DeltaTime * LerpSpeed; + float OrigAlpha = FMath::Clamp(CurrentLerpTime, 0.f, 1.0f); + Alpha = OrigAlpha; + + if (bUseCurve) + { + if (FRichCurve * richCurve = OptionalCurveToFollow.GetRichCurve()) + { + /*if (CurrentLerpTime > richCurve->GetLastKey().Time) + { + // Stop lerping + OnLerpToHandFinished.Broadcast(); + CurrentLerpTime = 0.0f; + bIsActive = false; + return true; + } + else*/ + { + Alpha = FMath::Clamp(richCurve->Eval(Alpha), 0.f, 1.f); + //CurrentLerpTime += DeltaTime; + } + } + } + + FTransform NB = WorldTransform; + NA.NormalizeRotation(); + NB.NormalizeRotation(); + + // Quaternion interpolation + if (LerpInterpolationMode == EVRLerpInterpolationMode::QuatInterp) + { + WorldTransform.Blend(NA, NB, Alpha); + } + + // Euler Angle interpolation + else if (LerpInterpolationMode == EVRLerpInterpolationMode::EulerInterp) + { + WorldTransform.SetTranslation(FMath::Lerp(NA.GetTranslation(), NB.GetTranslation(), Alpha)); + WorldTransform.SetScale3D(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + + FRotator A = NA.Rotator(); + FRotator B = NB.Rotator(); + WorldTransform.SetRotation(FQuat(A + (Alpha * (B - A)))); + } + // Dual quaternion interpolation + else + { + if ((NB.GetRotation() | NA.GetRotation()) < 0.0f) + { + NB.SetRotation(NB.GetRotation()*-1.0f); + } + WorldTransform = (FDualQuat(NA)*(1 - Alpha) + FDualQuat(NB)*Alpha).Normalized().AsFTransform(FMath::Lerp(NA.GetScale3D(), NB.GetScale3D(), Alpha)); + } + + // Turn it off if we need to + if (OrigAlpha == 1.0f) + { + OnLerpToHandFinished.Broadcast(); + Grip.bIsLerping = false; + GrippingController->OnLerpToHandFinished.Broadcast(Grip); + CurrentLerpTime = 0.0f; + bIsActive = false; + } + + return true; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Melee.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Melee.cpp new file mode 100644 index 0000000..c66fecb --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Melee.cpp @@ -0,0 +1,964 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_Melee.h" +#include "VRGripInterface.h" +#include "GameFramework/WorldSettings.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "PhysicsEngine/PhysicsConstraintActor.h" +#include "PhysicsEngine/PhysicsConstraintComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripMotionControllerComponent.h" +#include "VRGlobalSettings.h" +#include "DrawDebugHelpers.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "GripMotionControllerComponent.h" + +UGS_Melee::UGS_Melee(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::ModifiesWorldTransform; + bDenyLateUpdates = true; + + + bInjectPrePhysicsHandle = true; + bInjectPostPhysicsHandle = true; + WeaponRootOrientationComponent = NAME_None; + OrientationComponentRelativeFacing = FTransform::Identity; + + bAutoSetPrimaryAndSecondaryHands = true; + PrimaryHandSelectionType = EVRMeleePrimaryHandType::VRPHAND_Rear; + bHasValidPrimaryHand = false; + + //RollingVelocityAverage = FVector::ZeroVector; + bIsLodged = false; + + //bCanEverTick = true; + bCheckLodge = false; + bIsHeld = false; + bCanEverTick = false; + bAlwaysTickPenetration = false; + COMType = EVRMeleeComType::VRPMELEECOM_BetweenHands; + bOnlyPenetrateWithTwoHands = false; +} + +void UGS_Melee::UpdateDualHandInfo() +{ + TArray<FBPGripPair> HoldingControllers; + + bool bIsHeldOther; + IVRGripInterface::Execute_IsHeld(GetParent(), HoldingControllers, bIsHeldOther); + + float PHand = 0.0f; + float SHand = 0.0f; + bHasValidPrimaryHand = false; + + FBPActorGripInformation* FrontHandGrip = nullptr; + FBPActorGripInformation* RearHandGrip = nullptr; + + SecondaryHand = FBPGripPair(); + PrimaryHand = FBPGripPair(); + + int NumControllers = HoldingControllers.Num(); + + for (FBPGripPair& Grip : HoldingControllers) + { + if (NumControllers > 1) + { + FBPActorGripInformation* GripInfo = Grip.HoldingController->GetGripPtrByID(Grip.GripID); + if (GripInfo) + { + float GripDistanceOnPrimaryAxis = 0.f; + FTransform relTransform(GripInfo->RelativeTransform.ToInverseMatrixWithScale()); + relTransform = relTransform.GetRelativeTransform(OrientationComponentRelativeFacing); + + // This is the Forward vector projected transform + // The most negative one of these is the rearmost hand + FVector localLoc = relTransform.GetTranslation(); + + switch (PrimaryHandSelectionType) + { + case EVRMeleePrimaryHandType::VRPHAND_Slotted: + { + if (GripInfo->bIsSlotGrip) + { + PrimaryHand = Grip; + bHasValidPrimaryHand = true; + } + else + { + if (!PrimaryHand.IsValid()) + { + PrimaryHand = Grip; + } + + SecondaryHand = Grip; + } + }break; + case EVRMeleePrimaryHandType::VRPHAND_Front: + case EVRMeleePrimaryHandType::VRPHAND_Rear: + { + + if (((PrimaryHandSelectionType == EVRMeleePrimaryHandType::VRPHAND_Rear) ? localLoc.X < PHand : localLoc.X > PHand) || !PrimaryHand.HoldingController) + { + PrimaryHand = Grip; + PHand = localLoc.X; + bHasValidPrimaryHand = true; + } + + if ((((PrimaryHandSelectionType == EVRMeleePrimaryHandType::VRPHAND_Rear) ? localLoc.X > SHand : localLoc.X < SHand) || !SecondaryHand.HoldingController || SecondaryHand.HoldingController == PrimaryHand.HoldingController)) + { + SecondaryHand = Grip; + SHand = localLoc.X; + } + + }break; + default:break; + } + } + } + else + { + PrimaryHand = Grip; + SecondaryHand = FBPGripPair(); + } + } + + + if (PrimaryHand.IsValid() && (COMType == EVRMeleeComType::VRPMELEECOM_BetweenHands || COMType == EVRMeleeComType::VRPMELEECOM_PrimaryHand)) + { + FBPActorGripInformation* GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + if (SecondaryHand.IsValid()) + { + FBPActorGripInformation* GripInfoS = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + + if (GripInfo && GripInfoS) + { + FVector Primary = GripInfo->RelativeTransform.InverseTransformPositionNoScale(FVector::ZeroVector); + FVector Secondary = GripInfoS->RelativeTransform.InverseTransformPositionNoScale(FVector::ZeroVector); + + FVector Final = (COMType == EVRMeleeComType::VRPMELEECOM_PrimaryHand) ? Primary : ((Primary + Secondary) / 2.f); + ObjectRelativeGripCenter.SetLocation(Final); + } + } + else + { + if (GripInfo) + { + + if (GripInfo->SecondaryGripInfo.bHasSecondaryAttachment) + { + FVector gripLoc = GripInfo->RelativeTransform.InverseTransformPositionNoScale(FVector::ZeroVector); + FVector secGripLoc = GripInfo->SecondaryGripInfo.SecondaryRelativeTransform.GetLocation(); + FVector finalloc = (COMType == EVRMeleeComType::VRPMELEECOM_PrimaryHand) ? gripLoc : (gripLoc + secGripLoc) / 2.f; + FVector finalScaled = finalloc * GripInfo->RelativeTransform.GetScale3D(); + + FTransform ownerTrans = GetOwner()->GetActorTransform(); + + //DrawDebugSphere(GetWorld(), ownerTrans.TransformPosition(finalScaled), 4.0f, 32, FColor::Orange, true); + + + ObjectRelativeGripCenter.SetLocation(finalScaled); + PrimaryHand.HoldingController->ReCreateGrip(*GripInfo); + } + else + { + ObjectRelativeGripCenter = FTransform::Identity; + } + } + } + } +} + +void UGS_Melee::UpdateHandPositionAndRotation(FBPGripPair HandPair, FTransform HandWorldTransform, FVector& LocDifference, float& RotDifference, bool bUpdateLocation, bool bUpdateRotation) +{ + LocDifference = FVector::ZeroVector; + + if (HandPair.IsValid()) + { + FBPActorGripInformation* GripInfo = HandPair.HoldingController->GetGripPtrByID(HandPair.GripID); + + if (GripInfo) + { + // Make hand relative to object transform + FTransform RelativeTrans = GripInfo->RelativeTransform.Inverse(); + FVector OriginalLoc = RelativeTrans.GetLocation(); + FQuat OriginalRot = RelativeTrans.GetRotation(); + + // Get our current parent transform + FTransform ParentTransform = GetParentTransform(); + + FQuat orientationRot = OrientationComponentRelativeFacing.GetRotation(); + + if (bUpdateLocation) + { + FVector currentRelVec = orientationRot.RotateVector(ParentTransform.InverseTransformPosition(HandWorldTransform.GetLocation())); + FVector currentLoc = orientationRot.RotateVector(RelativeTrans.GetLocation()); + currentLoc.X = currentRelVec.X; + RelativeTrans.SetLocation(orientationRot.UnrotateVector(currentLoc)); + } + + if (bUpdateRotation) + { + FRotator currentRelRot = (orientationRot * (ParentTransform.GetRotation().Inverse() * HandWorldTransform.GetRotation())).Rotator(); + FRotator currentRot = (orientationRot * RelativeTrans.GetRotation()).Rotator(); + currentRot.Roll = currentRelRot.Roll; + RelativeTrans.SetRotation(orientationRot.Inverse() * currentRot.Quaternion()); + } + + GripInfo->RelativeTransform = RelativeTrans.Inverse(); + HandPair.HoldingController->UpdatePhysicsHandle(*GripInfo, true); + HandPair.HoldingController->NotifyGripTransformChanged(*GripInfo); + + LocDifference = RelativeTrans.GetLocation() - OriginalLoc; + RotDifference = RelativeTrans.GetRotation().Rotator().Roll - OriginalRot.Rotator().Roll; + + // Instead of recreating, can directly set local pose here + + + FBPGripPair SecHand = SecondaryHand; + UpdateDualHandInfo(); + + if (SecondaryHand.IsValid() && !(SecHand == SecondaryHand)) + { + + GripInfo = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + + FBPActorPhysicsHandleInformation* HandleInfo = SecondaryHand.HoldingController->GetPhysicsGrip(SecondaryHand.GripID); + if (HandleInfo) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + SecondaryHand.HoldingController->UpdatePhysicsHandle(SecondaryHand.GripID, true); + } + + GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + switch (COMType) + { + case EVRMeleeComType::VRPMELEECOM_Normal: + case EVRMeleeComType::VRPMELEECOM_BetweenHands: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + }break; + + case EVRMeleeComType::VRPMELEECOM_PrimaryHand: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal) + SetComBetweenHands(HandPair.HoldingController, HandPair.HoldingController->GetPhysicsGrip(HandPair.GripID)); + } + } +} + +void UGS_Melee::UpdateHandPosition(FBPGripPair HandPair, FVector HandWorldPosition, FVector& LocDifference) +{ + LocDifference = FVector::ZeroVector; + + if (HandPair.IsValid()) + { + FBPActorGripInformation* GripInfo = HandPair.HoldingController->GetGripPtrByID(HandPair.GripID); + + if (GripInfo) + { + // Make hand relative to object transform + FTransform RelativeTrans = GripInfo->RelativeTransform.Inverse(); + FVector OriginalLoc = RelativeTrans.GetLocation(); + + // Get our current parent transform + FTransform ParentTransform = GetParentTransform(); + + FQuat orientationRot = OrientationComponentRelativeFacing.GetRotation(); + FVector currentRelVec = orientationRot.RotateVector(ParentTransform.InverseTransformPosition(HandWorldPosition)); + //currentRelVec = OrientationComponentRelativeFacing.GetRotation().UnrotateVector(currentRelVec); + + FVector currentLoc = orientationRot.RotateVector(RelativeTrans.GetLocation()); + currentLoc.X = currentRelVec.X; + + RelativeTrans.SetLocation(orientationRot.UnrotateVector(currentLoc)); + GripInfo->RelativeTransform = RelativeTrans.Inverse(); + HandPair.HoldingController->UpdatePhysicsHandle(*GripInfo, true); + HandPair.HoldingController->NotifyGripTransformChanged(*GripInfo); + + LocDifference = RelativeTrans.GetLocation() - OriginalLoc; + + // Instead of recreating, can directly set local pose here + + + FBPGripPair SecHand = SecondaryHand; + UpdateDualHandInfo(); + + if (SecondaryHand.IsValid() && !(SecHand == SecondaryHand)) + { + + GripInfo = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + + FBPActorPhysicsHandleInformation* HandleInfo = SecondaryHand.HoldingController->GetPhysicsGrip(SecondaryHand.GripID); + if (HandleInfo) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + SecondaryHand.HoldingController->UpdatePhysicsHandle(SecondaryHand.GripID, true); + } + + GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + switch (COMType) + { + case EVRMeleeComType::VRPMELEECOM_Normal: + case EVRMeleeComType::VRPMELEECOM_BetweenHands: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + }break; + + case EVRMeleeComType::VRPMELEECOM_PrimaryHand: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal) + SetComBetweenHands(HandPair.HoldingController, HandPair.HoldingController->GetPhysicsGrip(HandPair.GripID)); + } + } +} + +void UGS_Melee::SetPrimaryAndSecondaryHands(FBPGripPair& PrimaryGrip, FBPGripPair& SecondaryGrip) +{ + PrimaryHand = PrimaryGrip; + SecondaryHand = SecondaryGrip; +} + +void UGS_Melee::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* Controller, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) +{ + if (!bIsActive) + return; + + UpdateDualHandInfo(); +} + +void UGS_Melee::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + if (!bIsActive) + return; + + // Not storing an id, we should only be doing this once +// GetOwner()->OnActorHit.AddDynamic(this, &UGS_Melee::OnActorHit); + + // This lets us change the grip settings prior to actually starting the grip off + //SetTickEnabled(true); + //bCheckLodge = true; + bIsHeld = true; + + //if (GrippingController->HasGripAuthority(GripInformation)) + { + UpdateDualHandInfo(); + + // If we have multiple hands then alter the grip settings here for what we have already, the other will wait until post event + if (SecondaryHand.IsValid()) + { + FBPActorGripInformation * GripInfo = SecondaryHand.HoldingController->GetGripPtrByID(SecondaryHand.GripID); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + + FBPActorPhysicsHandleInformation* HandleInfo = SecondaryHand.HoldingController->GetPhysicsGrip(SecondaryHand.GripID); + if (HandleInfo) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + SecondaryHand.HoldingController->UpdatePhysicsHandle(SecondaryHand.GripID, true); + } + + GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + switch (COMType) + { + case EVRMeleeComType::VRPMELEECOM_Normal: + case EVRMeleeComType::VRPMELEECOM_BetweenHands: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_GripAtControllerLoc; + }break; + + case EVRMeleeComType::VRPMELEECOM_PrimaryHand: + { + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = EPhysicsGripCOMType::COM_SetAndGripAt; + } + } + + HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + } +} + +void UGS_Melee::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + + if (!bIsActive) + return; + + // Refresh our IsHeld var + TArray<FBPGripPair> HoldingControllers; + IVRGripInterface::Execute_IsHeld(GetParent(), HoldingControllers, bIsHeld); + + //if(!bAlwaysTickPenetration) + //SetTickEnabled(false); + + //if (!bAlwaysTickPenetration) + // bCheckLodge = false; + + if (SecondaryHand.IsValid() && SecondaryHand.HoldingController == ReleasingController && SecondaryHand.GripID == GripInformation.GripID) + { + SecondaryHand = FBPGripPair(); + } + else if (PrimaryHand.IsValid() && PrimaryHand.HoldingController == ReleasingController && PrimaryHand.GripID == GripInformation.GripID) + { + if (SecondaryHand.IsValid()) + { + PrimaryHand = SecondaryHand; + SecondaryHand = FBPGripPair(); + } + else + { + PrimaryHand = FBPGripPair(); + } + } + + + if (PrimaryHand.IsValid()) + { + + FBPActorPhysicsHandleInformation* HandleInfo = PrimaryHand.HoldingController->GetPhysicsGrip(PrimaryHand.GripID); + if (HandleInfo) + { + FBPActorGripInformation * GripInfo = PrimaryHand.HoldingController->GetGripPtrByID(PrimaryHand.GripID); + + if (GripInfo) + { + //Reset defaults here still!!! + HandleInfo->LinConstraint.XDrive.bEnablePositionDrive = true; + HandleInfo->LinConstraint.XDrive.bEnableVelocityDrive = true; + HandleInfo->LinConstraint.XDrive.Stiffness = GripInfo->Stiffness; + HandleInfo->LinConstraint.XDrive.Damping = GripInfo->Damping; + + HandleInfo->LinConstraint.YDrive = HandleInfo->LinConstraint.XDrive; + HandleInfo->LinConstraint.ZDrive = HandleInfo->LinConstraint.XDrive; + + HandleInfo->AngConstraint.SwingDrive.bEnablePositionDrive = false; + HandleInfo->AngConstraint.SwingDrive.bEnableVelocityDrive = false; + HandleInfo->AngConstraint.TwistDrive.bEnablePositionDrive = false; + HandleInfo->AngConstraint.TwistDrive.bEnableVelocityDrive = false; + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::SLERP; + HandleInfo->AngConstraint.SlerpDrive.bEnablePositionDrive = true; + HandleInfo->AngConstraint.SlerpDrive.bEnableVelocityDrive = true; + + if (GripInfo->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings && GripInfo->AdvancedGripSettings.PhysicsSettings.bUseCustomAngularValues) + { + HandleInfo->AngConstraint.SlerpDrive.Damping = GripInfo->AdvancedGripSettings.PhysicsSettings.AngularDamping; + HandleInfo->AngConstraint.SlerpDrive.Stiffness = GripInfo->AdvancedGripSettings.PhysicsSettings.AngularStiffness; + } + else + { + HandleInfo->AngConstraint.SlerpDrive.Damping = GripInfo->Damping * 1.4f; + HandleInfo->AngConstraint.SlerpDrive.Stiffness = GripInfo->Stiffness * 1.5f; + } + + FBPAdvGripSettings AdvSettings = IVRGripInterface::Execute_AdvancedGripSettings(GripInfo->GrippedObject); + GripInfo->AdvancedGripSettings.PhysicsSettings.PhysicsGripLocationSettings = AdvSettings.PhysicsSettings.PhysicsGripLocationSettings; + + PrimaryHand.HoldingController->UpdatePhysicsHandle(PrimaryHand.GripID, true); + } + } + } + +} + +void UGS_Melee::OnBeginPlay_Implementation(UObject * CallingOwner) +{ + // Grip base has no super of this + + if (AActor * Owner = GetOwner()) + { + FName CurrentCompName = NAME_None; + bool bSearchRootComp = WeaponRootOrientationComponent.IsValid(); + int RemainingCount = PenetrationNotifierComponents.Num(); + for (UActorComponent* ChildComp : Owner->GetComponents()) + { + CurrentCompName = ChildComp->GetFName(); + if (CurrentCompName == NAME_None) + continue; + + if (bSearchRootComp && CurrentCompName == WeaponRootOrientationComponent) + { + bSearchRootComp = false; + if (USceneComponent * SceneComp = Cast<USceneComponent>(ChildComp)) + { + OrientationComponentRelativeFacing = SceneComp->GetRelativeTransform(); + } + } + + if (FBPLodgeComponentInfo * Found = PenetrationNotifierComponents.FindByKey(CurrentCompName)) + { + if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(ChildComp)) + { + Found->TargetComponent = TObjectPtr<UPrimitiveComponent>(PrimComp); + //PrimComp->OnComponentHit.AddDynamic(this, &UGS_Melee::OnLodgeHitCallback); + } + + // Decrement even if it failed the cast, they just had it wrong. + RemainingCount--; + } + + if (!bSearchRootComp && RemainingCount < 1) + { + break; + } + } + + // If we found at least one penetration object + if (RemainingCount < PenetrationNotifierComponents.Num()) + { + Owner->OnActorHit.AddDynamic(this, &UGS_Melee::OnLodgeHitCallback); + bCheckLodge = true; + } + } +} + +void UGS_Melee::OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason) +{ + if (AActor * Owner = GetOwner()) + { + Owner->OnActorHit.RemoveDynamic(this, &UGS_Melee::OnLodgeHitCallback); + } +} + +void UGS_Melee::OnLodgeHitCallback(AActor* SelfActor, AActor* OtherActor, FVector NormalImpulse, const FHitResult& Hit) +{ + if (!Hit.GetComponent()) + return; + + if (!bCheckLodge || !bIsActive || bIsLodged || OtherActor == SelfActor) + { + if (bAlwaysTickPenetration || bIsHeld) + { + OnMeleeInvalidHit.Broadcast(OtherActor, Hit.GetComponent(), NormalImpulse, Hit); + } + return; + } + + // Escape out if we are not held and are not set to always tick penetration + if (!bAlwaysTickPenetration && !bIsHeld) + return; + + TArray<FBPHitSurfaceProperties> AllowedPenetrationSurfaceTypes; + + if (OverrideMeleeSurfaceSettings.Num() > 0) + { + // Use our local copy + AllowedPenetrationSurfaceTypes = OverrideMeleeSurfaceSettings; + } + else + { + // Use the global settings + UVRGlobalSettings::GetMeleeSurfaceGlobalSettings(AllowedPenetrationSurfaceTypes); + } + + FBPHitSurfaceProperties HitSurfaceProperties; + if (Hit.PhysMaterial.IsValid()) + { + HitSurfaceProperties.SurfaceType = Hit.PhysMaterial->SurfaceType; + } + + if (AllowedPenetrationSurfaceTypes.Num()) + { + // Reject bad surface types + if (!Hit.PhysMaterial.IsValid()) + { + OnMeleeInvalidHit.Broadcast(OtherActor, Hit.GetComponent(), NormalImpulse, Hit); + return; + } + + EPhysicalSurface PhysSurfaceType = Hit.PhysMaterial->SurfaceType; + int32 IndexOfSurface = AllowedPenetrationSurfaceTypes.IndexOfByPredicate([&PhysSurfaceType](const FBPHitSurfaceProperties& Entry) { return Entry.SurfaceType == PhysSurfaceType; }); + + if (IndexOfSurface != INDEX_NONE) + { + HitSurfaceProperties = AllowedPenetrationSurfaceTypes[IndexOfSurface]; + } + else + { + // Surface is not part of our default list, don't allow penetration + HitSurfaceProperties.bSurfaceAllowsPenetration = false; + // Not a valid surface type to throw an event + //return; + } + } + + /*if (UPrimitiveComponent * root = Cast<UPrimitiveComponent>(SelfActor->GetRootComponent())) + { + if (FBodyInstance * rBodyInstance = root->GetBodyInstance()) + { + //float mass =rBodyInstance->GetBodyMass(); + //ImpulseVelocity = (NormalImpulse / rBodyInstance->GetBodyMass()).SizeSquared(); + RollingVelocityAverage += FVector::CrossProduct(RollingAngVelocityAverage, Hit.ImpactPoint - (rBodyInstance->GetCOMPosition())); + } + }*/ + +// FVector FrameToFrameVelocity = RollingVelocityAverage; + + // If we hit, then regardless of penetration, end the velocity history and reset +// RollingVelocityAverage = FVector::ZeroVector; +// RollingAngVelocityAverage = FVector::ZeroVector; + + bool bHadFirstHit = false; + FBPLodgeComponentInfo FirstHitComp; + + float HitNormalImpulse = NormalImpulse.SizeSquared(); + + for(FBPLodgeComponentInfo &LodgeData : PenetrationNotifierComponents) + { + if (!IsValid(LodgeData.TargetComponent)) + continue; + + FBox LodgeLocalBox = LodgeData.TargetComponent->CalcLocalBounds().GetBox(); + FVector LocalHit = LodgeData.TargetComponent->GetComponentTransform().InverseTransformPosition(Hit.ImpactPoint); + //FBox LodgeBox = LodgeData.TargetComponent->Bounds.GetBox(); + if (IsValid(LodgeData.TargetComponent) && LodgeLocalBox.IsInsideOrOn(LocalHit))//LodgeBox.IsInsideOrOn(Hit.ImpactPoint)) + { + FVector ForwardVec = LodgeData.TargetComponent->GetForwardVector(); + + // Using swept objects hit normal as we are looking for a facing from ourselves + float DotValue = FMath::Abs(FVector::DotProduct(Hit.Normal, ForwardVec)); + float Velocity = NormalImpulse.ProjectOnToNormal(ForwardVec).SizeSquared();//FrameToFrameVelocity.ProjectOnToNormal(ForwardVec); + // Check if the velocity was strong enough along our axis to count as a lodge event + // Also that our facing was in the relatively correct direction + + if (HitSurfaceProperties.bSurfaceAllowsPenetration && (!bOnlyPenetrateWithTwoHands || SecondaryHand.IsValid())) + { + if (LodgeData.ZoneType != EVRMeleeZoneType::VRPMELLE_ZONETYPE_Hit && DotValue >= (1.0f - LodgeData.AcceptableForwardProductRange) && (Velocity * HitSurfaceProperties.StabVelocityScaler) >= FMath::Square(LodgeData.PenetrationVelocity)) + { + OnShouldLodgeInObject.Broadcast(LodgeData, OtherActor, Hit.GetComponent(), Hit.GetComponent()->GetCollisionObjectType(), HitSurfaceProperties, NormalImpulse, Hit); + return; + //break; + } + } + + float HitImpulse = LodgeData.bIgnoreForwardVectorForHitImpulse ? HitNormalImpulse : Velocity; + + if (!bHadFirstHit && LodgeData.ZoneType > EVRMeleeZoneType::VRPMELLE_ZONETYPE_Stab && DotValue >= (1.0f - LodgeData.AcceptableForwardProductRangeForHits) && HitImpulse >= FMath::Square(LodgeData.MinimumHitVelocity)) + { + bHadFirstHit = true; + FirstHitComp = LodgeData; + } + } + } + + if (bHadFirstHit) + { + OnMeleeHit.Broadcast(FirstHitComp, OtherActor, Hit.GetComponent(), Hit.GetComponent()->GetCollisionObjectType(), HitSurfaceProperties, NormalImpulse, Hit); + } + else + { + OnMeleeInvalidHit.Broadcast(OtherActor, Hit.GetComponent(), NormalImpulse, Hit); + } +} + + +void UGS_Melee::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose) +{ + if (!bIsActive) + return; + + if (WeaponRootOrientationComponent != NAME_None) + { + // Alter to rotate to x+ if we have an orientation component + FQuat DeltaQuat = OrientationComponentRelativeFacing.GetRotation(); + + // This moves the kinematic actor to face its X+ in the direction designated + KinPose.SetRotation(KinPose.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + } +} + +void UGS_Melee::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) +{ + if (!bIsActive) + return; + + if (SecondaryHand.IsValid() )// && GrippingController == PrimaryHand.HoldingController) + { + if (GrippingController == SecondaryHand.HoldingController && HandleInfo->GripID == SecondaryHand.GripID) + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + else if (GrippingController == PrimaryHand.HoldingController && HandleInfo->GripID == PrimaryHand.GripID) + { + if (bHasValidPrimaryHand) + { + PrimaryHandPhysicsSettings.FillTo(HandleInfo); + } + else + { + SecondaryHandPhysicsSettings.FillTo(HandleInfo); + } + } + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal) + SetComBetweenHands(GrippingController, HandleInfo); + } + else + { + //HandleInfo->bSetCOM = false; // Should i remove this? + HandleInfo->bSkipResettingCom = false; + } +} + +void UGS_Melee::SetComBetweenHands(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) +{ + + if (!GrippingController || !HandleInfo) + return; + + if (COMType != EVRMeleeComType::VRPMELEECOM_Normal && SecondaryHand.IsValid()) + { + //if (PrimaryHand.HoldingController == GrippingController) + { + if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(GetParentSceneComp())) + { + if (FBodyInstance * rBodyInstance = PrimComp->GetBodyInstance()) + { + FPhysicsCommand::ExecuteWrite(rBodyInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor) + { + FTransform localCom = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor); + localCom.SetLocation((HandleInfo->RootBoneRotation * ObjectRelativeGripCenter).GetLocation()); + FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, localCom); + + }); + } + } + } + + HandleInfo->bSetCOM = true; // Should i remove this? + HandleInfo->bSkipResettingCom = true; + } +} + +/*void UGS_Melee::Tick(float DeltaTime) +{ + AActor* myOwner = GetOwner(); + + if (UPrimitiveComponent * root = Cast<UPrimitiveComponent>(myOwner->GetRootComponent())) + { + FBodyInstance* rBodyInstance = root->GetBodyInstance(); + if (rBodyInstance && rBodyInstance->IsValidBodyInstance()) + { + RollingVelocityAverage = rBodyInstance->GetUnrealWorldVelocity(); + RollingAngVelocityAverage = rBodyInstance->GetUnrealWorldAngularVelocityInRadians(); + } + } +}*/ + +bool UGS_Melee::Wants_DenyTeleport_Implementation(UGripMotionControllerComponent* Controller) +{ + /*if (PrimaryHand.IsValid() && Controller != PrimaryHand.HoldingController) + { + return true; + }*/ + + return false; +} + +bool UGS_Melee::GetWorldTransform_Implementation +( + UGripMotionControllerComponent* GrippingController, + float DeltaTime, FTransform & WorldTransform, + const FTransform &ParentTransform, + FBPActorGripInformation &Grip, + AActor * actor, + UPrimitiveComponent * root, + bool bRootHasInterface, + bool bActorHasInterface, + bool bIsForTeleport +) +{ + if (!GrippingController) + return false; + + // Just simple transform setting + WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + WorldTransform.SetLocation((GrippingController->GetPivotLocation() + Grip.SecondaryGripInfo.SecondaryAttachment->GetComponentLocation()) / 2.f); + } + + // Check the grip lerp state, this it ouside of the secondary attach check below because it can change the result of it + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + switch (Grip.SecondaryGripInfo.GripLerpState) + { + case EGripLerpState::StartLerp: + case EGripLerpState::EndLerp: + { + if (Grip.SecondaryGripInfo.curLerp > 0.01f) + Grip.SecondaryGripInfo.curLerp -= DeltaTime; + else + { + /*if (Grip.SecondaryGripInfo.bHasSecondaryAttachment && + Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && + Grip.AdvancedGripSettings.SecondaryGripSettings.SecondaryGripScaler_DEPRECATED < 1.0f) + { + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::ConstantLerp_DEPRECATED; + } + else*/ + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + } + + }break; + //case EGripLerpState::ConstantLerp_DEPRECATED: + case EGripLerpState::NotLerping: + default:break; + } + } + + // Handle the interp and multi grip situations, re-checking the grip situation here as it may have changed in the switch above. + if ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && Grip.SecondaryGripInfo.SecondaryAttachment) || Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + FTransform SecondaryTransform = Grip.RelativeTransform * ParentTransform; + + // Checking secondary grip type for the scaling setting + ESecondaryGripType SecondaryType = ESecondaryGripType::SG_None; + + if (bRootHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(root); + else if (bActorHasInterface) + SecondaryType = IVRGripInterface::Execute_SecondaryGripType(actor); + + // If the grip is a custom one, skip all of this logic we won't be changing anything + if (SecondaryType != ESecondaryGripType::SG_Custom) + { + // Variables needed for multi grip transform + FVector BasePoint = ParentTransform.GetLocation(); // Get our pivot point + const FTransform PivotToWorld = FTransform(FQuat::Identity, BasePoint); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -BasePoint); + + FVector frontLocOrig; + FVector frontLoc; + + // Ending lerp out of a multi grip + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::EndLerp) + { + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + frontLoc = Grip.SecondaryGripInfo.LastRelativeLocation; + + frontLocOrig = FMath::Lerp(frontLoc, frontLocOrig, FMath::Clamp(Grip.SecondaryGripInfo.curLerp / Grip.SecondaryGripInfo.LerpToRate, 0.0f, 1.0f)); + } + else // Is in a multi grip, might be lerping into it as well. + { + //FVector curLocation; // Current location of the secondary grip + + // Calculates the correct secondary attachment location and sets frontLoc to it + CalculateSecondaryLocation(frontLoc, BasePoint, Grip, GrippingController); + + frontLocOrig = (/*WorldTransform*/SecondaryTransform.TransformPosition(Grip.SecondaryGripInfo.SecondaryRelativeTransform.GetLocation())) - BasePoint; + + // Apply any smoothing settings and lerping in / constant lerping + ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime); + + Grip.SecondaryGripInfo.LastRelativeLocation = frontLoc; + } + + // Get any scaling addition from a scaling secondary grip type + FVector Scaler = FVector(1.0f); + if (SecondaryType == ESecondaryGripType::SG_FreeWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_SlotOnlyWithScaling_Retain || SecondaryType == ESecondaryGripType::SG_ScalingOnly) + { + GetAnyScaling(Scaler, Grip, frontLoc, frontLocOrig, SecondaryType, SecondaryTransform); + } + + Grip.SecondaryGripInfo.SecondaryGripDistance = FVector::Dist(frontLocOrig, frontLoc); + + /*if (Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripSettings && Grip.AdvancedGripSettings.SecondaryGripSettings.bUseSecondaryGripDistanceInfluence_DEPRECATED) + { + float rotScaler = 1.0f - FMath::Clamp((Grip.SecondaryGripInfo.SecondaryGripDistance - Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDeadZone_DEPRECATED) / FMath::Max(Grip.AdvancedGripSettings.SecondaryGripSettings.GripInfluenceDistanceToZero_DEPRECATED, 1.0f), 0.0f, 1.0f); + frontLoc = FMath::Lerp(frontLocOrig, frontLoc, rotScaler); + }*/ + + // Skip rot val for scaling only + if (SecondaryType != ESecondaryGripType::SG_ScalingOnly) + { + // Get the rotation difference from the initial second grip + FQuat rotVal = FQuat::FindBetweenVectors(frontLocOrig, frontLoc); + + // Rebase the world transform to the pivot point, add the rotation, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(rotVal, FVector::ZeroVector, Scaler) * PivotToWorld; + } + else + { + // Rebase the world transform to the pivot point, add the scaler, remove the pivot point rebase + WorldTransform = WorldTransform * WorldToPivot * FTransform(FQuat::Identity, FVector::ZeroVector, Scaler) * PivotToWorld; + } + } + + + // Fixup X rotation to keep it aligned with the primary hand. + //WorldTransform = Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; + + // Get primary hand relative position + FTransform InverseTrans(Grip.RelativeTransform.ToInverseMatrixWithScale()); + + // Get the original location of the hand + FVector origLocation = InverseTrans.GetLocation(); + + FVector orientedvector = FVector::VectorPlaneProject(origLocation, -OrientationComponentRelativeFacing.GetRotation().GetForwardVector()); + FVector newLocation = FVector::VectorPlaneProject(WorldTransform.InverseTransformPosition(GrippingController->GetPivotLocation()), OrientationComponentRelativeFacing.GetRotation().GetForwardVector()); + + FQuat DeltaQuat = FQuat::FindBetweenVectors(orientedvector, newLocation); + + WorldTransform.SetRotation(DeltaQuat * WorldTransform.GetRotation()); + } + + return true; +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Physics.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Physics.cpp new file mode 100644 index 0000000..a52db03 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/GS_Physics.cpp @@ -0,0 +1,109 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/GS_Physics.h" +#include "VRGripInterface.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "GripMotionControllerComponent.h" + +UGS_Physics::UGS_Physics(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + bIsActive = true; + WorldTransformOverrideType = EGSTransformOverrideType::None; + + bDenyLateUpdates = true; + + bInjectPrePhysicsHandle = false; + bInjectPostPhysicsHandle = true; + + bCanEverTick = false; + + SingleHandPhysicsSettings.TwistSettings.MaxForceCoefficient = 1.f; + SingleHandPhysicsSettings.SwingSettings = SingleHandPhysicsSettings.TwistSettings; + SingleHandPhysicsSettings.SlerpSettings = SingleHandPhysicsSettings.TwistSettings; + + SingleHandPhysicsSettings.XAxisSettings.MaxForceCoefficient = 1.f; + SingleHandPhysicsSettings.YAxisSettings = SingleHandPhysicsSettings.XAxisSettings; + SingleHandPhysicsSettings.ZAxisSettings = SingleHandPhysicsSettings.XAxisSettings; + +} + +void UGS_Physics::UpdateDualHandInfo(UGripMotionControllerComponent* GrippingController, bool bReCreate) +{ + TArray<FBPGripPair> HoldingControllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(GetParent(), HoldingControllers, bIsHeld); + + int NumControllers = HoldingControllers.Num(); + + for (FBPGripPair& Grip : HoldingControllers) + { + if (NumControllers > 1) + { + FBPActorPhysicsHandleInformation* HandleInfo = Grip.HoldingController->GetPhysicsGrip(Grip.GripID); + if (HandleInfo) + { + MultiHandPhysicsSettings.FillTo(HandleInfo); + + if(bReCreate && Grip.HoldingController != GrippingController) + Grip.HoldingController->UpdatePhysicsHandle(Grip.GripID, true); + } + } + else + { + FBPActorPhysicsHandleInformation* HandleInfo = Grip.HoldingController->GetPhysicsGrip(Grip.GripID); + if (HandleInfo) + { + SingleHandPhysicsSettings.FillTo(HandleInfo); + + if(bReCreate) + Grip.HoldingController->UpdatePhysicsHandle(Grip.GripID, true); + } + } + } +} + + +void UGS_Physics::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + if (!bIsActive) + return; + + UpdateDualHandInfo(GrippingController, true); +} + +void UGS_Physics::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + + if (!bIsActive) + return; + + UpdateDualHandInfo(nullptr, true); +} + +// Should I be orienting it to the controller for these types of grips? +/*void UGS_Physics::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose) +{ + if (!bIsActive) + return; + + if (WeaponRootOrientationComponent != NAME_None) + { + // Alter to rotate to x+ if we have an orientation component + FQuat DeltaQuat = OrientationComponentRelativeFacing.GetRotation(); + + // This moves the kinematic actor to face its X+ in the direction designated + KinPose.SetRotation(KinPose.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + HandleInfo->COMPosition.SetRotation(HandleInfo->COMPosition.GetRotation() * (HandleInfo->RootBoneRotation.GetRotation().Inverse() * DeltaQuat)); + } +}*/ + +void UGS_Physics::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) +{ + if (!bIsActive) + return; + + + UpdateDualHandInfo(GrippingController, false); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/VRGripScriptBase.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/VRGripScriptBase.cpp new file mode 100644 index 0000000..8590aa3 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripScripts/VRGripScriptBase.cpp @@ -0,0 +1,355 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "GripScripts/VRGripScriptBase.h" +#include "GripMotionControllerComponent.h" +#include "VRGripInterface.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "Components/PrimitiveComponent.h" +#include "GameFramework/Actor.h" +#include "Net/UnrealNetwork.h" +#include "Engine/NetDriver.h" + + +UVRGripScriptBase::UVRGripScriptBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +// PrimaryComponentTick.bCanEverTick = false; +// PrimaryComponentTick.bStartWithTickEnabled = false; +// PrimaryComponentTick.TickGroup = ETickingGroup::TG_PrePhysics; + WorldTransformOverrideType = EGSTransformOverrideType::None; + bDenyAutoDrop = false; + bDenyLateUpdates = false; + bForceDrop = false; + bIsActive = false; + + bCanEverTick = false; + bAllowTicking = false; +} + +void UVRGripScriptBase::OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason) {}; +void UVRGripScriptBase::OnBeginPlay_Implementation(UObject * CallingOwner) {}; + +bool UVRGripScriptBase::GetWorldTransform_Implementation(UGripMotionControllerComponent* GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) { return true; } +void UVRGripScriptBase::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRGripScriptBase::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRGripScriptBase::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRGripScriptBase::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} + + +EGSTransformOverrideType UVRGripScriptBase::GetWorldTransformOverrideType() { return WorldTransformOverrideType; } +bool UVRGripScriptBase::IsScriptActive() { return bIsActive; } +//bool UVRGripScriptBase::Wants_DenyAutoDrop() { return bDenyAutoDrop; } +//bool UVRGripScriptBase::Wants_DenyLateUpdates() { return bDenyLateUpdates; } +//bool UVRGripScriptBase::Wants_ToForceDrop() { return bForceDrop; } +bool UVRGripScriptBase::Wants_DenyTeleport_Implementation(UGripMotionControllerComponent * Controller) { return false; } +void UVRGripScriptBase::HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose) {} +void UVRGripScriptBase::HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo) {} + +UVRGripScriptBase* UVRGripScriptBase::GetGripScriptByClass(UObject* WorldContextObject, TSubclassOf<UVRGripScriptBase> GripScriptClass, EBPVRResultSwitch& Result) +{ + if (WorldContextObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(WorldContextObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script && Script->IsA(GripScriptClass)) + { + Result = EBPVRResultSwitch::OnSucceeded; + return Script; + } + } + } + } + + Result = EBPVRResultSwitch::OnFailed; + return nullptr; +} + +void UVRGripScriptBase::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + // Uobject has no replicated props + //Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + // Replicate here if required + UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()); + if (BPClass != NULL) + { + BPClass->GetLifetimeBlueprintReplicationList(OutLifetimeProps); + } +} + +void UVRGripScriptBase::Tick(float DeltaTime) +{ + // Do nothing by default +} + +bool UVRGripScriptBase::IsTickable() const +{ + return bAllowTicking; +} + +UWorld* UVRGripScriptBase::GetTickableGameObjectWorld() const +{ + return GetWorld(); +} + +bool UVRGripScriptBase::IsTickableInEditor() const +{ + return false; +} + +bool UVRGripScriptBase::IsTickableWhenPaused() const +{ + return false; +} + +ETickableTickType UVRGripScriptBase::GetTickableTickType() const +{ + if(IsTemplate(RF_ClassDefaultObject)) + return ETickableTickType::Never; + + return bCanEverTick ? ETickableTickType::Conditional : ETickableTickType::Never; +} + +TStatId UVRGripScriptBase::GetStatId() const +{ + RETURN_QUICK_DECLARE_CYCLE_STAT(UVRGripScriptBase, STATGROUP_Tickables); +} + +void UVRGripScriptBase::SetTickEnabled(bool bTickEnabled) +{ + bAllowTicking = bTickEnabled; +} + + +// Not currently compiling in editor builds....not entirely sure why... +/* +void UVRGripScriptBase::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + + // In the grippables pre replication to pass it on +#ifndef WITH_EDITOR + // Run pre-replication for any grip scripts + if (GripLogicScripts.Num()) + { + if (UNetDriver* NetDriver = GetNetDriver()) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + Script->PreReplication(*((IRepChangedPropertyTracker *)NetDriver->FindOrCreateRepChangedPropertyTracker(Script).Get())); + } + } + } + } +#endif + + UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()); + if (BPClass != NULL) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } +}*/ + +bool UVRGripScriptBase::CallRemoteFunction(UFunction * Function, void * Parms, FOutParmRec * OutParms, FFrame * Stack) +{ + bool bProcessed = false; + + if (AActor* MyOwner = GetOwner()) + { + FWorldContext* const Context = GEngine->GetWorldContextFromWorld(GetWorld()); + if (Context != nullptr) + { + for (FNamedNetDriver& Driver : Context->ActiveNetDrivers) + { + if (Driver.NetDriver != nullptr && Driver.NetDriver->ShouldReplicateFunction(MyOwner, Function)) + { + Driver.NetDriver->ProcessRemoteFunction(MyOwner, Function, Parms, OutParms, Stack, this); + + bProcessed = true; + } + } + } + } + + return bProcessed; +} + +int32 UVRGripScriptBase::GetFunctionCallspace(UFunction * Function, FFrame * Stack) +{ + AActor* Owner = GetOwner(); + + if (HasAnyFlags(RF_ClassDefaultObject) || !IsSupportedForNetworking() || !Owner) + { + // This handles absorbing authority/cosmetic + return GEngine->GetGlobalFunctionCallspace(Function, this, Stack); + } + + // Owner is certified valid now + return Owner->GetFunctionCallspace(Function, Stack); +} + +FTransform UVRGripScriptBase::GetGripTransform(const FBPActorGripInformation &Grip, const FTransform & ParentTransform) +{ + return Grip.RelativeTransform * Grip.AdditionTransform * ParentTransform; +} + +USceneComponent * UVRGripScriptBase::GetParentSceneComp() +{ + UObject* ParentObj = this->GetParent(); + + if (USceneComponent * PrimParent = Cast<USceneComponent>(ParentObj)) + { + return PrimParent; + } + else if (AActor * ParentActor = Cast<AActor>(ParentObj)) + { + return ParentActor->GetRootComponent(); + } + + return nullptr; +} + +FTransform UVRGripScriptBase::GetParentTransform(bool bGetWorldTransform, FName BoneName) +{ + UObject* ParentObj = this->GetParent(); + + if (USceneComponent* PrimParent = Cast<USceneComponent>(ParentObj)) + { + if (BoneName != NAME_None) + { + return PrimParent->GetSocketTransform(BoneName); + } + else + { + return PrimParent->GetComponentTransform(); + } + } + else if (AActor* ParentActor = Cast<AActor>(ParentObj)) + { + return ParentActor->GetActorTransform(); + } + + return FTransform::Identity; +} + +FBodyInstance * UVRGripScriptBase::GetParentBodyInstance(FName OptionalBoneName) +{ + UObject * ParentObj = this->GetParent(); + + if (UPrimitiveComponent * PrimParent = Cast<UPrimitiveComponent>(ParentObj)) + { + return PrimParent->GetBodyInstance(OptionalBoneName); + } + else if (AActor * ParentActor = Cast<AActor>(ParentObj)) + { + if (UPrimitiveComponent * Prim = Cast<UPrimitiveComponent>(ParentActor->GetRootComponent())) + { + return Prim->GetBodyInstance(OptionalBoneName); + } + } + + return nullptr; +} + +UObject * UVRGripScriptBase::GetParent() +{ + return this->GetOuter(); +} + +AActor * UVRGripScriptBase::GetOwner() +{ + UObject * myOuter = this->GetOuter(); + + if (!myOuter) + return nullptr; + + if (AActor * ActorOwner = Cast<AActor>(myOuter)) + { + return ActorOwner; + } + else if (UActorComponent * ComponentOwner = Cast<UActorComponent>(myOuter)) + { + return ComponentOwner->GetOwner(); + } + + return nullptr; +} + +bool UVRGripScriptBase::HasAuthority() +{ + if (AActor * MyOwner = GetOwner()) + { + return MyOwner->GetLocalRole() == ROLE_Authority; + } + + return false; +} + +bool UVRGripScriptBase::IsServer() +{ + if (AActor * MyOwner = GetOwner()) + { + return MyOwner->GetNetMode() < ENetMode::NM_Client; + } + + return false; +} + +UWorld* UVRGripScriptBase::GetWorld() const +{ + if (IsTemplate()) + return nullptr; + + if (GIsEditor && !GIsPlayInEditorWorld) + { + return nullptr; + } + else if (UObject * Outer = GetOuter()) + { + return Outer->GetWorld(); + } + + return nullptr; +} + +void UVRGripScriptBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + OnEndPlay(EndPlayReason); +} + +void UVRGripScriptBase::BeginPlay(UObject * CallingOwner) +{ + if (bAlreadyNotifiedPlay) + return; + + bAlreadyNotifiedPlay = true; + + // Notify the subscripts about begin play + OnBeginPlay(CallingOwner); +} + +void UVRGripScriptBase::PostInitProperties() +{ + Super::PostInitProperties(); + + //Called in game, when World exist . BeginPlay will not be called in editor + if (GetWorld()) + { + if (AActor* Owner = GetOwner()) + { + if (Owner->IsActorInitialized()) + { + BeginPlay(GetOwner()); + } + } + } +} + +void UVRGripScriptBaseBP::Tick(float DeltaTime) +{ + ReceiveTick(DeltaTime); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableActor.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableActor.cpp new file mode 100644 index 0000000..40b824c --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableActor.cpp @@ -0,0 +1,788 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableActor.h" +#include "TimerManager.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Misc/BucketUpdateSubsystem.h" +#include "GripScripts/VRGripScriptBase.h" +#include "DrawDebugHelpers.h" +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + + + //============================================================================= +AGrippableActor::AGrippableActor(const FObjectInitializer& ObjectInitializer) + : Super() +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + // Default replication on for multiplayer + //this->bNetLoadOnClient = false; + SetReplicatingMovement(true); + bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bAllowIgnoringAttachOnOwner = true; + bReplicateGripScripts = false; + + // Setting a minimum of every 3rd frame (VR 90fps) for replication consideration + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 30.0f; +} + +void AGrippableActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AGrippableActor, GripLogicScripts, COND_Custom); + DOREPLIFETIME(AGrippableActor, bReplicateGripScripts); + DOREPLIFETIME(AGrippableActor, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(AGrippableActor, bAllowIgnoringAttachOnOwner); + DOREPLIFETIME(AGrippableActor, ClientAuthReplicationData); + DOREPLIFETIME_CONDITION(AGrippableActor, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(AGrippableActor, GameplayTags, COND_Custom); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); + + FDoRepLifetimeParams AttachmentReplicationParams{ COND_Custom, REPNOTIFY_Always, /*bIsPushBased=*/true }; + DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableActor, AttachmentWeldReplication, AttachmentReplicationParams); +} + +void AGrippableActor::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableActor, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableActor, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableActor, GripLogicScripts, bReplicateGripScripts); + + //Super::PreReplication(ChangedPropertyTracker); + +#if WITH_PUSH_MODEL + const AActor* const OldAttachParent = AttachmentWeldReplication.AttachParent; + const UActorComponent* const OldAttachComponent = AttachmentWeldReplication.AttachComponent; +#endif + + // Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment. + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + GatherCurrentMovement(); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(AActor, ReplicatedMovement, IsReplicatingMovement()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableActor, AttachmentWeldReplication, RootComponent && !RootComponent->GetIsReplicated()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(AActor, AttachmentReplication, false);// RootComponent && !RootComponent->GetIsReplicated()); + + +#if WITH_PUSH_MODEL + if (UNLIKELY(OldAttachParent != AttachmentWeldReplication.AttachParent || OldAttachComponent != AttachmentWeldReplication.AttachComponent)) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableActor, AttachmentWeldReplication, this); + } +#endif + + PRAGMA_DISABLE_DEPRECATION_WARNINGS + UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()); + if (BPClass != nullptr) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS +} + +void AGrippableActor::GatherCurrentMovement() +{ + if (IsReplicatingMovement() || (RootComponent && RootComponent->GetAttachParent())) + { + bool bWasAttachmentModified = false; + bool bWasRepMovementModified = false; + + AActor* OldAttachParent = AttachmentWeldReplication.AttachParent; + USceneComponent* OldAttachComponent = AttachmentWeldReplication.AttachComponent; + + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + FRepMovement& RepMovement = GetReplicatedMovement_Mutable(); + + UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + + RepMovement.FillFrom(RBState, this); + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + RepMovement.bRepPhysics = !RootPrimComp->IsWelded(); + + if (!RepMovement.bRepPhysics) + { + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + } + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + else if (RootComponent != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + else + { + RepMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this); + RepMovement.Rotation = RootComponent->GetComponentRotation(); + RepMovement.LinearVelocity = GetVelocity(); + RepMovement.AngularVelocity = FVector::ZeroVector; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + + bWasRepMovementModified = (bWasRepMovementModified || RepMovement.bRepPhysics); + RepMovement.bRepPhysics = false; + } + +#if WITH_PUSH_MODEL + if (bWasRepMovementModified) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AActor, ReplicatedMovement, this); + } + + if (bWasAttachmentModified || + OldAttachParent != AttachmentWeldReplication.AttachParent || + OldAttachComponent != AttachmentWeldReplication.AttachComponent) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableActor, AttachmentWeldReplication, this); + } +#endif + } +} + +void AGrippableActor::OnRep_AttachmentReplication() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication()) + { + return; + } + + if (AttachmentWeldReplication.AttachParent) + { + if (RootComponent) + { + USceneComponent* AttachParentComponent = (AttachmentWeldReplication.AttachComponent ? ToRawPtr(AttachmentWeldReplication.AttachComponent) : AttachmentWeldReplication.AttachParent->GetRootComponent()); + + if (AttachParentComponent) + { + RootComponent->SetRelativeLocation_Direct(AttachmentWeldReplication.LocationOffset); + RootComponent->SetRelativeRotation_Direct(AttachmentWeldReplication.RotationOffset); + RootComponent->SetRelativeScale3D_Direct(AttachmentWeldReplication.RelativeScale3D); + + // If we're already attached to the correct Parent and Socket, then the update must be position only. + // AttachToComponent would early out in this case. + // Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated + // if the body is simulated (see AActor::GatherMovement). + const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && AttachmentWeldReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent)); + if (bAlreadyAttached) + { + // Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above). + RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + else + { + FAttachmentTransformRules attachRules = FAttachmentTransformRules::KeepRelativeTransform; + attachRules.bWeldSimulatedBodies = AttachmentWeldReplication.bIsWelded; + RootComponent->AttachToComponent(AttachParentComponent, attachRules, AttachmentWeldReplication.AttachSocket); + } + } + } + } + else + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Handle the case where an object was both detached and moved on the server in the same frame. + // Calling this extraneously does not hurt but will properly fire events if the movement state changed while attached. + // This is needed because client side movement is ignored when attached + if (IsReplicatingMovement()) + { + OnRep_ReplicatedMovement(); + } + } +} + +bool AGrippableActor::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +AGrippableActor::~AGrippableActor() +{ +} + +void AGrippableActor::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } +} + + +void AGrippableActor::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void AGrippableActor::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void AGrippableActor::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void AGrippableActor::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void AGrippableActor::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void AGrippableActor::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void AGrippableActor::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void AGrippableActor::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void AGrippableActor::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void AGrippableActor::OnUsed_Implementation() {} +void AGrippableActor::OnEndUsed_Implementation() {} +void AGrippableActor::OnSecondaryUsed_Implementation() {} +void AGrippableActor::OnEndSecondaryUsed_Implementation() {} +void AGrippableActor::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool AGrippableActor::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool AGrippableActor::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + + +EGripInterfaceTeleportBehavior AGrippableActor::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool AGrippableActor::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType AGrippableActor::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType AGrippableActor::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings AGrippableActor::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings AGrippableActor::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void AGrippableActor::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings AGrippableActor::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float AGrippableActor::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void AGrippableActor::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool AGrippableActor::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void AGrippableActor::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +bool AGrippableActor::AddToClientReplicationBucket() +{ + if (ShouldWeSkipAttachmentReplication(false)) + { + // The subsystem automatically removes entries with the same function signature so its safe to just always add here + GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->AddObjectToBucket(ClientAuthReplicationData.UpdateRate, this, FName(TEXT("PollReplicationEvent"))); + ClientAuthReplicationData.bIsCurrentlyClientAuth = true; + + if (UWorld * World = GetWorld()) + ClientAuthReplicationData.TimeAtInitialThrow = World->GetTimeSeconds(); + + return true; + } + + return false; +} + +bool AGrippableActor::RemoveFromClientReplicationBucket() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + { + GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->RemoveObjectFromBucketByFunctionName(this, FName(TEXT("PollReplicationEvent"))); + CeaseReplicationBlocking(); + return true; + } + + return false; +} + +void AGrippableActor::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void AGrippableActor::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + RemoveFromClientReplicationBucket(); + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + } + else + { + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + + if (ClientAuthReplicationData.bUseClientAuthThrowing && !VRGripInterfaceSettings.bIsHeld) + { + bool bWasLocallyOwned = HoldingController ? HoldingController->IsLocallyControlled() : false; + if (bWasLocallyOwned && ShouldWeSkipAttachmentReplication(false)) + { + if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(GetRootComponent())) + { + if (PrimComp->IsSimulatingPhysics()) + { + AddToClientReplicationBucket(); + } + } + } + } + } +} + +bool AGrippableActor::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +/*FBPInteractionSettings AGrippableActor::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool AGrippableActor::PollReplicationEvent() +{ + if (!ClientAuthReplicationData.bIsCurrentlyClientAuth || !this->HasLocalNetOwner() || VRGripInterfaceSettings.bIsHeld) + return false; // Tell the bucket subsystem to remove us from consideration + + UWorld* OurWorld = GetWorld(); + if (!OurWorld) + return false; // Tell the bucket subsystem to remove us from consideration + + bool bRemoveBlocking = false; + + if ((OurWorld->GetTimeSeconds() - ClientAuthReplicationData.TimeAtInitialThrow) > 10.0f) + { + // Lets time out sending, its been 10 seconds since we threw the object and its likely that it is conflicting with some server + // Authed movement that is forcing it to keep momentum. + //return false; // Tell the bucket subsystem to remove us from consideration + bRemoveBlocking = true; + } + + // Store current transform for resting check + FTransform CurTransform = this->GetActorTransform(); + + if (!bRemoveBlocking) + { + if (!CurTransform.GetRotation().Equals(ClientAuthReplicationData.LastActorTransform.GetRotation()) || !CurTransform.GetLocation().Equals(ClientAuthReplicationData.LastActorTransform.GetLocation())) + { + ClientAuthReplicationData.LastActorTransform = CurTransform; + + if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(RootComponent)) + { + // Need to clamp to a max time since start, to handle cases with conflicting collisions + if (PrimComp->IsSimulatingPhysics() && ShouldWeSkipAttachmentReplication(false)) + { + FRepMovementVR ClientAuthMovementRep; + if (ClientAuthMovementRep.GatherActorsMovement(this)) + { + Server_GetClientAuthReplication(ClientAuthMovementRep); + + if (PrimComp->RigidBodyIsAwake()) + { + return true; + } + } + } + } + else + { + bRemoveBlocking = true; + //return false; // Tell the bucket subsystem to remove us from consideration + } + } + //else + // { + // Difference is too small, lets end sending location + //ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + // } + } + + bool TimedBlockingRelease = false; + + AActor* TopOwner = GetOwner(); + if (TopOwner != nullptr) + { + AActor* tempOwner = TopOwner->GetOwner(); + + // I have an owner so search that for the top owner + while (tempOwner) + { + TopOwner = tempOwner; + tempOwner = TopOwner->GetOwner(); + } + + if (APlayerController * PlayerController = Cast<APlayerController>(TopOwner)) + { + if (APlayerState * PlayerState = PlayerController->PlayerState) + { + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + + // Lets clamp the ping to a min / max value just in case + float clampedPing = FMath::Clamp(PlayerState->ExactPing * 0.001f, 0.0f, 1000.0f); + OurWorld->GetTimerManager().SetTimer(ClientAuthReplicationData.ResetReplicationHandle, this, &AGrippableActor::CeaseReplicationBlocking, clampedPing, false); + TimedBlockingRelease = true; + } + } + } + + if (!TimedBlockingRelease) + { + CeaseReplicationBlocking(); + } + + // Tell server to kill us + Server_EndClientAuthReplication(); + return false; // Tell the bucket subsystem to remove us from consideration +} + +void AGrippableActor::CeaseReplicationBlocking() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + ClientAuthReplicationData.bIsCurrentlyClientAuth = false; + + ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + if (UWorld * OurWorld = GetWorld()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + } +} + +void AGrippableActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + + RemoveFromClientReplicationBucket(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } + + Super::EndPlay(EndPlayReason); +} + +bool AGrippableActor::Server_EndClientAuthReplication_Validate() +{ + return true; +} + +void AGrippableActor::Server_EndClientAuthReplication_Implementation() +{ + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (FPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + if (UPrimitiveComponent* RootPrim = Cast<UPrimitiveComponent>(GetRootComponent())) + { + PhysicsReplication->RemoveReplicatedTarget(RootPrim); + } + } + } + } +} + +bool AGrippableActor::Server_GetClientAuthReplication_Validate(const FRepMovementVR & newMovement) +{ + return true; +} + +void AGrippableActor::Server_GetClientAuthReplication_Implementation(const FRepMovementVR & newMovement) +{ + if (!VRGripInterfaceSettings.bIsHeld) + { + if (!newMovement.Location.ContainsNaN() && !newMovement.Rotation.ContainsNaN()) + { + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + newMovement.CopyTo(MovementRep); + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableActor::OnRep_ReplicateMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + { + return; + } + + if (RootComponent) + { + const FRepAttachment ReplicationAttachment = GetAttachmentReplication(); + if (!ReplicationAttachment.AttachParent) + { + const FRepMovement& RepMove = GetReplicatedMovement(); + + // This "fix" corrects the simulation state not replicating over correctly + // If you turn off movement replication, simulate an object, turn movement replication back on and un-simulate, it never knows the difference + // This change ensures that it is checking against the current state + if (RootComponent->IsSimulatingPhysics() != RepMove.bRepPhysics)//SavedbRepPhysics != ReplicatedMovement.bRepPhysics) + { + // Turn on/off physics sim to match server. + SyncReplicatedPhysicsSimulation(); + + // It doesn't really hurt to run it here, the super can call it again but it will fail out as they already match + } + } + } + + Super::OnRep_ReplicateMovement(); +} + +void AGrippableActor::OnRep_ReplicatedMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (ClientAuthReplicationData.bIsCurrentlyClientAuth && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::OnRep_ReplicatedMovement(); +} + +void AGrippableActor::PostNetReceivePhysicState() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if ((ClientAuthReplicationData.bIsCurrentlyClientAuth || VRGripInterfaceSettings.bIsHeld) && bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::PostNetReceivePhysicState(); +} +// This isn't called very many places but it does come up +void AGrippableActor::MarkComponentsAsPendingKill() +{ + Super::MarkComponentsAsPendingKill(); + + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +/** Called right before being marked for destruction due to network replication */ +// Clean up our objects so that they aren't sitting around for GC +void AGrippableActor::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + OnSubobjectDestroyFromReplication(SubObject); //-V595 + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + for (UActorComponent * ActorComp : GetComponents()) + { + // Pending kill components should have already had this called as they were network spawned and are being killed + if (ActorComp && IsValid(ActorComp) && ActorComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + ActorComp->PreDestroyFromReplication(); + } + + GripLogicScripts.Empty(); +} + +// On Destroy clean up our objects +void AGrippableActor::BeginDestroy() +{ + Super::BeginDestroy(); + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableActor::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*>& ObjList) +{ + Super::GetSubobjectsWithStableNamesForNetworking(ObjList); + + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableBoxComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableBoxComponent.cpp new file mode 100644 index 0000000..df86b72 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableBoxComponent.cpp @@ -0,0 +1,317 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableBoxComponent.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + +//============================================================================= +UGrippableBoxComponent::~UGrippableBoxComponent() +{ +} + + //============================================================================= +UGrippableBoxComponent::UGrippableBoxComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; +} + +void UGrippableBoxComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableBoxComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableBoxComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableBoxComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableBoxComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableBoxComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableBoxComponent, GameplayTags, COND_Custom); +} + +void UGrippableBoxComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableBoxComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableBoxComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableBoxComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableBoxComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + + +void UGrippableBoxComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableBoxComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableBoxComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableBoxComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableBoxComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableBoxComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void UGrippableBoxComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableBoxComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableBoxComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableBoxComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableBoxComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableBoxComponent::OnUsed_Implementation() {} +void UGrippableBoxComponent::OnEndUsed_Implementation() {} +void UGrippableBoxComponent::OnSecondaryUsed_Implementation() {} +void UGrippableBoxComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableBoxComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableBoxComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableBoxComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableBoxComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableBoxComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableBoxComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableBoxComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableBoxComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableBoxComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableBoxComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableBoxComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableBoxComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableBoxComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableBoxComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableBoxComponent::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableBoxComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableBoxComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableBoxComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool UGrippableBoxComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableBoxComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableBoxComponent::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +// This one is for components to clean up +void UGrippableBoxComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCapsuleComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCapsuleComponent.cpp new file mode 100644 index 0000000..241aa2f --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCapsuleComponent.cpp @@ -0,0 +1,317 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableCapsuleComponent.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableCapsuleComponent::UGrippableCapsuleComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; +} + +void UGrippableCapsuleComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableCapsuleComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableCapsuleComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableCapsuleComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableCapsuleComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableCapsuleComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableCapsuleComponent, GameplayTags, COND_Custom); +} + +void UGrippableCapsuleComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableCapsuleComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableCapsuleComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableCapsuleComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableCapsuleComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableCapsuleComponent::~UGrippableCapsuleComponent() +{ +} + +void UGrippableCapsuleComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableCapsuleComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableCapsuleComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableCapsuleComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableCapsuleComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableCapsuleComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableCapsuleComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableCapsuleComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableCapsuleComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableCapsuleComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableCapsuleComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableCapsuleComponent::OnUsed_Implementation() {} +void UGrippableCapsuleComponent::OnEndUsed_Implementation() {} +void UGrippableCapsuleComponent::OnSecondaryUsed_Implementation() {} +void UGrippableCapsuleComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableCapsuleComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableCapsuleComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + + +bool UGrippableCapsuleComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableCapsuleComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableCapsuleComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableCapsuleComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableCapsuleComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableCapsuleComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableCapsuleComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableCapsuleComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableCapsuleComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableCapsuleComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableCapsuleComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableCapsuleComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableCapsuleComponent::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableCapsuleComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableCapsuleComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableCapsuleComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool UGrippableCapsuleComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableCapsuleComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableCapsuleComponent::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableCapsuleComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCharacter.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCharacter.cpp new file mode 100644 index 0000000..1de0c0a --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableCharacter.cpp @@ -0,0 +1,29 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableCharacter.h" +#include "Grippables/GrippableSkeletalMeshComponent.h" + + +AGrippableCharacter::AGrippableCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass<UGrippableSkeletalMeshComponent>(ACharacter::MeshComponentName)) + +{ + ViewOriginationSocket = NAME_None; + GrippableMeshReference = Cast<UGrippableSkeletalMeshComponent>(GetMesh()); +} + +void AGrippableCharacter::GetActorEyesViewPoint(FVector& Location, FRotator& Rotation) const +{ + if (ViewOriginationSocket != NAME_None) + { + if (USkeletalMeshComponent* MyMesh = GetMesh()) + { + FTransform SocketTransform = MyMesh->GetSocketTransform(ViewOriginationSocket, RTS_World); + Location = SocketTransform.GetLocation(); + Rotation = SocketTransform.GetRotation().Rotator(); + return; + } + } + + Super::GetActorEyesViewPoint(Location, Rotation); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippablePhysicsReplication.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippablePhysicsReplication.cpp new file mode 100644 index 0000000..713dfa9 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippablePhysicsReplication.cpp @@ -0,0 +1,701 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "Grippables/GrippablePhysicsReplication.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" +#include "DrawDebugHelpers.h" +#if WITH_CHAOS +#include "Chaos/ChaosMarshallingManager.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "PBDRigidsSolver.h" +#include "Chaos/PBDRigidsEvolutionGBF.h" +#endif +#include "VRGlobalSettings.h" +//#include "Engine/Classes/GameFramework/PlayerController.h" +//#include "Engine/Classes/GameFramework/PlayerState.h" +//#include "Engine/Player.h" +#include "PhysicsEngine/PhysicsSettings.h" +//#include "Components/SkeletalMeshComponent.h" +#include "Misc/ScopeRWLock.h" +//#include "PhysicsInterfaceTypesCore.h" + +// I cannot dynamic cast without RTTI so I am using a static var as a declarative in case the user removed our custom replicator +// We don't want our casts to cause issues. +namespace VRPhysicsReplicationStatics +{ + static bool bHasVRPhysicsReplication = false; +} + +#if WITH_CHAOS +struct FAsyncPhysicsRepCallbackDataVR : public Chaos::FSimCallbackInput +{ + TArray<FAsyncPhysicsDesiredState> Buffer; + float LinearVelocityCoefficient; + float AngularVelocityCoefficient; + float PositionLerp; + float AngleLerp; + + void Reset() + { + Buffer.Reset(); + } +}; + +class FPhysicsReplicationAsyncCallbackVR final : public Chaos::TSimCallbackObject<FAsyncPhysicsRepCallbackDataVR> +{ + virtual void OnPreSimulate_Internal() override + { + FPhysicsReplicationVR::ApplyAsyncDesiredStateVR(GetDeltaTime_Internal(), GetConsumerInput_Internal()); + } +}; +#endif + +FPhysicsReplicationVR::~FPhysicsReplicationVR() +{ +#if WITH_CHAOS + if (AsyncCallbackServer) + { + if (auto* Solver = PhysSceneVR->GetSolver()) + { + Solver->UnregisterAndFreeSimCallbackObject_External(AsyncCallbackServer); + } + } +#endif +} + + +void ComputeDeltasVR(const FVector& CurrentPos, const FQuat& CurrentQuat, const FVector& TargetPos, const FQuat& TargetQuat, FVector& OutLinDiff, float& OutLinDiffSize, + FVector& OutAngDiffAxis, float& OutAngDiff, float& OutAngDiffSize) +{ + OutLinDiff = TargetPos - CurrentPos; + OutLinDiffSize = OutLinDiff.Size(); + const FQuat InvCurrentQuat = CurrentQuat.Inverse(); + const FQuat DeltaQuat = InvCurrentQuat * TargetQuat; + DeltaQuat.ToAxisAndAngle(OutAngDiffAxis, OutAngDiff); + OutAngDiff = FMath::RadiansToDegrees(FMath::UnwindRadians(OutAngDiff)); + OutAngDiffSize = FMath::Abs(OutAngDiff); +} + +FPhysicsReplicationVR::FPhysicsReplicationVR(FPhysScene* PhysScene) : + FPhysicsReplication(PhysScene) +{ + PhysSceneVR = PhysScene; +#if WITH_CHAOS + CurAsyncDataVR = nullptr; + AsyncCallbackServer = nullptr; +#endif + VRPhysicsReplicationStatics::bHasVRPhysicsReplication = true; +} + +#if WITH_CHAOS + +void FPhysicsReplicationVR::PrepareAsyncData_ExternalVR(const FRigidBodyErrorCorrection& ErrorCorrection) +{ + //todo move this logic into a common function? + static const auto CVarLinSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.PositionLerp")); + const float PositionLerp = CVarLinSet->GetFloat() >= 0.0f ? CVarLinSet->GetFloat() : ErrorCorrection.PositionLerp; + + static const auto CVarLinLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.LinearVelocityCoefficient")); + const float LinearVelocityCoefficient = CVarLinLerp->GetFloat() >= 0.0f ? CVarLinLerp->GetFloat() : ErrorCorrection.LinearVelocityCoefficient; + + static const auto CVarAngSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngleLerp")); + const float AngleLerp = CVarAngSet->GetFloat() >= 0.0f ? CVarAngSet->GetFloat() : ErrorCorrection.AngleLerp; + + static const auto CVarAngLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngularVelocityCoefficient")); + const float AngularVelocityCoefficient = CVarAngLerp->GetFloat() >= 0.0f ? CVarAngLerp->GetFloat() : ErrorCorrection.AngularVelocityCoefficient; + + CurAsyncDataVR = AsyncCallbackServer->GetProducerInputData_External(); + CurAsyncDataVR->PositionLerp = PositionLerp; + CurAsyncDataVR->AngleLerp = AngleLerp; + CurAsyncDataVR->LinearVelocityCoefficient = LinearVelocityCoefficient; + CurAsyncDataVR->AngularVelocityCoefficient = AngularVelocityCoefficient; +} + +void FPhysicsReplicationVR::ApplyAsyncDesiredStateVR(const float DeltaSeconds, const FAsyncPhysicsRepCallbackDataVR* AsyncData) +{ + using namespace Chaos; + if (AsyncData) + { + const float LinearVelocityCoefficient = AsyncData->LinearVelocityCoefficient; + const float AngularVelocityCoefficient = AsyncData->AngularVelocityCoefficient; + const float PositionLerp = AsyncData->PositionLerp; + const float AngleLerp = AsyncData->AngleLerp; + + for (const FAsyncPhysicsDesiredState& State : AsyncData->Buffer) + { + //Proxy should exist because we are using latest and any pending deletes would have been enqueued after + FSingleParticlePhysicsProxy* Proxy = State.Proxy; + auto* Handle = Proxy->GetPhysicsThreadAPI(); + + + if (Handle && Handle->CanTreatAsRigid()) + { + const FVector TargetPos = State.WorldTM.GetLocation(); + const FQuat TargetQuat = State.WorldTM.GetRotation(); + + // Get Current state + FRigidBodyState CurrentState; + CurrentState.Position = Handle->X(); + CurrentState.Quaternion = Handle->R(); + CurrentState.AngVel = Handle->W(); + CurrentState.LinVel = Handle->V(); + + FVector LinDiff; + float LinDiffSize; + FVector AngDiffAxis; + float AngDiff; + float AngDiffSize; + ComputeDeltasVR(CurrentState.Position, CurrentState.Quaternion, TargetPos, TargetQuat, LinDiff, LinDiffSize, AngDiffAxis, AngDiff, AngDiffSize); + + const FVector NewLinVel = FVector(State.LinearVelocity) + (LinDiff * LinearVelocityCoefficient * DeltaSeconds); + const FVector NewAngVel = FVector(State.AngularVelocity) + (AngDiffAxis * AngDiff * AngularVelocityCoefficient * DeltaSeconds); + + const FVector NewPos = FMath::Lerp(FVector(CurrentState.Position), TargetPos, PositionLerp); + const FQuat NewAng = FQuat::Slerp(CurrentState.Quaternion, TargetQuat, AngleLerp); + + Handle->SetX(NewPos); + Handle->SetR(NewAng); + Handle->SetV(NewLinVel); + Handle->SetW(FMath::DegreesToRadians(NewAngVel)); + + EObjectStateType ObjectStateType = State.ObjectState; + static const auto CVarApplyAsyncSleepState = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ApplyAsyncSleepState")); + if ((CVarApplyAsyncSleepState->GetInt() != 0) && State.bShouldSleep) + { + ObjectStateType = EObjectStateType::Sleeping; + } + // don't allow kinematic to sleeping transition + bool bInvalidObjectState = (ObjectStateType == EObjectStateType::Sleeping && Handle->ObjectState() == EObjectStateType::Kinematic); + if (!bInvalidObjectState) + { + auto* Solver = Proxy->GetSolver<FPBDRigidsSolver>(); + Solver->GetEvolution()->SetParticleObjectState(Proxy->GetHandle_LowLevel()->CastToRigidParticle(), ObjectStateType); //todo: move object state into physics thread api + } + } + } + } +} +#endif + + +bool FPhysicsReplicationVR::IsInitialized() +{ + return VRPhysicsReplicationStatics::bHasVRPhysicsReplication; +} + +bool FPhysicsReplicationVR::ApplyRigidBodyState(float DeltaSeconds, FBodyInstance* BI, FReplicatedPhysicsTarget& PhysicsTarget, const FRigidBodyErrorCorrection& ErrorCorrection, const float PingSecondsOneWay) +{ + // Skip all of the custom logic if we aren't the server + if (const UWorld* World = GetOwningWorld()) + { + if (World->GetNetMode() == ENetMode::NM_Client) + { + return FPhysicsReplication::ApplyRigidBodyState(DeltaSeconds, BI, PhysicsTarget, ErrorCorrection, PingSecondsOneWay); + } + } + + static const auto CVarSkipPhysicsReplication = IConsoleManager::Get().FindConsoleVariable(TEXT("p.SkipPhysicsReplication")); + if (CVarSkipPhysicsReplication->GetInt()) + { + return false; + } + + if (!BI->IsInstanceSimulatingPhysics()) + { + return false; + } + + // + // NOTES: + // + // The operation of this method has changed since 4.18. + // + // When a new remote physics state is received, this method will + // be called on tick until the local state is within an adequate + // tolerance of the new state. + // + // The received state is extrapolated based on ping, by some + // adjustable amount. + // + // A correction velocity is added new state's velocity, and assigned + // to the body. The correction velocity scales with the positional + // difference, so without the interference of external forces, this + // will result in an exponentially decaying correction. + // + // Generally it is not needed and will interrupt smoothness of + // the replication, but stronger corrections can be obtained by + // adjusting position lerping. + // + // If progress is not being made towards equilibrium, due to some + // divergence in physics states between the owning and local sims, + // an error value is accumulated, representing the amount of time + // spent in an unresolvable state. + // + // Once the error value has exceeded some threshold (0.5 seconds + // by default), a hard snap to the target physics state is applied. + // + + bool bRestoredState = true; + const FRigidBodyState NewState = PhysicsTarget.TargetState; + const float NewQuatSizeSqr = NewState.Quaternion.SizeSquared(); + + // failure cases + if (!BI->IsInstanceSimulatingPhysics()) + { + UE_LOG(LogPhysics, Warning, TEXT("Physics replicating on non-simulated body. (%s)"), *BI->GetBodyDebugName()); + return bRestoredState; + } + else if (NewQuatSizeSqr < KINDA_SMALL_NUMBER) + { + UE_LOG(LogPhysics, Warning, TEXT("Invalid zero quaternion set for body. (%s)"), *BI->GetBodyDebugName()); + return bRestoredState; + } + else if (FMath::Abs(NewQuatSizeSqr - 1.f) > KINDA_SMALL_NUMBER) + { + UE_LOG(LogPhysics, Warning, TEXT("Quaternion (%f %f %f %f) with non-unit magnitude detected. (%s)"), + NewState.Quaternion.X, NewState.Quaternion.Y, NewState.Quaternion.Z, NewState.Quaternion.W, *BI->GetBodyDebugName()); + return bRestoredState; + } + + // Grab configuration variables from engine config or from CVars if overriding is turned on. + static const auto CVarNetPingExtrapolation = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetPingExtrapolation")); + const float NetPingExtrapolation = CVarNetPingExtrapolation->GetFloat() >= 0.0f ? CVarNetPingExtrapolation->GetFloat() : ErrorCorrection.PingExtrapolation; + + static const auto CVarNetPingLimit = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetPingLimit")); + const float NetPingLimit = CVarNetPingLimit->GetFloat() > 0.0f ? CVarNetPingLimit->GetFloat() : ErrorCorrection.PingLimit; + + static const auto CVarErrorPerLinearDifference = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorPerLinearDifference")); + const float ErrorPerLinearDiff = CVarErrorPerLinearDifference->GetFloat() >= 0.0f ? CVarErrorPerLinearDifference->GetFloat() : ErrorCorrection.ErrorPerLinearDifference; + + static const auto CVarErrorPerAngularDifference = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorPerAngularDifference")); + const float ErrorPerAngularDiff = CVarErrorPerAngularDifference->GetFloat() >= 0.0f ? CVarErrorPerAngularDifference->GetFloat() : ErrorCorrection.ErrorPerAngularDifference; + + static const auto CVarMaxRestoredStateError = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxRestoredStateError")); + const float MaxRestoredStateError = CVarMaxRestoredStateError->GetFloat() >= 0.0f ? CVarMaxRestoredStateError->GetFloat() : ErrorCorrection.MaxRestoredStateError; + + static const auto CVarErrorAccumulation = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationSeconds")); + const float ErrorAccumulationSeconds = CVarErrorAccumulation->GetFloat() >= 0.0f ? CVarErrorAccumulation->GetFloat() : ErrorCorrection.ErrorAccumulationSeconds; + + static const auto CVarErrorAccumulationDistanceSq = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationDistanceSq")); + const float ErrorAccumulationDistanceSq = CVarErrorAccumulationDistanceSq->GetFloat() >= 0.0f ? CVarErrorAccumulationDistanceSq->GetFloat() : ErrorCorrection.ErrorAccumulationDistanceSq; + + static const auto CVarErrorAccumulationSimilarity = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ErrorAccumulationSimilarity")); + const float ErrorAccumulationSimilarity = CVarErrorAccumulationSimilarity->GetFloat() >= 0.0f ? CVarErrorAccumulationSimilarity->GetFloat() : ErrorCorrection.ErrorAccumulationSimilarity; + + static const auto CVarLinSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.PositionLerp")); + const float PositionLerp = CVarLinSet->GetFloat() >= 0.0f ? CVarLinSet->GetFloat() : ErrorCorrection.PositionLerp; + + static const auto CVarLinLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.LinearVelocityCoefficient")); + const float LinearVelocityCoefficient = CVarLinLerp->GetFloat() >= 0.0f ? CVarLinLerp->GetFloat() : ErrorCorrection.LinearVelocityCoefficient; + + static const auto CVarAngSet = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngleLerp")); + const float AngleLerp = CVarAngSet->GetFloat() >= 0.0f ? CVarAngSet->GetFloat() : ErrorCorrection.AngleLerp; + + static const auto CVarAngLerp = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AngularVelocityCoefficient")); + const float AngularVelocityCoefficient = CVarAngLerp->GetFloat() >= 0.0f ? CVarAngLerp->GetFloat() : ErrorCorrection.AngularVelocityCoefficient; + + static const auto CVarMaxLinearHardSnapDistance = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxLinearHardSnapDistance")); + const float MaxLinearHardSnapDistance = CVarMaxLinearHardSnapDistance->GetFloat() >= 0.f ? CVarMaxLinearHardSnapDistance->GetFloat() : ErrorCorrection.MaxLinearHardSnapDistance; + + // Get Current state + FRigidBodyState CurrentState; + BI->GetRigidBodyState(CurrentState); + + /////// EXTRAPOLATE APPROXIMATE TARGET VALUES /////// + + // Starting from the last known authoritative position, and + // extrapolate an approximation using the last known velocity + // and ping. + const float PingSeconds = FMath::Clamp(PingSecondsOneWay, 0.f, NetPingLimit); + const float ExtrapolationDeltaSeconds = PingSeconds * NetPingExtrapolation; + const FVector ExtrapolationDeltaPos = NewState.LinVel * ExtrapolationDeltaSeconds; + const FVector_NetQuantize100 TargetPos = NewState.Position + ExtrapolationDeltaPos; + float NewStateAngVel; + FVector NewStateAngVelAxis; + NewState.AngVel.FVector::ToDirectionAndLength(NewStateAngVelAxis, NewStateAngVel); + NewStateAngVel = FMath::DegreesToRadians(NewStateAngVel); + const FQuat ExtrapolationDeltaQuaternion = FQuat(NewStateAngVelAxis, NewStateAngVel * ExtrapolationDeltaSeconds); + FQuat TargetQuat = ExtrapolationDeltaQuaternion * NewState.Quaternion; + + /////// COMPUTE DIFFERENCES /////// + + FVector LinDiff; + float LinDiffSize; + FVector AngDiffAxis; + float AngDiff; + + float AngDiffSize; + + ComputeDeltasVR(CurrentState.Position, CurrentState.Quaternion, TargetPos, TargetQuat, LinDiff, LinDiffSize, AngDiffAxis, AngDiff, AngDiffSize); + + /////// ACCUMULATE ERROR IF NOT APPROACHING SOLUTION /////// + + // Store sleeping state + const bool bShouldSleep = (NewState.Flags & ERigidBodyFlags::Sleeping) != 0; + const bool bWasAwake = BI->IsInstanceAwake(); + const bool bAutoWake = false; + + const float Error = (LinDiffSize * ErrorPerLinearDiff) + (AngDiffSize * ErrorPerAngularDiff); + bRestoredState = Error < MaxRestoredStateError; + if (bRestoredState) + { + PhysicsTarget.AccumulatedErrorSeconds = 0.0f; + } + else + { + // + // The heuristic for error accumulation here is: + // 1. Did the physics tick from the previous step fail to + // move the body towards a resolved position? + // 2. Was the linear error in the same direction as the + // previous frame? + // 3. Is the linear error large enough to accumulate error? + // + // If these conditions are met, then "error" time will accumulate. + // Once error has accumulated for a certain number of seconds, + // a hard-snap to the target will be performed. + // + // TODO: Rotation while moving linearly can still mess up this + // heuristic. We need to account for it. + // + + // Project the change in position from the previous tick onto the + // linear error from the previous tick. This value roughly represents + // how much correction was performed over the previous physics tick. + const float PrevProgress = FVector::DotProduct( + FVector(CurrentState.Position) - PhysicsTarget.PrevPos, + (PhysicsTarget.PrevPosTarget - PhysicsTarget.PrevPos).GetSafeNormal()); + + // Project the current linear error onto the linear error from the + // previous tick. This value roughly represents how little the direction + // of the linear error state has changed, and how big the error is. + const float PrevSimilarity = FVector::DotProduct( + TargetPos - FVector(CurrentState.Position), + PhysicsTarget.PrevPosTarget - PhysicsTarget.PrevPos); + + // If the conditions from the heuristic outlined above are met, accumulate + // error. Otherwise, reduce it. + if (PrevProgress < ErrorAccumulationDistanceSq && + PrevSimilarity > ErrorAccumulationSimilarity) + { + PhysicsTarget.AccumulatedErrorSeconds += DeltaSeconds; + } + else + { + PhysicsTarget.AccumulatedErrorSeconds = FMath::Max(PhysicsTarget.AccumulatedErrorSeconds - DeltaSeconds, 0.0f); + } + + // Hard snap if error accumulation or linear error is big enough, and clear the error accumulator. + static const auto CVarAlwaysHardSnap = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AlwaysHardSnap")); + const bool bHardSnap = + LinDiffSize > MaxLinearHardSnapDistance || + PhysicsTarget.AccumulatedErrorSeconds > ErrorAccumulationSeconds || + CVarAlwaysHardSnap->GetInt(); + + const FTransform IdealWorldTM(TargetQuat, TargetPos); + + if (bHardSnap) + { + // Too much error so just snap state here and be done with it + PhysicsTarget.AccumulatedErrorSeconds = 0.0f; + bRestoredState = true; + BI->SetBodyTransform(IdealWorldTM, ETeleportType::ResetPhysics, bAutoWake); + + // Set the new velocities + BI->SetLinearVelocity(NewState.LinVel, false, bAutoWake); + BI->SetAngularVelocityInRadians(FMath::DegreesToRadians(NewState.AngVel), false, bAutoWake); + } + else + { + // Small enough error to interpolate +#if WITH_CHAOS + if (AsyncCallbackServer == nullptr) //sync case +#endif + { + const FVector NewLinVel = FVector(NewState.LinVel) + (LinDiff * LinearVelocityCoefficient * DeltaSeconds); + const FVector NewAngVel = FVector(NewState.AngVel) + (AngDiffAxis * AngDiff * AngularVelocityCoefficient * DeltaSeconds); + + const FVector NewPos = FMath::Lerp(FVector(CurrentState.Position), FVector(TargetPos), PositionLerp); + const FQuat NewAng = FQuat::Slerp(CurrentState.Quaternion, TargetQuat, AngleLerp); + + BI->SetBodyTransform(FTransform(NewAng, NewPos), ETeleportType::ResetPhysics); + BI->SetLinearVelocity(NewLinVel, false); + BI->SetAngularVelocityInRadians(FMath::DegreesToRadians(NewAngVel), false); + } +#if WITH_CHAOS + else + { + //If async is used, enqueue for callback + FAsyncPhysicsDesiredState AsyncDesiredState; + AsyncDesiredState.WorldTM = IdealWorldTM; + AsyncDesiredState.LinearVelocity = NewState.LinVel; + AsyncDesiredState.AngularVelocity = NewState.AngVel; + AsyncDesiredState.Proxy = static_cast<FSingleParticlePhysicsProxy*>(BI->GetPhysicsActorHandle()); + AsyncDesiredState.ObjectState = AsyncDesiredState.Proxy->GetGameThreadAPI().ObjectState(); + AsyncDesiredState.bShouldSleep = bShouldSleep; + CurAsyncDataVR->Buffer.Add(AsyncDesiredState); + } +#endif + } + + // Should we show the async part? +#if !UE_BUILD_SHIPPING + static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections")); + if (CVarNetShowCorrections->GetInt() != 0) + { + PhysicsTarget.ErrorHistory.bAutoAdjustMinMax = false; + PhysicsTarget.ErrorHistory.MinValue = 0.0f; + PhysicsTarget.ErrorHistory.MaxValue = 1.0f; + PhysicsTarget.ErrorHistory.AddSample(PhysicsTarget.AccumulatedErrorSeconds / ErrorAccumulationSeconds); + if (UWorld* OwningWorld = GetOwningWorld()) + { + FColor Color = FColor::White; + static const auto CVarNetCorrectionLifetime = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetCorrectionLifetime")); + DrawDebugDirectionalArrow(OwningWorld, CurrentState.Position, TargetPos, 5.0f, Color, true, CVarNetCorrectionLifetime->GetFloat(), 0, 1.5f); +#if 0 + //todo: do we show this in async mode? + DrawDebugFloatHistory(*OwningWorld, PhysicsTarget.ErrorHistory, NewPos + FVector(0.0f, 0.0f, 100.0f), FVector2D(100.0f, 50.0f), FColor::White); +#endif + } + } +#endif + } + + /////// SLEEP UPDATE /////// + +#if WITH_CHAOS + if (bShouldSleep) + { + // In the async case, we apply sleep state in ApplyAsyncDesiredState + if (AsyncCallbackServer == nullptr) + { + BI->PutInstanceToSleep(); + } + } +#endif + + PhysicsTarget.PrevPosTarget = TargetPos; + PhysicsTarget.PrevPos = FVector(CurrentState.Position); + + return bRestoredState; +} + +void FPhysicsReplicationVR::OnTick(float DeltaSeconds, TMap<TWeakObjectPtr<UPrimitiveComponent>, FReplicatedPhysicsTarget>& ComponentsToTargets) +{ + // Skip all of the custom logic if we aren't the server + if (const UWorld* World = GetOwningWorld()) + { + if (World->GetNetMode() == ENetMode::NM_Client) + { + return FPhysicsReplication::OnTick(DeltaSeconds, ComponentsToTargets); + } + } + +#if WITH_CHAOS + using namespace Chaos; + if (AsyncCallbackServer == nullptr) + { + if (auto* Solver = PhysSceneVR->GetSolver()) + { + AsyncCallbackServer = Solver->CreateAndRegisterSimCallbackObject_External<FPhysicsReplicationAsyncCallbackVR>(); + } + } +#endif + + const FRigidBodyErrorCorrection& PhysicErrorCorrection = UPhysicsSettings::Get()->PhysicErrorCorrection; + +#if WITH_CHAOS + using namespace Chaos; + if (AsyncCallbackServer) + { + PrepareAsyncData_ExternalVR(PhysicErrorCorrection); + } +#endif + + // Get the ping between this PC & the server + const float LocalPing = 0.0f;//GetLocalPing(); + + /*float CurrentTimeSeconds = 0.0f; + + if (UWorld* OwningWorld = GetOwningWorld()) + { + CurrentTimeSeconds = OwningWorld->GetTimeSeconds(); + }*/ + + for (auto Itr = ComponentsToTargets.CreateIterator(); Itr; ++Itr) + { + + // Its been more than half a second since the last update, lets cease using the target as a failsafe + // Clients will never update with that much latency, and if they somehow are, then they are dropping so many + // packets that it will be useless to use their data anyway + /*if ((CurrentTimeSeconds - Itr.Value().ArrivedTimeSeconds) > 0.5f) + { + OnTargetRestored(Itr.Key().Get(), Itr.Value()); + Itr.RemoveCurrent(); + } + else */if (UPrimitiveComponent* PrimComp = Itr.Key().Get()) + { + bool bRemoveItr = false; + + if (FBodyInstance* BI = PrimComp->GetBodyInstance(Itr.Value().BoneName)) + { + FReplicatedPhysicsTarget& PhysicsTarget = Itr.Value(); + FRigidBodyState& UpdatedState = PhysicsTarget.TargetState; + bool bUpdated = false; + if (AActor* OwningActor = PrimComp->GetOwner()) + { + + // Remove if there is no owner + if (!OwningActor->GetNetOwningPlayer()) + { + bRemoveItr = true; + } + else + { + // Removed as this is server sided + /*const ENetRole OwnerRole = OwningActor->GetLocalRole(); + const bool bIsSimulated = OwnerRole == ROLE_SimulatedProxy; + const bool bIsReplicatedAutonomous = OwnerRole == ROLE_AutonomousProxy && PrimComp->bReplicatePhysicsToAutonomousProxy; + if (bIsSimulated || bIsReplicatedAutonomous)*/ + + + // Deleted everything here, we will always be the server, I already filtered out clients to default logic + { + /*const*/ float OwnerPing = 0.0f;// GetOwnerPing(OwningActor, PhysicsTarget); + + /*if (UPlayer* OwningPlayer = OwningActor->GetNetOwningPlayer()) + { + if (APlayerController* PlayerController = OwningPlayer->GetPlayerController(nullptr)) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + OwnerPing = PlayerState->ExactPing; + } + } + }*/ + + // Get the total ping - this approximates the time since the update was + // actually generated on the machine that is doing the authoritative sim. + // NOTE: We divide by 2 to approximate 1-way ping from 2-way ping. + const float PingSecondsOneWay = 0.0f;// (LocalPing + OwnerPing) * 0.5f * 0.001f; + + + if (UpdatedState.Flags & ERigidBodyFlags::NeedsUpdate) + { + const bool bRestoredState = ApplyRigidBodyState(DeltaSeconds, BI, PhysicsTarget, PhysicErrorCorrection, PingSecondsOneWay); + + // Need to update the component to match new position. + static const auto CVarSkipSkeletalRepOptimization = IConsoleManager::Get().FindConsoleVariable(TEXT("p.SkipSkeletalRepOptimization")); + if (/*PhysicsReplicationCVars::SkipSkeletalRepOptimization*/CVarSkipSkeletalRepOptimization->GetInt() == 0 || Cast<USkeletalMeshComponent>(PrimComp) == nullptr) //simulated skeletal mesh does its own polling of physics results so we don't need to call this as it'll happen at the end of the physics sim + { + PrimComp->SyncComponentToRBPhysics(); + } + + // Added a sleeping check from the input state as well, we always want to cease activity on sleep + if (bRestoredState || ((UpdatedState.Flags & ERigidBodyFlags::Sleeping) != 0)) + { + bRemoveItr = true; + } + } + } + } + } + } + + if (bRemoveItr) + { + OnTargetRestored(Itr.Key().Get(), Itr.Value()); + Itr.RemoveCurrent(); + } + } + } +#if WITH_CHAOS + CurAsyncDataVR = nullptr; +#endif + + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Phys Rep Tick!")); + //FPhysicsReplication::OnTick(DeltaSeconds, ComponentsToTargets); +} + +FRepMovementVR::FRepMovementVR() : FRepMovement() +{ + LocationQuantizationLevel = EVectorQuantization::RoundTwoDecimals; + VelocityQuantizationLevel = EVectorQuantization::RoundTwoDecimals; + RotationQuantizationLevel = ERotatorQuantization::ShortComponents; +} + +FRepMovementVR::FRepMovementVR(FRepMovement& other) : FRepMovement() +{ + FRepMovementVR(); + + LinearVelocity = other.LinearVelocity; + AngularVelocity = other.AngularVelocity; + Location = other.Location; + Rotation = other.Rotation; + bSimulatedPhysicSleep = other.bSimulatedPhysicSleep; + bRepPhysics = other.bRepPhysics; +} + +void FRepMovementVR::CopyTo(FRepMovement& other) const +{ + other.LinearVelocity = LinearVelocity; + other.AngularVelocity = AngularVelocity; + other.Location = Location; + other.Rotation = Rotation; + other.bSimulatedPhysicSleep = bSimulatedPhysicSleep; + other.bRepPhysics = bRepPhysics; +} + +bool FRepMovementVR::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + return FRepMovement::NetSerialize(Ar, Map, bOutSuccess); +} + +bool FRepMovementVR::GatherActorsMovement(AActor* OwningActor) +{ + //if (/*bReplicateMovement || (RootComponent && RootComponent->GetAttachParent())*/) + { + UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(OwningActor->GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + + FillFrom(RBState, OwningActor); + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + bRepPhysics = !RootPrimComp->IsWelded(); + } + else if (RootPrimComp != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootPrimComp->GetAttachParent() != nullptr) + { + return false; // We don't handle attachment rep + + } + else + { + Location = FRepMovement::RebaseOntoZeroOrigin(RootPrimComp->GetComponentLocation(), OwningActor); + Rotation = RootPrimComp->GetComponentRotation(); + LinearVelocity = OwningActor->GetVelocity(); + AngularVelocity = FVector::ZeroVector; + } + + bRepPhysics = false; + } + } + + /*if (const UWorld* World = GetOwningWorld()) + { + if (APlayerController* PlayerController = World->GetFirstPlayerController()) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + CurrentPing = PlayerState->ExactPing; + } + } + }*/ + + return true; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshActor.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshActor.cpp new file mode 100644 index 0000000..72b8a05 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshActor.cpp @@ -0,0 +1,803 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableSkeletalMeshActor.h" +#include "TimerManager.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Misc/BucketUpdateSubsystem.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + +UOptionalRepSkeletalMeshComponent::UOptionalRepSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = true; +} + +void UOptionalRepSkeletalMeshComponent::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UOptionalRepSkeletalMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UOptionalRepSkeletalMeshComponent, bReplicateMovement); +} + +//============================================================================= +AGrippableSkeletalMeshActor::AGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass<UOptionalRepSkeletalMeshComponent>(TEXT("SkeletalMeshComponent0"))) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + // Default replication on for multiplayer + //this->bNetLoadOnClient = false; + SetReplicatingMovement(true); + bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + bAllowIgnoringAttachOnOwner = true; + + // Setting a minimum of every 3rd frame (VR 90fps) for replication consideration + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 30.0f; +} + +void AGrippableSkeletalMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AGrippableSkeletalMeshActor, GripLogicScripts, COND_Custom); + DOREPLIFETIME(AGrippableSkeletalMeshActor, bReplicateGripScripts); + DOREPLIFETIME(AGrippableSkeletalMeshActor, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(AGrippableSkeletalMeshActor, bAllowIgnoringAttachOnOwner); + DOREPLIFETIME(AGrippableSkeletalMeshActor, ClientAuthReplicationData); + DOREPLIFETIME_CONDITION(AGrippableSkeletalMeshActor, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(AGrippableSkeletalMeshActor, GameplayTags, COND_Custom); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); + + FDoRepLifetimeParams AttachmentReplicationParams{ COND_Custom, REPNOTIFY_Always, /*bIsPushBased=*/true }; + DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableSkeletalMeshActor, AttachmentWeldReplication, AttachmentReplicationParams); +} + +void AGrippableSkeletalMeshActor::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableSkeletalMeshActor, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableSkeletalMeshActor, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableSkeletalMeshActor, GripLogicScripts, bReplicateGripScripts); + + //Super::PreReplication(ChangedPropertyTracker); + +#if WITH_PUSH_MODEL + const AActor* const OldAttachParent = AttachmentWeldReplication.AttachParent; + const UActorComponent* const OldAttachComponent = AttachmentWeldReplication.AttachComponent; +#endif + + // Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment. + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + GatherCurrentMovement(); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(AActor, ReplicatedMovement, IsReplicatingMovement()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableSkeletalMeshActor, AttachmentWeldReplication, RootComponent && !RootComponent->GetIsReplicated()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(AActor, AttachmentReplication, false);// RootComponent && !RootComponent->GetIsReplicated()); + + +#if WITH_PUSH_MODEL + if (UNLIKELY(OldAttachParent != AttachmentWeldReplication.AttachParent || OldAttachComponent != AttachmentWeldReplication.AttachComponent)) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableSkeletalMeshActor, AttachmentWeldReplication, this); + } +#endif + + PRAGMA_DISABLE_DEPRECATION_WARNINGS + UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()); + if (BPClass != nullptr) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS +} + +void AGrippableSkeletalMeshActor::GatherCurrentMovement() +{ + if (IsReplicatingMovement() || (RootComponent && RootComponent->GetAttachParent())) + { + bool bWasAttachmentModified = false; + bool bWasRepMovementModified = false; + + AActor* OldAttachParent = AttachmentWeldReplication.AttachParent; + USceneComponent* OldAttachComponent = AttachmentWeldReplication.AttachComponent; + + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + FRepMovement& RepMovement = GetReplicatedMovement_Mutable(); + + UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + + RepMovement.FillFrom(RBState, this); + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + RepMovement.bRepPhysics = !RootPrimComp->IsWelded(); + + if (!RepMovement.bRepPhysics) + { + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + } + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + else if (RootComponent != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + else + { + RepMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this); + RepMovement.Rotation = RootComponent->GetComponentRotation(); + RepMovement.LinearVelocity = GetVelocity(); + RepMovement.AngularVelocity = FVector::ZeroVector; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + + bWasRepMovementModified = (bWasRepMovementModified || RepMovement.bRepPhysics); + RepMovement.bRepPhysics = false; + } +#if WITH_PUSH_MODEL + if (bWasRepMovementModified) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AActor, ReplicatedMovement, this); + } + + if (bWasAttachmentModified || + OldAttachParent != AttachmentWeldReplication.AttachParent || + OldAttachComponent != AttachmentWeldReplication.AttachComponent) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableSkeletalMeshActor, AttachmentWeldReplication, this); + } +#endif + } +} + +bool AGrippableSkeletalMeshActor::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch* Bunch, FReplicationFlags* RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +/*void AGrippableSkeletalMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + DOREPLIFETIME(AGrippableSkeletalMeshActor, VRGripInterfaceSettings); +}*/ + +//============================================================================= +AGrippableSkeletalMeshActor::~AGrippableSkeletalMeshActor() +{ +} + +void AGrippableSkeletalMeshActor::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } +} + +void AGrippableSkeletalMeshActor::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void AGrippableSkeletalMeshActor::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void AGrippableSkeletalMeshActor::TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) {} +void AGrippableSkeletalMeshActor::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void AGrippableSkeletalMeshActor::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void AGrippableSkeletalMeshActor::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void AGrippableSkeletalMeshActor::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void AGrippableSkeletalMeshActor::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void AGrippableSkeletalMeshActor::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void AGrippableSkeletalMeshActor::OnUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnEndUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnSecondaryUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnEndSecondaryUsed_Implementation() {} +void AGrippableSkeletalMeshActor::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool AGrippableSkeletalMeshActor::RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) { return false; } + +bool AGrippableSkeletalMeshActor::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + + +EGripInterfaceTeleportBehavior AGrippableSkeletalMeshActor::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool AGrippableSkeletalMeshActor::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType AGrippableSkeletalMeshActor::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType AGrippableSkeletalMeshActor::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings AGrippableSkeletalMeshActor::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings AGrippableSkeletalMeshActor::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void AGrippableSkeletalMeshActor::GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings AGrippableSkeletalMeshActor::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float AGrippableSkeletalMeshActor::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void AGrippableSkeletalMeshActor::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool AGrippableSkeletalMeshActor::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void AGrippableSkeletalMeshActor::IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +bool AGrippableSkeletalMeshActor::AddToClientReplicationBucket() +{ + if (ShouldWeSkipAttachmentReplication(false)) + { + // The subsystem automatically removes entries with the same function signature so its safe to just always add here + GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->AddObjectToBucket(ClientAuthReplicationData.UpdateRate, this, FName(TEXT("PollReplicationEvent"))); + ClientAuthReplicationData.bIsCurrentlyClientAuth = true; + + if (UWorld* World = GetWorld()) + ClientAuthReplicationData.TimeAtInitialThrow = World->GetTimeSeconds(); + + return true; + } + + return false; +} + +bool AGrippableSkeletalMeshActor::RemoveFromClientReplicationBucket() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + { + GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->RemoveObjectFromBucketByFunctionName(this, FName(TEXT("PollReplicationEvent"))); + CeaseReplicationBlocking(); + return true; + } + + return false; +} + +void AGrippableSkeletalMeshActor::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void AGrippableSkeletalMeshActor::SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + RemoveFromClientReplicationBucket(); + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + } + else + { + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + + if (ClientAuthReplicationData.bUseClientAuthThrowing && !VRGripInterfaceSettings.bIsHeld) + { + bool bWasLocallyOwned = HoldingController ? HoldingController->IsLocallyControlled() : false; + if (bWasLocallyOwned && ShouldWeSkipAttachmentReplication(false)) + { + if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(GetRootComponent())) + { + if (PrimComp->IsSimulatingPhysics()) + { + AddToClientReplicationBucket(); + } + } + } + } + } +} + +/*FBPInteractionSettings AGrippableSkeletalMeshActor::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool AGrippableSkeletalMeshActor::GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +bool AGrippableSkeletalMeshActor::PollReplicationEvent() +{ + if (!ClientAuthReplicationData.bIsCurrentlyClientAuth || !this->HasLocalNetOwner() || VRGripInterfaceSettings.bIsHeld) + return false; // Tell the bucket subsystem to remove us from consideration + + UWorld* OurWorld = GetWorld(); + if (!OurWorld) + return false; // Tell the bucket subsystem to remove us from consideration + + bool bRemoveBlocking = false; + + if ((OurWorld->GetTimeSeconds() - ClientAuthReplicationData.TimeAtInitialThrow) > 10.0f) + { + // Lets time out sending, its been 10 seconds since we threw the object and its likely that it is conflicting with some server + // Authed movement that is forcing it to keep momentum. + //return false; // Tell the bucket subsystem to remove us from consideration + bRemoveBlocking = true; + } + + // Store current transform for resting check + FTransform CurTransform = this->GetActorTransform(); + + if (!bRemoveBlocking) + { + if (!CurTransform.GetRotation().Equals(ClientAuthReplicationData.LastActorTransform.GetRotation()) || !CurTransform.GetLocation().Equals(ClientAuthReplicationData.LastActorTransform.GetLocation())) + { + ClientAuthReplicationData.LastActorTransform = CurTransform; + + if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(RootComponent)) + { + // Need to clamp to a max time since start, to handle cases with conflicting collisions + if (PrimComp->IsSimulatingPhysics() && ShouldWeSkipAttachmentReplication(false)) + { + FRepMovementVR ClientAuthMovementRep; + if (ClientAuthMovementRep.GatherActorsMovement(this)) + { + Server_GetClientAuthReplication(ClientAuthMovementRep); + + if (PrimComp->RigidBodyIsAwake()) + { + return true; + } + } + } + } + else + { + bRemoveBlocking = true; + //return false; // Tell the bucket subsystem to remove us from consideration + } + } + //else + // { + // Difference is too small, lets end sending location + //ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + // } + } + + bool TimedBlockingRelease = false; + + AActor* TopOwner = GetOwner(); + if (TopOwner != nullptr) + { + AActor* tempOwner = TopOwner->GetOwner(); + + // I have an owner so search that for the top owner + while (tempOwner) + { + TopOwner = tempOwner; + tempOwner = TopOwner->GetOwner(); + } + + if (APlayerController* PlayerController = Cast<APlayerController>(TopOwner)) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + + // Lets clamp the ping to a min / max value just in case + float clampedPing = FMath::Clamp(PlayerState->ExactPing * 0.001f, 0.0f, 1000.0f); + OurWorld->GetTimerManager().SetTimer(ClientAuthReplicationData.ResetReplicationHandle, this, &AGrippableSkeletalMeshActor::CeaseReplicationBlocking, clampedPing, false); + TimedBlockingRelease = true; + } + } + } + + if (!TimedBlockingRelease) + { + CeaseReplicationBlocking(); + } + + // Tell server to kill us + Server_EndClientAuthReplication(); + return false; // Tell the bucket subsystem to remove us from consideration +} + +void AGrippableSkeletalMeshActor::CeaseReplicationBlocking() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + ClientAuthReplicationData.bIsCurrentlyClientAuth = false; + + ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + if (UWorld* OurWorld = GetWorld()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + } +} + +void AGrippableSkeletalMeshActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + RemoveFromClientReplicationBucket(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } + + Super::EndPlay(EndPlayReason); +} + +bool AGrippableSkeletalMeshActor::Server_EndClientAuthReplication_Validate() +{ + return true; +} + +void AGrippableSkeletalMeshActor::Server_EndClientAuthReplication_Implementation() +{ + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (FPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + PhysicsReplication->RemoveReplicatedTarget(this->GetSkeletalMeshComponent()); + } + } + } +} + +bool AGrippableSkeletalMeshActor::Server_GetClientAuthReplication_Validate(const FRepMovementVR& newMovement) +{ + return true; +} + +void AGrippableSkeletalMeshActor::Server_GetClientAuthReplication_Implementation(const FRepMovementVR& newMovement) +{ + if (!VRGripInterfaceSettings.bIsHeld) + { + if (!newMovement.Location.ContainsNaN() && !newMovement.Rotation.ContainsNaN()) + { + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + newMovement.CopyTo(MovementRep); + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableSkeletalMeshActor::OnRep_AttachmentReplication() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication()) + { + return; + } + + if (AttachmentWeldReplication.AttachParent) + { + if (RootComponent) + { + USceneComponent* AttachParentComponent = (AttachmentWeldReplication.AttachComponent ? ToRawPtr(AttachmentWeldReplication.AttachComponent) : AttachmentWeldReplication.AttachParent->GetRootComponent()); + + if (AttachParentComponent) + { + RootComponent->SetRelativeLocation_Direct(AttachmentWeldReplication.LocationOffset); + RootComponent->SetRelativeRotation_Direct(AttachmentWeldReplication.RotationOffset); + RootComponent->SetRelativeScale3D_Direct(AttachmentWeldReplication.RelativeScale3D); + + // If we're already attached to the correct Parent and Socket, then the update must be position only. + // AttachToComponent would early out in this case. + // Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated + // if the body is simulated (see AActor::GatherMovement). + const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && AttachmentWeldReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent)); + if (bAlreadyAttached) + { + // Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above). + RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + else + { + FAttachmentTransformRules attachRules = FAttachmentTransformRules::KeepRelativeTransform; + attachRules.bWeldSimulatedBodies = AttachmentWeldReplication.bIsWelded; + RootComponent->AttachToComponent(AttachParentComponent, attachRules, AttachmentWeldReplication.AttachSocket); + } + } + } + } + else + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Handle the case where an object was both detached and moved on the server in the same frame. + // Calling this extraneously does not hurt but will properly fire events if the movement state changed while attached. + // This is needed because client side movement is ignored when attached + if (IsReplicatingMovement()) + { + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableSkeletalMeshActor::OnRep_ReplicateMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + { + return; + } + + if (RootComponent) + { + const FRepAttachment ReplicationAttachment = GetAttachmentReplication(); + if (!ReplicationAttachment.AttachParent) + { + const FRepMovement& RepMove = GetReplicatedMovement(); + + // This "fix" corrects the simulation state not replicating over correctly + // If you turn off movement replication, simulate an object, turn movement replication back on and un-simulate, it never knows the difference + // This change ensures that it is checking against the current state + if (RootComponent->IsSimulatingPhysics() != RepMove.bRepPhysics)//SavedbRepPhysics != ReplicatedMovement.bRepPhysics) + { + // Turn on/off physics sim to match server. + SyncReplicatedPhysicsSimulation(); + + // It doesn't really hurt to run it here, the super can call it again but it will fail out as they already match + } + } + } + + Super::OnRep_ReplicateMovement(); +} + +void AGrippableSkeletalMeshActor::OnRep_ReplicatedMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (ClientAuthReplicationData.bIsCurrentlyClientAuth && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::OnRep_ReplicatedMovement(); +} + +void AGrippableSkeletalMeshActor::PostNetReceivePhysicState() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if ((ClientAuthReplicationData.bIsCurrentlyClientAuth || VRGripInterfaceSettings.bIsHeld) && bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::PostNetReceivePhysicState(); +} + +void AGrippableSkeletalMeshActor::MarkComponentsAsPendingKill() +{ + Super::MarkComponentsAsPendingKill(); + + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableSkeletalMeshActor::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + OnSubobjectDestroyFromReplication(SubObject); //-V595 + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + for (UActorComponent* ActorComp : GetComponents()) + { + // Pending kill components should have already had this called as they were network spawned and are being killed + if (ActorComp && IsValid(ActorComp) && ActorComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + ActorComp->PreDestroyFromReplication(); + } + + GripLogicScripts.Empty(); +} + +void AGrippableSkeletalMeshActor::BeginDestroy() +{ + Super::BeginDestroy(); + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableSkeletalMeshActor::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*>& ObjList) +{ + Super::GetSubobjectsWithStableNamesForNetworking(ObjList); + + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshComponent.cpp new file mode 100644 index 0000000..b4cc2c6 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSkeletalMeshComponent.cpp @@ -0,0 +1,316 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableSkeletalMeshComponent.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableSkeletalMeshComponent::UGrippableSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; +} + +void UGrippableSkeletalMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableSkeletalMeshComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableSkeletalMeshComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableSkeletalMeshComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableSkeletalMeshComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableSkeletalMeshComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableSkeletalMeshComponent, GameplayTags, COND_Custom); +} + +void UGrippableSkeletalMeshComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableSkeletalMeshComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableSkeletalMeshComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableSkeletalMeshComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableSkeletalMeshComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableSkeletalMeshComponent::~UGrippableSkeletalMeshComponent() +{ +} + +void UGrippableSkeletalMeshComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableSkeletalMeshComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableSkeletalMeshComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableSkeletalMeshComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableSkeletalMeshComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableSkeletalMeshComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void UGrippableSkeletalMeshComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableSkeletalMeshComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableSkeletalMeshComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableSkeletalMeshComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableSkeletalMeshComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableSkeletalMeshComponent::OnUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnEndUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnSecondaryUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableSkeletalMeshComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableSkeletalMeshComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableSkeletalMeshComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableSkeletalMeshComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableSkeletalMeshComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableSkeletalMeshComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableSkeletalMeshComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableSkeletalMeshComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableSkeletalMeshComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableSkeletalMeshComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableSkeletalMeshComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableSkeletalMeshComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableSkeletalMeshComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableSkeletalMeshComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableSkeletalMeshComponent::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableSkeletalMeshComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableSkeletalMeshComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableSkeletalMeshComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + +bool UGrippableSkeletalMeshComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableSkeletalMeshComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableSkeletalMeshComponent::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableSkeletalMeshComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSphereComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSphereComponent.cpp new file mode 100644 index 0000000..247c8a8 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableSphereComponent.cpp @@ -0,0 +1,316 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableSphereComponent.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableSphereComponent::UGrippableSphereComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + bReplicateMovement = false; + //this->bReplicates = true; + + VRGripInterfaceSettings.bIsHeld = false; + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; +} + +void UGrippableSphereComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableSphereComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableSphereComponent, bReplicateGripScripts) + DOREPLIFETIME(UGrippableSphereComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableSphereComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableSphereComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableSphereComponent, GameplayTags, COND_Custom); +} + +void UGrippableSphereComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableSphereComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableSphereComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableSphereComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableSphereComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableSphereComponent::~UGrippableSphereComponent() +{ +} + +void UGrippableSphereComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableSphereComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableSphereComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableSphereComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableSphereComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableSphereComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableSphereComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableSphereComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableSphereComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableSphereComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableSphereComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableSphereComponent::OnUsed_Implementation() {} +void UGrippableSphereComponent::OnEndUsed_Implementation() {} +void UGrippableSphereComponent::OnSecondaryUsed_Implementation() {} +void UGrippableSphereComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableSphereComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableSphereComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableSphereComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableSphereComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableSphereComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableSphereComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableSphereComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableSphereComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableSphereComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableSphereComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableSphereComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableSphereComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableSphereComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableSphereComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableSphereComponent::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableSphereComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableSphereComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +/*FBPInteractionSettings UGrippableSphereComponent::GetInteractionSettings_Implementation() +{ + return VRGripInterfaceSettings.InteractionSettings; +}*/ + + +bool UGrippableSphereComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableSphereComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableSphereComponent::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableSphereComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshActor.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshActor.cpp new file mode 100644 index 0000000..d2cb3b4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshActor.cpp @@ -0,0 +1,824 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableStaticMeshActor.h" +#include "TimerManager.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/PlayerState.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Misc/BucketUpdateSubsystem.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#include "GripScripts/VRGripScriptBase.h" +#include "DrawDebugHelpers.h" + +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + +// #TODO: Pull request this? This macro could be very useful +/*#define DOREPLIFETIME_CHANGE_NOTIFY(c,v,rncond) \ +{ \ + static UProperty* sp##v = GetReplicatedProperty(StaticClass(), c::StaticClass(),GET_MEMBER_NAME_CHECKED(c,v)); \ + bool bFound = false; \ + for ( int32 i = 0; i < OutLifetimeProps.Num(); i++ ) \ + { \ + if ( OutLifetimeProps[i].RepIndex == sp##v->RepIndex ) \ + { \ + for ( int32 j = 0; j < sp##v->ArrayDim; j++ ) \ + { \ + OutLifetimeProps[i + j].RepNotifyCondition = rncond; \ + } \ + bFound = true; \ + break; \ + } \ + } \ + check( bFound ); \ +}*/ + + +UOptionalRepStaticMeshComponent::UOptionalRepStaticMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = true; +} + +void UOptionalRepStaticMeshComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); + + +} + +void UOptionalRepStaticMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UOptionalRepStaticMeshComponent, bReplicateMovement); +} + + //============================================================================= +AGrippableStaticMeshActor::AGrippableStaticMeshActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass<UOptionalRepStaticMeshComponent>(TEXT("StaticMeshComponent0"))) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::TeleportAllComponents; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::InteractiveCollisionWithPhysics; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + this->SetMobility(EComponentMobility::Movable); + + // Default replication on for multiplayer + //this->bNetLoadOnClient = false; + SetReplicatingMovement(true); + this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; + bAllowIgnoringAttachOnOwner = true; + + // Setting a minimum of every 3rd frame (VR 90fps) for replication consideration + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 30.0f; +} + +void AGrippableStaticMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(AGrippableStaticMeshActor, GripLogicScripts, COND_Custom); + DOREPLIFETIME(AGrippableStaticMeshActor, bReplicateGripScripts); + DOREPLIFETIME(AGrippableStaticMeshActor, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(AGrippableStaticMeshActor, bAllowIgnoringAttachOnOwner); + DOREPLIFETIME(AGrippableStaticMeshActor, ClientAuthReplicationData); + DOREPLIFETIME_CONDITION(AGrippableStaticMeshActor, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(AGrippableStaticMeshActor, GameplayTags, COND_Custom); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); + + FDoRepLifetimeParams AttachmentReplicationParams{ COND_Custom, REPNOTIFY_Always, /*bIsPushBased=*/true }; + DOREPLIFETIME_WITH_PARAMS_FAST(AGrippableStaticMeshActor, AttachmentWeldReplication, AttachmentReplicationParams); +} + +void AGrippableStaticMeshActor::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + //Super::PreReplication(ChangedPropertyTracker); + + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableStaticMeshActor, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableStaticMeshActor, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableStaticMeshActor, GripLogicScripts, bReplicateGripScripts); + + //Super::PreReplication(ChangedPropertyTracker); + +#if WITH_PUSH_MODEL + const AActor* const OldAttachParent = AttachmentWeldReplication.AttachParent; + const UActorComponent* const OldAttachComponent = AttachmentWeldReplication.AttachComponent; +#endif + + // Attachment replication gets filled in by GatherCurrentMovement(), but in the case of a detached root we need to trigger remote detachment. + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + GatherCurrentMovement(); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(AActor, ReplicatedMovement, IsReplicatingMovement()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE(AGrippableStaticMeshActor, AttachmentWeldReplication, RootComponent && !RootComponent->GetIsReplicated()); + + // Don't need to replicate AttachmentReplication if the root component replicates, because it already handles it. + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(AActor, AttachmentReplication, false);// RootComponent && !RootComponent->GetIsReplicated()); + + +#if WITH_PUSH_MODEL + if (UNLIKELY(OldAttachParent != AttachmentWeldReplication.AttachParent || OldAttachComponent != AttachmentWeldReplication.AttachComponent)) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, AttachmentWeldReplication, this); + } +#endif + + PRAGMA_DISABLE_DEPRECATION_WARNINGS + UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass()); + if (BPClass != nullptr) + { + BPClass->InstancePreReplication(this, ChangedPropertyTracker); + } + PRAGMA_ENABLE_DEPRECATION_WARNINGS + +} + +void AGrippableStaticMeshActor::GatherCurrentMovement() +{ + if (IsReplicatingMovement() || (RootComponent && RootComponent->GetAttachParent())) + { + bool bWasAttachmentModified = false; + bool bWasRepMovementModified = false; + + AActor* OldAttachParent = AttachmentWeldReplication.AttachParent; + USceneComponent* OldAttachComponent = AttachmentWeldReplication.AttachComponent; + + AttachmentWeldReplication.AttachParent = nullptr; + AttachmentWeldReplication.AttachComponent = nullptr; + + FRepMovement& RepMovement = GetReplicatedMovement_Mutable(); + + UPrimitiveComponent* RootPrimComp = Cast<UPrimitiveComponent>(GetRootComponent()); + if (RootPrimComp && RootPrimComp->IsSimulatingPhysics()) + { + FRigidBodyState RBState; + RootPrimComp->GetRigidBodyState(RBState); + + RepMovement.FillFrom(RBState, this); + // Don't replicate movement if we're welded to another parent actor. + // Their replication will affect our position indirectly since we are attached. + RepMovement.bRepPhysics = !RootPrimComp->IsWelded(); + + if (!RepMovement.bRepPhysics) + { + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + } + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + else if (RootComponent != nullptr) + { + // If we are attached, don't replicate absolute position, use AttachmentReplication instead. + if (RootComponent->GetAttachParent() != nullptr) + { + // Networking for attachments assumes the RootComponent of the AttachParent actor. + // If that's not the case, we can't update this, as the client wouldn't be able to resolve the Component and would detach as a result. + AttachmentWeldReplication.AttachParent = RootComponent->GetAttachParent()->GetAttachmentRootActor(); + if (AttachmentWeldReplication.AttachParent != nullptr) + { + AttachmentWeldReplication.LocationOffset = RootComponent->GetRelativeLocation(); + AttachmentWeldReplication.RotationOffset = RootComponent->GetRelativeRotation(); + AttachmentWeldReplication.RelativeScale3D = RootComponent->GetRelativeScale3D(); + AttachmentWeldReplication.AttachComponent = RootComponent->GetAttachParent(); + AttachmentWeldReplication.AttachSocket = RootComponent->GetAttachSocketName(); + AttachmentWeldReplication.bIsWelded = RootPrimComp ? RootPrimComp->IsWelded() : false; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasAttachmentModified = true; + } + } + else + { + RepMovement.Location = FRepMovement::RebaseOntoZeroOrigin(RootComponent->GetComponentLocation(), this); + RepMovement.Rotation = RootComponent->GetComponentRotation(); + RepMovement.LinearVelocity = GetVelocity(); + RepMovement.AngularVelocity = FVector::ZeroVector; + + // Technically, the values might have stayed the same, but we'll just assume they've changed. + bWasRepMovementModified = true; + } + + bWasRepMovementModified = (bWasRepMovementModified || RepMovement.bRepPhysics); + RepMovement.bRepPhysics = false; + } +#if WITH_PUSH_MODEL + if (bWasRepMovementModified) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AActor, ReplicatedMovement, this); + } + + if (bWasAttachmentModified || + OldAttachParent != AttachmentWeldReplication.AttachParent || + OldAttachComponent != AttachmentWeldReplication.AttachComponent) + { + MARK_PROPERTY_DIRTY_FROM_NAME(AGrippableStaticMeshActor, AttachmentWeldReplication, this); + } +#endif + } +} + +bool AGrippableStaticMeshActor::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +AGrippableStaticMeshActor::~AGrippableStaticMeshActor() +{ +} + +void AGrippableStaticMeshActor::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } +} + +void AGrippableStaticMeshActor::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void AGrippableStaticMeshActor::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void AGrippableStaticMeshActor::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void AGrippableStaticMeshActor::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void AGrippableStaticMeshActor::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void AGrippableStaticMeshActor::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void AGrippableStaticMeshActor::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void AGrippableStaticMeshActor::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void AGrippableStaticMeshActor::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void AGrippableStaticMeshActor::OnUsed_Implementation() {} +void AGrippableStaticMeshActor::OnEndUsed_Implementation() {} +void AGrippableStaticMeshActor::OnSecondaryUsed_Implementation() {} +void AGrippableStaticMeshActor::OnEndSecondaryUsed_Implementation() {} +void AGrippableStaticMeshActor::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool AGrippableStaticMeshActor::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool AGrippableStaticMeshActor::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + + +EGripInterfaceTeleportBehavior AGrippableStaticMeshActor::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool AGrippableStaticMeshActor::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType AGrippableStaticMeshActor::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType AGrippableStaticMeshActor::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings AGrippableStaticMeshActor::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings AGrippableStaticMeshActor::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void AGrippableStaticMeshActor::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings AGrippableStaticMeshActor::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float AGrippableStaticMeshActor::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void AGrippableStaticMeshActor::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool AGrippableStaticMeshActor::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void AGrippableStaticMeshActor::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +bool AGrippableStaticMeshActor::AddToClientReplicationBucket() +{ + if (ShouldWeSkipAttachmentReplication(false)) + { + // The subsystem automatically removes entries with the same function signature so its safe to just always add here + GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->AddObjectToBucket(ClientAuthReplicationData.UpdateRate, this, FName(TEXT("PollReplicationEvent"))); + ClientAuthReplicationData.bIsCurrentlyClientAuth = true; + + if (UWorld * World = GetWorld()) + ClientAuthReplicationData.TimeAtInitialThrow = World->GetTimeSeconds(); + + return true; + } + + return false; +} + +bool AGrippableStaticMeshActor::RemoveFromClientReplicationBucket() +{ + if (ClientAuthReplicationData.bIsCurrentlyClientAuth) + { + GetWorld()->GetSubsystem<UBucketUpdateSubsystem>()->RemoveObjectFromBucketByFunctionName(this, FName(TEXT("PollReplicationEvent"))); + CeaseReplicationBlocking(); + return true; + } + + return false; +} + +void AGrippableStaticMeshActor::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void AGrippableStaticMeshActor::SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + RemoveFromClientReplicationBucket(); + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + } + else + { + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; + + if (ClientAuthReplicationData.bUseClientAuthThrowing && !VRGripInterfaceSettings.bIsHeld) + { + bool bWasLocallyOwned = HoldingController ? HoldingController->IsLocallyControlled() : false; + if (bWasLocallyOwned && ShouldWeSkipAttachmentReplication(false)) + { + if (UPrimitiveComponent* PrimComp = Cast<UPrimitiveComponent>(GetRootComponent())) + { + if (PrimComp->IsSimulatingPhysics()) + { + AddToClientReplicationBucket(); + } + } + } + } + } +} + +bool AGrippableStaticMeshActor::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +bool AGrippableStaticMeshActor::PollReplicationEvent() +{ + if (!ClientAuthReplicationData.bIsCurrentlyClientAuth || !this->HasLocalNetOwner() || VRGripInterfaceSettings.bIsHeld) + return false; // Tell the bucket subsystem to remove us from consideration + + UWorld *OurWorld = GetWorld(); + if (!OurWorld) + return false; // Tell the bucket subsystem to remove us from consideration + + bool bRemoveBlocking = false; + + if ((OurWorld->GetTimeSeconds() - ClientAuthReplicationData.TimeAtInitialThrow) > 10.0f) + { + // Lets time out sending, its been 10 seconds since we threw the object and its likely that it is conflicting with some server + // Authed movement that is forcing it to keep momentum. + //return false; // Tell the bucket subsystem to remove us from consideration + bRemoveBlocking = true; + } + + // Store current transform for resting check + FTransform CurTransform = this->GetActorTransform(); + + if (!bRemoveBlocking) + { + if (!CurTransform.GetRotation().Equals(ClientAuthReplicationData.LastActorTransform.GetRotation()) || !CurTransform.GetLocation().Equals(ClientAuthReplicationData.LastActorTransform.GetLocation())) + { + ClientAuthReplicationData.LastActorTransform = CurTransform; + + if (UPrimitiveComponent * PrimComp = Cast<UPrimitiveComponent>(RootComponent)) + { + // Need to clamp to a max time since start, to handle cases with conflicting collisions + if (PrimComp->IsSimulatingPhysics() && ShouldWeSkipAttachmentReplication(false)) + { + FRepMovementVR ClientAuthMovementRep; + if (ClientAuthMovementRep.GatherActorsMovement(this)) + { + Server_GetClientAuthReplication(ClientAuthMovementRep); + + if (PrimComp->RigidBodyIsAwake()) + { + return true; + } + } + } + } + else + { + bRemoveBlocking = true; + //return false; // Tell the bucket subsystem to remove us from consideration + } + } + //else + // { + // Difference is too small, lets end sending location + //ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + // } + } + + bool TimedBlockingRelease = false; + + AActor* TopOwner = GetOwner(); + if (TopOwner != nullptr) + { + AActor * tempOwner = TopOwner->GetOwner(); + + // I have an owner so search that for the top owner + while (tempOwner) + { + TopOwner = tempOwner; + tempOwner = TopOwner->GetOwner(); + } + + if (APlayerController* PlayerController = Cast<APlayerController>(TopOwner)) + { + if (APlayerState* PlayerState = PlayerController->PlayerState) + { + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + + // Lets clamp the ping to a min / max value just in case + float clampedPing = FMath::Clamp(PlayerState->ExactPing * 0.001f, 0.0f, 1000.0f); + OurWorld->GetTimerManager().SetTimer(ClientAuthReplicationData.ResetReplicationHandle, this, &AGrippableStaticMeshActor::CeaseReplicationBlocking, clampedPing, false); + TimedBlockingRelease = true; + } + } + } + + if (!TimedBlockingRelease) + { + CeaseReplicationBlocking(); + } + + // Tell server to kill us + Server_EndClientAuthReplication(); + return false; // Tell the bucket subsystem to remove us from consideration +} + +void AGrippableStaticMeshActor::CeaseReplicationBlocking() +{ + if(ClientAuthReplicationData.bIsCurrentlyClientAuth) + ClientAuthReplicationData.bIsCurrentlyClientAuth = false; + + ClientAuthReplicationData.LastActorTransform = FTransform::Identity; + + if (ClientAuthReplicationData.ResetReplicationHandle.IsValid()) + { + if (UWorld * OurWorld = GetWorld()) + { + OurWorld->GetTimerManager().ClearTimer(ClientAuthReplicationData.ResetReplicationHandle); + } + } +} + +void AGrippableStaticMeshActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + RemoveFromClientReplicationBucket(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } + + Super::EndPlay(EndPlayReason); +} + +bool AGrippableStaticMeshActor::Server_EndClientAuthReplication_Validate() +{ + return true; +} + +void AGrippableStaticMeshActor::Server_EndClientAuthReplication_Implementation() +{ + if (UWorld* World = GetWorld()) + { + if (FPhysScene* PhysScene = World->GetPhysicsScene()) + { + if (FPhysicsReplication* PhysicsReplication = PhysScene->GetPhysicsReplication()) + { + PhysicsReplication->RemoveReplicatedTarget(this->GetStaticMeshComponent()); + } + } + } +} + +bool AGrippableStaticMeshActor::Server_GetClientAuthReplication_Validate(const FRepMovementVR & newMovement) +{ + return true; +} + +void AGrippableStaticMeshActor::Server_GetClientAuthReplication_Implementation(const FRepMovementVR & newMovement) +{ + if (!VRGripInterfaceSettings.bIsHeld) + { + if (!newMovement.Location.ContainsNaN() && !newMovement.Rotation.ContainsNaN()) + { + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + newMovement.CopyTo(MovementRep); + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableStaticMeshActor::OnRep_AttachmentReplication() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication()) + { + return; + } + + if (AttachmentWeldReplication.AttachParent) + { + if (RootComponent) + { + USceneComponent* AttachParentComponent = (AttachmentWeldReplication.AttachComponent ? ToRawPtr(AttachmentWeldReplication.AttachComponent) : AttachmentWeldReplication.AttachParent->GetRootComponent()); + + if (AttachParentComponent) + { + RootComponent->SetRelativeLocation_Direct(AttachmentWeldReplication.LocationOffset); + RootComponent->SetRelativeRotation_Direct(AttachmentWeldReplication.RotationOffset); + RootComponent->SetRelativeScale3D_Direct(AttachmentWeldReplication.RelativeScale3D); + + // If we're already attached to the correct Parent and Socket, then the update must be position only. + // AttachToComponent would early out in this case. + // Note, we ignore the special case for simulated bodies in AttachToComponent as AttachmentReplication shouldn't get updated + // if the body is simulated (see AActor::GatherMovement). + const bool bAlreadyAttached = (AttachParentComponent == RootComponent->GetAttachParent() && AttachmentWeldReplication.AttachSocket == RootComponent->GetAttachSocketName() && AttachParentComponent->GetAttachChildren().Contains(RootComponent)); + if (bAlreadyAttached) + { + // Note, this doesn't match AttachToComponent, but we're assuming it's safe to skip physics (see comment above). + RootComponent->UpdateComponentToWorld(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + else + { + FAttachmentTransformRules attachRules = FAttachmentTransformRules::KeepRelativeTransform; + attachRules.bWeldSimulatedBodies = AttachmentWeldReplication.bIsWelded; + RootComponent->AttachToComponent(AttachParentComponent, attachRules, AttachmentWeldReplication.AttachSocket); + } + } + } + } + else + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Handle the case where an object was both detached and moved on the server in the same frame. + // Calling this extraneously does not hurt but will properly fire events if the movement state changed while attached. + // This is needed because client side movement is ignored when attached + if (IsReplicatingMovement()) + { + OnRep_ReplicatedMovement(); + } + } +} + +void AGrippableStaticMeshActor::OnRep_ReplicateMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + { + return; + } + + if (RootComponent) + { + const FRepAttachment ReplicationAttachment = GetAttachmentReplication(); + if (!ReplicationAttachment.AttachParent) + { + const FRepMovement& RepMove = GetReplicatedMovement(); + + // This "fix" corrects the simulation state not replicating over correctly + // If you turn off movement replication, simulate an object, turn movement replication back on and un-simulate, it never knows the difference + // This change ensures that it is checking against the current state + if (RootComponent->IsSimulatingPhysics() != RepMove.bRepPhysics)//SavedbRepPhysics != ReplicatedMovement.bRepPhysics) + { + // Turn on/off physics sim to match server. + SyncReplicatedPhysicsSimulation(); + + // It doesn't really hurt to run it here, the super can call it again but it will fail out as they already match + } + } + } + + Super::OnRep_ReplicateMovement(); +} + +void AGrippableStaticMeshActor::OnRep_ReplicatedMovement() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if (ClientAuthReplicationData.bIsCurrentlyClientAuth && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::OnRep_ReplicatedMovement(); +} + +void AGrippableStaticMeshActor::PostNetReceivePhysicState() +{ + if (bAllowIgnoringAttachOnOwner && (ClientAuthReplicationData.bIsCurrentlyClientAuth || ShouldWeSkipAttachmentReplication())) + //if ((ClientAuthReplicationData.bIsCurrentlyClientAuth || VRGripInterfaceSettings.bIsHeld) && bAllowIgnoringAttachOnOwner && ShouldWeSkipAttachmentReplication(false)) + { + return; + } + + Super::PostNetReceivePhysicState(); +} + +void AGrippableStaticMeshActor::MarkComponentsAsPendingKill() +{ + Super::MarkComponentsAsPendingKill(); + + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableStaticMeshActor::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + OnSubobjectDestroyFromReplication(SubObject); //-V595 + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + for (UActorComponent * ActorComp : GetComponents()) + { + // Pending kill components should have already had this called as they were network spawned and are being killed + // We only call this on our interfaced components since they are the only ones that should implement grip scripts + if (ActorComp && IsValid(ActorComp) && ActorComp->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + ActorComp->PreDestroyFromReplication(); + } + + GripLogicScripts.Empty(); +} + +void AGrippableStaticMeshActor::BeginDestroy() +{ + Super::BeginDestroy(); + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void AGrippableStaticMeshActor::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*>& ObjList) +{ + Super::GetSubobjectsWithStableNamesForNetworking(ObjList); + + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshComponent.cpp new file mode 100644 index 0000000..7182f36 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/GrippableStaticMeshComponent.cpp @@ -0,0 +1,311 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/GrippableStaticMeshComponent.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripScripts/VRGripScriptBase.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UGrippableStaticMeshComponent::UGrippableStaticMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRGripInterfaceSettings.bDenyGripping = false; + VRGripInterfaceSettings.OnTeleportBehavior = EGripInterfaceTeleportBehavior::DropOnTeleport; + VRGripInterfaceSettings.bSimulateOnDrop = true; + VRGripInterfaceSettings.SlotDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.FreeDefaultGripType = EGripCollisionType::ManipulationGrip; + VRGripInterfaceSettings.SecondaryGripType = ESecondaryGripType::SG_None; + VRGripInterfaceSettings.MovementReplicationType = EGripMovementReplicationSettings::ForceClientSideMovement; + VRGripInterfaceSettings.LateUpdateSetting = EGripLateUpdateSettings::LateUpdatesAlwaysOff; + VRGripInterfaceSettings.ConstraintStiffness = 1500.0f; + VRGripInterfaceSettings.ConstraintDamping = 200.0f; + VRGripInterfaceSettings.ConstraintBreakDistance = 100.0f; + VRGripInterfaceSettings.SecondarySlotRange = 20.0f; + VRGripInterfaceSettings.PrimarySlotRange = 20.0f; + + VRGripInterfaceSettings.bIsHeld = false; + + bReplicateMovement = false; + //this->bReplicates = true; + + bRepGripSettingsAndGameplayTags = true; + bReplicateGripScripts = false; +} + +void UGrippableStaticMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME_CONDITION(UGrippableStaticMeshComponent, GripLogicScripts, COND_Custom); + DOREPLIFETIME(UGrippableStaticMeshComponent, bReplicateGripScripts); + DOREPLIFETIME(UGrippableStaticMeshComponent, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME(UGrippableStaticMeshComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UGrippableStaticMeshComponent, VRGripInterfaceSettings, COND_Custom); + DOREPLIFETIME_CONDITION(UGrippableStaticMeshComponent, GameplayTags, COND_Custom); +} + +void UGrippableStaticMeshComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableStaticMeshComponent, VRGripInterfaceSettings, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableStaticMeshComponent, GameplayTags, bRepGripSettingsAndGameplayTags); + DOREPLIFETIME_ACTIVE_OVERRIDE(UGrippableStaticMeshComponent, GripLogicScripts, bReplicateGripScripts); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +bool UGrippableStaticMeshComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) +{ + bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags); + + if (bReplicateGripScripts) + { + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script && IsValid(Script)) + { + WroteSomething |= Channel->ReplicateSubobject(Script, *Bunch, *RepFlags); + } + } + } + + return WroteSomething; +} + +//============================================================================= +UGrippableStaticMeshComponent::~UGrippableStaticMeshComponent() +{ +} + +void UGrippableStaticMeshComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->BeginPlay(this); + } + } + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UGrippableStaticMeshComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + // Call the base class + Super::EndPlay(EndPlayReason); + + // Call all grip scripts begin play events so they can perform any needed logic + for (UVRGripScriptBase* Script : GripLogicScripts) + { + if (Script) + { + Script->EndPlay(EndPlayReason); + } + } +} + +void UGrippableStaticMeshComponent::SetDenyGripping(bool bDenyGripping) +{ + VRGripInterfaceSettings.bDenyGripping = bDenyGripping; +} + +void UGrippableStaticMeshComponent::SetGripPriority(int NewGripPriority) +{ + VRGripInterfaceSettings.AdvancedGripSettings.GripPriority = NewGripPriority; +} + +void UGrippableStaticMeshComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) {} +void UGrippableStaticMeshComponent::OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) { } +void UGrippableStaticMeshComponent::OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) { } +void UGrippableStaticMeshComponent::OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) {} +void UGrippableStaticMeshComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed) {} +void UGrippableStaticMeshComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripAdded.Broadcast(GripOwningController, GripInformation); } +void UGrippableStaticMeshComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) { OnSecondaryGripRemoved.Broadcast(GripOwningController, GripInformation); } +void UGrippableStaticMeshComponent::OnUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnEndUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnSecondaryUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnEndSecondaryUsed_Implementation() {} +void UGrippableStaticMeshComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UGrippableStaticMeshComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UGrippableStaticMeshComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return VRGripInterfaceSettings.bDenyGripping; +} + +EGripInterfaceTeleportBehavior UGrippableStaticMeshComponent::TeleportBehavior_Implementation() +{ + return VRGripInterfaceSettings.OnTeleportBehavior; +} + +bool UGrippableStaticMeshComponent::SimulateOnDrop_Implementation() +{ + return VRGripInterfaceSettings.bSimulateOnDrop; +} + +EGripCollisionType UGrippableStaticMeshComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return bIsSlot ? VRGripInterfaceSettings.SlotDefaultGripType : VRGripInterfaceSettings.FreeDefaultGripType; +} + +ESecondaryGripType UGrippableStaticMeshComponent::SecondaryGripType_Implementation() +{ + return VRGripInterfaceSettings.SecondaryGripType; +} + +EGripMovementReplicationSettings UGrippableStaticMeshComponent::GripMovementReplicationType_Implementation() +{ + return VRGripInterfaceSettings.MovementReplicationType; +} + +EGripLateUpdateSettings UGrippableStaticMeshComponent::GripLateUpdateSetting_Implementation() +{ + return VRGripInterfaceSettings.LateUpdateSetting; +} + +void UGrippableStaticMeshComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = VRGripInterfaceSettings.ConstraintStiffness; + GripDampingOut = VRGripInterfaceSettings.ConstraintDamping; +} + +FBPAdvGripSettings UGrippableStaticMeshComponent::AdvancedGripSettings_Implementation() +{ + return VRGripInterfaceSettings.AdvancedGripSettings; +} + +float UGrippableStaticMeshComponent::GripBreakDistance_Implementation() +{ + return VRGripInterfaceSettings.ConstraintBreakDistance; +} + +void UGrippableStaticMeshComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? VRGripInterfaceSettings.SecondarySlotRange : VRGripInterfaceSettings.PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UGrippableStaticMeshComponent::AllowsMultipleGrips_Implementation() +{ + return VRGripInterfaceSettings.bAllowMultipleGrips; +} + +void UGrippableStaticMeshComponent::IsHeld_Implementation(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld) +{ + HoldingControllers = VRGripInterfaceSettings.HoldingControllers; + bIsHeld = VRGripInterfaceSettings.bIsHeld; +} + +void UGrippableStaticMeshComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UGrippableStaticMeshComponent::SetHeld_Implementation(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld) +{ + if (bIsHeld) + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!VRGripInterfaceSettings.bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + + VRGripInterfaceSettings.bWasHeld = true; + VRGripInterfaceSettings.HoldingControllers.AddUnique(FBPGripPair(HoldingController, GripID)); + } + else + { + if (VRGripInterfaceSettings.MovementReplicationType != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + + VRGripInterfaceSettings.HoldingControllers.Remove(FBPGripPair(HoldingController, GripID)); + } + + VRGripInterfaceSettings.bIsHeld = VRGripInterfaceSettings.HoldingControllers.Num() > 0; +} + +bool UGrippableStaticMeshComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + ArrayReference = GripLogicScripts; + return GripLogicScripts.Num() > 0; +} + +void UGrippableStaticMeshComponent::PreDestroyFromReplication() +{ + Super::PreDestroyFromReplication(); + + // Destroy any sub-objects we created + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->PreDestroyFromReplication(); + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} + +void UGrippableStaticMeshComponent::GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) +{ + if (bReplicateGripScripts) + { + for (int32 i = 0; i < GripLogicScripts.Num(); ++i) + { + if (UObject* SubObject = GripLogicScripts[i]) + { + ObjList.Add(SubObject); + } + } + } +} + +void UGrippableStaticMeshComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + // Call the super at the end, after we've done what we needed to do + Super::OnComponentDestroyed(bDestroyingHierarchy); + + // Don't set these in editor preview window and the like, it causes saving issues + if (UWorld * World = GetWorld()) + { + EWorldType::Type WorldType = World->WorldType; + if (WorldType == EWorldType::Editor || WorldType == EWorldType::EditorPreview) + { + return; + } + } + + for (int32 i = 0; i < GripLogicScripts.Num(); i++) + { + if (UObject *SubObject = GripLogicScripts[i]) + { + SubObject->MarkAsGarbage(); + } + } + + GripLogicScripts.Empty(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp new file mode 100644 index 0000000..a635c7e --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp @@ -0,0 +1,807 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Grippables/HandSocketComponent.h" +#include "Engine/CollisionProfile.h" +#include "Animation/AnimSequence.h" +#include "Animation/AnimInstanceProxy.h" +#include "Animation/PoseSnapshot.h" +//#include "VRExpansionFunctionLibrary.h" +#include "Components/SkeletalMeshComponent.h" +#include "Components/PoseableMeshComponent.h" +#include "GripMotionControllerComponent.h" +//#include "VRGripInterface.h" +//#include "VRBPDatatypes.h" +#include "Net/UnrealNetwork.h" +#include "Serialization/CustomVersion.h" + +DEFINE_LOG_CATEGORY(LogVRHandSocketComponent); + +const FGuid FVRHandSocketCustomVersion::GUID(0x5A018B7F, 0x48A7AFDE, 0xAFBEB580, 0xAD575412); + +// Register the custom version with core +FCustomVersionRegistration GRegisterHandSocketCustomVersion(FVRHandSocketCustomVersion::GUID, FVRHandSocketCustomVersion::LatestVersion, TEXT("HandSocketVer")); + + +void UHandSocketComponent::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FVRHandSocketCustomVersion::GUID); + +#if WITH_EDITORONLY_DATA + const int32 CustomHandSocketVersion = Ar.CustomVer(FVRHandSocketCustomVersion::GUID); + + if (CustomHandSocketVersion < FVRHandSocketCustomVersion::HandSocketStoringSetState) + { + bDecoupled = bDecoupleMeshPlacement; + } +#endif +} + + //============================================================================= +UHandSocketComponent::UHandSocketComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = false; + PrimaryComponentTick.bCanEverTick = false; + PrimaryComponentTick.bStartWithTickEnabled = false; + // Setting absolute scale so we don't have to care about our parents scale + this->SetUsingAbsoluteScale(true); + //this->bReplicates = true; + + bRepGameplayTags = true; + +#if WITH_EDITORONLY_DATA + bTickedPose = false; + bDecoupled = false; + bShowVisualizationMesh = true; + bMirrorVisualizationMesh = false; + bShowRangeVisualization = false; +#endif + + HandRelativePlacement = FTransform::Identity; + bAlwaysInRange = false; + bDisabled = false; + bMatchRotation = false; + OverrideDistance = 0.0f; + SlotPrefix = FName("VRGripP"); + bUseCustomPoseDeltas = false; + HandTargetAnimation = nullptr; + MirroredScale = FVector(1.f, 1.f, -1.f); + bOnlySnapMesh = false; + bFlipForLeftHand = false; + bLeftHandDominant = false; + bOnlyFlipRotation = false; + + MirrorAxis = EVRAxis::X; + FlipAxis = EVRAxis::Y; +} + +UAnimSequence* UHandSocketComponent::GetTargetAnimation() +{ + return HandTargetAnimation; +} + +bool UHandSocketComponent::GetAnimationSequenceAsPoseSnapShot(UAnimSequence* InAnimationSequence, FPoseSnapshot& OutPoseSnapShot, USkeletalMeshComponent* TargetMesh, bool bSkipRootBone, bool bFlipHand) +{ + if (InAnimationSequence) + { + OutPoseSnapShot.SkeletalMeshName = /*TargetMesh ? TargetMesh->SkeletalMesh->GetFName(): */InAnimationSequence->GetSkeleton()->GetFName(); + OutPoseSnapShot.SnapshotName = InAnimationSequence->GetFName(); + OutPoseSnapShot.BoneNames.Empty(); + OutPoseSnapShot.LocalTransforms.Empty(); + + TArray<FName> AnimSeqNames; + + if (USkeleton* AnimationSkele = InAnimationSequence->GetSkeleton()) + { + // pre-size the array to avoid unnecessary reallocation + OutPoseSnapShot.BoneNames.AddUninitialized(AnimationSkele->GetReferenceSkeleton().GetNum()); + for (int32 i = 0; i < AnimationSkele->GetReferenceSkeleton().GetNum(); i++) + { + OutPoseSnapShot.BoneNames[i] = AnimationSkele->GetReferenceSkeleton().GetBoneName(i); + if (bFlipHand) + { + FString bName = OutPoseSnapShot.BoneNames[i].ToString(); + + if (bName.Contains("_r")) + { + bName = bName.Replace(TEXT("_r"), TEXT("_l")); + } + else + { + bName = bName.Replace(TEXT("_l"), TEXT("_r")); + } + + OutPoseSnapShot.BoneNames[i] = FName(bName); + } + } + } + else + { + return false; + } + + const FReferenceSkeleton& RefSkeleton = (TargetMesh) ? TargetMesh->SkeletalMesh->GetRefSkeleton() : InAnimationSequence->GetSkeleton()->GetReferenceSkeleton(); + FTransform LocalTransform; + + const TArray<FTrackToSkeletonMap>& TrackMap = InAnimationSequence->GetCompressedTrackToSkeletonMapTable(); + int32 TrackIndex = INDEX_NONE; + + OutPoseSnapShot.LocalTransforms.Reserve(OutPoseSnapShot.BoneNames.Num()); + + for (int32 BoneNameIndex = 0; BoneNameIndex < OutPoseSnapShot.BoneNames.Num(); ++BoneNameIndex) + { + + const FName& BoneName = OutPoseSnapShot.BoneNames[BoneNameIndex]; + + TrackIndex = INDEX_NONE; + if (BoneNameIndex != INDEX_NONE && BoneNameIndex < TrackMap.Num() && TrackMap[BoneNameIndex].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = BoneNameIndex; + } + else + { + // This shouldn't happen but I need a fallback + // Don't currently want to reconstruct the map inversely + for (int i = 0; i < TrackMap.Num(); ++i) + { + if (TrackMap[i].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = i; + break; + } + } + } + + if (TrackIndex != INDEX_NONE && (!bSkipRootBone || TrackIndex != 0)) + { + InAnimationSequence->GetBoneTransform(LocalTransform, TrackIndex, 0.f, false); + } + else + { + // otherwise, get ref pose if exists + const int32 BoneIDX = RefSkeleton.FindBoneIndex(BoneName); + if (BoneIDX != INDEX_NONE) + { + LocalTransform = RefSkeleton.GetRefBonePose()[BoneIDX]; + } + else + { + LocalTransform = FTransform::Identity; + } + } + + if (bFlipHand && (!bSkipRootBone || TrackIndex != 0)) + { + FMatrix M = LocalTransform.ToMatrixWithScale(); + M.Mirror(EAxis::X, EAxis::X); + M.Mirror(EAxis::Y, EAxis::Y); + M.Mirror(EAxis::Z, EAxis::Z); + LocalTransform.SetFromMatrix(M); + } + + OutPoseSnapShot.LocalTransforms.Add(LocalTransform); + } + + OutPoseSnapShot.bIsValid = true; + return true; + } + + return false; +} + +bool UHandSocketComponent::GetBlendedPoseSnapShot(FPoseSnapshot& PoseSnapShot, USkeletalMeshComponent* TargetMesh, bool bSkipRootBone, bool bFlipHand) +{ + if (HandTargetAnimation)// && bUseCustomPoseDeltas && CustomPoseDeltas.Num() > 0) + { + PoseSnapShot.SkeletalMeshName = HandTargetAnimation->GetSkeleton()->GetFName(); + PoseSnapShot.SnapshotName = HandTargetAnimation->GetFName(); + PoseSnapShot.BoneNames.Empty(); + PoseSnapShot.LocalTransforms.Empty(); + TArray<FName> OrigBoneNames; + + if(USkeleton * AnimationSkele = HandTargetAnimation->GetSkeleton()) + { + // pre-size the array to avoid unnecessary reallocation + PoseSnapShot.BoneNames.AddUninitialized(AnimationSkele->GetReferenceSkeleton().GetNum()); + OrigBoneNames.AddUninitialized(AnimationSkele->GetReferenceSkeleton().GetNum()); + for (int32 i = 0; i < AnimationSkele->GetReferenceSkeleton().GetNum(); i++) + { + PoseSnapShot.BoneNames[i] = AnimationSkele->GetReferenceSkeleton().GetBoneName(i); + OrigBoneNames[i] = PoseSnapShot.BoneNames[i]; + if (bFlipHand) + { + FString bName = PoseSnapShot.BoneNames[i].ToString(); + + if (bName.Contains("_r")) + { + bName = bName.Replace(TEXT("_r"), TEXT("_l")); + } + else + { + bName = bName.Replace(TEXT("_l"), TEXT("_r")); + } + + PoseSnapShot.BoneNames[i] = FName(bName); + } + } + } + else + { + return false; + } + + const FReferenceSkeleton& RefSkeleton = (TargetMesh) ? TargetMesh->SkeletalMesh->GetRefSkeleton() : HandTargetAnimation->GetSkeleton()->GetReferenceSkeleton(); + FTransform LocalTransform; + + const TArray<FTrackToSkeletonMap>& TrackMap = HandTargetAnimation->GetCompressedTrackToSkeletonMapTable(); + int32 TrackIndex = INDEX_NONE; + + for (int32 BoneNameIndex = 0; BoneNameIndex < PoseSnapShot.BoneNames.Num(); ++BoneNameIndex) + { + TrackIndex = INDEX_NONE; + if (BoneNameIndex < TrackMap.Num() && TrackMap[BoneNameIndex].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = BoneNameIndex; + } + else + { + // This shouldn't happen but I need a fallback + // Don't currently want to reconstruct the map inversely + for (int i = 0; i < TrackMap.Num(); ++i) + { + if (TrackMap[i].BoneTreeIndex == BoneNameIndex) + { + TrackIndex = i; + break; + } + } + } + + const FName& BoneName = PoseSnapShot.BoneNames[BoneNameIndex]; + + if (TrackIndex != INDEX_NONE && (!bSkipRootBone || TrackIndex != 0)) + { + HandTargetAnimation->GetBoneTransform(LocalTransform, TrackIndex, 0.f, false); + } + else + { + // otherwise, get ref pose if exists + const int32 BoneIDX = RefSkeleton.FindBoneIndex(BoneName); + if (BoneIDX != INDEX_NONE) + { + LocalTransform = RefSkeleton.GetRefBonePose()[BoneIDX]; + } + else + { + LocalTransform = FTransform::Identity; + } + } + + if (bUseCustomPoseDeltas) + { + FQuat DeltaQuat = FQuat::Identity; + if (FBPVRHandPoseBonePair* HandPair = CustomPoseDeltas.FindByKey(OrigBoneNames[BoneNameIndex])) + { + DeltaQuat = HandPair->DeltaPose; + } + + LocalTransform.ConcatenateRotation(DeltaQuat); + LocalTransform.NormalizeRotation(); + } + + if (bFlipHand && (!bSkipRootBone || TrackIndex != 0)) + { + FMatrix M = LocalTransform.ToMatrixWithScale(); + M.Mirror(EAxis::X, EAxis::X); + M.Mirror(EAxis::Y, EAxis::Y); + M.Mirror(EAxis::Z, EAxis::Z); + LocalTransform.SetFromMatrix(M); + } + + PoseSnapShot.LocalTransforms.Add(LocalTransform); + } + + PoseSnapShot.bIsValid = true; + return true; + } + else if (bUseCustomPoseDeltas && CustomPoseDeltas.Num() && TargetMesh) + { + PoseSnapShot.SkeletalMeshName = TargetMesh->SkeletalMesh->GetSkeleton()->GetFName(); + PoseSnapShot.SnapshotName = FName(TEXT("RawDeltaPose")); + PoseSnapShot.BoneNames.Empty(); + PoseSnapShot.LocalTransforms.Empty(); + TargetMesh->GetBoneNames(PoseSnapShot.BoneNames); + + PoseSnapShot.LocalTransforms = TargetMesh->SkeletalMesh->GetSkeleton()->GetRefLocalPoses(); + + FQuat DeltaQuat = FQuat::Identity; + FName TargetBoneName = NAME_None; + + for (FBPVRHandPoseBonePair& HandPair : CustomPoseDeltas) + { + if (bFlipHand) + { + FString bName = HandPair.BoneName.ToString(); + + if (bName.Contains("_r")) + { + bName = bName.Replace(TEXT("_r"), TEXT("_l")); + } + else + { + bName = bName.Replace(TEXT("_l"), TEXT("_r")); + } + + TargetBoneName = FName(bName); + } + else + { + TargetBoneName = HandPair.BoneName; + } + + int32 BoneIdx = TargetMesh->GetBoneIndex(TargetBoneName); + if (BoneIdx != INDEX_NONE) + { + DeltaQuat = HandPair.DeltaPose; + + if (bFlipHand) + { + FTransform DeltaTrans(DeltaQuat); + FMatrix M = DeltaTrans.ToMatrixWithScale(); + M.Mirror(EAxis::X, EAxis::X); + M.Mirror(EAxis::Y, EAxis::Y); + M.Mirror(EAxis::Z, EAxis::Z); + DeltaTrans.SetFromMatrix(M); + DeltaQuat = DeltaTrans.GetRotation(); + } + + PoseSnapShot.LocalTransforms[BoneIdx].ConcatenateRotation(DeltaQuat); + PoseSnapShot.LocalTransforms[BoneIdx].NormalizeRotation(); + + } + } + + PoseSnapShot.bIsValid = true; + return true; + } + + return false; +} + +FTransform UHandSocketComponent::GetHandRelativePlacement() +{ + // Optionally mirror for left hand + + if (bDecoupleMeshPlacement) + { + if (USceneComponent* ParentComp = GetAttachParent()) + { + return HandRelativePlacement.GetRelativeTransform(this->GetRelativeTransform()); + //FTransform curTrans = HandRelativePlacement * ParentComp->GetComponentTransform(); + //return curTrans.GetRelativeTransform(this->GetComponentTransform()); + } + } + + return HandRelativePlacement; +} + +FTransform UHandSocketComponent::GetHandSocketTransform(UGripMotionControllerComponent* QueryController, bool bIgnoreOnlySnapMesh) +{ + // Optionally mirror for left hand + + if (!bIgnoreOnlySnapMesh && bOnlySnapMesh) + { + if (!QueryController) + { + // No controller input + UE_LOG(LogVRMotionController, Warning, TEXT("HandSocketComponent::GetHandSocketTransform was missing required motion controller for bOnlySnapMesh! Check that you are passing a controller into GetClosestSocketInRange!")); + } + else + { + return QueryController->GetPivotTransform(); + } + } + + if (bFlipForLeftHand) + { + if (!QueryController) + { + // No controller input + UE_LOG(LogVRMotionController, Warning, TEXT("HandSocketComponent::GetHandSocketTransform was missing required motion controller for bFlipForLeftand! Check that you are passing a controller into GetClosestSocketInRange!")); + } + else + { + EControllerHand HandType; + QueryController->GetHandType(HandType); + bool bIsRightHand = HandType == EControllerHand::Right; + if (bLeftHandDominant == bIsRightHand) + { + FTransform ReturnTrans = this->GetRelativeTransform(); + ReturnTrans.Mirror(GetAsEAxis(MirrorAxis), GetAsEAxis(FlipAxis)); + if (bOnlyFlipRotation) + { + ReturnTrans.SetTranslation(this->GetRelativeLocation()); + } + + if (USceneComponent* AttParent = this->GetAttachParent()) + { + ReturnTrans = ReturnTrans * AttParent->GetComponentTransform(); + } + return ReturnTrans; + } + } + } + + return this->GetComponentTransform(); +} + +FTransform UHandSocketComponent::GetMeshRelativeTransform(bool bIsRightHand, bool bUseParentScale, bool bUseMirrorScale) +{ + // Optionally mirror for left hand + + // Failsafe + if (!this->GetAttachParent()) + return FTransform::Identity; + + FTransform relTrans = this->GetRelativeTransform(); + FTransform HandTrans = GetHandRelativePlacement(); + FTransform ReturnTrans = FTransform::Identity; + + // Fix the scale + if (!bUseParentScale && this->IsUsingAbsoluteScale() /*&& !bDecoupleMeshPlacement*/) + { + FVector ParentScale = this->GetAttachParent()->GetComponentScale(); + // Take parent scale out of our relative transform early + relTrans.ScaleTranslation(ParentScale); + ReturnTrans = HandTrans * relTrans; + // We add in the inverse of the parent scale to adjust the hand mesh + ReturnTrans.ScaleTranslation((FVector(1.0f) / ParentScale)); + ReturnTrans.SetScale3D(FVector(1.0f)); + } + else + { + ReturnTrans = HandTrans * relTrans; + } + + // If we should mirror the transform, do it now that it is in our parent relative space + if ((bFlipForLeftHand && (bLeftHandDominant == bIsRightHand))) + { + //FTransform relTrans = this->GetRelativeTransform(); + MirrorHandTransform(ReturnTrans, relTrans); + + if (bUseMirrorScale) + { + ReturnTrans.SetScale3D(ReturnTrans.GetScale3D() * MirroredScale.GetSignVector()); + } + } + + + return ReturnTrans; +} + +#if WITH_EDITORONLY_DATA +FTransform UHandSocketComponent::GetBoneTransformAtTime(UAnimSequence* MyAnimSequence, /*float AnimTime,*/ int BoneIdx, FName BoneName, bool bUseRawDataOnly) +{ + float tracklen = MyAnimSequence->GetPlayLength(); + FTransform BoneTransform = FTransform::Identity; + IAnimationDataController& AnimController = MyAnimSequence->GetController(); + + if (UAnimDataModel* AnimModel = AnimController.GetModel()) + { + int32 TrackIndex = AnimModel->GetBoneTrackIndexByName(BoneName); + if (TrackIndex != INDEX_NONE) + { + MyAnimSequence->GetBoneTransform(BoneTransform, TrackIndex, /*AnimTime*/ tracklen, bUseRawDataOnly); + return BoneTransform; + } + } + + return FTransform::Identity; +} +#endif + +void UHandSocketComponent::OnRegister() +{ + +#if WITH_EDITORONLY_DATA + AActor* MyOwner = GetOwner(); + if (bShowVisualizationMesh && (MyOwner != nullptr) && !IsRunningCommandlet()) + { + if (HandVisualizerComponent == nullptr && bShowVisualizationMesh) + { + HandVisualizerComponent = NewObject<UPoseableMeshComponent>(MyOwner, NAME_None, RF_Transactional | RF_TextExportTransient); + HandVisualizerComponent->SetupAttachment(this); + HandVisualizerComponent->SetIsVisualizationComponent(true); + HandVisualizerComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + HandVisualizerComponent->CastShadow = false; + HandVisualizerComponent->CreationMethod = CreationMethod; + //HandVisualizerComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + HandVisualizerComponent->SetComponentTickEnabled(false); + HandVisualizerComponent->SetHiddenInGame(true); + HandVisualizerComponent->RegisterComponentWithWorld(GetWorld()); + //HandVisualizerComponent->SetUsingAbsoluteScale(true); + } + else if (!bShowVisualizationMesh && HandVisualizerComponent) + { + HideVisualizationMesh(); + } + + if (HandVisualizerComponent) + { + bTickedPose = false; + + if (VisualizationMesh) + { + HandVisualizerComponent->SetSkeletalMesh(VisualizationMesh); + if (HandPreviewMaterial) + { + HandVisualizerComponent->SetMaterial(0, (UMaterialInterface*)HandPreviewMaterial); + } + } + + PositionVisualizationMesh(); + PoseVisualizationToAnimation(true); + } + } + +#endif // WITH_EDITORONLY_DATA + + Super::OnRegister(); +} + +#if WITH_EDITORONLY_DATA + +void UHandSocketComponent::PositionVisualizationMesh() +{ + if (!HandVisualizerComponent) + { + return; + } + + if (USceneComponent* ParentAttach = this->GetAttachParent()) + { + FTransform relTrans = this->GetRelativeTransform(); + + if (bDecoupled != bDecoupleMeshPlacement) + { + if (bDecoupleMeshPlacement) + { + HandRelativePlacement = HandRelativePlacement * GetRelativeTransform(); + } + else + { + HandRelativePlacement = HandRelativePlacement.GetRelativeTransform(GetRelativeTransform()); + } + } + + FTransform HandPlacement = GetHandRelativePlacement(); + FTransform ReturnTrans = (HandPlacement * relTrans); + + if (bMirrorVisualizationMesh)//(bFlipForLeftHand && !bIsRightHand)) + { + MirrorHandTransform(ReturnTrans, relTrans); + } + + if ((bLeftHandDominant && !bMirrorVisualizationMesh) || (!bLeftHandDominant && bMirrorVisualizationMesh)) + { + ReturnTrans.SetScale3D(ReturnTrans.GetScale3D() * MirroredScale); + } + + HandVisualizerComponent->SetRelativeTransform(ReturnTrans.GetRelativeTransform(relTrans)/*newRel*/); + } +} + +void UHandSocketComponent::HideVisualizationMesh() +{ + if (!bShowVisualizationMesh && HandVisualizerComponent) + { + HandVisualizerComponent->SetVisibility(false); + HandVisualizerComponent->DestroyComponent(); + HandVisualizerComponent = nullptr; + } +} + +#endif + +#if WITH_EDITORONLY_DATA +void UHandSocketComponent::PoseVisualizationToAnimation(bool bForceRefresh) +{ + + if (!HandVisualizerComponent || !HandVisualizerComponent->SkeletalMesh) + return; + + TArray<FTransform> LocalPoses; + if (!HandTargetAnimation) + { + // Store local poses for posing + LocalPoses = HandVisualizerComponent->SkeletalMesh->GetSkeleton()->GetRefLocalPoses(); + } + + TArray<FName> BonesNames; + HandVisualizerComponent->GetBoneNames(BonesNames); + int32 Bones = HandVisualizerComponent->GetNumBones(); + + for (int32 i = 0; i < Bones; i++) + { + if (!HandTargetAnimation && !bUseCustomPoseDeltas) + { + HandVisualizerComponent->ResetBoneTransformByName(BonesNames[i]); + continue; + } + + FName ParentBone = HandVisualizerComponent->GetParentBone(BonesNames[i]); + FTransform ParentTrans = FTransform::Identity; + if (ParentBone != NAME_None) + { + ParentTrans = HandVisualizerComponent->GetBoneTransformByName(ParentBone, EBoneSpaces::ComponentSpace); + } + + + FQuat DeltaQuat = FQuat::Identity; + if (bUseCustomPoseDeltas) + { + for (FBPVRHandPoseBonePair BonePairC : CustomPoseDeltas) + { + if (BonePairC.BoneName == BonesNames[i]) + { + DeltaQuat = BonePairC.DeltaPose; + DeltaQuat.Normalize(); + break; + } + } + } + + FTransform BoneTrans = FTransform::Identity; + + if (HandTargetAnimation) + { + BoneTrans = GetBoneTransformAtTime(HandTargetAnimation, /*FLT_MAX,*/ i, BonesNames[i], false); // true; + } + else + { + BoneTrans = LocalPoses[i]; + } + + BoneTrans = BoneTrans * ParentTrans;// *HandVisualizerComponent->GetComponentTransform(); + BoneTrans.NormalizeRotation(); + + //DeltaQuat *= HandVisualizerComponent->GetComponentTransform().GetRotation().Inverse(); + + BoneTrans.ConcatenateRotation(DeltaQuat); + BoneTrans.NormalizeRotation(); + HandVisualizerComponent->SetBoneTransformByName(BonesNames[i], BoneTrans, EBoneSpaces::ComponentSpace); + + } + + if (HandVisualizerComponent && (!bTickedPose || bForceRefresh)) + { + // Tick Pose first + if (HandVisualizerComponent->IsRegistered()) + { + bTickedPose = true; + HandVisualizerComponent->TickPose(1.0f, false); + if (HandVisualizerComponent->MasterPoseComponent.IsValid()) + { + HandVisualizerComponent->UpdateSlaveComponent(); + } + else + { + HandVisualizerComponent->RefreshBoneTransforms(&HandVisualizerComponent->PrimaryComponentTick); + } + } + } +} + +void UHandSocketComponent::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) +{ + UHandSocketComponent* This = CastChecked<UHandSocketComponent>(InThis); + Collector.AddReferencedObject(This->HandVisualizerComponent); + + Super::AddReferencedObjects(InThis, Collector); +} + +void UHandSocketComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); + + if (HandVisualizerComponent) + { + HandVisualizerComponent->DestroyComponent(); + } +} + +#endif + +void UHandSocketComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UHandSocketComponent, bRepGameplayTags); + DOREPLIFETIME(UHandSocketComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UHandSocketComponent, GameplayTags, COND_Custom); +} + +void UHandSocketComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UHandSocketComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +//============================================================================= +UHandSocketComponent::~UHandSocketComponent() +{ +} + +#if WITH_EDITOR + +void UHandSocketComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty* PropertyThatChanged = PropertyChangedEvent.Property; + + if (PropertyThatChanged != nullptr) + { +#if WITH_EDITORONLY_DATA + if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandTargetAnimation) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, VisualizationMesh) + ) + { + PositionVisualizationMesh(); + PoseVisualizationToAnimation(true); + } + else if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, CustomPoseDeltas)) + { + PoseVisualizationToAnimation(true); + } + else if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, HandRelativePlacement)) + { + PositionVisualizationMesh(); + } + else if (PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UHandSocketComponent, bShowVisualizationMesh)) + { + HideVisualizationMesh(); + } +#endif + } +} +#endif + +UHandSocketComponent* UHandSocketComponent::GetHandSocketComponentFromObject(UObject* ObjectToCheck, FName SocketName) +{ + if (AActor* OwningActor = Cast<AActor>(ObjectToCheck)) + { + if (USceneComponent* OwningRoot = Cast<USceneComponent>(OwningActor->GetRootComponent())) + { + TArray<USceneComponent*> AttachChildren = OwningRoot->GetAttachChildren(); + for (USceneComponent* AttachChild : AttachChildren) + { + if (AttachChild && AttachChild->IsA<UHandSocketComponent>() && AttachChild->GetFName() == SocketName) + { + return Cast<UHandSocketComponent>(AttachChild); + } + } + } + } + else if (USceneComponent* OwningRoot = Cast<USceneComponent>(ObjectToCheck)) + { + TArray<USceneComponent*> AttachChildren = OwningRoot->GetAttachChildren(); + for (USceneComponent* AttachChild : AttachChildren) + { + if (AttachChild && AttachChild->IsA<UHandSocketComponent>() && AttachChild->GetFName() == SocketName) + { + return Cast<UHandSocketComponent>(AttachChild); + } + } + } + + return nullptr; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRButtonComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRButtonComponent.cpp new file mode 100644 index 0000000..6460913 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRButtonComponent.cpp @@ -0,0 +1,414 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRButtonComponent.h" +#include "Net/UnrealNetwork.h" +//#include "VRGripInterface.h" +#include "GripMotionControllerComponent.h" +#include "GameFramework/Character.h" + + //============================================================================= +UVRButtonComponent::UVRButtonComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + LastToggleTime = 0.0f; + DepressDistance = 8.0f; + ButtonEngageDepth = 8.0f; + DepressSpeed = 50.0f; + + ButtonAxis = EVRInteractibleAxis::Axis_Z; + ButtonType = EVRButtonType::Btn_Toggle_Return; + + MinTimeBetweenEngaging = 0.1f; + + bIsEnabled = true; + StateChangeAuthorityType = EVRStateChangeAuthorityType::CanChangeState_All; + bButtonState = false; + + this->SetCollisionResponseToAllChannels(ECR_Overlap); + + bSkipOverlapFiltering = false; + InitialRelativeTransform = FTransform::Identity; + + bReplicateMovement = false; +} + +//============================================================================= +UVRButtonComponent::~UVRButtonComponent() +{ +} + +void UVRButtonComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRButtonComponent, InitialRelativeTransform); + DOREPLIFETIME(UVRButtonComponent, bReplicateMovement); + DOREPLIFETIME(UVRButtonComponent, StateChangeAuthorityType); + DOREPLIFETIME_CONDITION(UVRButtonComponent, bButtonState, COND_InitialOnly); +} + +void UVRButtonComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Replicate the levers initial transform if we are replicating movement + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRButtonComponent, InitialRelativeTransform, bReplicateMovement); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRButtonComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialButtonLocation(); +} + +void UVRButtonComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + SetButtonToRestingPosition(); + + OnComponentBeginOverlap.AddUniqueDynamic(this, &UVRButtonComponent::OnOverlapBegin); + OnComponentEndOverlap.AddUniqueDynamic(this, &UVRButtonComponent::OnOverlapEnd); +} + +void UVRButtonComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + const float WorldTime = GetWorld()->GetRealTimeSeconds(); + + if (IsValid(LocalInteractingComponent)) + { + // If button was set to inactive during use + if (!bIsEnabled) + { + // Remove interacting component and return, next tick will begin lerping back + LocalInteractingComponent = nullptr; + return; + } + + FTransform OriginalBaseTransform = CalcNewComponentToWorld(InitialRelativeTransform); + + float CheckDepth = FMath::Clamp(GetAxisValue(InitialLocation) - GetAxisValue(OriginalBaseTransform.InverseTransformPosition(LocalInteractingComponent->GetComponentLocation())), 0.0f, DepressDistance); + + if (CheckDepth > 0.0f) + { + + float ClampMinDepth = 0.0f; + + // If active and a toggled stay, then clamp min to the toggled stay location + if (ButtonType == EVRButtonType::Btn_Toggle_Stay && bButtonState) + ClampMinDepth = -(ButtonEngageDepth + (1.e-2f)); // + NOT_SO_KINDA_SMALL_NUMBER + + float NewDepth = FMath::Clamp(GetAxisValue(InitialComponentLoc) + (-CheckDepth), -DepressDistance, ClampMinDepth); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(SetAxisValue(NewDepth)), false); + + if (ButtonType == EVRButtonType::Btn_Toggle_Return || ButtonType == EVRButtonType::Btn_Toggle_Stay) + { + if ((StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_All) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Server && GetNetMode() < ENetMode::NM_Client) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Owner && IsValid(LocalLastInteractingActor) && LocalLastInteractingActor->HasLocalNetOwner())) + { + if (!bToggledThisTouch && NewDepth <= (-ButtonEngageDepth) + KINDA_SMALL_NUMBER && (WorldTime - LastToggleTime) >= MinTimeBetweenEngaging) + { + LastToggleTime = WorldTime; + bToggledThisTouch = true; + bButtonState = !bButtonState; + ReceiveButtonStateChanged(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + OnButtonStateChanged.Broadcast(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } + } + } + } + } + else + { + // Std precision tolerance should be fine + if (this->GetRelativeLocation().Equals(GetTargetRelativeLocation())) + { + this->SetComponentTickEnabled(false); + + OnButtonEndInteraction.Broadcast(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + ReceiveButtonEndInteraction(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + + LocalInteractingComponent = nullptr; // Just reset it here so it only does it once + LocalLastInteractingComponent = nullptr; + } + else + this->SetRelativeLocation(FMath::VInterpConstantTo(this->GetRelativeLocation(), GetTargetRelativeLocation(), DeltaTime, DepressSpeed), false); + } + + + // Press buttons always get checked, both during press AND during lerping for if they are active or not. + if (ButtonType == EVRButtonType::Btn_Press) + { + if ((StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_All) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Server && GetNetMode() < ENetMode::NM_Client) || + (StateChangeAuthorityType == EVRStateChangeAuthorityType::CanChangeState_Owner && IsValid(LocalLastInteractingActor) && LocalLastInteractingActor->HasLocalNetOwner())) + { + // Check for if we should set the state of the button, done here as for the press button the lerp counts for input + bool bCheckState = (GetAxisValue(InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation())) <= (-ButtonEngageDepth) + KINDA_SMALL_NUMBER); + if (bButtonState != bCheckState && (WorldTime - LastToggleTime) >= MinTimeBetweenEngaging) + + { + LastToggleTime = WorldTime; + bButtonState = bCheckState; + ReceiveButtonStateChanged(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + OnButtonStateChanged.Broadcast(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } + } + } + +} + +bool UVRButtonComponent::IsValidOverlap_Implementation(UPrimitiveComponent * OverlapComponent) +{ + + // Early out on the simple checks + if (!OverlapComponent || OverlapComponent == GetAttachParent() || OverlapComponent->GetAttachParent() == GetAttachParent()) + return false; + + // Should return faster checking for owning character + AActor * OverlapOwner = OverlapComponent->GetOwner(); + if (OverlapOwner && OverlapOwner->IsA(ACharacter::StaticClass())) + return true; + + // Because epic motion controllers are not owned by characters have to check here too in case someone implements it like that + // Now since our grip controllers are a subclass to the std ones we only need to check for the base one instead of both. + USceneComponent * OurAttachParent = OverlapComponent->GetAttachParent(); + if (OurAttachParent && OurAttachParent->IsA(UMotionControllerComponent::StaticClass())) + return true; + + // Now check for if it is a grippable object and if it is currently held + if (OverlapComponent->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray<FBPGripPair> Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(OverlapComponent, Controllers, bIsHeld); + + if (bIsHeld) + return true; + } + else if(OverlapOwner && OverlapOwner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray<FBPGripPair> Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(OverlapOwner, Controllers, bIsHeld); + + if (bIsHeld) + return true; + } + + return false; +} + +void UVRButtonComponent::SetLastInteractingActor() +{ + + // Early out on the simple checks + if (!IsValid(LocalInteractingComponent) || LocalInteractingComponent == GetAttachParent() || LocalInteractingComponent->GetAttachParent() == GetAttachParent()) + { + LocalLastInteractingActor = nullptr; + LocalLastInteractingComponent = nullptr; + return; + } + + LocalLastInteractingComponent = LocalInteractingComponent; + + // Should return faster checking for owning character + AActor * OverlapOwner = LocalInteractingComponent->GetOwner(); + if (OverlapOwner && OverlapOwner->IsA(ACharacter::StaticClass())) + { + LocalLastInteractingActor = OverlapOwner; + return; + } + + // Now check for if it is a grippable object and if it is currently held + if (LocalInteractingComponent->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray<FBPGripPair> Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(LocalLastInteractingComponent.Get(), Controllers, bIsHeld); + + if (bIsHeld && Controllers.Num()) + { + AActor * ControllerOwner = Controllers[0].HoldingController != nullptr ? Controllers[0].HoldingController->GetOwner() : nullptr; + if (ControllerOwner) + { + LocalLastInteractingActor = ControllerOwner; + return; + } + } + } + else if (OverlapOwner && OverlapOwner->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + TArray<FBPGripPair> Controllers; + bool bIsHeld; + IVRGripInterface::Execute_IsHeld(OverlapOwner, Controllers, bIsHeld); + + if (bIsHeld && Controllers.Num()) + { + AActor * ControllerOwner = Controllers[0].HoldingController != nullptr ? Controllers[0].HoldingController->GetOwner() : nullptr; + if (ControllerOwner) + { + LocalLastInteractingActor = ControllerOwner; + return; + } + } + } + + // Fall back to the owner, wasn't held and wasn't a character + if (OverlapOwner) + { + LocalLastInteractingActor = OverlapOwner; + return; + } + + LocalLastInteractingActor = nullptr; + return; +} + +void UVRButtonComponent::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + // Other Actor is the actor that triggered the event. Check that is not ourself. + if (bIsEnabled && !IsValid(LocalInteractingComponent) && (bSkipOverlapFiltering || IsValidOverlap(OtherComp))) + { + LocalInteractingComponent = OtherComp; + + FTransform OriginalBaseTransform = CalcNewComponentToWorld(InitialRelativeTransform); + FVector loc = LocalInteractingComponent->GetComponentLocation(); + InitialLocation = OriginalBaseTransform.InverseTransformPosition(LocalInteractingComponent->GetComponentLocation()); + InitialComponentLoc = OriginalBaseTransform.InverseTransformPosition(this->GetComponentLocation()); + bToggledThisTouch = false; + + this->SetComponentTickEnabled(true); + + if (LocalInteractingComponent != LocalLastInteractingComponent.Get()) + { + SetLastInteractingActor(); + OnButtonBeginInteraction.Broadcast(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + ReceiveButtonBeginInteraction(LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } + } +} + +void UVRButtonComponent::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) +{ + if (IsValid(LocalInteractingComponent) && OtherComp == LocalInteractingComponent) + { + LocalInteractingComponent = nullptr; + } +} + +FVector UVRButtonComponent::GetTargetRelativeLocation() +{ + // If target is the half pressed + if (ButtonType == EVRButtonType::Btn_Toggle_Stay && bButtonState) + { + // 1.e-2f = MORE_KINDA_SMALL_NUMBER + return InitialRelativeTransform.TransformPosition(SetAxisValue(-(ButtonEngageDepth + (1.e-2f)))); + } + + // Else return going all the way back + return InitialRelativeTransform.GetTranslation(); + +} + +void UVRButtonComponent::SetButtonToRestingPosition(bool bLerpToPosition) +{ + switch (ButtonType) + { + case EVRButtonType::Btn_Press: + { + }break; + case EVRButtonType::Btn_Toggle_Return: + {}break; + case EVRButtonType::Btn_Toggle_Stay: + { + if (!bLerpToPosition) + { + float ClampMinDepth = 0.0f; + + // If active and a toggled stay, then clamp min to the toggled stay location + if (bButtonState) + ClampMinDepth = -(ButtonEngageDepth + (1.e-2f)); // + NOT_SO_KINDA_SMALL_NUMBER + + float NewDepth = FMath::Clamp(ClampMinDepth, -DepressDistance, ClampMinDepth); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(SetAxisValue(NewDepth)), false); + } + else + this->SetComponentTickEnabled(true); // This will trigger the lerp to resting position + + }break; + default:break; + } +} + +void UVRButtonComponent::SetButtonState(bool bNewButtonState, bool bCallButtonChangedEvent, bool bSnapIntoPosition) +{ + // No change + if (bButtonState == bNewButtonState) + return; + + bButtonState = bNewButtonState; + SetButtonToRestingPosition(!bSnapIntoPosition); + LastToggleTime = GetWorld()->GetRealTimeSeconds(); + + if (bCallButtonChangedEvent) + { + ReceiveButtonStateChanged(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + OnButtonStateChanged.Broadcast(bButtonState, LocalLastInteractingActor.Get(), LocalLastInteractingComponent.Get()); + } +} + +void UVRButtonComponent::ResetInitialButtonLocation() +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); +} + +bool UVRButtonComponent::IsButtonInUse() +{ + return IsValid(LocalInteractingComponent); +} + +float UVRButtonComponent::GetAxisValue(FVector CheckLocation) +{ + switch (ButtonAxis) + { + case EVRInteractibleAxis::Axis_X: + return CheckLocation.X; break; + case EVRInteractibleAxis::Axis_Y: + return CheckLocation.Y; break; + case EVRInteractibleAxis::Axis_Z: + return CheckLocation.Z; break; + default:return 0.0f; break; + } +} + +FVector UVRButtonComponent::SetAxisValue(float SetValue) +{ + FVector vec = FVector::ZeroVector; + + switch (ButtonAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.X = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Y = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Z = SetValue; break; + } + + return vec; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRDialComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRDialComponent.cpp new file mode 100644 index 0000000..26b74c4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRDialComponent.cpp @@ -0,0 +1,586 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRDialComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripMotionControllerComponent.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UVRDialComponent::UVRDialComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + DialRotationAxis = EVRInteractibleAxis::Axis_Z; + InteractorRotationAxis = EVRInteractibleAxis::Axis_X; + + bDialUsesAngleSnap = false; + bDialUseSnapAngleList = false; + SnapAngleThreshold = 45.0f; + SnapAngleIncrement = 45.0f; + LastSnapAngle = 0.0f; + RotationScaler = 1.0f; + + ClockwiseMaximumDialAngle = 180.0f; + CClockwiseMaximumDialAngle = 180.0f; + bDenyGripping = false; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + + bLerpBackOnRelease = false; + bSendDialEventsDuringLerp = false; + DialReturnSpeed = 90.0f; + bIsLerping = false; + + bDialUseDirectHandRotation = false; + LastGripRot = 0.0f; + InitialGripRot = 0.f; + InitialRotBackEnd = 0.f; + bUseRollover = false; +} + +//============================================================================= +UVRDialComponent::~UVRDialComponent() +{ +} + + +void UVRDialComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRDialComponent, InitialRelativeTransform); + //DOREPLIFETIME_CONDITION(UVRDialComponent, bIsLerping, COND_InitialOnly); + + DOREPLIFETIME(UVRDialComponent, bRepGameplayTags); + DOREPLIFETIME(UVRDialComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRDialComponent, GameplayTags, COND_Custom); +} + +void UVRDialComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UVRDialComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRDialComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialDialLocation(); // Load the original dial location +} + +void UVRDialComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + CalculateDialProgress(); + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UVRDialComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (bIsLerping) + { + if (bUseRollover) + { + this->SetDialAngle(FMath::FInterpConstantTo(CurRotBackEnd, 0.f, DeltaTime, DialReturnSpeed), bSendDialEventsDuringLerp); + } + else + { + // Flip lerp direction if we are on the other side + if (CurrentDialAngle > ClockwiseMaximumDialAngle) + this->SetDialAngle(FMath::FInterpConstantTo(CurRotBackEnd, 360.f, DeltaTime, DialReturnSpeed), bSendDialEventsDuringLerp); + else + this->SetDialAngle(FMath::FInterpConstantTo(CurRotBackEnd, 0.f, DeltaTime, DialReturnSpeed), bSendDialEventsDuringLerp); + } + + if (CurRotBackEnd == 0.f) + { + this->SetComponentTickEnabled(false); + bIsLerping = false; + OnDialFinishedLerping.Broadcast(); + ReceiveDialFinishedLerping(); + } + } + else + { + this->SetComponentTickEnabled(false); + } +} + +void UVRDialComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + + // #TODO: Should this use a pivot rotation? it wouldn't make that much sense to me? + float DeltaRot = 0.0f; + + if (!bDialUseDirectHandRotation) + { + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FVector CurInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(GrippingController->GetPivotLocation()); + + float NewRot = FRotator::ClampAxis(UVRInteractibleFunctionLibrary::GetAtan2Angle(DialRotationAxis, CurInteractorLocation)); + //DeltaRot = RotationScaler * ( NewRot - LastGripRot); + + DeltaRot = RotationScaler * FMath::FindDeltaAngleDegrees(LastGripRot, NewRot); + + float LimitTest = FRotator::ClampAxis(((NewRot - InitialGripRot) + InitialRotBackEnd)); + float MaxCheckValue = bUseRollover ? -CClockwiseMaximumDialAngle : 360.0f - CClockwiseMaximumDialAngle; + + if (FMath::IsNearlyZero(CClockwiseMaximumDialAngle)) + { + if (LimitTest > ClockwiseMaximumDialAngle && (CurRotBackEnd == ClockwiseMaximumDialAngle || CurRotBackEnd == 0.f)) + { + DeltaRot = 0.f; + } + } + else if (FMath::IsNearlyZero(ClockwiseMaximumDialAngle)) + { + if (LimitTest < MaxCheckValue && (CurRotBackEnd == MaxCheckValue || CurRotBackEnd == 0.f)) + { + DeltaRot = 0.f; + } + } + else if (LimitTest > ClockwiseMaximumDialAngle && LimitTest < MaxCheckValue && (CurRotBackEnd == ClockwiseMaximumDialAngle || CurRotBackEnd == MaxCheckValue)) + { + DeltaRot = 0.f; + } + + LastGripRot = NewRot; + } + else + { + FRotator curRotation = GrippingController->GetComponentRotation(); + DeltaRot = RotationScaler * UVRInteractibleFunctionLibrary::GetAxisValue(InteractorRotationAxis, (curRotation - LastRotation).GetNormalized()); + LastRotation = curRotation; + } + + AddDialAngle(DeltaRot, true); + + // Handle the auto drop + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + return; + } +} + +void UVRDialComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform CurrentTransform = this->GetComponentTransform(); + FTransform RelativeToGripTransform = ReversedRelativeTransform * CurrentTransform; + + //FTransform InitialTrans = RelativeToGripTransform.GetRelativeTransform(CurrentRelativeTransform); + + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialDropLocation = ReversedRelativeTransform.GetTranslation(); + + if (!bDialUseDirectHandRotation) + { + LastGripRot = FRotator::ClampAxis(UVRInteractibleFunctionLibrary::GetAtan2Angle(DialRotationAxis, InitialInteractorLocation)); + InitialGripRot = LastGripRot; + InitialRotBackEnd = CurRotBackEnd; + } + else + { + LastRotation = RelativeToGripTransform.GetRotation().Rotator(); // Forcing into world space now so that initial can be correct over the network + } + + bIsLerping = false; + + //OnGripped.Broadcast(GrippingController, GripInformation); +} + +void UVRDialComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + if (bDialUsesAngleSnap && bDialUseSnapAngleList) + { + float closestAngle = 0.f; + float closestVal = FMath::Abs(closestAngle - CurRotBackEnd); + float closestValt = 0.f; + for (float val : DialSnapAngleList) + { + closestValt = FMath::Abs(val - CurRotBackEnd); + if (closestValt < closestVal) + { + closestAngle = val; + closestVal = closestValt; + } + } + + if (closestAngle != LastSnapAngle) + { + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(closestAngle), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(closestAngle); + CurRotBackEnd = CurrentDialAngle; + + if (!FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + LastSnapAngle = CurrentDialAngle; + } + } + } + else if (bDialUsesAngleSnap && SnapAngleIncrement > 0.f && FMath::Abs(FMath::Fmod(CurRotBackEnd, SnapAngleIncrement)) <= FMath::Min(SnapAngleIncrement, SnapAngleThreshold)) + { + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurRotBackEnd = FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement); + CurrentDialAngle = FRotator::ClampAxis(FMath::RoundToFloat(CurRotBackEnd)); + + if (!FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + LastSnapAngle = CurrentDialAngle; + } + } + + if (bLerpBackOnRelease) + { + bIsLerping = true; + this->SetComponentTickEnabled(true); + } + else + this->SetComponentTickEnabled(false); + + //OnDropped.Broadcast(ReleasingController, GripInformation, bWasSocketed); +} + +void UVRDialComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRDialComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRDialComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRDialComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRDialComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRDialComponent::OnUsed_Implementation() {} +void UVRDialComponent::OnEndUsed_Implementation() {} +void UVRDialComponent::OnSecondaryUsed_Implementation() {} +void UVRDialComponent::OnEndSecondaryUsed_Implementation() {} +void UVRDialComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRDialComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRDialComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRDialComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRDialComponent::SimulateOnDrop_Implementation() +{ + return false; +} + +/*EGripCollisionType UVRDialComponent::SlotGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +} + +EGripCollisionType UVRDialComponent::FreeGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +}*/ + +EGripCollisionType UVRDialComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRDialComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRDialComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRDialComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRDialComponent::GripStiffness_Implementation() +{ + return 1500.0f; +} + +float UVRDialComponent::GripDamping_Implementation() +{ + return 200.0f; +}*/ + +void UVRDialComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = 0.0f; + GripDampingOut = 0.0f; +} + +FBPAdvGripSettings UVRDialComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRDialComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRDialComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +} + +void UVRDialComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +}*/ + +void UVRDialComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRDialComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRDialComponent::IsHeld_Implementation(TArray<FBPGripPair> & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRDialComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UVRDialComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if(!bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRDialComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + +bool UVRDialComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + return false; +} + +void UVRDialComponent::SetDialAngle(float DialAngle, bool bCallEvents) +{ + CurRotBackEnd = DialAngle; + AddDialAngle(0.0f); +} + +void UVRDialComponent::AddDialAngle(float DialAngleDelta, bool bCallEvents, bool bSkipSettingRot) +{ + //FindDeltaAngleDegrees + /** Utility to ensure angle is between +/- 180 degrees by unwinding. */ +//static float UnwindDegrees(float A) + float MaxCheckValue = bUseRollover ? -CClockwiseMaximumDialAngle : 360.0f - CClockwiseMaximumDialAngle; + + float DeltaRot = DialAngleDelta; + float tempCheck = bUseRollover ? CurRotBackEnd + DeltaRot : FRotator::ClampAxis(CurRotBackEnd + DeltaRot); + + // Clamp it to the boundaries + if (FMath::IsNearlyZero(CClockwiseMaximumDialAngle)) + { + CurRotBackEnd = FMath::Clamp(CurRotBackEnd + DeltaRot, 0.0f, ClockwiseMaximumDialAngle); + } + else if (FMath::IsNearlyZero(ClockwiseMaximumDialAngle)) + { + if (bUseRollover) + { + CurRotBackEnd = FMath::Clamp(CurRotBackEnd + DeltaRot, -CClockwiseMaximumDialAngle, 0.0f); + } + else + { + if (CurRotBackEnd < MaxCheckValue) + CurRotBackEnd = FMath::Clamp(360.0f + DeltaRot, MaxCheckValue, 360.0f); + else + CurRotBackEnd = FMath::Clamp(CurRotBackEnd + DeltaRot, MaxCheckValue, 360.0f); + } + } + else if(!bUseRollover && tempCheck > ClockwiseMaximumDialAngle && tempCheck < MaxCheckValue) + { + if (CurRotBackEnd < MaxCheckValue) + { + CurRotBackEnd = ClockwiseMaximumDialAngle; + } + else + { + CurRotBackEnd = MaxCheckValue; + } + } + else if (bUseRollover) + { + if (tempCheck > ClockwiseMaximumDialAngle) + { + CurRotBackEnd = ClockwiseMaximumDialAngle; + } + else if (tempCheck < MaxCheckValue) + { + CurRotBackEnd = MaxCheckValue; + } + else + { + CurRotBackEnd = tempCheck; + } + } + else + { + CurRotBackEnd = tempCheck; + } + + if (bDialUsesAngleSnap && bDialUseSnapAngleList) + { + float closestAngle = 0.f; + // Always default 0.0f to the list + float closestVal = FMath::Abs(closestAngle - CurRotBackEnd); + float closestValt = 0.f; + for (float val : DialSnapAngleList) + { + closestValt = FMath::Abs(val - CurRotBackEnd); + if (closestValt < closestVal) + { + closestAngle = val; + closestVal = closestValt; + } + } + + if (closestAngle != LastSnapAngle) + { + if (!bSkipSettingRot) + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(closestAngle), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(closestAngle); + + if (bCallEvents && !FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + } + + LastSnapAngle = CurrentDialAngle; + } + } + else if (bDialUsesAngleSnap && SnapAngleIncrement > 0.f && FMath::Abs(FMath::Fmod(CurRotBackEnd, SnapAngleIncrement)) <= FMath::Min(SnapAngleIncrement, SnapAngleThreshold)) + { + if (!bSkipSettingRot) + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement)), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(FMath::GridSnap(CurRotBackEnd, SnapAngleIncrement)); + + if (bCallEvents && !FMath::IsNearlyEqual(LastSnapAngle, CurrentDialAngle)) + { + ReceiveDialHitSnapAngle(CurrentDialAngle); + OnDialHitSnapAngle.Broadcast(CurrentDialAngle); + } + + LastSnapAngle = CurrentDialAngle; + } + else + { + if (!bSkipSettingRot) + this->SetRelativeRotation((FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot(DialRotationAxis, FMath::UnwindDegrees(CurRotBackEnd), FRotator::ZeroRotator)) * InitialRelativeTransform).Rotator()); + CurrentDialAngle = FMath::RoundToFloat(CurRotBackEnd); + } + +} + +void UVRDialComponent::ResetInitialDialLocation() +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + CurRotBackEnd = 0.0f; + CalculateDialProgress(); +} + +void UVRDialComponent::CalculateDialProgress() +{ + FTransform CurRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + LastGripRot = UVRInteractibleFunctionLibrary::GetDeltaAngleFromTransforms(DialRotationAxis, InitialRelativeTransform, CurRelativeTransform); + CurRotBackEnd = LastGripRot; + AddDialAngle(0.0f, false, true); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRInteractibleFunctionLibrary.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRInteractibleFunctionLibrary.cpp new file mode 100644 index 0000000..be3c8be --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRInteractibleFunctionLibrary.cpp @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "Interactibles/VRInteractibleFunctionLibrary.h" +//#include "Engine/Engine.h" + +//General Log +DEFINE_LOG_CATEGORY(VRInteractibleFunctionLibraryLog); \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRLeverComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRLeverComponent.cpp new file mode 100644 index 0000000..dc866ed --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRLeverComponent.cpp @@ -0,0 +1,865 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRLeverComponent.h" +#include "GripMotionControllerComponent.h" +//#include "PhysicsEngine/ConstraintInstance.h" +#include "VRExpansionFunctionLibrary.h" +//#include "PhysicsPublic.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UVRLeverComponent::UVRLeverComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + Stiffness = 1500.0f; + Damping = 200.0f; + + //SceneIndex = 0; + + bIsPhysicsLever = false; + ParentComponent = nullptr; + LeverRotationAxis = EVRInteractibleLeverAxis::Axis_X; + + LeverLimitNegative = 0.0f; + LeverLimitPositive = 90.0f; + FlightStickYawLimit = 180.0f; + bLeverState = false; + LeverTogglePercentage = 0.8f; + + LastDeltaAngle = 0.0f; + FullCurrentAngle = 0.0f; + + LeverReturnTypeWhenReleased = EVRInteractibleLeverReturnType::ReturnToZero; + LeverReturnSpeed = 50.0f; + + MomentumAtDrop = 0.0f; + LeverMomentumFriction = 5.0f; + MaxLeverMomentum = 180.0f; + FramesToAverage = 3; + + bBlendAxisValuesByAngleThreshold = false; + AngleThreshold = 90.0f; + + LastLeverAngle = 0.0f; + + bSendLeverEventsDuringLerp = false; + + InitialRelativeTransform = FTransform::Identity; + InitialInteractorLocation = FVector::ZeroVector; + InteractorOffsetTransform = FTransform::Identity; + AllCurrentLeverAngles = FRotator::ZeroRotator; + InitialGripRot = 0.0f; + qRotAtGrab = FQuat::Identity; + bIsLerping = false; + bUngripAtTargetRotation = false; + bDenyGripping = false; + + bIsLocked = false; + bAutoDropWhenLocked = true; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + + // Set to only overlap with things so that its not ruined by touching over actors + this->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); +} + +//============================================================================= +UVRLeverComponent::~UVRLeverComponent() +{ +} + + +void UVRLeverComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRLeverComponent, InitialRelativeTransform); + //DOREPLIFETIME_CONDITION(UVRLeverComponent, bIsLerping, COND_InitialOnly); + + DOREPLIFETIME(UVRLeverComponent, bRepGameplayTags); + DOREPLIFETIME(UVRLeverComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRLeverComponent, GameplayTags, COND_Custom); +} + +void UVRLeverComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Replicate the levers initial transform if we are replicating movement + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRLeverComponent, InitialRelativeTransform, bReplicateMovement); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UVRLeverComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRLeverComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialLeverLocation(); // Load the original lever location +} + +void UVRLeverComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + ReCalculateCurrentAngle(true); + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UVRLeverComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + bool bWasLerping = bIsLerping; + + // If we are locked then end the lerp, no point + if (bIsLocked) + { + + if (bWasLerping) + { + bIsLerping = false; + + // If we start lerping while locked, just end it + OnLeverFinishedLerping.Broadcast(CurrentLeverAngle); + ReceiveLeverFinishedLerping(CurrentLeverAngle); + } + + return; + } + + if (bIsLerping) + { + FTransform CurRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_X: + case EVRInteractibleLeverAxis::Axis_Y: + case EVRInteractibleLeverAxis::Axis_Z: + { + LerpAxis(FullCurrentAngle, DeltaTime); + }break; + case EVRInteractibleLeverAxis::Axis_XY: + case EVRInteractibleLeverAxis::FlightStick_XY: + { + // Only supporting LerpToZero with this mode currently + FQuat LerpedQuat = FMath::QInterpConstantTo(CurRelativeTransform.GetRelativeTransform(InitialRelativeTransform).GetRotation(), FQuat::Identity, DeltaTime, FMath::DegreesToRadians(LeverReturnSpeed)); + + if (LerpedQuat.IsIdentity()) + { + this->SetComponentTickEnabled(false); + bIsLerping = false; + bReplicateMovement = bOriginalReplicatesMovement; + this->SetRelativeRotation(InitialRelativeTransform.Rotator()); + } + else + { + this->SetRelativeRotation((FTransform(LerpedQuat) * InitialRelativeTransform).GetRotation()); + } + }break; + default:break; + } + } + + FTransform CurrentRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + CalculateCurrentAngle(CurrentRelativeTransform); + + + if (!bWasLerping && LeverReturnTypeWhenReleased == EVRInteractibleLeverReturnType::RetainMomentum) + { + // Rolling average across num samples + MomentumAtDrop -= MomentumAtDrop / FramesToAverage; + MomentumAtDrop += ((FullCurrentAngle - LastLeverAngle) / DeltaTime) / FramesToAverage; + + MomentumAtDrop = FMath::Min(MaxLeverMomentum, MomentumAtDrop); + + LastLeverAngle = FullCurrentAngle; + } + + // Check for events and set current state and check for auto drop + ProccessCurrentState(bWasLerping, true, true); + + // If the lerping state changed from the above + if (bWasLerping && !bIsLerping) + { + OnLeverFinishedLerping.Broadcast(CurrentLeverAngle); + ReceiveLeverFinishedLerping(CurrentLeverAngle); + } +} + +void UVRLeverComponent::ProccessCurrentState(bool bWasLerping, bool bThrowEvents, bool bCheckAutoDrop) +{ + bool bNewLeverState = (!FMath::IsNearlyZero(LeverLimitNegative) && FullCurrentAngle <= -(LeverLimitNegative * LeverTogglePercentage)) || (!FMath::IsNearlyZero(LeverLimitPositive) && FullCurrentAngle >= (LeverLimitPositive * LeverTogglePercentage)); + //if (FMath::Abs(CurrentLeverAngle) >= LeverLimit ) + if (bNewLeverState != bLeverState) + { + bLeverState = bNewLeverState; + + if (bThrowEvents && (bSendLeverEventsDuringLerp || !bWasLerping)) + { + ReceiveLeverStateChanged(bLeverState, FullCurrentAngle >= 0.0f ? EVRInteractibleLeverEventType::LeverPositive : EVRInteractibleLeverEventType::LeverNegative, CurrentLeverAngle, FullCurrentAngle); + OnLeverStateChanged.Broadcast(bLeverState, FullCurrentAngle >= 0.0f ? EVRInteractibleLeverEventType::LeverPositive : EVRInteractibleLeverEventType::LeverNegative, CurrentLeverAngle, FullCurrentAngle); + } + + if (bCheckAutoDrop) + { + if (!bWasLerping && bUngripAtTargetRotation && bLeverState && HoldingGrip.IsValid()) + { + FBPActorGripInformation GripInformation; + EBPVRResultSwitch result; + HoldingGrip.HoldingController->GetGripByID(GripInformation, HoldingGrip.GripID, result); + if (result == EBPVRResultSwitch::OnSucceeded && HoldingGrip.HoldingController->HasGripAuthority(GripInformation)) + { + HoldingGrip.HoldingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + } + } + } +} + +void UVRLeverComponent::OnUnregister() +{ + DestroyConstraint(); + Super::OnUnregister(); +} + +bool UVRLeverComponent::CheckAutoDrop(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) +{ + // Converted to a relative value now so it should be correct + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialInteractorDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + + return true; + } + + return false; +} + +void UVRLeverComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + if (bIsLocked) + { + if (bAutoDropWhenLocked) + { + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); + } + + return; + } + + // Handle manual tracking here + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform CurrentRelativeTransform = InitialRelativeTransform * ParentTransform; + FTransform PivotTransform = GrippingController->GetPivotTransform(); + FVector CurInteractorLocation = (InteractorOffsetTransform * PivotTransform).GetRelativeTransform(CurrentRelativeTransform).GetTranslation(); + + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_XY: + case EVRInteractibleLeverAxis::FlightStick_XY: + { + FRotator Rot; + + FVector nAxis; + float nAngle = 0.0f; + + FQuat::FindBetweenVectors(qRotAtGrab.UnrotateVector(InitialInteractorLocation), CurInteractorLocation).ToAxisAndAngle(nAxis, nAngle); + float MaxAngle = FMath::DegreesToRadians(LeverLimitPositive); + + bool bWasClamped = nAngle > MaxAngle; + nAngle = FMath::Clamp(nAngle, 0.0f, MaxAngle); + Rot = FQuat(nAxis, nAngle).Rotator(); + + if (LeverRotationAxis == EVRInteractibleLeverAxis::FlightStick_XY) + { + // Store our projected relative transform + FTransform CalcTransform = (FTransform(Rot) * InitialRelativeTransform); + + // Fixup yaw if this is a flight stick + + if (bWasClamped) + { + // If we clamped the angle due to limits then lets re-project the hand back to get the correct facing again + // This is only when things have been clamped to avoid the extra calculations + FTransform NewPivTrans = PivotTransform.GetRelativeTransform((CalcTransform * ParentTransform)); + CurInteractorLocation = NewPivTrans.GetTranslation(); + + FVector OffsetVal = CurInteractorLocation + NewPivTrans.GetRotation().RotateVector(InteractorOffsetTransform.GetTranslation()); + OffsetVal.Z = 0; + + CurInteractorLocation -= OffsetVal; + } + else + { + CurInteractorLocation = (CalcTransform * ParentTransform).InverseTransformPosition(GrippingController->GetPivotLocation()); + } + + float CurrentLeverYawAngle = FRotator::NormalizeAxis(UVRInteractibleFunctionLibrary::GetAtan2Angle(EVRInteractibleAxis::Axis_Z, CurInteractorLocation, InitialGripRot)); + + if (FlightStickYawLimit < 180.0f) + { + CurrentLeverYawAngle = FMath::Clamp(CurrentLeverYawAngle, -FlightStickYawLimit, FlightStickYawLimit); + } + + FQuat newLocalRot = CalcTransform.GetRotation() * FQuat(FVector::UpVector, FMath::DegreesToRadians(CurrentLeverYawAngle)); + this->SetRelativeRotation(newLocalRot.Rotator()); + } + else + { + this->SetRelativeRotation((FTransform(Rot) * InitialRelativeTransform).Rotator()); + } + } + break; + case EVRInteractibleLeverAxis::Axis_X: + case EVRInteractibleLeverAxis::Axis_Y: + case EVRInteractibleLeverAxis::Axis_Z: + { + float DeltaAngle = CalcAngle(LeverRotationAxis, CurInteractorLocation); + LastDeltaAngle = DeltaAngle; + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, DeltaAngle, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + }break; + default:break; + } + + // Recalc current angle + CurrentRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + CalculateCurrentAngle(CurrentRelativeTransform); + + // Check for events and set current state and check for auto drop + ProccessCurrentState(bIsLerping, true, true); + + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); +} + +void UVRLeverComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + ParentComponent = this->GetAttachParent(); + + if (bIsPhysicsLever) + { + SetupConstraint(); + } + else + { + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform CurrentTransform = this->GetComponentTransform(); + FTransform RelativeToGripTransform = FTransform::Identity; + + if (LeverRotationAxis == EVRInteractibleLeverAxis::FlightStick_XY) + { + // Offset the grip to the same height on the cross axis and centered on the lever + FVector InitialInteractorOffset = ReversedRelativeTransform.GetTranslation(); + FTransform InitTrans = ReversedRelativeTransform; + InitialInteractorOffset.X = 0; + InitialInteractorOffset.Y = 0; + InteractorOffsetTransform = ReversedRelativeTransform; + InteractorOffsetTransform.AddToTranslation(-InitialInteractorOffset); + InteractorOffsetTransform = FTransform(InteractorOffsetTransform.ToInverseMatrixWithScale()); + + InitialInteractorOffset = ReversedRelativeTransform.GetTranslation(); + InitialInteractorOffset.Z = 0; + + InitTrans.AddToTranslation(-InitialInteractorOffset); + RelativeToGripTransform = InitTrans * CurrentTransform; + } + else + { + RelativeToGripTransform = ReversedRelativeTransform * CurrentTransform; + InteractorOffsetTransform = FTransform::Identity; + } + + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialInteractorDropLocation = ReversedRelativeTransform.GetTranslation(); + + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_XY: + { + qRotAtGrab = this->GetComponentTransform().GetRelativeTransform(CurrentRelativeTransform).GetRotation(); + }break; + case EVRInteractibleLeverAxis::FlightStick_XY: + { + qRotAtGrab = this->GetComponentTransform().GetRelativeTransform(CurrentRelativeTransform).GetRotation(); + InitialGripRot = UVRInteractibleFunctionLibrary::GetAtan2Angle(EVRInteractibleAxis::Axis_Z, ReversedRelativeTransform.GetTranslation()); + }break; + case EVRInteractibleLeverAxis::Axis_X: + case EVRInteractibleLeverAxis::Axis_Y: + { + // Get our initial interactor rotation + InitialGripRot = UVRInteractibleFunctionLibrary::GetAtan2Angle((EVRInteractibleAxis)LeverRotationAxis, InitialInteractorLocation); + }break; + + case EVRInteractibleLeverAxis::Axis_Z: + { + // Get our initial interactor rotation + InitialGripRot = UVRInteractibleFunctionLibrary::GetAtan2Angle((EVRInteractibleAxis)LeverRotationAxis, InitialInteractorLocation); + }break; + + default:break; + } + + // Get out current rotation at grab + RotAtGrab = UVRInteractibleFunctionLibrary::GetDeltaAngleFromTransforms((EVRInteractibleAxis)LeverRotationAxis, CurrentRelativeTransform, CurrentTransform); + } + + LastLeverAngle = CurrentLeverAngle; + bIsLerping = false; + bIsInFirstTick = true; + MomentumAtDrop = 0.0f; + + this->SetComponentTickEnabled(true); + + //OnGripped.Broadcast(GrippingController, GripInformation); +} + +void UVRLeverComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + if(bIsPhysicsLever) + { + DestroyConstraint(); + FAttachmentTransformRules AttachRules(EAttachmentRule::KeepWorld, true); + this->AttachToComponent(ParentComponent.Get(), AttachRules); + } + + if (LeverReturnTypeWhenReleased != EVRInteractibleLeverReturnType::Stay) + { + bIsLerping = true; + this->SetComponentTickEnabled(true); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + bReplicateMovement = false; + } + else + { + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + } + + //OnDropped.Broadcast(ReleasingController, GripInformation, bWasSocketed); +} + +void UVRLeverComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRLeverComponent::SetIsLocked(bool bNewLockedState) +{ + bIsLocked = bNewLockedState; +} + +void UVRLeverComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRLeverComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRLeverComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRLeverComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRLeverComponent::OnUsed_Implementation() {} +void UVRLeverComponent::OnEndUsed_Implementation() {} +void UVRLeverComponent::OnSecondaryUsed_Implementation() {} +void UVRLeverComponent::OnEndSecondaryUsed_Implementation() {} +void UVRLeverComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRLeverComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRLeverComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRLeverComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRLeverComponent::SimulateOnDrop_Implementation() +{ + return false; +} + +/*EGripCollisionType UVRLeverComponent::SlotGripType_Implementation() +{ + if (bIsPhysicsLever) + return EGripCollisionType::ManipulationGrip; + else + return EGripCollisionType::CustomGrip; +} + +EGripCollisionType UVRLeverComponent::FreeGripType_Implementation() +{ + if (bIsPhysicsLever) + return EGripCollisionType::ManipulationGrip; + else + return EGripCollisionType::CustomGrip; +}*/ + +EGripCollisionType UVRLeverComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + if (bIsPhysicsLever) + return EGripCollisionType::ManipulationGrip; + else + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRLeverComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRLeverComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRLeverComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRLeverComponent::GripStiffness_Implementation() +{ + return Stiffness; +} + +float UVRLeverComponent::GripDamping_Implementation() +{ + return Damping; +}*/ +void UVRLeverComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = Stiffness; + GripDampingOut = Damping; +} + +FBPAdvGripSettings UVRLeverComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRLeverComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRLeverComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +} + +void UVRLeverComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +}*/ + +void UVRLeverComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRLeverComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRLeverComponent::IsHeld_Implementation(TArray<FBPGripPair> & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRLeverComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UVRLeverComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!bIsHeld && !bIsLerping) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRLeverComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + +bool UVRLeverComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + return false; +} + +bool UVRLeverComponent::DestroyConstraint() +{ + // #TODO: Recreate with chaos + return true; +} + +bool UVRLeverComponent::SetupConstraint() +{ + // #TODO: Recreate with chaos + return false; +} + +float UVRLeverComponent::ReCalculateCurrentAngle(bool bAllowThrowingEvents) +{ + FTransform CurRelativeTransform = this->GetComponentTransform().GetRelativeTransform(UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this)); + CalculateCurrentAngle(CurRelativeTransform); + ProccessCurrentState(bIsLerping, bAllowThrowingEvents, bAllowThrowingEvents); + return CurrentLeverAngle; +} + +void UVRLeverComponent::SetLeverAngle(float NewAngle, FVector DualAxisForwardVector, bool bAllowThrowingEvents) +{ + NewAngle = -NewAngle; // Need to inverse the sign + + FVector ForwardVector = DualAxisForwardVector; + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_X: + ForwardVector = FVector(FMath::Sign(NewAngle), 0.0f, 0.0f); break; + case EVRInteractibleLeverAxis::Axis_Y: + ForwardVector = FVector(0.0f, FMath::Sign(NewAngle), 0.0f); break; + case EVRInteractibleLeverAxis::Axis_Z: + ForwardVector = FVector(0.0f, 0.0f, FMath::Sign(NewAngle)); break; + default:break; + } + + FQuat NewLeverRotation(ForwardVector, FMath::DegreesToRadians(FMath::Abs(NewAngle))); + + this->SetRelativeTransform(FTransform(NewLeverRotation) * InitialRelativeTransform); + ReCalculateCurrentAngle(bAllowThrowingEvents); +} + +void UVRLeverComponent::ResetInitialLeverLocation(bool bAllowThrowingEvents) +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + CalculateCurrentAngle(InitialRelativeTransform); + ProccessCurrentState(bIsLerping, bAllowThrowingEvents, bAllowThrowingEvents); +} + +void UVRLeverComponent::CalculateCurrentAngle(FTransform & CurrentTransform) +{ + float Angle; + switch (LeverRotationAxis) + { + case EVRInteractibleLeverAxis::Axis_XY: + case EVRInteractibleLeverAxis::FlightStick_XY: + { + FTransform RelativeToSpace = CurrentTransform.GetRelativeTransform(InitialRelativeTransform); + FQuat CurrentRelRot = RelativeToSpace.GetRotation();// CurrentTransform.GetRotation(); + FVector UpVec = CurrentRelRot.GetUpVector(); + + CurrentLeverForwardVector = FVector::VectorPlaneProject(UpVec, FVector::UpVector); + CurrentLeverForwardVector.Normalize(); + + FullCurrentAngle = FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(UpVec, FVector::UpVector))); + CurrentLeverAngle = FMath::RoundToFloat(FullCurrentAngle); + + AllCurrentLeverAngles.Roll = FMath::Sign(UpVec.Y) * FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(FVector(0.0f, UpVec.Y, UpVec.Z), FVector::UpVector))); + AllCurrentLeverAngles.Pitch = FMath::Sign(UpVec.X) * FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(FVector(UpVec.X, 0.0f, UpVec.Z), FVector::UpVector))); + + if (bBlendAxisValuesByAngleThreshold) + { + FVector ProjectedLoc = FVector(UpVec.X, UpVec.Y, 0.0f).GetSafeNormal(); + AllCurrentLeverAngles.Pitch *= FMath::Clamp(1.0f - (FMath::Abs(FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(ProjectedLoc, FMath::Sign(UpVec.X) * FVector::ForwardVector)))) / AngleThreshold), 0.0f, 1.0f); + AllCurrentLeverAngles.Roll *= FMath::Clamp(1.0f - (FMath::Abs(FMath::RadiansToDegrees(FMath::Acos(FVector::DotProduct(ProjectedLoc, FMath::Sign(UpVec.Y) * FVector::RightVector)))) / AngleThreshold), 0.0f, 1.0f); + } + + AllCurrentLeverAngles.Roll = FMath::RoundToFloat(AllCurrentLeverAngles.Roll); + AllCurrentLeverAngles.Pitch = FMath::RoundToFloat(AllCurrentLeverAngles.Pitch); + + if (LeverRotationAxis == EVRInteractibleLeverAxis::FlightStick_XY) + { + AllCurrentLeverAngles.Yaw = FRotator::NormalizeAxis(FMath::RoundToFloat(UVRExpansionFunctionLibrary::GetHMDPureYaw_I(CurrentRelRot.Rotator()).Yaw)); + if (FlightStickYawLimit < 180.0f) + { + AllCurrentLeverAngles.Yaw = FMath::Clamp(AllCurrentLeverAngles.Yaw, -FlightStickYawLimit, FlightStickYawLimit); + } + } + else + AllCurrentLeverAngles.Yaw = 0.0f; + + }break; + default: + { + Angle = UVRInteractibleFunctionLibrary::GetDeltaAngleFromTransforms((EVRInteractibleAxis)LeverRotationAxis, InitialRelativeTransform, CurrentTransform); + FullCurrentAngle = Angle; + CurrentLeverAngle = FMath::RoundToFloat(FullCurrentAngle); + CurrentLeverForwardVector = UVRInteractibleFunctionLibrary::SetAxisValueVec((EVRInteractibleAxis)LeverRotationAxis, FMath::Sign(Angle)); + AllCurrentLeverAngles = UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, CurrentLeverAngle, FRotator::ZeroRotator); + + }break; + } +} + +void UVRLeverComponent::LerpAxis(float CurrentAngle, float DeltaTime) +{ + float TargetAngle = 0.0f; + float FinalReturnSpeed = LeverReturnSpeed; + + switch (LeverReturnTypeWhenReleased) + { + case EVRInteractibleLeverReturnType::LerpToMax: + { + if (CurrentAngle >= 0) + TargetAngle = FMath::RoundToFloat(LeverLimitPositive); + else + TargetAngle = -FMath::RoundToFloat(LeverLimitNegative); + }break; + case EVRInteractibleLeverReturnType::LerpToMaxIfOverThreshold: + { + if ((!FMath::IsNearlyZero(LeverLimitPositive) && CurrentAngle >= (LeverLimitPositive * LeverTogglePercentage))) + TargetAngle = FMath::RoundToFloat(LeverLimitPositive); + else if ((!FMath::IsNearlyZero(LeverLimitNegative) && CurrentAngle <= -(LeverLimitNegative * LeverTogglePercentage))) + TargetAngle = -FMath::RoundToFloat(LeverLimitNegative); + }break; + case EVRInteractibleLeverReturnType::RetainMomentum: + { + if (FMath::IsNearlyZero(MomentumAtDrop * DeltaTime, 0.1f)) + { + MomentumAtDrop = 0.0f; + this->SetComponentTickEnabled(false); + bIsLerping = false; + bReplicateMovement = bOriginalReplicatesMovement; + return; + } + else + { + MomentumAtDrop = FMath::FInterpTo(MomentumAtDrop, 0.0f, DeltaTime, LeverMomentumFriction); + + FinalReturnSpeed = FMath::Abs(MomentumAtDrop); + + if (MomentumAtDrop >= 0.0f) + TargetAngle = FMath::RoundToFloat(LeverLimitPositive); + else + TargetAngle = -FMath::RoundToFloat(LeverLimitNegative); + } + + }break; + case EVRInteractibleLeverReturnType::ReturnToZero: + default: + {}break; + } + + //float LerpedVal = FMath::FixedTurn(CurrentAngle, TargetAngle, FinalReturnSpeed * DeltaTime); + float LerpedVal = FMath::FInterpConstantTo(CurrentAngle, TargetAngle, DeltaTime, FinalReturnSpeed); + + if (FMath::IsNearlyEqual(LerpedVal, TargetAngle)) + { + if (LeverRestitution > 0.0f) + { + MomentumAtDrop = -(MomentumAtDrop * LeverRestitution); + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, TargetAngle, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + } + else + { + this->SetComponentTickEnabled(false); + bIsLerping = false; + bReplicateMovement = bOriginalReplicatesMovement; + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, TargetAngle, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + } + } + else + { + FTransform CalcTransform = (FTransform(UVRInteractibleFunctionLibrary::SetAxisValueRot((EVRInteractibleAxis)LeverRotationAxis, LerpedVal, FRotator::ZeroRotator)) * InitialRelativeTransform); + this->SetRelativeRotation(CalcTransform.Rotator()); + } +} + +float UVRLeverComponent::CalcAngle(EVRInteractibleLeverAxis AxisToCalc, FVector CurInteractorLocation, bool bSkipLimits) +{ + float ReturnAxis = 0.0f; + + ReturnAxis = UVRInteractibleFunctionLibrary::GetAtan2Angle((EVRInteractibleAxis)AxisToCalc, CurInteractorLocation, InitialGripRot); + + if (bSkipLimits) + return ReturnAxis; + + if (LeverLimitPositive > 0.0f && LeverLimitNegative > 0.0f && FMath::IsNearlyEqual(LeverLimitNegative, 180.f, 0.01f) && FMath::IsNearlyEqual(LeverLimitPositive, 180.f, 0.01f)) + { + // Don't run the clamping or the flip detection, we are a 360 degree lever + } + else + { + ReturnAxis = FMath::ClampAngle(FRotator::NormalizeAxis(RotAtGrab + ReturnAxis), -LeverLimitNegative, LeverLimitPositive); + + // Ignore rotations that would flip the angle of the lever to the other side, with a 90 degree allowance + if (!bIsInFirstTick && ((LeverLimitPositive > 0.0f && LastDeltaAngle >= LeverLimitPositive) || (LeverLimitNegative > 0.0f && LastDeltaAngle <= -LeverLimitNegative)) && FMath::Sign(LastDeltaAngle) != FMath::Sign(ReturnAxis)) + { + ReturnAxis = LastDeltaAngle; + } + } + + bIsInFirstTick = false; + return ReturnAxis; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRMountComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRMountComponent.cpp new file mode 100644 index 0000000..23d01cf --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRMountComponent.cpp @@ -0,0 +1,582 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRMountComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "GripMotionControllerComponent.h" +//#include "PhysicsPublic.h" +//#include "PhysicsEngine/ConstraintInstance.h" +#include "Net/UnrealNetwork.h" + +//============================================================================= +UVRMountComponent::UVRMountComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + Stiffness = 1500.0f; + Damping = 200.0f; + + MountRotationAxis = EVRInteractibleMountAxis::Axis_XZ; + + + InitialRelativeTransform = FTransform::Identity; + InitialInteractorLocation = FVector::ZeroVector; + InitialGripRot = 0.0f; + qRotAtGrab = FQuat::Identity; + + bDenyGripping = false; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + + FlipingZone = 0.4; + FlipReajustYawSpeed = 7.7; + + // Set to only overlap with things so that its not ruined by touching over actors + this->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); +} + +//============================================================================= +UVRMountComponent::~UVRMountComponent() +{ +} + + +void UVRMountComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRMountComponent, bRepGameplayTags); + DOREPLIFETIME(UVRMountComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRMountComponent, GameplayTags, COND_Custom); +} + +void UVRMountComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UVRMountComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRMountComponent::OnRegister() +{ + Super::OnRegister(); + ResetInitialMountLocation(); // Load the original mount location +} + +void UVRMountComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); +} + +void UVRMountComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + +} + +void UVRMountComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void UVRMountComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + // Handle manual tracking here + + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FVector CurInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(GrippingController->GetPivotLocation()); + + switch (MountRotationAxis) + { + case EVRInteractibleMountAxis::Axis_XZ: + { + //The current Mount to target location vector. + FVector MountToTarget; + + //In case the Mount is initially gripped on the back (negative of relative x axis) + if (GrippedOnBack) + { + CurInteractorLocation = CurInteractorLocation *-1; + } + + //Rotate the Initial Grip relative to the Forward Axis so it represents the current correct vector after Mount is rotated. + FRotator RelativeRot = GetRelativeRotation(); + FVector CurToForwardAxisVec = FRotator(RelativeRot.Pitch, RelativeRot.Yaw, TwistDiff).RotateVector(InitialGripToForwardVec); + + //The Current InteractorLocation based on current interactor location to intersection point on sphere with forward axis. + CurInteractorLocation = (CurInteractorLocation.GetSafeNormal() * InitialInteractorLocation.Size() + CurToForwardAxisVec).GetSafeNormal()*CurInteractorLocation.Size(); + + FRotator Rot; + + //If we are inside the fliping zone defined from Cone Area on top or bottom. ToDo: This probably can be combined with later FlipZone. + if (bIsInsideFrontFlipingZone || bIsInsideBackFlipZone) + { + // Entering the flip zone for the first time, a initial forward plane is made to ajust currentinteractorlocation relative x and y. This immitates a mechanical pull/push + if (!bFirstEntryToHalfFlipZone) + { + //Unmodified Right vector can still be used here to create the plane, before it will be changed later. + ForwardPullPlane = FPlane(FVector::ZeroVector, FVector(EntryRightVec.X, EntryRightVec.Y, 0).GetSafeNormal()); + + bFirstEntryToHalfFlipZone = true; + bLerpingOutOfFlipZone = false; + + CurInterpGripLoc = CurInteractorLocation; + + CurPointOnForwardPlane = FPlane::PointPlaneProject(CurInteractorLocation, ForwardPullPlane); + } + + LastPointOnForwardPlane = CurPointOnForwardPlane; + + CurPointOnForwardPlane = FPlane::PointPlaneProject(CurInteractorLocation, ForwardPullPlane); + + FVector ForwardPlainDiffVec = CurPointOnForwardPlane - LastPointOnForwardPlane; + + //Add the difference to how much we moved trough the forward plane since the last frame. + CurInterpGripLoc += ForwardPlainDiffVec; + + // InterpTo the current projected point on forward plane over time when inside the FlipZone. + CurInterpGripLoc = FMath::VInterpConstantTo(CurInterpGripLoc, CurPointOnForwardPlane, GetWorld()->GetDeltaSeconds(), 50); + + //The current location of the motion controller projected on to the forward plane. Only x and y is used from interpolation. + MountToTarget = FVector(CurInterpGripLoc.X, CurInterpGripLoc.Y, CurInteractorLocation.Z).GetSafeNormal(); + + //Save the CurInteractorLocation once to modify roll with it later + CurInteractorLocation = FVector(CurInterpGripLoc.X, CurInterpGripLoc.Y, CurInteractorLocation.Z); + } + else + { + //When going out of whole FlipZone Lerp yaw back to curinteractorlocation in case relative entry xy deviates to much from when leaving area + if (bLerpingOutOfFlipZone) + { + //If projected point on plane is not close enough to the "real" motion controller location lerp + if ((CurInterpGripLoc - CurInteractorLocation).Size() >= 1.0f) + { + //How fast yaw should rotate back when leaving flipzone. ToDo: For LerpOutSpeed maybe better us distance deviation + LerpOutAlpha = FMath::Clamp(LerpOutAlpha + FlipReajustYawSpeed / 100.0f, 0.0f, 1.0f); + + //Lerp + CurInterpGripLoc = CurInterpGripLoc + LerpOutAlpha * (CurInteractorLocation - CurInterpGripLoc); + + //The new vector to make rotation from. + MountToTarget = CurInterpGripLoc.GetSafeNormal(); + + //We left the flipzone completly, set everything back to normal. + if (LerpOutAlpha >= 0.97f) + { + bLerpingOutOfFlipZone = false; + bIsInsideBackFlipZone = false; + } + } + else + { + //If we are already near the real controller location just snap back + bLerpingOutOfFlipZone = false; + bIsInsideBackFlipZone = false; + MountToTarget = CurInteractorLocation.GetSafeNormal(); + } + } + else + { + //There is still a possibility here maybe to be inside backflipzone, but Mount is actually already outside. So just use unmodified rotation. + MountToTarget = CurInteractorLocation.GetSafeNormal(); + bIsInsideBackFlipZone = false; + } + + } + + //Setting the relative rotation once before using "accumulated" relative rotation to calculate roll onto it. + Rot = MountToTarget.Rotation(); + this->SetRelativeRotation((FTransform(FRotator(Rot.Pitch, Rot.Yaw, 0))*InitialRelativeTransform).Rotator()); + + + FVector nAxis; + float FlipAngle; + FQuat::FindBetweenVectors(FVector(0, 0, 1), CurInteractorLocation.GetSafeNormal()).ToAxisAndAngle(nAxis, FlipAngle); + + // This part takes care of the roll rotation ff Mount is inside flipping zone on top or on bottom. + if (FlipAngle < FlipingZone || FlipAngle > PI - FlipingZone) + { + //When entering FlipZone for the first time setup initial properties + if (!bIsInsideFrontFlipingZone && !bIsInsideBackFlipZone) + { + //We entered the FrontFlipzone + bIsInsideFrontFlipingZone = true; + + //Up and Right Vector when entering the FlipZone. Parent Rotation is accounted for. + if (USceneComponent * ParentComp = GetAttachParent()) + { + EntryUpVec = ParentComp->GetComponentRotation().UnrotateVector(GetUpVector()); + EntryRightVec = ParentComp->GetComponentRotation().UnrotateVector(GetRightVector()); + } + else + { + EntryUpVec = GetUpVector(); + EntryRightVec = GetRightVector(); + } + + //Only relative x and y is important for a Mount which has XY rotation limitation for the flip plane. + EntryUpXYNeg = FVector(EntryUpVec.X, EntryUpVec.Y, 0).GetSafeNormal()*-1; + //If flipping over the bottom + if (FlipAngle > PI - FlipingZone) + { + EntryUpXYNeg *= -1; + } + + //A Plane perpendicular to the FlipZone relative EntryPoint XY UpVector. This plane determines when the roll has to be turned by 180 degree + FlipPlane = FPlane(FVector::ZeroVector, EntryUpXYNeg); + } + + //CurInteractor vector to its projected point on flipplane + FVector CurInteractorToFlipPlaneVec = CurInteractorLocation - FPlane::PointPlaneProject(CurInteractorLocation, FlipPlane); + + if (bIsInsideFrontFlipingZone) + { + //If Mount rotation is on or over flipplane but still inside frontflipzone flip the roll. + if (FVector::DotProduct(CurInteractorToFlipPlaneVec, EntryUpXYNeg) <= 0) + { + bIsInsideFrontFlipingZone = false; + bIsInsideBackFlipZone = true; + + bIsFlipped = !bIsFlipped; + + if (bIsFlipped) + { + TwistDiff = 180; + } + else + { + TwistDiff = 0; + } + } + else + { + //If Mount Rotation is still inside FrontFlipZone ajust the roll so it looks naturally when moving the Mount against the FlipPlane + + FVector RelativeUpVec = GetUpVector(); + + if(USceneComponent * ParentComp = GetAttachParent()) + RelativeUpVec = ParentComp->GetComponentRotation().UnrotateVector(RelativeUpVec); + + FVector CurrentUpVec = FVector(RelativeUpVec.X, RelativeUpVec.Y, 0).GetSafeNormal(); + + //If rotating over the top ajust relative UpVector + if (FlipAngle < FlipingZone) + { + CurrentUpVec *= -1; + } + + float EntryTwist = FMath::Atan2(EntryUpXYNeg.Y, EntryUpXYNeg.X); + float CurTwist = FMath::Atan2(CurrentUpVec.Y, CurrentUpVec.X); + + //Rotate the roll so relative up vector x y looks at the flip plane + if (bIsFlipped) + { + TwistDiff = FMath::RadiansToDegrees(EntryTwist - CurTwist - PI); + } + else + { + TwistDiff = FMath::RadiansToDegrees(EntryTwist - CurTwist); + } + } + } + else + { + //If Inside Back Flip Zone just flip the roll. ToDo: Dont just ajust roll to 0 or 180. Calculate Twist diff according to up vector to FlipPlane. + if (bIsInsideBackFlipZone) + { + if (FVector::DotProduct(CurInteractorToFlipPlaneVec, EntryUpXYNeg) >= 0) + { + bIsInsideFrontFlipingZone = true; + bIsInsideBackFlipZone = false; + + bIsFlipped = !bIsFlipped; + + if (bIsFlipped) + { + TwistDiff = 180; + } + else + { + TwistDiff = 0; + } + } + } + } + + } + else + { + //If the Mount went into the flipping zone and back out without going over flip plane reset roll + bIsInsideFrontFlipingZone = false; + bIsInsideBackFlipZone = false; + + if (bIsFlipped) + { + TwistDiff = 180; + } + else + { + TwistDiff = 0; + } + + if (bFirstEntryToHalfFlipZone) + { + //If never left FlipZone before but doing now it now, reset first time entry bool. ToDo: Maybe better to do this elsewhere. + bFirstEntryToHalfFlipZone = false; + LerpOutAlpha = 0; + + //We left the flipzone so rotate yaw back from interpolated controller position on forward pull plane back to "real" one. + bLerpingOutOfFlipZone = true; + } + + } + + //Add Roll modifications to accumulated LocalRotation. + this->AddLocalRotation(FRotator(0, 0, -TwistDiff)); + + }break; + default:break; + } + + // #TODO: This drop code is incorrect, it is based off of the initial point and not the location at grip - revise it at some point + // Also set it to after rotation + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialInteractorDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + return; + } +} + +void UVRMountComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform RelativeToGripTransform = ReversedRelativeTransform * this->GetComponentTransform(); + + //continue here CurToForwardAxis is based on last gripped location ---> change this + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialInteractorDropLocation = ReversedRelativeTransform.GetTranslation(); + + switch (MountRotationAxis) + { + case EVRInteractibleMountAxis::Axis_XZ: + { + //spaceharry + + qRotAtGrab = this->GetComponentTransform().GetRelativeTransform(CurrentRelativeTransform).GetRotation(); + + FVector ForwardVectorToUse = GetForwardVector(); + + if (USceneComponent * ParentComp = GetAttachParent()) + { + ForwardVectorToUse = ParentComp->GetComponentRotation().UnrotateVector(ForwardVectorToUse); + } + + InitialForwardVector = InitialInteractorLocation.Size() * ForwardVectorToUse; + + if (FVector::DotProduct(InitialInteractorLocation, ForwardVectorToUse) <= 0) + { + GrippedOnBack = true; + InitialGripToForwardVec = (InitialForwardVector + InitialInteractorLocation); + } + else + { + InitialGripToForwardVec = InitialForwardVector - InitialInteractorLocation; + GrippedOnBack = false; + } + + FRotator RelativeRot = GetRelativeRotation(); + InitialGripToForwardVec = FRotator(RelativeRot.Pitch, RelativeRot.Yaw, TwistDiff).UnrotateVector(InitialGripToForwardVec); + + }break; + default:break; + } + + + + + this->SetComponentTickEnabled(true); +} + +void UVRMountComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + this->SetComponentTickEnabled(false); +} + +void UVRMountComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRMountComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRMountComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRMountComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRMountComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRMountComponent::OnUsed_Implementation() {} +void UVRMountComponent::OnEndUsed_Implementation() {} +void UVRMountComponent::OnSecondaryUsed_Implementation() {} +void UVRMountComponent::OnEndSecondaryUsed_Implementation() {} +void UVRMountComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRMountComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRMountComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRMountComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRMountComponent::SimulateOnDrop_Implementation() +{ + return false; +} + + +EGripCollisionType UVRMountComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRMountComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRMountComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRMountComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRMountComponent::GripStiffness_Implementation() +{ +return Stiffness; +} + +float UVRMountComponent::GripDamping_Implementation() +{ +return Damping; +}*/ +void UVRMountComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = Stiffness; + GripDampingOut = Damping; +} + +FBPAdvGripSettings UVRMountComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRMountComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRMountComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ +bHadSlotInRange = false; +} + +void UVRMountComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ +bHadSlotInRange = false; +}*/ + +void UVRMountComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRMountComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRMountComponent::IsHeld_Implementation(TArray<FBPGripPair> & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRMountComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!bIsHeld) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRMountComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + + +bool UVRMountComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + return false; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRSliderComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRSliderComponent.cpp new file mode 100644 index 0000000..1bcd2d4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Interactibles/VRSliderComponent.cpp @@ -0,0 +1,941 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Interactibles/VRSliderComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "Components/SplineComponent.h" +#include "GripMotionControllerComponent.h" +#include "Net/UnrealNetwork.h" + + //============================================================================= +UVRSliderComponent::UVRSliderComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetGenerateOverlapEvents(true); + this->PrimaryComponentTick.bStartWithTickEnabled = false; + PrimaryComponentTick.bCanEverTick = true; + + bRepGameplayTags = false; + + // Defaulting these true so that they work by default in networked environments + bReplicateMovement = true; + + MovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement; + BreakDistance = 100.0f; + + InitialRelativeTransform = FTransform::Identity; + bDenyGripping = false; + + bUpdateInTick = false; + bPassThrough = false; + + MinSlideDistance = FVector::ZeroVector; + MaxSlideDistance = FVector(10.0f, 0.f, 0.f); + SliderRestitution = 0.0f; + CurrentSliderProgress = 0.0f; + LastSliderProgress = FVector::ZeroVector;//0.0f; + SplineLastSliderProgress = 0.0f; + + MomentumAtDrop = FVector::ZeroVector;// 0.0f; + SplineMomentumAtDrop = 0.0f; + SliderMomentumFriction = 3.0f; + MaxSliderMomentum = 1.0f; + FramesToAverage = 3; + + InitialInteractorLocation = FVector::ZeroVector; + InitialGripLoc = FVector::ZeroVector; + + bSlideDistanceIsInParentSpace = true; + bUseLegacyLogic = false; + bIsLocked = false; + bAutoDropWhenLocked = true; + + SplineComponentToFollow = nullptr; + + bFollowSplineRotationAndScale = false; + SplineLerpType = EVRInteractibleSliderLerpType::Lerp_None; + SplineLerpValue = 8.f; + + PrimarySlotRange = 100.f; + SecondarySlotRange = 100.f; + GripPriority = 1; + LastSliderProgressState = -1.0f; + LastInputKey = 0.0f; + + bSliderUsesSnapPoints = false; + SnapIncrement = 0.1f; + SnapThreshold = 0.1f; + bIncrementProgressBetweenSnapPoints = false; + EventThrowThreshold = 1.0f; + bHitEventThreshold = false; + + // Set to only overlap with things so that its not ruined by touching over actors + this->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap); +} + +//============================================================================= +UVRSliderComponent::~UVRSliderComponent() +{ +} + + +void UVRSliderComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UVRSliderComponent, InitialRelativeTransform); + DOREPLIFETIME(UVRSliderComponent, SplineComponentToFollow); + //DOREPLIFETIME_CONDITION(UVRSliderComponent, bIsLerping, COND_InitialOnly); + + DOREPLIFETIME(UVRSliderComponent, bRepGameplayTags); + DOREPLIFETIME(UVRSliderComponent, bReplicateMovement); + DOREPLIFETIME_CONDITION(UVRSliderComponent, GameplayTags, COND_Custom); +} + +void UVRSliderComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Replicate the levers initial transform if we are replicating movement + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRSliderComponent, InitialRelativeTransform, bReplicateMovement); + //DOREPLIFETIME_ACTIVE_OVERRIDE(UVRSliderComponent, SplineComponentToFollow, bReplicateMovement); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE(UVRSliderComponent, GameplayTags, bRepGameplayTags); + + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UVRSliderComponent::OnRegister() +{ + Super::OnRegister(); + + // Init the slider settings + if (USplineComponent * ParentSpline = Cast<USplineComponent>(GetAttachParent())) + { + SetSplineComponentToFollow(ParentSpline); + } + else + { + ResetInitialSliderLocation(); + } +} + +void UVRSliderComponent::BeginPlay() +{ + // Call the base class + Super::BeginPlay(); + + CalculateSliderProgress(); + + bOriginalReplicatesMovement = bReplicateMovement; +} + +void UVRSliderComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Call supers tick (though I don't think any of the base classes to this actually implement it) + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (bIsHeld && bUpdateInTick && HoldingGrip.HoldingController) + { + FBPActorGripInformation GripInfo; + EBPVRResultSwitch Result; + HoldingGrip.HoldingController->GetGripByID(GripInfo, HoldingGrip.GripID, Result); + + if (Result == EBPVRResultSwitch::OnSucceeded) + { + bPassThrough = true; + TickGrip_Implementation(HoldingGrip.HoldingController, GripInfo, DeltaTime); + bPassThrough = false; + } + return; + } + + // If we are locked then end the lerp, no point + if (bIsLocked) + { + // Notify the end user + OnSliderFinishedLerping.Broadcast(CurrentSliderProgress); + ReceiveSliderFinishedLerping(CurrentSliderProgress); + + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + + return; + } + + if (bIsLerping) + { + if ((SplineComponentToFollow && FMath::IsNearlyZero((SplineMomentumAtDrop * DeltaTime), 0.00001f)) || (!SplineComponentToFollow && (MomentumAtDrop * DeltaTime).IsNearlyZero(0.00001f))) + { + bIsLerping = false; + } + else + { + if (this->SplineComponentToFollow) + { + SplineMomentumAtDrop = FMath::FInterpTo(SplineMomentumAtDrop, 0.0f, DeltaTime, SliderMomentumFriction); + float newProgress = CurrentSliderProgress + (SplineMomentumAtDrop * DeltaTime); + + if (newProgress < 0.0f || FMath::IsNearlyEqual(newProgress, 0.0f, 0.00001f)) + { + if (SliderRestitution > 0.0f) + { + // Reverse the momentum + SplineMomentumAtDrop = -(SplineMomentumAtDrop * SliderRestitution); + this->SetSliderProgress(0.0f); + } + else + { + bIsLerping = false; + this->SetSliderProgress(0.0f); + } + } + else if (newProgress > 1.0f || FMath::IsNearlyEqual(newProgress, 1.0f, 0.00001f)) + { + if (SliderRestitution > 0.0f) + { + // Reverse the momentum + SplineMomentumAtDrop = -(SplineMomentumAtDrop * SliderRestitution); + this->SetSliderProgress(1.0f); + } + else + { + bIsLerping = false; + this->SetSliderProgress(1.0f); + } + } + else + { + this->SetSliderProgress(newProgress); + } + } + else + { + MomentumAtDrop = FMath::VInterpTo(MomentumAtDrop, FVector::ZeroVector, DeltaTime, SliderMomentumFriction); + + FVector ClampedLocation = ClampSlideVector(InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation()) + (MomentumAtDrop * DeltaTime)); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(ClampedLocation)); + CurrentSliderProgress = GetCurrentSliderProgress(bSlideDistanceIsInParentSpace ? ClampedLocation * InitialRelativeTransform.GetScale3D() : ClampedLocation); + float newProgress = CurrentSliderProgress; + if (SliderRestitution > 0.0f) + { + // Implement bounce + FVector CurLoc = ClampedLocation; + + if ( + (FMath::Abs(MinSlideDistance.X) > 0.0f && CurLoc.X <= -FMath::Abs(this->MinSlideDistance.X)) || + (FMath::Abs(MinSlideDistance.Y) > 0.0f && CurLoc.Y <= -FMath::Abs(this->MinSlideDistance.Y)) || + (FMath::Abs(MinSlideDistance.Z) > 0.0f && CurLoc.Z <= -FMath::Abs(this->MinSlideDistance.Z)) || + (FMath::Abs(MaxSlideDistance.X) > 0.0f && CurLoc.X >= FMath::Abs(this->MaxSlideDistance.X)) || + (FMath::Abs(MaxSlideDistance.Y) > 0.0f && CurLoc.Y >= FMath::Abs(this->MaxSlideDistance.Y)) || + (FMath::Abs(MaxSlideDistance.Z) > 0.0f && CurLoc.Z >= FMath::Abs(this->MaxSlideDistance.Z)) + ) + { + MomentumAtDrop = (-MomentumAtDrop * SliderRestitution); + } + } + else + { + if (newProgress < 0.0f || FMath::IsNearlyEqual(newProgress, 0.0f, 0.00001f)) + { + bIsLerping = false; + this->SetSliderProgress(0.0f); + } + else if (newProgress > 1.0f || FMath::IsNearlyEqual(newProgress, 1.0f, 0.00001f)) + { + bIsLerping = false; + this->SetSliderProgress(1.0f); + } + } + } + } + + if (!bIsLerping) + { + // Notify the end user + OnSliderFinishedLerping.Broadcast(CurrentSliderProgress); + ReceiveSliderFinishedLerping(CurrentSliderProgress); + + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + } + + // Check for the hit point always + CheckSliderProgress(); + } +} + +void UVRSliderComponent::TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) +{ + + // Skip this tick if its not triggered from the pass through + if (bUpdateInTick && !bPassThrough) + return; + + // If the sliders progress is locked then just exit early + if (bIsLocked) + { + if (bAutoDropWhenLocked) + { + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); + } + + return; + } + + // Handle manual tracking here + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform CurrentRelativeTransform = InitialRelativeTransform * ParentTransform; + FVector CurInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(GrippingController->GetPivotLocation()); + + FVector CalculatedLocation = InitialGripLoc + (CurInteractorLocation - InitialInteractorLocation); + + float SplineProgress = CurrentSliderProgress; + if (SplineComponentToFollow != nullptr) + { + FVector WorldCalculatedLocation = CurrentRelativeTransform.TransformPosition(CalculatedLocation); + float ClosestKey = SplineComponentToFollow->FindInputKeyClosestToWorldLocation(WorldCalculatedLocation); + + if (bSliderUsesSnapPoints) + { + float SplineLength = SplineComponentToFollow->GetSplineLength(); + SplineProgress = GetCurrentSliderProgress(WorldCalculatedLocation, true, ClosestKey); + + SplineProgress = UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(SplineProgress, SnapIncrement, SnapThreshold); + + const int32 NumPoints = SplineComponentToFollow->SplineCurves.Position.Points.Num(); + + if (SplineComponentToFollow->SplineCurves.Position.Points.Num() > 1) + { + ClosestKey = SplineComponentToFollow->SplineCurves.ReparamTable.Eval(SplineProgress * SplineLength, 0.0f); + } + + WorldCalculatedLocation = SplineComponentToFollow->GetLocationAtSplineInputKey(ClosestKey, ESplineCoordinateSpace::World); + } + + bool bLerpToNewKey = true; + bool bChangedLocation = false; + + if (bEnforceSplineLinearity && LastInputKey >= 0.0f && + FMath::Abs((FMath::TruncToFloat(ClosestKey) - FMath::TruncToFloat(LastInputKey))) > 1.0f && + (!bSliderUsesSnapPoints || (SplineProgress - CurrentSliderProgress > SnapIncrement)) + ) + { + bLerpToNewKey = false; + } + else + { + LerpedKey = ClosestKey; + } + + if (bFollowSplineRotationAndScale) + { + FTransform trans; + if (SplineLerpType != EVRInteractibleSliderLerpType::Lerp_None && LastInputKey >= 0.0f && !FMath::IsNearlyEqual(LerpedKey, LastInputKey)) + { + GetLerpedKey(LerpedKey, DeltaTime); + trans = SplineComponentToFollow->GetTransformAtSplineInputKey(LerpedKey, ESplineCoordinateSpace::World, true); + bChangedLocation = true; + } + else if (bLerpToNewKey) + { + trans = SplineComponentToFollow->FindTransformClosestToWorldLocation(WorldCalculatedLocation, ESplineCoordinateSpace::World, true); + bChangedLocation = true; + } + + if (bChangedLocation) + { + trans.MultiplyScale3D(InitialRelativeTransform.GetScale3D()); + trans = trans * ParentTransform.Inverse(); + this->SetRelativeTransform(trans); + } + } + else + { + FVector WorldLocation; + if (SplineLerpType != EVRInteractibleSliderLerpType::Lerp_None && LastInputKey >= 0.0f && !FMath::IsNearlyEqual(LerpedKey, LastInputKey)) + { + GetLerpedKey(LerpedKey, DeltaTime); + WorldLocation = SplineComponentToFollow->GetLocationAtSplineInputKey(LerpedKey, ESplineCoordinateSpace::World); + bChangedLocation = true; + } + else if (bLerpToNewKey) + { + WorldLocation = SplineComponentToFollow->FindLocationClosestToWorldLocation(WorldCalculatedLocation, ESplineCoordinateSpace::World); + bChangedLocation = true; + } + + if (bChangedLocation) + this->SetRelativeLocation(ParentTransform.InverseTransformPosition(WorldLocation)); + } + + CurrentSliderProgress = GetCurrentSliderProgress(WorldCalculatedLocation, true, bLerpToNewKey ? LerpedKey : ClosestKey); + if (bLerpToNewKey) + { + LastInputKey = LerpedKey; + } + } + else + { + FVector ClampedLocation = ClampSlideVector(CalculatedLocation); + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(ClampedLocation)); + CurrentSliderProgress = GetCurrentSliderProgress(bSlideDistanceIsInParentSpace ? ClampedLocation * InitialRelativeTransform.GetScale3D() : ClampedLocation); + } + + if (SliderBehaviorWhenReleased == EVRInteractibleSliderDropBehavior::RetainMomentum) + { + if (SplineComponentToFollow) + { + // Rolling average across num samples + SplineMomentumAtDrop -= SplineMomentumAtDrop / FramesToAverage; + SplineMomentumAtDrop += ((CurrentSliderProgress - SplineLastSliderProgress) / DeltaTime) / FramesToAverage; + + float momentumSign = FMath::Sign(SplineMomentumAtDrop); + SplineMomentumAtDrop = momentumSign * FMath::Min(MaxSliderMomentum, FMath::Abs(SplineMomentumAtDrop)); + + SplineLastSliderProgress = CurrentSliderProgress; + } + else + { + // Rolling average across num samples + MomentumAtDrop -= MomentumAtDrop / FramesToAverage; + FVector CurProgress = InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation()); + if (bSlideDistanceIsInParentSpace) + CurProgress *= FVector(1.0f) / InitialRelativeTransform.GetScale3D(); + + MomentumAtDrop += ((/*CurrentSliderProgress*/CurProgress - LastSliderProgress) / DeltaTime) / FramesToAverage; + + //MomentumAtDrop = FMath::Min(MaxSliderMomentum, MomentumAtDrop); + + LastSliderProgress = CurProgress;//CurrentSliderProgress; + } + } + + CheckSliderProgress(); + + // Check if we should auto drop + CheckAutoDrop(GrippingController, GripInformation); +} + +bool UVRSliderComponent::CheckAutoDrop(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + // Converted to a relative value now so it should be correct + if (BreakDistance > 0.f && GrippingController->HasGripAuthority(GripInformation) && FVector::DistSquared(InitialDropLocation, this->GetComponentTransform().InverseTransformPosition(GrippingController->GetPivotLocation())) >= FMath::Square(BreakDistance)) + { + if (GrippingController->OnGripOutOfRange.IsBound()) + { + uint8 GripID = GripInformation.GripID; + GrippingController->OnGripOutOfRange.Broadcast(GripInformation, GripInformation.GripDistance); + } + else + { + GrippingController->DropObjectByInterface(this, HoldingGrip.GripID); + } + return true; + } + + return false; +} + +void UVRSliderComponent::CheckSliderProgress() +{ + // Skip first check, this will skip an event throw on rounded + if (LastSliderProgressState < 0.0f) + { + // Skip first tick, this is our resting position + if (!bSliderUsesSnapPoints) + LastSliderProgressState = FMath::RoundToFloat(CurrentSliderProgress); // Ensure it is rounded to 0 or 1 + else + LastSliderProgressState = CurrentSliderProgress; + } + else if ((LastSliderProgressState != CurrentSliderProgress) || bHitEventThreshold) + { + if ((!bSliderUsesSnapPoints && (CurrentSliderProgress == 1.0f || CurrentSliderProgress == 0.0f)) || + (bSliderUsesSnapPoints && SnapIncrement > 0.f && FMath::IsNearlyEqual(FMath::Fmod(CurrentSliderProgress, SnapIncrement), 0.0f, 0.001f)) + ) + { + // I am working with exacts here because of the clamping, it should actually work with no precision issues + // I wanted to ABS(Last-Cur) == 1.0 but it would cause an initial miss on whatever one last was inited to. + + if (!bSliderUsesSnapPoints) + LastSliderProgressState = FMath::RoundToFloat(CurrentSliderProgress); // Ensure it is rounded to 0 or 1 + else + LastSliderProgressState = CurrentSliderProgress; + + ReceiveSliderHitPoint(LastSliderProgressState); + OnSliderHitPoint.Broadcast(LastSliderProgressState); + bHitEventThreshold = false; + } + } + + if (FMath::Abs(LastSliderProgressState - CurrentSliderProgress) >= (bSliderUsesSnapPoints ? FMath::Min(EventThrowThreshold, SnapIncrement / 2.0f) : EventThrowThreshold)) + { + bHitEventThreshold = true; + } +} + +void UVRSliderComponent::OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) +{ + FTransform CurrentRelativeTransform = InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + + // This lets me use the correct original location over the network without changes + FTransform ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + FTransform RelativeToGripTransform = ReversedRelativeTransform * this->GetComponentTransform(); + + InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + InitialGripLoc = InitialRelativeTransform.InverseTransformPosition(this->GetRelativeLocation()); + InitialDropLocation = ReversedRelativeTransform.GetTranslation(); + LastInputKey = -1.0f; + LerpedKey = 0.0f; + bHitEventThreshold = false; + //LastSliderProgressState = -1.0f; + LastSliderProgress = InitialGripLoc;//CurrentSliderProgress; + SplineLastSliderProgress = CurrentSliderProgress; + + bIsLerping = false; + MomentumAtDrop = FVector::ZeroVector;//0.0f; + SplineMomentumAtDrop = 0.0f; + + if (GripInformation.GripMovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = false; + } + + if (bUpdateInTick) + SetComponentTickEnabled(true); + + //OnGripped.Broadcast(GrippingController, GripInformation); + +} + +void UVRSliderComponent::OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) +{ + //this->SetComponentTickEnabled(false); + // #TODO: Handle letting go and how lerping works, specifically with the snap points it may be an issue + if (SliderBehaviorWhenReleased != EVRInteractibleSliderDropBehavior::Stay) + { + bIsLerping = true; + this->SetComponentTickEnabled(true); + + FVector Len = (MinSlideDistance.GetAbs() + MaxSlideDistance.GetAbs()); + if(bSlideDistanceIsInParentSpace) + Len *= (FVector(1.0f) / InitialRelativeTransform.GetScale3D()); + + float TotalDistance = Len.Size(); + + if (!SplineComponentToFollow) + { + if (MaxSliderMomentum * TotalDistance < MomentumAtDrop.Size()) + { + MomentumAtDrop = MomentumAtDrop.GetSafeNormal() * (TotalDistance * MaxSliderMomentum); + } + } + + if(MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + bReplicateMovement = false; + } + else + { + this->SetComponentTickEnabled(false); + bReplicateMovement = bOriginalReplicatesMovement; + } + + //OnDropped.Broadcast(ReleasingController, GripInformation, bWasSocketed); +} + +void UVRSliderComponent::SetIsLocked(bool bNewLockedState) +{ + bIsLocked = bNewLockedState; +} + +void UVRSliderComponent::SetGripPriority(int NewGripPriority) +{ + GripPriority = NewGripPriority; +} + +void UVRSliderComponent::OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) {} +void UVRSliderComponent::OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) {} +void UVRSliderComponent::OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRSliderComponent::OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) {} +void UVRSliderComponent::OnUsed_Implementation() {} +void UVRSliderComponent::OnEndUsed_Implementation() {} +void UVRSliderComponent::OnSecondaryUsed_Implementation() {} +void UVRSliderComponent::OnEndSecondaryUsed_Implementation() {} +void UVRSliderComponent::OnInput_Implementation(FKey Key, EInputEvent KeyEvent) {} +bool UVRSliderComponent::RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) { return false; } + +bool UVRSliderComponent::DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator) +{ + return bDenyGripping; +} + +EGripInterfaceTeleportBehavior UVRSliderComponent::TeleportBehavior_Implementation() +{ + return EGripInterfaceTeleportBehavior::DropOnTeleport; +} + +bool UVRSliderComponent::SimulateOnDrop_Implementation() +{ + return false; +} + +/*EGripCollisionType UVRSliderComponent::SlotGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +} + +EGripCollisionType UVRSliderComponent::FreeGripType_Implementation() +{ + return EGripCollisionType::CustomGrip; +}*/ + +EGripCollisionType UVRSliderComponent::GetPrimaryGripType_Implementation(bool bIsSlot) +{ + return EGripCollisionType::CustomGrip; +} + +ESecondaryGripType UVRSliderComponent::SecondaryGripType_Implementation() +{ + return ESecondaryGripType::SG_None; +} + + +EGripMovementReplicationSettings UVRSliderComponent::GripMovementReplicationType_Implementation() +{ + return MovementReplicationSetting; +} + +EGripLateUpdateSettings UVRSliderComponent::GripLateUpdateSetting_Implementation() +{ + return EGripLateUpdateSettings::LateUpdatesAlwaysOff; +} + +/*float UVRSliderComponent::GripStiffness_Implementation() +{ + return 0.0f; +} + +float UVRSliderComponent::GripDamping_Implementation() +{ + return 0.0f; +}*/ +void UVRSliderComponent::GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) +{ + GripStiffnessOut = 0.0f; + GripDampingOut = 0.0f; +} + +FBPAdvGripSettings UVRSliderComponent::AdvancedGripSettings_Implementation() +{ + return FBPAdvGripSettings(GripPriority); +} + +float UVRSliderComponent::GripBreakDistance_Implementation() +{ + return BreakDistance; +} + +/*void UVRSliderComponent::ClosestSecondarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +} + +void UVRSliderComponent::ClosestPrimarySlotInRange_Implementation(FVector WorldLocation, bool & bHadSlotInRange, FTransform & SlotWorldTransform, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + bHadSlotInRange = false; +}*/ + +void UVRSliderComponent::ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController, FName OverridePrefix) +{ + if (OverridePrefix.IsNone()) + bSecondarySlot ? OverridePrefix = "VRGripS" : OverridePrefix = "VRGripP"; + + UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(OverridePrefix, this, WorldLocation, bSecondarySlot ? SecondarySlotRange : PrimarySlotRange, bHadSlotInRange, SlotWorldTransform, SlotName, CallingController); +} + +bool UVRSliderComponent::AllowsMultipleGrips_Implementation() +{ + return false; +} + +void UVRSliderComponent::IsHeld_Implementation(TArray<FBPGripPair> & CurHoldingControllers, bool & bCurIsHeld) +{ + CurHoldingControllers.Empty(); + if (HoldingGrip.IsValid()) + { + CurHoldingControllers.Add(HoldingGrip); + bCurIsHeld = bIsHeld; + } + else + { + bCurIsHeld = false; + } +} + +void UVRSliderComponent::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + if (bGripped) + { + OnGripped.Broadcast(Controller, GripInformation); + } + else + { + OnDropped.Broadcast(Controller, GripInformation, bWasSocketed); + } +} + +void UVRSliderComponent::SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) +{ + if (bNewIsHeld) + { + HoldingGrip = FBPGripPair(NewHoldingController, GripID); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + if (!bIsHeld && !bIsLerping) + bOriginalReplicatesMovement = bReplicateMovement; + bReplicateMovement = false; + } + } + else + { + HoldingGrip.Clear(); + if (MovementReplicationSetting != EGripMovementReplicationSettings::ForceServerSideMovement) + { + bReplicateMovement = bOriginalReplicatesMovement; + } + } + + bIsHeld = bNewIsHeld; +} + +/*FBPInteractionSettings UVRSliderComponent::GetInteractionSettings_Implementation() +{ + return FBPInteractionSettings(); +}*/ + +bool UVRSliderComponent::GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) +{ + return false; +} + +FVector UVRSliderComponent::ClampSlideVector(FVector ValueToClamp) +{ + FVector fScaleFactor = FVector(1.0f); + + if (bSlideDistanceIsInParentSpace) + fScaleFactor = fScaleFactor / InitialRelativeTransform.GetScale3D(); + + FVector MinScale = (bUseLegacyLogic ? MinSlideDistance : MinSlideDistance.GetAbs()) * fScaleFactor; + + FVector Dist = (bUseLegacyLogic ? (MinSlideDistance + MaxSlideDistance) : (MinSlideDistance.GetAbs() + MaxSlideDistance.GetAbs())) * fScaleFactor; + FVector Progress = (ValueToClamp - (-MinScale)) / Dist; + + if (bSliderUsesSnapPoints) + { + Progress.X = FMath::Clamp(UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(Progress.X, SnapIncrement, SnapThreshold), 0.0f, 1.0f); + Progress.Y = FMath::Clamp(UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(Progress.Y, SnapIncrement, SnapThreshold), 0.0f, 1.0f); + Progress.Z = FMath::Clamp(UVRInteractibleFunctionLibrary::Interactible_GetThresholdSnappedValue(Progress.Z, SnapIncrement, SnapThreshold), 0.0f, 1.0f); + } + else + { + Progress.X = FMath::Clamp(Progress.X, 0.f, 1.f); + Progress.Y = FMath::Clamp(Progress.Y, 0.f, 1.f); + Progress.Z = FMath::Clamp(Progress.Z, 0.f, 1.f); + } + + return (Progress * Dist) - (MinScale); +} + +float UVRSliderComponent::GetDistanceAlongSplineAtSplineInputKey(float InKey) const +{ + + const int32 NumPoints = SplineComponentToFollow->SplineCurves.Position.Points.Num(); + const int32 NumSegments = SplineComponentToFollow->IsClosedLoop() ? NumPoints : NumPoints - 1; + + if ((InKey >= 0) && (InKey < NumSegments)) + { + const int32 ReparamPrevIndex = static_cast<int32>(InKey * SplineComponentToFollow->ReparamStepsPerSegment); + const int32 ReparamNextIndex = ReparamPrevIndex + 1; + + const float Alpha = (InKey * SplineComponentToFollow->ReparamStepsPerSegment) - static_cast<float>(ReparamPrevIndex); + + const float PrevDistance = SplineComponentToFollow->SplineCurves.ReparamTable.Points[ReparamPrevIndex].InVal; + const float NextDistance = SplineComponentToFollow->SplineCurves.ReparamTable.Points[ReparamNextIndex].InVal; + + // ReparamTable assumes that distance and input keys have a linear relationship in-between entries. + return FMath::Lerp(PrevDistance, NextDistance, Alpha); + } + else if (InKey >= NumSegments) + { + return SplineComponentToFollow->SplineCurves.GetSplineLength(); + } + + return 0.0f; +} + +float UVRSliderComponent::GetCurrentSliderProgress(FVector CurLocation, bool bUseKeyInstead, float CurKey) +{ + if (SplineComponentToFollow != nullptr) + { + // In this case it is a world location + float ClosestKey = CurKey; + + if (!bUseKeyInstead) + ClosestKey = SplineComponentToFollow->FindInputKeyClosestToWorldLocation(CurLocation); + + /*int32 primaryKey = FMath::TruncToInt(ClosestKey); + + float distance1 = SplineComponentToFollow->GetDistanceAlongSplineAtSplinePoint(primaryKey); + float distance2 = SplineComponentToFollow->GetDistanceAlongSplineAtSplinePoint(primaryKey + 1); + + float FinalDistance = ((distance2 - distance1) * (ClosestKey - (float)primaryKey)) + distance1; + return FMath::Clamp(FinalDistance / SplineComponentToFollow->GetSplineLength(), 0.0f, 1.0f);*/ + float SplineLength = SplineComponentToFollow->GetSplineLength(); + return GetDistanceAlongSplineAtSplineInputKey(ClosestKey) / SplineLength; + } + + // Should need the clamp normally, but if someone is manually setting locations it could go out of bounds + float Progress = 0.f; + + if (bUseLegacyLogic) + { + Progress = FMath::Clamp(FVector::Dist(-MinSlideDistance, CurLocation) / FVector::Dist(-MinSlideDistance, MaxSlideDistance), 0.0f, 1.0f); + } + else + { + Progress = FMath::Clamp(FVector::Dist(-MinSlideDistance.GetAbs(), CurLocation) / FVector::Dist(-MinSlideDistance.GetAbs(), MaxSlideDistance.GetAbs()), 0.0f, 1.0f); + } + + if (bSliderUsesSnapPoints && SnapThreshold < SnapIncrement) + { + if (FMath::Fmod(Progress, SnapIncrement) < SnapThreshold) + { + Progress = FMath::GridSnap(Progress, SnapIncrement); + } + else if(!bIncrementProgressBetweenSnapPoints) + { + Progress = CurrentSliderProgress; + } + } + + return Progress; +} + +void UVRSliderComponent::GetLerpedKey(float &ClosestKey, float DeltaTime) +{ + switch (SplineLerpType) + { + case EVRInteractibleSliderLerpType::Lerp_Interp: + { + ClosestKey = FMath::FInterpTo(LastInputKey, ClosestKey, DeltaTime, SplineLerpValue); + }break; + case EVRInteractibleSliderLerpType::Lerp_InterpConstantTo: + { + ClosestKey = FMath::FInterpConstantTo(LastInputKey, ClosestKey, DeltaTime, SplineLerpValue); + }break; + + default: break; + } +} + +void UVRSliderComponent::SetSplineComponentToFollow(USplineComponent * SplineToFollow) +{ + SplineComponentToFollow = SplineToFollow; + + if (SplineToFollow != nullptr) + ResetToParentSplineLocation(); + else + CalculateSliderProgress(); +} + +void UVRSliderComponent::ResetInitialSliderLocation() +{ + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + ResetToParentSplineLocation(); + + if (SplineComponentToFollow == nullptr) + CurrentSliderProgress = GetCurrentSliderProgress(FVector(0, 0, 0)); +} + +void UVRSliderComponent::ResetToParentSplineLocation() +{ + if (SplineComponentToFollow != nullptr) + { + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform WorldTransform = SplineComponentToFollow->FindTransformClosestToWorldLocation(this->GetComponentLocation(), ESplineCoordinateSpace::World, true); + if (bFollowSplineRotationAndScale) + { + WorldTransform.MultiplyScale3D(InitialRelativeTransform.GetScale3D()); + WorldTransform = WorldTransform * ParentTransform.Inverse(); + this->SetRelativeTransform(WorldTransform); + } + else + { + this->SetWorldLocation(WorldTransform.GetLocation()); + } + + CurrentSliderProgress = GetCurrentSliderProgress(WorldTransform.GetLocation()); + } +} + +void UVRSliderComponent::SetSliderProgress(float NewSliderProgress) +{ + NewSliderProgress = FMath::Clamp(NewSliderProgress, 0.0f, 1.0f); + + if (SplineComponentToFollow != nullptr) + { + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + float splineProgress = SplineComponentToFollow->GetSplineLength() * NewSliderProgress; + + if (bFollowSplineRotationAndScale) + { + FTransform trans = SplineComponentToFollow->GetTransformAtDistanceAlongSpline(splineProgress, ESplineCoordinateSpace::World, true); + trans.MultiplyScale3D(InitialRelativeTransform.GetScale3D()); + trans = trans * ParentTransform.Inverse(); + this->SetRelativeTransform(trans); + } + else + { + this->SetRelativeLocation(ParentTransform.InverseTransformPosition(SplineComponentToFollow->GetLocationAtDistanceAlongSpline(splineProgress, ESplineCoordinateSpace::World))); + } + } + else // Not a spline follow + { + // Doing it min+max because the clamp value subtracts the min value + FVector CalculatedLocation = bUseLegacyLogic ? FMath::Lerp(-MinSlideDistance, MaxSlideDistance, NewSliderProgress) : FMath::Lerp(-MinSlideDistance.GetAbs(), MaxSlideDistance.GetAbs(), NewSliderProgress); + + if (bSlideDistanceIsInParentSpace) + CalculatedLocation *= FVector(1.0f) / InitialRelativeTransform.GetScale3D(); + + FVector ClampedLocation = ClampSlideVector(CalculatedLocation); + + //if (bSlideDistanceIsInParentSpace) + // this->SetRelativeLocation(InitialRelativeTransform.TransformPositionNoScale(ClampedLocation)); + //else + this->SetRelativeLocation(InitialRelativeTransform.TransformPosition(ClampedLocation)); + } + + CurrentSliderProgress = NewSliderProgress; +} + +float UVRSliderComponent::CalculateSliderProgress() +{ + if (this->SplineComponentToFollow != nullptr) + { + CurrentSliderProgress = GetCurrentSliderProgress(this->GetComponentLocation()); + } + else + { + FTransform ParentTransform = UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(this); + FTransform CurrentRelativeTransform = InitialRelativeTransform * ParentTransform; + FVector CalculatedLocation = CurrentRelativeTransform.InverseTransformPosition(this->GetComponentLocation()); + + CurrentSliderProgress = GetCurrentSliderProgress(bSlideDistanceIsInParentSpace ? CalculatedLocation * InitialRelativeTransform.GetScale3D() : CalculatedLocation); + } + + return CurrentSliderProgress; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/BucketUpdateSubsystem.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/BucketUpdateSubsystem.cpp new file mode 100644 index 0000000..74137f9 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/BucketUpdateSubsystem.cpp @@ -0,0 +1,397 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/BucketUpdateSubsystem.h" + + bool UBucketUpdateSubsystem::AddObjectToBucket(int32 UpdateHTZ, UObject* InObject, FName FunctionName) + { + if (!InObject || UpdateHTZ < 1) + return false; + + return BucketContainer.AddBucketObject(UpdateHTZ, InObject, FunctionName); + } + + bool UBucketUpdateSubsystem::K2_AddObjectToBucket(int32 UpdateHTZ, UObject* InObject, FName FunctionName) + { + if (!InObject || UpdateHTZ < 1) + return false; + + return BucketContainer.AddBucketObject(UpdateHTZ, InObject, FunctionName); + } + + + bool UBucketUpdateSubsystem::K2_AddObjectEventToBucket(FDynamicBucketUpdateTickSignature Delegate, int32 UpdateHTZ) + { + if (!Delegate.IsBound()) + return false; + + return BucketContainer.AddBucketObject(UpdateHTZ, Delegate); + } + + bool UBucketUpdateSubsystem::RemoveObjectFromBucketByFunctionName(UObject* InObject, FName FunctionName) + { + if (!InObject) + return false; + + return BucketContainer.RemoveBucketObject(InObject, FunctionName); + } + + bool UBucketUpdateSubsystem::RemoveObjectFromBucketByEvent(FDynamicBucketUpdateTickSignature Delegate) + { + if (!Delegate.IsBound()) + return false; + + return BucketContainer.RemoveBucketObject(Delegate); + } + + bool UBucketUpdateSubsystem::RemoveObjectFromAllBuckets(UObject* InObject) + { + if (!InObject) + return false; + + return BucketContainer.RemoveObjectFromAllBuckets(InObject); + } + + bool UBucketUpdateSubsystem::IsObjectFunctionInBucket(UObject* InObject, FName FunctionName) + { + if (!InObject) + return false; + + return BucketContainer.IsObjectFunctionInBucket(InObject, FunctionName); + } + + bool UBucketUpdateSubsystem::IsActive() + { + return BucketContainer.bNeedsUpdate; + } + + void UBucketUpdateSubsystem::Tick(float DeltaTime) + { + BucketContainer.UpdateBuckets(DeltaTime); + } + + bool UBucketUpdateSubsystem::IsTickable() const + { + return BucketContainer.bNeedsUpdate; + } + + UWorld* UBucketUpdateSubsystem::GetTickableGameObjectWorld() const + { + return GetWorld(); + } + + bool UBucketUpdateSubsystem::IsTickableInEditor() const + { + return false; + } + + bool UBucketUpdateSubsystem::IsTickableWhenPaused() const + { + return false; + } + + ETickableTickType UBucketUpdateSubsystem::GetTickableTickType() const + { + if (IsTemplate(RF_ClassDefaultObject)) + return ETickableTickType::Never; + + return ETickableTickType::Conditional; + } + + TStatId UBucketUpdateSubsystem::GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(UVRGripScriptBase, STATGROUP_Tickables); + } + + bool FUpdateBucketDrop::ExecuteBoundCallback() + { + if (NativeCallback.IsBound()) + { + return NativeCallback.Execute(); + } + else if (DynamicCallback.IsBound()) + { + DynamicCallback.Execute(); + return true; + } + + return false; + } + + bool FUpdateBucketDrop::IsBoundToObjectFunction(UObject * Obj, FName & FuncName) + { + return (NativeCallback.IsBoundToObject(Obj) && FunctionName == FuncName); + } + + bool FUpdateBucketDrop::IsBoundToObjectDelegate(FDynamicBucketUpdateTickSignature & DynEvent) + { + return DynamicCallback == DynEvent; + } + + bool FUpdateBucketDrop::IsBoundToObject(UObject * Obj) + { + return (NativeCallback.IsBoundToObject(Obj) || DynamicCallback.IsBoundToObject(Obj)); + } + + FUpdateBucketDrop::FUpdateBucketDrop() + { + FunctionName = NAME_None; + } + + FUpdateBucketDrop::FUpdateBucketDrop(FDynamicBucketUpdateTickSignature & DynCallback) + { + DynamicCallback = DynCallback; + } + + FUpdateBucketDrop::FUpdateBucketDrop(UObject * Obj, FName FuncName) + { + if (Obj && Obj->FindFunction(FuncName)) + { + FunctionName = FuncName; + NativeCallback.BindUFunction(Obj, FunctionName); + } + else + { + FunctionName = NAME_None; + } + } + + bool FUpdateBucket::Update(float DeltaTime) + { + //#TODO: Need to consider batching / spreading out load if there are a lot of updating objects in the bucket + if (Callbacks.Num() < 1) + return false; + + // Check for if this bucket is ready to fire events + nUpdateCount += DeltaTime; + if (nUpdateCount >= nUpdateRate) + { + nUpdateCount = 0.0f; + for (int i = Callbacks.Num() - 1; i >= 0; --i) + { + if (Callbacks[i].ExecuteBoundCallback()) + { + // If this returns true then we keep it in the queue + continue; + } + + // Remove the callback, it is complete or invalid + Callbacks.RemoveAt(i); + } + } + + return Callbacks.Num() > 0; + } + + void FUpdateBucketContainer::UpdateBuckets(float DeltaTime) + { + TArray<uint32> BucketsToRemove; + for(auto& Bucket : ReplicationBuckets) + { + if (!Bucket.Value.Update(DeltaTime)) + { + // Add Bucket to list to remove at end of update + BucketsToRemove.Add(Bucket.Key); + } + } + + // Remove unused buckets so that they don't get ticked + for (const uint32 Key : BucketsToRemove) + { + ReplicationBuckets.Remove(Key); + } + + if (ReplicationBuckets.Num() < 1) + bNeedsUpdate = false; + } + + bool FUpdateBucketContainer::AddBucketObject(uint32 UpdateHTZ, UObject* InObject, FName FunctionName) + { + if (!InObject || InObject->FindFunction(FunctionName) == nullptr || UpdateHTZ < 1) + return false; + + // First verify that this object isn't already contained in a bucket, if it is then erase it so that we can replace it below + RemoveBucketObject(InObject, FunctionName); + + if (ReplicationBuckets.Contains(UpdateHTZ)) + { + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(InObject, FunctionName)); + } + else + { + FUpdateBucket & newBucket = ReplicationBuckets.Add(UpdateHTZ, FUpdateBucket(UpdateHTZ)); + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(InObject, FunctionName)); + } + + if (ReplicationBuckets.Num() > 0) + bNeedsUpdate = true; + + return true; + } + + + bool FUpdateBucketContainer::AddBucketObject(uint32 UpdateHTZ, FDynamicBucketUpdateTickSignature &Delegate) + { + if (!Delegate.IsBound() || UpdateHTZ < 1) + return false; + + // First verify that this object isn't already contained in a bucket, if it is then erase it so that we can replace it below + RemoveBucketObject(Delegate); + + if (ReplicationBuckets.Contains(UpdateHTZ)) + { + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(Delegate)); + } + else + { + FUpdateBucket & newBucket = ReplicationBuckets.Add(UpdateHTZ, FUpdateBucket(UpdateHTZ)); + ReplicationBuckets[UpdateHTZ].Callbacks.Add(FUpdateBucketDrop(Delegate)); + } + + if (ReplicationBuckets.Num() > 0) + bNeedsUpdate = true; + + return true; + } + + bool FUpdateBucketContainer::RemoveBucketObject(UObject * ObjectToRemove, FName FunctionName) + { + if (!ObjectToRemove || ObjectToRemove->FindFunction(FunctionName) == nullptr) + return false; + + // Store if we ended up removing it + bool bRemovedObject = false; + + TArray<uint32> BucketsToRemove; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectFunction(ObjectToRemove, FunctionName)) + { + Bucket.Value.Callbacks.RemoveAt(i); + bRemovedObject = true; + + // Leave the loop, this is called in add as well so we should never get duplicate entries + break; + } + } + + if (bRemovedObject) + { + break; + } + } + + return bRemovedObject; + } + + bool FUpdateBucketContainer::RemoveBucketObject(FDynamicBucketUpdateTickSignature &DynEvent) + { + if (!DynEvent.IsBound()) + return false; + + // Store if we ended up removing it + bool bRemovedObject = false; + + TArray<uint32> BucketsToRemove; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectDelegate(DynEvent)) + { + Bucket.Value.Callbacks.RemoveAt(i); + bRemovedObject = true; + + // Leave the loop, this is called in add as well so we should never get duplicate entries + break; + } + } + + if (bRemovedObject) + { + break; + } + } + + return bRemovedObject; + } + + bool FUpdateBucketContainer::RemoveObjectFromAllBuckets(UObject * ObjectToRemove) + { + if (!ObjectToRemove) + return false; + + // Store if we ended up removing it + bool bRemovedObject = false; + + TArray<uint32> BucketsToRemove; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObject(ObjectToRemove)) + { + Bucket.Value.Callbacks.RemoveAt(i); + bRemovedObject = true; + } + } + } + + return bRemovedObject; + } + + bool FUpdateBucketContainer::IsObjectInBucket(UObject * ObjectToRemove) + { + if (!ObjectToRemove) + return false; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObject(ObjectToRemove)) + { + return true; + } + } + } + + return false; + } + + bool FUpdateBucketContainer::IsObjectFunctionInBucket(UObject * ObjectToRemove, FName FunctionName) + { + if (!ObjectToRemove) + return false; + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectFunction(ObjectToRemove, FunctionName)) + { + return true; + } + } + } + + return false; + } + + bool FUpdateBucketContainer::IsObjectDelegateInBucket(FDynamicBucketUpdateTickSignature &DynEvent) + { + if (!DynEvent.IsBound()) + return false; + + for (auto& Bucket : ReplicationBuckets) + { + for (int i = Bucket.Value.Callbacks.Num() - 1; i >= 0; --i) + { + if (Bucket.Value.Callbacks[i].IsBoundToObjectDelegate(DynEvent)) + { + return true; + } + } + } + + return false; + } \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/CollisionIgnoreSubsystem.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/CollisionIgnoreSubsystem.cpp new file mode 100644 index 0000000..371bad3 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/CollisionIgnoreSubsystem.cpp @@ -0,0 +1,431 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/CollisionIgnoreSubsystem.h" +#include "Components/SkeletalMeshComponent.h" +#include "Runtime/Engine/Classes/Kismet/GameplayStatics.h" + +#if WITH_CHAOS +#include "Chaos/ParticleHandle.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "Chaos/ParticleHandle.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "PBDRigidsSolver.h" +#endif + +DEFINE_LOG_CATEGORY(VRE_CollisionIgnoreLog); + + +void UCollisionIgnoreSubsystem::CheckActiveFilters() +{ + for (TPair<FCollisionPrimPair, FCollisionIgnorePairArray>& KeyPair : CollisionTrackedPairs) + { + // First check for invalid primitives + if (!IsValid(KeyPair.Key.Prim1) || !IsValid(KeyPair.Key.Prim2)) + { + // If we don't have a map element for this pair, then add it now + if (!RemovedPairs.Contains(KeyPair.Key)) + { + RemovedPairs.Add(KeyPair.Key, KeyPair.Value); + } + + continue; // skip remaining checks as we have invalid primitives anyway + } + + // Now check for lost physics handles + // Implement later + + + /*for (int i = KeyPair.Value.PairArray.Num() - 1; i >= 0; i--) + { + + FPhysicsActorHandle hand1 = KeyPair.Value.PairArray[i].Actor1; + FPhysicsActorHandle hand2 = KeyPair.Value.PairArray[i].Actor2; + + if ( + (!KeyPair.Value.PairArray[i].Actor1 || !FPhysicsInterface::IsValid(KeyPair.Value.PairArray[i].Actor1)) || + (!KeyPair.Value.PairArray[i].Actor2 || !FPhysicsInterface::IsValid(KeyPair.Value.PairArray[i].Actor2)) + ) + { + + FName Bone1 = KeyPair.Value.PairArray[i].BoneName1; + FName Bone2 = KeyPair.Value.PairArray[i].BoneName2; + + FBodyInstance* Inst1 = KeyPair.Key.Prim1->GetBodyInstance(Bone1); + FBodyInstance* Inst2 = KeyPair.Key.Prim2->GetBodyInstance(Bone2); + + if (Inst1 && Inst2) + { + // We still have the bones available, lets go ahead and re-init for them + KeyPair.Value.PairArray.RemoveAt(i); + SetComponentCollisionIgnoreState(false, false, KeyPair.Key.Prim1, Bone1, KeyPair.Key.Prim2, Bone2, true, false); + } + else + { + // Its not still available, lets remove the pair. + KeyPair.Value.PairArray.RemoveAt(i); + } + } + } + + // If there are no pairs left + if (KeyPair.Value.PairArray.Num() < 1) + { + // Try and remove it, chaos should be cleaning up the ignore setups + if (!RemovedPairs.Contains(KeyPair.Key)) + { + RemovedPairs.Add(KeyPair.Key, KeyPair.Value); + } + }*/ + } + +/*#if WITH_CHAOS + if (FPhysScene* PhysScene2 = GetWorld()->GetPhysicsScene()) + { + Chaos::FIgnoreCollisionManager& IgnoreCollisionManager = PhysScene2->GetSolver()->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager(); + int32 ExternalTimestamp = PhysScene2->GetSolver()->GetMarshallingManager().GetExternalTimestamp_External(); + + Chaos::FIgnoreCollisionManager::FPendingMap IgnoreSet = IgnoreCollisionManager.GetPendingActivationsForGameThread(ExternalTimestamp); + Chaos::FIgnoreCollisionManager::FDeactivationSet DeactiveSet = IgnoreCollisionManager.GetPendingDeactivationsForGameThread(ExternalTimestamp); + //Chaos::FIgnoreCollisionManager::FDeactivationSet IgnoreSet = + + // Prints out the list of items currently being re-activated after one of their pairs died. + // Chaos automatically cleans up here, I don't need to do anything. + GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("Pending activate: %i - Pending deativate: %i"), IgnoreSet.Num(), DeactiveSet.Num())); + } +#endif*/ + + for (const TPair<FCollisionPrimPair, FCollisionIgnorePairArray>& KeyPair : RemovedPairs) + { + if (CollisionTrackedPairs.Contains(KeyPair.Key)) + { + CollisionTrackedPairs[KeyPair.Key].PairArray.Empty(); + CollisionTrackedPairs.Remove(KeyPair.Key); + } + } + + UpdateTimer(); +} + +void UCollisionIgnoreSubsystem::RemoveComponentCollisionIgnoreState(UPrimitiveComponent* Prim1) +{ + + if (!Prim1) + return; + + + TMap<FCollisionPrimPair, FCollisionIgnorePairArray> PairsToRemove; + + for (const TPair<FCollisionPrimPair, FCollisionIgnorePairArray>& KeyPair : CollisionTrackedPairs) + { + // First check for invalid primitives + if (KeyPair.Key.Prim1 == Prim1 || KeyPair.Key.Prim2 == Prim1) + { + // If we don't have a map element for this pair, then add it now + if (!PairsToRemove.Contains(KeyPair.Key)) + { + PairsToRemove.Add(KeyPair.Key, KeyPair.Value); + } + } + } + + + for (const TPair<FCollisionPrimPair, FCollisionIgnorePairArray>& KeyPair : PairsToRemove) + { + if (CollisionTrackedPairs.Contains(KeyPair.Key)) + { + for (const FCollisionIgnorePair& newIgnorePair : KeyPair.Value.PairArray) + { + // Clear out current ignores + SetComponentCollisionIgnoreState(false, false, KeyPair.Key.Prim1, newIgnorePair.BoneName1, KeyPair.Key.Prim2, newIgnorePair.BoneName2, false, false); + } + + /*if (CollisionTrackedPairs.Contains(KeyPair.Key)) + { + // Ensure we are empty + CollisionTrackedPairs[KeyPair.Key].PairArray.Empty(); + CollisionTrackedPairs.Remove(KeyPair.Key); + }*/ + } + } + + UpdateTimer(); +} + +bool UCollisionIgnoreSubsystem::IsComponentIgnoringCollision(UPrimitiveComponent* Prim1) +{ + + if (!Prim1) + return false; + + for (const TPair<FCollisionPrimPair, FCollisionIgnorePairArray>& KeyPair : CollisionTrackedPairs) + { + // First check for invalid primitives + if (KeyPair.Key.Prim1 == Prim1 || KeyPair.Key.Prim2 == Prim1) + { + return true; + } + } + + return false; +} + +bool UCollisionIgnoreSubsystem::AreComponentsIgnoringCollisions(UPrimitiveComponent* Prim1, UPrimitiveComponent* Prim2) +{ + + if (!Prim1 || !Prim2) + return false; + + TSet<FCollisionPrimPair> CurrentKeys; + int numKeys = CollisionTrackedPairs.GetKeys(CurrentKeys); + + FCollisionPrimPair SearchPair; + SearchPair.Prim1 = Prim1; + SearchPair.Prim2 = Prim2; + + // This checks if we exist already as well as provides an index + if (FCollisionPrimPair* ExistingPair = CurrentKeys.Find(SearchPair)) + { + // These components are ignoring collision + return true; + } + + return false; +} + +void UCollisionIgnoreSubsystem::InitiateIgnore() +{ + +} + +bool UCollisionIgnoreSubsystem::HasCollisionIgnorePairs() +{ + return CollisionTrackedPairs.Num() > 0; +} + +void UCollisionIgnoreSubsystem::SetComponentCollisionIgnoreState(bool bIterateChildren1, bool bIterateChildren2, UPrimitiveComponent* Prim1, FName OptionalBoneName1, UPrimitiveComponent* Prim2, FName OptionalBoneName2, bool bIgnoreCollision, bool bCheckFilters) +{ + if (!Prim1 || !Prim2) + { + UE_LOG(VRE_CollisionIgnoreLog, Error, TEXT("Set Objects Ignore Collision called with invalid object(s)!!")); + return; + } + + if (Prim1->GetCollisionEnabled() == ECollisionEnabled::NoCollision || Prim2->GetCollisionEnabled() == ECollisionEnabled::NoCollision) + { + UE_LOG(VRE_CollisionIgnoreLog, Error, TEXT("Set Objects Ignore Collision called with one or more objects with no collision!! %s, %s"), *Prim1->GetName(), *Prim2->GetName()); + return; + } + + if (Prim1->Mobility == EComponentMobility::Static || Prim2->Mobility == EComponentMobility::Static) + { + UE_LOG(VRE_CollisionIgnoreLog, Error, TEXT("Set Objects Ignore Collision called with at least one static mobility object (cannot ignore collision with it)!!")); + if (bIgnoreCollision) + { + return; + } + } + + USkeletalMeshComponent* SkeleMesh = nullptr; + USkeletalMeshComponent* SkeleMesh2 = nullptr; + + if (bIterateChildren1) + { + SkeleMesh = Cast<USkeletalMeshComponent>(Prim1); + } + + if (bIterateChildren2) + { + SkeleMesh2 = Cast<USkeletalMeshComponent>(Prim2); + } + + struct BodyPairStore + { + FBodyInstance* BInstance; + FName BName; + + BodyPairStore(FBodyInstance* BI, FName BoneName) + { + BInstance = BI; + BName = BoneName; + } + }; + + TArray<BodyPairStore> ApplicableBodies; + + if (SkeleMesh) + { + UPhysicsAsset* PhysAsset = SkeleMesh ? SkeleMesh->GetPhysicsAsset() : nullptr; + if (PhysAsset) + { + int32 NumBodiesFound = SkeleMesh->ForEachBodyBelow(OptionalBoneName1, true, false, [PhysAsset, &ApplicableBodies](FBodyInstance* BI) + { + const FName IterBodyName = PhysAsset->SkeletalBodySetups[BI->InstanceBodyIndex]->BoneName; + ApplicableBodies.Add(BodyPairStore(BI, IterBodyName)); + }); + } + } + else + { + FBodyInstance* Inst1 = Prim1->GetBodyInstance(OptionalBoneName1); + if (Inst1) + { + ApplicableBodies.Add(BodyPairStore(Inst1, OptionalBoneName1)); + } + } + + TArray<BodyPairStore> ApplicableBodies2; + if (SkeleMesh2) + { + UPhysicsAsset* PhysAsset = SkeleMesh2 ? SkeleMesh2->GetPhysicsAsset() : nullptr; + if (PhysAsset) + { + int32 NumBodiesFound = SkeleMesh2->ForEachBodyBelow(OptionalBoneName2, true, false, [PhysAsset, &ApplicableBodies2](FBodyInstance* BI) + { + const FName IterBodyName = PhysAsset->SkeletalBodySetups[BI->InstanceBodyIndex]->BoneName; + ApplicableBodies2.Add(BodyPairStore(BI, IterBodyName)); + }); + } + } + else + { + FBodyInstance* Inst1 = Prim2->GetBodyInstance(OptionalBoneName2); + if (Inst1) + { + ApplicableBodies2.Add(BodyPairStore(Inst1, OptionalBoneName2)); + } + } + + + FCollisionPrimPair newPrimPair; + newPrimPair.Prim1 = Prim1; + newPrimPair.Prim2 = Prim2; + + // Check our active filters and handle inconsistencies before we run the next logic + // (This prevents cases where null ptrs get added too) + if (bCheckFilters) + { + CheckActiveFilters(); + } + + // If we don't have a map element for this pair, then add it now + if (bIgnoreCollision && !CollisionTrackedPairs.Contains(newPrimPair)) + { + CollisionTrackedPairs.Add(newPrimPair, FCollisionIgnorePairArray()); + } + else if (!bIgnoreCollision && !CollisionTrackedPairs.Contains(newPrimPair)) + { + // Early out, we don't even have this pair to remove it + return; + } + + for (int i = 0; i < ApplicableBodies.Num(); ++i) + { + for (int j = 0; j < ApplicableBodies2.Num(); ++j) + { + if (ApplicableBodies[i].BInstance && ApplicableBodies2[j].BInstance) + { + if (FPhysScene* PhysScene = Prim1->GetWorld()->GetPhysicsScene()) + { + FCollisionIgnorePair newIgnorePair; + newIgnorePair.Actor1 = ApplicableBodies[i].BInstance->ActorHandle; + newIgnorePair.BoneName1 = ApplicableBodies[i].BName; + newIgnorePair.Actor2 = ApplicableBodies2[j].BInstance->ActorHandle; + newIgnorePair.BoneName2 = ApplicableBodies2[j].BName; + +#if WITH_CHAOS + Chaos::FUniqueIdx ID0 = ApplicableBodies[i].BInstance->ActorHandle->GetParticle_LowLevel()->UniqueIdx(); + Chaos::FUniqueIdx ID1 = ApplicableBodies2[j].BInstance->ActorHandle->GetParticle_LowLevel()->UniqueIdx(); + + Chaos::FIgnoreCollisionManager& IgnoreCollisionManager = PhysScene->GetSolver()->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager(); + + FPhysicsCommand::ExecuteWrite(PhysScene, [&]() + { + using namespace Chaos; + + if (bIgnoreCollision) + { + if (!IgnoreCollisionManager.IgnoresCollision(ID0, ID1)) + { + TPBDRigidParticleHandle<FReal, 3>* ParticleHandle0 = ApplicableBodies[i].BInstance->ActorHandle->GetHandle_LowLevel()->CastToRigidParticle(); + TPBDRigidParticleHandle<FReal, 3>* ParticleHandle1 = ApplicableBodies2[j].BInstance->ActorHandle->GetHandle_LowLevel()->CastToRigidParticle(); + + if (ParticleHandle0 && ParticleHandle1) + { + ParticleHandle0->AddCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + IgnoreCollisionManager.AddIgnoreCollisionsFor(ID0, ID1); + + ParticleHandle1->AddCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + IgnoreCollisionManager.AddIgnoreCollisionsFor(ID1, ID0); + + + TSet<FCollisionPrimPair> CurrentKeys; + int numKeys = CollisionTrackedPairs.GetKeys(CurrentKeys); + + // This checks if we exist already as well as provides an index + if (FCollisionPrimPair* CurrentPair = CurrentKeys.Find(newPrimPair)) + { + // Check if the current one has the same primitive ordering as the new check + if (CurrentPair->Prim1 != newPrimPair.Prim1) + { + // If not then lets flip the elements around in order to match it + newIgnorePair.FlipElements(); + } + + CollisionTrackedPairs[newPrimPair].PairArray.AddUnique(newIgnorePair); + } + + /*if (ApplicableBodies[i].BInstance->bContactModification != bIgnoreCollision) + ApplicableBodies[i].BInstance->SetContactModification(true); + + if (ApplicableBodies2[j].BInstance->bContactModification != bIgnoreCollision) + ApplicableBodies2[j].BInstance->SetContactModification(true);*/ + } + } + } + else + { + if (IgnoreCollisionManager.IgnoresCollision(ID0, ID1)) + { + TPBDRigidParticleHandle<FReal, 3>* ParticleHandle0 = ApplicableBodies[i].BInstance->ActorHandle->GetHandle_LowLevel()->CastToRigidParticle(); + TPBDRigidParticleHandle<FReal, 3>* ParticleHandle1 = ApplicableBodies2[j].BInstance->ActorHandle->GetHandle_LowLevel()->CastToRigidParticle(); + + if (ParticleHandle0 && ParticleHandle1) + { + IgnoreCollisionManager.RemoveIgnoreCollisionsFor(ID0, ID1); + IgnoreCollisionManager.RemoveIgnoreCollisionsFor(ID1, ID0); + + if (IgnoreCollisionManager.NumIgnoredCollision(ID0) < 1) + { + ParticleHandle0->RemoveCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + } + + if (IgnoreCollisionManager.NumIgnoredCollision(ID1) < 1) + { + ParticleHandle1->RemoveCollisionConstraintFlag(Chaos::ECollisionConstraintFlags::CCF_BroadPhaseIgnoreCollisions); + } + } + + CollisionTrackedPairs[newPrimPair].PairArray.Remove(newIgnorePair); + if (CollisionTrackedPairs[newPrimPair].PairArray.Num() < 1) + { + CollisionTrackedPairs.Remove(newPrimPair); + } + + // If we don't have a map element for this pair, then add it now + if (!RemovedPairs.Contains(newPrimPair)) + { + RemovedPairs.Add(newPrimPair, FCollisionIgnorePairArray()); + } + RemovedPairs[newPrimPair].PairArray.AddUnique(newIgnorePair); + } + } + }); +#endif + } + } + } + } + + // Update our timer state + UpdateTimer(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/OptionalRepSkeletalMeshActor.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/OptionalRepSkeletalMeshActor.cpp new file mode 100644 index 0000000..a8a94e8 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/OptionalRepSkeletalMeshActor.cpp @@ -0,0 +1,601 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/OptionalRepSkeletalMeshActor.h" +#include "TimerManager.h" +#include "Net/UnrealNetwork.h" +#include "PhysicsReplication.h" +#if WITH_PUSH_MODEL +#include "Net/Core/PushModel/PushModel.h" +#endif + +UNoRepSphereComponent::UNoRepSphereComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + this->SetIsReplicatedByDefault(true); + this->PrimaryComponentTick.bCanEverTick = false; + SphereRadius = 4.0f; + SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); + SetCollisionResponseToAllChannels(ECR_Ignore); + //SetAllMassScale(0.0f); Engine hates calling this in constructor + + + BodyInstance.bOverrideMass = true; + BodyInstance.SetMassOverride(0.f); +} + +void UNoRepSphereComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UNoRepSphereComponent, bReplicateMovement); + + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, AttachParent, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, AttachSocketName, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, AttachChildren, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, COND_InitialOnly); + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, COND_InitialOnly); + //DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); +} + +void UNoRepSphereComponent::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + +} + +void FSkeletalMeshComponentEndPhysicsTickFunctionVR::ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) +{ + //QUICK_SCOPE_CYCLE_COUNTER(FSkeletalMeshComponentEndPhysicsTickFunction_ExecuteTick); + //CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Physics); + + FActorComponentTickFunction::ExecuteTickHelper(TargetVR, /*bTickInEditor=*/ false, DeltaTime, TickType, [this](float DilatedTime) + { + TargetVR->EndPhysicsTickComponentVR(*this); + }); +} + +FString FSkeletalMeshComponentEndPhysicsTickFunctionVR::DiagnosticMessage() +{ + if (TargetVR) + { + return TargetVR->GetFullName() + TEXT("[EndPhysicsTickVR]"); + } + return TEXT("<NULL>[EndPhysicsTick]"); +} + +FName FSkeletalMeshComponentEndPhysicsTickFunctionVR::DiagnosticContext(bool bDetailed) +{ + return FName(TEXT("SkeletalMeshComponentEndPhysicsTickVR")); +} + + +UInversePhysicsSkeletalMeshComponent::UInversePhysicsSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicateMovement = true; + this->EndPhysicsTickFunction.bCanEverTick = false; + bReplicatePhysicsToAutonomousProxy = false; + + EndPhysicsTickFunctionVR.TickGroup = TG_EndPhysics; + EndPhysicsTickFunctionVR.bCanEverTick = true; + EndPhysicsTickFunctionVR.bStartWithTickEnabled = true; +} + +void UInversePhysicsSkeletalMeshComponent::PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't replicate if set to not do it + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeLocation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeRotation, bReplicateMovement); + DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D, bReplicateMovement); +} + +void UInversePhysicsSkeletalMeshComponent::EndPhysicsTickComponentVR(FSkeletalMeshComponentEndPhysicsTickFunctionVR& ThisTickFunction) +{ + //IMPORTANT! + // + // The decision on whether to use EndPhysicsTickComponent or not is made by ShouldRunEndPhysicsTick() + // Any changes that are made to EndPhysicsTickComponent that affect whether it should be run or not + // have to be reflected in ShouldRunEndPhysicsTick() as well + + // if physics is disabled on dedicated server, no reason to be here. + if (!bEnablePhysicsOnDedicatedServer && IsRunningDedicatedServer()) + { + FinalizeBoneTransform(); + return; + } + + if (IsRegistered() && IsSimulatingPhysics() && RigidBodyIsAwake()) + { + if (bNotifySyncComponentToRBPhysics) + { + OnSyncComponentToRBPhysics(); + } + + SyncComponentToRBPhysics(); + } + + // this used to not run if not rendered, but that causes issues such as bounds not updated + // causing it to not rendered, at the end, I think we should blend body positions + // for example if you're only simulating, this has to happen all the time + // whether looking at it or not, otherwise + // @todo better solution is to check if it has moved by changing SyncComponentToRBPhysics to return true if anything modified + // and run this if that is true or rendered + // that will at least reduce the chance of mismatch + // generally if you move your actor position, this has to happen to approximately match their bounds + if (ShouldBlendPhysicsBones()) + { + if (IsRegistered()) + { + BlendInPhysicsInternalVR(ThisTickFunction); + } + } +} + +void UInversePhysicsSkeletalMeshComponent::BlendInPhysicsInternalVR(FTickFunction& ThisTickFunction) +{ + check(IsInGameThread()); + + // Can't do anything without a SkeletalMesh + if (!SkeletalMesh) + { + return; + } + + // We now have all the animations blended together and final relative transforms for each bone. + // If we don't have or want any physics, we do nothing. + if (Bodies.Num() > 0 && CollisionEnabledHasPhysics(GetCollisionEnabled())) + { + //HandleExistingParallelEvaluationTask(/*bBlockOnTask = */ true, /*bPerformPostAnimEvaluation =*/ true); + // start parallel work + //check(!IsValidRef(ParallelAnimationEvaluationTask)); + + const bool bParallelBlend = false;// !!CVarUseParallelBlendPhysics.GetValueOnGameThread() && FApp::ShouldUseThreadingForPerformance(); + if (bParallelBlend) + { + /*SwapEvaluationContextBuffers(); + + ParallelAnimationEvaluationTask = TGraphTask<FParallelBlendPhysicsTask>::CreateTask().ConstructAndDispatchWhenReady(this); + + // set up a task to run on the game thread to accept the results + FGraphEventArray Prerequistes; + Prerequistes.Add(ParallelAnimationEvaluationTask); + + check(!IsValidRef(ParallelBlendPhysicsCompletionTask)); + ParallelBlendPhysicsCompletionTask = TGraphTask<FParallelBlendPhysicsCompletionTask>::CreateTask(&Prerequistes).ConstructAndDispatchWhenReady(this); + + ThisTickFunction.GetCompletionHandle()->DontCompleteUntil(ParallelBlendPhysicsCompletionTask);*/ + } + else + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + PerformBlendPhysicsBonesVR(RequiredBones, GetEditableComponentSpaceTransforms(), BoneSpaceTransforms); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + FinalizeAnimationUpdateVR(); + } + } +} + +void UInversePhysicsSkeletalMeshComponent::FinalizeAnimationUpdateVR() +{ + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate); + + // Flip bone buffer and send 'post anim' notification + FinalizeBoneTransform(); + + if (!bSimulationUpdatesChildTransforms || !IsSimulatingPhysics()) //If we simulate physics the call to MoveComponent already updates the children transforms. If we are confident that animation will not be needed this can be skipped. TODO: this should be handled at the scene component layer + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateChildTransforms); + + // Update Child Transform - The above function changes bone transform, so will need to update child transform + // But only children attached to us via a socket. + UpdateChildTransforms(EUpdateTransformFlags::OnlyUpdateIfUsingSocket); + } + + if (bUpdateOverlapsOnAnimationFinalize) + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateOverlaps); + + // animation often change overlap. + UpdateOverlaps(); + } + + // update bounds + // *NOTE* This is a private var, I have to remove it for this temp fix + /*if (bSkipBoundsUpdateWhenInterpolating) + { + if (AnimEvaluationContext.bDoEvaluation) + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateBounds); + // Cached local bounds are now out of date + InvalidateCachedBounds(); + + UpdateBounds(); + } + } + else*/ + { + //SCOPE_CYCLE_COUNTER(STAT_FinalizeAnimationUpdate_UpdateBounds); + // Cached local bounds are now out of date + InvalidateCachedBounds(); + + UpdateBounds(); + } + + // Need to send new bounds to + MarkRenderTransformDirty(); + + // New bone positions need to be sent to render thread + MarkRenderDynamicDataDirty(); + + // If we have any Slave Components, they need to be refreshed as well. + RefreshSlaveComponents(); +} + +struct FAssetWorldBoneTM +{ + FTransform TM; // Should never contain scaling. + bool bUpToDate; // If this equals PhysAssetUpdateNum, then the matrix is up to date. +}; + +typedef TArray<FAssetWorldBoneTM, TMemStackAllocator<alignof(FAssetWorldBoneTM)>> TAssetWorldBoneTMArray; + +void UpdateWorldBoneTMVR(TAssetWorldBoneTMArray& WorldBoneTMs, const TArray<FTransform>& InBoneSpaceTransforms, int32 BoneIndex, USkeletalMeshComponent* SkelComp, const FTransform& LocalToWorldTM, const FVector& Scale3D) +{ + // If its already up to date - do nothing + if (WorldBoneTMs[BoneIndex].bUpToDate) + { + return; + } + + FTransform ParentTM, RelTM; + if (BoneIndex == 0) + { + // If this is the root bone, we use the mesh component LocalToWorld as the parent transform. + ParentTM = LocalToWorldTM; + } + else + { + // If not root, use our cached world-space bone transforms. + int32 ParentIndex = SkelComp->SkeletalMesh->GetRefSkeleton().GetParentIndex(BoneIndex); + UpdateWorldBoneTMVR(WorldBoneTMs, InBoneSpaceTransforms, ParentIndex, SkelComp, LocalToWorldTM, Scale3D); + ParentTM = WorldBoneTMs[ParentIndex].TM; + } + + if (InBoneSpaceTransforms.IsValidIndex(BoneIndex)) + { + RelTM = InBoneSpaceTransforms[BoneIndex]; + RelTM.ScaleTranslation(Scale3D); + + WorldBoneTMs[BoneIndex].TM = RelTM * ParentTM; + WorldBoneTMs[BoneIndex].bUpToDate = true; + } +} + +void UInversePhysicsSkeletalMeshComponent::PerformBlendPhysicsBonesVR(const TArray<FBoneIndexType>& InRequiredBones, TArray<FTransform>& InOutComponentSpaceTransforms, TArray<FTransform>& InOutBoneSpaceTransforms) +{ + //SCOPE_CYCLE_COUNTER(STAT_BlendInPhysics); + // Get drawscale from Owner (if there is one) + FVector TotalScale3D = GetComponentTransform().GetScale3D(); + FVector RecipScale3D = TotalScale3D.Reciprocal(); + + UPhysicsAsset* const PhysicsAsset = GetPhysicsAsset(); + check(PhysicsAsset); + + if (InOutComponentSpaceTransforms.Num() == 0) + { + return; + } + + // Get the scene, and do nothing if we can't get one. + FPhysScene* PhysScene = nullptr; + if (GetWorld() != nullptr) + { + PhysScene = GetWorld()->GetPhysicsScene(); + } + + if (PhysScene == nullptr) + { + return; + } + + FMemMark Mark(FMemStack::Get()); + // Make sure scratch space is big enough. + TAssetWorldBoneTMArray WorldBoneTMs; + WorldBoneTMs.AddZeroed(InOutComponentSpaceTransforms.Num()); + + FTransform LocalToWorldTM = GetComponentTransform(); + + // This fixes the simulated inversed scaled skeletal mesh bug + LocalToWorldTM.SetScale3D(LocalToWorldTM.GetScale3D().GetSignVector()); + LocalToWorldTM.NormalizeRotation(); + //LocalToWorldTM.RemoveScaling(); + struct FBodyTMPair + { + FBodyInstance* BI; + FTransform TM; + }; + + FPhysicsCommand::ExecuteRead(this, [&]() + { + bool bSetParentScale = false; + const bool bSimulatedRootBody = Bodies.IsValidIndex(RootBodyData.BodyIndex) && Bodies[RootBodyData.BodyIndex]->IsInstanceSimulatingPhysics(); + const FTransform NewComponentToWorld = bSimulatedRootBody ? GetComponentTransformFromBodyInstance(Bodies[RootBodyData.BodyIndex]) : FTransform::Identity; + + // For each bone - see if we need to provide some data for it. + for (int32 i = 0; i < InRequiredBones.Num(); i++) + { + int32 BoneIndex = InRequiredBones[i]; + + // See if this is a physics bone.. + int32 BodyIndex = PhysicsAsset->FindBodyIndex(SkeletalMesh->GetRefSkeleton().GetBoneName(BoneIndex)); + // need to update back to physics so that physics knows where it was after blending + FBodyInstance* PhysicsAssetBodyInstance = nullptr; + + // If so - get its world space matrix and its parents world space matrix and calc relative atom. + if (BodyIndex != INDEX_NONE) + { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + // tracking down TTP 280421. Remove this if this doesn't happen. + if (!ensure(Bodies.IsValidIndex(BodyIndex))) + { + UE_LOG(LogPhysics, Warning, TEXT("%s(Mesh %s, PhysicsAsset %s)"), + *GetName(), *GetNameSafe(SkeletalMesh), *GetNameSafe(PhysicsAsset)); + UE_LOG(LogPhysics, Warning, TEXT(" - # of BodySetup (%d), # of Bodies (%d), Invalid BodyIndex(%d)"), + PhysicsAsset->SkeletalBodySetups.Num(), Bodies.Num(), BodyIndex); + continue; + } +#endif + PhysicsAssetBodyInstance = Bodies[BodyIndex]; + + //if simulated body copy back and blend with animation + if (PhysicsAssetBodyInstance->IsInstanceSimulatingPhysics()) + { + FTransform PhysTM = PhysicsAssetBodyInstance->GetUnrealWorldTransform_AssumesLocked(); + + // Store this world-space transform in cache. + WorldBoneTMs[BoneIndex].TM = PhysTM; + WorldBoneTMs[BoneIndex].bUpToDate = true; + + float UsePhysWeight = (bBlendPhysics) ? 1.f : PhysicsAssetBodyInstance->PhysicsBlendWeight; + + // if the body instance is disabled, then we want to use the animation transform and ignore the physics one + if (PhysicsAssetBodyInstance->IsPhysicsDisabled()) + { + UsePhysWeight = 0.0f; + } + + // Find this bones parent matrix. + FTransform ParentWorldTM; + + // if we wan't 'full weight' we just find + if (UsePhysWeight > 0.f) + { + if (!(ensure(InOutBoneSpaceTransforms.Num()))) + { + continue; + } + + if (BoneIndex == 0) + { + ParentWorldTM = LocalToWorldTM; + } + else + { + // If not root, get parent TM from cache (making sure its up-to-date). + int32 ParentIndex = SkeletalMesh->GetRefSkeleton().GetParentIndex(BoneIndex); + UpdateWorldBoneTMVR(WorldBoneTMs, InOutBoneSpaceTransforms, ParentIndex, this, LocalToWorldTM, TotalScale3D); + ParentWorldTM = WorldBoneTMs[ParentIndex].TM; + } + + + // Then calc rel TM and convert to atom. + FTransform RelTM = PhysTM.GetRelativeTransform(ParentWorldTM); + RelTM.RemoveScaling(); + FQuat RelRot(RelTM.GetRotation()); + FVector RelPos = RecipScale3D * RelTM.GetLocation(); + FTransform PhysAtom = FTransform(RelRot, RelPos, InOutBoneSpaceTransforms[BoneIndex].GetScale3D()); + + // Now blend in this atom. See if we are forcing this bone to always be blended in + InOutBoneSpaceTransforms[BoneIndex].Blend(InOutBoneSpaceTransforms[BoneIndex], PhysAtom, UsePhysWeight); + + if (!bSetParentScale) + { + //We must update RecipScale3D based on the atom scale of the root + TotalScale3D *= InOutBoneSpaceTransforms[0].GetScale3D(); + RecipScale3D = TotalScale3D.Reciprocal(); + bSetParentScale = true; + } + + } + } + } + + if (!(ensure(BoneIndex < InOutComponentSpaceTransforms.Num()))) + { + continue; + } + + // Update SpaceBases entry for this bone now + if (BoneIndex == 0) + { + if (!(ensure(InOutBoneSpaceTransforms.Num()))) + { + continue; + } + InOutComponentSpaceTransforms[0] = InOutBoneSpaceTransforms[0]; + } + else + { + if (bLocalSpaceKinematics || BodyIndex == INDEX_NONE || Bodies[BodyIndex]->IsInstanceSimulatingPhysics()) + { + if (!(ensure(BoneIndex < InOutBoneSpaceTransforms.Num()))) + { + continue; + } + const int32 ParentIndex = SkeletalMesh->GetRefSkeleton().GetParentIndex(BoneIndex); + InOutComponentSpaceTransforms[BoneIndex] = InOutBoneSpaceTransforms[BoneIndex] * InOutComponentSpaceTransforms[ParentIndex]; + + /** + * Normalize rotations. + * We want to remove any loss of precision due to accumulation of error. + * i.e. A componentSpace transform is the accumulation of all of its local space parents. The further down the chain, the greater the error. + * SpaceBases are used by external systems, we feed this to Physics, send this to gameplay through bone and socket queries, etc. + * So this is a good place to make sure all transforms are normalized. + */ + InOutComponentSpaceTransforms[BoneIndex].NormalizeRotation(); + } + else if (bSimulatedRootBody) + { + InOutComponentSpaceTransforms[BoneIndex] = Bodies[BodyIndex]->GetUnrealWorldTransform_AssumesLocked().GetRelativeTransform(NewComponentToWorld); + } + } + } + }); //end scope for read lock + +} + +void UInversePhysicsSkeletalMeshComponent::RegisterEndPhysicsTick(bool bRegister) +{ + // For testing if the engine fix is live yet or not + //return Super::RegisterEndPhysicsTick(bRegister); + + if (bRegister != EndPhysicsTickFunctionVR.IsTickFunctionRegistered()) + { + if (bRegister) + { + if (SetupActorComponentTickFunction(&EndPhysicsTickFunctionVR)) + { + EndPhysicsTickFunctionVR.TargetVR = this; + // Make sure our EndPhysicsTick gets called after physics simulation is finished + UWorld* World = GetWorld(); + if (World != nullptr) + { + EndPhysicsTickFunctionVR.AddPrerequisite(World, World->EndPhysicsTickFunction); + } + } + } + else + { + EndPhysicsTickFunctionVR.UnRegisterTickFunction(); + } + } +} + +void UInversePhysicsSkeletalMeshComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + //CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Animation); + + bool bShouldRunPhysTick = (bEnablePhysicsOnDedicatedServer || !IsNetMode(NM_DedicatedServer)) && // Early out if we are on a dedicated server and not running physics. + ((IsSimulatingPhysics() && RigidBodyIsAwake()) || ShouldBlendPhysicsBones()); + + RegisterEndPhysicsTick(PrimaryComponentTick.IsTickFunctionRegistered() && bShouldRunPhysTick); + //UpdateEndPhysicsTickRegisteredState(); + RegisterClothTick(PrimaryComponentTick.IsTickFunctionRegistered() && ShouldRunClothTick()); + //UpdateClothTickRegisteredState(); + + + // If we are suspended, we will not simulate clothing, but as clothing is simulated in local space + // relative to a root bone we need to extract simulation positions as this bone could be animated. + /*if (bClothingSimulationSuspended && this->GetClothingSimulation() && this->GetClothingSimulation()->ShouldSimulate()) + { + //CSV_SCOPED_TIMING_STAT(Animation, Cloth); + + this->GetClothingSimulation()->GetSimulationData(CurrentSimulationData, this, Cast<USkeletalMeshComponent>(MasterPoseComponent.Get())); + }*/ + + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + PendingRadialForces.Reset(); + + // Update bOldForceRefPose + bOldForceRefPose = bForceRefpose; + + + static const auto CVarAnimationDelaysEndGroup = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("tick.AnimationDelaysEndGroup")); + /** Update the end group and tick priority */ + const bool bDoLateEnd = CVarAnimationDelaysEndGroup->GetValueOnGameThread() > 0; + const bool bRequiresPhysics = EndPhysicsTickFunctionVR.IsTickFunctionRegistered(); + const ETickingGroup EndTickGroup = bDoLateEnd && !bRequiresPhysics ? TG_PostPhysics : TG_PrePhysics; + if (ThisTickFunction) + { + ThisTickFunction->EndTickGroup = TG_PostPhysics;// EndTickGroup; + + // Note that if animation is so long that we are blocked in EndPhysics we may want to reduce the priority. However, there is a risk that this function will not go wide early enough. + // This requires profiling and is very game dependent so cvar for now makes sense + + static const auto CVarHiPriSkinnedMeshesTicks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("tick.HiPriSkinnedMeshes")); + bool bDoHiPri = CVarHiPriSkinnedMeshesTicks->GetValueOnGameThread() > 0; + if (ThisTickFunction->bHighPriority != bDoHiPri) + { + ThisTickFunction->SetPriorityIncludingPrerequisites(bDoHiPri); + } + } + + // If we are waiting for ParallelEval to complete or if we require Physics, + // then FinalizeBoneTransform will be called and Anim events will be dispatched there. + // We prefer doing it there so these events are triggered once we have a new updated pose. + // Note that it's possible that FinalizeBoneTransform has already been called here if not using ParallelUpdate. + // or it's possible that it hasn't been called at all if we're skipping Evaluate due to not being visible. + // ConditionallyDispatchQueuedAnimEvents will catch that and only Dispatch events if not already done. + if (!IsRunningParallelEvaluation() && !bRequiresPhysics) + { + ///////////////////////////////////////////////////////////////////////////// + // Notify / Event Handling! + // This can do anything to our component (including destroy it) + // Any code added after this point needs to take that into account + ///////////////////////////////////////////////////////////////////////////// + + ConditionallyDispatchQueuedAnimEvents(); + } +} + +void UInversePhysicsSkeletalMeshComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(UInversePhysicsSkeletalMeshComponent, bReplicateMovement); +} + +AOptionalRepGrippableSkeletalMeshActor::AOptionalRepGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer.SetDefaultSubobjectClass<UInversePhysicsSkeletalMeshComponent>(TEXT("SkeletalMeshComponent0"))) +{ + bIgnoreAttachmentReplication = false; + bIgnorePhysicsReplication = false; +} + +void AOptionalRepGrippableSkeletalMeshActor::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(AOptionalRepGrippableSkeletalMeshActor, bIgnoreAttachmentReplication); + DOREPLIFETIME(AOptionalRepGrippableSkeletalMeshActor, bIgnorePhysicsReplication); + + if (bIgnoreAttachmentReplication) + { + RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(AActor, AttachmentReplication, COND_InitialOnly); + } + //DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, AttachmentReplication); +} + +void AOptionalRepGrippableSkeletalMeshActor::OnRep_ReplicateMovement() +{ + if (bIgnorePhysicsReplication) + { + return; + } + + Super::OnRep_ReplicateMovement(); +} + +void AOptionalRepGrippableSkeletalMeshActor::PostNetReceivePhysicState() +{ + if (bIgnorePhysicsReplication) + { + return; + } + + Super::PostNetReceivePhysicState(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRAIPerceptionOverrides.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRAIPerceptionOverrides.cpp new file mode 100644 index 0000000..d192f1a --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRAIPerceptionOverrides.cpp @@ -0,0 +1,984 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRAIPerceptionOverrides.h" +#include "EngineDefines.h" +#include "EngineGlobals.h" +#include "CollisionQueryParams.h" +//#include "Engine/Engine.h" +#include "AIModule/Classes/AISystem.h" +#include "AIModule/Classes/Perception/AIPerceptionComponent.h" +#include "VisualLogger/VisualLogger.h" +#include "AIModule/Classes/Perception/AISightTargetInterface.h" +#include "AIModule/Classes/Perception/AISenseConfig_Sight.h" +#include "AIModule/Classes/Perception/AIPerceptionSystem.h" + +#if WITH_GAMEPLAY_DEBUGGER +#include "GameplayDebugger/Public/GameplayDebuggerTypes.h" +#include "GameplayDebugger/Public/GameplayDebuggerCategory.h" +#endif +DEFINE_LOG_CATEGORY(LogAIPerceptionVR); + +#define DO_SIGHT_VLOGGINGVR (0 && ENABLE_VISUAL_LOG) + +#if DO_SIGHT_VLOGGINGVR +#define SIGHT_LOG_SEGMENTVR UE_VLOG_SEGMENT +#define SIGHT_LOG_LOCATIONVR UE_VLOG_LOCATION +#else +#define SIGHT_LOG_SEGMENTVR(...) +#define SIGHT_LOG_LOCATIONVR(...) +#endif // DO_SIGHT_VLOGGING + +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight"), STAT_AI_Sense_Sight, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Update Sort"), STAT_AI_Sense_Sight_UpdateSort, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Listener Update"), STAT_AI_Sense_Sight_ListenerUpdate, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Register Target"), STAT_AI_Sense_Sight_RegisterTarget, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove By Listener"), STAT_AI_Sense_Sight_RemoveByListener, STATGROUP_AI); +DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove To Target"), STAT_AI_Sense_Sight_RemoveToTarget, STATGROUP_AI); + + +static const int32 DefaultMaxTracesPerTick = 6; +static const int32 DefaultMinQueriesPerTimeSliceCheck = 40; + +enum class EForEachResult : uint8 +{ + Break, + Continue, +}; + +template <typename T, class PREDICATE_CLASS> +EForEachResult ForEach(T& Array, const PREDICATE_CLASS& Predicate) +{ + for (typename T::ElementType& Element : Array) + { + if (Predicate(Element) == EForEachResult::Break) + { + return EForEachResult::Break; + } + } + return EForEachResult::Continue; +} + +enum EReverseForEachResult : uint8 +{ + UnTouched, + Modified, +}; + +template <typename T, class PREDICATE_CLASS> +EReverseForEachResult ReverseForEach(T& Array, const PREDICATE_CLASS& Predicate) +{ + EReverseForEachResult RetVal = EReverseForEachResult::UnTouched; + for (int32 Index = Array.Num() - 1; Index >= 0; --Index) + { + if (Predicate(Array, Index) == EReverseForEachResult::Modified) + { + RetVal = EReverseForEachResult::Modified; + } + } + return RetVal; +} + +//----------------------------------------------------------------------// +// helpers +//----------------------------------------------------------------------// +FORCEINLINE_DEBUGGABLE bool CheckIsTargetInSightPie(const FPerceptionListener& Listener, const UAISense_Sight_VR::FDigestedSightProperties& DigestedProps, const FVector& TargetLocation, const float SightRadiusSq) +{ + if (FVector::DistSquared(Listener.CachedLocation, TargetLocation) <= SightRadiusSq) + { + const FVector DirectionToTarget = (TargetLocation - Listener.CachedLocation).GetUnsafeNormal(); + return FVector::DotProduct(DirectionToTarget, Listener.CachedDirection) > DigestedProps.PeripheralVisionAngleCos; + } + + return false; +} + +//----------------------------------------------------------------------// +// FAISightTargetVR +//----------------------------------------------------------------------// +const FAISightTargetVR::FTargetId FAISightTargetVR::InvalidTargetId = FAISystem::InvalidUnsignedID; + +FAISightTargetVR::FAISightTargetVR(AActor* InTarget, FGenericTeamId InTeamId) + : Target(InTarget), SightTargetInterface(NULL), TeamId(InTeamId) +{ + if (InTarget) + { + TargetId = InTarget->GetUniqueID(); + } + else + { + TargetId = InvalidTargetId; + } +} + +//----------------------------------------------------------------------// +// FDigestedSightProperties +//----------------------------------------------------------------------// +UAISense_Sight_VR::FDigestedSightProperties::FDigestedSightProperties(const UAISenseConfig_Sight_VR& SenseConfig) +{ + SightRadiusSq = FMath::Square(SenseConfig.SightRadius); + LoseSightRadiusSq = FMath::Square(SenseConfig.LoseSightRadius); + PeripheralVisionAngleCos = FMath::Cos(FMath::Clamp(FMath::DegreesToRadians(SenseConfig.PeripheralVisionAngleDegrees), 0.f, PI)); + AffiliationFlags = SenseConfig.DetectionByAffiliation.GetAsFlags(); + // keep the special value of FAISystem::InvalidRange (-1.f) if it's set. + AutoSuccessRangeSqFromLastSeenLocation = (SenseConfig.AutoSuccessRangeFromLastSeenLocation == FAISystem::InvalidRange) ? FAISystem::InvalidRange : FMath::Square(SenseConfig.AutoSuccessRangeFromLastSeenLocation); +} + +UAISense_Sight_VR::FDigestedSightProperties::FDigestedSightProperties() + : PeripheralVisionAngleCos(0.f), SightRadiusSq(-1.f), AutoSuccessRangeSqFromLastSeenLocation(FAISystem::InvalidRange), LoseSightRadiusSq(-1.f), AffiliationFlags(-1) +{} + +//----------------------------------------------------------------------// +// UAISense_Sight_VR +//----------------------------------------------------------------------// +UAISense_Sight_VR::UAISense_Sight_VR(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , MaxTracesPerTick(DefaultMaxTracesPerTick) + , MinQueriesPerTimeSliceCheck(DefaultMinQueriesPerTimeSliceCheck) + , MaxTimeSlicePerTick(0.005) // 5ms + , HighImportanceQueryDistanceThreshold(300.f) + , MaxQueryImportance(60.f) + , SightLimitQueryImportance(10.f) +{ + if (HasAnyFlags(RF_ClassDefaultObject) == false) + { + UAISenseConfig_Sight_VR* SightConfigCDO = GetMutableDefault<UAISenseConfig_Sight_VR>(); + SightConfigCDO->Implementation = UAISense_Sight_VR::StaticClass(); + + OnNewListenerDelegate.BindUObject(this, &UAISense_Sight_VR::OnNewListenerImpl); + OnListenerUpdateDelegate.BindUObject(this, &UAISense_Sight_VR::OnListenerUpdateImpl); + OnListenerRemovedDelegate.BindUObject(this, &UAISense_Sight_VR::OnListenerRemovedImpl); + } + + NotifyType = EAISenseNotifyType::OnPerceptionChange; + + bAutoRegisterAllPawnsAsSources = true; + bNeedsForgettingNotification = true; + + DefaultSightCollisionChannel = GET_AI_CONFIG_VAR(DefaultSightCollisionChannel); +} + +FORCEINLINE_DEBUGGABLE float UAISense_Sight_VR::CalcQueryImportance(const FPerceptionListener& Listener, const FVector& TargetLocation, const float SightRadiusSq) const +{ + const float DistanceSq = FVector::DistSquared(Listener.CachedLocation, TargetLocation); + return DistanceSq <= HighImportanceDistanceSquare ? MaxQueryImportance + : FMath::Clamp((SightLimitQueryImportance - MaxQueryImportance) / SightRadiusSq * DistanceSq + MaxQueryImportance, 0.f, MaxQueryImportance); +} + +void UAISense_Sight_VR::PostInitProperties() +{ + Super::PostInitProperties(); + HighImportanceDistanceSquare = FMath::Square(HighImportanceQueryDistanceThreshold); +} + +#if WITH_EDITOR +void UAISenseConfig_Sight_VR::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) +{ + static const FName NAME_AutoSuccessRangeFromLastSeenLocation = GET_MEMBER_NAME_CHECKED(UAISenseConfig_Sight_VR, AutoSuccessRangeFromLastSeenLocation); + + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.Property) + { + const FName PropName = PropertyChangedEvent.Property->GetFName(); + if (PropName == NAME_AutoSuccessRangeFromLastSeenLocation) + { + if (AutoSuccessRangeFromLastSeenLocation < 0) + { + AutoSuccessRangeFromLastSeenLocation = FAISystem::InvalidRange; + } + } + } +} +#endif // WITH_EDITOR + +bool UAISense_Sight_VR::ShouldAutomaticallySeeTarget(const FDigestedSightProperties& PropDigest, FAISightQueryVR* SightQuery, FPerceptionListener& Listener, AActor* TargetActor, float& OutStimulusStrength) const +{ + OutStimulusStrength = 1.0f; + + if ((PropDigest.AutoSuccessRangeSqFromLastSeenLocation != FAISystem::InvalidRange) && (SightQuery->LastSeenLocation != FAISystem::InvalidLocation)) + { + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(TargetActor); + const float DistanceToLastSeenLocationSq = FVector::DistSquared(VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(), SightQuery->LastSeenLocation); + return (DistanceToLastSeenLocationSq <= PropDigest.AutoSuccessRangeSqFromLastSeenLocation); + } + + return false; +} + +float UAISense_Sight_VR::Update() +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight); + + const UWorld* World = GEngine->GetWorldFromContextObject(GetPerceptionSystem()->GetOuter(), EGetWorldErrorMode::LogAndReturnNull); + + if (World == NULL) + { + return SuspendNextUpdate; + } + + // sort Sight Queries + { + auto RecalcScore = [](FAISightQueryVR& SightQuery)->EForEachResult + { + SightQuery.RecalcScore(); + return EForEachResult::Continue; + }; + + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_UpdateSort); + // Sort out of range queries + if (bSightQueriesOutOfRangeDirty) + { + ForEach(SightQueriesOutOfRange, RecalcScore); + SightQueriesOutOfRange.Sort(FAISightQueryVR::FSortPredicate()); + NextOutOfRangeIndex = 0; + bSightQueriesOutOfRangeDirty = false; + } + + // Sort in range queries + ForEach(SightQueriesInRange, RecalcScore); + SightQueriesInRange.Sort(FAISightQueryVR::FSortPredicate()); + } + + int32 TracesCount = 0; + int32 NumQueriesProcessed = 0; + double TimeSliceEnd = FPlatformTime::Seconds() + MaxTimeSlicePerTick; + bool bHitTimeSliceLimit = false; + //#define AISENSE_SIGHT_TIMESLICING_DEBUG +#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG + double TimeSpent = 0.0; + double LastTime = FPlatformTime::Seconds(); +#endif // AISENSE_SIGHT_TIMESLICING_DEBUG + static const int32 InitialInvalidItemsSize = 16; + enum class EOperationType : uint8 + { + Remove, + SwapList + }; + struct FQueryOperation + { + FQueryOperation(bool bInInRange, EOperationType InOpType, int32 InIndex) : bInRange(bInInRange), OpType(InOpType), Index(InIndex) {} + bool bInRange; + EOperationType OpType; + int32 Index; + }; + TArray<FQueryOperation> QueryOperations; + TArray<FAISightTargetVR::FTargetId> InvalidTargets; + QueryOperations.Reserve(InitialInvalidItemsSize); + InvalidTargets.Reserve(InitialInvalidItemsSize); + + AIPerception::FListenerMap& ListenersMap = *GetListeners(); + + int32 InRangeItr = 0; + int32 OutOfRangeItr = 0; + for (int32 QueryIndex = 0; QueryIndex < SightQueriesInRange.Num() + SightQueriesOutOfRange.Num(); ++QueryIndex) + { + // Calculate next in range query + int32 InRangeIndex = SightQueriesInRange.IsValidIndex(InRangeItr) ? InRangeItr : INDEX_NONE; + FAISightQueryVR* InRangeQuery = InRangeIndex != INDEX_NONE ? &SightQueriesInRange[InRangeIndex] : nullptr; + + // Calculate next out of range query + int32 OutOfRangeIndex = SightQueriesOutOfRange.IsValidIndex(OutOfRangeItr) ? (NextOutOfRangeIndex + OutOfRangeItr) % SightQueriesOutOfRange.Num() : INDEX_NONE; + FAISightQueryVR* OutOfRangeQuery = OutOfRangeIndex != INDEX_NONE ? &SightQueriesOutOfRange[OutOfRangeIndex] : nullptr; + if (OutOfRangeQuery) + { + OutOfRangeQuery->RecalcScore(); + } + + // Compare to real find next query + const bool bIsInRangeQuery = (InRangeQuery && OutOfRangeQuery) ? FAISightQueryVR::FSortPredicate()(*InRangeQuery, *OutOfRangeQuery) : !OutOfRangeQuery; + FAISightQueryVR* SightQuery = bIsInRangeQuery ? InRangeQuery : OutOfRangeQuery; + + // Time slice limit check - spread out checks to every N queries so we don't spend more time checking timer than doing work + NumQueriesProcessed++; +#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG + TimeSpent += (FPlatformTime::Seconds() - LastTime); + LastTime = FPlatformTime::Seconds(); +#endif // AISENSE_SIGHT_TIMESLICING_DEBUG + if (bHitTimeSliceLimit == false && (NumQueriesProcessed % MinQueriesPerTimeSliceCheck) == 0 && FPlatformTime::Seconds() > TimeSliceEnd) + { + bHitTimeSliceLimit = true; + // do not break here since that would bypass queue aging + } + + if (TracesCount < MaxTracesPerTick && bHitTimeSliceLimit == false) + { + bIsInRangeQuery ? ++InRangeItr : ++OutOfRangeItr; + + FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId]; + + FAISightTargetVR& Target = ObservedTargets[SightQuery->TargetId]; + AActor* TargetActor = Target.Target.Get(); + UAIPerceptionComponent* ListenerPtr = Listener.Listener.Get(); + ensure(ListenerPtr); + + // @todo figure out what should we do if not valid + if (TargetActor && ListenerPtr) + { + //AActor* nTargetActor = Target.Target.Get(); + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(TargetActor); + const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(); + + const FDigestedSightProperties& PropDigest = DigestedProperties[SightQuery->ObserverId]; + const float SightRadiusSq = SightQuery->bLastResult ? PropDigest.LoseSightRadiusSq : PropDigest.SightRadiusSq; + + float StimulusStrength = 1.f; + + // @Note that automagical "seeing" does not care about sight range nor vision cone + const bool bShouldAutomatically = ShouldAutomaticallySeeTarget(PropDigest, SightQuery, Listener, TargetActor, StimulusStrength); + if (bShouldAutomatically) + { + // Pretend like we've seen this target where we last saw them + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, StimulusStrength, SightQuery->LastSeenLocation, Listener.CachedLocation)); + SightQuery->bLastResult = true; + } + else if (CheckIsTargetInSightPie(Listener, PropDigest, TargetLocation, SightRadiusSq)) + { + SIGHT_LOG_SEGMENTVR(ListenerPtr->GetOwner(), Listener.CachedLocation, TargetLocation, FColor::Green, TEXT("%s"), *(Target.TargetId.ToString())); + + FVector OutSeenLocation(0.f); + // do line checks + if (Target.SightTargetInterface != NULL) + { + int32 NumberOfLoSChecksPerformed = 0; + // defaulting to 1 to have "full strength" by default instead of "no strength" + const bool bWasVisible = SightQuery->bLastResult; + if (Target.SightTargetInterface->CanBeSeenFrom(Listener.CachedLocation, OutSeenLocation, NumberOfLoSChecksPerformed, StimulusStrength, ListenerPtr->GetBodyActor(), &bWasVisible, &SightQuery->UserData) == true) + { + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, StimulusStrength, OutSeenLocation, Listener.CachedLocation)); + SightQuery->bLastResult = true; + SightQuery->LastSeenLocation = OutSeenLocation; + } + // communicate failure only if we've seen give actor before + else if (SightQuery->bLastResult == true) + { + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed)); + SightQuery->bLastResult = false; + SightQuery->LastSeenLocation = FAISystem::InvalidLocation; + } + + if (SightQuery->bLastResult == false) + { + SIGHT_LOG_LOCATIONVR(ListenerPtr->GetOwner(), TargetLocation, 25.f, FColor::Red, TEXT("")); + } + + TracesCount += NumberOfLoSChecksPerformed; + } + else + { + // we need to do tests ourselves + FHitResult HitResult; + const bool bHit = World->LineTraceSingleByChannel(HitResult, Listener.CachedLocation, TargetLocation + , DefaultSightCollisionChannel + , FCollisionQueryParams(SCENE_QUERY_STAT(AILineOfSight), true, ListenerPtr->GetBodyActor())); + + ++TracesCount; + + auto HitResultActorIsOwnedByTargetActor = [&HitResult, TargetActor]() + { + AActor* HitResultActor = HitResult.HitObjectHandle.FetchActor(); + return (HitResultActor ? HitResultActor->IsOwnedBy(TargetActor) : false); + }; + + if (bHit == false || HitResultActorIsOwnedByTargetActor()) + { + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 1.f, TargetLocation, Listener.CachedLocation)); + SightQuery->bLastResult = true; + SightQuery->LastSeenLocation = TargetLocation; + } + // communicate failure only if we've seen give actor before + else if (SightQuery->bLastResult == true) + { + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed)); + SightQuery->bLastResult = false; + SightQuery->LastSeenLocation = FAISystem::InvalidLocation; + } + + if (SightQuery->bLastResult == false) + { + SIGHT_LOG_LOCATIONVR(ListenerPtr->GetOwner(), TargetLocation, 25.f, FColor::Red, TEXT("")); + } + } + } + // communicate failure only if we've seen give actor before + else if (SightQuery->bLastResult) + { + SIGHT_LOG_SEGMENTVR(ListenerPtr->GetOwner(), Listener.CachedLocation, TargetLocation, FColor::Red, TEXT("%s"), *(Target.TargetId.ToString())); + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed)); + SightQuery->bLastResult = false; + } + + SightQuery->Importance = CalcQueryImportance(Listener, TargetLocation, SightRadiusSq); + const bool bShouldBeInRange = SightQuery->Importance > 0.0f; + if (bIsInRangeQuery != bShouldBeInRange) + { + QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::SwapList, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex)); + } + + // restart query + SightQuery->OnProcessed(); + } + else + { + // put this index to "to be removed" array + QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::Remove, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex)); + if (TargetActor == nullptr) + { + InvalidTargets.AddUnique(SightQuery->TargetId); + } + } + } + else + { + break; + } + } + + NextOutOfRangeIndex = SightQueriesOutOfRange.Num() > 0 ? (NextOutOfRangeIndex + OutOfRangeItr) % SightQueriesOutOfRange.Num() : 0; + +#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG + UE_LOG(LogAIPerceptionVR, VeryVerbose, TEXT("UAISense_Sight_VR::Update processed %d sources in %f seconds [time slice limited? %d]"), NumQueriesProcessed, TimeSpent, bHitTimeSliceLimit ? 1 : 0); +#else + UE_LOG(LogAIPerceptionVR, VeryVerbose, TEXT("UAISense_Sight_VR::Update processed %d sources [time slice limited? %d]"), NumQueriesProcessed, bHitTimeSliceLimit ? 1 : 0); +#endif // AISENSE_SIGHT_TIMESLICING_DEBUG + + if (QueryOperations.Num() > 0) + { + // Sort by InRange and by descending Index + QueryOperations.Sort([](const FQueryOperation& LHS, const FQueryOperation& RHS)->bool + { + if (LHS.bInRange != RHS.bInRange) + return LHS.bInRange; + return LHS.Index > RHS.Index; + }); + // Do all the removes first and save the out of range swaps because we will insert them at the right location to prevent sorting + TArray<FAISightQueryVR> SightQueriesOutOfRangeToInsert; + for (FQueryOperation& Operation : QueryOperations) + { + if (Operation.OpType == EOperationType::SwapList) + { + if (Operation.bInRange) + { + SightQueriesOutOfRangeToInsert.Push(SightQueriesInRange[Operation.Index]); + } + else + { + SightQueriesInRange.Add(SightQueriesOutOfRange[Operation.Index]); + } + } + + if (Operation.bInRange) + { + // In range queries are always sorted at the beginning of the update + SightQueriesInRange.RemoveAtSwap(Operation.Index, 1, /*bAllowShrinking*/false); + } + else + { + // Preserve the list ordered + SightQueriesOutOfRange.RemoveAt(Operation.Index, 1, /*bAllowShrinking*/false); + if (Operation.Index < NextOutOfRangeIndex) + { + NextOutOfRangeIndex--; + } + } + } + // Reinsert the saved out of range swaps + if (SightQueriesOutOfRangeToInsert.Num() > 0) + { + SightQueriesOutOfRange.Insert(SightQueriesOutOfRangeToInsert.GetData(), SightQueriesOutOfRangeToInsert.Num(), NextOutOfRangeIndex); + NextOutOfRangeIndex += SightQueriesOutOfRangeToInsert.Num(); + } + + if (InvalidTargets.Num() > 0) + { + // this should not be happening since UAIPerceptionSystem::OnPerceptionStimuliSourceEndPlay introduction + UE_VLOG(GetPerceptionSystem(), LogAIPerceptionVR, Error, TEXT("Invalid sight targets found during UAISense_Sight_VR::Update call")); + + for (const auto& TargetId : InvalidTargets) + { + // remove affected queries + RemoveAllQueriesToTarget(TargetId); + // remove target itself + ObservedTargets.Remove(TargetId); + } + + // remove holes + ObservedTargets.Compact(); + } + } + + //return SightQueryQueue.Num() > 0 ? 1.f/6 : FLT_MAX; + return 0.f; +} + +void UAISense_Sight_VR::RegisterEvent(const FAISightEventVR& Event) +{ + +} + +void UAISense_Sight_VR::RegisterSource(AActor& SourceActor) +{ + RegisterTarget(SourceActor); +} + +void UAISense_Sight_VR::UnregisterSource(AActor& SourceActor) +{ + const FAISightTargetVR::FTargetId AsTargetId = SourceActor.GetUniqueID(); + FAISightTargetVR AsTarget; + + if (ObservedTargets.RemoveAndCopyValue(AsTargetId, AsTarget) + && (SightQueriesInRange.Num() + SightQueriesOutOfRange.Num()) > 0) + { + AActor* TargetActor = AsTarget.Target.Get(); + + if (TargetActor) + { + // notify all interested observers that this source is no longer + // visible + AIPerception::FListenerMap& ListenersMap = *GetListeners(); + auto RemoveQuery = [this, &ListenersMap, &AsTargetId, &TargetActor](TArray<FAISightQueryVR>& SightQueries, const int32 QueryIndex)->EReverseForEachResult + { + FAISightQueryVR* SightQuery = &SightQueries[QueryIndex]; + if (SightQuery->TargetId == AsTargetId) + { + if (SightQuery->bLastResult == true) + { + FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId]; + ensure(Listener.Listener.IsValid()); + + Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, SightQuery->LastSeenLocation, Listener.CachedLocation, FAIStimulus::SensingFailed)); + } + + SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false); + return EReverseForEachResult::Modified; + } + + return EReverseForEachResult::UnTouched; + }; + + ReverseForEach(SightQueriesInRange, RemoveQuery); + if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified) + { + bSightQueriesOutOfRangeDirty = true; + } + } + } +} + +bool UAISense_Sight_VR::RegisterTarget(AActor& TargetActor, const TFunction<void(FAISightQueryVR&)>& OnAddedFunc /*= nullptr*/) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RegisterTarget); + + FAISightTargetVR* SightTarget = ObservedTargets.Find(TargetActor.GetUniqueID()); + + if (SightTarget != nullptr && SightTarget->GetTargetActor() != &TargetActor) + { + // this means given unique ID has already been recycled. + FAISightTargetVR NewSightTarget(&TargetActor); + + SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget)); + SightTarget->SightTargetInterface = Cast<IAISightTargetInterface>(&TargetActor); + } + else if (SightTarget == nullptr) + { + FAISightTargetVR NewSightTarget(&TargetActor); + + SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget)); + SightTarget->SightTargetInterface = Cast<IAISightTargetInterface>(&TargetActor); + } + + // set/update data + SightTarget->TeamId = FGenericTeamId::GetTeamIdentifier(&TargetActor); + + // generate all pairs and add them to current Sight Queries + bool bNewQueriesAdded = false; + AIPerception::FListenerMap& ListenersMap = *GetListeners(); + + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(&TargetActor); + const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor.GetActorLocation(); + + for (AIPerception::FListenerMap::TConstIterator ItListener(ListenersMap); ItListener; ++ItListener) + { + const FPerceptionListener& Listener = ItListener->Value; + const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent(); + + if (Listener.HasSense(GetSenseID()) && Listener.GetBodyActor() != &TargetActor) + { + const FDigestedSightProperties& PropDigest = DigestedProperties[Listener.GetListenerID()]; + if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, TargetActor, PropDigest.AffiliationFlags)) + { + // create a sight query + const float Importance = CalcQueryImportance(ItListener->Value, TargetLocation, PropDigest.SightRadiusSq); + const bool bInRange = Importance > 0.0f; + if (!bInRange) + { + bSightQueriesOutOfRangeDirty = true; + } + FAISightQueryVR& AddedQuery = bInRange ? SightQueriesInRange.AddDefaulted_GetRef() : SightQueriesOutOfRange.AddDefaulted_GetRef(); + AddedQuery.ObserverId = ItListener->Key; + AddedQuery.TargetId = SightTarget->TargetId; + + AddedQuery.Importance = Importance; + + if (OnAddedFunc) + { + OnAddedFunc(AddedQuery); + } + bNewQueriesAdded = true; + } + } + } + + // sort Sight Queries + if (bNewQueriesAdded) + { + RequestImmediateUpdate(); + } + + return bNewQueriesAdded; +} + +void UAISense_Sight_VR::OnNewListenerImpl(const FPerceptionListener& NewListener) +{ + UAIPerceptionComponent* NewListenerPtr = NewListener.Listener.Get(); + check(NewListenerPtr); + const UAISenseConfig_Sight_VR* SenseConfig = Cast<const UAISenseConfig_Sight_VR>(NewListenerPtr->GetSenseConfig(GetSenseID())); + + check(SenseConfig); + const FDigestedSightProperties PropertyDigest(*SenseConfig); + DigestedProperties.Add(NewListener.GetListenerID(), PropertyDigest); + + GenerateQueriesForListener(NewListener, PropertyDigest); +} + +void UAISense_Sight_VR::GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, const TFunction<void(FAISightQueryVR&)>& OnAddedFunc/*= nullptr */) +{ + bool bNewQueriesAdded = false; + const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent(); + const AActor* Avatar = Listener.GetBodyActor(); + + // create sight queries with all legal targets + for (FTargetsContainer::TConstIterator ItTarget(ObservedTargets); ItTarget; ++ItTarget) + { + const AActor* TargetActor = ItTarget->Value.GetTargetActor(); + if (TargetActor == NULL || TargetActor == Avatar) + { + continue; + } + + if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, *TargetActor, PropertyDigest.AffiliationFlags)) + { + // create a sight query + const float Importance = CalcQueryImportance(Listener, ItTarget->Value.GetLocationSimple(), PropertyDigest.SightRadiusSq); + const bool bInRange = Importance > 0.0f; + if (!bInRange) + { + bSightQueriesOutOfRangeDirty = true; + } + FAISightQueryVR& AddedQuery = bInRange ? SightQueriesInRange.AddDefaulted_GetRef() : SightQueriesOutOfRange.AddDefaulted_GetRef(); + AddedQuery.ObserverId = Listener.GetListenerID(); + AddedQuery.TargetId = ItTarget->Key; + + AddedQuery.Importance = Importance; + + + if (OnAddedFunc) + { + OnAddedFunc(AddedQuery); + } + bNewQueriesAdded = true; + } + } + + // sort Sight Queries + if (bNewQueriesAdded) + { + RequestImmediateUpdate(); + } +} + +void UAISense_Sight_VR::OnListenerUpdateImpl(const FPerceptionListener& UpdatedListener) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_ListenerUpdate); + + // first, naive implementation: + // 1. remove all queries by this listener + // 2. proceed as if it was a new listener + + // see if this listener is a Target as well + const FAISightTargetVR::FTargetId AsTargetId = UpdatedListener.GetBodyActorUniqueID(); + FAISightTargetVR* AsTarget = ObservedTargets.Find(AsTargetId); + if (AsTarget != NULL) + { + if (AsTarget->Target.IsValid()) + { + // if still a valid target then backup list of observers for which the listener was visible to restore in the newly created queries + TSet<FPerceptionListenerID> LastVisibleObservers; + RemoveAllQueriesToTarget(AsTargetId, [&LastVisibleObservers](const FAISightQueryVR& Query) + { + if (Query.bLastResult) + { + LastVisibleObservers.Add(Query.ObserverId); + } + }); + + RegisterTarget(*(AsTarget->Target.Get()), [&LastVisibleObservers](FAISightQueryVR& Query) + { + Query.bLastResult = LastVisibleObservers.Contains(Query.ObserverId); + }); + } + else + { + RemoveAllQueriesToTarget(AsTargetId); + } + } + + const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID(); + + if (UpdatedListener.HasSense(GetSenseID())) + { + // if still a valid sense then backup list of targets that were visible by the listener to restore in the newly created queries + TSet<FAISightTargetVR::FTargetId> LastVisibleTargets; + RemoveAllQueriesByListener(UpdatedListener, [&LastVisibleTargets](const FAISightQueryVR& Query) + { + if (Query.bLastResult) + { + LastVisibleTargets.Add(Query.TargetId); + } + }); + + const UAISenseConfig_Sight_VR* SenseConfig = Cast<const UAISenseConfig_Sight_VR>(UpdatedListener.Listener->GetSenseConfig(GetSenseID())); + check(SenseConfig); + FDigestedSightProperties& PropertiesDigest = DigestedProperties.FindOrAdd(ListenerID); + PropertiesDigest = FDigestedSightProperties(*SenseConfig); + + GenerateQueriesForListener(UpdatedListener, PropertiesDigest, [&LastVisibleTargets](FAISightQueryVR & Query) + { + Query.bLastResult = LastVisibleTargets.Contains(Query.TargetId); + }); + } + else + { + // remove all queries + RemoveAllQueriesByListener(UpdatedListener); + DigestedProperties.Remove(ListenerID); + } +} + +void UAISense_Sight_VR::OnListenerConfigUpdated(const FPerceptionListener& UpdatedListener) +{ + + bool bSkipListenerUpdate = false; + const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID(); + + + FDigestedSightProperties* PropertiesDigest = DigestedProperties.Find(ListenerID); + if (PropertiesDigest) + { + // The only parameter we need to rebuild all the queries for this listener is if the affiliation mask changed, otherwise there is nothing to update. + const UAISenseConfig_Sight_VR* SenseConfig = CastChecked<const UAISenseConfig_Sight_VR>(UpdatedListener.Listener->GetSenseConfig(GetSenseID())); + FDigestedSightProperties NewPropertiesDigest(*SenseConfig); + bSkipListenerUpdate = NewPropertiesDigest.AffiliationFlags == PropertiesDigest->AffiliationFlags; + *PropertiesDigest = NewPropertiesDigest; + } + + if (!bSkipListenerUpdate) + { + Super::OnListenerConfigUpdated(UpdatedListener); + } +} + +void UAISense_Sight_VR::OnListenerRemovedImpl(const FPerceptionListener& RemovedListener) +{ + + RemoveAllQueriesByListener(RemovedListener); + + DigestedProperties.FindAndRemoveChecked(RemovedListener.GetListenerID()); + + // note: there use to be code to remove all queries _to_ listener here as well + // but that was wrong - the fact that a listener gets unregistered doesn't have to + // mean it's being removed from the game altogether. +} + + +void UAISense_Sight_VR::RemoveAllQueriesByListener(const FPerceptionListener& Listener, const TFunction<void(const FAISightQueryVR&)>& OnRemoveFunc/*= nullptr */) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveByListener); + + if ((SightQueriesInRange.Num() + SightQueriesOutOfRange.Num()) == 0) + { + return; + } + + const uint32 ListenerId = Listener.GetListenerID(); + + + + auto RemoveQuery = [&ListenerId, &OnRemoveFunc](TArray<FAISightQueryVR>& SightQueries, const int32 QueryIndex)->EReverseForEachResult + { + const FAISightQueryVR& SightQuery = SightQueries[QueryIndex]; + + if (SightQuery.ObserverId == ListenerId) + { + if (OnRemoveFunc) + { + OnRemoveFunc(SightQuery); + } + SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false); + + return EReverseForEachResult::Modified; + } + + + + return EReverseForEachResult::UnTouched; + }; + ReverseForEach(SightQueriesInRange, RemoveQuery); + if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified) + { + + bSightQueriesOutOfRangeDirty = true; + } +} + +void UAISense_Sight_VR::RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId& TargetId, const TFunction<void(const FAISightQueryVR&)>& OnRemoveFunc/*= nullptr */) +{ + SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveToTarget); + + auto RemoveQuery = [&TargetId, &OnRemoveFunc](TArray<FAISightQueryVR>& SightQueries, const int32 QueryIndex)->EReverseForEachResult + { + const FAISightQueryVR& SightQuery = SightQueries[QueryIndex]; + + if (SightQuery.TargetId == TargetId) + { + if (OnRemoveFunc) + { + OnRemoveFunc(SightQuery); + } + SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false); + + return EReverseForEachResult::Modified; + } + + return EReverseForEachResult::UnTouched; + }; + ReverseForEach(SightQueriesInRange, RemoveQuery); + if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified) + { + + bSightQueriesOutOfRangeDirty = true; + } +} + + +void UAISense_Sight_VR::OnListenerForgetsActor(const FPerceptionListener& Listener, AActor& ActorToForget) +{ + const uint32 ListenerId = Listener.GetListenerID(); + const uint32 TargetId = ActorToForget.GetUniqueID(); + + auto ForgetPreviousResult = [&ListenerId, &TargetId](FAISightQueryVR& SightQuery)->EForEachResult + { + if (SightQuery.ObserverId == ListenerId && SightQuery.TargetId == TargetId) + { + // assuming one query per observer-target pair + SightQuery.ForgetPreviousResult(); + return EForEachResult::Break; + } + return EForEachResult::Continue; + }; + + if (ForEach(SightQueriesInRange, ForgetPreviousResult) == EForEachResult::Continue) + { + ForEach(SightQueriesOutOfRange, ForgetPreviousResult); + } +} + +void UAISense_Sight_VR::OnListenerForgetsAll(const FPerceptionListener& Listener) +{ + const uint32 ListenerId = Listener.GetListenerID(); + + auto ForgetPreviousResult = [&ListenerId](FAISightQueryVR& SightQuery)->EForEachResult + { + if (SightQuery.ObserverId == ListenerId) + { + SightQuery.ForgetPreviousResult(); + } + + return EForEachResult::Continue; + }; + + ForEach(SightQueriesInRange, ForgetPreviousResult); + ForEach(SightQueriesOutOfRange, ForgetPreviousResult); +} + + +//----------------------------------------------------------------------// +// +//----------------------------------------------------------------------// +UAISenseConfig_Sight_VR::UAISenseConfig_Sight_VR(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + DebugColor = FColor::Green; + AutoSuccessRangeFromLastSeenLocation = -1.0; + SightRadius = 3000.f; + LoseSightRadius = 3500.f; + PeripheralVisionAngleDegrees = 90; + DetectionByAffiliation.bDetectEnemies = true; + Implementation = UAISense_Sight_VR::StaticClass(); +} + +TSubclassOf<UAISense> UAISenseConfig_Sight_VR::GetSenseImplementation() const +{ + return *Implementation; +} + +#if WITH_GAMEPLAY_DEBUGGER +static FString DescribeColorHelper(const FColor& Color) +{ + int32 MaxColors = GColorList.GetColorsNum(); + for (int32 Idx = 0; Idx < MaxColors; Idx++) + { + if (Color == GColorList.GetFColorByIndex(Idx)) + { + return GColorList.GetColorNameByIndex(Idx); + } + } + + return FString(TEXT("color")); +} + +void UAISenseConfig_Sight_VR::DescribeSelfToGameplayDebugger(const UAIPerceptionComponent* PerceptionComponent, FGameplayDebuggerCategory* DebuggerCategory) const +{ + if (PerceptionComponent == nullptr || DebuggerCategory == nullptr) + { + return; + } + + FColor SightRangeColor = FColor::Green; + FColor LoseSightRangeColor = FColorList::NeonPink; + + // don't call Super implementation on purpose, replace color description line + DebuggerCategory->AddTextLine( + FString::Printf(TEXT("%s: {%s}%s {white}rangeIN:{%s}%s {white} rangeOUT:{%s}%s"), *GetSenseName(), + *GetDebugColor().ToString(), *DescribeColorHelper(GetDebugColor()), + *SightRangeColor.ToString(), *DescribeColorHelper(SightRangeColor), + *LoseSightRangeColor.ToString(), *DescribeColorHelper(LoseSightRangeColor)) + ); + + const AActor* BodyActor = PerceptionComponent->GetBodyActor(); + if (BodyActor != nullptr) + { + FVector BodyLocation, BodyFacing; + PerceptionComponent->GetLocationAndDirection(BodyLocation, BodyFacing); + + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeCylinder(BodyLocation, LoseSightRadius, 25.0f, LoseSightRangeColor)); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeCylinder(BodyLocation, SightRadius, 25.0f, SightRangeColor)); + + const float SightPieLength = FMath::Max(LoseSightRadius, SightRadius); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing * SightPieLength), SightRangeColor)); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing.RotateAngleAxis(PeripheralVisionAngleDegrees, FVector::UpVector) * SightPieLength), SightRangeColor)); + DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing.RotateAngleAxis(-PeripheralVisionAngleDegrees, FVector::UpVector) * SightPieLength), SightRangeColor)); + } +} +#endif // WITH_GAMEPLAY_DEBUGGER \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VREPhysicalAnimationComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VREPhysicalAnimationComponent.cpp new file mode 100644 index 0000000..1a868b9 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VREPhysicalAnimationComponent.cpp @@ -0,0 +1,273 @@ +#include "Misc/VREPhysicalAnimationComponent.h" +#include "SceneManagement.h" +#include "Components/SkeletalMeshComponent.h" +#include "PhysicsEngine/PhysicsAsset.h" +#include "PhysicsEngine/ConstraintInstance.h" +#include "ReferenceSkeleton.h" +#include "DrawDebugHelpers.h" +#include "Physics/PhysicsInterfaceCore.h" +#include "Physics/PhysicsInterfaceTypes.h" + +UVREPhysicalAnimationComponent::UVREPhysicalAnimationComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bAutoSetPhysicsSleepSensitivity = true; + SleepThresholdMultiplier = 0.0f; +} + +/*void UVREPhysicalAnimationComponent::CustomPhysics(float DeltaTime, FBodyInstance* BodyInstance) +{ + //UpdateWeldedBoneDriver(DeltaTime); +}*/ + +/*void UVREPhysicalAnimationComponent::OnWeldedMassUpdated(FBodyInstance* BodyInstance) +{ + // If our mass changed then our body was altered, lets re-init + SetupWeldedBoneDriver(true); +}*/ + +void UVREPhysicalAnimationComponent::SetWeldedBoneDriverPaused(bool bPaused) +{ + bIsPaused = bPaused; +} + +bool UVREPhysicalAnimationComponent::IsWeldedBoneDriverPaused() +{ + return bIsPaused; +} + +void UVREPhysicalAnimationComponent::RefreshWeldedBoneDriver() +{ + SetupWeldedBoneDriver_Implementation(true); +} + +void UVREPhysicalAnimationComponent::SetupWeldedBoneDriver(TArray<FName> BaseBoneNames) +{ + if (BaseBoneNames.Num()) + BaseWeldedBoneDriverNames = BaseBoneNames; + + SetupWeldedBoneDriver_Implementation(false); +} + +FTransform UVREPhysicalAnimationComponent::GetWorldSpaceRefBoneTransform(FReferenceSkeleton& RefSkel, int32 BoneIndex, int32 ParentBoneIndex) +{ + FTransform BoneTransform; + + if (BoneIndex > 0 && BoneIndex != ParentBoneIndex) + { + BoneTransform = RefSkel.GetRefBonePose()[BoneIndex]; + + FMeshBoneInfo BoneInfo = RefSkel.GetRefBoneInfo()[BoneIndex]; + if (BoneInfo.ParentIndex != 0 && BoneInfo.ParentIndex != ParentBoneIndex) + { + BoneTransform *= GetWorldSpaceRefBoneTransform(RefSkel, BoneInfo.ParentIndex, ParentBoneIndex); + } + } + + return BoneTransform; +} + +// #TODO: support off scaling +FTransform UVREPhysicalAnimationComponent::GetRefPoseBoneRelativeTransform(USkeletalMeshComponent* SkeleMesh, FName BoneName, FName ParentBoneName) +{ + FTransform BoneTransform; + + if (SkeleMesh && !BoneName.IsNone() && !ParentBoneName.IsNone()) + { + //SkelMesh->ClearRefPoseOverride(); + FReferenceSkeleton RefSkel; + RefSkel = SkeleMesh->SkeletalMesh->GetRefSkeleton(); + + BoneTransform = GetWorldSpaceRefBoneTransform(RefSkel, RefSkel.FindBoneIndex(BoneName), RefSkel.FindBoneIndex(ParentBoneName)); + } + + return BoneTransform; +} + +void UVREPhysicalAnimationComponent::SetupWeldedBoneDriver_Implementation(bool bReInit) +{ + TArray<FWeldedBoneDriverData> OriginalData; + if (bReInit) + { + OriginalData = BoneDriverMap; + } + + BoneDriverMap.Empty(); + + USkeletalMeshComponent* SkeleMesh = GetSkeletalMesh(); + + if (!SkeleMesh || !SkeleMesh->Bodies.Num()) + return; + + // Get ref pose position and walk up the tree to the welded root to get our relative base pose. + //SkeleMesh->GetRefPosePosition() + + UPhysicsAsset* PhysAsset = SkeleMesh ? SkeleMesh->GetPhysicsAsset() : nullptr; + if (PhysAsset && SkeleMesh->SkeletalMesh) + { + + for (FName BaseWeldedBoneDriverName : BaseWeldedBoneDriverNames) + { + int32 ParentBodyIdx = PhysAsset->FindBodyIndex(BaseWeldedBoneDriverName); + + if (FBodyInstance* ParentBody = (ParentBodyIdx == INDEX_NONE ? nullptr : SkeleMesh->Bodies[ParentBodyIdx])) + { + // Build map of bodies that we want to control. + FPhysicsActorHandle& ActorHandle = ParentBody->WeldParent ? ParentBody->WeldParent->GetPhysicsActorHandle() : ParentBody->GetPhysicsActorHandle(); + + if (FPhysicsInterface::IsValid(ActorHandle) /*&& FPhysicsInterface::IsRigidBody(ActorHandle)*/) + { + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](FPhysicsActorHandle& Actor) + { + //TArray<FPhysicsShapeHandle> Shapes; + PhysicsInterfaceTypes::FInlineShapeArray Shapes; + FPhysicsInterface::GetAllShapes_AssumedLocked(Actor, Shapes); + + for (FPhysicsShapeHandle& Shape : Shapes) + { + if (ParentBody->WeldParent) + { + const FBodyInstance* OriginalBI = ParentBody->WeldParent->GetOriginalBodyInstance(Shape); + + if (OriginalBI != ParentBody) + { + // Not originally our shape + continue; + } + } +#if WITH_CHAOS + FKShapeElem* ShapeElem = FChaosUserData::Get<FKShapeElem>(FPhysicsInterface::GetUserData(Shape)); +#endif + if (ShapeElem) + { + FName TargetBoneName = ShapeElem->GetName(); + int32 BoneIdx = SkeleMesh->GetBoneIndex(TargetBoneName); + + if (BoneIdx != INDEX_NONE) + { + FWeldedBoneDriverData DriverData; + DriverData.BoneName = TargetBoneName; + DriverData.ShapeHandle = Shape; + + if (bReInit && OriginalData.Num() - 1 >= BoneDriverMap.Num()) + { + DriverData.RelativeTransform = OriginalData[BoneDriverMap.Num()].RelativeTransform; + } + else + { + FTransform BoneTransform = FTransform::Identity; + if (SkeleMesh->GetBoneIndex(TargetBoneName) != INDEX_NONE) + BoneTransform = GetRefPoseBoneRelativeTransform(SkeleMesh, TargetBoneName, BaseWeldedBoneDriverName).Inverse(); + + //FTransform BoneTransform = SkeleMesh->GetSocketTransform(TargetBoneName, ERelativeTransformSpace::RTS_World); + + // Calc shape global pose + //FTransform RelativeTM = FPhysicsInterface::GetLocalTransform(Shape) * FPhysicsInterface::GetGlobalPose_AssumesLocked(ActorHandle); + + //RelativeTM = RelativeTM * BoneTransform.Inverse(); + + DriverData.RelativeTransform = FPhysicsInterface::GetLocalTransform(Shape) * BoneTransform; + } + + BoneDriverMap.Add(DriverData); + } + } + } + + if (bAutoSetPhysicsSleepSensitivity && !ParentBody->WeldParent && BoneDriverMap.Num() > 0) + { + ParentBody->SleepFamily = ESleepFamily::Custom; + ParentBody->CustomSleepThresholdMultiplier = SleepThresholdMultiplier; + float SleepEnergyThresh = FPhysicsInterface::GetSleepEnergyThreshold_AssumesLocked(Actor); + SleepEnergyThresh *= ParentBody->GetSleepThresholdMultiplier(); + FPhysicsInterface::SetSleepEnergyThreshold_AssumesLocked(Actor, SleepEnergyThresh); + } + }); + } + } + } + } +} + +void UVREPhysicalAnimationComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + // Make sure base physical animation component runs its logic + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + UpdateWeldedBoneDriver(DeltaTime); +} + +void UVREPhysicalAnimationComponent::UpdateWeldedBoneDriver(float DeltaTime) +{ + + if (!BoneDriverMap.Num()) + return; + + USkeletalMeshComponent* SkeleMesh = GetSkeletalMesh(); + + if (!SkeleMesh || !SkeleMesh->Bodies.Num())// || (!SkeleMesh->IsSimulatingPhysics(BaseWeldedBoneDriverNames) && !SkeleMesh->IsWelded())) + return; + + UPhysicsAsset* PhysAsset = SkeleMesh ? SkeleMesh->GetPhysicsAsset() : nullptr; + if(PhysAsset && SkeleMesh->SkeletalMesh) + { + for (FName BaseWeldedBoneDriverName : BaseWeldedBoneDriverNames) + { + int32 ParentBodyIdx = PhysAsset->FindBodyIndex(BaseWeldedBoneDriverName); + + if (FBodyInstance* ParentBody = (ParentBodyIdx == INDEX_NONE ? nullptr : SkeleMesh->Bodies[ParentBodyIdx])) + { + if (!ParentBody->IsInstanceSimulatingPhysics() && !ParentBody->WeldParent) + return; + + FPhysicsActorHandle& ActorHandle = ParentBody->WeldParent ? ParentBody->WeldParent->GetPhysicsActorHandle() : ParentBody->GetPhysicsActorHandle(); + + if (FPhysicsInterface::IsValid(ActorHandle) /*&& FPhysicsInterface::IsRigidBody(ActorHandle)*/) + { + + bool bModifiedBody = false; + FPhysicsCommand::ExecuteWrite(ActorHandle, [&](FPhysicsActorHandle& Actor) + { + PhysicsInterfaceTypes::FInlineShapeArray Shapes; + FPhysicsInterface::GetAllShapes_AssumedLocked(Actor, Shapes); + + FTransform GlobalPose = FPhysicsInterface::GetGlobalPose_AssumesLocked(ActorHandle).Inverse(); + + for (FPhysicsShapeHandle& Shape : Shapes) + { + + if (ParentBody->WeldParent) + { + const FBodyInstance* OriginalBI = ParentBody->WeldParent->GetOriginalBodyInstance(Shape); + + if (OriginalBI != ParentBody) + { + // Not originally our shape + continue; + } + } + + if (FWeldedBoneDriverData* WeldedData = BoneDriverMap.FindByKey(Shape)) + { + bModifiedBody = true; + + FTransform Trans = SkeleMesh->GetSocketTransform(WeldedData->BoneName, ERelativeTransformSpace::RTS_World); + + // This fixes a bug with simulating inverse scaled meshes + //Trans.SetScale3D(FVector(1.f) * Trans.GetScale3D().GetSignVector()); + FTransform GlobalTransform = WeldedData->RelativeTransform * Trans; + FTransform RelativeTM = GlobalTransform * GlobalPose; + + if (!WeldedData->LastLocal.Equals(RelativeTM)) + { + FPhysicsInterface::SetLocalTransform(Shape, RelativeTM); + WeldedData->LastLocal = RelativeTM; + } + } + } + }); + } + + } + } + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRFullScreenUserWidget.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRFullScreenUserWidget.cpp new file mode 100644 index 0000000..f144392 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRFullScreenUserWidget.cpp @@ -0,0 +1,1234 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRFullScreenUserWidget.h" + +//#include "Components/PostProcessComponent.h" +//#include "Engine/Engine.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Engine/UserInterfaceSettings.h" +#include "GameFramework/WorldSettings.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "RenderingThread.h" +#include "RHI.h" +#include "UObject/ConstructorHelpers.h" +#include "HAL/PlatformApplicationMisc.h" + +#include "Framework/Application/SlateApplication.h" +#include "Input/HittestGrid.h" +#include "Layout/Visibility.h" +#include "Slate/SceneViewport.h" +#include "Slate/WidgetRenderer.h" +#include "Widgets/Layout/SConstraintCanvas.h" +#include "Widgets/Layout/SDPIScaler.h" +#include "Widgets/SViewport.h" + +#include "IXRTrackingSystem.h" +#include "IHeadMountedDisplay.h" + +#if WITH_EDITOR +#include "LevelEditor.h" +#include "Modules/ModuleManager.h" +#include "SLevelViewport.h" +#endif + +#define LOCTEXT_NAMESPACE "VRFullScreenUserWidget" + +///////////////////////////////////////////////////// +// Internal helper +namespace +{ + const FName NAME_LevelEditorName = "LevelEditor"; + //const FName NAME_SlateUI = "SlateUI"; + //const FName NAME_TintColorAndOpacity = "TintColorAndOpacity"; + //const FName NAME_OpacityFromTexture = "OpacityFromTexture"; + + EVisibility ConvertWindowVisibilityToVisibility(EWindowVisibility visibility) + { + switch (visibility) + { + case EWindowVisibility::Visible: + return EVisibility::Visible; + case EWindowVisibility::SelfHitTestInvisible: + return EVisibility::SelfHitTestInvisible; + default: + checkNoEntry(); + return EVisibility::SelfHitTestInvisible; + } + } + + namespace VPVRFullScreenUserWidgetPrivate + { + /** + * Class made to handle world cleanup and hide/cleanup active UserWidget to avoid touching public headers + */ + class FWorldCleanupListener + { + public: + + static FWorldCleanupListener* Get() + { + static FWorldCleanupListener Instance; + return &Instance; + } + + /** Disallow Copying / Moving */ + UE_NONCOPYABLE(FWorldCleanupListener); + + ~FWorldCleanupListener() + { + FWorldDelegates::OnWorldCleanup.RemoveAll(this); + } + + void AddWidget(UVRFullScreenUserWidget* InWidget) + { + WidgetsToHide.AddUnique(InWidget); + } + + void RemoveWidget(UVRFullScreenUserWidget* InWidget) + { + WidgetsToHide.RemoveSingleSwap(InWidget, false); + } + + private: + + FWorldCleanupListener() + { + FWorldDelegates::OnWorldCleanup.AddRaw(this, &FWorldCleanupListener::OnWorldCleanup); + } + + void OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources) + { + for (auto WeakWidgetIter = WidgetsToHide.CreateIterator(); WeakWidgetIter; ++WeakWidgetIter) + { + TWeakObjectPtr<UVRFullScreenUserWidget>& WeakWidget = *WeakWidgetIter; + if (UVRFullScreenUserWidget* Widget = WeakWidget.Get()) + { + if (Widget->IsDisplayed() + && Widget->GetWidget() + && (Widget->GetWidget()->GetWorld() == InWorld)) + { + //Remove first since Hide removes object from the list + WeakWidgetIter.RemoveCurrent(); + Widget->Hide(); + } + } + else + { + WeakWidgetIter.RemoveCurrent(); + } + } + } + + private: + + TArray<TWeakObjectPtr<UVRFullScreenUserWidget>> WidgetsToHide; + }; + } +} + + +///////////////////////////////////////////////////// +// FVRWidgetPostProcessHitTester +class FVRWidgetPostProcessHitTester : public ICustomHitTestPath +{ +public: + FVRWidgetPostProcessHitTester(UWorld* InWorld, TSharedPtr<SVirtualWindow> InSlateWindow) + : World(InWorld) + , SlateWindow(InSlateWindow) + , WidgetDrawSize(FIntPoint::ZeroValue) + , LastLocalHitLocation(FVector2D::ZeroVector) + {} + + virtual TArray<FWidgetAndPointer> GetBubblePathAndVirtualCursors(const FGeometry& InGeometry, FVector2D DesktopSpaceCoordinate, bool bIgnoreEnabledStatus) const override + { + // Get the list of widget at the requested location. + TArray<FWidgetAndPointer> ArrangedWidgets; + if (TSharedPtr<SVirtualWindow> SlateWindowPin = SlateWindow.Pin()) + { + FVector2D LocalMouseCoordinate = InGeometry.AbsoluteToLocal(DesktopSpaceCoordinate); + float CursorRadius = 0.f; + ArrangedWidgets = SlateWindowPin->GetHittestGrid().GetBubblePath(LocalMouseCoordinate, CursorRadius, bIgnoreEnabledStatus); + + FVirtualPointerPosition VirtualMouseCoordinate(LocalMouseCoordinate, LastLocalHitLocation); + + LastLocalHitLocation = LocalMouseCoordinate; + + for (FWidgetAndPointer& ArrangedWidget : ArrangedWidgets) + { + ArrangedWidget.SetPointerPosition(VirtualMouseCoordinate); + } + } + + return ArrangedWidgets; + } + + virtual void ArrangeCustomHitTestChildren(FArrangedChildren& ArrangedChildren) const override + { + // Add the displayed slate to the list of widgets. + if (TSharedPtr<SVirtualWindow> SlateWindowPin = SlateWindow.Pin()) + { + FGeometry WidgetGeom; + ArrangedChildren.AddWidget(FArrangedWidget(SlateWindowPin.ToSharedRef(), WidgetGeom.MakeChild(WidgetDrawSize, FSlateLayoutTransform()))); + } + } + + virtual TOptional<FVirtualPointerPosition> TranslateMouseCoordinateForCustomHitTestChild(const SWidget& ChildWidget, const FGeometry& MyGeometry, const FVector2D ScreenSpaceMouseCoordinate, const FVector2D LastScreenSpaceMouseCoordinate) const override + { + return TOptional<FVirtualPointerPosition>(); + } + + void SetWidgetDrawSize(FIntPoint NewWidgetDrawSize) + { + WidgetDrawSize = NewWidgetDrawSize; + } + +private: + TWeakObjectPtr<UWorld> World; + TWeakPtr<SVirtualWindow> SlateWindow; + FIntPoint WidgetDrawSize; + mutable FVector2D LastLocalHitLocation; +}; + +///////////////////////////////////////////////////// +// FVRFullScreenUserWidget_Viewport +FVRFullScreenUserWidget_Viewport::FVRFullScreenUserWidget_Viewport() + : bAddedToGameViewport(false) +{ +} + +bool FVRFullScreenUserWidget_Viewport::Display(UWorld* World, UUserWidget* Widget, float InDPIScale) +{ + TSharedPtr<SConstraintCanvas> FullScreenWidgetPinned = FullScreenCanvasWidget.Pin(); + if (Widget == nullptr || World == nullptr || FullScreenWidgetPinned.IsValid()) + { + return false; + } + + UGameViewportClient* ViewportClient = nullptr; +#if WITH_EDITOR + TSharedPtr<SLevelViewport> ActiveLevelViewport; +#endif + + bool bResult = false; + if (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE) + { + ViewportClient = World->GetGameViewport(); + bResult = ViewportClient != nullptr; + } +#if WITH_EDITOR + else if (FModuleManager::Get().IsModuleLoaded(NAME_LevelEditorName)) + { + FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(NAME_LevelEditorName); + if (TargetViewport.IsValid()) + { + ActiveLevelViewport = TargetViewport.Pin(); + } + else + { + ActiveLevelViewport = LevelEditorModule.GetFirstActiveLevelViewport(); + } + bResult = ActiveLevelViewport.IsValid(); + } +#endif + + if (bResult) + { + TSharedRef<SConstraintCanvas> FullScreenCanvas = SNew(SConstraintCanvas); + FullScreenCanvasWidget = FullScreenCanvas; + + FullScreenCanvas->AddSlot() + .Offset(FMargin(0, 0, 0, 0)) + .Anchors(FAnchors(0, 0, 1, 1)) + .Alignment(FVector2D(0, 0)) + [ + SNew(SDPIScaler) + .DPIScale(InDPIScale) + [ + Widget->TakeWidget() + ] + ]; + + if (ViewportClient) + { + ViewportClient->AddViewportWidgetContent(FullScreenCanvas); + } +#if WITH_EDITOR + else + { + check(ActiveLevelViewport.IsValid()); + ActiveLevelViewport->AddOverlayWidget(FullScreenCanvas); + OverlayWidgetLevelViewport = ActiveLevelViewport; + } +#endif + } + + return bResult; +} + +void FVRFullScreenUserWidget_Viewport::Hide(UWorld* World) +{ + TSharedPtr<SConstraintCanvas> FullScreenWidgetPinned = FullScreenCanvasWidget.Pin(); + if (FullScreenWidgetPinned.IsValid()) + { + // Remove from Viewport and Fullscreen, in case the settings changed before we had the chance to hide. + UGameViewportClient* ViewportClient = World ? World->GetGameViewport() : nullptr; + if (ViewportClient) + { + ViewportClient->RemoveViewportWidgetContent(FullScreenWidgetPinned.ToSharedRef()); + } + +#if WITH_EDITOR + TSharedPtr<SLevelViewport> OverlayWidgetLevelViewportPinned = OverlayWidgetLevelViewport.Pin(); + if (OverlayWidgetLevelViewportPinned) + { + OverlayWidgetLevelViewportPinned->RemoveOverlayWidget(FullScreenWidgetPinned.ToSharedRef()); + } + OverlayWidgetLevelViewport.Reset(); +#endif + + FullScreenCanvasWidget.Reset(); + } +} + +void FVRFullScreenUserWidget_Viewport::Tick(UWorld* World, float DeltaSeconds) +{ + +} + +///////////////////////////////////////////////////// +// FVRFullScreenUserWidget_PostProcess + +FVRFullScreenUserWidget_PostProcess::FVRFullScreenUserWidget_PostProcess() + :// PostProcessMaterial(nullptr) + //, PostProcessTintColorAndOpacity(FLinearColor::White) + //, PostProcessOpacityFromTexture(1.0f) + bWidgetDrawSize(false) + , WidgetDrawSize(FIntPoint(640, 360)) + , bWindowFocusable(true) + , WindowVisibility(EWindowVisibility::SelfHitTestInvisible) + , bReceiveHardwareInput(true) + , RenderTargetBackgroundColor(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f)) + , RenderTargetBlendMode(EWidgetBlendMode::Masked) + , WidgetRenderTarget(nullptr) + //, PostProcessComponent(nullptr) + //, PostProcessMaterialInstance(nullptr) + , WidgetRenderer(nullptr) + , CurrentWidgetDrawSize(FIntPoint::ZeroValue) +{ + bRenderToTextureOnly = true; + bDrawToVRPreview = true; + VRDisplayType = ESpectatorScreenMode::TexturePlusEye; + PostVRDisplayType = ESpectatorScreenMode::SingleEye; +} + +bool FVRFullScreenUserWidget_PostProcess::Display(UWorld* World, UUserWidget* Widget, bool bInRenderToTextureOnly, float InDPIScale) +{ + + bool bOk = CreateRenderer(World, Widget, InDPIScale); + + if (bRenderToTextureOnly && IsValid(WidgetRenderTarget) && bDrawToVRPreview) + { + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + ISpectatorScreenController* Controller = nullptr; + if (HMD) + { + Controller = HMD->GetSpectatorScreenController(); + } + + if (Controller) + { + if (VRDisplayType == ESpectatorScreenMode::TexturePlusEye) + { + if (Controller->GetSpectatorScreenMode() != ESpectatorScreenMode::TexturePlusEye) + { + Controller->SetSpectatorScreenMode(ESpectatorScreenMode::TexturePlusEye); + } + + FSpectatorScreenModeTexturePlusEyeLayout Layout; + Layout.bClearBlack = true; + Layout.bDrawEyeFirst = true; + Layout.bUseAlpha = true; + Layout.EyeRectMin = FVector2D(0.f, 0.f); + Layout.EyeRectMax = FVector2D(1.f, 1.f); + Layout.TextureRectMin = FVector2D(0.f, 0.f); + Layout.TextureRectMax = FVector2D(1.f, 1.f); + Controller->SetSpectatorScreenModeTexturePlusEyeLayout(Layout); + Controller->SetSpectatorScreenTexture(WidgetRenderTarget); + } + else if (VRDisplayType == ESpectatorScreenMode::Texture) + { + if (Controller->GetSpectatorScreenMode() != ESpectatorScreenMode::TexturePlusEye) + { + Controller->SetSpectatorScreenMode(ESpectatorScreenMode::Texture); + } + + Controller->SetSpectatorScreenTexture(WidgetRenderTarget); + } + } + } + + if (!bRenderToTextureOnly) + { + //bOk &= CreatePostProcessComponent(World); + } + + return bOk; +} + +void FVRFullScreenUserWidget_PostProcess::Hide(UWorld* World) +{ + if (!bRenderToTextureOnly) + { + //ReleasePostProcessComponent(); + } + + if (bRenderToTextureOnly && bDrawToVRPreview) + { + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + ISpectatorScreenController* Controller = nullptr; + if (HMD) + { + Controller = HMD->GetSpectatorScreenController(); + } + + if (Controller) + { + if (Controller->GetSpectatorScreenMode() == ESpectatorScreenMode::TexturePlusEye || Controller->GetSpectatorScreenMode() == ESpectatorScreenMode::Texture) + { + Controller->SetSpectatorScreenMode(PostVRDisplayType); + Controller->SetSpectatorScreenTexture(nullptr); + } + } + } + + ReleaseRenderer(); +} + +void FVRFullScreenUserWidget_PostProcess::Tick(UWorld* World, float DeltaSeconds) +{ + TickRenderer(World, DeltaSeconds); +} + +TSharedPtr<SVirtualWindow> FVRFullScreenUserWidget_PostProcess::GetSlateWindow() const +{ + return SlateWindow; +} + +/*bool FVRFullScreenUserWidget_PostProcess::CreatePostProcessComponent(UWorld* World) +{ + ReleasePostProcessComponent(); + if (World && PostProcessMaterial) + { + AWorldSettings* WorldSetting = World->GetWorldSettings(); + PostProcessComponent = NewObject<UPostProcessComponent>(WorldSetting, NAME_None, RF_Transient); + PostProcessComponent->bEnabled = true; + PostProcessComponent->bUnbound = true; + PostProcessComponent->RegisterComponent(); + + PostProcessMaterialInstance = UMaterialInstanceDynamic::Create(PostProcessMaterial, World); + + // set the parameter immediately + PostProcessMaterialInstance->SetTextureParameterValue(NAME_SlateUI, WidgetRenderTarget); + PostProcessMaterialInstance->SetVectorParameterValue(NAME_TintColorAndOpacity, PostProcessTintColorAndOpacity); + PostProcessMaterialInstance->SetScalarParameterValue(NAME_OpacityFromTexture, PostProcessOpacityFromTexture); + + PostProcessComponent->Settings.WeightedBlendables.Array.SetNumZeroed(1); + PostProcessComponent->Settings.WeightedBlendables.Array[0].Weight = 1.f; + PostProcessComponent->Settings.WeightedBlendables.Array[0].Object = PostProcessMaterialInstance; + } + + return PostProcessComponent && PostProcessMaterialInstance; +}*/ + +/*void FVRFullScreenUserWidget_PostProcess::ReleasePostProcessComponent() +{ + if (PostProcessComponent) + { + PostProcessComponent->UnregisterComponent(); + } + PostProcessComponent = nullptr; + PostProcessMaterialInstance = nullptr; +}*/ + +bool FVRFullScreenUserWidget_PostProcess::CreateRenderer(UWorld* World, UUserWidget* Widget, float InDPIScale) +{ + ReleaseRenderer(); + + if (World && Widget) + { + const FIntPoint CalculatedWidgetSize = CalculateWidgetDrawSize(World); + if (IsTextureSizeValid(CalculatedWidgetSize)) + { + CurrentWidgetDrawSize = CalculatedWidgetSize; + + const bool bApplyGammaCorrection = true; + WidgetRenderer = new FWidgetRenderer(bApplyGammaCorrection); + WidgetRenderer->SetIsPrepassNeeded(true); + + SlateWindow = SNew(SVirtualWindow).Size(CurrentWidgetDrawSize); + SlateWindow->SetIsFocusable(bWindowFocusable); + SlateWindow->SetVisibility(ConvertWindowVisibilityToVisibility(WindowVisibility)); + SlateWindow->SetContent(SNew(SDPIScaler).DPIScale(InDPIScale) + [ + Widget->TakeWidget() + ] + ); + + RegisterHitTesterWithViewport(World); + + if (!Widget->IsDesignTime() && World->IsGameWorld()) + { + UGameInstance* GameInstance = World->GetGameInstance(); + UGameViewportClient* GameViewportClient = GameInstance ? GameInstance->GetGameViewportClient() : nullptr; + if (GameViewportClient) + { + SlateWindow->AssignParentWidget(GameViewportClient->GetGameViewportWidget()); + } + } + + FLinearColor ActualBackgroundColor = RenderTargetBackgroundColor; + switch (RenderTargetBlendMode) + { + case EWidgetBlendMode::Opaque: + ActualBackgroundColor.A = 1.0f; + break; + case EWidgetBlendMode::Masked: + ActualBackgroundColor.A = 0.0f; + break; + } + + AWorldSettings* WorldSetting = World->GetWorldSettings(); + WidgetRenderTarget = NewObject<UTextureRenderTarget2D>(WorldSetting, NAME_None, RF_Transient); + WidgetRenderTarget->ClearColor = ActualBackgroundColor; + WidgetRenderTarget->InitCustomFormat(CurrentWidgetDrawSize.X, CurrentWidgetDrawSize.Y, PF_B8G8R8A8, false); + WidgetRenderTarget->UpdateResourceImmediate(); + + /*if (!bRenderToTextureOnly && PostProcessMaterialInstance) + { + PostProcessMaterialInstance->SetTextureParameterValue(NAME_SlateUI, WidgetRenderTarget); + }*/ + } + } + + return WidgetRenderer && WidgetRenderTarget; +} + +void FVRFullScreenUserWidget_PostProcess::ReleaseRenderer() +{ + if (WidgetRenderer) + { + BeginCleanup(WidgetRenderer); + WidgetRenderer = nullptr; + } + UnRegisterHitTesterWithViewport(); + + SlateWindow.Reset(); + WidgetRenderTarget = nullptr; + CurrentWidgetDrawSize = FIntPoint::ZeroValue; +} + +void FVRFullScreenUserWidget_PostProcess::TickRenderer(UWorld* World, float DeltaSeconds) +{ + check(World); + if (IsValid(WidgetRenderTarget)) + { + + if (bRenderToTextureOnly && bDrawToVRPreview) + { + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + ISpectatorScreenController* Controller = nullptr; + if (HMD) + { + Controller = HMD->GetSpectatorScreenController(); + } + + if (Controller) + { + if (Controller->GetSpectatorScreenMode() != ESpectatorScreenMode::TexturePlusEye) + { + Controller->SetSpectatorScreenMode(ESpectatorScreenMode::TexturePlusEye); + FSpectatorScreenModeTexturePlusEyeLayout Layout; + Layout.bClearBlack = true; + Layout.bDrawEyeFirst = true; + Layout.bUseAlpha = true; + Layout.EyeRectMin = FVector2D(0.f, 0.f); + Layout.EyeRectMax = FVector2D(1.f, 1.f); + Layout.TextureRectMin = FVector2D(0.f, 0.f); + Layout.TextureRectMax = FVector2D(1.f, 1.f); + Controller->SetSpectatorScreenModeTexturePlusEyeLayout(Layout); + Controller->SetSpectatorScreenTexture(WidgetRenderTarget); + } + } + } + + const float DrawScale = 1.0f; + + const FIntPoint NewCalculatedWidgetSize = CalculateWidgetDrawSize(World); + if (NewCalculatedWidgetSize != CurrentWidgetDrawSize) + { + if (IsTextureSizeValid(NewCalculatedWidgetSize)) + { + CurrentWidgetDrawSize = NewCalculatedWidgetSize; + WidgetRenderTarget->InitCustomFormat(CurrentWidgetDrawSize.X, CurrentWidgetDrawSize.Y, PF_B8G8R8A8, false); + WidgetRenderTarget->UpdateResourceImmediate(); + SlateWindow->Resize(CurrentWidgetDrawSize); + if (CustomHitTestPath) + { + CustomHitTestPath->SetWidgetDrawSize(CurrentWidgetDrawSize); + } + } + else + { + Hide(World); + } + } + + if (WidgetRenderer) + { + WidgetRenderer->DrawWindow( + WidgetRenderTarget, + SlateWindow->GetHittestGrid(), + SlateWindow.ToSharedRef(), + DrawScale, + CurrentWidgetDrawSize, + DeltaSeconds); + } + } +} + +FIntPoint FVRFullScreenUserWidget_PostProcess::CalculateWidgetDrawSize(UWorld* World) +{ + if (bWidgetDrawSize) + { + return WidgetDrawSize; + } + + if (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE) + { + if (UGameViewportClient* ViewportClient = World->GetGameViewport()) + { + // The viewport maybe resizing or not yet initialized. + //See TickRenderer(), it will be resize on the next tick to the proper size. + //We initialized all the rendering with an small size. + + const float SmallWidgetSize = 16.f; + FVector2D OutSize = FVector2D(SmallWidgetSize, SmallWidgetSize); + ViewportClient->GetViewportSize(OutSize); + if (OutSize.X < SMALL_NUMBER) + { + OutSize = FVector2D(SmallWidgetSize, SmallWidgetSize); + } + return OutSize.IntPoint(); + } + } +#if WITH_EDITOR + else if (FModuleManager::Get().IsModuleLoaded(NAME_LevelEditorName)) + { + FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(NAME_LevelEditorName); + TSharedPtr<SLevelViewport> ActiveLevelViewport; + if (TargetViewport.IsValid()) + { + ActiveLevelViewport = TargetViewport.Pin(); + } + else + { + ActiveLevelViewport = LevelEditorModule.GetFirstActiveLevelViewport(); + } + if (ActiveLevelViewport.IsValid()) + { + if (TSharedPtr<FSceneViewport> SharedActiveViewport = ActiveLevelViewport->GetSharedActiveViewport()) + { + return SharedActiveViewport->GetSize(); + } + } + } +#endif + return FIntPoint::ZeroValue; +} + +bool FVRFullScreenUserWidget_PostProcess::IsTextureSizeValid(FIntPoint Size) const +{ + const int32 MaxAllowedDrawSize = GetMax2DTextureDimension(); + return Size.X > 0 && Size.Y > 0 && Size.X <= MaxAllowedDrawSize && Size.Y <= MaxAllowedDrawSize; +} + +void FVRFullScreenUserWidget_PostProcess::RegisterHitTesterWithViewport(UWorld* World) +{ + if (!bReceiveHardwareInput && FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().RegisterVirtualWindow(SlateWindow.ToSharedRef()); + } + + TSharedPtr<SViewport> EngineViewportWidget; + if (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE) + { + EngineViewportWidget = GEngine->GetGameViewportWidget(); + } +#if WITH_EDITOR + else if (FModuleManager::Get().IsModuleLoaded(NAME_LevelEditorName)) + { + FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(NAME_LevelEditorName); + + TSharedPtr<SLevelViewport> ActiveLevelViewport; + if (TargetViewport.IsValid()) + { + ActiveLevelViewport = TargetViewport.Pin(); + } + else + { + ActiveLevelViewport = LevelEditorModule.GetFirstActiveLevelViewport(); + } + if (ActiveLevelViewport.IsValid()) + { + EngineViewportWidget = ActiveLevelViewport->GetViewportWidget().Pin(); + } + } +#endif + + if (EngineViewportWidget && bReceiveHardwareInput) + { + if (EngineViewportWidget->GetCustomHitTestPath()) + { + //UE_LOG(LogVPUtilities, Warning, TEXT("Can't register a hit tester for FullScreenUserWidget. There is already one defined.")); + } + else + { + ViewportWidget = EngineViewportWidget; + CustomHitTestPath = MakeShared<FVRWidgetPostProcessHitTester>(World, SlateWindow); + CustomHitTestPath->SetWidgetDrawSize(CurrentWidgetDrawSize); + EngineViewportWidget->SetCustomHitTestPath(CustomHitTestPath); + } + } +} + +void FVRFullScreenUserWidget_PostProcess::UnRegisterHitTesterWithViewport() +{ + if (SlateWindow.IsValid() && FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().UnregisterVirtualWindow(SlateWindow.ToSharedRef()); + } + + if (TSharedPtr<SViewport> ViewportWidgetPin = ViewportWidget.Pin()) + { + if (ViewportWidgetPin->GetCustomHitTestPath() == CustomHitTestPath) + { + ViewportWidgetPin->SetCustomHitTestPath(nullptr); + } + } + + ViewportWidget.Reset(); + CustomHitTestPath.Reset(); +} + +///////////////////////////////////////////////////// +// UVRFullScreenUserWidget + +UVRFullScreenUserWidget::UVRFullScreenUserWidget(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CurrentDisplayType(EVRWidgetDisplayType::Inactive) + , bDisplayRequested(false) +{ + //Material'/VRExpansionPlugin/Materials/VRWidgetPostProcessMaterial.WidgetPostProcessMaterial' + //static ConstructorHelpers::FObjectFinder<UMaterialInterface> PostProcessMaterial_Finder(TEXT("/VRExpansionPlugin/Materials/VRWidgetPostProcessMaterial")); + //PostProcessDisplayType.PostProcessMaterial = PostProcessMaterial_Finder.Object; +} + +void UVRFullScreenUserWidget::BeginDestroy() +{ + Hide(); + Super::BeginDestroy(); +} + +bool UVRFullScreenUserWidget::ShouldDisplay(UWorld* InWorld) const +{ +#if UE_SERVER + return false; +#else + if (GUsingNullRHI || HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject) || IsRunningDedicatedServer()) + { + return false; + } + + return GetDisplayType(InWorld) != EVRWidgetDisplayType::Inactive; +#endif //!UE_SERVER +} + +EVRWidgetDisplayType UVRFullScreenUserWidget::GetDisplayType(UWorld* InWorld) const +{ + if (InWorld) + { + if (InWorld->WorldType == EWorldType::Game) + { + return GameDisplayType; + } +#if WITH_EDITOR + else if (InWorld->WorldType == EWorldType::PIE) + { + return PIEDisplayType; + } + else if (InWorld->WorldType == EWorldType::Editor) + { + return EditorDisplayType; + } +#endif // WITH_EDITOR + } + return EVRWidgetDisplayType::Inactive; +} + +bool UVRFullScreenUserWidget::IsDisplayed() const +{ + return CurrentDisplayType != EVRWidgetDisplayType::Inactive; +} + +bool UVRFullScreenUserWidget::Display(UWorld* InWorld) +{ + bDisplayRequested = true; + + World = InWorld; + + bool bWasAdded = false; + if (InWorld && WidgetClass && ShouldDisplay(InWorld) && CurrentDisplayType == EVRWidgetDisplayType::Inactive) + { + CurrentDisplayType = GetDisplayType(InWorld); + + InitWidget(); + + const float DPIScale = GetViewportDPIScale(); + + if (CurrentDisplayType == EVRWidgetDisplayType::Viewport) + { + bWasAdded = ViewportDisplayType.Display(InWorld, Widget, DPIScale); + } + else if (CurrentDisplayType == EVRWidgetDisplayType::PostProcess /*|| (CurrentDisplayType == EVRWidgetDisplayType::Composure)*/) + { + bWasAdded = PostProcessDisplayType.Display(InWorld, Widget, /*(CurrentDisplayType == EVRWidgetDisplayType::Composure)*/true, DPIScale); + } + + if (bWasAdded) + { + FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &UVRFullScreenUserWidget::OnLevelRemovedFromWorld); + FWorldDelegates::OnWorldCleanup.AddUObject(this, &UVRFullScreenUserWidget::OnWorldCleanup); + VPVRFullScreenUserWidgetPrivate::FWorldCleanupListener::Get()->AddWidget(this); + + // If we are using Composure as our output, then send the WidgetRenderTarget to each one + /*if (CurrentDisplayType == EVRWidgetDisplayType::Composure) + { + static const FString TextureCompClassName("BP_TextureRTCompElement_C"); + static const FName TextureInputPropertyName("TextureRTInput"); + + for (ACompositingElement* Layer : PostProcessDisplayType.ComposureLayerTargets) + { + if (Layer && (Layer->GetClass()->GetName() == TextureCompClassName)) + { + FProperty* TextureInputProperty = Layer->GetClass()->FindPropertyByName(TextureInputPropertyName); + if (TextureInputProperty) + { + FObjectProperty* TextureInputObjectProperty = CastField<FObjectProperty>(TextureInputProperty); + if (TextureInputObjectProperty) + { + UTextureRenderTarget2D** DestTextureRT2D = TextureInputProperty->ContainerPtrToValuePtr<UTextureRenderTarget2D*>(Layer); + if (DestTextureRT2D) + { + TextureInputObjectProperty->SetObjectPropertyValue(DestTextureRT2D, PostProcessDisplayType.WidgetRenderTarget); + Layer->RerunConstructionScripts(); + } + } + } + } + else if (Layer) + { + UE_LOG(LogVPUtilities, Warning, TEXT("VRFullScreenUserWidget - ComposureLayerTarget entry '%s' is not the correct class '%s'"), *Layer->GetName(), *TextureCompClassName); + } + } + }*/ + } + } + + return bWasAdded; +} + +void UVRFullScreenUserWidget::Hide() +{ + bDisplayRequested = false; + + if (CurrentDisplayType != EVRWidgetDisplayType::Inactive) + { + ReleaseWidget(); + FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this); + FWorldDelegates::OnWorldCleanup.RemoveAll(this); + VPVRFullScreenUserWidgetPrivate::FWorldCleanupListener::Get()->RemoveWidget(this); + + if (CurrentDisplayType == EVRWidgetDisplayType::Viewport) + { + ViewportDisplayType.Hide(World.Get()); + } + else if (CurrentDisplayType == EVRWidgetDisplayType::PostProcess /*|| (CurrentDisplayType == EVRWidgetDisplayType::Composure)*/) + { + PostProcessDisplayType.Hide(World.Get()); + } + CurrentDisplayType = EVRWidgetDisplayType::Inactive; + } + + World.Reset(); +} + +void UVRFullScreenUserWidget::OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources) +{ + if (IsDisplayed() && World == InWorld) + { + Hide(); + } +} + +void UVRFullScreenUserWidget::Tick(float DeltaSeconds) +{ + if (CurrentDisplayType != EVRWidgetDisplayType::Inactive) + { + UWorld* CurrentWorld = World.Get(); + if (CurrentWorld == nullptr) + { + Hide(); + } + else + { + if (CurrentDisplayType == EVRWidgetDisplayType::Viewport) + { + ViewportDisplayType.Tick(CurrentWorld, DeltaSeconds); + } + else if (CurrentDisplayType == EVRWidgetDisplayType::PostProcess /*|| (CurrentDisplayType == EVRWidgetDisplayType::Composure)*/) + { + PostProcessDisplayType.Tick(CurrentWorld, DeltaSeconds); + } + } + } +} + +void UVRFullScreenUserWidget::SetDisplayTypes(EVRWidgetDisplayType InEditorDisplayType, EVRWidgetDisplayType InGameDisplayType, EVRWidgetDisplayType InPIEDisplayType) +{ + EditorDisplayType = InEditorDisplayType; + GameDisplayType = InGameDisplayType; + PIEDisplayType = InPIEDisplayType; +} + +void UVRFullScreenUserWidget::InitWidget() +{ + // Don't do any work if Slate is not initialized + if (FSlateApplication::IsInitialized()) + { + if (WidgetClass && Widget == nullptr) + { + check(World.Get()); + Widget = CreateWidget(World.Get(), WidgetClass); + Widget->SetFlags(RF_Transient); + } + } +} + +void UVRFullScreenUserWidget::ReleaseWidget() +{ + Widget = nullptr; +} + +void UVRFullScreenUserWidget::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) +{ + // If the InLevel is invalid, then the entire world is about to disappear. + //Hide the widget to clear the memory and reference to the world it may hold. + if (InLevel == nullptr && InWorld && InWorld == World.Get()) + { + Hide(); + } +} + +FVector2D UVRFullScreenUserWidget::FindSceneViewportSize() +{ + FVector2D OutSize; + + UWorld* CurrentWorld = World.Get(); + if (CurrentWorld && (CurrentWorld->WorldType == EWorldType::Game || CurrentWorld->WorldType == EWorldType::PIE)) + { + if (UGameViewportClient* ViewportClient = World->GetGameViewport()) + { + ViewportClient->GetViewportSize(OutSize); + } + } +#if WITH_EDITOR + else if (FModuleManager::Get().IsModuleLoaded(NAME_LevelEditorName)) + { + FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(NAME_LevelEditorName); + TSharedPtr<SLevelViewport> ActiveLevelViewport; + if (TargetViewport.IsValid()) + { + ActiveLevelViewport = TargetViewport.Pin(); + } + else + { + ActiveLevelViewport = LevelEditorModule.GetFirstActiveLevelViewport(); + } + if (ActiveLevelViewport.IsValid()) + { + if (TSharedPtr<FSceneViewport> SharedActiveViewport = ActiveLevelViewport->GetSharedActiveViewport()) + { + OutSize = FVector2D(SharedActiveViewport->GetSize()); + } + } + } +#endif + + return OutSize; +} + +float UVRFullScreenUserWidget::GetViewportDPIScale() +{ + float UIScale = 1.0f; + float PlatformScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(10.0f, 10.0f); + + UWorld* CurrentWorld = World.Get(); + if ((CurrentDisplayType == EVRWidgetDisplayType::Viewport) && CurrentWorld && (CurrentWorld->WorldType == EWorldType::Game || CurrentWorld->WorldType == EWorldType::PIE)) + { + // If we are in Game or PIE in Viewport display mode, the GameLayerManager will scale correctly so just return the Platform Scale + UIScale = PlatformScale; + } + else + { + // Otherwise when in Editor mode, the editor automatically scales to the platform size, so we only care about the UI scale + FIntPoint ViewportSize = FindSceneViewportSize().IntPoint(); + + const UUserInterfaceSettings* UserInterfaceSettings = GetDefault<UUserInterfaceSettings>(UUserInterfaceSettings::StaticClass()); + if (UserInterfaceSettings) + { + UIScale = UserInterfaceSettings->GetDPIScaleBasedOnSize(ViewportSize); + } + } + + return UIScale; +} + + +#if WITH_EDITOR +void UVRFullScreenUserWidget::SetAllTargetViewports(TWeakPtr<SLevelViewport> InTargetViewport) +{ + TargetViewport = InTargetViewport; + ViewportDisplayType.TargetViewport = InTargetViewport; + PostProcessDisplayType.TargetViewport = InTargetViewport; +} + +void UVRFullScreenUserWidget::ResetAllTargetViewports() +{ + TargetViewport.Reset(); + ViewportDisplayType.TargetViewport.Reset(); + PostProcessDisplayType.TargetViewport.Reset(); +} + +void UVRFullScreenUserWidget::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + FProperty* Property = PropertyChangedEvent.MemberProperty; + + if (Property && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) + { + static FName NAME_WidgetClass = GET_MEMBER_NAME_CHECKED(UVRFullScreenUserWidget, WidgetClass); + static FName NAME_EditorDisplayType = GET_MEMBER_NAME_CHECKED(UVRFullScreenUserWidget, EditorDisplayType); + //static FName NAME_PostProcessMaterial = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostProcessMaterial); + static FName NAME_WidgetDrawSize = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, WidgetDrawSize); + static FName NAME_WindowFocusable = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, bWindowFocusable); + static FName NAME_WindowVisibility = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, WindowVisibility); + static FName NAME_ReceiveHardwareInput = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, bReceiveHardwareInput); + static FName NAME_RenderTargetBackgroundColor = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, RenderTargetBackgroundColor); + static FName NAME_RenderTargetBlendMode = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, RenderTargetBlendMode); + //static FName NAME_PostProcessTintColorAndOpacity = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostProcessTintColorAndOpacity); + //static FName NAME_PostProcessOpacityFromTexture = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostProcessOpacityFromTexture); + static FName NAME_DrawToVRPreview = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, bDrawToVRPreview); + static FName NAME_VRDisplayType = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, VRDisplayType); + static FName NAME_PostVRDisplayType = GET_MEMBER_NAME_CHECKED(FVRFullScreenUserWidget_PostProcess, PostVRDisplayType); + + if (Property->GetFName() == NAME_WidgetClass + || Property->GetFName() == NAME_EditorDisplayType + //|| Property->GetFName() == NAME_PostProcessMaterial + || Property->GetFName() == NAME_WidgetDrawSize + || Property->GetFName() == NAME_WindowFocusable + || Property->GetFName() == NAME_WindowVisibility + || Property->GetFName() == NAME_ReceiveHardwareInput + || Property->GetFName() == NAME_RenderTargetBackgroundColor + || Property->GetFName() == NAME_RenderTargetBlendMode + || Property->GetFName() == NAME_DrawToVRPreview + || Property->GetFName() == NAME_VRDisplayType + || Property->GetFName() == NAME_PostVRDisplayType) + //|| Property->GetFName() == NAME_PostProcessTintColorAndOpacity + //|| Property->GetFName() == NAME_PostProcessOpacityFromTexture) + { + bool bWasRequestedDisplay = bDisplayRequested; + UWorld* CurrentWorld = World.Get(); + Hide(); + if (bWasRequestedDisplay && CurrentWorld) + { + Display(CurrentWorld); + } + } + } + + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif + +///////////////////////////////////////////////////// +// AVRFullScreenUserWidgetActor + +AVRFullScreenUserWidgetActor::AVRFullScreenUserWidgetActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +#if WITH_EDITOR + , bEditorDisplayRequested(false) +#endif //WITH_EDITOR +{ + ScreenUserWidget = CreateDefaultSubobject<UVRFullScreenUserWidget>(TEXT("ScreenUserWidget")); + + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bStartWithTickEnabled = false; + bAllowTickBeforeBeginPlay = false; + SetActorTickEnabled(false); + //SetHidden(false); + + bShowOnInit = false; +} + +void AVRFullScreenUserWidgetActor::PostInitializeComponents() +{ + Super::PostInitializeComponents(); + +#if WITH_EDITOR + bEditorDisplayRequested = true; +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::PostLoad() +{ + Super::PostLoad(); + +#if WITH_EDITOR + bEditorDisplayRequested = true; +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::PostActorCreated() +{ + Super::PostActorCreated(); + +#if WITH_EDITOR + bEditorDisplayRequested = true; +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::Destroyed() +{ + if (ScreenUserWidget) + { + ScreenUserWidget->Hide(); + } + Super::Destroyed(); +} + +void AVRFullScreenUserWidgetActor::BeginPlay() +{ + if (ScreenUserWidget && bShowOnInit) + { + RequestGameDisplay(); + } + + Super::BeginPlay(); +} + +void AVRFullScreenUserWidgetActor::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (ScreenUserWidget) + { + UWorld* ActorWorld = GetWorld(); + if (ActorWorld && (ActorWorld->WorldType == EWorldType::Game || ActorWorld->WorldType == EWorldType::PIE)) + { + ScreenUserWidget->Hide(); + } + } +} + +void AVRFullScreenUserWidgetActor::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + +#if WITH_EDITOR + if (bEditorDisplayRequested) + { + bEditorDisplayRequested = false; + RequestEditorDisplay(); + } +#endif //WITH_EDITOR + + // Don't tick if not requested + if (ScreenUserWidget && ScreenUserWidget->IsDisplayRequested()) + { + ScreenUserWidget->Tick(DeltaSeconds); + } +} + +void AVRFullScreenUserWidgetActor::RequestEditorDisplay() +{ +#if WITH_EDITOR + UWorld* ActorWorld = GetWorld(); + if (ScreenUserWidget && ActorWorld && ActorWorld->WorldType == EWorldType::Editor) + { + ScreenUserWidget->Display(ActorWorld); + } +#endif //WITH_EDITOR +} + +void AVRFullScreenUserWidgetActor::RequestGameDisplay() +{ + UWorld* ActorWorld = GetWorld(); + if (ScreenUserWidget && ActorWorld && (ActorWorld->WorldType == EWorldType::Game || ActorWorld->WorldType == EWorldType::PIE)) + { + ScreenUserWidget->Display(ActorWorld); + SetActorTickEnabled(true); + } +} + +void AVRFullScreenUserWidgetActor::SetWidgetVisible(bool bIsVisible) +{ + if (ScreenUserWidget) + { + if (!bIsVisible) + { + ScreenUserWidget->Hide(); + SetActorTickEnabled(false); + } + else + { + RequestGameDisplay(); + } + } +} + +UVRFullScreenUserWidget* AVRFullScreenUserWidgetActor::GetPreviewWidgetComp() +{ + return ScreenUserWidget; +} + +UUserWidget* AVRFullScreenUserWidgetActor::GetWidget() +{ + if (ScreenUserWidget) + { + return ScreenUserWidget->GetWidget(); + } + + return nullptr; +} + +UTextureRenderTarget2D* AVRFullScreenUserWidgetActor::GetPostProcessRenderTarget() +{ + if (ScreenUserWidget) + { + return ScreenUserWidget->GetPostProcessRenderTarget(); + } + + return nullptr; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRLogComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRLogComponent.cpp new file mode 100644 index 0000000..15a3303 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRLogComponent.cpp @@ -0,0 +1,199 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRLogComponent.h" +//#include "Engine/Engine.h" + +/* Top of File */ +#define LOCTEXT_NAMESPACE "VRLogComponent" + + //============================================================================= +UVRLogComponent::UVRLogComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = false; + MaxLineLength = 130; + MaxStoredMessages = 10000; +} + +//============================================================================= +UVRLogComponent::~UVRLogComponent() +{ + +} + + +void UVRLogComponent::SetConsoleText(FString Text) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + + if (!ViewportConsole) + return; + + // Using append because UpdatePrecompletedInputLine is private and append calls it + ViewportConsole->SetInputText(""); + ViewportConsole->AppendInputText(Text); +} + +void UVRLogComponent::SendKeyEventToConsole(FKey Key, EInputEvent KeyEvent) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + + if (!ViewportConsole) + return; + + ViewportConsole->FakeGotoState(FName(TEXT("Typing"))); + ViewportConsole->InputKey(0, Key, KeyEvent); + ViewportConsole->FakeGotoState(NAME_None); +} + +void UVRLogComponent::AppendTextToConsole(FString Text, bool bReturnAtEnd) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + + if (!ViewportConsole) + return; + + ViewportConsole->AppendInputText(Text); + + if (bReturnAtEnd) + { + ViewportConsole->FakeGotoState(FName(TEXT("Typing"))); + ViewportConsole->InputKey(0, EKeys::Enter, EInputEvent::IE_Released); + ViewportConsole->FakeGotoState(NAME_None); + } + +} + +bool UVRLogComponent::DrawConsoleToRenderTarget2D(EBPVRConsoleDrawType DrawType, UTextureRenderTarget2D * Texture, float ScrollOffset, bool bForceDraw) +{ + if (!bForceDraw && DrawType == EBPVRConsoleDrawType::VRConsole_Draw_OutputLogOnly && !OutputLogHistory.bIsDirty) + { + return false; + } + //LastRenderedOutputLogSize + +// check(WorldContextObject); + UWorld* World = GetWorld();//GEngine->GetWorldFromContextObject(WorldContextObject, false); + + if (!World) + return false; + + // Create or find the canvas object to use to render onto the texture. Multiple canvas render target textures can share the same canvas. + UCanvas* Canvas = World->GetCanvasForRenderingToTarget(); + + if (!Canvas) + return false; + + // Create the FCanvas which does the actual rendering. + //const ERHIFeatureLevel::Type FeatureLevel = World != nullptr ? World->FeatureLevel : GMaxRHIFeatureLevel; + + FCanvas * RenderCanvas = new FCanvas( + Texture->GameThread_GetRenderTargetResource(), + nullptr, + World, + World->FeatureLevel, + // Draw immediately so that interleaved SetVectorParameter (etc) function calls work as expected + FCanvas::CDM_ImmediateDrawing); + + Canvas->Init(Texture->GetSurfaceWidth(), Texture->GetSurfaceHeight(), nullptr, RenderCanvas); + Canvas->Update(); + + switch (DrawType) + { + //case EBPVRConsoleDrawType::VRConsole_Draw_ConsoleAndOutputLog: DrawConsole(true, Canvas); DrawOutputLog(true, Canvas); break; + case EBPVRConsoleDrawType::VRConsole_Draw_ConsoleOnly: DrawConsole(false, Canvas); break; + case EBPVRConsoleDrawType::VRConsole_Draw_OutputLogOnly: DrawOutputLog(false, Canvas, ScrollOffset); break; + default: break; + } + + // Clean up and flush the rendering canvas. + Canvas->Canvas = nullptr; + RenderCanvas->Flush_GameThread(); + delete RenderCanvas; + RenderCanvas = nullptr; + + // It renders without this, is it actually required? + // Enqueue the rendering command to copy the freshly rendering texture resource back to the render target RHI + // so that the texture is updated and available for rendering. + /*ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER + ( + CanvasRenderTargetResolveCommand, + FTextureRenderTargetResource*, + RenderTargetResource, + Texture->GameThread_GetRenderTargetResource(), + { + RHICmdList.CopyToResolveTarget(RenderTargetResource->GetRenderTargetTexture(), RenderTargetResource->TextureRHI, true, FResolveParams()); + } + );*/ + + return true; +} + + + +void UVRLogComponent::DrawConsole(bool bLowerHalf, UCanvas* Canvas) +{ + UConsole* ViewportConsole = (GEngine->GameViewport != nullptr) ? GEngine->GameViewport->ViewportConsole : nullptr; + if (!ViewportConsole) + return; + + ViewportConsole->PostRender_Console_Open(Canvas); + +} + +void UVRLogComponent::DrawOutputLog(bool bUpperHalf, UCanvas* Canvas, float ScrollOffset) +{ + UFont* Font = GEngine->GetSmallFont();// GEngine->GetTinyFont();//GEngine->GetSmallFont(); + + // determine the height of the text + float xl, yl; + Canvas->StrLen(Font, TEXT("M"), xl, yl); + float Height = FMath::FloorToFloat(Canvas->ClipY);// *0.75f); + + + // Background + FLinearColor BackgroundColor = FColor::Black.ReinterpretAsLinear(); + BackgroundColor.A = 1.0f; + FCanvasTileItem ConsoleTile(FVector2D(0, 0.0f), GBlackTexture, FVector2D(Canvas->ClipX, Canvas->ClipY), FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f), BackgroundColor); + + // Preserve alpha to allow single-pass composite + ConsoleTile.BlendMode = SE_BLEND_AlphaBlend; + + Canvas->DrawItem(ConsoleTile); + + FCanvasTextItem ConsoleText(FVector2D(0, 0 + Height - 5 - yl), FText::FromString(TEXT("")), Font, FColor::Emerald); + + const TArray< TSharedPtr<FVRLogMessage> > LoggedMessages = OutputLogHistory.GetMessages(); + + int32 ScrollPos = 0; + + if(ScrollOffset > 0 && LoggedMessages.Num() > 1) + ScrollPos = FMath::Clamp(FMath::RoundToInt(LoggedMessages.Num() * ScrollOffset ) , 0, LoggedMessages.Num() - 1); + + float Xpos = 0.0f; + float Ypos = 0.0f; + for (int i = LoggedMessages.Num() - (1 + ScrollPos); i >= 0 && Ypos <= Height - yl; i--)//auto &Message : LoggedMessages) + { + switch (LoggedMessages[i]->Verbosity) + { + + case ELogVerbosity::Error: + case ELogVerbosity::Fatal: ConsoleText.SetColor(FLinearColor(0.7f,0.1f,0.1f)); break; + case ELogVerbosity::Warning: ConsoleText.SetColor(FLinearColor(0.5f,0.5f,0.0f)); break; + + case ELogVerbosity::Log: + default: ConsoleText.SetColor(FLinearColor(0.8f,0.8f,0.8f)); + } + + Ypos += yl; + ConsoleText.Text = FText::Format(NSLOCTEXT("VRLogComponent", "ConsoleFormat", "{0}"), FText::FromString(*LoggedMessages[i]->Message)); + Canvas->DrawItem(ConsoleText, 0, Height - Ypos); + } + + OutputLogHistory.bIsDirty = false; +} + + + +#undef LOCTEXT_NAMESPACE +/* Bottom of File */ \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRPlayerStart.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRPlayerStart.cpp new file mode 100644 index 0000000..92e0119 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRPlayerStart.cpp @@ -0,0 +1,19 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRPlayerStart.h" + + +AVRPlayerStart::AVRPlayerStart(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + VRRootComp = CreateDefaultSubobject<USceneComponent>(TEXT("VRRootComp")); + VRRootComp->Mobility = EComponentMobility::Static; + RootComponent = VRRootComp; + + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (CapsuleComp && VRRootComp) + { + CapsuleComp->SetupAttachment(VRRootComp); + CapsuleComp->SetRelativeLocation(FVector(0.f,0.f,CapsuleComp->GetScaledCapsuleHalfHeight())); + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRRenderTargetManager.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRRenderTargetManager.cpp new file mode 100644 index 0000000..20998d2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Misc/VRRenderTargetManager.cpp @@ -0,0 +1,1792 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "Misc/VRRenderTargetManager.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerState.h" +#include "GameFramework/PlayerController.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Kismet/KismetMathLibrary.h" +#include "Kismet/KismetRenderingLibrary.h" +#include "Engine/CanvasRenderTarget2D.h" +#include "Engine/Canvas.h" +#include "GeomTools.h" +#include "Serialization/ArchiveSaveCompressedProxy.h" +#include "Serialization/ArchiveLoadCompressedProxy.h" +#include "Materials/Material.h" +#include "Net/UnrealNetwork.h" + +namespace RLE_Funcs +{ + enum RLE_Flags + { + RLE_CompressedByte = 1, + RLE_CompressedShort = 2, + RLE_Compressed24 = 3, + RLE_NotCompressedByte = 4, + RLE_NotCompressedShort = 5, + RLE_NotCompressed24 = 6, + //RLE_Empty = 3, + //RLE_AllSame = 4, + RLE_ContinueRunByte = 7, + RLE_ContinueRunShort = 8, + RLE_ContinueRun24 = 9 + }; + + template <typename DataType> + static bool RLEEncodeLine(TArray<DataType>* LineToEncode, TArray<uint8>* EncodedLine); + + template <typename DataType> + static bool RLEEncodeBuffer(DataType* BufferToEncode, uint32 EncodeLength, TArray<uint8>* EncodedLine); + + template <typename DataType> + static void RLEDecodeLine(TArray<uint8>* LineToDecode, TArray<DataType>* DecodedLine, bool bCompressed); + + template <typename DataType> + static void RLEDecodeLine(const uint8* LineToDecode, uint32 Num, TArray<DataType>* DecodedLine, bool bCompressed); + + static inline void RLEWriteContinueFlag(uint32 Count, uint8** loc); + + template <typename DataType> + static inline void RLEWriteRunFlag(uint32 Count, uint8** loc, TArray<DataType>& Data, bool bCompressed); +} + +UVRRenderTargetManager::UVRRenderTargetManager(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + + PollRelevancyTime = 0.1f; + DrawRate = 0.0333; + + bIsStoringImage = false; + RenderTarget = nullptr; + RenderTargetWidth = 100; + RenderTargetHeight = 100; + ClearColor = FColor::White; + + TextureBlobSize = 512; + MaxBytesPerSecondRate = 5000; + + bInitiallyReplicateTexture = false; + bIsLoadingTextureBuffer = false; + + OwnerIDCounter = 0; +} + +bool UVRRenderTargetManager::SendDrawOperations_Validate(const TArray<FRenderManagerOperation>& RenderOperationStoreList) +{ + return true; +} + +void UVRRenderTargetManager::SendDrawOperations_Implementation(const TArray<FRenderManagerOperation>& RenderOperationStoreList) +{ + if (GetNetMode() == ENetMode::NM_Client) + { + RenderOperationStore.Append(RenderOperationStoreList); + } + + DrawOperations(); +} + + +void UVRRenderTargetManager::AddLineDrawOperation(FVector2D Point1, FVector2D Point2, FColor Color, int32 Thickness) +{ + FRenderManagerOperation NewOperation; + NewOperation.OperationType = ERenderManagerOperationType::Op_LineDraw; + NewOperation.Color = Color; + NewOperation.P1 = Point1; + NewOperation.P2 = Point2; + NewOperation.Thickness = (uint32)Thickness; + + if (GetNetMode() < ENetMode::NM_Client) + RenderOperationStore.Add(NewOperation); + else + LocalRenderOperationStore.Add(NewOperation); + + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + // Send to server now +} + +void UVRRenderTargetManager::AddTextureDrawOperation(FVector2D Position, UTexture2D* TextureToDisplay) +{ + + if (!TextureToDisplay) + return; + + FRenderManagerOperation NewOperation; + NewOperation.OperationType = ERenderManagerOperationType::Op_TexDraw; + NewOperation.P1 = Position; + NewOperation.Texture = TextureToDisplay; + + if (GetNetMode() < ENetMode::NM_Client) + RenderOperationStore.Add(NewOperation); + else + LocalRenderOperationStore.Add(NewOperation); + + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + // Send to server now +} + +void UVRRenderTargetManager::AddMaterialTrianglesDrawOperation(TArray<FCanvasUVTri> Tris, UMaterial* Material) +{ + + if (!Tris.Num()) + return; + + FRenderManagerOperation NewOperation; + NewOperation.OperationType = ERenderManagerOperationType::Op_TriDraw; + NewOperation.Color = Tris[0].V0_Color.ToFColor(true); + + NewOperation.Tris.AddUninitialized(Tris.Num()); + int Counter = 0; + FRenderManagerTri RenderTri; + for (FCanvasUVTri Tri : Tris) + { + RenderTri.P1 = Tri.V0_Pos; + RenderTri.P2 = Tri.V1_Pos; + RenderTri.P3 = Tri.V2_Pos; + NewOperation.Tris[Counter++] = RenderTri; + } + + NewOperation.Material = Material; + + if (GetNetMode() < ENetMode::NM_Client) + RenderOperationStore.Add(NewOperation); + else + LocalRenderOperationStore.Add(NewOperation); + + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + // Send to server now +} + +void UVRRenderTargetManager::DrawOperation(UCanvas* Canvas, const FRenderManagerOperation& Operation) +{ + if (IsValid(LocalProxy) && LocalProxy->OwnersID == Operation.OwnerID) + { + return; + } + + switch (Operation.OperationType) + { + case ERenderManagerOperationType::Op_LineDraw: + { + FCanvasLineItem LineItem; + LineItem.Origin = FVector(Operation.P1.X, Operation.P1.Y, 0.f); + LineItem.EndPos = FVector(Operation.P2.X, Operation.P2.Y, 0.f); + LineItem.LineThickness = (float)Operation.Thickness; + LineItem.SetColor(Operation.Color.ReinterpretAsLinear()); + Canvas->DrawItem(LineItem); + }break; + case ERenderManagerOperationType::Op_TexDraw: + { + if (Operation.Texture && Operation.Texture->GetResource()) + { + //FTexture* RenderTextureResource = (RenderBase) ? RenderBase->Resource : GWhiteTexture; + FCanvasTileItem TileItem(Operation.P1, Operation.Texture->GetResource(), FVector2D(Operation.Texture->GetSizeX(), Operation.Texture->GetSizeY()), FVector2D(0, 0), FVector2D(1.f, 1.f), ClearColor); + TileItem.BlendMode = FCanvas::BlendToSimpleElementBlend(EBlendMode::BLEND_Translucent); + Canvas->DrawItem(TileItem); + } + }break; + case ERenderManagerOperationType::Op_TriDraw: + { + if (Operation.Tris.Num() && Operation.Material) + { + FCanvasTriangleItem TriangleItem(FVector2D::ZeroVector, FVector2D::ZeroVector, FVector2D::ZeroVector, NULL); + TriangleItem.MaterialRenderProxy = Operation.Material->GetRenderProxy(); + + FCanvasUVTri triStore; + triStore.V0_Color = Operation.Color; + triStore.V1_Color = Operation.Color; + triStore.V2_Color = Operation.Color; + + TriangleItem.TriangleList.Reset(Operation.Tris.Num()); + TriangleItem.TriangleList.AddUninitialized(Operation.Tris.Num()); + uint32 Counter = 0; + for (FRenderManagerTri Tri : Operation.Tris) + { + triStore.V0_Pos = Tri.P1; + triStore.V1_Pos = Tri.P2; + triStore.V2_Pos = Tri.P3; + TriangleItem.TriangleList[Counter++] = triStore; + } + + Canvas->DrawItem(TriangleItem); + } + }break; + } + +} + +void UVRRenderTargetManager::DrawPoll() +{ + if (!RenderOperationStore.Num() && !LocalRenderOperationStore.Num()) + { + GetWorld()->GetTimerManager().ClearTimer(DrawHandle); + return; + } + + if (GetNetMode() < ENetMode::NM_Client) + { + SendDrawOperations(RenderOperationStore); + } + else + { + if (LocalRenderOperationStore.Num()) + { + // Send operations to server + if (IsValid(LocalProxy)) + { + LocalProxy->SendLocalDrawOperations(LocalRenderOperationStore); + } + + RenderOperationStore.Append(LocalRenderOperationStore); + LocalRenderOperationStore.Empty(); + } + + DrawOperations(); + } +} + +void UVRRenderTargetManager::DrawOperations() +{ + + if (bIsLoadingTextureBuffer) + { + if (!DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(DrawHandle, this, &UVRRenderTargetManager::DrawPoll, DrawRate, true); + + return; + } + + if (GetNetMode() == ENetMode::NM_DedicatedServer) + { + RenderOperationStore.Empty(); + return; + } + + UWorld* World = GetWorld(); + + if (!World || !World->bBegunPlay) + return; + + // Reference to the Render Target resource + FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); + + if (!RenderTargetResource) + { + RenderOperationStore.Empty(); + return; + } + + // Retrieve a UCanvas form the world to avoid creating a new one each time + UCanvas* CanvasToUse = World->GetCanvasForDrawMaterialToRenderTarget(); + + // Creates a new FCanvas for rendering + FCanvas RenderCanvas( + RenderTargetResource, + nullptr, + World, + World->FeatureLevel); + + // Setup the canvas with the FCanvas reference + CanvasToUse->Init(RenderTarget->SizeX, RenderTarget->SizeY, nullptr, &RenderCanvas); + CanvasToUse->Update(); + + if (CanvasToUse) + { + for (const FRenderManagerOperation& opt : RenderOperationStore) + { + DrawOperation(CanvasToUse, opt); + } + + RenderOperationStore.Empty(); + + // Perform the drawing + RenderCanvas.Flush_GameThread(); + + // Cleanup the FCanvas reference, to delete it + CanvasToUse->Canvas = NULL; + } +} + + +ARenderTargetReplicationProxy::ARenderTargetReplicationProxy(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bOnlyRelevantToOwner = true; + bNetUseOwnerRelevancy = true; + bReplicates = true; + PrimaryActorTick.bCanEverTick = false; + SetReplicateMovement(false); + bWaitingForManager = false; +} + +void ARenderTargetReplicationProxy::OnRep_Manager() +{ + // If our manager is valid, save off a reference to ourselves to the local copy. + if (IsValid(OwningManager)) + { + OwningManager->LocalProxy = this; + + // If we loaded a texture before the manager loaded + if (bWaitingForManager) + { + OwningManager->bIsLoadingTextureBuffer = false; + OwningManager->RenderTargetStore = TextureStore; + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture, total byte count: %i"), OwningManager->RenderTargetStore.PackedData.Num())); + OwningManager->DeCompressRenderTarget2D(); + bWaitingForManager = false; + } + } +} + +bool ARenderTargetReplicationProxy::SendLocalDrawOperations_Validate(const TArray<FRenderManagerOperation>& LocalRenderOperationStoreList) +{ + return true; +} + +void ARenderTargetReplicationProxy::SendLocalDrawOperations_Implementation(const TArray<FRenderManagerOperation>& LocalRenderOperationStoreList) +{ + if (IsValid(OwningManager)) + { + OwningManager->RenderOperationStore.Append(LocalRenderOperationStoreList); + + // ID the render operations to the player that sent them in + if (APlayerController* OwningPlayer = Cast<APlayerController>(GetOwner())) + { + for (int i = (OwningManager->RenderOperationStore.Num() - LocalRenderOperationStoreList.Num()); i < OwningManager->RenderOperationStore.Num(); i++) + { + OwningManager->RenderOperationStore[i].OwnerID = OwnersID; + } + } + + if (!OwningManager->DrawHandle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(OwningManager->DrawHandle, OwningManager.Get(), &UVRRenderTargetManager::DrawPoll, OwningManager->DrawRate, true); + } +} + +void ARenderTargetReplicationProxy::ReceiveTexture_Implementation(const FBPVRReplicatedTextureStore& TextureData) +{ + if (IsValid(OwningManager)) + { + OwningManager->RenderTargetStore = TextureData; + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture, byte count: %i"), TextureData.PackedData.Num())); + OwningManager->DeCompressRenderTarget2D(); + } +} + +void ARenderTargetReplicationProxy::InitTextureSend_Implementation(int32 Width, int32 Height, int32 TotalDataCount, int32 BlobCount, EPixelFormat PixelFormat, bool bIsZipped/*, bool bIsJPG*/) +{ + TextureStore.Reset(); + TextureStore.PixelFormat = PixelFormat; + TextureStore.bIsZipped = bIsZipped; + //TextureStore.bJPG = bIsJPG; + TextureStore.Width = Width; + TextureStore.Height = Height; + + TextureStore.PackedData.Reset(TotalDataCount); + TextureStore.PackedData.AddUninitialized(TotalDataCount); + + BlobNum = BlobCount; + + if (IsValid(OwningManager)) + { + OwningManager->bIsLoadingTextureBuffer = true; + } + + Ack_InitTextureSend(TotalDataCount); +} + +bool ARenderTargetReplicationProxy::Ack_InitTextureSend_Validate(int32 TotalDataCount) +{ + return true; +} + +void ARenderTargetReplicationProxy::Ack_InitTextureSend_Implementation(int32 TotalDataCount) +{ + if (TotalDataCount == TextureStore.PackedData.Num()) + { + BlobNum = 0; + + // Calculate time offset to achieve our max bytes per second with the given blob size + float SendRate = 1.f / (MaxBytesPerSecondRate / (float)TextureBlobSize); + + GetWorld()->GetTimerManager().SetTimer(SendTimer_Handle, this, &ARenderTargetReplicationProxy::SendNextDataBlob, SendRate, true); + + // Start sending data blobs + //SendNextDataBlob(); + } +} + +void ARenderTargetReplicationProxy::SendInitMessage() +{ + int32 TotalBlobs = TextureStore.PackedData.Num() / TextureBlobSize + (TextureStore.PackedData.Num() % TextureBlobSize > 0 ? 1 : 0); + + InitTextureSend(TextureStore.Width, TextureStore.Height, TextureStore.PackedData.Num(), TotalBlobs, TextureStore.PixelFormat, TextureStore.bIsZipped/*, TextureStore.bJPG*/); + +} + +void ARenderTargetReplicationProxy::SendNextDataBlob() +{ + if (!IsValid(this) || !this->GetOwner() || !IsValid(this->GetOwner())) + { + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + BlobNum = 0; + if (SendTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(SendTimer_Handle); + + return; + } + + BlobNum++; + int32 TotalBlobs = TextureStore.PackedData.Num() / TextureBlobSize + (TextureStore.PackedData.Num() % TextureBlobSize > 0 ? 1 : 0); + + if (BlobNum <= TotalBlobs) + { + TArray<uint8> BlobStore; + int32 BlobLen = (BlobNum == TotalBlobs ? TextureStore.PackedData.Num() % TextureBlobSize : TextureBlobSize); + + + BlobStore.AddUninitialized(BlobLen); + uint8* MemLoc = TextureStore.PackedData.GetData(); + int32 MemCount = (BlobNum - 1) * TextureBlobSize; + MemLoc += MemCount; + + FMemory::Memcpy(BlobStore.GetData(), MemLoc, BlobLen); + + ReceiveTextureBlob(BlobStore, MemCount, BlobNum); + } + else + { + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + if (SendTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(SendTimer_Handle); + BlobNum = 0; + } +} + +//============================================================================= +void ARenderTargetReplicationProxy::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty >& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + + DOREPLIFETIME(ARenderTargetReplicationProxy, OwningManager); + DOREPLIFETIME(ARenderTargetReplicationProxy, OwnersID); +} + +void ARenderTargetReplicationProxy::ReceiveTextureBlob_Implementation(const TArray<uint8>& TextureBlob, int32 LocationInData, int32 BlobNumber) +{ + if (LocationInData + TextureBlob.Num() <= TextureStore.PackedData.Num()) + { + uint8* MemLoc = TextureStore.PackedData.GetData(); + MemLoc += LocationInData; + FMemory::Memcpy(MemLoc, TextureBlob.GetData(), TextureBlob.Num()); + + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture blob, byte count: %i"), TextureBlob.Num())); + } + + if (BlobNumber == BlobNum) + { + Ack_ReceiveTextureBlob(BlobNum); + + // We finished, unpack and display + if (IsValid(OwningManager)) + { + OwningManager->bIsLoadingTextureBuffer = false; + OwningManager->RenderTargetStore = TextureStore; + TextureStore.Reset(); + TextureStore.PackedData.Empty(); + TextureStore.UnpackedData.Empty(); + //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("Recieved Texture, total byte count: %i"), OwningManager->RenderTargetStore.PackedData.Num())); + OwningManager->DeCompressRenderTarget2D(); + } + else + { + bWaitingForManager = true; + } + } + +} + +bool ARenderTargetReplicationProxy::Ack_ReceiveTextureBlob_Validate(int32 BlobCount) +{ + return true; +} + +void ARenderTargetReplicationProxy::Ack_ReceiveTextureBlob_Implementation(int32 BlobCount) +{ + // Send next data blob + //SendNextDataBlob(); + +} + +void UVRRenderTargetManager::UpdateRelevancyMap() +{ + AActor* myOwner = GetOwner(); + + for (int i = NetRelevancyLog.Num() - 1; i >= 0; i--) + { + if (!IsValid(NetRelevancyLog[i].PC) || NetRelevancyLog[i].PC->IsLocalController() || !NetRelevancyLog[i].PC->GetPawn()) + { + NetRelevancyLog[i].ReplicationProxy->Destroy(); + NetRelevancyLog.RemoveAt(i); + } + else + { + if (APawn* pawn = NetRelevancyLog[i].PC->GetPawn()) + { + if (!myOwner->IsNetRelevantFor(NetRelevancyLog[i].PC.Get(), pawn, pawn->GetActorLocation())) + { + NetRelevancyLog[i].bIsRelevant = false; + NetRelevancyLog[i].bIsDirty = false; + //NetRelevancyLog.RemoveAt(i); + } + } + } + } + + + bool bHadDirtyActors = false; + + for (FConstPlayerControllerIterator PCIt = GetWorld()->GetPlayerControllerIterator(); PCIt; ++PCIt) + { + if (APlayerController* PC = PCIt->Get()) + { + if (PC->IsLocalController()) + continue; + + if (!PC->HasClientLoadedCurrentWorld()) + continue; + + if (APawn* pawn = PC->GetPawn()) + { + + if (myOwner->IsNetRelevantFor(PC, pawn, pawn->GetActorLocation())) + { + FClientRepData* RepData = NetRelevancyLog.FindByPredicate([PC](const FClientRepData& Other) + { + return Other.PC == PC; + }); + + if (!RepData) + { + FClientRepData ClientRepData; + + FTransform NewTransform = this->GetOwner()->GetActorTransform(); + ARenderTargetReplicationProxy* RenderProxy = GetWorld()->SpawnActorDeferred<ARenderTargetReplicationProxy>(ARenderTargetReplicationProxy::StaticClass(), NewTransform, PC); + if (RenderProxy) + { + RenderProxy->OwnersID = ++OwnerIDCounter; + RenderProxy->OwningManager = this; + RenderProxy->MaxBytesPerSecondRate = MaxBytesPerSecondRate; + RenderProxy->TextureBlobSize = TextureBlobSize; + UGameplayStatics::FinishSpawningActor(RenderProxy, NewTransform); + } + + if (RenderProxy) + { + RenderProxy->AttachToActor(this->GetOwner(), FAttachmentTransformRules::SnapToTargetIncludingScale); + + + ClientRepData.PC = PC; + ClientRepData.ReplicationProxy = RenderProxy; + ClientRepData.bIsRelevant = true; + ClientRepData.bIsDirty = true; + bHadDirtyActors = true; + NetRelevancyLog.Add(ClientRepData); + } + // Update this client with the new data + } + else + { + if (!RepData->bIsRelevant) + { + RepData->bIsRelevant = true; + RepData->bIsDirty = true; + bHadDirtyActors = true; + } + } + } + } + + } + } + + if (bHadDirtyActors && bInitiallyReplicateTexture && GetNetMode() != ENetMode::NM_DedicatedServer) + { + QueueImageStore(); + } +} + +bool UVRRenderTargetManager::DeCompressRenderTarget2D() +{ + if (!RenderTarget) + return false; + + RenderTargetStore.UnPackData(); + + + int32 Width = RenderTargetStore.Width; + int32 Height = RenderTargetStore.Height; + EPixelFormat PixelFormat = RenderTargetStore.PixelFormat; + uint8 PixelFormat8 = 0; + + TArray<FColor> FinalColorData; + FinalColorData.AddUninitialized(RenderTargetStore.UnpackedData.Num()); + + uint32 Counter = 0; + FColor ColorVal; + ColorVal.A = 0xFF; + for (uint16 CompColor : RenderTargetStore.UnpackedData) + { + //CompColor.FillTo(ColorVal); + ColorVal.R = CompColor << 3; + ColorVal.G = CompColor >> 5 << 2; + ColorVal.B = CompColor >> 11 << 3; + ColorVal.A = 0xFF; + FinalColorData[Counter++] = ColorVal; + } + + // Write this to a texture2d + UTexture2D* RenderBase = UTexture2D::CreateTransient(Width, Height, PF_R8G8B8A8);// RenderTargetStore.PixelFormat); + + // Switched to a Memcpy instead of byte by byte transer + uint8* MipData = (uint8*)RenderBase->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(MipData, (void*)FinalColorData.GetData(), FinalColorData.Num() * sizeof(FColor)); + RenderBase->GetPlatformData()->Mips[0].BulkData.Unlock(); + + //Setting some Parameters for the Texture and finally returning it + RenderBase->GetPlatformData()->SetNumSlices(1); + RenderBase->NeverStream = true; + RenderBase->SRGB = true; + //Avatar->CompressionSettings = TC_EditorIcon; + + RenderBase->UpdateResource(); + + /*uint32 size = sizeof(FColor); + + uint8* pData = new uint8[FinalColorData.Num() * sizeof(FColor)]; + FMemory::Memcpy(pData, (void*)FinalColorData.GetData(), FinalColorData.Num() * sizeof(FColor)); + + UTexture2D* TexturePtr = RenderBase; + const uint8* TextureData = pData; + ENQUEUE_RENDER_COMMAND(VRRenderTargetManager_FillTexture)( + [TexturePtr, TextureData](FRHICommandList& RHICmdList) + { + FUpdateTextureRegion2D region; + region.SrcX = 0; + region.SrcY = 0; + region.DestX = 0; + region.DestY = 0; + region.Width = TexturePtr->GetSizeX();// TEX_WIDTH; + region.Height = TexturePtr->GetSizeY();//TEX_HEIGHT; + + FTexture2DResource* resource = (FTexture2DResource*)TexturePtr->Resource; + RHIUpdateTexture2D(resource->GetTexture2DRHI(), 0, region, region.Width * GPixelFormats[TexturePtr->GetPixelFormat()].BlockBytes, TextureData); + delete[] TextureData; + });*/ + + + // Using this as it saves custom implementation + + UWorld* World = GetWorld(); + + // Reference to the Render Target resource + FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); + + // Retrieve a UCanvas form the world to avoid creating a new one each time + UCanvas* CanvasToUse = World->GetCanvasForDrawMaterialToRenderTarget(); + + // Creates a new FCanvas for rendering + FCanvas RenderCanvas( + RenderTargetResource, + nullptr, + World, + World->FeatureLevel); + + // Setup the canvas with the FCanvas reference + CanvasToUse->Init(RenderTarget->SizeX, RenderTarget->SizeY, nullptr, &RenderCanvas); + CanvasToUse->Update(); + + if (CanvasToUse) + { + FTexture* RenderTextureResource = (RenderBase) ? RenderBase->GetResource() : GWhiteTexture; + FCanvasTileItem TileItem(FVector2D(0, 0), RenderTextureResource, FVector2D(RenderTarget->SizeX, RenderTarget->SizeY), FVector2D(0, 0), FVector2D(1.f, 1.f), FLinearColor::White); + TileItem.BlendMode = FCanvas::BlendToSimpleElementBlend(EBlendMode::BLEND_Opaque); + CanvasToUse->DrawItem(TileItem); + + + // Perform the drawing + RenderCanvas.Flush_GameThread(); + + // Cleanup the FCanvas reference, to delete it + CanvasToUse->Canvas = NULL; + } + + RenderBase->ReleaseResource(); + RenderBase->MarkAsGarbage(); + + return true; +} + +void UVRRenderTargetManager::QueueImageStore() +{ + + if (!bInitiallyReplicateTexture || !RenderTarget || bIsStoringImage || GetNetMode() == ENetMode::NM_DedicatedServer) + { + return; + } + + bIsStoringImage = true; + + // Init new RenderRequest + FRenderDataStore* renderData = new FRenderDataStore(); + + // Get RenderContext + FTextureRenderTargetResource* renderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); + + if (!renderTargetResource) + return; + + renderData->Size2D = renderTargetResource->GetSizeXY(); + renderData->PixelFormat = RenderTarget->GetFormat(); + + struct FReadSurfaceContext { + FRenderTarget* SrcRenderTarget; + TArray<FColor>* OutData; + FIntRect Rect; + FReadSurfaceDataFlags Flags; + }; + + // Setup GPU command + FReadSurfaceContext readSurfaceContext = + { + renderTargetResource, + &(renderData->ColorData), + FIntRect(0,0,renderTargetResource->GetSizeXY().X, renderTargetResource->GetSizeXY().Y), + FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX) + }; + + ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)( + [readSurfaceContext](FRHICommandListImmediate& RHICmdList) { + RHICmdList.ReadSurfaceData( + readSurfaceContext.SrcRenderTarget->GetRenderTargetTexture(), + readSurfaceContext.Rect, + *readSurfaceContext.OutData, + readSurfaceContext.Flags + ); + }); + + // Notify new task in RenderQueue + RenderDataQueue.Enqueue(renderData); + + // Set RenderCommandFence + renderData->RenderFence.BeginFence(); + + this->SetComponentTickEnabled(true); +} + +void UVRRenderTargetManager::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Read pixels once RenderFence is completed + if (!bInitiallyReplicateTexture || RenderDataQueue.IsEmpty() || GetNetMode() == ENetMode::NM_DedicatedServer) + { + SetComponentTickEnabled(false); + } + else + { + // Peek the next RenderRequest from queue + FRenderDataStore* nextRenderData; + RenderDataQueue.Peek(nextRenderData); + + if (nextRenderData) + { + if (nextRenderData->RenderFence.IsFenceComplete()) + { + bIsStoringImage = false; + RenderTargetStore.Reset(); + uint32 SizeOfData = nextRenderData->ColorData.Num(); + + RenderTargetStore.UnpackedData.Reset(SizeOfData); + RenderTargetStore.UnpackedData.AddUninitialized(SizeOfData); + + uint16 ColorVal = 0; + uint32 Counter = 0; + + // Convert to 16bit color + for (FColor col : nextRenderData->ColorData) + { + ColorVal = (col.R >> 3) << 11 | (col.G >> 2) << 5 | (col.B >> 3); + RenderTargetStore.UnpackedData[Counter++] = ColorVal; + } + + FIntPoint Size2D = nextRenderData->Size2D; + RenderTargetStore.Width = Size2D.X; + RenderTargetStore.Height = Size2D.Y; + RenderTargetStore.PixelFormat = nextRenderData->PixelFormat; + RenderTargetStore.PackData(); + + +//#if WITH_PUSH_MODEL + //MARK_PROPERTY_DIRTY_FROM_NAME(UVRRenderTargetManager, RenderTargetStore, this); +//#endif + + + // Delete the first element from RenderQueue + RenderDataQueue.Pop(); + delete nextRenderData; + + for (int i = NetRelevancyLog.Num() - 1; i >= 0; i--) + { + if (NetRelevancyLog[i].bIsDirty && IsValid(NetRelevancyLog[i].PC) && !NetRelevancyLog[i].PC->IsLocalController()) + { + if (IsValid(NetRelevancyLog[i].ReplicationProxy)) + { + NetRelevancyLog[i].ReplicationProxy->TextureStore = RenderTargetStore; + NetRelevancyLog[i].ReplicationProxy->SendInitMessage(); + NetRelevancyLog[i].bIsDirty = false; + } + } + } + + + } + } + } + +} + +void UVRRenderTargetManager::BeginPlay() +{ + Super::BeginPlay(); + + InitRenderTarget(); + + if (/*bInitiallyReplicateTexture && */GetNetMode() < ENetMode::NM_Client) + GetWorld()->GetTimerManager().SetTimer(NetRelevancyTimer_Handle, this, &UVRRenderTargetManager::UpdateRelevancyMap, PollRelevancyTime, true); +} + +void UVRRenderTargetManager::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + FRenderDataStore* Store = nullptr; + while (!RenderDataQueue.IsEmpty()) + { + RenderDataQueue.Dequeue(Store); + + if (Store) + { + delete Store; + } + } + + if (GetNetMode() < ENetMode::NM_Client) + GetWorld()->GetTimerManager().ClearTimer(NetRelevancyTimer_Handle); + + if(DrawHandle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(DrawHandle); + + if (RenderTarget) + { + RenderTarget->ReleaseResource(); + RenderTarget = nullptr; + } + + for (FClientRepData& RepData : NetRelevancyLog) + { + RepData.PC = nullptr; + if (IsValid(RepData.ReplicationProxy.Get())) + { + RepData.ReplicationProxy->Destroy(); + } + + RepData.ReplicationProxy = nullptr; + } + +} + +void UVRRenderTargetManager::InitRenderTarget() +{ + if (this->GetNetMode() == ENetMode::NM_DedicatedServer) + { + return; // Dedicated servers cannot handle render targets + } + + UWorld* World = GetWorld(); + + if (RenderTargetWidth > 0 && RenderTargetHeight > 0 && World) + { + RenderTarget = NewObject<UCanvasRenderTarget2D>(this); + if (RenderTarget) + { + //NewCanvasRenderTarget->World = World; + RenderTarget->InitAutoFormat(RenderTargetWidth, RenderTargetHeight); + RenderTarget->ClearColor = ClearColor; + RenderTarget->bAutoGenerateMips = false; + RenderTarget->UpdateResourceImmediate(true); + } + else + { + RenderTarget = nullptr; + } + } + else + { + RenderTarget = nullptr; + } +} + + +bool UVRRenderTargetManager::GenerateTrisFromBoxPlaneIntersection(UPrimitiveComponent* PrimToBoxCheck, FTransform WorldTransformOfPlane, const FPlane& LocalProjectionPlane, FVector2D PlaneSize, FColor UVColor, TArray<FCanvasUVTri>& OutTris) +{ + + if (!PrimToBoxCheck) + return false; + + OutTris.Reset(); + + FBoxSphereBounds LocalBounds = PrimToBoxCheck->CalcLocalBounds(); + FVector Center = LocalBounds.Origin; + FVector Extent = LocalBounds.BoxExtent; + + // Transform into plane local space from our localspace + FTransform LocalTrans = PrimToBoxCheck->GetComponentTransform() * WorldTransformOfPlane.Inverse(); + + FVector BoxMin = Center - Extent; + FVector BoxMax = Center + Extent; + + TArray<FVector> PointList; + PointList.AddUninitialized(8); // 8 Is number of points on box + + PointList[0] = LocalTrans.TransformPosition(BoxMin); + PointList[1] = LocalTrans.TransformPosition(BoxMax); + PointList[2] = LocalTrans.TransformPosition(FVector(BoxMin.X, BoxMin.Y, BoxMax.Z)); + PointList[3] = LocalTrans.TransformPosition(FVector(BoxMin.X, BoxMax.Y, BoxMin.Z)); + PointList[4] = LocalTrans.TransformPosition(FVector(BoxMax.X, BoxMin.Y, BoxMin.Z)); + PointList[5] = LocalTrans.TransformPosition(FVector(BoxMin.X, BoxMax.Y, BoxMax.Z)); + PointList[6] = LocalTrans.TransformPosition(FVector(BoxMax.X, BoxMin.Y, BoxMax.Z)); + PointList[7] = LocalTrans.TransformPosition(FVector(BoxMax.X, BoxMax.Y, BoxMin.Z)); + + // List of edges to check, 12 total, 2 points per edge + int EdgeList[24] = + { + 0, 3, + 0, 4, + 0, 2, + 2, 5, + 2, 6, + 4, 7, + 4, 6, + 6, 1, + 1, 7, + 1, 5, + 5, 3, + 3, 7 + }; + + TArray<FVector2D> IntersectionPoints; + + FVector Intersection; + float Time; + + FVector2D HalfPlane = PlaneSize / 2.f; + FVector2D PtCenter; + FVector2D NewPt; + FVector PlanePoint; + int CenterCount = 0; + for (int i = 0; i < 24; i += 2) + { + + if (UKismetMathLibrary::LinePlaneIntersection(PointList[EdgeList[i]], PointList[EdgeList[i + 1]], LocalProjectionPlane, Time, Intersection)) + { + //DrawDebugSphere(GetWorld(), WorldTransformOfPlane.TransformPosition(Intersection), 2.f, 32.f, FColor::Black); + PlanePoint = Intersection; + + if (IsValid(RenderTarget)) + { + NewPt.X = ((PlanePoint.X + HalfPlane.X) / PlaneSize.X) * RenderTarget->SizeX; + NewPt.Y = ((PlanePoint.Y + HalfPlane.Y) / PlaneSize.Y) * RenderTarget->SizeY; + } + else + { + NewPt.X = ((PlanePoint.X + HalfPlane.X) / PlaneSize.X) * RenderTargetWidth; + NewPt.Y = ((PlanePoint.Y + HalfPlane.Y) / PlaneSize.Y) * RenderTargetHeight; + } + + IntersectionPoints.Add(NewPt); + PtCenter += NewPt; + CenterCount++; + } + } + + if (IntersectionPoints.Num() <= 2) + { + return false; + } + + // Get our center value + PtCenter /= CenterCount; + + // Sort the points clockwise + struct FPointSortCompare + { + public: + FVector2D CenterPoint; + FPointSortCompare(const FVector2D& InCenterPoint) + : CenterPoint(InCenterPoint) + { + + } + + FORCEINLINE bool operator()(const FVector2D& A, const FVector2D& B) const + { + if (A.Y - CenterPoint.X >= 0 && B.X - CenterPoint.X < 0) + return true; + if (A.X - CenterPoint.X < 0 && B.X - CenterPoint.X >= 0) + return false; + if (A.X - CenterPoint.X == 0 && B.X - CenterPoint.X == 0) { + if (A.Y - CenterPoint.Y >= 0 || B.Y - CenterPoint.Y >= 0) + return A.Y > B.Y; + return B.Y > A.Y; + } + + // compute the cross product of vectors (center -> a) x (center -> b) + int det = (A.X - CenterPoint.X) * (B.Y - CenterPoint.Y) - (B.X - CenterPoint.X) * (A.Y - CenterPoint.Y); + if (det < 0) + return true; + if (det > 0) + return false; + + // points a and b are on the same line from the center + // check which point is closer to the center + int d1 = (A.X - CenterPoint.X) * (A.X - CenterPoint.X) + (A.Y - CenterPoint.Y) * (A.Y - CenterPoint.Y); + int d2 = (B.X - CenterPoint.X) * (B.X - CenterPoint.X) + (B.Y - CenterPoint.Y) * (B.Y - CenterPoint.Y); + return d1 > d2; + } + }; + + IntersectionPoints.Sort(FPointSortCompare(PtCenter)); + + FCanvasUVTri Tri; + Tri.V0_Color = UVColor; + Tri.V1_Color = UVColor; + Tri.V2_Color = UVColor; + + OutTris.Reserve(IntersectionPoints.Num() - 2); + + // Now that we have our sorted list, we can generate a tri map from it, just doing a Fan from first to last + for (int i = 1; i < IntersectionPoints.Num() - 1; i++) + { + Tri.V0_Pos = IntersectionPoints[0]; + Tri.V1_Pos = IntersectionPoints[i]; + Tri.V2_Pos = IntersectionPoints[i + 1]; + + OutTris.Add(Tri); + } + + return true; +} + +void FBPVRReplicatedTextureStore::PackData() +{ + if (UnpackedData.Num() > 0) + { + TArray<uint8> TmpPacked; + RLE_Funcs::RLEEncodeBuffer<uint16>(UnpackedData.GetData(), UnpackedData.Num(), &TmpPacked); + UnpackedData.Reset(); + + /*if (TmpPacked.Num() > 30000) + { + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")); + TSharedPtr<IImageWrapper> imageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG); + + imageWrapper->SetRaw(UnpackedData.GetData(), UnpackedData.Num(), Width, Height, ERGBFormat::RGBA, 8); + const TArray64<uint8>& ImgData = imageWrapper->GetCompressed(1); + + + PackedData.Reset(ImgData.Num()); + PackedData.AddUninitialized(ImgData.Num()); + FMemory::Memcpy(PackedData.GetData(), ImgData.GetData(), ImgData.Num()); + bJPG = true; + bIsZipped = false; + } + else */if (TmpPacked.Num() > 512) + { + FArchiveSaveCompressedProxy Compressor(PackedData, NAME_Zlib, COMPRESS_BiasSpeed); + Compressor << TmpPacked; + Compressor.Flush(); + bIsZipped = true; + //bJPG = false; + } + else + { + PackedData = TmpPacked; + bIsZipped = false; + //bJPG = false; + } + } +} + + +void FBPVRReplicatedTextureStore::UnPackData() +{ + if (PackedData.Num() > 0) + { + + /*if (bJPG) + { + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")); + TSharedPtr<IImageWrapper> imageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG); + + + if (imageWrapper.IsValid() && (PackedData.Num() > 0) && imageWrapper->SetCompressed(PackedData.GetData(), PackedData.Num())) + { + Width = imageWrapper->GetWidth(); + Height = imageWrapper->GetHeight(); + + if (imageWrapper->GetRaw(ERGBFormat::BGRA, 8, UnpackedData)) + { + //bSucceeded = true; + } + } + } + else */if (bIsZipped) + { + TArray<uint8> RLEEncodedData; + FArchiveLoadCompressedProxy DataArchive(PackedData, NAME_Zlib); + DataArchive << RLEEncodedData; + RLE_Funcs::RLEDecodeLine<uint16>(&RLEEncodedData, &UnpackedData, true); + } + else + { + RLE_Funcs::RLEDecodeLine<uint16>(&PackedData, &UnpackedData, true); + } + + PackedData.Reset(); + } +} + +/** Network serialization */ +// Doing a custom NetSerialize here because this is sent via RPCs and should change on every update +bool FBPVRReplicatedTextureStore::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + //Ar.SerializeBits(&bIsJPG, 1); + Ar.SerializeBits(&bIsZipped, 1); + Ar.SerializeIntPacked(Width); + Ar.SerializeIntPacked(Height); + Ar.SerializeBits(&PixelFormat, 8); + + Ar << PackedData; + + //uint32 UncompressedBufferSize = PackedData.Num(); + + return bOutSuccess; +} + + +// BEGIN RLE FUNCTIONS /// + +// Followed by a count of the following voxels +template <typename DataType> +void RLE_Funcs::RLEDecodeLine(TArray<uint8>* LineToDecode, TArray<DataType>* DecodedLine, bool bCompressed) +{ + if (!LineToDecode || !DecodedLine) + return; + + RLEDecodeLine(LineToDecode->GetData(), LineToDecode->Num(), DecodedLine, bCompressed); +} + +// Followed by a count of the following voxels +template <typename DataType> +void RLE_Funcs::RLEDecodeLine(const uint8* LineToDecode, uint32 Num, TArray<DataType>* DecodedLine, bool bCompressed) +{ + if (!bCompressed) + { + DecodedLine->Empty(Num / sizeof(DataType)); + DecodedLine->AddUninitialized(Num / sizeof(DataType)); + FMemory::Memcpy(DecodedLine->GetData(), LineToDecode, Num); + return; + } + + const uint8* StartLoc = LineToDecode; + const uint8* EndLoc = StartLoc + Num; + uint8 incr = sizeof(DataType); + + DataType ValToWrite = *((DataType*)LineToDecode); // This is just to prevent stupid compiler warnings without disabling them + + DecodedLine->Empty(); + + uint8 RLE_FLAG; + uint32 Length32; + uint32 Length8; + uint32 Length16; + int origLoc; + + for (const uint8* loc = StartLoc; loc < EndLoc;) + { + RLE_FLAG = *loc >> 4; // Get the RLE flag from the first 4 bits of the first byte + + switch (RLE_FLAG) + { + case RLE_Flags::RLE_CompressedByte: + { + Length8 = (*loc & ~0xF0) + 1; + loc++; + ValToWrite = *((DataType*)loc); + loc += incr; + + origLoc = DecodedLine->AddUninitialized(Length8); + + for (uint32 i = origLoc; i < origLoc + Length8; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_CompressedShort: + { + Length16 = (((uint16)(*loc & ~0xF0)) << 8 | (*(loc + 1))) + 1; + loc += 2; + ValToWrite = *((DataType*)loc); + loc += incr; + + origLoc = DecodedLine->AddUninitialized(Length16); + + for (uint32 i = origLoc; i < origLoc + Length16; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_Compressed24: + { + Length32 = (((uint32)(*loc & ~0xF0)) << 16 | ((uint32)(*(loc + 1))) << 8 | (uint32)(*(loc + 2))) + 1; + loc += 3; + ValToWrite = *((DataType*)loc); + loc += incr; + + origLoc = DecodedLine->AddUninitialized(Length32); + + for (uint32 i = origLoc; i < origLoc + Length32; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + + case RLE_Flags::RLE_NotCompressedByte: + { + Length8 = (*loc & ~0xF0) + 1; + loc++; + + origLoc = DecodedLine->AddUninitialized(Length8); + + for (uint32 i = origLoc; i < origLoc + Length8; i++) + { + (*DecodedLine)[i] = *((DataType*)loc); + loc += incr; + } + + }break; + case RLE_Flags::RLE_NotCompressedShort: + { + Length16 = (((uint16)(*loc & ~0xF0)) << 8 | (*(loc + 1))) + 1; + loc += 2; + + origLoc = DecodedLine->AddUninitialized(Length16); + + for (uint32 i = origLoc; i < origLoc + Length16; i++) + { + (*DecodedLine)[i] = *((DataType*)loc); + loc += incr; + } + + }break; + case RLE_Flags::RLE_NotCompressed24: + { + Length32 = (((uint32)(*loc & ~0xF0)) << 16 | ((uint32)(*(loc + 1))) << 8 | ((uint32)(*(loc + 2)))) + 1; + loc += 3; + + origLoc = DecodedLine->AddUninitialized(Length32); + + for (uint32 i = origLoc; i < origLoc + Length32; i++) + { + (*DecodedLine)[i] = *((DataType*)loc); + loc += incr; + } + + }break; + + case RLE_Flags::RLE_ContinueRunByte: + { + Length8 = (*loc & ~0xF0) + 1; + loc++; + + origLoc = DecodedLine->AddUninitialized(Length8); + + for (uint32 i = origLoc; i < origLoc + Length8; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_ContinueRunShort: + { + Length16 = (((uint16)(*loc & ~0xF0)) << 8 | (*(loc + 1))) + 1; + loc += 2; + + origLoc = DecodedLine->AddUninitialized(Length16); + + for (uint32 i = origLoc; i < origLoc + Length16; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + case RLE_Flags::RLE_ContinueRun24: + { + Length32 = (((uint32)(*loc & ~0xF0)) << 16 | ((uint32)(*(loc + 1))) << 8 | (*(loc + 2))) + 1; + loc += 3; + + origLoc = DecodedLine->AddUninitialized(Length32); + + for (uint32 i = origLoc; i < origLoc + Length32; i++) + { + (*DecodedLine)[i] = ValToWrite; + } + + }break; + + } + } +} + +template <typename DataType> +bool RLE_Funcs::RLEEncodeLine(TArray<DataType>* LineToEncode, TArray<uint8>* EncodedLine) +{ + return RLEEncodeBuffer<DataType>(LineToEncode->GetData(), LineToEncode->Num(), EncodedLine); +} + +void RLE_Funcs::RLEWriteContinueFlag(uint32 count, uint8** loc) +{ + if (count <= 16) + { + **loc = (((uint8)RLE_Flags::RLE_ContinueRunByte << 4) | ((uint8)count - 1)); + (*loc)++; + } + else if (count <= 4096) + { + uint16 val = ((((uint16)RLE_Flags::RLE_ContinueRunShort) << 12) | ((uint16)count - 1)); + **loc = val >> 8; + (*loc)++; + **loc = (uint8)val; + (*loc)++; + } + else + { + uint32 val = ((((uint32)RLE_Flags::RLE_ContinueRun24) << 20) | ((uint32)count - 1)); + **loc = (uint8)(val >> 16); + (*loc)++; + **loc = (uint8)(val >> 8); + (*loc)++; + **loc = (uint8)val; + (*loc)++; + } +} + +template <typename DataType> +void RLE_Funcs::RLEWriteRunFlag(uint32 count, uint8** loc, TArray<DataType>& Data, bool bCompressed) +{ + + if (count <= 16) + { + uint8 val; + if (bCompressed) + val = ((((uint8)RLE_Flags::RLE_CompressedByte) << 4) | ((uint8)count - 1)); + else + val = ((((uint8)RLE_Flags::RLE_NotCompressedByte) << 4) | ((uint8)count - 1)); + + **loc = val; + (*loc)++; + } + else if (count <= 4096) + { + uint16 val; + if (bCompressed) + val = ((((uint16)RLE_Flags::RLE_CompressedShort) << 12) | ((uint16)count - 1)); + else + val = ((((uint16)RLE_Flags::RLE_NotCompressedShort) << 12) | ((uint16)count - 1)); + + **loc = (uint8)(val >> 8); + (*loc)++; + **loc = (uint8)val; + (*loc)++; + } + else + { + uint32 val; + if (bCompressed) + val = ((((uint32)RLE_Flags::RLE_Compressed24) << 20) | ((uint32)count - 1)); + else + val = ((((uint32)RLE_Flags::RLE_NotCompressed24) << 20) | ((uint32)count - 1)); + + **loc = (uint8)(val >> 16); + (*loc)++; + **loc = (uint8)(val >> 8); + (*loc)++; + **loc = (uint8)(val); + (*loc)++; + } + + FMemory::Memcpy(*loc, Data.GetData(), Data.Num() * sizeof(DataType)); + *loc += Data.Num() * sizeof(DataType); + Data.Empty(256); +} + +template <typename DataType> +bool RLE_Funcs::RLEEncodeBuffer(DataType* BufferToEncode, uint32 EncodeLength, TArray<uint8>* EncodedLine) +{ + uint32 OrigNum = EncodeLength;//LineToEncode->Num(); + uint8 incr = sizeof(DataType); + uint32 MAX_COUNT = 1048576; // Max of 2.5 bytes as 0.5 bytes is used for control flags + + EncodedLine->Empty((OrigNum * sizeof(DataType)) + (OrigNum * (sizeof(short)))); + // Reserve enough memory to account for a perfectly bad situation (original size + 3 bytes per max array value) + // Remove the remaining later with RemoveAt() and a count + EncodedLine->AddUninitialized((OrigNum * sizeof(DataType)) + ((OrigNum / MAX_COUNT * 3))); + + DataType* First = BufferToEncode;// LineToEncode->GetData(); + DataType Last; + uint32 RunCount = 0; + + uint8* loc = EncodedLine->GetData(); + //uint8 * countLoc = NULL; + + bool bInRun = false; + bool bWroteStart = false; + bool bContinueRun = false; + + TArray<DataType> TempBuffer; + TempBuffer.Reserve(256); + uint32 TempCount = 0; + + Last = *First; + First++; + + for (uint32 i = 0; i < OrigNum - 1; i++, First++) + { + if (Last == *First) + { + if (bWroteStart && !bInRun) + { + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + bWroteStart = false; + } + + if (bInRun && /**countLoc*/TempCount < MAX_COUNT) + { + TempCount++; + + if (TempCount == MAX_COUNT) + { + // Write run byte + if (bContinueRun) + { + RLE_Funcs::RLEWriteContinueFlag(TempCount, &loc); + } + else + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + + bContinueRun = true; + TempCount = 0; + } + } + else + { + bInRun = true; + bWroteStart = false; + bContinueRun = false; + + TempBuffer.Add(Last); + TempCount = 1; + } + + // Begin Run Here + } + else if (bInRun) + { + bInRun = false; + + if (bContinueRun) + { + TempCount++; + RLE_Funcs::RLEWriteContinueFlag(TempCount, &loc); + } + else + { + TempCount++; + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + } + + bContinueRun = false; + } + else + { + if (bWroteStart && TempCount/**countLoc*/ < MAX_COUNT) + { + TempCount++; + TempBuffer.Add(Last); + } + else if (bWroteStart && TempCount == MAX_COUNT) + { + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + + bWroteStart = true; + TempBuffer.Add(Last); + TempCount = 1; + } + else + { + TempBuffer.Add(Last); + TempCount = 1; + //*countLoc = 1; + + bWroteStart = true; + } + } + + Last = *First; + } + + // Finish last num + if (bInRun) + { + if (TempCount <= MAX_COUNT) + { + if (TempCount == MAX_COUNT) + { + // Write run byte + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + bContinueRun = true; + } + + if (bContinueRun) + { + TempCount++; + RLE_Funcs::RLEWriteContinueFlag(TempCount, &loc); + } + else + { + TempCount++; + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, true); + } + } + + // Begin Run Here + } + else + { + if (bWroteStart && TempCount/**countLoc*/ <= MAX_COUNT) + { + if (TempCount/**countLoc*/ == MAX_COUNT) + { + // Write run byte + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + TempCount = 0; + } + + TempCount++; + TempBuffer.Add(Last); + RLE_Funcs::RLEWriteRunFlag(TempCount, &loc, TempBuffer, false); + } + } + + // Resize the out array to fit compressed contents + uint32 Wrote = loc - EncodedLine->GetData(); + EncodedLine->RemoveAt(Wrote, EncodedLine->Num() - Wrote, true); + + // If the compression performed worse than the original file size, throw the results array and use the original instead. + // This will almost never happen with voxels but can so should be accounted for. + + return true; + // Skipping non compressed for now, the overhead is so low that it isn't worth supporting since the last revision + if (Wrote > OrigNum * incr) + { + EncodedLine->Empty(OrigNum * incr); + EncodedLine->AddUninitialized(OrigNum * incr); + FMemory::Memcpy(EncodedLine->GetData(), BufferToEncode/*LineToEncode->GetData()*/, OrigNum * incr); + return false; // Return that there was no compression, so the decoder can receive it later + } + else + return true; +} + +template<int32 ScaleFactor, int32 MaxBitsPerComponent> +bool WritePackedVector2D(FVector2D Value, FArchive& Ar) // Note Value is intended to not be a reference since we are scaling it before serializing! +{ + check(Ar.IsSaving()); + + // Scale vector by quant factor first + Value *= ScaleFactor; + + // Nan Check + if (Value.ContainsNaN()) + { + logOrEnsureNanError(TEXT("WritePackedVector2D: Value contains NaN, clearing for safety.")); + FVector2D Dummy(0, 0); + WritePackedVector2D<ScaleFactor, MaxBitsPerComponent>(Dummy, Ar); + return false; + } + + float MinV = -1073741824.0f; + float MaxV = 1073741760.0f; + + // Some platforms have RoundToInt implementations that essentially reduces the allowed inputs to 2^31. + const FVector2D ClampedValue = FVector2D(FMath::Clamp(Value.X, MinV, MaxV), FMath::Clamp(Value.Y, MinV, MaxV)); + bool bClamp = ClampedValue != Value; + + // Do basically FVector::SerializeCompressed + int32 IntX = FMath::RoundToInt(ClampedValue.X); + int32 IntY = FMath::RoundToInt(ClampedValue.Y); + + uint32 Bits = FMath::Clamp<uint32>(FMath::CeilLogTwo(1 + FMath::Max(FMath::Abs(IntX), FMath::Abs(IntY))), 1, MaxBitsPerComponent) - 1; + + // Serialize how many bits each component will have + Ar.SerializeInt(Bits, MaxBitsPerComponent); + + int32 Bias = 1 << (Bits + 1); + uint32 Max = 1 << (Bits + 2); + uint32 DX = IntX + Bias; + uint32 DY = IntY + Bias; + + if (DX >= Max) { bClamp = true; DX = static_cast<int32>(DX) > 0 ? Max - 1 : 0; } + if (DY >= Max) { bClamp = true; DY = static_cast<int32>(DY) > 0 ? Max - 1 : 0; } + + Ar.SerializeInt(DX, Max); + Ar.SerializeInt(DY, Max); + + return !bClamp; +} + +template<uint32 ScaleFactor, int32 MaxBitsPerComponent> +bool ReadPackedVector2D(FVector2D& Value, FArchive& Ar) +{ + uint32 Bits = 0; + + // Serialize how many bits each component will have + Ar.SerializeInt(Bits, MaxBitsPerComponent); + + int32 Bias = 1 << (Bits + 1); + uint32 Max = 1 << (Bits + 2); + uint32 DX = 0; + uint32 DY = 0; + + Ar.SerializeInt(DX, Max); + Ar.SerializeInt(DY, Max); + + + float fact = (float)ScaleFactor; + + Value.X = (float)(static_cast<int32>(DX) - Bias) / fact; + Value.Y = (float)(static_cast<int32>(DY) - Bias) / fact; + + return true; +} + +bool FRenderManagerOperation::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + + Ar.SerializeIntPacked(OwnerID); + Ar.SerializeBits(&OperationType, 3); + + switch (OperationType) + { + case ERenderManagerOperationType::Op_LineDraw: + { + Ar << Color; + Ar.SerializeIntPacked(Thickness); + + if (Ar.IsSaving()) + { + bOutSuccess &= WritePackedVector2D<1, 20>(P1, Ar); + bOutSuccess &= WritePackedVector2D<1, 20>(P2, Ar); + } + else + { + ReadPackedVector2D<1, 20>(P1, Ar); + ReadPackedVector2D<1, 20>(P2, Ar); + } + }break; + case ERenderManagerOperationType::Op_TexDraw: + { + Ar << Texture; + + if (Ar.IsSaving()) + { + bOutSuccess &= WritePackedVector2D<1, 20>(P1, Ar); + } + else + { + ReadPackedVector2D<1, 20>(P1, Ar); + } + }break; + case ERenderManagerOperationType::Op_TriDraw: + { + Ar << Color; + Ar << Material; + + uint32 ArrayCt = Tris.Num(); + Ar.SerializeIntPacked(ArrayCt); + + if (Ar.IsLoading()) + { + Tris.Reset(ArrayCt); + Tris.AddUninitialized(ArrayCt); + + FRenderManagerTri TriTemp; + for (uint32 i = 0; i < ArrayCt; ++i) + { + ReadPackedVector2D<1, 20>(TriTemp.P1, Ar); + ReadPackedVector2D<1, 20>(TriTemp.P2, Ar); + ReadPackedVector2D<1, 20>(TriTemp.P3, Ar); + Tris[i] = TriTemp; + } + } + else + { + for (uint32 i = 0; i < ArrayCt; ++i) + { + WritePackedVector2D<1, 20>(Tris[i].P1, Ar); + WritePackedVector2D<1, 20>(Tris[i].P2, Ar); + WritePackedVector2D<1, 20>(Tris[i].P3, Ar); + } + } + + }break; + } + + return bOutSuccess; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ParentRelativeAttachmentComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ParentRelativeAttachmentComponent.cpp new file mode 100644 index 0000000..5d0444e --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ParentRelativeAttachmentComponent.cpp @@ -0,0 +1,174 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "ParentRelativeAttachmentComponent.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.h" +#include "IXRTrackingSystem.h" +#include "VRRootComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" +//#include "VRSimpleCharacter.h" +//#include "VRCharacter.h" + + +UParentRelativeAttachmentComponent::UParentRelativeAttachmentComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + // Let it sit in DuringPhysics like is the default + //PrimaryComponentTick.TickGroup = TG_PrePhysics; + + bWantsInitializeComponent = true; + + SetRelativeScale3D(FVector(1.0f, 1.0f, 1.0f)); + SetRelativeLocation(FVector::ZeroVector); + YawTolerance = 0.0f; + bOffsetByHMD = false; + + bLerpTransition = true; + LerpSpeed = 100.0f; + LastLerpVal = 0.0f; + LerpTarget = 0.0f; + bWasSetOnce = false; + + LeftControllerTrans = FTransform::Identity; + RightControllerTrans = FTransform::Identity; + + bIgnoreRotationFromParent = false; + bUpdateInCharacterMovement = true; + + bUseFeetLocation = false; + CustomOffset = FVector::ZeroVector; + + //YawRotationMethod = EVR_PRC_RotationMethod::PRC_ROT_HMD; +} + +void UParentRelativeAttachmentComponent::InitializeComponent() +{ + Super::InitializeComponent(); + + // Update our tracking + if (!bUseFeetLocation && IsValid(AttachChar)) // New case to early out and with less calculations + { + SetRelativeTransform(AttachChar->VRReplicatedCamera->GetRelativeTransform()); + } + +} + +void UParentRelativeAttachmentComponent::OnAttachmentChanged() +{ + if (AVRCharacter* CharacterOwner = Cast<AVRCharacter>(this->GetOwner())) + { + AttachChar = CharacterOwner; + } + else + { + AttachChar = nullptr; + } + + if (AVRBaseCharacter * BaseCharacterOwner = Cast<AVRBaseCharacter>(this->GetOwner())) + { + AttachBaseChar = BaseCharacterOwner; + } + else + { + AttachBaseChar = nullptr; + } + + Super::OnAttachmentChanged(); +} + +void UParentRelativeAttachmentComponent::UpdateTracking(float DeltaTime) +{ + if (OptionalWaistTrackingParent.IsValid()) + { + //#TODO: bOffsetByHMD not supported with this currently, fix it, need to check for both camera and HMD + FTransform TrackedParentWaist = IVRTrackedParentInterface::Default_GetWaistOrientationAndPosition(OptionalWaistTrackingParent); + + if (bUseFeetLocation) + { + TrackedParentWaist.SetTranslation(TrackedParentWaist.GetTranslation() * FVector(1.0f, 1.0f, 0.0f)); + + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(TrackedParentWaist.Rotator()); + + TrackedParentWaist.SetRotation(GetCalculatedRotation(InverseRot, DeltaTime)); + } + } + + TrackedParentWaist.AddToTranslation(CustomOffset); + SetRelativeTransform(TrackedParentWaist); + + } + else if (IsValid(AttachChar)) // New case to early out and with less calculations + { + SetRelativeRotAndLoc(AttachChar->VRRootReference->curCameraLoc, AttachChar->VRRootReference->StoredCameraRotOffset, DeltaTime); + } + else if (IsLocallyControlled() && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed()) + { + FQuat curRot; + FVector curCameraLoc; + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curCameraLoc)) + { + if (bOffsetByHMD) + { + curCameraLoc.X = 0; + curCameraLoc.Y = 0; + } + + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curRot.Rotator()); + SetRelativeRotAndLoc(curCameraLoc, InverseRot, DeltaTime); + } + else + SetRelativeRotAndLoc(curCameraLoc, FRotator::ZeroRotator, DeltaTime); + } + } + else if (IsValid(AttachBaseChar)) + { + if (AttachBaseChar->VRReplicatedCamera) + { + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw(AttachBaseChar->VRReplicatedCamera->GetRelativeRotation()); + SetRelativeRotAndLoc(AttachBaseChar->VRReplicatedCamera->GetRelativeLocation(), InverseRot, DeltaTime); + } + else + SetRelativeRotAndLoc(AttachBaseChar->VRReplicatedCamera->GetRelativeLocation(), FRotator::ZeroRotator, DeltaTime); + } + } + else if (AActor* owner = this->GetOwner()) + { + if (UCameraComponent* CameraOwner = owner->FindComponentByClass<UCameraComponent>()) + { + if (!bIgnoreRotationFromParent) + { + FRotator InverseRot = UVRExpansionFunctionLibrary::GetHMDPureYaw(CameraOwner->GetRelativeRotation()); + SetRelativeRotAndLoc(CameraOwner->GetRelativeLocation(), InverseRot, DeltaTime); + } + else + SetRelativeRotAndLoc(CameraOwner->GetRelativeLocation(), FRotator::ZeroRotator, DeltaTime); + } + } +} + +void UParentRelativeAttachmentComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!bUpdateInCharacterMovement || !IsValid(AttachChar)) + { + UpdateTracking(DeltaTime); + } + else + { + UCharacterMovementComponent * CharMove = AttachChar->GetCharacterMovement(); + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive()) + { + // Our character movement isn't handling our updates, lets do it ourself. + UpdateTracking(DeltaTime); + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ReplicatedVRCameraComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ReplicatedVRCameraComponent.cpp new file mode 100644 index 0000000..d336ab4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/ReplicatedVRCameraComponent.cpp @@ -0,0 +1,399 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "ReplicatedVRCameraComponent.h" +#include "Net/UnrealNetwork.h" +//#include "Engine/Engine.h" +#include "Net/UnrealNetwork.h" +#include "VRBaseCharacter.h" +#include "IXRTrackingSystem.h" +#include "IXRCamera.h" +#include "Rendering/MotionVectorSimulation.h" +#include "VRBaseCharacter.h" +#include "IHeadMountedDisplay.h" + + +UReplicatedVRCameraComponent::UReplicatedVRCameraComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + //PrimaryComponentTick.TickGroup = TG_PrePhysics; + + SetIsReplicatedByDefault(true); + SetRelativeScale3D(FVector(1.0f, 1.0f, 1.0f)); + + // Default 100 htz update rate, same as the 100htz update rate of rep_notify, will be capped to 90/45 though because of vsync on HMD + //bReplicateTransform = true; + NetUpdateRate = 100.0f; // 100 htz is default + NetUpdateCount = 0.0f; + + bUsePawnControlRotation = false; + bAutoSetLockToHmd = true; + bScaleTracking = false; + TrackingScaler = FVector(1.0f); + bOffsetByHMD = false; + bLimitMinHeight = false; + MinimumHeightAllowed = 0.0f; + bLimitMaxHeight = false; + MaxHeightAllowed = 300.f; + bLimitBounds = false; + // Just shy of 20' distance from the center of tracked space + MaximumTrackedBounds = 1028; + + bSetPositionDuringTick = false; + bSmoothReplicatedMotion = false; + bLerpingPosition = false; + bReppedOnce = false; + + OverrideSendTransform = nullptr; + + LastRelativePosition = FTransform::Identity; + bSampleVelocityInWorldSpace = false; + bHadValidFirstVelocity = false; + + //bUseVRNeckOffset = true; + //VRNeckOffset = FTransform(FRotator::ZeroRotator, FVector(15.0f,0,0), FVector(1.0f)); + +} + + +//============================================================================= +void UReplicatedVRCameraComponent::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + + // I am skipping the Scene component replication here + // Generally components aren't set to replicate anyway and I need it to NOT pass the Relative position through the network + // There isn't much in the scene component to replicate anyway + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeLocation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeRotation); + DISABLE_REPLICATED_PRIVATE_PROPERTY(USceneComponent, RelativeScale3D); + + // Skipping the owner with this as the owner will use the location directly + DOREPLIFETIME_CONDITION(UReplicatedVRCameraComponent, ReplicatedCameraTransform, COND_SkipOwner); + DOREPLIFETIME(UReplicatedVRCameraComponent, NetUpdateRate); + DOREPLIFETIME(UReplicatedVRCameraComponent, bSmoothReplicatedMotion); + //DOREPLIFETIME(UReplicatedVRCameraComponent, bReplicateTransform); +} + +// Just skipping this, it generates warnings for attached meshes when using this method of denying transform replication +/*void UReplicatedVRCameraComponent::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + // Don't ever replicate these, they are getting replaced by my custom send anyway + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeLocation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeRotation, false); + DOREPLIFETIME_ACTIVE_OVERRIDE(USceneComponent, RelativeScale3D, false); +}*/ + +void UReplicatedVRCameraComponent::Server_SendCameraTransform_Implementation(FBPVRComponentPosRep NewTransform) +{ + // Store new transform and trigger OnRep_Function + ReplicatedCameraTransform = NewTransform; + + // Don't call on rep on the server if the server controls this controller + if (!bHasAuthority) + { + OnRep_ReplicatedCameraTransform(); + } +} + +bool UReplicatedVRCameraComponent::Server_SendCameraTransform_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +/*bool UReplicatedVRCameraComponent::IsServer() +{ + if (GEngine != nullptr && GWorld != nullptr) + { + switch (GEngine->GetNetMode(GWorld)) + { + case NM_Client: + {return false; } break; + case NM_DedicatedServer: + case NM_ListenServer: + default: + {return true; } break; + } + } + + return false; +}*/ + +void UReplicatedVRCameraComponent::OnAttachmentChanged() +{ + if (AVRBaseCharacter* CharacterOwner = Cast<AVRBaseCharacter>(this->GetOwner())) + { + AttachChar = CharacterOwner; + } + else + { + AttachChar = nullptr; + } + + Super::OnAttachmentChanged(); +} + +bool UReplicatedVRCameraComponent::HasTrackingParameters() +{ + return bOffsetByHMD || bScaleTracking || bLimitMaxHeight || bLimitMinHeight || bLimitBounds; +} + +void UReplicatedVRCameraComponent::ApplyTrackingParameters(FVector &OriginalPosition) +{ + if (bOffsetByHMD) + { + OriginalPosition.X = 0; + OriginalPosition.Y = 0; + } + + if (bLimitBounds) + { + OriginalPosition.X = FMath::Clamp(OriginalPosition.X, -MaximumTrackedBounds, MaximumTrackedBounds); + OriginalPosition.Y = FMath::Clamp(OriginalPosition.Y, -MaximumTrackedBounds, MaximumTrackedBounds); + } + + if (bScaleTracking) + { + OriginalPosition *= TrackingScaler; + } + + if (bLimitMaxHeight) + { + OriginalPosition.Z = FMath::Min(MaxHeightAllowed, OriginalPosition.Z); + } + + if (bLimitMinHeight) + { + OriginalPosition.Z = FMath::Max(MinimumHeightAllowed, OriginalPosition.Z); + } +} + +void UReplicatedVRCameraComponent::UpdateTracking(float DeltaTime) +{ + bHasAuthority = IsLocallyControlled(); + + // Don't do any of the below if we aren't the authority + if (bHasAuthority) + { + // For non view target positional updates (third party and the like) + if (bSetPositionDuringTick && bLockToHmd && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + //ResetRelativeTransform(); + FQuat Orientation; + FVector Position; + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, Orientation, Position)) + { + if (HasTrackingParameters()) + { + ApplyTrackingParameters(Position); + } + + SetRelativeTransform(FTransform(Orientation, Position)); + } + } + } + else + { + if (bLerpingPosition) + { + NetUpdateCount += DeltaTime; + float LerpVal = FMath::Clamp(NetUpdateCount / (1.0f / NetUpdateRate), 0.0f, 1.0f); + + if (LerpVal >= 1.0f) + { + SetRelativeLocationAndRotation(ReplicatedCameraTransform.Position, ReplicatedCameraTransform.Rotation); + + // Stop lerping, wait for next update if it is delayed or lost then it will hitch here + // Actual prediction might be something to consider in the future, but rough to do in VR + // considering the speed and accuracy of movements + // would like to consider sub stepping but since there is no server rollback...not sure how useful it would be + // and might be perf taxing enough to not make it worth it. + bLerpingPosition = false; + NetUpdateCount = 0.0f; + } + else + { + // Removed variables to speed this up a bit + SetRelativeLocationAndRotation( + FMath::Lerp(LastUpdatesRelativePosition, (FVector)ReplicatedCameraTransform.Position, LerpVal), + FMath::Lerp(LastUpdatesRelativeRotation, ReplicatedCameraTransform.Rotation, LerpVal) + ); + } + } + } + + // Save out the component velocity from this and last frame + if(bHadValidFirstVelocity || !LastRelativePosition.Equals(FTransform::Identity)) + { + bHadValidFirstVelocity = true; + ComponentVelocity = ((bSampleVelocityInWorldSpace ? GetComponentLocation() : GetRelativeLocation()) - LastRelativePosition.GetTranslation()) / DeltaTime; + } + + LastRelativePosition = bSampleVelocityInWorldSpace ? this->GetComponentTransform() : this->GetRelativeTransform(); +} + + +void UReplicatedVRCameraComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + + if (!bUpdateInCharacterMovement || !IsValid(AttachChar)) + { + UpdateTracking(DeltaTime); + } + else + { + UCharacterMovementComponent* CharMove = AttachChar->GetCharacterMovement(); + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive() || (!CharMove->PrimaryComponentTick.bTickEvenWhenPaused && GetWorld()->IsPaused())) + { + // Our character movement isn't handling our updates, lets do it ourself. + UpdateTracking(DeltaTime); + } + } + + if (bHasAuthority) + { + // Send changes + if (this->GetIsReplicated()) + { + FRotator RelativeRot = GetRelativeRotation(); + FVector RelativeLoc = GetRelativeLocation(); + + // Don't rep if no changes + if (!RelativeLoc.Equals(ReplicatedCameraTransform.Position) || !RelativeRot.Equals(ReplicatedCameraTransform.Rotation)) + { + NetUpdateCount += DeltaTime; + + if (NetUpdateCount >= (1.0f / NetUpdateRate)) + { + NetUpdateCount = 0.0f; + ReplicatedCameraTransform.Position = RelativeLoc; + ReplicatedCameraTransform.Rotation = RelativeRot; + + + if (GetNetMode() == NM_Client) + { + AVRBaseCharacter* OwningChar = Cast<AVRBaseCharacter>(GetOwner()); + if (OverrideSendTransform != nullptr && OwningChar != nullptr) + { + (OwningChar->* (OverrideSendTransform))(ReplicatedCameraTransform); + } + else + { + // Don't bother with any of this if not replicating transform + //if (bHasAuthority && bReplicateTransform) + Server_SendCameraTransform(ReplicatedCameraTransform); + } + } + } + } + } + } +} + +void UReplicatedVRCameraComponent::GetCameraView(float DeltaTime, FMinimalViewInfo& DesiredView) +{ + bool bIsLocallyControlled = IsLocallyControlled(); + + if (bAutoSetLockToHmd) + { + if (bIsLocallyControlled) + bLockToHmd = true; + else + bLockToHmd = false; + } + + if (bIsLocallyControlled && GEngine && GEngine->XRSystem.IsValid() && GetWorld() && GetWorld()->WorldType != EWorldType::Editor) + { + IXRTrackingSystem* XRSystem = GEngine->XRSystem.Get(); + auto XRCamera = XRSystem->GetXRCamera(); + + if (XRCamera.IsValid()) + { + if (XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + const FTransform ParentWorld = CalcNewComponentToWorld(FTransform()); + XRCamera->SetupLateUpdate(ParentWorld, this, bLockToHmd == 0); + + if (bLockToHmd) + { + FQuat Orientation; + FVector Position; + if (XRCamera->UpdatePlayerCamera(Orientation, Position)) + { + if (HasTrackingParameters()) + { + ApplyTrackingParameters(Position); + } + + SetRelativeTransform(FTransform(Orientation, Position)); + } + else + { + SetRelativeScale3D(FVector(1.0f)); + //ResetRelativeTransform(); stop doing this, it is problematic + // Let the camera freeze in the last position instead + // Setting scale by itself makes sure we don't get camera scaling but keeps the last location and rotation alive + } + } + + // #TODO: Check back on this, was moved here in 4.20 but shouldn't it be inside of bLockToHMD? + XRCamera->OverrideFOV(this->FieldOfView); + } + } + } + + if (bUsePawnControlRotation) + { + const APawn* OwningPawn = Cast<APawn>(GetOwner()); + const AController* OwningController = OwningPawn ? OwningPawn->GetController() : nullptr; + if (OwningController && OwningController->IsLocalPlayerController()) + { + const FRotator PawnViewRotation = OwningPawn->GetViewRotation(); + if (!PawnViewRotation.Equals(GetComponentRotation())) + { + SetWorldRotation(PawnViewRotation); + } + } + } + + if (bUseAdditiveOffset) + { + FTransform OffsetCamToBaseCam = AdditiveOffset; + FTransform BaseCamToWorld = GetComponentToWorld(); + FTransform OffsetCamToWorld = OffsetCamToBaseCam * BaseCamToWorld; + + DesiredView.Location = OffsetCamToWorld.GetLocation(); + DesiredView.Rotation = OffsetCamToWorld.Rotator(); + } + else + { + DesiredView.Location = GetComponentLocation(); + DesiredView.Rotation = GetComponentRotation(); + } + + DesiredView.FOV = bUseAdditiveOffset ? (FieldOfView + AdditiveFOVOffset) : FieldOfView; + DesiredView.AspectRatio = AspectRatio; + DesiredView.bConstrainAspectRatio = bConstrainAspectRatio; + DesiredView.bUseFieldOfViewForLOD = bUseFieldOfViewForLOD; + DesiredView.ProjectionMode = ProjectionMode; + DesiredView.OrthoWidth = OrthoWidth; + DesiredView.OrthoNearClipPlane = OrthoNearClipPlane; + DesiredView.OrthoFarClipPlane = OrthoFarClipPlane; + + // See if the CameraActor wants to override the PostProcess settings used. + DesiredView.PostProcessBlendWeight = PostProcessBlendWeight; + if (PostProcessBlendWeight > 0.0f) + { + DesiredView.PostProcessSettings = PostProcessSettings; + } + + // If this camera component has a motion vector simumlation transform, use that for the current view's previous transform + DesiredView.PreviousViewTransform = FMotionVectorSimulation::Get().GetPreviousTransform(this); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacter.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacter.cpp new file mode 100644 index 0000000..d3d8fcd --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacter.cpp @@ -0,0 +1,112 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "SimpleChar/VRSimpleCharacter.h" +#include "Components/CapsuleComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + +AVRSimpleCharacter::AVRSimpleCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass<UVRSimpleCharacterMovementComponent>(ACharacter::CharacterMovementComponentName)) + +{ + + // Remove the movement jitter with slow speeds + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + MovementRep.LocationQuantizationLevel = EVectorQuantization::RoundTwoDecimals; + + VRMovementReference = Cast<UVRBaseCharacterMovementComponent>(GetMovementComponent()); + if (VRMovementReference) + VRMovementReference->bApplyAdditionalVRInputVectorAsNegative = false; + + VRSceneComponent = CreateDefaultSubobject<USceneComponent>(TEXT("VR Scene Component")); + + if (VRSceneComponent) + { + VRSceneComponent->SetupAttachment(NetSmoother); + VRSceneComponent->SetRelativeLocation(FVector(0, 0, -96)); + } + + //VRReplicatedCamera = CreateDefaultSubobject<UReplicatedVRCameraComponent>(TEXT("VR Replicated Camera")); + if (VRReplicatedCamera && VRSceneComponent) + { + VRReplicatedCamera->bOffsetByHMD = true; + VRReplicatedCamera->SetupAttachment(VRSceneComponent); + } + + /*VRHeadCollider = CreateDefaultSubobject<UCapsuleComponent>(TEXT("VR Head Collider")); + if (VRHeadCollider && VRReplicatedCamera) + { + VRHeadCollider->SetCapsuleSize(20.0f, 25.0f); + VRHeadCollider->SetupAttachment(VRReplicatedCamera); + }*/ + +// ParentRelativeAttachment = CreateDefaultSubobject<UParentRelativeAttachmentComponent>(TEXT("Parent Relative Attachment")); + if (ParentRelativeAttachment && VRReplicatedCamera && VRSceneComponent) + { + ParentRelativeAttachment->bOffsetByHMD = true; + ParentRelativeAttachment->SetupAttachment(VRSceneComponent); + } + + if (LeftMotionController) + { + LeftMotionController->bOffsetByHMD = true; + + if (VRSceneComponent) + { + LeftMotionController->SetupAttachment(VRSceneComponent); + } + } + + //RightMotionController = CreateDefaultSubobject<UGripMotionControllerComponent>(TEXT("Right Grip Motion Controller")); + if (RightMotionController) + { + RightMotionController->bOffsetByHMD = true; + + if (VRSceneComponent) + { + RightMotionController->SetupAttachment(VRSceneComponent); + } + } +} + +void AVRSimpleCharacter::BeginPlay() +{ + Super::BeginPlay(); + + // I am re-forcing these to true here + // The editor loses these values sometimes when copying over from the std character + // And I like copying since it saves me hours of work. + + if(LeftMotionController) + LeftMotionController->bOffsetByHMD = true; + + if(RightMotionController) + RightMotionController->bOffsetByHMD = true; + + if(ParentRelativeAttachment) + ParentRelativeAttachment->bOffsetByHMD = true; + + if(VRReplicatedCamera) + VRReplicatedCamera->bOffsetByHMD = true; + +} + +FVector AVRSimpleCharacter::GetTeleportLocation(FVector OriginalLocation) +{ + //FVector modifier = VRRootReference->GetVRLocation_Inline() - this->GetActorLocation(); + //modifier.Z = 0.0f; // Null out Z + //return OriginalLocation - modifier; + + return OriginalLocation; +} + +bool AVRSimpleCharacter::TeleportTo(const FVector& DestLocation, const FRotator& DestRotation, bool bIsATest, bool bNoCheck) +{ + bool bTeleportSucceeded = Super::TeleportTo(DestLocation + FVector(0,0,GetCapsuleComponent()->GetScaledCapsuleHalfHeight()), DestRotation, bIsATest, bNoCheck); + + if (bTeleportSucceeded) + { + NotifyOfTeleport(); + } + + return bTeleportSucceeded; +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacterMovementComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacterMovementComponent.cpp new file mode 100644 index 0000000..bbc778d --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/SimpleChar/VRSimpleCharacterMovementComponent.cpp @@ -0,0 +1,1619 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Movement.cpp: Character movement implementation + +=============================================================================*/ + +#include "SimpleChar/VRSimpleCharacterMovementComponent.h" +#include "GameFramework/PhysicsVolume.h" +#include "GameFramework/GameNetworkManager.h" +#include "IHeadMountedDisplay.h" +#include "SimpleChar/VRSimpleCharacter.h" +#include "NavigationSystem.h" +#include "GameFramework/Character.h" +#include "GameFramework/GameState.h" +#include "Engine/Engine.h" +#include "Components/PrimitiveComponent.h" +#include "IXRTrackingSystem.h" +#include "Animation/AnimMontage.h" +//#include "PhysicsEngine/DestructibleActor.h" + +// @todo this is here only due to circular dependency to AIModule. To be removed +#include "Navigation/PathFollowingComponent.h" +#include "AI/Navigation/AvoidanceManager.h" +#include "Components/CapsuleComponent.h" +#include "Components/BrushComponent.h" +//#include "Components/DestructibleComponent.h" + +#include "Engine/DemoNetDriver.h" +#include "Engine/NetworkObjectList.h" + +DEFINE_LOG_CATEGORY(LogSimpleCharacterMovement); + +DECLARE_CYCLE_STAT(TEXT("Char ReplicateMoveToServerVRSimple"), STAT_CharacterMovementReplicateMoveToServerVRSimple, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char CallServerMoveVRSimple"), STAT_CharacterMovementCallServerMoveVRSimple, STATGROUP_Character); + +// Defines for build configs +#if DO_CHECK && !UE_BUILD_SHIPPING // Disable even if checks in shipping are enabled. +#define devCode( Code ) checkCode( Code ) +#else +#define devCode(...) +#endif + +namespace CharacterMovementConstants +{ + // MAGIC NUMBERS + const float MAX_STEP_SIDE_ZZ = 0.08f; // maximum z value for the normal on the vertical side of steps + const float VERTICAL_SLOPE_NORMAL_ZZ = 0.001f; // Slope is vertical if Abs(Normal.Z) <= this threshold. Accounts for precision problems that sometimes angle normals slightly off horizontal for vertical surface. +} + +UVRSimpleCharacterMovementComponent::UVRSimpleCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PostPhysicsTickFunction.bCanEverTick = true; + PostPhysicsTickFunction.bStartWithTickEnabled = false; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + VRRootCapsule = NULL; + //VRCameraCollider = NULL; + + this->bRequestedMoveUseAcceleration = false; + //this->MaxAcceleration = 200048.0f; + //this->BrakingDecelerationWalking = 200048.0f; + this->bUpdateOnlyIfRendered = false; + this->AirControl = 0.0f; + + bSkipHMDChecks = false; + bIsFirstTick = true; + //LastAdditionalVRInputVector = FVector::ZeroVector; + AdditionalVRInputVector = FVector::ZeroVector; + CustomVRInputVector = FVector::ZeroVector; + + SetNetworkMoveDataContainer(VRNetworkMoveDataContainer); + SetMoveResponseDataContainer(VRMoveResponseDataContainer); + + //bMaintainHorizontalGroundVelocity = true; +} + +bool UVRSimpleCharacterMovementComponent::VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult) +{ + //SCOPE_CYCLE_COUNTER(STAT_CharStepUp); + + if (!CanStepUp(InHit) || MaxStepHeight <= 0.f) + { + return false; + } + + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + + // Don't bother stepping up if top of capsule is hitting something. + const float InitialImpactZ = InHit.ImpactPoint.Z; + if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius)) + { + return false; + } + + if (GravDir.IsZero()) + { + return false; + } + + // Gravity should be a normalized direction + ensure(GravDir.IsNormalized()); + + float StepTravelUpHeight = MaxStepHeight; + float StepTravelDownHeight = StepTravelUpHeight; + const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir); + float PawnInitialFloorBaseZ = OldLocation.Z - PawnHalfHeight; + float PawnFloorPointZ = PawnInitialFloorBaseZ; + + if (IsMovingOnGround() && CurrentFloor.IsWalkableFloor()) + { + // Since we float a variable amount off the floor, we need to enforce max step height off the actual point of impact with the floor. + const float FloorDist = FMath::Max(0.f, CurrentFloor.GetDistanceToFloor()); + PawnInitialFloorBaseZ -= FloorDist; + StepTravelUpHeight = FMath::Max(StepTravelUpHeight - FloorDist, 0.f); + StepTravelDownHeight = (MaxStepHeight + MAX_FLOOR_DIST*2.f); + + const bool bHitVerticalFace = !IsWithinEdgeTolerance(InHit.Location, InHit.ImpactPoint, PawnRadius); + if (!CurrentFloor.bLineTrace && !bHitVerticalFace) + { + PawnFloorPointZ = CurrentFloor.HitResult.ImpactPoint.Z; + } + else + { + // Base floor point is the base of the capsule moved down by how far we are hovering over the surface we are hitting. + PawnFloorPointZ -= CurrentFloor.FloorDist; + } + } + + // Don't step up if the impact is below us, accounting for distance from floor. + if (InitialImpactZ <= PawnInitialFloorBaseZ) + { + return false; + } + + // Scope our movement updates, and do not apply them until all intermediate moves are completed. + FScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates); + + // step up - treat as vertical wall + FHitResult SweepUpHit(1.f); + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit); + + if (SweepUpHit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // step fwd + FHitResult Hit(1.f); + MoveUpdatedComponent(Delta, PawnRotation, true, &Hit); + + // Check result of forward movement + if (Hit.bBlockingHit) + { + if (Hit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If we hit something above us and also something ahead of us, we should notify about the upward hit as well. + // The forward hit will be handled later (in the bSteppedOver case below). + // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification. + if (SweepUpHit.bBlockingHit && Hit.bBlockingHit) + { + HandleImpact(SweepUpHit); + } + + // pawn ran into a wall + HandleImpact(Hit); + if (IsFalling()) + { + return true; + } + + // Don't adjust or slide, just fail here in VR + ScopedStepUpMovement.RevertMove(); + return false; + /* + // adjust and try again + const float ForwardHitTime = Hit.Time; + const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true); + + if (IsFalling()) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If both the forward hit and the deflection got us nowhere, there is no point in this step up. + if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f) + { + ScopedStepUpMovement.RevertMove(); + return false; + }*/ + } + + // Step down + MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit); + + // If step down was initially penetrating abort the step up + if (Hit.bStartPenetrating) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + FStepDownResult StepDownResult; + if (Hit.IsValidBlockingHit()) + { + // See if this step sequence would have allowed us to travel higher than our max step height allows. + const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ; + if (DeltaZ > MaxStepHeight) + { + //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f to %f"), DeltaZ, PawnInitialFloorBaseZ, NewLocation.Z); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Reject unwalkable surface normals here. + if (!IsWalkable(Hit)) + { + // Reject if normal opposes movement direction + const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f; + if (bNormalTowardsMe) + { + //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Also reject if we would end up being higher than our starting location by stepping down. + // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge. + if (Hit.Location.Z > OldLocation.Z) + { + //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well. + if (!IsWithinEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius)) + { + //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Don't step up onto invalid surfaces if traveling higher. + if (DeltaZ > 0.f && !CanStepUp(Hit)) + { + //UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method. + if (OutStepDownResult != NULL) + { + FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit); + + // Reject unwalkable normals if we end up higher than our initial height. + // It's fine to walk down onto an unwalkable surface, don't reject those moves. + if (Hit.Location.Z > OldLocation.Z) + { + // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare). + // In those cases we should instead abort the step up and try to slide along the stair. + if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < CharacterMovementConstants::MAX_STEP_SIDE_ZZ) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + StepDownResult.bComputedFloor = true; + } + } + + // Copy step down result. + if (OutStepDownResult != NULL) + { + *OutStepDownResult = StepDownResult; + } + + // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. + bJustTeleported |= !bMaintainHorizontalGroundVelocity; + + return true; +} + +void UVRSimpleCharacterMovementComponent::PhysFlying(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + RestorePreAdditiveRootMotionVelocity(); + RestorePreAdditiveVRMotionVelocity(); + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + if (bCheatFlying && Acceleration.IsZero()) + { + Velocity = FVector::ZeroVector; + } + const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction; + CalcVelocity(deltaTime, Friction, true, BrakingDecelerationFlying); + } + + ApplyRootMotionToVelocity(deltaTime); + ApplyVRMotionToVelocity(deltaTime); + + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector Adjusted = Velocity * deltaTime; + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.Time < 1.f) + { + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + float stepZ = UpdatedComponent->GetComponentLocation().Z; + bSteppedUp = StepUp(GravDir, Adjusted * (1.f - Hit.Time), Hit); + if (bSteppedUp) + { + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + } + + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / deltaTime; + } +} + +void UVRSimpleCharacterMovementComponent::PhysNavWalking(float deltaTime, int32 Iterations) +{ + //SCOPE_CYCLE_COUNTER(STAT_CharPhysNavWalking); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + if ((!CharacterOwner || !CharacterOwner->Controller) && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Acceleration = FVector::ZeroVector; + Velocity = FVector::ZeroVector; + return; + } + + RestorePreAdditiveRootMotionVelocity(); + RestorePreAdditiveVRMotionVelocity(); + + // Ensure velocity is horizontal. + MaintainHorizontalGroundVelocity(); + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN before CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + //bound acceleration + Acceleration.Z = 0.f; + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + CalcVelocity(deltaTime, GroundFriction, false, BrakingDecelerationWalking); + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + } + + ApplyRootMotionToVelocity(deltaTime); + ApplyVRMotionToVelocity(deltaTime); + + if (IsFalling()) + { + // Root motion could have put us into Falling + StartNewPhysics(deltaTime, Iterations); + return; + } + + Iterations++; + + FVector DesiredMove = Velocity; + DesiredMove.Z = 0.f; + + const FVector OldLocation = GetActorFeetLocation(); + const FVector DeltaMove = DesiredMove * deltaTime; + const bool bDeltaMoveNearlyZero = DeltaMove.IsNearlyZero(); + + FVector AdjustedDest = OldLocation + DeltaMove; + FNavLocation DestNavLocation; + + bool bSameNavLocation = false; + if (CachedNavLocation.NodeRef != INVALID_NAVNODEREF) + { + if (bProjectNavMeshWalking) + { + const float DistSq2D = (OldLocation - CachedNavLocation.Location).SizeSquared2D(); + const float DistZ = FMath::Abs(OldLocation.Z - CachedNavLocation.Location.Z); + + const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f; + const float ProjectionScale = (OldLocation.Z > CachedNavLocation.Location.Z) ? NavMeshProjectionHeightScaleUp : NavMeshProjectionHeightScaleDown; + const float DistZThr = TotalCapsuleHeight * FMath::Max(0.f, ProjectionScale); + + bSameNavLocation = (DistSq2D <= KINDA_SMALL_NUMBER) && (DistZ < DistZThr); + } + else + { + bSameNavLocation = CachedNavLocation.Location.Equals(OldLocation); + } + + if (bDeltaMoveNearlyZero && bSameNavLocation) + { + if (const INavigationDataInterface * NavData = GetNavData()) + { + if (!NavData->IsNodeRefValid(CachedNavLocation.NodeRef)) + { + CachedNavLocation.NodeRef = INVALID_NAVNODEREF; + bSameNavLocation = false; + } + } + } + } + + + if (bDeltaMoveNearlyZero && bSameNavLocation) + { + DestNavLocation = CachedNavLocation; + //UE_LOG(LogNavMeshMovement, VeryVerbose, TEXT("%s using cached navmesh location! (bProjectNavMeshWalking = %d)"), *GetNameSafe(CharacterOwner), bProjectNavMeshWalking); + } + else + { + // SCOPE_CYCLE_COUNTER(STAT_CharNavProjectPoint); + + // Start the trace from the Z location of the last valid trace. + // Otherwise if we are projecting our location to the underlying geometry and it's far above or below the navmesh, + // we'll follow that geometry's plane out of range of valid navigation. + if (bSameNavLocation && bProjectNavMeshWalking) + { + AdjustedDest.Z = CachedNavLocation.Location.Z; + } + + // Find the point on the NavMesh + const bool bHasNavigationData = FindNavFloor(AdjustedDest, DestNavLocation); + if (!bHasNavigationData) + { + SetMovementMode(MOVE_Walking); + return; + } + + CachedNavLocation = DestNavLocation; + } + + if (DestNavLocation.NodeRef != INVALID_NAVNODEREF) + { + FVector NewLocation(AdjustedDest.X, AdjustedDest.Y, DestNavLocation.Location.Z); + if (bProjectNavMeshWalking) + { + // SCOPE_CYCLE_COUNTER(STAT_CharNavProjectLocation); + const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f; + const float UpOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleUp); + const float DownOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleDown); + NewLocation = ProjectLocationFromNavMesh(deltaTime, OldLocation, NewLocation, UpOffset, DownOffset); + } + + FVector AdjustedDelta = NewLocation - OldLocation; + + if (!AdjustedDelta.IsNearlyZero()) + { + FHitResult HitResult; + SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweepWhileNavWalking, HitResult); + } + + // Update velocity to reflect actual move + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasVelocity()) + { + Velocity = (GetActorFeetLocation() - OldLocation) / deltaTime; + MaintainHorizontalGroundVelocity(); + } + + bJustTeleported = false; + } + else + { + StartFalling(Iterations, deltaTime, deltaTime, DeltaMove, OldLocation); + } +} + +void UVRSimpleCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations) +{ + //SCOPE_CYCLE_COUNTER(STAT_CharPhysFalling); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + FVector FallAcceleration = GetFallingLateralAcceleration(deltaTime); + FallAcceleration.Z = 0.f; + const bool bHasLimitedAirControl = ShouldLimitAirControl(deltaTime, FallAcceleration); + + float remainingTime = deltaTime; + while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations)) + { + Iterations++; + float timeTick = GetSimulationTimeStep(remainingTime, Iterations); + remainingTime -= timeTick; + + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + bJustTeleported = false; + + const FVector OldVelocityWithRootMotion = Velocity; + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + const FVector OldVelocity = Velocity; + + // Apply input + const float MaxDecel = GetMaxBrakingDeceleration(); + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + // Compute Velocity + { + // Acceleration = FallAcceleration for CalcVelocity(), but we restore it after using it. + TGuardValue<FVector> RestoreAcceleration(Acceleration, FallAcceleration); + Velocity.Z = 0.f; + CalcVelocity(timeTick, FallingLateralFriction, false, BrakingDecelerationFalling); + Velocity.Z = OldVelocity.Z; + } + } + + // Compute current gravity + const FVector Gravity(0.f, 0.f, GetGravityZ()); + + float GravityTime = timeTick; + + // If jump is providing force, gravity may be affected. + bool bEndingJumpForce = false; + if (CharacterOwner->JumpForceTimeRemaining > 0.0f) + { + // Consume some of the force time. Only the remaining time (if any) is affected by gravity when bApplyGravityWhileJumping=false. + const float JumpForceTime = FMath::Min(CharacterOwner->JumpForceTimeRemaining, timeTick); + GravityTime = bApplyGravityWhileJumping ? timeTick : FMath::Max(0.0f, timeTick - JumpForceTime); + + // Update Character state + CharacterOwner->JumpForceTimeRemaining -= JumpForceTime; + if (CharacterOwner->JumpForceTimeRemaining <= 0.0f) + { + CharacterOwner->ResetJumpState(); + bEndingJumpForce = true; + } + } + + // Apply gravity + Velocity = NewFallVelocity(Velocity, Gravity, GravityTime); + + //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) OldVelocityWithRootMotion=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *OldVelocityWithRootMotion.ToString(), *Velocity.ToString()); + ApplyRootMotionToVelocity(timeTick); + DecayFormerBaseVelocity(timeTick); + + // See if we need to sub-step to exactly reach the apex. This is important for avoiding "cutting off the top" of the trajectory as framerate varies. + static const auto CVarForceJumpPeakSubstep = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ForceJumpPeakSubstep")); + if (CVarForceJumpPeakSubstep->GetInt() != 0 && OldVelocityWithRootMotion.Z > 0.f && Velocity.Z <= 0.f && NumJumpApexAttempts < MaxJumpApexAttemptsPerSimulation) + { + const FVector DerivedAccel = (Velocity - OldVelocityWithRootMotion) / timeTick; + if (!FMath::IsNearlyZero(DerivedAccel.Z)) + { + const float TimeToApex = -OldVelocityWithRootMotion.Z / DerivedAccel.Z; + + // The time-to-apex calculation should be precise, and we want to avoid adding a substep when we are basically already at the apex from the previous iteration's work. + const float ApexTimeMinimum = 0.0001f; + if (TimeToApex >= ApexTimeMinimum && TimeToApex < timeTick) + { + const FVector ApexVelocity = OldVelocityWithRootMotion + (DerivedAccel * TimeToApex); + Velocity = ApexVelocity; + Velocity.Z = 0.f; // Should be nearly zero anyway, but this makes apex notifications consistent. + + // We only want to move the amount of time it takes to reach the apex, and refund the unused time for next iteration. + const float TimeToRefund = (timeTick - TimeToApex); + remainingTime += TimeToRefund; + timeTick = TimeToApex; + Iterations--; + NumJumpApexAttempts++; + + // Refund time to any active Root Motion Sources as well + for (TSharedPtr<FRootMotionSource> RootMotionSource : CurrentRootMotion.RootMotionSources) + { + const float RewoundRMSTime = FMath::Max(0.0f, RootMotionSource->GetTime() - TimeToRefund); + RootMotionSource->SetTime(RewoundRMSTime); + } + } + } + } + + //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *Velocity.ToString()); + + ApplyRootMotionToVelocity(timeTick); + //ApplyVRMotionToVelocity(timeTick); + + if (bNotifyApex && (Velocity.Z < 0.f)) + { + // Just passed jump apex since now going down + bNotifyApex = false; + NotifyJumpApex(); + } + + // Compute change in position (using midpoint integration method). + FVector Adjusted = (0.5f * (OldVelocityWithRootMotion + Velocity) * timeTick) + (AdditionalVRInputVector /** timeTick*/); + + // Special handling if ending the jump force where we didn't apply gravity during the jump. + if (bEndingJumpForce && !bApplyGravityWhileJumping) + { + // We had a portion of the time at constant speed then a portion with acceleration due to gravity. + // Account for that here with a more correct change in position. + const float NonGravityTime = FMath::Max(0.f, timeTick - GravityTime); + Adjusted = ((OldVelocityWithRootMotion * NonGravityTime) + (0.5f * (OldVelocityWithRootMotion + Velocity) * GravityTime)) + (AdditionalVRInputVector /** timeTick*/); + } + + + // Move + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted, PawnRotation, true, Hit); + + if (!HasValidData()) + { + return; + } + + float LastMoveTimeSlice = timeTick; + float subTimeTickRemaining = timeTick * (1.f - Hit.Time); + + if (IsSwimming()) //just entered water + { + remainingTime += subTimeTickRemaining; + StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations); + return; + } + else if (Hit.bBlockingHit) + { + if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit)) + { + remainingTime += subTimeTickRemaining; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + else + { + // Compute impact deflection based on final velocity, not integration step. + // This allows us to compute a new velocity from the deflected vector, and ensures the full gravity effect is included in the slide result. + Adjusted = Velocity * timeTick; + + // See if we can convert a normally invalid landing spot (based on the hit result) to a usable one. + if (!Hit.bStartPenetrating && ShouldCheckForValidLandingSpot(timeTick, Adjusted, Hit)) + { + const FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + FFindFloorResult FloorResult; + FindFloor(PawnLocation, FloorResult, false); + if (FloorResult.IsWalkableFloor() && IsValidLandingSpot(PawnLocation, FloorResult.HitResult)) + { + remainingTime += subTimeTickRemaining; + ProcessLanded(FloorResult.HitResult, remainingTime, Iterations); + return; + } + } + + HandleImpact(Hit, LastMoveTimeSlice, Adjusted); + + // If we've changed physics mode, abort. + if (!HasValidData() || !IsFalling()) + { + return; + } + + // Limit air control based on what we hit. + // We moved to the impact point using air control, but may want to deflect from there based on a limited air control acceleration. + FVector VelocityNoAirControl = OldVelocity; + FVector AirControlAccel = Acceleration; + if (bHasLimitedAirControl) + { + // Compute VelocityNoAirControl + { + // Find velocity *without* acceleration. + TGuardValue<FVector> RestoreAcceleration(Acceleration, FVector::ZeroVector); + TGuardValue<FVector> RestoreVelocity(Velocity, OldVelocity); + Velocity.Z = 0.f; + CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel); + VelocityNoAirControl = FVector(Velocity.X, Velocity.Y, OldVelocity.Z); + VelocityNoAirControl = NewFallVelocity(VelocityNoAirControl, Gravity, GravityTime); + } + + + const bool bCheckLandingSpot = false; // we already checked above. + AirControlAccel = (Velocity - VelocityNoAirControl) / timeTick; + const FVector AirControlDeltaV = LimitAirControl(LastMoveTimeSlice, AirControlAccel, Hit, bCheckLandingSpot) * LastMoveTimeSlice; + Adjusted = (VelocityNoAirControl + AirControlDeltaV) * LastMoveTimeSlice; + } + + const FVector OldHitNormal = Hit.Normal; + const FVector OldHitImpactNormal = Hit.ImpactNormal; + FVector Delta = ComputeSlideVector(Adjusted, 1.f - Hit.Time, OldHitNormal, Hit); + + // Compute velocity after deflection (only gravity component for RootMotion) + const UPrimitiveComponent* HitComponent = Hit.GetComponent(); + static const auto CVarUseTargetVelocityOnImpact = IConsoleManager::Get().FindConsoleVariable(TEXT("p.UseTargetVelocityOnImpact")); + if (CVarUseTargetVelocityOnImpact->GetInt() && !Velocity.IsNearlyZero() && MovementBaseUtility::IsSimulatedBase(HitComponent)) + { + const FVector ContactVelocity = MovementBaseUtility::GetMovementBaseVelocity(HitComponent, NAME_None) + MovementBaseUtility::GetMovementBaseTangentialVelocity(HitComponent, NAME_None, Hit.ImpactPoint); + const FVector NewVelocity = Velocity - Hit.ImpactNormal * FVector::DotProduct(Velocity - ContactVelocity, Hit.ImpactNormal); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + else if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported) + { + const FVector NewVelocity = (Delta / subTimeTickRemaining); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + + if (subTimeTickRemaining > KINDA_SMALL_NUMBER && (Delta | Adjusted) > 0.f) + { + // Move in deflected direction. + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + + if (Hit.bBlockingHit) + { + // hit second wall + LastMoveTimeSlice = subTimeTickRemaining; + subTimeTickRemaining = subTimeTickRemaining * (1.f - Hit.Time); + + if (IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit)) + { + remainingTime += subTimeTickRemaining; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + + HandleImpact(Hit, LastMoveTimeSlice, Delta); + + // If we've changed physics mode, abort. + if (!HasValidData() || !IsFalling()) + { + return; + } + + // Act as if there was no air control on the last move when computing new deflection. + if (bHasLimitedAirControl && Hit.Normal.Z > CharacterMovementConstants::VERTICAL_SLOPE_NORMAL_ZZ) + { + const FVector LastMoveNoAirControl = VelocityNoAirControl * LastMoveTimeSlice; + Delta = ComputeSlideVector(LastMoveNoAirControl, 1.f, OldHitNormal, Hit); + } + + FVector PreTwoWallDelta = Delta; + TwoWallAdjust(Delta, Hit, OldHitNormal); + + // Limit air control, but allow a slide along the second wall. + if (bHasLimitedAirControl) + { + const bool bCheckLandingSpot = false; // we already checked above. + const FVector AirControlDeltaV = LimitAirControl(subTimeTickRemaining, AirControlAccel, Hit, bCheckLandingSpot) * subTimeTickRemaining; + + // Only allow if not back in to first wall + if (FVector::DotProduct(AirControlDeltaV, OldHitNormal) > 0.f) + { + Delta += (AirControlDeltaV * subTimeTickRemaining); + } + } + + // Compute velocity after deflection (only gravity component for RootMotion) + if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported) + { + const FVector NewVelocity = (Delta / subTimeTickRemaining); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + + // bDitch=true means that pawn is straddling two slopes, neither of which he can stand on + bool bDitch = ((OldHitImpactNormal.Z > 0.f) && (Hit.ImpactNormal.Z > 0.f) && (FMath::Abs(Delta.Z) <= KINDA_SMALL_NUMBER) && ((Hit.ImpactNormal | OldHitImpactNormal) < 0.f)); + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + if (Hit.Time == 0.f) + { + // if we are stuck then try to side step + FVector SideDelta = (OldHitNormal + Hit.ImpactNormal).GetSafeNormal2D(); + if (SideDelta.IsNearlyZero()) + { + SideDelta = FVector(OldHitNormal.Y, -OldHitNormal.X, 0).GetSafeNormal(); + } + SafeMoveUpdatedComponent(SideDelta, PawnRotation, true, Hit); + } + + if (bDitch || IsValidLandingSpot(UpdatedComponent->GetComponentLocation(), Hit) || Hit.Time == 0.f) + { + remainingTime = 0.f; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + else if (GetPerchRadiusThreshold() > 0.f && Hit.Time == 1.f && OldHitImpactNormal.Z >= GetWalkableFloorZ()) + { + // We might be in a virtual 'ditch' within our perch radius. This is rare. + const FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + const float ZMovedDist = FMath::Abs(PawnLocation.Z - OldLocation.Z); + const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D(); + if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick) + { + Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + Velocity.Z = FMath::Max<float>(JumpZVelocity * 0.25f, 1.f); + Delta = Velocity * timeTick; + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + } + } + } + } + } + } + + if (Velocity.SizeSquared2D() <= KINDA_SMALL_NUMBER * 10.f) + { + Velocity.X = 0.f; + Velocity.Y = 0.f; + } + } +} + +void UVRSimpleCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations) +{ +// SCOPE_CYCLE_COUNTER(STAT_CharPhysWalking); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy))) + { + Acceleration = FVector::ZeroVector; + Velocity = FVector::ZeroVector; + return; + } + + if (!UpdatedComponent->IsQueryCollisionEnabled()) + { + SetMovementMode(MOVE_Walking); + return; + } + + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN before Iteration (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + bJustTeleported = false; + bool bCheckedFall = false; + bool bTriedLedgeMove = false; + float remainingTime = deltaTime; + + //bool bHasLastAdditiveVelocity = false; + //FVector LastPreAdditiveVRVelocity; + // Perform the move + while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy))) + { + Iterations++; + bJustTeleported = false; + const float timeTick = GetSimulationTimeStep(remainingTime, Iterations); + remainingTime -= timeTick; + + // Save current values + UPrimitiveComponent * const OldBase = GetMovementBase(); + const FVector PreviousBaseLocation = (OldBase != NULL) ? OldBase->GetComponentLocation() : FVector::ZeroVector; + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FFindFloorResult OldFloor = CurrentFloor; + + RestorePreAdditiveRootMotionVelocity(); + RestorePreAdditiveVRMotionVelocity(); + + // Ensure velocity is horizontal. + MaintainHorizontalGroundVelocity(); + const FVector OldVelocity = Velocity; + Acceleration.Z = 0.f; + + // Apply acceleration + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + CalcVelocity(timeTick, GroundFriction, false, BrakingDecelerationWalking); + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + } + + ApplyRootMotionToVelocity(timeTick); + ApplyVRMotionToVelocity(deltaTime);//timeTick); + + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after Root Motion application (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + //LastPreAdditiveVRVelocity = Velocity; + //bHasLastAdditiveVelocity = true; + //Velocity += AdditionalVRInputVector / timeTick; + + if (IsFalling()) + { + // Root motion could have put us into Falling. + // No movement has taken place this movement tick so we pass on full time/past iteration count + StartNewPhysics(remainingTime + timeTick, Iterations - 1); + return; + } + + + // Compute move parameters + const FVector MoveVelocity = Velocity; + const FVector Delta = (timeTick * MoveVelocity);// +AdditionalVRInputVector; + + const bool bZeroDelta = Delta.IsNearlyZero(); + FStepDownResult StepDownResult; + + + if (bZeroDelta) + { + remainingTime = 0.f; + } + else + { + // try to move forward + MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult); + + if (IsFalling()) + { + // pawn decided to jump up + const float DesiredDist = Delta.Size(); + if (DesiredDist > KINDA_SMALL_NUMBER) + { + const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D(); + remainingTime += timeTick * (1.f - FMath::Min(1.f, ActualDist / DesiredDist)); + } + StartNewPhysics(remainingTime, Iterations); + return; + } + else if (IsSwimming()) //just entered water + { + StartSwimming(OldLocation, OldVelocity, timeTick, remainingTime, Iterations); + return; + } + } + + // Update floor. + // StepUp might have already done it for us. + if (StepDownResult.bComputedFloor) + { + CurrentFloor = StepDownResult.FloorResult; + } + else + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL); + } + + // check for ledges here + const bool bCheckLedges = !CanWalkOffLedges(); + if (bCheckLedges && !CurrentFloor.IsWalkableFloor()) + { + // calculate possible alternate movement + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldLocation, Delta, GravDir); + if (!NewDelta.IsZero()) + { + // first revert this move + RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false); + + // avoid repeated ledge moves if the first one fails + bTriedLedgeMove = true; + + // Try new movement direction + Velocity = NewDelta / timeTick; + remainingTime += timeTick; + continue; + } + else + { + // see if it is OK to jump + // @todo collision : only thing that can be problem is that oldbase has world collision on + bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase))); + if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump)) + { + return; + } + bCheckedFall = true; + + // revert this move + RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true); + remainingTime = 0.f; + break; + } + } + else + { + // Validate the floor check + if (CurrentFloor.IsWalkableFloor()) + { + if (ShouldCatchAir(OldFloor, CurrentFloor)) + { + HandleWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, timeTick); + if (IsMovingOnGround()) + { + // If still walking, then fall. If not, assume the user set a different mode they want to keep. + StartFalling(Iterations, remainingTime, timeTick, Delta, OldLocation); + } + return; + } + + AdjustFloorHeight(); + SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + } + else if (CurrentFloor.HitResult.bStartPenetrating && remainingTime <= 0.f) + { + // The floor check failed because it started in penetration + // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor. + FHitResult Hit(CurrentFloor.HitResult); + Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST); + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit); + ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck = true; + } + + // check if just entered water + if (IsSwimming()) + { + StartSwimming(OldLocation, Velocity, timeTick, remainingTime, Iterations); + return; + } + + // See if we need to start falling. + if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating) + { + const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase))); + if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump)) + { + return; + } + bCheckedFall = true; + } + } + + + // Allow overlap events and such to change physics state and velocity + if (IsMovingOnGround()) + { + // Make velocity reflect actual move + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && timeTick >= MIN_TICK_TIME) + { + // TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes? + Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick; + MaintainHorizontalGroundVelocity(); + } + } + + // If we didn't move at all this iteration then abort (since future iterations will also be stuck). + if (UpdatedComponent->GetComponentLocation() == OldLocation) + { + remainingTime = 0.f; + break; + } + } + + if (IsMovingOnGround()) + { + MaintainHorizontalGroundVelocity(); + } +} + + +void UVRSimpleCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!bSkipHMDChecks) + { + if (CharacterOwner->IsLocallyControlled()) + { + FQuat curRot; + bool bWasHeadset = false; + + if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + bWasHeadset = true; + + if (GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curCameraLoc)) + { + curCameraRot = curRot.Rotator(); + } + else + { + curCameraLoc = lastCameraLoc; + curCameraRot = lastCameraRot; + } + } + else if (VRCameraComponent) + { + FVector curRelLoc = VRCameraComponent->GetRelativeLocation(); + curCameraLoc = curRelLoc; + curCameraRot = VRCameraComponent->GetRelativeRotation(); + VRCameraComponent->SetRelativeLocation(FVector(0, 0, curRelLoc.Z)); + } + + if (!bIsFirstTick) + { + FVector DifferenceFromLastFrame = (curCameraLoc - lastCameraLoc); + + // Can adjust the relative tolerances to remove jitter and some update processing + if (!DifferenceFromLastFrame.IsNearlyZero(0.001f) /*|| !(curCameraRot - lastCameraRot).IsNearlyZero(0.001f)*/) + { + if (VRRootCapsule) + { + DifferenceFromLastFrame *= VRRootCapsule->GetComponentScale(); // Scale up with character + AdditionalVRInputVector = VRRootCapsule->GetComponentRotation().RotateVector(DifferenceFromLastFrame); // Apply over a second + AdditionalVRInputVector.Z = 0.0f; // Don't use the Z value anyway, and lets me repurpose it for the CapsuleHalfHeight + } + } + else + { + AdditionalVRInputVector = FVector::ZeroVector; + } + } + else + bIsFirstTick = false; + + if (bWasHeadset) + { + lastCameraLoc = curCameraLoc; + lastCameraRot = curCameraRot; + } + else + lastCameraLoc = FVector::ZeroVector; // Technically this would be incorrect for Z, but we don't use Z anyway + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (AVRSimpleCharacter * owningChar = Cast<AVRSimpleCharacter>(GetOwner())) + { + if (VRRootCapsule) + owningChar->VRSceneComponent->SetRelativeLocation(FVector(0, 0, -VRRootCapsule->GetUnscaledCapsuleHalfHeight())); + + owningChar->GenerateOffsetToWorld(); + } + } + else + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + +void UVRSimpleCharacterMovementComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent) +{ + Super::SetUpdatedComponent(NewUpdatedComponent); + + if (UpdatedComponent) + { + // Fill the VRRootCapsule if we can + VRRootCapsule = Cast<UCapsuleComponent>(UpdatedComponent); + + if (AVRSimpleCharacter * simpleChar = Cast<AVRSimpleCharacter>(GetOwner())) + { + VRCameraComponent = Cast<UCameraComponent>(simpleChar->VRReplicatedCamera); + } + + // Stop the tick forcing + //UpdatedComponent->PrimaryComponentTick.RemovePrerequisite(this, PrimaryComponentTick); + + // Start forcing the root to tick before this, the actor tick will still tick after the movement component + // We want the root component to tick first because it is setting its offset location based off of tick + //this->PrimaryComponentTick.AddPrerequisite(UpdatedComponent, UpdatedComponent->PrimaryComponentTick); + } +} + + +/////////////////////////////// REPLICATION /////////////////////////// +void UVRSimpleCharacterMovementComponent::ServerMove_PerformMovement(const FCharacterNetworkMoveData& MoveData) +{ + QUICK_SCOPE_CYCLE_COUNTER(VRCharacterMovementServerMove_PerformMovement); + //SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementServerMove); + //CSV_SCOPED_TIMING_STAT(CharacterMovement, CharacterMovementServerMove); + + if (!HasValidData() || !IsActive()) + { + return; + } + + bool bAutoAcceptPacket = false; + FNetworkPredictionData_Server_Character* ServerData = GetPredictionData_Server_Character(); + check(ServerData); + + if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + return; + } + else if (bJustUnseated) + { + ServerData->CurrentClientTimeStamp = MoveData.TimeStamp; + bAutoAcceptPacket = true; + bJustUnseated = false; + } + + const float ClientTimeStamp = MoveData.TimeStamp; + FVector_NetQuantize10 ClientAccel = MoveData.Acceleration; + const uint8 ClientMoveFlags = MoveData.CompressedMoveFlags; + const FRotator ClientControlRotation = MoveData.ControlRotation; + + if (!bAutoAcceptPacket && !VerifyClientTimeStamp(ClientTimeStamp, *ServerData)) + { + const float ServerTimeStamp = ServerData->CurrentClientTimeStamp; + // This is more severe if the timestamp has a large discrepancy and hasn't been recently reset. + static const auto CVarNetServerMoveTimestampExpiredWarningThreshold = IConsoleManager::Get().FindConsoleVariable(TEXT("net.NetServerMoveTimestampExpiredWarningThreshold")); + if (ServerTimeStamp > 1.0f && FMath::Abs(ServerTimeStamp - ClientTimeStamp) > CVarNetServerMoveTimestampExpiredWarningThreshold->GetFloat()) + { + UE_LOG(LogNetPlayerMovement, Warning, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + else + { + UE_LOG(LogNetPlayerMovement, Log, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + return; + } + + + // Convert to our stored move data array + const FVRCharacterNetworkMoveData* MoveDataVR = (const FVRCharacterNetworkMoveData*)&MoveData; + + // Scope these, they nest with Outer references so it should work fine, this keeps the update rotation and move autonomous from double updating the char + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + bool bServerReadyForClient = true; + APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController()); + if (PC) + { + bServerReadyForClient = PC->NotifyServerReceivedClientData(CharacterOwner, ClientTimeStamp); + if (!bServerReadyForClient) + { + ClientAccel = FVector::ZeroVector; + } + } + + const UWorld* MyWorld = GetWorld(); + const float DeltaTime = ServerData->GetServerMoveDeltaTime(ClientTimeStamp, CharacterOwner->GetActorTimeDilation(*MyWorld)); + + if (DeltaTime > 0.f) + { + ServerData->CurrentClientTimeStamp = ClientTimeStamp; + ServerData->ServerAccumulatedClientTimeStamp += DeltaTime; + ServerData->ServerTimeStamp = MyWorld->GetTimeSeconds(); + ServerData->ServerTimeStampLastServerMove = ServerData->ServerTimeStamp; + + if (PC) + { + PC->SetControlRotation(ClientControlRotation); + } + + if (!bServerReadyForClient) + { + return; + } + + // Perform actual movement + if ((MyWorld->GetWorldSettings()->GetPauserPlayerState() == NULL)) + { + if (PC) + { + PC->UpdateRotation(DeltaTime); + } + + if (!MoveDataVR->ConditionalMoveReps.RequestedVelocity.IsZero()) + { + RequestedVelocity = MoveDataVR->ConditionalMoveReps.RequestedVelocity; + bHasRequestedVelocity = true; + } + + CustomVRInputVector = MoveDataVR->ConditionalMoveReps.CustomVRInputVector; + MoveActionArray = MoveDataVR->ConditionalMoveReps.MoveActionArray; + AdditionalVRInputVector = MoveDataVR->LFDiff; + VRReplicatedMovementMode = MoveDataVR->ReplicatedMovementMode; + + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->VRReplicateCapsuleHeight && MoveDataVR->LFDiff.Z > 0.0f && !FMath::IsNearlyEqual(MoveDataVR->LFDiff.Z, VRRootCapsule->GetUnscaledCapsuleHalfHeight())) + { + BaseVRCharacterOwner->SetCharacterHalfHeightVR(MoveDataVR->LFDiff.Z, false); + // BaseChar->ReplicatedCapsuleHeight.CapsuleHeight = LFDiff.Z; + //VRRootCapsule->SetCapsuleHalfHeight(LFDiff.Z, false); + } + } + + MoveAutonomous(ClientTimeStamp, DeltaTime, ClientMoveFlags, ClientAccel); + bHasRequestedVelocity = false; + } + + UE_CLOG(CharacterOwner && UpdatedComponent, LogSimpleCharacterMovement, VeryVerbose, TEXT("ServerMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d)"), + ClientTimeStamp, *ClientAccel.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), DeltaTime, *GetMovementName(), + *GetNameSafe(GetMovementBase()), *CharacterOwner->GetBasedMovement().BoneName.ToString(), MovementBaseUtility::IsDynamicBase(GetMovementBase()) ? 1 : 0); + } + + // #TODO: Handle this better at some point? Client also denies it later on during correction (ApplyNetworkMovementMode in base movement) + // Pre handling the errors, lets avoid rolling back to/from custom movement modes, they tend to be scripted and this can screw things up + const uint8 CurrentPackedMovementMode = PackNetworkMovementMode(); + if (CurrentPackedMovementMode != MoveData.MovementMode) + { + TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None); + TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(MoveData.MovementMode, NetMovementMode, NetCustomMode, NetGroundMode); + + // Custom movement modes aren't going to be rolled back as they are client authed for our pawns + if (NetMovementMode == EMovementMode::MOVE_Custom || MovementMode == EMovementMode::MOVE_Custom) + { + if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing) + SetMovementMode(NetMovementMode, NetCustomMode); + } + } + + // Validate move only after old and first dual portion, after all moves are completed. + if (MoveData.NetworkMoveType == FCharacterNetworkMoveData::ENetworkMoveType::NewMove) + { + ServerMoveHandleClientError(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode); + //ServerMoveHandleClientError(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode); + } +} + +void UVRSimpleCharacterMovementComponent::ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) +{ + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementReplicateMoveToServerVRSimple); + check(CharacterOwner != NULL); + + // Can only start sending moves if our controllers are synced up over the network, otherwise we flood the reliable buffer. + APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController()); + if (PC && PC->AcknowledgedPawn != CharacterOwner) + { + return; + } + + // Bail out if our character's controller doesn't have a Player. This may be the case when the local player + // has switched to another controller, such as a debug camera controller. + if (PC && PC->Player == nullptr) + { + return; + } + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + if (!ClientData) + { + return; + } + + // Update our delta time for physics simulation. + DeltaTime = ClientData->UpdateTimeStampAndDeltaTime(DeltaTime, *CharacterOwner, *this); + + // Find the oldest (unacknowledged) important move (OldMove). + // Don't include the last move because it may be combined with the next new move. + // A saved move is interesting if it differs significantly from the last acknowledged move + FSavedMovePtr OldMove = NULL; + if (ClientData->LastAckedMove.IsValid()) + { + const int32 NumSavedMoves = ClientData->SavedMoves.Num(); + for (int32 i = 0; i < NumSavedMoves - 1; i++) + { + const FSavedMovePtr& CurrentMove = ClientData->SavedMoves[i]; + if (CurrentMove->IsImportantMove(ClientData->LastAckedMove)) + { + OldMove = CurrentMove; + break; + } + } + } + + // Get a SavedMove object to store the movement in. + FSavedMovePtr NewMovePtr = ClientData->CreateSavedMove(); + FSavedMove_Character* const NewMove = NewMovePtr.Get(); + if (NewMove == nullptr) + { + return; + } + + NewMove->SetMoveFor(CharacterOwner, DeltaTime, NewAcceleration, *ClientData); + const UWorld* MyWorld = GetWorld(); + + // see if the two moves could be combined + // do not combine moves which have different TimeStamps (before and after reset). + if (const FSavedMove_Character* PendingMove = ClientData->PendingMove.Get()) + { + if (PendingMove->CanCombineWith(NewMovePtr, CharacterOwner, ClientData->MaxMoveDeltaTime * CharacterOwner->GetActorTimeDilation(*MyWorld))) + { + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCombineNetMove); + + // Only combine and move back to the start location if we don't move back in to a spot that would make us collide with something new. + const FVector OldStartLocation = PendingMove->GetRevertedLocation(); + const bool bAttachedToObject = (NewMovePtr->StartAttachParent != nullptr); + if (bAttachedToObject || !OverlapTest(OldStartLocation, PendingMove->StartRotation.Quaternion(), UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CharacterOwner)) + { + // Avoid updating Mesh bones to physics during the teleport back, since PerformMovement() will update it right away anyway below. + // Note: this must be before the FScopedMovementUpdate below, since that scope is what actually moves the character and mesh. + AVRBaseCharacter * BaseCharacter = Cast<AVRBaseCharacter>(CharacterOwner); + FScopedMeshBoneUpdateOverrideVR ScopedNoMeshBoneUpdate(CharacterOwner->GetMesh(), EKinematicBonesUpdateToPhysics::SkipAllBones); + + // Accumulate multiple transform updates until scope ends. + FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates); + UE_LOG(LogSimpleCharacterMovement, VeryVerbose, TEXT("CombineMove: add delta %f + %f and revert from %f %f to %f %f"), DeltaTime, ClientData->PendingMove->DeltaTime, UpdatedComponent->GetComponentLocation().X, UpdatedComponent->GetComponentLocation().Y, OldStartLocation.X, OldStartLocation.Y); + + NewMove->CombineWith(PendingMove, CharacterOwner, PC, OldStartLocation); + + if (PC) + { + // We reverted position to that at the start of the pending move (above), however some code paths expect rotation to be set correctly + // before character movement occurs (via FaceRotation), so try that now. The bOrientRotationToMovement path happens later as part of PerformMovement() and PhysicsRotation(). + CharacterOwner->FaceRotation(PC->GetControlRotation(), NewMove->DeltaTime); + } + + SaveBaseLocation(); + NewMove->SetInitialPosition(CharacterOwner); + + // Remove pending move from move list. It would have to be the last move on the list. + if (ClientData->SavedMoves.Num() > 0 && ClientData->SavedMoves.Last() == ClientData->PendingMove) + { + const bool bAllowShrinking = false; + ClientData->SavedMoves.Pop(bAllowShrinking); + } + ClientData->FreeMove(ClientData->PendingMove); + ClientData->PendingMove = nullptr; + PendingMove = nullptr; // Avoid dangling reference, it's deleted above. + } + else + { + UE_LOG(LogSimpleCharacterMovement, Verbose, TEXT("Not combining move [would collide at start location]")); + } + } + /*else + { + UE_LOG(LogSimpleCharacterMovement, Verbose, TEXT("Not combining move [not allowed by CanCombineWith()]")); + }*/ + } + + // Acceleration should match what we send to the server, plus any other restrictions the server also enforces (see MoveAutonomous). + Acceleration = NewMove->Acceleration.GetClampedToMaxSize(GetMaxAcceleration()); + AnalogInputModifier = ComputeAnalogInputModifier(); // recompute since acceleration may have changed. + + // Perform the move locally + CharacterOwner->ClientRootMotionParams.Clear(); + CharacterOwner->SavedRootMotion.Clear(); + PerformMovement(NewMove->DeltaTime); + + NewMove->PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Record); + + // Add NewMove to the list + if (CharacterOwner->IsReplicatingMovement()) + { + check(NewMove == NewMovePtr.Get()); + ClientData->SavedMoves.Push(NewMovePtr); + + //const bool bCanDelayMove = (CharacterMovementCVars::NetEnableMoveCombining != 0) && CanDelaySendingMove(NewMove); + static const auto CVarNetEnableMoveCombiningVRSimple = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableMoveCombining")); + const bool bCanDelayMove = (CVarNetEnableMoveCombiningVRSimple->GetInt() != 0) && CanDelaySendingMove(NewMovePtr); + + if (bCanDelayMove && ClientData->PendingMove.IsValid() == false) + { + // Decide whether to hold off on move + const float NetMoveDelta = FMath::Clamp(GetClientNetSendDeltaTime(PC, ClientData, NewMovePtr), 1.f / 120.f, 1.f / 5.f); + + if ((MyWorld->TimeSeconds - ClientData->ClientUpdateTime) * MyWorld->GetWorldSettings()->GetEffectiveTimeDilation() < NetMoveDelta) + { + // Delay sending this move. + ClientData->PendingMove = NewMovePtr; + return; + } + } + + // Uncomment 4.16 + ClientData->ClientUpdateTime = MyWorld->TimeSeconds; + + UE_CLOG(CharacterOwner&& UpdatedComponent, LogSimpleCharacterMovement, VeryVerbose, TEXT("ClientMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d) DualMove? %d"), + NewMove->TimeStamp, *NewMove->Acceleration.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), NewMove->DeltaTime, *GetMovementName(), + *GetNameSafe(NewMove->EndBase.Get()), *NewMove->EndBoneName.ToString(), MovementBaseUtility::IsDynamicBase(NewMove->EndBase.Get()) ? 1 : 0, ClientData->PendingMove.IsValid() ? 1 : 0); + + bool bSendServerMove = true; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + + // Testing options: Simulated packet loss to server + const float TimeSinceLossStart = (MyWorld->RealTimeSeconds - ClientData->DebugForcedPacketLossTimerStart); + static const auto CVarNetForceClientServerMoveLossDuration = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossDuration")); + static const auto CVarNetForceClientServerMoveLossPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossPercent")); + if (ClientData->DebugForcedPacketLossTimerStart > 0.f && (TimeSinceLossStart < CVarNetForceClientServerMoveLossDuration->GetFloat())) + { + bSendServerMove = false; + UE_LOG(LogSimpleCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat() - TimeSinceLossStart); + } + else if (CVarNetForceClientServerMoveLossPercent->GetFloat() != 0.f && (RandomStream.FRand() < CVarNetForceClientServerMoveLossPercent->GetFloat())) + { + bSendServerMove = false; + ClientData->DebugForcedPacketLossTimerStart = (CVarNetForceClientServerMoveLossDuration->GetFloat() > 0) ? MyWorld->RealTimeSeconds : 0.0f; + UE_LOG(LogSimpleCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat()); + } + else + { + ClientData->DebugForcedPacketLossTimerStart = 0.f; + } +#endif + + // Send move to server if this character is replicating movement + if (bSendServerMove) + { + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCallServerMoveVRSimple); + if (ShouldUsePackedMovementRPCs()) + { + CallServerMovePacked(NewMove, ClientData->PendingMove.Get(), OldMove.Get()); + } + /*else + { + CallServerMove(NewMove, OldMove.Get()); + }*/ + } + } + + ClientData->PendingMove = NULL; +} + +FNetworkPredictionData_Client* UVRSimpleCharacterMovementComponent::GetPredictionData_Client() const +{ + // Should only be called on client or listen server (for remote clients) in network games + check(CharacterOwner != NULL); + checkSlow(CharacterOwner->GetLocalRole() < ROLE_Authority || (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy && GetNetMode() == NM_ListenServer)); + checkSlow(GetNetMode() == NM_Client || GetNetMode() == NM_ListenServer); + + if (!ClientPredictionData) + { + UVRSimpleCharacterMovementComponent* MutableThis = const_cast<UVRSimpleCharacterMovementComponent*>(this); + MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_VRSimpleCharacter(*this); + } + + return ClientPredictionData; +} + +FNetworkPredictionData_Server* UVRSimpleCharacterMovementComponent::GetPredictionData_Server() const +{ + // Should only be called on server in network games + check(CharacterOwner != NULL); + check(CharacterOwner->GetLocalRole() == ROLE_Authority); + checkSlow(GetNetMode() < NM_Client); + + if (!ServerPredictionData) + { + UVRSimpleCharacterMovementComponent* MutableThis = const_cast<UVRSimpleCharacterMovementComponent*>(this); + MutableThis->ServerPredictionData = new FNetworkPredictionData_Server_VRSimpleCharacter(*this); + } + + return ServerPredictionData; +} + + +void FSavedMove_VRSimpleCharacter::Clear() +{ + //VRCapsuleLocation = FVector::ZeroVector; + //VRCapsuleRotation = FRotator::ZeroRotator; + LFDiff = FVector::ZeroVector; + //CustomVRInputVector = FVector::ZeroVector; + //RequestedVelocity = FVector::ZeroVector; + + FSavedMove_VRBaseCharacter::Clear(); +} + +void FSavedMove_VRSimpleCharacter::SetInitialPosition(ACharacter* C) +{ + // See if we can get the VR capsule location + if (AVRSimpleCharacter * VRC = Cast<AVRSimpleCharacter>(C)) + { + if (VRC->VRMovementReference) + { + LFDiff = VRC->VRMovementReference->AdditionalVRInputVector; + + //CustomVRInputVector = VRC->VRMovementReference->CustomVRInputVector; + + /* if (VRC->VRMovementReference->HasRequestedVelocity()) + RequestedVelocity = VRC->VRMovementReference->RequestedVelocity; + else + RequestedVelocity = FVector::ZeroVector;*/ + } + else + { + LFDiff = FVector::ZeroVector; + //CustomVRInputVector = FVector::ZeroVector; + // RequestedVelocity = FVector::ZeroVector; + } + + } + FSavedMove_VRBaseCharacter::SetInitialPosition(C); +} + +void FSavedMove_VRSimpleCharacter::PrepMoveFor(ACharacter* Character) +{ + UVRSimpleCharacterMovementComponent * CharMove = Cast<UVRSimpleCharacterMovementComponent>(Character->GetCharacterMovement()); + + // Set capsule location prior to testing movement + // I am overriding the replicated value here when movement is made on purpose + if (CharMove) + { + CharMove->AdditionalVRInputVector = FVector(LFDiff.X, LFDiff.Y, 0.0f); + } + + if (AVRBaseCharacter * BaseChar = Cast<AVRBaseCharacter>(CharMove->GetCharacterOwner())) + { + if (BaseChar->VRReplicateCapsuleHeight && LFDiff.Z != CharMove->VRRootCapsule->GetUnscaledCapsuleHalfHeight()) + { + BaseChar->SetCharacterHalfHeightVR(LFDiff.Z, false); + //CharMove->VRRootCapsule->SetCapsuleHalfHeightVR(LFDiff.Z, false); + } + } + + FSavedMove_VRBaseCharacter::PrepMoveFor(Character); +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRAIController.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRAIController.cpp new file mode 100644 index 0000000..e8550b5 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRAIController.cpp @@ -0,0 +1,140 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRAIController.h" +//#include "VRBPDatatypes.h" +#include "VRBaseCharacter.h" +#include "Components/CapsuleComponent.h" +#include "NetworkingDistanceConstants.h" // Needed for the LinOfSightTo function override to work +#include "Navigation/CrowdFollowingComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + + +FVector AVRAIController::GetFocalPointOnActor(const AActor *Actor) const +{ + if (const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(Actor)) + { + return VRChar->GetVRLocation_Inline(); + } + else + return Super::GetFocalPointOnActor(Actor); // Actor != nullptr ? Actor->GetActorLocation() : FAISystem::InvalidLocation; +} + +bool AVRAIController::LineOfSightTo(const AActor* Other, FVector ViewPoint, bool bAlternateChecks) const +{ + if (Other == nullptr) + { + return false; + } + + if (ViewPoint.IsZero()) + { + FRotator ViewRotation; + GetActorEyesViewPoint(ViewPoint, ViewRotation); + + // if we still don't have a view point we simply fail + if (ViewPoint.IsZero()) + { + return false; + } + } + + static FName NAME_LineOfSight = FName(TEXT("LineOfSight")); + FVector TargetLocation = Other->GetTargetLocation(GetPawn()); + + FCollisionQueryParams CollisionParams(NAME_LineOfSight, true, this->GetPawn()); + CollisionParams.AddIgnoredActor(Other); + + bool bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, TargetLocation, ECC_Visibility, CollisionParams); + if (!bHit) + { + return true; + } + + // if other isn't using a cylinder for collision and isn't a Pawn (which already requires an accurate cylinder for AI) + // then don't go any further as it likely will not be tracing to the correct location + const APawn * OtherPawn = Cast<const APawn>(Other); + if (!OtherPawn && Cast<UCapsuleComponent>(Other->GetRootComponent()) == NULL) + { + return false; + } + + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(Other); + const FVector OtherActorLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : Other->GetActorLocation(); + + const float DistSq = (OtherActorLocation - ViewPoint).SizeSquared(); + if (DistSq > FARSIGHTTHRESHOLDSQUARED) + { + return false; + } + + if (!OtherPawn && (DistSq > NEARSIGHTTHRESHOLDSQUARED)) + { + return false; + } + + float OtherRadius, OtherHeight; + Other->GetSimpleCollisionCylinder(OtherRadius, OtherHeight); + + if (!bAlternateChecks || !bLOSflag) + { + //try viewpoint to head + bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, OtherActorLocation + FVector(0.f, 0.f, OtherHeight), ECC_Visibility, CollisionParams); + if (!bHit) + { + return true; + } + } + + if (!bSkipExtraLOSChecks && (!bAlternateChecks || bLOSflag)) + { + // only check sides if width of other is significant compared to distance + if (OtherRadius * OtherRadius / (OtherActorLocation - ViewPoint).SizeSquared() < 0.0001f) + { + return false; + } + //try checking sides - look at dist to four side points, and cull furthest and closest + FVector Points[4]; + Points[0] = OtherActorLocation - FVector(OtherRadius, -1 * OtherRadius, 0); + Points[1] = OtherActorLocation + FVector(OtherRadius, OtherRadius, 0); + Points[2] = OtherActorLocation - FVector(OtherRadius, OtherRadius, 0); + Points[3] = OtherActorLocation + FVector(OtherRadius, -1 * OtherRadius, 0); + int32 IndexMin = 0; + int32 IndexMax = 0; + float CurrentMax = (Points[0] - ViewPoint).SizeSquared(); + float CurrentMin = CurrentMax; + for (int32 PointIndex = 1; PointIndex < 4; PointIndex++) + { + const float NextSize = (Points[PointIndex] - ViewPoint).SizeSquared(); + if (NextSize > CurrentMin) + { + CurrentMin = NextSize; + IndexMax = PointIndex; + } + else if (NextSize < CurrentMax) + { + CurrentMax = NextSize; + IndexMin = PointIndex; + } + } + + for (int32 PointIndex = 0; PointIndex < 4; PointIndex++) + { + if ((PointIndex != IndexMin) && (PointIndex != IndexMax)) + { + bHit = GetWorld()->LineTraceTestByChannel(ViewPoint, Points[PointIndex], ECC_Visibility, CollisionParams); + if (!bHit) + { + return true; + } + } + } + } + return false; +} + +AVRDetourCrowdAIController::AVRDetourCrowdAIController(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass<UCrowdFollowingComponent>(TEXT("PathFollowingComponent"))) +{ + +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBPDatatypes.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBPDatatypes.cpp new file mode 100644 index 0000000..3dfcd95 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBPDatatypes.cpp @@ -0,0 +1,217 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "VRBPDatatypes.h" + +namespace VRDataTypeCVARs +{ + // Doing it this way because I want as little rep and perf impact as possible and sampling a static var is that. + // This is to specifically help out very rare cases, DON'T USE THIS UNLESS YOU HAVE NO CHOICE + static int32 RepHighPrecisionTransforms = 0; + FAutoConsoleVariableRef CVarRepHighPrecisionTransforms( + TEXT("vrexp.RepHighPrecisionTransforms"), + RepHighPrecisionTransforms, + TEXT("When on, will rep Quantized transforms at full precision, WARNING use at own risk, if this isn't the same setting client & server then it will crash.\n") + TEXT("0: Disable, 1: Enable"), + ECVF_Default); +} + +bool FTransform_NetQuantize::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + bOutSuccess = true; + + FVector rTranslation; + FVector rScale3D; + //FQuat rRotation; + FRotator rRotation; + + uint16 ShortPitch = 0; + uint16 ShortYaw = 0; + uint16 ShortRoll = 0; + + bool bUseHighPrecision = VRDataTypeCVARs::RepHighPrecisionTransforms > 0; + + if (Ar.IsSaving()) + { + // Because transforms can be vectorized or not, need to use the inline retrievers + rTranslation = this->GetTranslation(); + rScale3D = this->GetScale3D(); + rRotation = this->Rotator();//this->GetRotation(); + + if (bUseHighPrecision) + { + Ar << rTranslation; + Ar << rScale3D; + Ar << rRotation; + } + else + { + // Translation set to 2 decimal precision + bOutSuccess &= SerializePackedVector<100, 30>(rTranslation, Ar); + + // Scale set to 2 decimal precision, had it 1 but realized that I used two already even + bOutSuccess &= SerializePackedVector<100, 30>(rScale3D, Ar); + + // Rotation converted to FRotator and short compressed, see below for conversion reason + // FRotator already serializes compressed short by default but I can save a func call here + rRotation.SerializeCompressedShort(Ar); + } + + + //Ar << rRotation; + + // If I converted it to a rotator and serialized as shorts I would save 6 bytes. + // I am unsure about a safe method of compressed serializing a quat, though I read through smallest three + // Epic already drops W from the Quat and reconstructs it after the send. + // Converting to rotator first may have conversion issues and has a perf cost....needs testing, epic attempts to handle + // Singularities in their conversion but I haven't tested it in all circumstances + //rRotation.SerializeCompressedShort(Ar); + } + else // If loading + { + + if (bUseHighPrecision) + { + Ar << rTranslation; + Ar << rScale3D; + Ar << rRotation; + } + else + { + bOutSuccess &= SerializePackedVector<100, 30>(rTranslation, Ar); + bOutSuccess &= SerializePackedVector<100, 30>(rScale3D, Ar); + rRotation.SerializeCompressedShort(Ar); + } + + //Ar << rRotation; + + // Set it + this->SetComponents(rRotation.Quaternion(), rTranslation, rScale3D); + this->NormalizeRotation(); + } + + return bOutSuccess; +} + +// ** Euro Low Pass Filter ** // + +void FBPEuroLowPassFilter::ResetSmoothingFilter() +{ + RawFilter.bFirstTime = true; + DeltaFilter.bFirstTime = true; +} + +FVector FBPEuroLowPassFilter::RunFilterSmoothing(const FVector &InRawValue, const float &InDeltaTime) +{ + if (InDeltaTime <= 0.0f) + { + // Invalid delta time, return the in value + return InRawValue; + } + + // Calculate the delta, if this is the first time then there is no delta + const FVector Delta = RawFilter.bFirstTime == true ? FVector::ZeroVector : (InRawValue - RawFilter.PreviousRaw) * 1.0f / InDeltaTime; + + // Filter the delta to get the estimated + const FVector Estimated = DeltaFilter.Filter(Delta, FVector(DeltaFilter.CalculateAlphaTau(DeltaCutoff, InDeltaTime))); + + // Use the estimated to calculate the cutoff + const FVector Cutoff = DeltaFilter.CalculateCutoff(Estimated, MinCutoff, CutoffSlope); + + // Filter passed value + return RawFilter.Filter(InRawValue, RawFilter.CalculateAlpha(Cutoff, InDeltaTime)); +} + +void FBPEuroLowPassFilterQuat::ResetSmoothingFilter() +{ + RawFilter.bFirstTime = true; + DeltaFilter.bFirstTime = true; +} + +FQuat FBPEuroLowPassFilterQuat::RunFilterSmoothing(const FQuat& InRawValue, const float& InDeltaTime) +{ + if (InDeltaTime <= 0.0f) + { + // Invalid delta time, return the in value + return InRawValue; + } + + FQuat NewInVal = InRawValue; + if (!RawFilter.bFirstTime) + { + // fix axial flipping, from unity open 1 Euro implementation + FVector4 PrevQuatAsVector(RawFilter.Previous.X, RawFilter.Previous.Y, RawFilter.Previous.Z, RawFilter.Previous.W); + FVector4 CurrQuatAsVector(InRawValue.X, InRawValue.Y, InRawValue.Z, InRawValue.W); + if ((PrevQuatAsVector - CurrQuatAsVector).SizeSquared() > 2) + NewInVal = FQuat(-InRawValue.X, -InRawValue.Y, -InRawValue.Z, -InRawValue.W); + } + + // Calculate the delta, if this is the first time then there is no delta + FQuat Delta = FQuat::Identity; + + if (!RawFilter.bFirstTime) + { + Delta = (NewInVal - RawFilter.PreviousRaw) * (1.0f / InDeltaTime); + } + + + float AlphaTau = DeltaFilter.CalculateAlphaTau(DeltaCutoff, InDeltaTime); + FQuat AlphaTauQ(AlphaTau, AlphaTau, AlphaTau, AlphaTau); + const FQuat Estimated = DeltaFilter.Filter(Delta, AlphaTauQ); + + // Use the estimated to calculate the cutoff + const FQuat Cutoff = DeltaFilter.CalculateCutoff(Estimated, MinCutoff, CutoffSlope); + + // Filter passed value + return RawFilter.Filter(NewInVal, RawFilter.CalculateAlpha(Cutoff, InDeltaTime)).GetNormalized();// .GetNormalized(); +} + +void FBPEuroLowPassFilterTrans::ResetSmoothingFilter() +{ + RawFilter.bFirstTime = true; + DeltaFilter.bFirstTime = true; +} + +FTransform FBPEuroLowPassFilterTrans::RunFilterSmoothing(const FTransform& InRawValue, const float& InDeltaTime) +{ + if (InDeltaTime <= 0.0f) + { + // Invalid delta time, return the in value + return InRawValue; + } + + FTransform NewInVal = InRawValue; + if (!RawFilter.bFirstTime) + { + FQuat TransQ = NewInVal.GetRotation(); + FQuat PrevQ = RawFilter.Previous.GetRotation(); + // fix axial flipping, from unity open 1 Euro implementation + FVector4 PrevQuatAsVector(PrevQ.X, PrevQ.Y, PrevQ.Z, PrevQ.W); + FVector4 CurrQuatAsVector(TransQ.X, TransQ.Y, TransQ.Z, TransQ.W); + if ((PrevQuatAsVector - CurrQuatAsVector).SizeSquared() > 2) + NewInVal.SetRotation(FQuat(-TransQ.X, -TransQ.Y, -TransQ.Z, -TransQ.W)); + } + + // Calculate the delta, if this is the first time then there is no delta + FTransform Delta = FTransform::Identity; + + float Frequency = 1.0f / InDeltaTime; + if (!RawFilter.bFirstTime) + { + Delta.SetLocation((NewInVal.GetLocation() - RawFilter.PreviousRaw.GetLocation()) * Frequency); + Delta.SetRotation((NewInVal.GetRotation() - RawFilter.PreviousRaw.GetRotation()) * Frequency); + Delta.SetScale3D((NewInVal.GetScale3D() - RawFilter.PreviousRaw.GetScale3D()) * Frequency); + } + + + float AlphaTau = DeltaFilter.CalculateAlphaTau(DeltaCutoff, InDeltaTime); + FTransform AlphaTauQ(FQuat(AlphaTau, AlphaTau, AlphaTau, AlphaTau), FVector(AlphaTau), FVector(AlphaTau)); + const FTransform Estimated = DeltaFilter.Filter(Delta, AlphaTauQ); + + // Use the estimated to calculate the cutoff + const FTransform Cutoff = DeltaFilter.CalculateCutoff(Estimated, MinCutoff, CutoffSlope); + + FTransform NewTrans = RawFilter.Filter(NewInVal, RawFilter.CalculateAlpha(Cutoff, InDeltaTime)); + NewTrans.NormalizeRotation(); + // Filter passed value + return NewTrans; +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacter.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacter.cpp new file mode 100644 index 0000000..ecb5a10 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacter.cpp @@ -0,0 +1,1107 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRBaseCharacter.h" +#include "VRPlayerController.h" +#include "NavigationSystem.h" +#include "GameFramework/Controller.h" +#include "Components/CapsuleComponent.h" +#include "ParentRelativeAttachmentComponent.h" +#include "GripMotionControllerComponent.h" +#include "VRPathFollowingComponent.h" +#include "Net/UnrealNetwork.h" +#include "XRMotionControllerBase.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + +DEFINE_LOG_CATEGORY(LogBaseVRCharacter); + +FName AVRBaseCharacter::LeftMotionControllerComponentName(TEXT("Left Grip Motion Controller")); +FName AVRBaseCharacter::RightMotionControllerComponentName(TEXT("Right Grip Motion Controller")); +FName AVRBaseCharacter::ReplicatedCameraComponentName(TEXT("VR Replicated Camera")); +FName AVRBaseCharacter::ParentRelativeAttachmentComponentName(TEXT("Parent Relative Attachment")); +FName AVRBaseCharacter::SmoothingSceneParentComponentName(TEXT("NetSmoother")); +FName AVRBaseCharacter::VRProxyComponentName(TEXT("VRProxy")); + + +FRepMovementVRCharacter::FRepMovementVRCharacter() +: Super() +{ + bJustTeleported = false; + bJustTeleportedGrips = false; + bPausedTracking = false; + PausedTrackingLoc = FVector::ZeroVector; + PausedTrackingRot = 0.f; + Owner = nullptr; +} + +AVRBaseCharacter::AVRBaseCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer/*.DoNotCreateDefaultSubobject(ACharacter::MeshComponentName)*/.SetDefaultSubobjectClass<UVRBaseCharacterMovementComponent>(ACharacter::CharacterMovementComponentName)) + +{ + + FRepMovement& MovementRep = GetReplicatedMovement_Mutable(); + + // Remove the movement jitter with slow speeds + MovementRep.LocationQuantizationLevel = EVectorQuantization::RoundTwoDecimals; + + if (UCapsuleComponent * cap = GetCapsuleComponent()) + { + cap->SetCapsuleSize(16.0f, 96.0f); + cap->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); + cap->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block); + } + + NetSmoother = CreateDefaultSubobject<USceneComponent>(AVRBaseCharacter::SmoothingSceneParentComponentName); + if (NetSmoother) + { + NetSmoother->SetupAttachment(RootComponent); + } + + VRProxyComponent = CreateDefaultSubobject<USceneComponent>(AVRBaseCharacter::VRProxyComponentName); + if (NetSmoother && VRProxyComponent) + { + VRProxyComponent->SetupAttachment(NetSmoother); + } + + VRReplicatedCamera = CreateDefaultSubobject<UReplicatedVRCameraComponent>(AVRBaseCharacter::ReplicatedCameraComponentName); + if (VRReplicatedCamera) + { + VRReplicatedCamera->bOffsetByHMD = false; + VRReplicatedCamera->SetupAttachment(VRProxyComponent); + VRReplicatedCamera->OverrideSendTransform = &AVRBaseCharacter::Server_SendTransformCamera; + } + + VRMovementReference = NULL; + if (GetMovementComponent()) + { + VRMovementReference = Cast<UVRBaseCharacterMovementComponent>(GetMovementComponent()); + //AddTickPrerequisiteComponent(this->GetCharacterMovement()); + } + + ParentRelativeAttachment = CreateDefaultSubobject<UParentRelativeAttachmentComponent>(AVRBaseCharacter::ParentRelativeAttachmentComponentName); + if (ParentRelativeAttachment && VRReplicatedCamera) + { + // Moved this to be root relative as the camera late updates were killing how it worked + ParentRelativeAttachment->SetupAttachment(VRProxyComponent); + ParentRelativeAttachment->bOffsetByHMD = false; + ParentRelativeAttachment->AddTickPrerequisiteComponent(VRReplicatedCamera); + + if (USkeletalMeshComponent * SKMesh = GetMesh()) + { + SKMesh->SetupAttachment(ParentRelativeAttachment); + } + } + + LeftMotionController = CreateDefaultSubobject<UGripMotionControllerComponent>(AVRBaseCharacter::LeftMotionControllerComponentName); + if (IsValid(LeftMotionController)) + { + LeftMotionController->SetupAttachment(VRProxyComponent); + //LeftMotionController->MotionSource = FXRMotionControllerBase::LeftHandSourceId; + LeftMotionController->SetTrackingMotionSource(FXRMotionControllerBase::LeftHandSourceId); + //LeftMotionController->Hand = EControllerHand::Left; + LeftMotionController->bOffsetByHMD = false; + //LeftMotionController->bUpdateInCharacterMovement = true; + // Keep the controllers ticking after movement + LeftMotionController->AddTickPrerequisiteComponent(GetCharacterMovement()); + LeftMotionController->OverrideSendTransform = &AVRBaseCharacter::Server_SendTransformLeftController; + } + + RightMotionController = CreateDefaultSubobject<UGripMotionControllerComponent>(AVRBaseCharacter::RightMotionControllerComponentName); + if (IsValid(RightMotionController)) + { + RightMotionController->SetupAttachment(VRProxyComponent); + //RightMotionController->MotionSource = FXRMotionControllerBase::RightHandSourceId; + RightMotionController->SetTrackingMotionSource(FXRMotionControllerBase::RightHandSourceId); + //RightMotionController->Hand = EControllerHand::Right; + RightMotionController->bOffsetByHMD = false; + //RightMotionController->bUpdateInCharacterMovement = true; + // Keep the controllers ticking after movement + RightMotionController->AddTickPrerequisiteComponent(GetCharacterMovement()); + RightMotionController->OverrideSendTransform = &AVRBaseCharacter::Server_SendTransformRightController; + } + + OffsetComponentToWorld = FTransform(FQuat(0.0f, 0.0f, 0.0f, 1.0f), FVector::ZeroVector, FVector(1.0f)); + + + // Setting a minimum of every frame for replication consideration (UT uses this value for characters and projectiles). + // Otherwise we will get some massive slow downs if the replication is allowed to hit the 2 per second minimum default + MinNetUpdateFrequency = 100.0f; + + // This is for smooth turning, we have more of a use for this than FPS characters do + // Due to roll/pitch almost never being off 0 for VR the cost is just one byte so i'm fine defaulting it here + // End users can reset to byte components if they ever want too. + MovementRep.RotationQuantizationLevel = ERotatorQuantization::ShortComponents; + + VRReplicateCapsuleHeight = false; + + bUseExperimentalUnseatModeFix = true; + + ReplicatedMovementVR.Owner = this; + bFlagTeleported = false; + bTrackingPaused = false; + PausedTrackingLoc = FVector::ZeroVector; + PausedTrackingRot = 0.f; +} + + void AVRBaseCharacter::PossessedBy(AController* NewController) + { + Super::PossessedBy(NewController); + OwningVRPlayerController = Cast<AVRPlayerController>(Controller); + } + +void AVRBaseCharacter::OnRep_Controller() +{ + Super::OnRep_Controller(); + OwningVRPlayerController = Cast<AVRPlayerController>(Controller); +} + +void AVRBaseCharacter::OnRep_PlayerState() +{ + OnPlayerStateReplicated_Bind.Broadcast(GetPlayerState()); + Super::OnRep_PlayerState(); +} + +void AVRBaseCharacter::PostInitializeComponents() +{ + QUICK_SCOPE_CYCLE_COUNTER(STAT_Character_PostInitComponents); + + Super::PostInitializeComponents(); + + if (IsValid(this)) + { + if (NetSmoother) + { + CacheInitialMeshOffset(NetSmoother->GetRelativeLocation(), NetSmoother->GetRelativeRotation()); + } + + if (USkeletalMeshComponent * myMesh = GetMesh()) + { + // force animation tick after movement component updates + if (myMesh->PrimaryComponentTick.bCanEverTick && GetMovementComponent()) + { + myMesh->PrimaryComponentTick.AddPrerequisite(GetMovementComponent(), GetMovementComponent()->PrimaryComponentTick); + } + } + + if (GetCharacterMovement() && GetCapsuleComponent()) + { + GetCharacterMovement()->UpdateNavAgent(*GetCapsuleComponent()); + } + + if (Controller == nullptr && GetNetMode() != NM_Client) + { + if (GetCharacterMovement() && GetCharacterMovement()->bRunPhysicsWithNoController) + { + GetCharacterMovement()->SetDefaultMovementMode(); + } + } + } +} + +/*void AVRBaseCharacter::CacheInitialMeshOffset(FVector MeshRelativeLocation, FRotator MeshRelativeRotation) +{ + BaseTranslationOffset = MeshRelativeLocation; + BaseRotationOffset = MeshRelativeRotation.Quaternion(); + +#if ENABLE_NAN_DIAGNOSTIC + if (BaseRotationOffset.ContainsNaN()) + { + logOrEnsureNanError(TEXT("ACharacter::PostInitializeComponents detected NaN in BaseRotationOffset! (%s)"), *BaseRotationOffset.ToString()); + } + + if (GetMesh()) + { + const FRotator LocalRotation = GetMesh()->GetRelativeRotation(); + if (LocalRotation.ContainsNaN()) + { + logOrEnsureNanError(TEXT("ACharacter::PostInitializeComponents detected NaN in Mesh->RelativeRotation! (%s)"), *LocalRotation.ToString()); + } + } +#endif +}*/ + +void AVRBaseCharacter::GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + DOREPLIFETIME_CONDITION(AVRBaseCharacter, SeatInformation, COND_None); + DOREPLIFETIME_CONDITION(AVRBaseCharacter, VRReplicateCapsuleHeight, COND_None); + DOREPLIFETIME_CONDITION(AVRBaseCharacter, ReplicatedCapsuleHeight, COND_SimulatedOnly); + + DISABLE_REPLICATED_PRIVATE_PROPERTY(AActor, ReplicatedMovement); + + DOREPLIFETIME_CONDITION_NOTIFY(AVRBaseCharacter, ReplicatedMovementVR, COND_SimulatedOrPhysics, REPNOTIFY_Always); +} + +void AVRBaseCharacter::PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) +{ + Super::PreReplication(ChangedPropertyTracker); + + DOREPLIFETIME_ACTIVE_OVERRIDE(AVRBaseCharacter, ReplicatedCapsuleHeight, VRReplicateCapsuleHeight); + DOREPLIFETIME_ACTIVE_OVERRIDE(AVRBaseCharacter, ReplicatedMovementVR, IsReplicatingMovement()); +} + +/*USkeletalMeshComponent* AVRBaseCharacter::GetIKMesh_Implementation() const +{ + return GetMesh(); +// return nullptr; +}*/ + +bool AVRBaseCharacter::Server_SetSeatedMode_Validate(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform_NetQuantize TargetTransform, FTransform_NetQuantize InitialRelCameraTransform, float AllowedRadius, float AllowedRadiusThreshold, bool bZeroToHead, EVRConjoinedMovementModes PostSeatedMovementMode) +{ + return true; +} + +void AVRBaseCharacter::Server_SetSeatedMode_Implementation(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform_NetQuantize TargetTransform, FTransform_NetQuantize InitialRelCameraTransform, float AllowedRadius, float AllowedRadiusThreshold, bool bZeroToHead, EVRConjoinedMovementModes PostSeatedMovementMode) +{ + SetSeatedMode(SeatParent, bSetSeatedMode, TargetTransform, InitialRelCameraTransform, AllowedRadius, AllowedRadiusThreshold, bZeroToHead, PostSeatedMovementMode); +} + +void AVRBaseCharacter::Server_ReZeroSeating_Implementation(FTransform_NetQuantize NewTargetTransform, FTransform_NetQuantize NewInitialRelCameraTransform, bool bZeroToHead) +{ + SeatInformation.StoredTargetTransform = NewTargetTransform; + SeatInformation.InitialRelCameraTransform = NewInitialRelCameraTransform; + + // Purify the yaw of the initial rotation + SeatInformation.InitialRelCameraTransform.SetRotation(UVRExpansionFunctionLibrary::GetHMDPureYaw_I(NewInitialRelCameraTransform.Rotator()).Quaternion()); + + // #TODO: Need to handle non 1 scaled values here eventually + if (bZeroToHead) + { + FVector newLocation = SeatInformation.InitialRelCameraTransform.GetTranslation(); + SeatInformation.StoredTargetTransform.AddToTranslation(FVector(0, 0, -newLocation.Z)); + } + + OnRep_SeatedCharInfo(); +} + +bool AVRBaseCharacter::Server_ReZeroSeating_Validate(FTransform_NetQuantize NewTargetTransform, FTransform_NetQuantize NewInitialRelCameraTransform, bool bZeroToHead) +{ + return true; +} + +void AVRBaseCharacter::OnCustomMoveActionPerformed_Implementation(EVRMoveAction MoveActionType, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags) +{ + +} + +void AVRBaseCharacter::OnBeginWallPushback_Implementation(FHitResult HitResultOfImpact, bool bHadLocomotionInput, FVector HmdInput) +{ + +} + +void AVRBaseCharacter::OnEndWallPushback_Implementation() +{ + +} + +void AVRBaseCharacter::OnClimbingSteppedUp_Implementation() +{ + +} + +void AVRBaseCharacter::Server_SendTransformCamera_Implementation(FBPVRComponentPosRep NewTransform) +{ + if(VRReplicatedCamera) + VRReplicatedCamera->Server_SendCameraTransform_Implementation(NewTransform); +} + +bool AVRBaseCharacter::Server_SendTransformCamera_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +void AVRBaseCharacter::Server_SendTransformLeftController_Implementation(FBPVRComponentPosRep NewTransform) +{ + if (IsValid(LeftMotionController)) + LeftMotionController->Server_SendControllerTransform_Implementation(NewTransform); +} + +bool AVRBaseCharacter::Server_SendTransformLeftController_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} + +void AVRBaseCharacter::Server_SendTransformRightController_Implementation(FBPVRComponentPosRep NewTransform) +{ + if(IsValid(RightMotionController)) + RightMotionController->Server_SendControllerTransform_Implementation(NewTransform); +} + +bool AVRBaseCharacter::Server_SendTransformRightController_Validate(FBPVRComponentPosRep NewTransform) +{ + return true; + // Optionally check to make sure that player is inside of their bounds and deny it if they aren't? +} +FVector AVRBaseCharacter::GetTeleportLocation(FVector OriginalLocation) +{ + return OriginalLocation; +} + + +void AVRBaseCharacter::NotifyOfTeleport(bool bRegisterAsTeleport) +{ + if (bRegisterAsTeleport) + { + if (GetNetMode() < ENetMode::NM_Client) + bFlagTeleported = true; + + if (VRMovementReference) + { + VRMovementReference->bNotifyTeleported = true; + } + } + + if (GetNetMode() < ENetMode::NM_Client) + { + if (bRegisterAsTeleport) + { + bFlagTeleported = true; + } + else + { + bFlagTeleportedGrips = true; + } + } + + if (IsValid(LeftMotionController)) + LeftMotionController->bIsPostTeleport = true; + + if (IsValid(RightMotionController)) + RightMotionController->bIsPostTeleport = true; +} + +void AVRBaseCharacter::OnRep_ReplicatedMovement() +{ + FRepMovement& ReppedMovement = GetReplicatedMovement_Mutable(); + + ReppedMovement.AngularVelocity = ReplicatedMovementVR.AngularVelocity; + ReppedMovement.bRepPhysics = ReplicatedMovementVR.bRepPhysics; + ReppedMovement.bSimulatedPhysicSleep = ReplicatedMovementVR.bSimulatedPhysicSleep; + ReppedMovement.LinearVelocity = ReplicatedMovementVR.LinearVelocity; + ReppedMovement.Location = ReplicatedMovementVR.Location; + ReppedMovement.Rotation = ReplicatedMovementVR.Rotation; + + Super::OnRep_ReplicatedMovement(); + + if (!IsLocallyControlled()) + { + if (ReplicatedMovementVR.bJustTeleported) + { + // Server should never get this value so it shouldn't be double throwing for them + NotifyOfTeleport(); + } + else if (ReplicatedMovementVR.bJustTeleportedGrips) + { + NotifyOfTeleport(false); + } + + bTrackingPaused = ReplicatedMovementVR.bPausedTracking; + if (bTrackingPaused) + { + PausedTrackingLoc = ReplicatedMovementVR.PausedTrackingLoc; + PausedTrackingRot = ReplicatedMovementVR.PausedTrackingRot; + } + } +} + +void AVRBaseCharacter::GatherCurrentMovement() +{ + Super::GatherCurrentMovement(); + + FRepMovement ReppedMovement = this->GetReplicatedMovement(); + + ReplicatedMovementVR.AngularVelocity = ReppedMovement.AngularVelocity; + ReplicatedMovementVR.bRepPhysics = ReppedMovement.bRepPhysics; + ReplicatedMovementVR.bSimulatedPhysicSleep = ReppedMovement.bSimulatedPhysicSleep; + ReplicatedMovementVR.LinearVelocity = ReppedMovement.LinearVelocity; + ReplicatedMovementVR.Location = ReppedMovement.Location; + ReplicatedMovementVR.Rotation = ReppedMovement.Rotation; + ReplicatedMovementVR.bJustTeleported = bFlagTeleported; + ReplicatedMovementVR.bJustTeleportedGrips = bFlagTeleportedGrips; + bFlagTeleported = false; + bFlagTeleportedGrips = false; + ReplicatedMovementVR.bPausedTracking = bTrackingPaused; + ReplicatedMovementVR.PausedTrackingLoc = PausedTrackingLoc; + ReplicatedMovementVR.PausedTrackingRot = PausedTrackingRot; + +} + + +void AVRBaseCharacter::OnRep_SeatedCharInfo() +{ + // Handle setting up the player here + + if (UPrimitiveComponent * root = Cast<UPrimitiveComponent>(GetRootComponent())) + { + if (SeatInformation.bSitting /*&& !SeatInformation.bWasSeated*/) // Removing WasSeated check because we may be switching seats + { + if (SeatInformation.bWasSeated) + { + if (SeatInformation.SeatParent != this->GetRootComponent()->GetAttachParent()) + { + InitSeatedModeTransition(); + } + else // Is just a reposition + { + //if (this->Role != ROLE_SimulatedProxy) + ZeroToSeatInformation(); + } + + } + else + { + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + /*if (UVRBaseCharacterMovementComponent * charMovement = Cast<UVRBaseCharacterMovementComponent>(GetMovementComponent())) + { + charMovement->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + }*/ + } + else + { + if (VRMovementReference) + { + VRMovementReference->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + } + } + } + } + else if (!SeatInformation.bSitting && SeatInformation.bWasSeated) + { + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + + /*if (UVRBaseCharacterMovementComponent * charMovement = Cast<UVRBaseCharacterMovementComponent>(GetMovementComponent())) + { + charMovement->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + //charMovement->SetComponentTickEnabled(true); + }*/ + + } + else + { + if (VRMovementReference) + { + VRMovementReference->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + } + } + } + } +} + +void AVRBaseCharacter::InitSeatedModeTransition() +{ + if (UPrimitiveComponent * root = Cast<UPrimitiveComponent>(GetRootComponent())) + { + if (SeatInformation.bSitting /*&& !SeatInformation.bWasSeated*/) // Removing WasSeated check because we may be switching seats + { + + if (SeatInformation.SeatParent /*&& !root->IsAttachedTo(SeatInformation.SeatParent)*/) + { + FAttachmentTransformRules TransformRule = FAttachmentTransformRules::SnapToTargetNotIncludingScale; + TransformRule.bWeldSimulatedBodies = true; + AttachToComponent(SeatInformation.SeatParent, TransformRule); + } + + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + if (VRMovementReference) + { + //charMovement->DisableMovement(); + //charMovement->SetComponentTickEnabled(false); + //charMovement->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + } + + root->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + // Set it before it is set below + if (!SeatInformation.bWasSeated) + SeatInformation.bOriginalControlRotation = bUseControllerRotationYaw; + + SeatInformation.bWasSeated = true; + bUseControllerRotationYaw = false; // This forces rotation in world space, something that we don't want + ZeroToSeatInformation(); + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + } + else + { + if (VRMovementReference) + { + //charMovement->DisableMovement(); + //charMovement->SetComponentTickEnabled(false); + //charMovement->SetMovementMode(MOVE_Custom, (uint8)EVRCustomMovementMode::VRMOVE_Seated); + //charMovement->bIgnoreClientMovementErrorChecksAndCorrection = true; + + if (this->GetLocalRole() == ROLE_AutonomousProxy) + { + FNetworkPredictionData_Client_Character* ClientData = VRMovementReference->GetPredictionData_Client_Character(); + check(ClientData); + + if (ClientData->SavedMoves.Num()) + { + // Ack our most recent move, we don't want to start sending old moves after un seating. + ClientData->AckMove(ClientData->SavedMoves.Num() - 1, *VRMovementReference); + } + } + + } + + root->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + // Set it before it is set below + if (!SeatInformation.bWasSeated) + { + SeatInformation.bOriginalControlRotation = bUseControllerRotationYaw; + } + + SeatInformation.bWasSeated = true; + bUseControllerRotationYaw = false; // This forces rotation in world space, something that we don't want + + ZeroToSeatInformation(); + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + } + } + else if (!SeatInformation.bSitting && SeatInformation.bWasSeated) + { + DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + if (this->GetLocalRole() == ROLE_SimulatedProxy) + { + root->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + + + bUseControllerRotationYaw = SeatInformation.bOriginalControlRotation; + + SetActorLocationAndRotationVR(SeatInformation.StoredTargetTransform.GetTranslation(), SeatInformation.StoredTargetTransform.Rotator(), true, true, true); + + if (IsValid(LeftMotionController)) + { + LeftMotionController->PostTeleportMoveGrippedObjects(); + } + + if (IsValid(RightMotionController)) + { + RightMotionController->PostTeleportMoveGrippedObjects(); + } + + /*if (UVRBaseCharacterMovementComponent * charMovement = Cast<UVRBaseCharacterMovementComponent>(GetMovementComponent())) + { + charMovement->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + //charMovement->SetComponentTickEnabled(true); + }*/ + + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + } + else + { + if (VRMovementReference) + { + //charMovement->ApplyReplicatedMovementMode(SeatInformation.PostSeatedMovementMode); + //charMovement->bIgnoreClientMovementErrorChecksAndCorrection = false; + //charMovement->SetComponentTickEnabled(true); + + if (this->GetLocalRole() == ROLE_Authority) + { + if (bUseExperimentalUnseatModeFix) + { + VRMovementReference->bJustUnseated = true; + FNetworkPredictionData_Server_Character * ServerData = VRMovementReference->GetPredictionData_Server_Character(); + check(ServerData); + ServerData->CurrentClientTimeStamp = 0.0f; + ServerData->PendingAdjustment = FClientAdjustment(); + //ServerData->CurrentClientTimeStamp = 0.f; + //ServerData->ServerAccumulatedClientTimeStamp = 0.0f; + //ServerData->LastUpdateTime = 0.f; + ServerData->ServerTimeStampLastServerMove = 0.f; + ServerData->bForceClientUpdate = false; + ServerData->TimeDiscrepancy = 0.f; + ServerData->bResolvingTimeDiscrepancy = false; + ServerData->TimeDiscrepancyResolutionMoveDeltaOverride = 0.f; + ServerData->TimeDiscrepancyAccumulatedClientDeltasSinceLastServerTick = 0.f; + } + //charMovement->ForceReplicationUpdate(); + //FNetworkPredictionData_Server_Character * ServerData = charMovement->GetPredictionData_Server_Character(); + //check(ServerData); + + // Reset client timestamp check so that there isn't a delay on ending seated mode before we accept movement packets + //ServerData->CurrentClientTimeStamp = 1.f; + } + } + + bUseControllerRotationYaw = SeatInformation.bOriginalControlRotation; + + // Re-purposing them for the new location and rotations + SetActorLocationAndRotationVR(SeatInformation.StoredTargetTransform.GetTranslation(), SeatInformation.StoredTargetTransform.Rotator(), true, true, true); + LeftMotionController->PostTeleportMoveGrippedObjects(); + RightMotionController->PostTeleportMoveGrippedObjects(); + + // Enable collision now + root->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + + OnSeatedModeChanged(SeatInformation.bSitting, SeatInformation.bWasSeated); + SeatInformation.ClearTempVals(); + } + } + } +} + +void AVRBaseCharacter::TickSeatInformation(float DeltaTime) +{ + float LastThresholdScaler = SeatInformation.CurrentThresholdScaler; + bool bLastOverThreshold = SeatInformation.bIsOverThreshold; + + FVector NewLoc = VRReplicatedCamera->GetRelativeLocation(); + FVector OrigLocation = SeatInformation.InitialRelCameraTransform.GetTranslation(); + + if (!SeatInformation.bZeroToHead) + { + NewLoc.Z = 0.0f; + OrigLocation.Z = 0.0f; + } + + if (FMath::IsNearlyZero(SeatInformation.AllowedRadius)) + { + // Nothing to process here, seated mode isn't sticking to a set radius + if (SeatInformation.bIsOverThreshold) + { + SeatInformation.bIsOverThreshold = false; + bLastOverThreshold = false; + } + return; + } + + float AbsDistance = FMath::Abs(FVector::Dist(OrigLocation, NewLoc)); + + //FTransform newTrans = SeatInformation.StoredTargetTransform * SeatInformation.SeatParent->GetComponentTransform(); + + // If over the allowed distance + if (AbsDistance > SeatInformation.AllowedRadius) + { + // Force them back into range + FVector diff = NewLoc - OrigLocation; + diff.Normalize(); + diff = (-diff * (AbsDistance - SeatInformation.AllowedRadius)); + + SetSeatRelativeLocationAndRotationVR(diff); + SeatInformation.bWasOverLimit = true; + } + else if (SeatInformation.bWasOverLimit) // Make sure we are in the zero point otherwise + { + SetSeatRelativeLocationAndRotationVR(FVector::ZeroVector); + SeatInformation.bWasOverLimit = false; + } + + if (AbsDistance > SeatInformation.AllowedRadius - SeatInformation.AllowedRadiusThreshold) + SeatInformation.bIsOverThreshold = true; + else + SeatInformation.bIsOverThreshold = false; + + SeatInformation.CurrentThresholdScaler = FMath::Clamp((AbsDistance - (SeatInformation.AllowedRadius - SeatInformation.AllowedRadiusThreshold)) / SeatInformation.AllowedRadiusThreshold, 0.0f, 1.0f); + + if (bLastOverThreshold != SeatInformation.bIsOverThreshold || !FMath::IsNearlyEqual(LastThresholdScaler, SeatInformation.CurrentThresholdScaler)) + { + OnSeatThreshholdChanged(!SeatInformation.bIsOverThreshold, SeatInformation.CurrentThresholdScaler); + OnSeatThreshholdChanged_Bind.Broadcast(!SeatInformation.bIsOverThreshold, SeatInformation.CurrentThresholdScaler); + } +} + +bool AVRBaseCharacter::SetSeatedMode(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform TargetTransform, FTransform InitialRelCameraTransform, float AllowedRadius, float AllowedRadiusThreshold, bool bZeroToHead, EVRConjoinedMovementModes PostSeatedMovementMode) +{ + if (!this->HasAuthority()) + return false; + + if (bSetSeatedMode) + { + if (!SeatParent) + return false; + + SeatInformation.SeatParent = SeatParent; + SeatInformation.bSitting = true; + SeatInformation.bZeroToHead = bZeroToHead; + SeatInformation.StoredTargetTransform = TargetTransform; + SeatInformation.InitialRelCameraTransform = InitialRelCameraTransform; + + // Purify the yaw of the initial rotation + SeatInformation.InitialRelCameraTransform.SetRotation(UVRExpansionFunctionLibrary::GetHMDPureYaw_I(InitialRelCameraTransform.Rotator()).Quaternion()); + SeatInformation.AllowedRadius = AllowedRadius; + SeatInformation.AllowedRadiusThreshold = AllowedRadiusThreshold; + + // #TODO: Need to handle non 1 scaled values here eventually + if (bZeroToHead) + { + FVector newLocation = SeatInformation.InitialRelCameraTransform.GetTranslation(); + SeatInformation.StoredTargetTransform.AddToTranslation(FVector(0, 0, -newLocation.Z)); + } + + //SetReplicateMovement(false);/ / No longer doing this, allowing it to replicate down to simulated clients now instead + } + else + { + SeatInformation.SeatParent = nullptr; + SeatInformation.StoredTargetTransform = TargetTransform; + SeatInformation.PostSeatedMovementMode = PostSeatedMovementMode; + //SetReplicateMovement(true); // No longer doing this, allowing it to replicate down to simulated clients now instead + SeatInformation.bSitting = false; + } + + OnRep_SeatedCharInfo(); // Call this on server side because it won't call itself + NotifyOfTeleport(); // Teleport the controllers + + return true; +} + +void AVRBaseCharacter::SetSeatRelativeLocationAndRotationVR(FVector DeltaLoc) +{ + /*if (bUseYawOnly) + { + NewRot.Pitch = 0.0f; + NewRot.Roll = 0.0f; + } + + NewLoc = NewLoc + Pivot; + NewLoc -= NewRot.RotateVector(Pivot); + + SetActorRelativeTransform(FTransform(NewRot, NewLoc, GetCapsuleComponent()->RelativeScale3D));*/ + + FTransform NewTrans = SeatInformation.StoredTargetTransform;// *SeatInformation.SeatParent->GetComponentTransform(); + + FVector NewLocation; + FRotator NewRotation; + FVector PivotPoint = SeatInformation.InitialRelCameraTransform.GetTranslation(); + PivotPoint.Z = 0.0f; + + NewRotation = SeatInformation.InitialRelCameraTransform.Rotator(); + NewRotation = (NewRotation.Quaternion().Inverse() * NewTrans.GetRotation()).Rotator(); + NewLocation = NewTrans.GetTranslation(); + NewLocation -= NewRotation.RotateVector(PivotPoint + (-DeltaLoc)); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorRelativeTransform(FTransform(NewRotation, NewLocation, GetCapsuleComponent()->GetRelativeScale3D())); +} + + +FVector AVRBaseCharacter::AddActorWorldRotationVR(FRotator DeltaRot, bool bUseYawOnly) +{ + AController* OwningController = GetController(); + + FVector NewLocation; + FRotator NewRotation; + FVector OrigLocation = GetActorLocation(); + FVector PivotPoint = GetActorTransform().InverseTransformPosition(GetVRLocation_Inline()); + PivotPoint.Z = 0.0f; + + NewRotation = bUseControllerRotationYaw && OwningController ? OwningController->GetControlRotation() : GetActorRotation(); + + if (bUseYawOnly) + { + NewRotation.Pitch = 0.0f; + NewRotation.Roll = 0.0f; + } + + NewLocation = OrigLocation + NewRotation.RotateVector(PivotPoint); + NewRotation = (NewRotation.Quaternion() * DeltaRot.Quaternion()).Rotator(); + NewLocation -= NewRotation.RotateVector(PivotPoint); + + if (bUseControllerRotationYaw && OwningController /*&& IsLocallyControlled()*/) + OwningController->SetControlRotation(NewRotation); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorLocationAndRotation(NewLocation, NewRotation); + return NewLocation - OrigLocation; +} + +FVector AVRBaseCharacter::SetActorRotationVR(FRotator NewRot, bool bUseYawOnly, bool bAccountForHMDRotation) +{ + AController* OwningController = GetController(); + + FVector NewLocation; + FRotator NewRotation; + FVector OrigLocation = GetActorLocation(); + FVector PivotPoint = GetActorTransform().InverseTransformPosition(GetVRLocation_Inline()); + PivotPoint.Z = 0.0f; + + FRotator OrigRotation = bUseControllerRotationYaw && OwningController ? OwningController->GetControlRotation() : GetActorRotation(); + + if (bUseYawOnly) + { + NewRot.Pitch = 0.0f; + NewRot.Roll = 0.0f; + } + + if (bAccountForHMDRotation) + { + NewRotation = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRReplicatedCamera->GetRelativeRotation()); + NewRotation = (NewRot.Quaternion() * NewRotation.Quaternion().Inverse()).Rotator(); + } + else + NewRotation = NewRot; + + NewLocation = OrigLocation + OrigRotation.RotateVector(PivotPoint); + //NewRotation = NewRot; + NewLocation -= NewRotation.RotateVector(PivotPoint); + + if (bUseControllerRotationYaw && OwningController /*&& IsLocallyControlled()*/) + OwningController->SetControlRotation(NewRotation); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorLocationAndRotation(NewLocation, NewRotation); + return NewLocation - OrigLocation; +} + +FVector AVRBaseCharacter::SetActorLocationAndRotationVR(FVector NewLoc, FRotator NewRot, bool bUseYawOnly, bool bAccountForHMDRotation, bool bTeleport) +{ + AController* OwningController = GetController(); + + FVector NewLocation; + FRotator NewRotation; + FVector PivotPoint = GetActorTransform().InverseTransformPosition(GetVRLocation_Inline()); + PivotPoint.Z = 0.0f; + + if (bUseYawOnly) + { + NewRot.Pitch = 0.0f; + NewRot.Roll = 0.0f; + } + + if (bAccountForHMDRotation) + { + NewRotation = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRReplicatedCamera->GetRelativeRotation());//bUseControllerRotationYaw && OwningController ? OwningController->GetControlRotation() : GetActorRotation(); + NewRotation = (NewRot.Quaternion() * NewRotation.Quaternion().Inverse()).Rotator(); + } + else + NewRotation = NewRot; + + NewLocation = NewLoc;// +PivotPoint;// NewRotation.RotateVector(PivotPoint); + //NewRotation = NewRot; + NewLocation -= NewRotation.RotateVector(PivotPoint); + + if (bUseControllerRotationYaw && OwningController /*&& IsLocallyControlled()*/) + OwningController->SetControlRotation(NewRotation); + + // Also setting actor rot because the control rot transfers to it anyway eventually + SetActorLocationAndRotation(NewLocation, NewRotation, false, nullptr, bTeleport ? ETeleportType::TeleportPhysics : ETeleportType::None); + return NewLocation - NewLoc; +} + +void AVRBaseCharacter::OnRep_CapsuleHeight() +{ + if (!VRReplicateCapsuleHeight) + return; + + if (UCapsuleComponent* Capsule = Cast<UCapsuleComponent>(GetRootComponent())) + { + if (ReplicatedCapsuleHeight.CapsuleHeight > 0.0f && !FMath::IsNearlyEqual(ReplicatedCapsuleHeight.CapsuleHeight, Capsule->GetUnscaledCapsuleHalfHeight())) + { + SetCharacterHalfHeightVR(ReplicatedCapsuleHeight.CapsuleHeight, false); + } + } +} + +void AVRBaseCharacter::SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps) +{ + if (UCapsuleComponent * Capsule = Cast<UCapsuleComponent>(this->RootComponent)) + { + if (!FMath::IsNearlyEqual(NewRadius, Capsule->GetUnscaledCapsuleRadius()) || !FMath::IsNearlyEqual(NewHalfHeight, Capsule->GetUnscaledCapsuleHalfHeight())) + Capsule->SetCapsuleSize(NewRadius, NewHalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client && VRReplicateCapsuleHeight) + ReplicatedCapsuleHeight.CapsuleHeight = Capsule->GetUnscaledCapsuleHalfHeight(); + } +} + +void AVRBaseCharacter::SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps) +{ + if (UCapsuleComponent * Capsule = Cast<UCapsuleComponent>(this->RootComponent)) + { + if (!FMath::IsNearlyEqual(HalfHeight, Capsule->GetUnscaledCapsuleHalfHeight())) + Capsule->SetCapsuleHalfHeight(HalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client && VRReplicateCapsuleHeight) + ReplicatedCapsuleHeight.CapsuleHeight = Capsule->GetUnscaledCapsuleHalfHeight(); + } +} + +void AVRBaseCharacter::ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bProjectDestinationToNavigation, bool bCanStrafe, TSubclassOf<UNavigationQueryFilter> FilterClass, bool bAllowPartialPaths) +{ + UNavigationSystemV1* NavSys = Controller ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(Controller->GetWorld()) : nullptr; + if (NavSys == nullptr || Controller == nullptr ) + { + UE_LOG(LogBaseVRCharacter, Warning, TEXT("UVRSimpleCharacter::ExtendedSimpleMoveToLocation called for NavSys:%s Controller:%s (if any of these is None then there's your problem"), + *GetNameSafe(NavSys), *GetNameSafe(Controller)); + return; + } + + UPathFollowingComponent* PFollowComp = nullptr; + //Controller->InitNavigationControl(PFollowComp); + if (Controller) + { + // New for 4.20, spawning the missing path following component here if there isn't already one + PFollowComp = Controller->FindComponentByClass<UPathFollowingComponent>(); + if (PFollowComp == nullptr) + { + PFollowComp = NewObject<UVRPathFollowingComponent>(Controller); + PFollowComp->RegisterComponentWithWorld(Controller->GetWorld()); + PFollowComp->Initialize(); + } + } + + if (PFollowComp == nullptr) + { + UE_LOG(LogBaseVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - No PathFollowingComponent Found")); + return; + } + + if (!PFollowComp->IsPathFollowingAllowed()) + { + UE_LOG(LogBaseVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - Path Following Movement Is Not Set To Allowed")); + return; + } + + EPathFollowingReachMode ReachMode; + if (bStopOnOverlap) + ReachMode = EPathFollowingReachMode::OverlapAgent; + else + ReachMode = EPathFollowingReachMode::ExactLocation; + + bool bAlreadyAtGoal = false; + + if(UVRPathFollowingComponent * pathcomp = Cast<UVRPathFollowingComponent>(PFollowComp)) + bAlreadyAtGoal = pathcomp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + else + bAlreadyAtGoal = PFollowComp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + + // script source, keep only one move request at time + if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + if (GetNetMode() == ENetMode::NM_Client) + { + // Stop the movement here, not keeping the velocity because it bugs out for clients, might be able to fix. + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, /*bAlreadyAtGoal ? */EPathFollowingVelocityMode::Reset /*: EPathFollowingVelocityMode::Keep*/); + } + else + { + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep); + } + } + + if (bAlreadyAtGoal) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Success); + } + else + { + const ANavigationData* NavData = NavSys->GetNavDataForProps(Controller->GetNavAgentPropertiesRef()); + if (NavData) + { + FPathFindingQuery Query(Controller, *NavData, Controller->GetNavAgentLocation(), GoalLocation); + FPathFindingResult Result = NavSys->FindPathSync(Query); + if (Result.IsSuccessful()) + { + FAIMoveRequest MoveReq(GoalLocation); + MoveReq.SetUsePathfinding(bUsePathfinding); + MoveReq.SetAllowPartialPath(bAllowPartialPaths); + MoveReq.SetProjectGoalLocation(bProjectDestinationToNavigation); + MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass); + MoveReq.SetAcceptanceRadius(AcceptanceRadius); + MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap); + MoveReq.SetCanStrafe(bCanStrafe); + MoveReq.SetReachTestIncludesGoalRadius(true); + + PFollowComp->RequestMove(/*FAIMoveRequest(GoalLocation)*/MoveReq, Result.Path); + } + else if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid); + } + } + } +} + +bool AVRBaseCharacter::GetCurrentNavigationPathPoints(TArray<FVector>& NavigationPointList) +{ + UPathFollowingComponent* PFollowComp = nullptr; + if (Controller) + { + // New for 4.20, spawning the missing path following component here if there isn't already one + PFollowComp = Controller->FindComponentByClass<UPathFollowingComponent>(); + if (PFollowComp) + { + FNavPathSharedPtr NavPtr = PFollowComp->GetPath(); + if (NavPtr.IsValid()) + { + TArray<FNavPathPoint>& NavPoints = NavPtr->GetPathPoints(); + if (NavPoints.Num()) + { + FTransform BaseTransform = FTransform::Identity; + if (AActor* BaseActor = NavPtr->GetBaseActor()) + { + BaseTransform = BaseActor->GetActorTransform(); + } + + NavigationPointList.Empty(NavPoints.Num()); + NavigationPointList.AddUninitialized(NavPoints.Num()); + + int counter = 0; + for (FNavPathPoint& pt : NavPoints) + { + NavigationPointList[counter++] = BaseTransform.TransformPosition(pt.Location); + } + + return true; + } + } + + return false; + } + } + + return false; +} + +void AVRBaseCharacter::NavigationMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) +{ + this->Controller->StopMovement(); + ReceiveNavigationMoveCompleted(Result.Code); +} + +EPathFollowingStatus::Type AVRBaseCharacter::GetMoveStatus() const +{ + if (!Controller) + return EPathFollowingStatus::Idle; + + if (UPathFollowingComponent* pathComp = Controller->FindComponentByClass<UPathFollowingComponent>()) + { + pathComp->GetStatus(); + } + + return EPathFollowingStatus::Idle; +} + +bool AVRBaseCharacter::HasPartialPath() const +{ + if (!Controller) + return false; + + if (UPathFollowingComponent* pathComp = Controller->FindComponentByClass<UPathFollowingComponent>()) + { + return pathComp->HasPartialPath(); + } + + return false; +} + +void AVRBaseCharacter::StopNavigationMovement() +{ + if (!Controller) + return; + + if (UPathFollowingComponent* pathComp = Controller->FindComponentByClass<UPathFollowingComponent>()) + { + // @note FPathFollowingResultFlags::ForcedScript added to make AITask_MoveTo instances + // not ignore OnRequestFinished notify that's going to be sent out due to this call + pathComp->AbortMove(*this, FPathFollowingResultFlags::MovementStop | FPathFollowingResultFlags::ForcedScript); + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacterMovementComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacterMovementComponent.cpp new file mode 100644 index 0000000..527a483 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRBaseCharacterMovementComponent.cpp @@ -0,0 +1,2014 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Movement.cpp: Character movement implementation + +=============================================================================*/ + +#include "VRBaseCharacterMovementComponent.h" +#include "VRBPDatatypes.h" +#include "ParentRelativeAttachmentComponent.h" +#include "VRBaseCharacter.h" +#include "VRRootComponent.h" +#include "AITypes.h" +#include "AI/Navigation/NavigationTypes.h" +#include "Navigation/PathFollowingComponent.h" +#include "VRPlayerController.h" +#include "GameFramework/PhysicsVolume.h" + +DEFINE_LOG_CATEGORY(LogVRBaseCharacterMovement); + +UVRBaseCharacterMovementComponent::UVRBaseCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.TickGroup = TG_PrePhysics; + + + //#TODO: Might not be ready to make this change globally yet.... + + // Set Acceleration and braking deceleration walking to high values to avoid ramp up on speed + // Realized that I wasn't doing this here for people to default to no acceleration. + /*this->bRequestedMoveUseAcceleration = false; + this->MaxAcceleration = 200048.0f; + this->BrakingDecelerationWalking = 200048.0f; + */ + + AdditionalVRInputVector = FVector::ZeroVector; + CustomVRInputVector = FVector::ZeroVector; + TrackingLossThreshold = 6000.f; + bApplyAdditionalVRInputVectorAsNegative = true; + bHadExtremeInput = false; + bHoldPositionOnTrackingLossThresholdHit = false; + + VRClimbingStepHeight = 96.0f; + VRClimbingEdgeRejectDistance = 5.0f; + VRClimbingStepUpMultiplier = 1.0f; + bClampClimbingStepUp = false; + VRClimbingStepUpMaxSize = 20.0f; + + VRClimbingMaxReleaseVelocitySize = 800.0f; + SetDefaultPostClimbMovementOnStepUp = true; + DefaultPostClimbMovement = EVRConjoinedMovementModes::C_MOVE_Falling; + + bIgnoreSimulatingComponentsInFloorCheck = true; + + VRWallSlideScaler = 1.0f; + VRLowGravWallFrictionScaler = 1.0f; + VRLowGravIgnoresDefaultFluidFriction = true; + + VREdgeRejectDistance = 0.01f; // Rounded minimum of root movement + + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX; + + NetworkSmoothingMode = ENetworkSmoothingMode::Exponential; + + bWasInPushBack = false; + bIsInPushBack = false; + + bRunControlRotationInMovementComponent = true; + + // Allow merging dual movements, generally this is wanted for the perf increase + bEnableServerDualMoveScopedMovementUpdates = true; + + bNotifyTeleported = false; + + bJustUnseated = false; + + bUseClientControlRotation = true; + bDisableSimulatedTickWhenSmoothingMovement = true; + bCapHMDMovementToMaxMovementSpeed = false; + + SetNetworkMoveDataContainer(VRNetworkMoveDataContainer); + SetMoveResponseDataContainer(VRMoveResponseDataContainer); +} + +void UVRBaseCharacterMovementComponent::OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) +{ + if (!HasValidData()) + { + return; + } + + // Clear out the old custom input vector, it will pollute the pool now that all modes allow it. + CustomVRInputVector = FVector::ZeroVector; + + if (PreviousMovementMode == EMovementMode::MOVE_Custom && PreviousCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + if (MovementMode != EMovementMode::MOVE_Custom || CustomMovementMode != (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + if (AVRBaseCharacter * BaseOwner = Cast<AVRBaseCharacter>(CharacterOwner)) + { + BaseOwner->InitSeatedModeTransition(); + } + } + } + + if (MovementMode == EMovementMode::MOVE_Custom) + { + if (CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + // Kill velocity and clear queued up events + StopMovementKeepPathing(); + CharacterOwner->ResetJumpState(); + ClearAccumulatedForces(); + + if (CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + if (AVRBaseCharacter * BaseOwner = Cast<AVRBaseCharacter>(CharacterOwner)) + { + BaseOwner->InitSeatedModeTransition(); + } + } + } + } + + Super::OnMovementModeChanged(PreviousMovementMode, PreviousCustomMode); +} + +bool UVRBaseCharacterMovementComponent::ForcePositionUpdate(float DeltaTime) +{ + // Skip force updating position if we are seated. + if ((MovementMode == EMovementMode::MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated)) + { + return false; + } + + return Super::ForcePositionUpdate(DeltaTime); +} + +void UVRBaseCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + + // Skip calling into BP if we aren't locally controlled + if (CharacterOwner->IsLocallyControlled() && GetReplicatedMovementMode() == EVRConjoinedMovementModes::C_VRMOVE_Climbing) + { + // Allow the player to run updates on the climb logic for CustomVRInputVector + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->UpdateClimbingMovement(DeltaTime); + } + } + + // Scope all of the movements, including PRC + { + UParentRelativeAttachmentComponent* OuterScopePRC = nullptr; + if (BaseVRCharacterOwner && BaseVRCharacterOwner->ParentRelativeAttachment && BaseVRCharacterOwner->ParentRelativeAttachment->bUpdateInCharacterMovement) + { + OuterScopePRC = BaseVRCharacterOwner->ParentRelativeAttachment; + } + + FScopedMovementUpdate ScopedPRCMovementUpdate(OuterScopePRC, EScopedUpdate::DeferredUpdates); + + { + UReplicatedVRCameraComponent* OuterScopeCamera = nullptr; + if (BaseVRCharacterOwner && BaseVRCharacterOwner->VRReplicatedCamera) + { + OuterScopeCamera = BaseVRCharacterOwner->VRReplicatedCamera; + } + + FScopedMovementUpdate ScopedCameraMovementUpdate(OuterScopeCamera, EScopedUpdate::DeferredUpdates); + + // Scope in the character movements first + { + // Scope these, they nest with Outer references so it should work fine + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + + //#TODO 5.0: Handle this? + /*FVector InputVector = FVector::ZeroVector; + bool bUsingAsyncTick = (CharacterMovementCVars::AsyncCharacterMovement == 1) && IsAsyncCallbackRegistered(); + if (!bUsingAsyncTick) + { + // Do not consume input if simulating asynchronously, we will consume input when filling out async inputs. + InputVector = ConsumeInputVector(); + }*/ + + + const FVector InputVector = ConsumeInputVector(); + if (!HasValidData() || ShouldSkipUpdate(DeltaTime)) + { + return; + } + + // Skip the perform movement logic, run the re-seat logic instead - running base movement component tick instead + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // See if we fell out of the world. + const bool bIsSimulatingPhysics = UpdatedComponent->IsSimulatingPhysics(); + if (CharacterOwner->GetLocalRole() == ROLE_Authority && (!bCheatFlying || bIsSimulatingPhysics) && !CharacterOwner->CheckStillInWorld()) + { + return; + } + + // If we are the owning client or the server then run the re-basing + if (CharacterOwner->GetLocalRole() > ROLE_SimulatedProxy) + { + // Run offset logic here, the server will update simulated proxies with the movement replication + if (AVRBaseCharacter* BaseChar = Cast<AVRBaseCharacter>(CharacterOwner)) + { + BaseChar->TickSeatInformation(DeltaTime); + } + + if (CharacterOwner && !CharacterOwner->IsLocallyControlled() && DeltaTime > 0.0f) + { + // If not playing root motion, tick animations after physics. We do this here to keep events, notifies, states and transitions in sync with client updates. + if (!CharacterOwner->bClientUpdating && !CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh()) + { + TickCharacterPose(DeltaTime); + // TODO: SaveBaseLocation() in case tick moves us? + + // Trigger Events right away, as we could be receiving multiple ServerMoves per frame. + CharacterOwner->GetMesh()->ConditionallyDispatchQueuedAnimEvents(); + } + } + + } + else + { + if (bNetworkUpdateReceived) + { + if (bNetworkMovementModeChanged) + { + ApplyNetworkMovementMode(CharacterOwner->GetReplicatedMovementMode()); + bNetworkMovementModeChanged = false; + } + } + } + } + else + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + + // This should be valid for both Simulated and owning clients as well as the server + // Better here than in perform movement + if (UVRRootComponent* VRRoot = Cast<UVRRootComponent>(CharacterOwner->GetCapsuleComponent())) + { + // If we didn't move the capsule, have it update itself here so the visual and physics representation is correct + // We do this specifically to avoid double calling into the render / physics threads. + if (!VRRoot->bCalledUpdateTransform) + VRRoot->OnUpdateTransform_Public(EUpdateTransformFlags::None, ETeleportType::None); + } + + // Make sure these are cleaned out for the next frame + AdditionalVRInputVector = FVector::ZeroVector; + CustomVRInputVector = FVector::ZeroVector; + } + + if (bRunControlRotationInMovementComponent && CharacterOwner->IsLocallyControlled()) + { + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->VRReplicatedCamera && BaseVRCharacterOwner->VRReplicatedCamera->bUsePawnControlRotation) + { + const AController* OwningController = BaseVRCharacterOwner->GetController(); + if (OwningController) + { + const FRotator PawnViewRotation = BaseVRCharacterOwner->GetViewRotation(); + if (!PawnViewRotation.Equals(BaseVRCharacterOwner->VRReplicatedCamera->GetComponentRotation())) + { + BaseVRCharacterOwner->VRReplicatedCamera->SetWorldRotation(PawnViewRotation); + } + } + } + } + } + + // If some of our important components run inside the cmc updates then lets update them now + if (OuterScopeCamera) + { + OuterScopeCamera->UpdateTracking(DeltaTime); + } + + if (OuterScopePRC) + { + OuterScopePRC->UpdateTracking(DeltaTime); + } + } + } + + if (bNotifyTeleported) + { + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->OnCharacterTeleported_Bind.Broadcast(); + bNotifyTeleported = false; + } + } +} + +bool UVRBaseCharacterMovementComponent::VerifyClientTimeStamp(float TimeStamp, FNetworkPredictionData_Server_Character & ServerData) +{ + // Server is auth on seated mode and we want to ignore incoming pending movements after we have decided to set the client to seated mode + if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + return false; + + return Super::VerifyClientTimeStamp(TimeStamp, ServerData); +} + +void UVRBaseCharacterMovementComponent::StartPushBackNotification(FHitResult HitResult) +{ + bIsInPushBack = true; + + if (bWasInPushBack) + return; + + bWasInPushBack = true; + + if (AVRBaseCharacter * OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + OwningCharacter->OnBeginWallPushback(HitResult, !Acceleration.Equals(FVector::ZeroVector), AdditionalVRInputVector); + } +} + +void UVRBaseCharacterMovementComponent::EndPushBackNotification() +{ + if (bIsInPushBack || !bWasInPushBack) + return; + + bIsInPushBack = false; + bWasInPushBack = false; + + if (AVRBaseCharacter * OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + OwningCharacter->OnEndWallPushback(); + } +} + +FVector UVRBaseCharacterMovementComponent::GetActorFeetLocationVR() const +{ + if (AVRBaseCharacter * BaseCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + return UpdatedComponent ? (BaseCharacter->OffsetComponentToWorld.GetLocation() - FVector(0, 0, UpdatedComponent->Bounds.BoxExtent.Z)) : FNavigationSystem::InvalidLocation; + } + else + { + return UpdatedComponent ? (UpdatedComponent->GetComponentLocation() - FVector(0, 0, UpdatedComponent->Bounds.BoxExtent.Z)) : FNavigationSystem::InvalidLocation; + } +} + +void UVRBaseCharacterMovementComponent::OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) +{ + if (AVRBaseCharacter* vrOwner = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + vrOwner->NavigationMoveCompleted(RequestID, Result); + } +} + +void UVRBaseCharacterMovementComponent::ComputeFloorDist(const FVector& CapsuleLocation, float LineDistance, float SweepDistance, FFindFloorResult& OutFloorResult, float SweepRadius, const FHitResult* DownwardSweepResult) const +{ + UE_LOG(LogVRBaseCharacterMovement, VeryVerbose, TEXT("[Role:%d] ComputeFloorDist: %s at location %s"), (int32)CharacterOwner->GetLocalRole(), *GetNameSafe(CharacterOwner), *CapsuleLocation.ToString()); + OutFloorResult.Clear(); + + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + + bool bSkipSweep = false; + if (DownwardSweepResult != NULL && DownwardSweepResult->IsValidBlockingHit()) + { + // Only if the supplied sweep was vertical and downward. + if ((DownwardSweepResult->TraceStart.Z > DownwardSweepResult->TraceEnd.Z) && + (DownwardSweepResult->TraceStart - DownwardSweepResult->TraceEnd).SizeSquared2D() <= KINDA_SMALL_NUMBER) + { + // Reject hits that are barely on the cusp of the radius of the capsule + if (IsWithinEdgeTolerance(DownwardSweepResult->Location, DownwardSweepResult->ImpactPoint, PawnRadius)) + { + // Don't try a redundant sweep, regardless of whether this sweep is usable. + bSkipSweep = true; + + const bool bIsWalkable = IsWalkable(*DownwardSweepResult); + const float FloorDist = (CapsuleLocation.Z - DownwardSweepResult->Location.Z); + OutFloorResult.SetFromSweep(*DownwardSweepResult, FloorDist, bIsWalkable); + + if (bIsWalkable) + { + // Use the supplied downward sweep as the floor hit result. + return; + } + } + } + } + + // We require the sweep distance to be >= the line distance, otherwise the HitResult can't be interpreted as the sweep result. + if (SweepDistance < LineDistance) + { + ensure(SweepDistance >= LineDistance); + return; + } + + bool bBlockingHit = false; + FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(ComputeFloorDist), false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(QueryParams, ResponseParam); + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + + // Skip physics bodies for floor check if we are skipping simulated objects + if (bIgnoreSimulatingComponentsInFloorCheck) + ResponseParam.CollisionResponse.PhysicsBody = ECollisionResponse::ECR_Ignore; + + // Sweep test + if (!bSkipSweep && SweepDistance > 0.f && SweepRadius > 0.f) + { + // Use a shorter height to avoid sweeps giving weird results if we start on a surface. + // This also allows us to adjust out of penetrations. + const float ShrinkScale = 0.9f; + const float ShrinkScaleOverlap = 0.1f; + float ShrinkHeight = (PawnHalfHeight - PawnRadius) * (1.f - ShrinkScale); + float TraceDist = SweepDistance + ShrinkHeight; + FCollisionShape CapsuleShape = FCollisionShape::MakeCapsule(SweepRadius, PawnHalfHeight - ShrinkHeight); + + FHitResult Hit(1.f); + bBlockingHit = FloorSweepTest(Hit, CapsuleLocation, CapsuleLocation + FVector(0.f, 0.f, -TraceDist), CollisionChannel, CapsuleShape, QueryParams, ResponseParam); + + if (bBlockingHit) + { + // Reject hits adjacent to us, we only care about hits on the bottom portion of our capsule. + // Check 2D distance to impact point, reject if within a tolerance from radius. + if (Hit.bStartPenetrating || !IsWithinEdgeTolerance(CapsuleLocation, Hit.ImpactPoint, CapsuleShape.Capsule.Radius)) + { + // Use a capsule with a slightly smaller radius and shorter height to avoid the adjacent object. + // Capsule must not be nearly zero or the trace will fall back to a line trace from the start point and have the wrong length. + CapsuleShape.Capsule.Radius = FMath::Max(0.f, CapsuleShape.Capsule.Radius - SWEEP_EDGE_REJECT_DISTANCE - KINDA_SMALL_NUMBER); + if (!CapsuleShape.IsNearlyZero()) + { + ShrinkHeight = (PawnHalfHeight - PawnRadius) * (1.f - ShrinkScaleOverlap); + TraceDist = SweepDistance + ShrinkHeight; + CapsuleShape.Capsule.HalfHeight = FMath::Max(PawnHalfHeight - ShrinkHeight, CapsuleShape.Capsule.Radius); + Hit.Reset(1.f, false); + + bBlockingHit = FloorSweepTest(Hit, CapsuleLocation, CapsuleLocation + FVector(0.f, 0.f, -TraceDist), CollisionChannel, CapsuleShape, QueryParams, ResponseParam); + } + } + + // Reduce hit distance by ShrinkHeight because we shrank the capsule for the trace. + // We allow negative distances here, because this allows us to pull out of penetrations. + const float MaxPenetrationAdjust = FMath::Max(MAX_FLOOR_DIST, PawnRadius); + const float SweepResult = FMath::Max(-MaxPenetrationAdjust, Hit.Time * TraceDist - ShrinkHeight); + + OutFloorResult.SetFromSweep(Hit, SweepResult, false); + if (Hit.IsValidBlockingHit() && IsWalkable(Hit)) + { + if (SweepResult <= SweepDistance) + { + // Hit within test distance. + OutFloorResult.bWalkableFloor = true; + return; + } + } + } + } + + // Since we require a longer sweep than line trace, we don't want to run the line trace if the sweep missed everything. + // We do however want to try a line trace if the sweep was stuck in penetration. + if (!OutFloorResult.bBlockingHit && !OutFloorResult.HitResult.bStartPenetrating) + { + OutFloorResult.FloorDist = SweepDistance; + return; + } + + // Line trace + if (LineDistance > 0.f) + { + const float ShrinkHeight = PawnHalfHeight; + const FVector LineTraceStart = CapsuleLocation; + const float TraceDist = LineDistance + ShrinkHeight; + const FVector Down = FVector(0.f, 0.f, -TraceDist); + QueryParams.TraceTag = SCENE_QUERY_STAT_NAME_ONLY(FloorLineTrace); + + FHitResult Hit(1.f); + bBlockingHit = GetWorld()->LineTraceSingleByChannel(Hit, LineTraceStart, LineTraceStart + Down, CollisionChannel, QueryParams, ResponseParam); + + if (bBlockingHit) + { + if (Hit.Time > 0.f) + { + // Reduce hit distance by ShrinkHeight because we started the trace higher than the base. + // We allow negative distances here, because this allows us to pull out of penetrations. + const float MaxPenetrationAdjust = FMath::Max(MAX_FLOOR_DIST, PawnRadius); + const float LineResult = FMath::Max(-MaxPenetrationAdjust, Hit.Time * TraceDist - ShrinkHeight); + + OutFloorResult.bBlockingHit = true; + if (LineResult <= LineDistance && IsWalkable(Hit)) + { + OutFloorResult.SetFromLineTrace(Hit, OutFloorResult.FloorDist, LineResult, true); + return; + } + } + } + } + + // No hits were acceptable. + OutFloorResult.bWalkableFloor = false; +} + + +float UVRBaseCharacterMovementComponent::SlideAlongSurface(const FVector& Delta, float Time, const FVector& InNormal, FHitResult& Hit, bool bHandleImpact) +{ + // Am running the CharacterMovementComponents calculations manually here now prior to scaling down the delta + + if (!Hit.bBlockingHit) + { + return 0.f; + } + + FVector Normal(InNormal); + if (IsMovingOnGround()) + { + // We don't want to be pushed up an unwalkable surface. + if (Normal.Z > 0.f) + { + if (!IsWalkable(Hit)) + { + Normal = Normal.GetSafeNormal2D(); + } + } + else if (Normal.Z < -KINDA_SMALL_NUMBER) + { + // Don't push down into the floor when the impact is on the upper portion of the capsule. + if (CurrentFloor.FloorDist < MIN_FLOOR_DIST && CurrentFloor.bBlockingHit) + { + const FVector FloorNormal = CurrentFloor.HitResult.Normal; + const bool bFloorOpposedToMovement = (Delta | FloorNormal) < 0.f && (FloorNormal.Z < 1.f - DELTA); + if (bFloorOpposedToMovement) + { + Normal = FloorNormal; + } + + Normal = Normal.GetSafeNormal2D(); + } + } + } + + + /*if ((Delta | InNormal) <= -0.2) + { + + }*/ + + StartPushBackNotification(Hit); + + // If the movement mode is one where sliding is an issue in VR, scale the delta by the custom scaler now + // that we have already validated the floor normal. + // Otherwise just pass in as normal, either way skip the parents implementation as we are doing it now. + if (IsMovingOnGround() || (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing)) + return Super::Super::SlideAlongSurface(Delta * VRWallSlideScaler, Time, Normal, Hit, bHandleImpact); + else + return Super::Super::SlideAlongSurface(Delta, Time, Normal, Hit, bHandleImpact); + + +} + +/*void UVRBaseCharacterMovementComponent::SetCrouchedHalfHeight(float NewCrouchedHalfHeight) +{ + this->CrouchedHalfHeight = NewCrouchedHalfHeight; +}*/ + +void UVRBaseCharacterMovementComponent::AddCustomReplicatedMovement(FVector Movement) +{ + // if we are a client then lets round this to match what it will be after net Replication + if (GetNetMode() == NM_Client) + CustomVRInputVector += RoundDirectMovement(Movement); + else + CustomVRInputVector += Movement; // If not a client, don't bother to round this down. +} + +void UVRBaseCharacterMovementComponent::ClearCustomReplicatedMovement() +{ + CustomVRInputVector = FVector::ZeroVector; +} + +void UVRBaseCharacterMovementComponent::CheckServerAuthedMoveAction() +{ + // If we are calling this on the server on a non owned character, there is no reason to wait around, just do the action now + // If we ARE locally controlled, keep the action inline with the movement component to maintain consistency + if (GetNetMode() < NM_Client) + { + ACharacter* OwningChar = GetCharacterOwner(); + if (OwningChar && !OwningChar->IsLocallyControlled()) + { + CheckForMoveAction(); + MoveActionArray.Clear(); + } + } +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_SetTrackingPaused(bool bNewTrackingPaused) +{ + StoreSetTrackingPaused(bNewTrackingPaused); +} + +void UVRBaseCharacterMovementComponent::StoreSetTrackingPaused(bool bNewTrackingPaused) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_PauseTracking; + MoveAction.MoveActionFlags = bNewTrackingPaused; + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_SnapTurn(float DeltaYawAngle, EVRMoveActionVelocityRetention VelocityRetention, bool bFlagGripTeleport, bool bFlagCharacterTeleport) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_SnapTurn; + + // Removed 2 decimal precision rounding in favor of matching the actual replicated short fidelity instead. + // MoveAction.MoveActionRot = FRotator(0.0f, FMath::RoundToFloat(((FRotator(0.f,DeltaYawAngle, 0.f).Quaternion() * UpdatedComponent->GetComponentQuat()).Rotator().Yaw) * 100.f) / 100.f, 0.0f); + + // Setting to the exact same fidelity as the replicated value ends up being, losing some precision + MoveAction.MoveActionRot = FRotator(0.0f, FRotator::DecompressAxisFromShort(FRotator::CompressAxisToShort((FRotator(0.f, DeltaYawAngle, 0.f).Quaternion() * UpdatedComponent->GetComponentQuat()).Rotator().Yaw)), 0.0f); + + if (bFlagCharacterTeleport) + MoveAction.MoveActionFlags = 0x02;// .MoveActionRot.Roll = 2.0f; + else if(bFlagGripTeleport) + MoveAction.MoveActionFlags = 0x01;//MoveActionRot.Roll = bFlagGripTeleport ? 1.0f : 0.0f; + + if (VelocityRetention == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + //MoveAction.MoveActionRot.Pitch = FMath::RoundToFloat(DeltaYawAngle * 100.f) / 100.f; + MoveAction.MoveActionRot.Pitch = DeltaYawAngle; + } + + MoveAction.VelRetentionSetting = VelocityRetention; + + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_SetRotation(float NewYaw, EVRMoveActionVelocityRetention VelocityRetention, bool bFlagGripTeleport, bool bFlagCharacterTeleport) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_SetRotation; + MoveAction.MoveActionRot = FRotator(0.0f, FMath::RoundToFloat(NewYaw * 100.f) / 100.f, 0.0f); + + if (bFlagCharacterTeleport) + MoveAction.MoveActionFlags = 0x02;// .MoveActionRot.Roll = 2.0f; + else if (bFlagGripTeleport) + MoveAction.MoveActionFlags = 0x01;//MoveActionRot.Roll = bFlagGripTeleport ? 1.0f : 0.0f; + + if (VelocityRetention == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + float DeltaYawAngle = FMath::FindDeltaAngleDegrees(UpdatedComponent->GetComponentRotation().Yaw, NewYaw); + //MoveAction.MoveActionRot.Pitch = FMath::RoundToFloat(DeltaYawAngle * 100.f) / 100.f; + MoveAction.MoveActionRot.Pitch = DeltaYawAngle; + } + + MoveAction.VelRetentionSetting = VelocityRetention; + + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_Teleport(FVector TeleportLocation, FRotator TeleportRotation, EVRMoveActionVelocityRetention VelocityRetention, bool bSkipEncroachmentCheck) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_Teleport; + MoveAction.MoveActionLoc = RoundDirectMovement(TeleportLocation); + MoveAction.MoveActionRot.Yaw = FMath::RoundToFloat(TeleportRotation.Yaw * 100.f) / 100.f; + MoveAction.MoveActionFlags |= (uint8)bSkipEncroachmentCheck;//.MoveActionRot.Roll = bSkipEncroachmentCheck ? 1.0f : 0.0f; + + if (VelocityRetention == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + float DeltaYawAngle = FMath::FindDeltaAngleDegrees(UpdatedComponent->GetComponentRotation().Yaw, TeleportRotation.Yaw); + //MoveAction.MoveActionRot.Pitch = FMath::RoundToFloat(DeltaYawAngle * 100.f) / 100.f; + MoveAction.MoveActionRot.Pitch = DeltaYawAngle; + } + + MoveAction.VelRetentionSetting = VelocityRetention; + + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_StopAllMovement() +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_StopAllMovement; + MoveActionArray.MoveActions.Add(MoveAction); + + CheckServerAuthedMoveAction(); +} + +void UVRBaseCharacterMovementComponent::PerformMoveAction_Custom(EVRMoveAction MoveActionToPerform, EVRMoveActionDataReq DataRequirementsForMoveAction, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = MoveActionToPerform; + + // Round the vector to 2 decimal precision + MoveAction.MoveActionLoc = RoundDirectMovement(MoveActionVector); + MoveAction.MoveActionRot = MoveActionRotator; + MoveAction.MoveActionDataReq = DataRequirementsForMoveAction; + MoveAction.MoveActionFlags = MoveActionFlags; + MoveActionArray.MoveActions.Add(MoveAction); + + CheckServerAuthedMoveAction(); +} + +bool UVRBaseCharacterMovementComponent::CheckForMoveAction() +{ + for (FVRMoveActionContainer& MoveAction : MoveActionArray.MoveActions) + { + switch (MoveAction.MoveAction) + { + case EVRMoveAction::VRMOVEACTION_SnapTurn: + { + /*return */DoMASnapTurn(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_Teleport: + { + /*return */DoMATeleport(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_StopAllMovement: + { + /*return */DoMAStopAllMovement(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_SetRotation: + { + /*return */DoMASetRotation(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_PauseTracking: + { + /*return */DoMAPauseTracking(MoveAction); + }break; + case EVRMoveAction::VRMOVEACTION_None: + {}break; + default: // All other move actions (CUSTOM) + { + if (AVRBaseCharacter * OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + OwningCharacter->OnCustomMoveActionPerformed(MoveAction.MoveAction, MoveAction.MoveActionLoc, MoveAction.MoveActionRot, MoveAction.MoveActionFlags); + } + }break; + } + } + + return true; +} + +bool UVRBaseCharacterMovementComponent::DoMASnapTurn(FVRMoveActionContainer& MoveAction) +{ + if (AVRBaseCharacter * OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + + FRotator TargetRot(0.f, MoveAction.MoveActionRot.Yaw, 0.f); + + FQuat OrigRot = OwningCharacter->GetActorQuat(); + + if (this->BaseVRCharacterOwner && this->BaseVRCharacterOwner->IsLocallyControlled()) + { + if (this->bUseClientControlRotation) + { + MoveAction.MoveActionLoc = OwningCharacter->SetActorRotationVR(TargetRot, true, false); + MoveAction.MoveActionFlags |= 0x04; // Flag that we are using loc only + } + else + { + OwningCharacter->SetActorRotationVR(TargetRot, true, false); + } + } + else + { + if (MoveAction.MoveActionFlags & 0x04) + { + OwningCharacter->SetActorLocation(OwningCharacter->GetActorLocation() + MoveAction.MoveActionLoc); + } + else + { + OwningCharacter->SetActorRotationVR(TargetRot, true, false); + } + } + + switch (MoveAction.VelRetentionSetting) + { + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None: + { + + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Clear: + { + this->Velocity = FVector::ZeroVector; + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn: + { + if (OwningCharacter->IsLocallyControlled()) + { + MoveAction.MoveActionVel = RoundDirectMovement(FRotator(0.f, MoveAction.MoveActionRot.Pitch, 0.f).RotateVector(this->Velocity)); + this->Velocity = MoveAction.MoveActionVel; + } + else + { + this->Velocity = MoveAction.MoveActionVel; + } + }break; + } + + // If we are flagged to teleport the grips + if (MoveAction.MoveActionFlags & 0x01 || MoveAction.MoveActionFlags & 0x02) + { + OwningCharacter->NotifyOfTeleport(MoveAction.MoveActionFlags & 0x02); + } + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMASetRotation(FVRMoveActionContainer& MoveAction) +{ + if (AVRBaseCharacter * OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + FRotator TargetRot(0.f, MoveAction.MoveActionRot.Yaw, 0.f); + if (this->BaseVRCharacterOwner && this->BaseVRCharacterOwner->IsLocallyControlled()) + { + if (this->bUseClientControlRotation) + { + MoveAction.MoveActionLoc = OwningCharacter->SetActorRotationVR(TargetRot, true); + MoveAction.MoveActionFlags |= 0x04; // Flag that we are using loc only + } + else + { + OwningCharacter->SetActorRotationVR(TargetRot, true); + } + } + else + { + if (MoveAction.MoveActionFlags & 0x04) + { + OwningCharacter->SetActorLocation(OwningCharacter->GetActorLocation() + MoveAction.MoveActionLoc); + } + else + { + OwningCharacter->SetActorRotationVR(TargetRot, true); + } + } + + switch (MoveAction.VelRetentionSetting) + { + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None: + { + + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Clear: + { + this->Velocity = FVector::ZeroVector; + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn: + { + if (OwningCharacter->IsLocallyControlled()) + { + MoveAction.MoveActionVel = RoundDirectMovement(FRotator(0.f, MoveAction.MoveActionRot.Pitch, 0.f).RotateVector(this->Velocity)); + this->Velocity = MoveAction.MoveActionVel; + } + else + { + this->Velocity = MoveAction.MoveActionVel; + } + }break; + } + + // If we are flagged to teleport the grips + if (MoveAction.MoveActionFlags & 0x01 || MoveAction.MoveActionFlags & 0x02) + { + OwningCharacter->NotifyOfTeleport(MoveAction.MoveActionFlags & 0x02); + } + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMATeleport(FVRMoveActionContainer& MoveAction) +{ + if (AVRBaseCharacter * OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + AController* OwningController = OwningCharacter->GetController(); + + if (!OwningController) + { + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_None; + return false; + } + + bool bSkipEncroachmentCheck = MoveAction.MoveActionFlags & 0x01; //MoveAction.MoveActionRot.Roll > 0.0f; + FRotator TargetRot(0.f, MoveAction.MoveActionRot.Yaw, 0.f); + OwningCharacter->TeleportTo(MoveAction.MoveActionLoc, TargetRot, false, bSkipEncroachmentCheck); + + switch (MoveAction.VelRetentionSetting) + { + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None: + { + + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Clear: + { + this->Velocity = FVector::ZeroVector; + }break; + case EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn: + { + if (OwningCharacter->IsLocallyControlled()) + { + MoveAction.MoveActionVel = RoundDirectMovement(FRotator(0.f, MoveAction.MoveActionRot.Pitch, 0.f).RotateVector(this->Velocity)); + this->Velocity = MoveAction.MoveActionVel; + } + else + { + this->Velocity = MoveAction.MoveActionVel; + } + }break; + } + + if (OwningCharacter->bUseControllerRotationYaw) + OwningController->SetControlRotation(TargetRot); + + return true; + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMAStopAllMovement(FVRMoveActionContainer& MoveAction) +{ + if (AVRBaseCharacter * OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + this->StopMovementImmediately(); + return true; + } + + return false; +} + +bool UVRBaseCharacterMovementComponent::DoMAPauseTracking(FVRMoveActionContainer& MoveAction) +{ + if (AVRBaseCharacter* OwningCharacter = Cast<AVRBaseCharacter>(GetCharacterOwner())) + { + OwningCharacter->bTrackingPaused = MoveAction.MoveActionFlags > 0; + OwningCharacter->PausedTrackingLoc = MoveAction.MoveActionLoc; + OwningCharacter->PausedTrackingRot = MoveAction.MoveActionRot.Yaw; + return true; + } + return false; +} + +void UVRBaseCharacterMovementComponent::PhysCustom(float deltaTime, int32 Iterations) +{ + switch (static_cast<EVRCustomMovementMode>(CustomMovementMode)) + { + case EVRCustomMovementMode::VRMOVE_Climbing: + PhysCustom_Climbing(deltaTime, Iterations); + break; + case EVRCustomMovementMode::VRMOVE_LowGrav: + PhysCustom_LowGrav(deltaTime, Iterations); + break; + case EVRCustomMovementMode::VRMOVE_Seated: + break; + default: + Super::PhysCustom(deltaTime, Iterations); + break; + } +} + +bool UVRBaseCharacterMovementComponent::VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult) +{ + return StepUp(GravDir, Delta, InHit, OutStepDownResult); +} + +void UVRBaseCharacterMovementComponent::PhysCustom_Climbing(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Skip calling into BP if we aren't locally controlled - *EDIT* MOVED TO TICKCOMPONENT to avoid batched movement issues + /*if (CharacterOwner->IsLocallyControlled()) + { + // Allow the player to run updates on the climb logic for CustomVRInputVector + if (AVRBaseCharacter * characterOwner = Cast<AVRBaseCharacter>(CharacterOwner)) + { + characterOwner->UpdateClimbingMovement(deltaTime); + } + }*/ + + + // I am forcing this to 0 to avoid some legacy velocity coming out of other movement modes, climbing should only be direct movement anyway. + Velocity = FVector::ZeroVector; + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector Adjusted = /*(Velocity * deltaTime) + */CustomVRInputVector; + FVector Delta = Adjusted + AdditionalVRInputVector; + bool bZeroDelta = Delta.IsNearlyZero(); + + FStepDownResult StepDownResult; + + // Instead of remaking the step up function, temp assign a custom step height and then fall back to the old one afterward + // This isn't the "proper" way to do it, but it saves on re-making stepup() for both vr characters seperatly (due to different hmd injection) + float OldMaxStepHeight = MaxStepHeight; + MaxStepHeight = VRClimbingStepHeight; + bool bSteppedUp = false; + + if (!bZeroDelta) + { + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.Time < 1.f) + { + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = (CustomVRInputVector).GetSafeNormal();//Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + //bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + // Scope our movement updates, and do not apply them until all intermediate moves are completed. + FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates); + + float stepZ = UpdatedComponent->GetComponentLocation().Z; + + // Making it easier to step up here with the multiplier, helps avoid falling back off + if(bClampClimbingStepUp) + bSteppedUp = VRClimbStepUp(GravDir, ((Adjusted.GetClampedToMaxSize2D(VRClimbingStepUpMaxSize) * VRClimbingStepUpMultiplier) + AdditionalVRInputVector) * (1.f - Hit.Time), Hit, &StepDownResult); + else + bSteppedUp = VRClimbStepUp(GravDir, ((Adjusted * VRClimbingStepUpMultiplier) + AdditionalVRInputVector) * (1.f - Hit.Time), Hit, &StepDownResult); + + if (bSteppedUp && OnPerformClimbingStepUp.IsBound()) + { + FVector finalLoc = UpdatedComponent->GetComponentLocation(); + + // Rewind the step up, the end user wants to handle it instead + ScopedStepUpMovement.RevertMove(); + + // Revert to old max step height + MaxStepHeight = OldMaxStepHeight; + + OnPerformClimbingStepUp.Broadcast(finalLoc); + return; + } + + if (bSteppedUp) + { + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + } + } + + // Revert to old max step height + MaxStepHeight = OldMaxStepHeight; + + if (bSteppedUp) + { + if (AVRBaseCharacter * ownerCharacter = Cast<AVRBaseCharacter>(CharacterOwner)) + { + if (SetDefaultPostClimbMovementOnStepUp) + { + // Takes effect next frame, this allows server rollback to correctly handle auto step up + SetReplicatedMovementMode(DefaultPostClimbMovement); + // Before doing this the server could rollback the client from a bad step up and leave it hanging in climb mode + // This way the rollback replay correctly sets the movement mode from the step up request + + Velocity = FVector::ZeroVector; + } + + // Notify the end user that they probably want to stop gripping now + ownerCharacter->OnClimbingSteppedUp(); + } + } + + // Update floor. + // StepUp might have already done it for us. + if (StepDownResult.bComputedFloor) + { + CurrentFloor = StepDownResult.FloorResult; + } + else + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL); + } + + if (CurrentFloor.IsWalkableFloor()) + { + if(CurrentFloor.GetDistanceToFloor() < (MIN_FLOOR_DIST + MAX_FLOOR_DIST) / 2) + AdjustFloorHeight(); + + // This was causing based movement to apply to climbing + //SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + } + else if (CurrentFloor.HitResult.bStartPenetrating) + { + // The floor check failed because it started in penetration + // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor. + FHitResult Hit(CurrentFloor.HitResult); + Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST); + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit); + ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck = true; + } + + if(!bSteppedUp || !SetDefaultPostClimbMovementOnStepUp) + { + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (((UpdatedComponent->GetComponentLocation() - OldLocation) - AdditionalVRInputVector) / deltaTime).GetClampedToMaxSize(VRClimbingMaxReleaseVelocitySize); + } + } +} + +void UVRBaseCharacterMovementComponent::PhysCustom_LowGrav(float deltaTime, int32 Iterations) +{ + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Skip calling into BP if we aren't locally controlled + if (CharacterOwner->IsLocallyControlled()) + { + // Allow the player to run updates on the push logic for CustomVRInputVector + if (AVRBaseCharacter * characterOwner = Cast<AVRBaseCharacter>(CharacterOwner)) + { + characterOwner->UpdateLowGravMovement(deltaTime); + } + } + + float Friction = 0.0f; + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + //RestorePreAdditiveVRMotionVelocity(); + + // If we are not in the default physics volume then accept the custom fluid friction setting + // I set this by default to be ignored as many will not alter the default fluid friction + if(!VRLowGravIgnoresDefaultFluidFriction || GetWorld()->GetDefaultPhysicsVolume() != GetPhysicsVolume()) + Friction = 0.5f * GetPhysicsVolume()->FluidFriction; + + CalcVelocity(deltaTime, Friction, true, 0.0f); + + // Adding in custom VR input vector here, can be used for custom movement during it + // AddImpulse is not multiplayer compatible client side + //Velocity += CustomVRInputVector; + + ApplyVRMotionToVelocity(deltaTime); + + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector Adjusted = (Velocity * deltaTime); + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted/* + AdditionalVRInputVector*/, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.Time < 1.f) + { + // Still running step up with grav dir + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + float stepZ = UpdatedComponent->GetComponentLocation().Z; + bSteppedUp = StepUp(GravDir, Adjusted * (1.f - Hit.Time), Hit); + if (bSteppedUp) + { + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (((UpdatedComponent->GetComponentLocation() - OldLocation) /* - AdditionalVRInputVector*/) / deltaTime) * VRLowGravWallFrictionScaler; + } + } + else + { + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Velocity = (((UpdatedComponent->GetComponentLocation() - OldLocation) /* - AdditionalVRInputVector*/) / deltaTime); + } + } + + RestorePreAdditiveVRMotionVelocity(); +} + + +void UVRBaseCharacterMovementComponent::SetClimbingMode(bool bIsClimbing) +{ + if (bIsClimbing) + VRReplicatedMovementMode = EVRConjoinedMovementModes::C_VRMOVE_Climbing; + else + VRReplicatedMovementMode = DefaultPostClimbMovement; +} + +void UVRBaseCharacterMovementComponent::SetReplicatedMovementMode(EVRConjoinedMovementModes NewMovementMode) +{ + // Only have up to 15 that it can go up to, the previous 7 index's are used up for std movement modes + VRReplicatedMovementMode = NewMovementMode; +} + +EVRConjoinedMovementModes UVRBaseCharacterMovementComponent::GetReplicatedMovementMode() +{ + if (MovementMode == EMovementMode::MOVE_Custom) + { + return (EVRConjoinedMovementModes)((int8)CustomMovementMode + (int8)EVRConjoinedMovementModes::C_VRMOVE_Climbing); + } + else + return (EVRConjoinedMovementModes)MovementMode.GetValue(); +} + +void UVRBaseCharacterMovementComponent::ApplyNetworkMovementMode(const uint8 ReceivedMode) +{ + if (CharacterOwner->GetLocalRole() != ENetRole::ROLE_SimulatedProxy) + { + const uint8 CurrentPackedMovementMode = PackNetworkMovementMode(); + if (CurrentPackedMovementMode != ReceivedMode) + { + TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None); + TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(ReceivedMode, NetMovementMode, NetCustomMode, NetGroundMode); + + // Custom movement modes aren't going to be rolled back as they are client authed for our pawns + if (NetMovementMode == EMovementMode::MOVE_Custom || MovementMode == EMovementMode::MOVE_Custom) + { + if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing) + return; // Don't rollback custom movement modes, we set the server to trust the client on them now so the server should get corrected + } + } + } + + Super::ApplyNetworkMovementMode(ReceivedMode); +} + +/*void UVRBaseCharacterMovementComponent::SendClientAdjustment() +{ + if (!HasValidData()) + { + return; + } + + FNetworkPredictionData_Server_Character* ServerData = GetPredictionData_Server_Character(); + check(ServerData); + + if (ServerData->PendingAdjustment.TimeStamp <= 0.f) + { + return; + } + + if (ServerData->PendingAdjustment.bAckGoodMove == true) + { + // just notify client this move was received + ClientAckGoodMove(ServerData->PendingAdjustment.TimeStamp); + } + else + { + const bool bIsPlayingNetworkedRootMotionMontage = CharacterOwner->IsPlayingNetworkedRootMotionMontage(); + if (HasRootMotionSources()) + { + FRotator Rotation = ServerData->PendingAdjustment.NewRot.GetNormalized(); + FVector_NetQuantizeNormal CompressedRotation(Rotation.Pitch / 180.f, Rotation.Yaw / 180.f, Rotation.Roll / 180.f); + ClientAdjustRootMotionSourcePosition + ( + ServerData->PendingAdjustment.TimeStamp, + CurrentRootMotion, + bIsPlayingNetworkedRootMotionMontage, + bIsPlayingNetworkedRootMotionMontage ? CharacterOwner->GetRootMotionAnimMontageInstance()->GetPosition() : -1.f, + ServerData->PendingAdjustment.NewLoc, + CompressedRotation, + ServerData->PendingAdjustment.NewVel.Z, + ServerData->PendingAdjustment.NewBase, + ServerData->PendingAdjustment.NewBaseBoneName, + ServerData->PendingAdjustment.NewBase != NULL, + ServerData->PendingAdjustment.bBaseRelativePosition, + PackNetworkMovementMode() + ); + } + else if (bIsPlayingNetworkedRootMotionMontage) + { + FRotator Rotation = ServerData->PendingAdjustment.NewRot.GetNormalized(); + FVector_NetQuantizeNormal CompressedRotation(Rotation.Pitch / 180.f, Rotation.Yaw / 180.f, Rotation.Roll / 180.f); + ClientAdjustRootMotionPosition + ( + ServerData->PendingAdjustment.TimeStamp, + CharacterOwner->GetRootMotionAnimMontageInstance()->GetPosition(), + ServerData->PendingAdjustment.NewLoc, + CompressedRotation, + ServerData->PendingAdjustment.NewVel.Z, + ServerData->PendingAdjustment.NewBase, + ServerData->PendingAdjustment.NewBaseBoneName, + ServerData->PendingAdjustment.NewBase != NULL, + ServerData->PendingAdjustment.bBaseRelativePosition, + PackNetworkMovementMode() + ); + } + else if (ServerData->PendingAdjustment.NewVel.IsZero()) + { + if (AVRBaseCharacter * VRC = Cast<AVRBaseCharacter>(GetOwner())) + { + FVector CusVec = VRC->GetVRLocation_Inline(); + GEngine->AddOnScreenDebugMessage(-1, 125.f, IsLocallyControlled() ? FColor::Red : FColor::Green, FString::Printf(TEXT("VrLoc: x: %f, y: %f, X: %f"), CusVec.X, CusVec.Y, CusVec.Z)); + } + GEngine->AddOnScreenDebugMessage(-1, 125.f, FColor::Red, TEXT("Correcting Client Location!")); + ClientVeryShortAdjustPosition + ( + ServerData->PendingAdjustment.TimeStamp, + ServerData->PendingAdjustment.NewLoc, + ServerData->PendingAdjustment.NewBase, + ServerData->PendingAdjustment.NewBaseBoneName, + ServerData->PendingAdjustment.NewBase != NULL, + ServerData->PendingAdjustment.bBaseRelativePosition, + PackNetworkMovementMode() + ); + } + else + { + if (AVRBaseCharacter * VRC = Cast<AVRBaseCharacter>(GetOwner())) + { + FVector CusVec = VRC->GetVRLocation_Inline(); + GEngine->AddOnScreenDebugMessage(-1, 125.f, IsLocallyControlled() ? FColor::Red : FColor::Green, FString::Printf(TEXT("VrLoc: x: %f, y: %f, X: %f"), CusVec.X, CusVec.Y, CusVec.Z)); + } + GEngine->AddOnScreenDebugMessage(-1, 125.f, FColor::Red, TEXT("Correcting Client Location!")); + ClientAdjustPosition + ( + ServerData->PendingAdjustment.TimeStamp, + ServerData->PendingAdjustment.NewLoc, + ServerData->PendingAdjustment.NewVel, + ServerData->PendingAdjustment.NewBase, + ServerData->PendingAdjustment.NewBaseBoneName, + ServerData->PendingAdjustment.NewBase != NULL, + ServerData->PendingAdjustment.bBaseRelativePosition, + PackNetworkMovementMode() + ); + } + } + + ServerData->PendingAdjustment.TimeStamp = 0; + ServerData->PendingAdjustment.bAckGoodMove = false; + ServerData->bForceClientUpdate = false; +} +*/ + +void UVRBaseCharacterMovementComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent) +{ + Super::SetUpdatedComponent(NewUpdatedComponent); + + BaseVRCharacterOwner = Cast<AVRBaseCharacter>(CharacterOwner); +} + + +void UVRBaseCharacterMovementComponent::PerformMovement(float DeltaSeconds) +{ + // Scope these, they nest with Outer references so it should work fine + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + // This moves it into update scope + if (bRunControlRotationInMovementComponent && CharacterOwner->IsLocallyControlled()) + { + if (BaseVRCharacterOwner && BaseVRCharacterOwner->OwningVRPlayerController) + { + BaseVRCharacterOwner->OwningVRPlayerController->RotationInput = BaseVRCharacterOwner->OwningVRPlayerController->LastRotationInput; + BaseVRCharacterOwner->OwningVRPlayerController->UpdateRotation(DeltaSeconds); + BaseVRCharacterOwner->OwningVRPlayerController->LastRotationInput = FRotator::ZeroRotator; + BaseVRCharacterOwner->OwningVRPlayerController->RotationInput = FRotator::ZeroRotator; + } + } + + // Apply any replicated movement modes that are pending + ApplyReplicatedMovementMode(VRReplicatedMovementMode, true); + + // Handle move actions here - Should be scoped + CheckForMoveAction(); + + // Clear out this flag prior to movement so we can see if it gets changed + bIsInPushBack = false; + + Super::PerformMovement(DeltaSeconds); + + EndPushBackNotification(); // Check if we need to notify of ending pushback + + // Make sure these are cleaned out for the next frame + //AdditionalVRInputVector = FVector::ZeroVector; + //CustomVRInputVector = FVector::ZeroVector; + + // Only clear it here if we are the server, the client clears it later + if (CharacterOwner->GetLocalRole() == ROLE_Authority) + { + MoveActionArray.Clear(); + } +} + +void UVRBaseCharacterMovementComponent::OnClientCorrectionReceived(class FNetworkPredictionData_Client_Character& ClientData, float TimeStamp, FVector NewLocation, FVector NewVelocity, UPrimitiveComponent* NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode) +{ + Super::OnClientCorrectionReceived(ClientData, TimeStamp, NewLocation, NewVelocity, NewBase, NewBaseBoneName, bHasBase, bBaseRelativePosition, ServerMovementMode); + + + // If we got corrected then lets teleport our grips, this means that we were out of sync with the server or the server moved us + if (BaseVRCharacterOwner) + { + BaseVRCharacterOwner->OnCharacterNetworkCorrected_Bind.Broadcast(); + BaseVRCharacterOwner->NotifyOfTeleport(false); + } +} + +void UVRBaseCharacterMovementComponent::SimulatedTick(float DeltaSeconds) +{ + //return Super::SimulatedTick(DeltaSeconds); + + QUICK_SCOPE_CYCLE_COUNTER(STAT_Character_CharacterMovementSimulated); + checkSlow(CharacterOwner != nullptr); + + if (NetworkSmoothingMode == ENetworkSmoothingMode::Replay) + { + const FVector OldLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + const FVector OldVelocity = Velocity; + + // Interpolate between appropriate samples + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_Character_CharacterMovementSmoothClientPosition); + SmoothClientPosition(DeltaSeconds); + } + + // Update replicated movement mode + ApplyNetworkMovementMode(GetCharacterOwner()->GetReplicatedMovementMode()); + + UpdateComponentVelocity(); + bJustTeleported = false; + + if (CharacterOwner) + { + CharacterOwner->RootMotionRepMoves.Empty(); + CurrentRootMotion.Clear(); + CharacterOwner->SavedRootMotion.Clear(); + } + + // Note: we do not call the Super implementation, that runs prediction. + // We do still need to call these though + OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity); + CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity); + + LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + LastUpdateVelocity = Velocity; + + //TickCharacterPose( DeltaSeconds ); + return; + } + + // If we are playing a RootMotion AnimMontage. + if (CharacterOwner->IsPlayingNetworkedRootMotionMontage()) + { + bWasSimulatingRootMotion = true; + UE_LOG(LogRootMotion, Verbose, TEXT("UCharacterMovementComponent::SimulatedTick")); + + // Tick animations before physics. + if (CharacterOwner && CharacterOwner->GetMesh()) + { + TickCharacterPose(DeltaSeconds); + + // Make sure animation didn't trigger an event that destroyed us + if (!HasValidData()) + { + return; + } + } + + if (RootMotionParams.bHasRootMotion) + { + const FQuat OldRotationQuat = UpdatedComponent->GetComponentQuat(); + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + SimulateRootMotion(DeltaSeconds, RootMotionParams.GetRootMotionTransform()); + +#if !(UE_BUILD_SHIPPING) + // debug + /*if (CharacterOwner && false) + { + const FRotator OldRotation = OldRotationQuat.Rotator(); + const FRotator NewRotation = UpdatedComponent->GetComponentRotation(); + const FVector NewLocation = UpdatedComponent->GetComponentLocation(); + DrawDebugCoordinateSystem(GetWorld(), CharacterOwner->GetMesh()->GetComponentLocation() + FVector(0, 0, 1), NewRotation, 50.f, false); + DrawDebugLine(GetWorld(), OldLocation, NewLocation, FColor::Red, false, 10.f); + + UE_LOG(LogRootMotion, Log, TEXT("UCharacterMovementComponent::SimulatedTick DeltaMovement Translation: %s, Rotation: %s, MovementBase: %s"), + *(NewLocation - OldLocation).ToCompactString(), *(NewRotation - OldRotation).GetNormalized().ToCompactString(), *GetNameSafe(CharacterOwner->GetMovementBase())); + }*/ +#endif // !(UE_BUILD_SHIPPING) + } + + // then, once our position is up to date with our animation, + // handle position correction if we have any pending updates received from the server. + if (CharacterOwner && (CharacterOwner->RootMotionRepMoves.Num() > 0)) + { + CharacterOwner->SimulatedRootMotionPositionFixup(DeltaSeconds); + } + } + else if (CurrentRootMotion.HasActiveRootMotionSources()) + { + // We have root motion sources and possibly animated root motion + bWasSimulatingRootMotion = true; + UE_LOG(LogRootMotion, Verbose, TEXT("UCharacterMovementComponent::SimulatedTick")); + + // If we have RootMotionRepMoves, find the most recent important one and set position/rotation to it + bool bCorrectedToServer = false; + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FQuat OldRotation = UpdatedComponent->GetComponentQuat(); + if (CharacterOwner->RootMotionRepMoves.Num() > 0) + { + // Move Actor back to position of that buffered move. (server replicated position). + FSimulatedRootMotionReplicatedMove& RootMotionRepMove = CharacterOwner->RootMotionRepMoves.Last(); + if (CharacterOwner->RestoreReplicatedMove(RootMotionRepMove)) + { + bCorrectedToServer = true; + } + Acceleration = RootMotionRepMove.RootMotion.Acceleration; + + CharacterOwner->PostNetReceiveVelocity(RootMotionRepMove.RootMotion.LinearVelocity); + LastUpdateVelocity = RootMotionRepMove.RootMotion.LinearVelocity; + + // Convert RootMotionSource Server IDs -> Local IDs in AuthoritativeRootMotion and cull invalid + // so that when we use this root motion it has the correct IDs + ConvertRootMotionServerIDsToLocalIDs(CurrentRootMotion, RootMotionRepMove.RootMotion.AuthoritativeRootMotion, RootMotionRepMove.Time); + RootMotionRepMove.RootMotion.AuthoritativeRootMotion.CullInvalidSources(); + + // Set root motion states to that of repped in state + CurrentRootMotion.UpdateStateFrom(RootMotionRepMove.RootMotion.AuthoritativeRootMotion, true); + + // Clear out existing RootMotionRepMoves since we've consumed the most recent + UE_LOG(LogRootMotion, Log, TEXT("\tClearing old moves in SimulatedTick (%d)"), CharacterOwner->RootMotionRepMoves.Num()); + CharacterOwner->RootMotionRepMoves.Reset(); + } + + // Perform movement + PerformMovement(DeltaSeconds); + + // After movement correction, smooth out error in position if any. + if (bCorrectedToServer || CurrentRootMotion.NeedsSimulatedSmoothing()) + { + SmoothCorrection(OldLocation, OldRotation, UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat()); + } + } + // Not playing RootMotion AnimMontage + else + { + // if we were simulating root motion, we've been ignoring regular ReplicatedMovement updates. + // If we're not simulating root motion anymore, force us to sync our movement properties. + // (Root Motion could leave Velocity out of sync w/ ReplicatedMovement) + if (bWasSimulatingRootMotion) + { + bWasSimulatingRootMotion = false; + CharacterOwner->RootMotionRepMoves.Empty(); + CharacterOwner->OnRep_ReplicatedMovement(); + CharacterOwner->OnRep_ReplicatedBasedMovement(); + ApplyNetworkMovementMode(GetCharacterOwner()->GetReplicatedMovementMode()); + } + + if (CharacterOwner->IsReplicatingMovement() && UpdatedComponent) + { + //USkeletalMeshComponent* Mesh = CharacterOwner->GetMesh(); + //const FVector SavedMeshRelativeLocation = Mesh ? Mesh->GetRelativeLocation() : FVector::ZeroVector; + //const FQuat SavedCapsuleRotation = UpdatedComponent->GetComponentQuat(); + const bool bPreventMeshMovement = !bNetworkSmoothingComplete; + + // Avoid moving the mesh during movement if SmoothClientPosition will take care of it. + if(NetworkSmoothingMode != ENetworkSmoothingMode::Disabled) + { + const FScopedPreventAttachedComponentMove PreventMeshMove(bPreventMeshMovement ? BaseVRCharacterOwner->NetSmoother : nullptr); + //const FScopedPreventAttachedComponentMove PreventMeshMovement(bPreventMeshMovement ? Mesh : nullptr); + if (CharacterOwner->IsPlayingRootMotion()) + { + PerformMovement(DeltaSeconds); + } + else + { + // Moved this var into the VRChar to control smoothing + //if(!bDisableSimulatedTickWhenSmoothingMovement) + SimulateMovement(DeltaSeconds); + } + } + else + { + if (CharacterOwner->IsPlayingRootMotion()) + { + PerformMovement(DeltaSeconds); + } + else + { + SimulateMovement(DeltaSeconds); + } + } + + // With Linear smoothing we need to know if the rotation changes, since the mesh should follow along with that (if it was prevented above). + // This should be rare that rotation changes during simulation, but it can happen when ShouldRemainVertical() changes, or standing on a moving base. + /*const bool bValidateRotation = bPreventMeshMovement && (NetworkSmoothingMode == ENetworkSmoothingMode::Linear); + if (bValidateRotation && UpdatedComponent) + { + // Same mesh with different rotation? + const FQuat NewCapsuleRotation = UpdatedComponent->GetComponentQuat(); + if (Mesh == CharacterOwner->GetMesh() && !NewCapsuleRotation.Equals(SavedCapsuleRotation, 1e-6f) && ClientPredictionData) + { + // Smoothing should lerp toward this new rotation target, otherwise it will just try to go back toward the old rotation. + ClientPredictionData->MeshRotationTarget = NewCapsuleRotation; + Mesh->SetRelativeLocationAndRotation(SavedMeshRelativeLocation, CharacterOwner->GetBaseRotationOffset()); + } + }*/ + } + } + + // Smooth mesh location after moving the capsule above. + if (!bNetworkSmoothingComplete) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_Character_CharacterMovementSmoothClientPosition); + SmoothClientPosition(DeltaSeconds); + } + else + { + UE_LOG(LogVRBaseCharacterMovement, Verbose, TEXT("Skipping network smoothing for %s."), *GetNameSafe(CharacterOwner)); + } +} + +void UVRBaseCharacterMovementComponent::MoveAutonomous( + float ClientTimeStamp, + float DeltaTime, + uint8 CompressedFlags, + const FVector& NewAccel +) +{ + if (!HasValidData()) + { + return; + } + + UpdateFromCompressedFlags(CompressedFlags); + CharacterOwner->CheckJumpInput(DeltaTime); + + Acceleration = ConstrainInputAcceleration(NewAccel); + Acceleration = Acceleration.GetClampedToMaxSize(GetMaxAcceleration()); + AnalogInputModifier = ComputeAnalogInputModifier(); + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + FQuat OldRotation = UpdatedComponent->GetComponentQuat(); + + if (BaseVRCharacterOwner && NetworkSmoothingMode == ENetworkSmoothingMode::Exponential) + { + OldLocation = BaseVRCharacterOwner->OffsetComponentToWorld.GetTranslation(); + OldRotation = BaseVRCharacterOwner->OffsetComponentToWorld.GetRotation(); + } + + const bool bWasPlayingRootMotion = CharacterOwner->IsPlayingRootMotion(); + + PerformMovement(DeltaTime); + + // Check if data is valid as PerformMovement can mark character for pending kill + if (!HasValidData()) + { + return; + } + + // If not playing root motion, tick animations after physics. We do this here to keep events, notifies, states and transitions in sync with client updates. + if (CharacterOwner && !CharacterOwner->bClientUpdating && !CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh()) + { + if (!bWasPlayingRootMotion) // If we were playing root motion before PerformMovement but aren't anymore, we're on the last frame of anim root motion and have already ticked character + { + TickCharacterPose(DeltaTime); + } + // TODO: SaveBaseLocation() in case tick moves us? + + // Trigger Events right away, as we could be receiving multiple ServerMoves per frame. + CharacterOwner->GetMesh()->ConditionallyDispatchQueuedAnimEvents(); + } + + if (CharacterOwner && UpdatedComponent) + { + // Smooth local view of remote clients on listen servers + static const auto CVarNetEnableListenServerSmoothing = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableListenServerSmoothing")); + if (CVarNetEnableListenServerSmoothing->GetInt() && + CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy && + IsNetMode(NM_ListenServer)) + { + SmoothCorrection(OldLocation, OldRotation, UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat()); + } + } +} + +void UVRBaseCharacterMovementComponent::SmoothCorrection(const FVector& OldLocation, const FQuat& OldRotation, const FVector& NewLocation, const FQuat& NewRotation) +{ + + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementSmoothCorrection); + if (!HasValidData()) + { + return; + } + + if (!BaseVRCharacterOwner) + Super::SmoothCorrection(OldLocation, OldRotation, NewLocation, NewRotation); + + // We shouldn't be running this on a server that is not a listen server. + checkSlow(GetNetMode() != NM_DedicatedServer); + checkSlow(GetNetMode() != NM_Standalone); + + // Only client proxies or remote clients on a listen server should run this code. + const bool bIsSimulatedProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + const bool bIsRemoteAutoProxy = (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy); + ensure(bIsSimulatedProxy || bIsRemoteAutoProxy); + + // Getting a correction means new data, so smoothing needs to run. + bNetworkSmoothingComplete = false; + + // Handle selected smoothing mode. + if (NetworkSmoothingMode == ENetworkSmoothingMode::Replay) + { + // Replays use pure interpolation in this mode, all of the work is done in SmoothClientPosition_Interpolate + return; + } + else if (NetworkSmoothingMode == ENetworkSmoothingMode::Disabled) + { + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, ETeleportType::TeleportPhysics); + bNetworkSmoothingComplete = true; + } + else if (FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character()) + { + const UWorld* MyWorld = GetWorld(); + if (!ensure(MyWorld != nullptr)) + { + return; + } + + // Handle my custom VR Offset + FVector OldWorldLocation = OldLocation; + FQuat OldWorldRotation = OldRotation; + FVector NewWorldLocation = NewLocation; + FQuat NewWorldRotation = NewRotation; + + if (BaseVRCharacterOwner && NetworkSmoothingMode == ENetworkSmoothingMode::Exponential) + { + if (GetNetMode() < ENetMode::NM_Client) + { + NewWorldLocation = BaseVRCharacterOwner->OffsetComponentToWorld.GetTranslation(); + NewWorldRotation = BaseVRCharacterOwner->OffsetComponentToWorld.GetRotation(); + } + else + { + FTransform NewWorldTransform(NewRotation, NewLocation, UpdatedComponent->GetRelativeScale3D()); + FTransform CurrentRelative = BaseVRCharacterOwner->OffsetComponentToWorld.GetRelativeTransform(UpdatedComponent->GetComponentTransform()); + FTransform NewWorld = CurrentRelative * NewWorldTransform; + OldWorldLocation = BaseVRCharacterOwner->OffsetComponentToWorld.GetLocation(); + OldWorldRotation = BaseVRCharacterOwner->OffsetComponentToWorld.GetRotation(); + NewWorldLocation = NewWorld.GetLocation(); + NewWorldRotation = NewWorld.GetRotation(); + } + } + + // The mesh doesn't move, but the capsule does so we have a new offset. + FVector NewToOldVector = (OldWorldLocation - NewWorldLocation); + if (bIsNavWalkingOnServer && FMath::Abs(NewToOldVector.Z) < NavWalkingFloorDistTolerance) + { + // ignore smoothing on Z axis + // don't modify new location (local simulation result), since it's probably more accurate than server data + // and shouldn't matter as long as difference is relatively small + NewToOldVector.Z = 0; + } + + const float DistSq = NewToOldVector.SizeSquared(); + if (DistSq > FMath::Square(ClientData->MaxSmoothNetUpdateDist)) + { + ClientData->MeshTranslationOffset = (DistSq > FMath::Square(ClientData->NoSmoothNetUpdateDist)) + ? FVector::ZeroVector + : ClientData->MeshTranslationOffset + ClientData->MaxSmoothNetUpdateDist * NewToOldVector.GetSafeNormal(); + } + else + { + ClientData->MeshTranslationOffset = ClientData->MeshTranslationOffset + NewToOldVector; + } + + UE_LOG(LogVRBaseCharacterMovement, Verbose, TEXT("Proxy %s SmoothCorrection(%.2f)"), *GetNameSafe(CharacterOwner), FMath::Sqrt(DistSq)); + if (NetworkSmoothingMode == ENetworkSmoothingMode::Linear) + { + // #TODO: Get this working in the future? + // I am currently skipping smoothing on rotation operations + if ((!OldRotation.Equals(NewRotation, 1e-5f)))// || Velocity.IsNearlyZero())) + { + BaseVRCharacterOwner->NetSmoother->SetRelativeLocation(FVector::ZeroVector); + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, GetTeleportType()); + ClientData->MeshTranslationOffset = FVector::ZeroVector; + ClientData->MeshRotationOffset = ClientData->MeshRotationTarget; + bNetworkSmoothingComplete = true; + } + else + { + ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset; + + // Remember the current and target rotation, we're going to lerp between them + ClientData->OriginalMeshRotationOffset = OldRotation; + ClientData->MeshRotationOffset = OldRotation; + ClientData->MeshRotationTarget = NewRotation; + + // Move the capsule, but not the mesh. + // Note: we don't change rotation, we lerp towards it in SmoothClientPosition. + if (NewLocation != OldLocation) + { + const FScopedPreventAttachedComponentMove PreventMeshMove(BaseVRCharacterOwner->NetSmoother); + UpdatedComponent->SetWorldLocation(NewLocation, false, nullptr, GetTeleportType()); + } + } + } + else + { + // #TODO: Get this working in the future? + // I am currently skipping smoothing on rotation operations + /*if ((!OldRotation.Equals(NewRotation, 1e-5f)))// || Velocity.IsNearlyZero())) + { + BaseVRCharacterOwner->NetSmoother->SetRelativeLocation(FVector::ZeroVector); + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, GetTeleportType()); + ClientData->MeshTranslationOffset = FVector::ZeroVector; + ClientData->MeshRotationOffset = ClientData->MeshRotationTarget; + bNetworkSmoothingComplete = true; + } + else*/ + { + // Calc rotation needed to keep current world rotation after UpdatedComponent moves. + // Take difference between where we were rotated before, and where we're going + ClientData->MeshRotationOffset = FQuat::Identity;// (NewRotation.Inverse() * OldRotation) * ClientData->MeshRotationOffset; + ClientData->MeshRotationTarget = FQuat::Identity; + + const FScopedPreventAttachedComponentMove PreventMeshMove(BaseVRCharacterOwner->NetSmoother); + UpdatedComponent->SetWorldLocationAndRotation(NewLocation, NewRotation, false, nullptr, GetTeleportType()); + } + } + + ////////////////////////////////////////////////////////////////////////// + // Update smoothing timestamps + + // If running ahead, pull back slightly. This will cause the next delta to seem slightly longer, and cause us to lerp to it slightly slower. + if (ClientData->SmoothingClientTimeStamp > ClientData->SmoothingServerTimeStamp) + { + const double OldClientTimeStamp = ClientData->SmoothingClientTimeStamp; + ClientData->SmoothingClientTimeStamp = FMath::LerpStable(ClientData->SmoothingServerTimeStamp, OldClientTimeStamp, 0.5); + + UE_LOG(LogVRBaseCharacterMovement, VeryVerbose, TEXT("SmoothCorrection: Pull back client from ClientTimeStamp: %.6f to %.6f, ServerTimeStamp: %.6f for %s"), + OldClientTimeStamp, ClientData->SmoothingClientTimeStamp, ClientData->SmoothingServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + + // Using server timestamp lets us know how much time actually elapsed, regardless of packet lag variance. + double OldServerTimeStamp = ClientData->SmoothingServerTimeStamp; + + if (bIsSimulatedProxy) + { + // This value is normally only updated on the server, however some code paths might try to read it instead of the replicated value so copy it for proxies as well. + ServerLastTransformUpdateTimeStamp = CharacterOwner->GetReplicatedServerLastTransformUpdateTimeStamp(); + } + ClientData->SmoothingServerTimeStamp = ServerLastTransformUpdateTimeStamp; + + // Initial update has no delta. + if (ClientData->LastCorrectionTime == 0) + { + ClientData->SmoothingClientTimeStamp = ClientData->SmoothingServerTimeStamp; + OldServerTimeStamp = ClientData->SmoothingServerTimeStamp; + } + + // Don't let the client fall too far behind or run ahead of new server time. + const double ServerDeltaTime = ClientData->SmoothingServerTimeStamp - OldServerTimeStamp; + const double MaxOffset = ClientData->MaxClientSmoothingDeltaTime; + const double MinOffset = FMath::Min(double(ClientData->SmoothNetUpdateTime), MaxOffset); + + // MaxDelta is the farthest behind we're allowed to be after receiving a new server time. + const double MaxDelta = FMath::Clamp(ServerDeltaTime * 1.25, MinOffset, MaxOffset); + ClientData->SmoothingClientTimeStamp = FMath::Clamp(ClientData->SmoothingClientTimeStamp, ClientData->SmoothingServerTimeStamp - MaxDelta, ClientData->SmoothingServerTimeStamp); + + // Compute actual delta between new server timestamp and client simulation. + ClientData->LastCorrectionDelta = ClientData->SmoothingServerTimeStamp - ClientData->SmoothingClientTimeStamp; + ClientData->LastCorrectionTime = MyWorld->GetTimeSeconds(); + + UE_LOG(LogVRBaseCharacterMovement, VeryVerbose, TEXT("SmoothCorrection: WorldTime: %.6f, ServerTimeStamp: %.6f, ClientTimeStamp: %.6f, Delta: %.6f for %s"), + MyWorld->GetTimeSeconds(), ClientData->SmoothingServerTimeStamp, ClientData->SmoothingClientTimeStamp, ClientData->LastCorrectionDelta, *GetNameSafe(CharacterOwner)); + /* + Visualize network smoothing was here, removed it + */ + } +} + +void UVRBaseCharacterMovementComponent::SmoothClientPosition(float DeltaSeconds) +{ + if (!HasValidData() || NetworkSmoothingMode == ENetworkSmoothingMode::Disabled) + { + return; + } + + // We shouldn't be running this on a server that is not a listen server. + checkSlow(GetNetMode() != NM_DedicatedServer); + checkSlow(GetNetMode() != NM_Standalone); + + // Only client proxies or remote clients on a listen server should run this code. + const bool bIsSimulatedProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + const bool bIsRemoteAutoProxy = (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy); + if (!ensure(bIsSimulatedProxy || bIsRemoteAutoProxy)) + { + return; + } + + SmoothClientPosition_Interpolate(DeltaSeconds); + + //SmoothClientPosition_UpdateVisuals(); No mesh, don't bother to run this + SmoothClientPosition_UpdateVRVisuals(); +} + +void UVRBaseCharacterMovementComponent::SmoothClientPosition_UpdateVRVisuals() +{ + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementSmoothClientPosition_Visual); + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + + if (!BaseVRCharacterOwner || !ClientData) + return; + + if (ClientData) + { + if (NetworkSmoothingMode == ENetworkSmoothingMode::Linear) + { + // Erased most of the code here, check back in later + const FVector NewRelLocation = ClientData->MeshRotationOffset.UnrotateVector(ClientData->MeshTranslationOffset);// + CharacterOwner->GetBaseTranslationOffset(); + BaseVRCharacterOwner->NetSmoother->SetRelativeLocation(NewRelLocation); + } + else if (NetworkSmoothingMode == ENetworkSmoothingMode::Exponential) + { + // Adjust mesh location and rotation + const FVector NewRelTranslation = UpdatedComponent->GetComponentToWorld().InverseTransformVectorNoScale(ClientData->MeshTranslationOffset);// +CharacterOwner->GetBaseTranslationOffset(); + const FQuat NewRelRotation = ClientData->MeshRotationOffset;// *CharacterOwner->GetBaseRotationOffset(); + //Basechar->NetSmoother->SetRelativeLocation(NewRelTranslation); + + BaseVRCharacterOwner->NetSmoother->SetRelativeLocationAndRotation(NewRelTranslation, NewRelRotation); + + } + else if (NetworkSmoothingMode == ENetworkSmoothingMode::Replay) + { + if (!UpdatedComponent->GetComponentQuat().Equals(ClientData->MeshRotationOffset, SCENECOMPONENT_QUAT_TOLERANCE))// || !UpdatedComponent->GetComponentLocation().Equals(ClientData->MeshTranslationOffset, KINDA_SMALL_NUMBER)) + { + //UpdatedComponent->SetWorldLocation(ClientData->MeshTranslationOffset); + UpdatedComponent->SetWorldLocationAndRotation(ClientData->MeshTranslationOffset, ClientData->MeshRotationOffset); + } + } + else + { + // Unhandled mode + } + } +} + +void UVRBaseCharacterMovementComponent::SetHasRequestedVelocity(bool bNewHasRequestedVelocity) +{ + bHasRequestedVelocity = bNewHasRequestedVelocity; +} + +bool UVRBaseCharacterMovementComponent::IsClimbing() const +{ + return ((MovementMode == MOVE_Custom) && (CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing)) && UpdatedComponent; +} + +FVector UVRBaseCharacterMovementComponent::RewindVRMovement() +{ + RewindVRRelativeMovement(); + return AdditionalVRInputVector; +} + +FVector UVRBaseCharacterMovementComponent::GetCustomInputVector() +{ + return CustomVRInputVector; +} + +void UVRBaseCharacterMovementComponent::UpdateFromCompressedFlags(uint8 Flags) +{ + // If is a custom or VR custom movement mode + //int32 MovementFlags = (Flags >> 2) & 15; + //VRReplicatedMovementMode = (EVRConjoinedMovementModes)MovementFlags; + + //bWantsToSnapTurn = ((Flags & FSavedMove_VRBaseCharacter::FLAG_SnapTurn) != 0); + + Super::UpdateFromCompressedFlags(Flags); +} + +FVector UVRBaseCharacterMovementComponent::RoundDirectMovement(FVector InMovement) const +{ + // Match FVector_NetQuantize100 (2 decimal place of precision). + InMovement.X = FMath::RoundToFloat(InMovement.X * 100.f) / 100.f; + InMovement.Y = FMath::RoundToFloat(InMovement.Y * 100.f) / 100.f; + InMovement.Z = FMath::RoundToFloat(InMovement.Z * 100.f) / 100.f; + return InMovement; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacter.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacter.cpp new file mode 100644 index 0000000..8480776 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacter.cpp @@ -0,0 +1,221 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRCharacter.h" +#include "NavigationSystem.h" +#include "VRBPDatatypes.h" +//#include "GripMotionControllerComponent.h" +//#include "VRExpansionFunctionLibrary.h" +//#include "ReplicatedVRCameraComponent.h" +//#include "ParentRelativeAttachmentComponent.h" +#include "VRRootComponent.h" +#include "VRCharacterMovementComponent.h" +#include "Runtime/Launch/Resources/Version.h" +#include "VRPathFollowingComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + +DEFINE_LOG_CATEGORY(LogVRCharacter); + +AVRCharacter::AVRCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer.SetDefaultSubobjectClass<UVRRootComponent>(ACharacter::CapsuleComponentName).SetDefaultSubobjectClass<UVRCharacterMovementComponent>(ACharacter::CharacterMovementComponentName)) +{ + VRRootReference = NULL; + if (GetCapsuleComponent()) + { + VRRootReference = Cast<UVRRootComponent>(GetCapsuleComponent()); + VRRootReference->SetCapsuleSize(20.0f, 96.0f); + //VRRootReference->VRCapsuleOffset = FVector(-8.0f, 0.0f, 0.0f); + VRRootReference->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); + VRRootReference->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldStatic, ECollisionResponse::ECR_Block); + } + + VRMovementReference = NULL; + if (GetMovementComponent()) + { + VRMovementReference = Cast<UVRBaseCharacterMovementComponent>(GetMovementComponent()); + //AddTickPrerequisiteComponent(this->GetCharacterMovement()); + } +} + + +FVector AVRCharacter::GetTeleportLocation(FVector OriginalLocation) +{ + FVector modifier = VRRootReference->OffsetComponentToWorld.GetLocation() - this->GetActorLocation(); + modifier.Z = 0.0f; // Null out Z + return OriginalLocation - modifier; +} + +bool AVRCharacter::TeleportTo(const FVector& DestLocation, const FRotator& DestRotation, bool bIsATest, bool bNoCheck) +{ + bool bTeleportSucceeded = Super::TeleportTo(DestLocation, DestRotation, bIsATest, bNoCheck); + + if (bTeleportSucceeded) + { + NotifyOfTeleport(); + } + + return bTeleportSucceeded; +} + +FVector AVRCharacter::GetNavAgentLocation() const +{ + FVector AgentLocation = FNavigationSystem::InvalidLocation; + + if (GetCharacterMovement() != nullptr) + { + if (VRMovementReference) + { + AgentLocation = VRMovementReference->GetActorFeetLocationVR(); + } + else + AgentLocation = GetCharacterMovement()->GetActorFeetLocation(); + } + + if (FNavigationSystem::IsValidLocation(AgentLocation) == false /*&& GetCapsuleComponent() != nullptr*/) + { + if (VRRootReference) + { + AgentLocation = VRRootReference->OffsetComponentToWorld.GetLocation() - FVector(0, 0, VRRootReference->GetScaledCapsuleHalfHeight()); + } + else if(GetCapsuleComponent() != nullptr) + AgentLocation = GetActorLocation() - FVector(0, 0, GetCapsuleComponent()->GetScaledCapsuleHalfHeight()); + } + + return AgentLocation; +} + +void AVRCharacter::ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius, bool bStopOnOverlap, bool bUsePathfinding, bool bProjectDestinationToNavigation, bool bCanStrafe, TSubclassOf<UNavigationQueryFilter> FilterClass, bool bAllowPartialPaths) +{ + UNavigationSystemV1* NavSys = Controller ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(Controller->GetWorld()) : nullptr; + if (NavSys == nullptr || Controller == nullptr ) + { + UE_LOG(LogVRCharacter, Warning, TEXT("UVRCharacter::ExtendedSimpleMoveToLocation called for NavSys:%s Controller:%s (if any of these is None then there's your problem"), + *GetNameSafe(NavSys), *GetNameSafe(Controller)); + return; + } + + UPathFollowingComponent* PFollowComp = nullptr; + //Controller->InitNavigationControl(PFollowComp); + + if (Controller) + { + // New for 4.20, spawning the missing path following component here if there isn't already one + PFollowComp = Controller->FindComponentByClass<UPathFollowingComponent>(); + if (PFollowComp == nullptr) + { + PFollowComp = NewObject<UVRPathFollowingComponent>(Controller); + PFollowComp->RegisterComponentWithWorld(Controller->GetWorld()); + PFollowComp->Initialize(); + } + } + + if (PFollowComp == nullptr) + { + UE_LOG(LogVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - No PathFollowingComponent Found")); + return; + } + + if (!PFollowComp->IsPathFollowingAllowed()) + { + UE_LOG(LogVRCharacter, Warning, TEXT("ExtendedSimpleMoveToLocation - Path Following Movement Is Not Set To Allowed")); + return; + } + + EPathFollowingReachMode ReachMode; + if (bStopOnOverlap) + ReachMode = EPathFollowingReachMode::OverlapAgent; + else + ReachMode = EPathFollowingReachMode::ExactLocation; + + bool bAlreadyAtGoal = false; + + if(UVRPathFollowingComponent * pathcomp = Cast<UVRPathFollowingComponent>(PFollowComp)) + bAlreadyAtGoal = pathcomp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + else + bAlreadyAtGoal = PFollowComp->HasReached(GoalLocation, /*EPathFollowingReachMode::OverlapAgent*/ReachMode); + + // script source, keep only one move request at time + if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + if (GetNetMode() == ENetMode::NM_Client) + { + // Stop the movement here, not keeping the velocity because it bugs out for clients, might be able to fix. + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, /*bAlreadyAtGoal ? */EPathFollowingVelocityMode::Reset /*: EPathFollowingVelocityMode::Keep*/); + } + else + { + PFollowComp->AbortMove(*NavSys, FPathFollowingResultFlags::ForcedScript | FPathFollowingResultFlags::NewRequest + , FAIRequestID::AnyRequest, bAlreadyAtGoal ? EPathFollowingVelocityMode::Reset : EPathFollowingVelocityMode::Keep); + } + } + + if (bAlreadyAtGoal) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Success); + } + else + { + const ANavigationData* NavData = NavSys->GetNavDataForProps(Controller->GetNavAgentPropertiesRef()); + if (NavData) + { + FPathFindingQuery Query(Controller, *NavData, Controller->GetNavAgentLocation(), GoalLocation); + FPathFindingResult Result = NavSys->FindPathSync(Query); + if (Result.IsSuccessful()) + { + FAIMoveRequest MoveReq(GoalLocation); + MoveReq.SetUsePathfinding(bUsePathfinding); + MoveReq.SetAllowPartialPath(bAllowPartialPaths); + MoveReq.SetProjectGoalLocation(bProjectDestinationToNavigation); + MoveReq.SetNavigationFilter(*FilterClass ? FilterClass : DefaultNavigationFilterClass); + MoveReq.SetAcceptanceRadius(AcceptanceRadius); + MoveReq.SetReachTestIncludesAgentRadius(bStopOnOverlap); + MoveReq.SetCanStrafe(bCanStrafe); + MoveReq.SetReachTestIncludesGoalRadius(true); + + PFollowComp->RequestMove(/*FAIMoveRequest(GoalLocation)*/MoveReq, Result.Path); + } + else if (PFollowComp->GetStatus() != EPathFollowingStatus::Idle) + { + PFollowComp->RequestMoveWithImmediateFinish(EPathFollowingResult::Invalid); + } + } + } +} + +void AVRCharacter::RegenerateOffsetComponentToWorld(bool bUpdateBounds, bool bCalculatePureYaw) +{ + if (VRRootReference) + { + VRRootReference->GenerateOffsetToWorld(bUpdateBounds, bCalculatePureYaw); + } +} + +void AVRCharacter::SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps) +{ + if (VRRootReference) + { + VRRootReference->SetCapsuleSizeVR(NewRadius, NewHalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client) + ReplicatedCapsuleHeight.CapsuleHeight = VRRootReference->GetUnscaledCapsuleHalfHeight(); + } + else + { + Super::SetCharacterSizeVR(NewRadius, NewHalfHeight, bUpdateOverlaps); + } +} + +void AVRCharacter::SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps) +{ + if (VRRootReference) + { + VRRootReference->SetCapsuleHalfHeightVR(HalfHeight, bUpdateOverlaps); + + if (GetNetMode() < ENetMode::NM_Client) + ReplicatedCapsuleHeight.CapsuleHeight = VRRootReference->GetUnscaledCapsuleHalfHeight(); + } + else + { + Super::SetCharacterHalfHeightVR(HalfHeight, bUpdateOverlaps); + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacterMovementComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacterMovementComponent.cpp new file mode 100644 index 0000000..9d5d97c --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRCharacterMovementComponent.cpp @@ -0,0 +1,4288 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + Movement.cpp: Character movement implementation + +=============================================================================*/ + +#include "VRCharacterMovementComponent.h" +#include "GameFramework/PhysicsVolume.h" +#include "GameFramework/GameNetworkManager.h" +#include "GameFramework/Character.h" +#include "GameFramework/GameState.h" +#include "Components/PrimitiveComponent.h" +#include "Animation/AnimMontage.h" +#include "DrawDebugHelpers.h" +//#include "PhysicsEngine/DestructibleActor.h" +#include "VRCharacter.h" +#include "VRExpansionFunctionLibrary.h" + +// @todo this is here only due to circular dependency to AIModule. To be removed +#include "Navigation/PathFollowingComponent.h" +#include "AI/Navigation/AvoidanceManager.h" +#include "Components/CapsuleComponent.h" +#include "Components/BrushComponent.h" +//#include "Components/DestructibleComponent.h" + +#include "Engine/DemoNetDriver.h" +#include "Engine/NetworkObjectList.h" + +#include "VRRootComponent.h" +#include "WorldCollision.h" +#include "Runtime/Launch/Resources/Version.h" +#include "GameFramework/CharacterMovementReplication.h" +#include "Interfaces/NetworkPredictionInterface.h" + +//#include "PerfCountersHelpers.h" + +DEFINE_LOG_CATEGORY(LogVRCharacterMovement); + +/** + * Character stats + */ +DECLARE_CYCLE_STAT(TEXT("Char StepUp"), STAT_CharStepUp, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char FindFloor"), STAT_CharFindFloor, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char ReplicateMoveToServer"), STAT_CharacterMovementReplicateMoveToServer, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char CallServerMove"), STAT_CharacterMovementCallServerMove, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char CombineNetMove"), STAT_CharacterMovementCombineNetMove, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char PhysWalking"), STAT_CharPhysWalking, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char PhysFalling"), STAT_CharPhysFalling, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char PhysNavWalking"), STAT_CharPhysNavWalking, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char NavProjectPoint"), STAT_CharNavProjectPoint, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char NavProjectLocation"), STAT_CharNavProjectLocation, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char AdjustFloorHeight"), STAT_CharAdjustFloorHeight, STATGROUP_Character); +DECLARE_CYCLE_STAT(TEXT("Char ProcessLanded"), STAT_CharProcessLanded, STATGROUP_Character); + +namespace CharacterMovementConstants +{ + // MAGIC NUMBERS + const float MAX_STEP_SIDE_ZVR = 0.08f; // maximum z value for the normal on the vertical side of steps + const float SWIMBOBSPEEDVR = -80.f; + const float VERTICAL_SLOPE_NORMAL_ZVR = 0.001f; // Slope is vertical if Abs(Normal.Z) <= this threshold. Accounts for precision problems that sometimes angle normals slightly off horizontal for vertical surface. +} + +// Defines for build configs +#if DO_CHECK && !UE_BUILD_SHIPPING // Disable even if checks in shipping are enabled. +#define devCode( Code ) checkCode( Code ) +#else +#define devCode(...) +#endif + +// Statics +namespace CharacterMovementComponentStatics +{ + static const FName CrouchTraceName = FName(TEXT("CrouchTrace")); + static const FName ImmersionDepthName = FName(TEXT("MovementComp_Character_ImmersionDepth")); + + static float fRotationCorrectionThreshold = 0.02f; + FAutoConsoleVariableRef CVarRotationCorrectionThreshold( + TEXT("vre.RotationCorrectionThreshold"), + fRotationCorrectionThreshold, + TEXT("Error threshold value before correcting a clients rotation.\n") + TEXT("Rotation is replicated at 2 decimal precision, so values less than 0.01 won't matter."), + ECVF_Default); + +} + +void UVRCharacterMovementComponent::StoreSetTrackingPaused(bool bNewTrackingPaused) +{ + FVRMoveActionContainer MoveAction; + MoveAction.MoveAction = EVRMoveAction::VRMOVEACTION_PauseTracking; + MoveAction.MoveActionFlags = bNewTrackingPaused; + MoveAction.MoveActionLoc = VRRootCapsule->curCameraLoc; + MoveAction.MoveActionRot = VRRootCapsule->StoredCameraRotOffset; + MoveActionArray.MoveActions.Add(MoveAction); + CheckServerAuthedMoveAction(); +} + +void UVRCharacterMovementComponent::Crouch(bool bClientSimulation) +{ + if (!HasValidData()) + { + return; + } + + if (!bClientSimulation && !CanCrouchInCurrentState()) + { + return; + } + + // See if collision is already at desired size. + if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == GetCrouchedHalfHeight()) + { + if (!bClientSimulation) + { + CharacterOwner->bIsCrouched = true; + } + CharacterOwner->OnStartCrouch(0.f, 0.f); + return; + } + + if (bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) + { + // restore collision size before crouching + ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>(); + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight()); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight()); + + bShrinkProxyCapsule = true; + } + + // Change collision size to crouching dimensions + const float ComponentScale = CharacterOwner->GetCapsuleComponent()->GetShapeScale(); + const float OldUnscaledHalfHeight = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(); + const float OldUnscaledRadius = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleRadius(); + // Height is not allowed to be smaller than radius. + const float ClampedCrouchedHalfHeight = FMath::Max3(0.f, OldUnscaledRadius, GetCrouchedHalfHeight()); + + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(OldUnscaledRadius, ClampedCrouchedHalfHeight); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(OldUnscaledRadius, ClampedCrouchedHalfHeight); + + float HalfHeightAdjust = (OldUnscaledHalfHeight - ClampedCrouchedHalfHeight); + float ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale; + + if (!bClientSimulation) + { + // Crouching to a larger height? (this is rare) + if (ClampedCrouchedHalfHeight > OldUnscaledHalfHeight) + { + FCollisionQueryParams CapsuleParams(CharacterMovementComponentStatics::CrouchTraceName, false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(CapsuleParams, ResponseParam); + + FVector capLocation; + if (VRRootCapsule) + { + capLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + } + else + capLocation = UpdatedComponent->GetComponentLocation(); + + const bool bEncroached = GetWorld()->OverlapBlockingTestByChannel(capLocation - FVector(0.f, 0.f, ScaledHalfHeightAdjust), FQuat::Identity, + UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CapsuleParams, ResponseParam); + + // If encroached, cancel + if (bEncroached) + { + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(OldUnscaledRadius, OldUnscaledHalfHeight); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(OldUnscaledRadius, OldUnscaledHalfHeight); + return; + } + } + + // Skipping this move down as the VR character's base root doesn't behave the same + if (bCrouchMaintainsBaseLocation) + { + // Intentionally not using MoveUpdatedComponent, where a horizontal plane constraint would prevent the base of the capsule from staying at the same spot. + //UpdatedComponent->MoveComponent(FVector(0.f, 0.f, -ScaledHalfHeightAdjust), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + } + + CharacterOwner->bIsCrouched = true; + } + + bForceNextFloorCheck = true; + + // OnStartCrouch takes the change from the Default size, not the current one (though they are usually the same). + const float MeshAdjust = ScaledHalfHeightAdjust; + ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>(); + HalfHeightAdjust = (DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() - ClampedCrouchedHalfHeight); + ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale; + + AdjustProxyCapsuleSize(); + CharacterOwner->OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust); + + // Don't smooth this change in mesh position + if (/*(bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) ||*/ (IsNetMode(NM_ListenServer) && CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy)) + { + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + + if (ClientData) + { + ClientData->MeshTranslationOffset -= FVector(0.f, 0.f, MeshAdjust); + ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset; + } + } +} + +void UVRCharacterMovementComponent::UnCrouch(bool bClientSimulation) +{ + if (!HasValidData()) + { + return; + } + + ACharacter* DefaultCharacter = CharacterOwner->GetClass()->GetDefaultObject<ACharacter>(); + + // See if collision is already at desired size. + if (CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() == DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight()) + { + if (!bClientSimulation) + { + CharacterOwner->bIsCrouched = false; + } + CharacterOwner->OnEndCrouch(0.f, 0.f); + return; + } + + const float CurrentCrouchedHalfHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight(); + + const float ComponentScale = CharacterOwner->GetCapsuleComponent()->GetShapeScale(); + const float OldUnscaledHalfHeight = CharacterOwner->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(); + const float HalfHeightAdjust = DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight() - OldUnscaledHalfHeight; + const float ScaledHalfHeightAdjust = HalfHeightAdjust * ComponentScale; + + /*const*/ FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + + if (VRRootCapsule) + { + PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + } + + // Grow to uncrouched size. + check(CharacterOwner->GetCapsuleComponent()); + + if (!bClientSimulation) + { + // Try to stay in place and see if the larger capsule fits. We use a slightly taller capsule to avoid penetration. + const UWorld* MyWorld = GetWorld(); + const float SweepInflation = KINDA_SMALL_NUMBER * 10.f; + FCollisionQueryParams CapsuleParams(CharacterMovementComponentStatics::CrouchTraceName, false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(CapsuleParams, ResponseParam); + + // Compensate for the difference between current capsule size and standing size + const FCollisionShape StandingCapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_HeightCustom, -SweepInflation - ScaledHalfHeightAdjust); // Shrink by negative amount, so actually grow it. + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + bool bEncroached = true; + + if (!bCrouchMaintainsBaseLocation) + { + // Expand in place + bEncroached = MyWorld->OverlapBlockingTestByChannel(PawnLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + + if (bEncroached) + { + // Try adjusting capsule position to see if we can avoid encroachment. + if (ScaledHalfHeightAdjust > 0.f) + { + // Shrink to a short capsule, sweep down to base to find where that would hit something, and then try to stand up from there. + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + const float ShrinkHalfHeight = PawnHalfHeight - PawnRadius; + const float TraceDist = PawnHalfHeight - ShrinkHalfHeight; + const FVector Down = FVector(0.f, 0.f, -TraceDist); + + FHitResult Hit(1.f); + const FCollisionShape ShortCapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_HeightCustom, ShrinkHalfHeight); + const bool bBlockingHit = MyWorld->SweepSingleByChannel(Hit, PawnLocation, PawnLocation + Down, FQuat::Identity, CollisionChannel, ShortCapsuleShape, CapsuleParams); + if (Hit.bStartPenetrating) + { + bEncroached = true; + } + else + { + // Compute where the base of the sweep ended up, and see if we can stand there + const float DistanceToBase = (Hit.Time * TraceDist) + ShortCapsuleShape.Capsule.HalfHeight; + const FVector NewLoc = FVector(PawnLocation.X, PawnLocation.Y, PawnLocation.Z - DistanceToBase + StandingCapsuleShape.Capsule.HalfHeight + SweepInflation + MIN_FLOOR_DIST / 2.f); + bEncroached = MyWorld->OverlapBlockingTestByChannel(NewLoc, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + if (!bEncroached) + { + // Intentionally not using MoveUpdatedComponent, where a horizontal plane constraint would prevent the base of the capsule from staying at the same spot. + UpdatedComponent->MoveComponent(NewLoc - PawnLocation, UpdatedComponent->GetComponentQuat(), false, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + } + } + } + } + } + else + { + // Expand while keeping base location the same. + FVector StandingLocation = PawnLocation + FVector(0.f, 0.f, StandingCapsuleShape.GetCapsuleHalfHeight() - CurrentCrouchedHalfHeight); + bEncroached = MyWorld->OverlapBlockingTestByChannel(StandingLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + + if (bEncroached) + { + if (IsMovingOnGround()) + { + // Something might be just barely overhead, try moving down closer to the floor to avoid it. + const float MinFloorDist = KINDA_SMALL_NUMBER * 10.f; + if (CurrentFloor.bBlockingHit && CurrentFloor.FloorDist > MinFloorDist) + { + StandingLocation.Z -= CurrentFloor.FloorDist - MinFloorDist; + bEncroached = MyWorld->OverlapBlockingTestByChannel(StandingLocation, FQuat::Identity, CollisionChannel, StandingCapsuleShape, CapsuleParams, ResponseParam); + } + } + } + + // Canceling this move because our VR capsule isn't actor based like it expects it to be + if (!bEncroached) + { + // Commit the change in location. + //UpdatedComponent->MoveComponent(StandingLocation - PawnLocation, UpdatedComponent->GetComponentQuat(), false, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bForceNextFloorCheck = true; + } + } + + // If still encroached then abort. + if (bEncroached) + { + return; + } + + CharacterOwner->bIsCrouched = false; + } + else + { + bShrinkProxyCapsule = true; + } + + // Now call SetCapsuleSize() to cause touch/untouch events and actually grow the capsule + if (VRRootCapsule) + VRRootCapsule->SetCapsuleSizeVR(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(), true); + else + CharacterOwner->GetCapsuleComponent()->SetCapsuleSize(DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleRadius(), DefaultCharacter->GetCapsuleComponent()->GetUnscaledCapsuleHalfHeight(), true); + + const float MeshAdjust = ScaledHalfHeightAdjust; + AdjustProxyCapsuleSize(); + CharacterOwner->OnEndCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust); + + // Don't smooth this change in mesh position + if (/*(bClientSimulation && CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy) || */(IsNetMode(NM_ListenServer) && CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy)) + { + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + + if (ClientData) + { + ClientData->MeshTranslationOffset += FVector(0.f, 0.f, MeshAdjust); + ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset; + } + } +} + +FNetworkPredictionData_Client* UVRCharacterMovementComponent::GetPredictionData_Client() const +{ + // Should only be called on client or listen server (for remote clients) in network games + check(CharacterOwner != NULL); + checkSlow(CharacterOwner->GetLocalRole() < ROLE_Authority || (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy && GetNetMode() == NM_ListenServer)); + checkSlow(GetNetMode() == NM_Client || GetNetMode() == NM_ListenServer); + + if (!ClientPredictionData) + { + UVRCharacterMovementComponent* MutableThis = const_cast<UVRCharacterMovementComponent*>(this); + MutableThis->ClientPredictionData = new FNetworkPredictionData_Client_VRCharacter(*this); + } + + return ClientPredictionData; +} + +FNetworkPredictionData_Server* UVRCharacterMovementComponent::GetPredictionData_Server() const +{ + // Should only be called on server in network games + check(CharacterOwner != NULL); + check(CharacterOwner->GetLocalRole() == ROLE_Authority); + checkSlow(GetNetMode() < NM_Client); + + if (!ServerPredictionData) + { + UVRCharacterMovementComponent* MutableThis = const_cast<UVRCharacterMovementComponent*>(this); + MutableThis->ServerPredictionData = new FNetworkPredictionData_Server_VRCharacter(*this); + } + + return ServerPredictionData; +} + +void FSavedMove_VRCharacter::SetInitialPosition(ACharacter* C) +{ + + // See if we can get the VR capsule location + if (AVRCharacter * VRC = Cast<AVRCharacter>(C)) + { + UVRCharacterMovementComponent * CharMove = Cast<UVRCharacterMovementComponent>(VRC->GetCharacterMovement()); + if (VRC->VRRootReference) + { + VRCapsuleLocation = VRC->VRRootReference->curCameraLoc; + VRCapsuleRotation = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRC->VRRootReference->curCameraRot); + LFDiff = VRC->VRRootReference->DifferenceFromLastFrame; + } + else + { + VRCapsuleLocation = FVector::ZeroVector; + VRCapsuleRotation = FRotator::ZeroRotator; + LFDiff = FVector::ZeroVector; + } + } + + FSavedMove_VRBaseCharacter::SetInitialPosition(C); +} + +void FSavedMove_VRCharacter::PrepMoveFor(ACharacter* Character) +{ + UVRCharacterMovementComponent * CharMove = Cast<UVRCharacterMovementComponent>(Character->GetCharacterMovement()); + + // Set capsule location prior to testing movement + // I am overriding the replicated value here when movement is made on purpose + if (CharMove && CharMove->VRRootCapsule) + { + CharMove->VRRootCapsule->curCameraLoc = this->VRCapsuleLocation; + CharMove->VRRootCapsule->curCameraRot = this->VRCapsuleRotation;//FRotator(0.0f, FRotator::DecompressAxisFromByte(CapsuleYaw), 0.0f); + CharMove->VRRootCapsule->DifferenceFromLastFrame = FVector(LFDiff.X, LFDiff.Y, 0.0f); + CharMove->AdditionalVRInputVector = CharMove->VRRootCapsule->DifferenceFromLastFrame; + + if (AVRBaseCharacter * BaseChar = Cast<AVRBaseCharacter>(CharMove->GetCharacterOwner())) + { + if (BaseChar->VRReplicateCapsuleHeight && this->LFDiff.Z > 0.0f && !FMath::IsNearlyEqual(this->LFDiff.Z, CharMove->VRRootCapsule->GetUnscaledCapsuleHalfHeight())) + { + BaseChar->SetCharacterHalfHeightVR(LFDiff.Z, false); + //CharMove->VRRootCapsule->SetCapsuleHalfHeight(this->LFDiff.Z, false); + } + } + + CharMove->VRRootCapsule->GenerateOffsetToWorld(false, false); + } + + FSavedMove_VRBaseCharacter::PrepMoveFor(Character); +} + + +void UVRCharacterMovementComponent::ServerMove_PerformMovement(const FCharacterNetworkMoveData& MoveData) +{ + QUICK_SCOPE_CYCLE_COUNTER(VRCharacterMovementServerMove_PerformMovement); + //SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementServerMove); + //CSV_SCOPED_TIMING_STAT(CharacterMovement, CharacterMovementServerMove); + + if (!HasValidData() || !IsActive()) + { + return; + } + + bool bAutoAcceptPacket = false; + + FNetworkPredictionData_Server_Character* ServerData = GetPredictionData_Server_Character(); + check(ServerData); + + if (MovementMode == MOVE_Custom && CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Seated) + { + return; + } + else if (bJustUnseated) + { + ServerData->CurrentClientTimeStamp = MoveData.TimeStamp; + bAutoAcceptPacket = true; + bJustUnseated = false; + } + + const float ClientTimeStamp = MoveData.TimeStamp; + FVector_NetQuantize10 ClientAccel = MoveData.Acceleration; + const uint8 ClientMoveFlags = MoveData.CompressedMoveFlags; + const FRotator ClientControlRotation = MoveData.ControlRotation; + + if (!bAutoAcceptPacket && !VerifyClientTimeStamp(ClientTimeStamp, *ServerData)) + { + const float ServerTimeStamp = ServerData->CurrentClientTimeStamp; + // This is more severe if the timestamp has a large discrepancy and hasn't been recently reset. + static const auto CVarNetServerMoveTimestampExpiredWarningThreshold = IConsoleManager::Get().FindConsoleVariable(TEXT("net.NetServerMoveTimestampExpiredWarningThreshold")); + if (ServerTimeStamp > 1.0f && FMath::Abs(ServerTimeStamp - ClientTimeStamp) > CVarNetServerMoveTimestampExpiredWarningThreshold->GetFloat()) + { + UE_LOG(LogNetPlayerMovement, Warning, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + else + { + UE_LOG(LogNetPlayerMovement, Log, TEXT("ServerMove: TimeStamp expired: %f, CurrentTimeStamp: %f, Character: %s"), ClientTimeStamp, ServerTimeStamp, *GetNameSafe(CharacterOwner)); + } + return; + } + + + // Convert to our stored move data array + const FVRCharacterNetworkMoveData* MoveDataVR = (const FVRCharacterNetworkMoveData*)&MoveData; + + // Scope these, they nest with Outer references so it should work fine, this keeps the update rotation and move autonomous from double updating the char + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + bool bServerReadyForClient = true; + APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController()); + if (PC) + { + bServerReadyForClient = PC->NotifyServerReceivedClientData(CharacterOwner, ClientTimeStamp); + if (!bServerReadyForClient) + { + ClientAccel = FVector::ZeroVector; + } + } + + const UWorld* MyWorld = GetWorld(); + const float DeltaTime = ServerData->GetServerMoveDeltaTime(ClientTimeStamp, CharacterOwner->GetActorTimeDilation(*MyWorld)); + + if (DeltaTime > 0.f) + { + ServerData->CurrentClientTimeStamp = ClientTimeStamp; + ServerData->ServerAccumulatedClientTimeStamp += DeltaTime; + ServerData->ServerTimeStamp = MyWorld->GetTimeSeconds(); + ServerData->ServerTimeStampLastServerMove = ServerData->ServerTimeStamp; + + if (PC && bUseClientControlRotation) + { + PC->SetControlRotation(ClientControlRotation); + } + + if (!bServerReadyForClient) + { + return; + } + + // Perform actual movement + if ((MyWorld->GetWorldSettings()->GetPauserPlayerState() == NULL)) + { + if (PC) + { + PC->UpdateRotation(DeltaTime); + } + + if (!MoveDataVR->ConditionalMoveReps.RequestedVelocity.IsZero()) + { + RequestedVelocity = MoveDataVR->ConditionalMoveReps.RequestedVelocity; + bHasRequestedVelocity = true; + } + + CustomVRInputVector = MoveDataVR->ConditionalMoveReps.CustomVRInputVector; + MoveActionArray = MoveDataVR->ConditionalMoveReps.MoveActionArray; + VRReplicatedMovementMode = MoveDataVR->ReplicatedMovementMode; + + // Set capsule location prior to testing movement + // I am overriding the replicated value here when movement is made on purpose + if (VRRootCapsule) + { + VRRootCapsule->curCameraLoc = MoveDataVR->VRCapsuleLocation; + VRRootCapsule->curCameraRot = FRotator(0.0f, FRotator::DecompressAxisFromShort(MoveDataVR->VRCapsuleRotation), 0.0f); + VRRootCapsule->DifferenceFromLastFrame = FVector(MoveDataVR->LFDiff.X, MoveDataVR->LFDiff.Y, 0.0f); + AdditionalVRInputVector = VRRootCapsule->DifferenceFromLastFrame; + + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->VRReplicateCapsuleHeight && MoveDataVR->LFDiff.Z > 0.0f && !FMath::IsNearlyEqual(MoveDataVR->LFDiff.Z, VRRootCapsule->GetUnscaledCapsuleHalfHeight())) + { + BaseVRCharacterOwner->SetCharacterHalfHeightVR(MoveDataVR->LFDiff.Z, false); + // BaseChar->ReplicatedCapsuleHeight.CapsuleHeight = LFDiff.Z; + //VRRootCapsule->SetCapsuleHalfHeight(LFDiff.Z, false); + } + } + + VRRootCapsule->GenerateOffsetToWorld(false, false); + + // #TODO: Should I actually implement the mesh translation from "Crouch"? Generally people are going to be + // IKing any mesh from the HMD instead. + /* + // Don't smooth this change in mesh position + if (bClientSimulation && CharacterOwner->Role == ROLE_SimulatedProxy) + { + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + if (ClientData && ClientData->MeshTranslationOffset.Z != 0.f) + { + ClientData->MeshTranslationOffset += FVector(0.f, 0.f, MeshAdjust); + ClientData->OriginalMeshTranslationOffset = ClientData->MeshTranslationOffset; + } + } + */ + } + + MoveAutonomous(ClientTimeStamp, DeltaTime, ClientMoveFlags, ClientAccel); + bHasRequestedVelocity = false; + } + + UE_CLOG(CharacterOwner && UpdatedComponent, LogNetPlayerMovement, VeryVerbose, TEXT("ServerMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d)"), + ClientTimeStamp, *ClientAccel.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), DeltaTime, *GetMovementName(), + *GetNameSafe(GetMovementBase()), *CharacterOwner->GetBasedMovement().BoneName.ToString(), MovementBaseUtility::IsDynamicBase(GetMovementBase()) ? 1 : 0); + } + + // #TODO: Handle this better at some point? Client also denies it later on during correction (ApplyNetworkMovementMode in base movement) + // Pre handling the errors, lets avoid rolling back to/from custom movement modes, they tend to be scripted and this can screw things up + const uint8 CurrentPackedMovementMode = PackNetworkMovementMode(); + if (CurrentPackedMovementMode != MoveData.MovementMode) + { + TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None); + TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(MoveData.MovementMode, NetMovementMode, NetCustomMode, NetGroundMode); + + // Custom movement modes aren't going to be rolled back as they are client authed for our pawns + if (NetMovementMode == EMovementMode::MOVE_Custom || MovementMode == EMovementMode::MOVE_Custom) + { + if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing || CustomMovementMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing) + SetMovementMode(NetMovementMode, NetCustomMode); + } + } + + // Validate move only after old and first dual portion, after all moves are completed. + if (MoveData.NetworkMoveType == FCharacterNetworkMoveData::ENetworkMoveType::NewMove) + { + ServerMoveHandleClientErrorVR(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, ClientControlRotation.Yaw, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode); + //ServerMoveHandleClientError(ClientTimeStamp, DeltaTime, ClientAccel, MoveData.Location, MoveData.MovementBase, MoveData.MovementBaseBoneName, MoveData.MovementMode); + } +} + +/*void UVRCharacterMovementComponent::CallServerMove +( + const class FSavedMove_Character* NewCMove, + const class FSavedMove_Character* OldCMove + ) +{ + // This is technically "safe", I know for sure that I am using my own FSavedMove + // I would have like to not override any of this, but I need a lot more specific information about the pawn + // So just setting flags in the FSaved Move doesn't cut it + // I could see a problem if someone overrides this override though + const FSavedMove_VRCharacter * NewMove = (const FSavedMove_VRCharacter *)NewCMove; + const FSavedMove_VRCharacter * OldMove = (const FSavedMove_VRCharacter *)OldCMove; + + check(NewMove != nullptr); + //uint32 ClientYawPitchINT = 0; + //uint8 ClientRollBYTE = 0; + //NewMove->GetPackedAngles(ClientYawPitchINT, ClientRollBYTE); + const uint16 CapsuleYawShort = FRotator::CompressAxisToShort(NewMove->VRCapsuleRotation.Yaw); + const uint16 ClientYawShort = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Yaw); + + // Determine if we send absolute or relative location + UPrimitiveComponent* ClientMovementBase = NewMove->EndBase.Get(); + const FName ClientBaseBone = NewMove->EndBoneName; + const FVector SendLocation = MovementBaseUtility::UseRelativeLocation(ClientMovementBase) ? NewMove->SavedRelativeLocation : NewMove->SavedLocation; + + // send old move if it exists + if (OldMove) + { + //const uint16 CapsuleYawShort = FRotator::CompressAxisToShort(OldMove->VRCapsuleRotation.Yaw); + ServerMoveVROld(OldMove->TimeStamp, OldMove->Acceleration, OldMove->GetCompressedFlags(), OldMove->ConditionalValues); + } + + // Pass these in here, don't pass in to old move, it will receive the new move values in dual operations + // Will automatically not replicate them if movement base is nullptr (1 bit cost to check this) + FVRConditionalMoveRep2 NewMoveConds; + NewMoveConds.ClientMovementBase = ClientMovementBase; + NewMoveConds.ClientBaseBoneName = ClientBaseBone; + + if (CharacterOwner && (CharacterOwner->bUseControllerRotationRoll || CharacterOwner->bUseControllerRotationPitch)) + { + NewMoveConds.ClientPitch = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Pitch); + NewMoveConds.ClientRoll = FRotator::CompressAxisToByte(NewMove->SavedControlRotation.Roll); + } + + NewMoveConds.ClientYaw = FRotator::CompressAxisToShort(NewMove->SavedControlRotation.Yaw); + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + if (const FSavedMove_Character* const PendingMove = ClientData->PendingMove.Get()) + { + // This should send same as the uint16 because it uses a packedINT send by default for shorts + //uint32 OldClientYawPitchINT = 0; + //uint8 OldClientRollBYTE = 0; + //ClientData->PendingMove->GetPackedAngles(OldClientYawPitchINT, OldClientRollBYTE); + + uint32 cPitch = 0; + if (CharacterOwner && (CharacterOwner->bUseControllerRotationPitch)) + cPitch = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Pitch); + + uint32 cYaw = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Yaw); + + // Switch the order of pitch and yaw to place Yaw in smallest value, this will cut down on rep cost since normally pitch is zero'd out in VR + uint32 OldClientYawPitchINT = (cPitch << 16) | (cYaw); + + FSavedMove_VRCharacter* oldMove = (FSavedMove_VRCharacter*)ClientData->PendingMove.Get(); + const uint16 OldCapsuleYawShort = FRotator::CompressAxisToShort(oldMove->VRCapsuleRotation.Yaw); + //const uint16 OldClientYawShort = FRotator::CompressAxisToShort(ClientData->PendingMove->SavedControlRotation.Yaw); + + + // If we delayed a move without root motion, and our new move has root motion, send these through a special function, so the server knows how to process them. + if ((PendingMove->RootMotionMontage == NULL) && (NewMove->RootMotionMontage != NULL)) + { + // send two moves simultaneously + ServerMoveVRDualHybridRootMotion + ( + PendingMove->TimeStamp, + PendingMove->Acceleration, + PendingMove->GetCompressedFlags(), + OldClientYawPitchINT, + oldMove->VRCapsuleLocation, + oldMove->ConditionalValues, + oldMove->LFDiff, + OldCapsuleYawShort, + NewMove->TimeStamp, + NewMove->Acceleration, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + else // Not Hybrid root motion rpc + { + // send two moves simultaneously + if (oldMove->Acceleration.IsZero() && NewMove->Acceleration.IsZero()) + { + ServerMoveVRDualExLight + ( + PendingMove->TimeStamp, + PendingMove->GetCompressedFlags(), + OldClientYawPitchINT, + oldMove->VRCapsuleLocation, + oldMove->ConditionalValues, + oldMove->LFDiff, + OldCapsuleYawShort, + NewMove->TimeStamp, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + else + { + ServerMoveVRDual + ( + PendingMove->TimeStamp, + PendingMove->Acceleration, + PendingMove->GetCompressedFlags(), + OldClientYawPitchINT, + oldMove->VRCapsuleLocation, + oldMove->ConditionalValues, + oldMove->LFDiff, + OldCapsuleYawShort, + NewMove->TimeStamp, + NewMove->Acceleration, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + } + } + else + { + + if (NewMove->Acceleration.IsZero()) + { + ServerMoveVRExLight + ( + NewMove->TimeStamp, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + else + { + ServerMoveVR + ( + NewMove->TimeStamp, + NewMove->Acceleration, + SendLocation, + NewMove->VRCapsuleLocation, + NewMove->ConditionalValues, + NewMove->LFDiff, + CapsuleYawShort, + NewMove->GetCompressedFlags(), + NewMoveConds, + NewMove->EndPackedMovementMode + ); + } + } + + MarkForClientCameraUpdate(); +}*/ + +bool UVRCharacterMovementComponent::ShouldCheckForValidLandingSpot(float DeltaTime, const FVector& Delta, const FHitResult& Hit) const +{ + // See if we hit an edge of a surface on the lower portion of the capsule. + // In this case the normal will not equal the impact normal, and a downward sweep may find a walkable surface on top of the edge. + if (Hit.Normal.Z > KINDA_SMALL_NUMBER && !Hit.Normal.Equals(Hit.ImpactNormal)) + { + FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + if (VRRootCapsule) + PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + + if (IsWithinEdgeTolerance(PawnLocation, Hit.ImpactPoint, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius())) + { + return true; + } + } + + return false; +} + +void UVRCharacterMovementComponent::PhysWalking(float deltaTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharPhysWalking); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + if (!CharacterOwner || (!CharacterOwner->Controller && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy))) + { + Acceleration = FVector::ZeroVector; + Velocity = FVector::ZeroVector; + return; + } + + if (!UpdatedComponent->IsQueryCollisionEnabled()) + { + SetMovementMode(MOVE_Walking); + return; + } + + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN before Iteration (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + bJustTeleported = false; + bool bCheckedFall = false; + bool bTriedLedgeMove = false; + float remainingTime = deltaTime; + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + // Perform the move + while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy))) + { + Iterations++; + bJustTeleported = false; + const float timeTick = GetSimulationTimeStep(remainingTime, Iterations); + remainingTime -= timeTick; + + // Save current values + UPrimitiveComponent * const OldBase = GetMovementBase(); + const FVector PreviousBaseLocation = (OldBase != NULL) ? OldBase->GetComponentLocation() : FVector::ZeroVector; + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + + // Used for ledge check + FVector OldCapsuleLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : OldLocation; + + const FFindFloorResult OldFloor = CurrentFloor; + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + // Ensure velocity is horizontal. + MaintainHorizontalGroundVelocity(); + const FVector OldVelocity = Velocity; + Acceleration.Z = 0.f; + + // Apply acceleration + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + CalcVelocity(timeTick, GroundFriction, false, GetMaxBrakingDeceleration()); + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + } + + ApplyRootMotionToVelocity(timeTick); + ApplyVRMotionToVelocity(deltaTime);//timeTick); + + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysWalking: Velocity contains NaN after Root Motion application (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + if (IsFalling()) + { + // Root motion could have put us into Falling. + // No movement has taken place this movement tick so we pass on full time/past iteration count + StartNewPhysics(remainingTime + timeTick, Iterations - 1); + return; + } + + // Compute move parameters + const FVector MoveVelocity = Velocity; + + const FVector Delta = timeTick * MoveVelocity; + + const bool bZeroDelta = Delta.IsNearlyZero(); + FStepDownResult StepDownResult; + + if (bZeroDelta) + { + remainingTime = 0.f; + // TODO: Bugged currently + /*if (VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride) + { + + FHitResult HitRes; + FCollisionQueryParams Params("RelativeMovementSweep", false, GetOwner()); + FCollisionResponseParams ResponseParam; + + VRRootCapsule->InitSweepCollisionParams(Params, ResponseParam); + Params.bFindInitialOverlaps = true; + bool bWasBlockingHit = false; + + bWasBlockingHit = GetWorld()->SweepSingleByChannel(HitRes, VRRootCapsule->OffsetComponentToWorld.GetLocation(), VRRootCapsule->OffsetComponentToWorld.GetLocation() + VRRootCapsule->DifferenceFromLastFrame, FQuat(0.0f, 0.0f, 0.0f, 1.0f), VRRootCapsule->GetCollisionObjectType(), VRRootCapsule->GetCollisionShape(), Params, ResponseParam); + + const FVector GravDir(0.f, 0.f, -1.f); + if (CanStepUp(HitRes) || (CharacterOwner->GetMovementBase() != NULL && CharacterOwner->GetMovementBase()->GetOwner() == HitRes.GetActor())) + StepUp(GravDir,VRRootCapsule->DifferenceFromLastFrame.GetSafeNormal2D(), HitRes, &StepDownResult); + }*/ + } + else + { + // try to move forward + MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult); + + if (IsFalling()) + { + // pawn decided to jump up + const float DesiredDist = Delta.Size(); + if (DesiredDist > KINDA_SMALL_NUMBER) + { + const float ActualDist = (UpdatedComponent->GetComponentLocation() - OldLocation).Size2D(); + remainingTime += timeTick * (1.f - FMath::Min(1.f, ActualDist / DesiredDist)); + } + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + else if (IsSwimming()) //just entered water + { + RestorePreAdditiveVRMotionVelocity(); + StartSwimmingVR(OldCapsuleLocation, OldVelocity, timeTick, remainingTime, Iterations); + return; + } + } + + // Update floor. + // StepUp might have already done it for us. + if (StepDownResult.bComputedFloor) + { + CurrentFloor = StepDownResult.FloorResult; + } + else + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, bZeroDelta, NULL); + } + + // check for ledges here + const bool bCheckLedges = !CanWalkOffLedges(); + if (bCheckLedges && !CurrentFloor.IsWalkableFloor()) + { + // calculate possible alternate movement + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector NewDelta = bTriedLedgeMove ? FVector::ZeroVector : GetLedgeMove(OldCapsuleLocation, Delta, GravDir); + if (!NewDelta.IsZero()) + { + // first revert this move + RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, false); + + // avoid repeated ledge moves if the first one fails + bTriedLedgeMove = true; + + // Try new movement direction + Velocity = NewDelta / timeTick; + remainingTime += timeTick; + RestorePreAdditiveVRMotionVelocity(); + continue; + } + else + { + // see if it is OK to jump + // @todo collision : only thing that can be problem is that oldbase has world collision on + /*bool bMustJump = bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase))); + if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump)) + { + RestorePreAdditiveVRMotionVelocity(); + return; + }*/ + bCheckedFall = true; + + // revert this move + RevertMove(OldLocation, OldBase, PreviousBaseLocation, OldFloor, true); + remainingTime = 0.f; + RestorePreAdditiveVRMotionVelocity(); + break; + } + } + else + { + // Validate the floor check + if (CurrentFloor.IsWalkableFloor()) + { + if (ShouldCatchAir(OldFloor, CurrentFloor)) + { + RestorePreAdditiveVRMotionVelocity(); + HandleWalkingOffLedge(OldFloor.HitResult.ImpactNormal, OldFloor.HitResult.Normal, OldLocation, timeTick); + if (IsMovingOnGround()) + { + // If still walking, then fall. If not, assume the user set a different mode they want to keep. + StartFalling(Iterations, remainingTime, timeTick, Delta, OldLocation); + } + return; + } + + AdjustFloorHeight(); + SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + } + else if (CurrentFloor.HitResult.bStartPenetrating && remainingTime <= 0.f) + { + // The floor check failed because it started in penetration + // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor. + FHitResult Hit(CurrentFloor.HitResult); + Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST); + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit); + ResolvePenetration(RequestedAdjustment, Hit, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck = true; + } + + // check if just entered water + if (IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartSwimmingVR(OldCapsuleLocation, Velocity, timeTick, remainingTime, Iterations); + return; + } + + // See if we need to start falling. + if (!CurrentFloor.IsWalkableFloor() && !CurrentFloor.HitResult.bStartPenetrating) + { + const bool bMustJump = bJustTeleported || bZeroDelta || (OldBase == NULL || (!OldBase->IsQueryCollisionEnabled() && MovementBaseUtility::IsDynamicBase(OldBase))); + if ((bMustJump || !bCheckedFall) && CheckFall(OldFloor, CurrentFloor.HitResult, Delta, OldLocation, remainingTime, timeTick, Iterations, bMustJump)) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + bCheckedFall = true; + } + } + + + // Allow overlap events and such to change physics state and velocity + if (IsMovingOnGround()) + { + // Make velocity reflect actual move + + if (!bJustTeleported && timeTick >= MIN_TICK_TIME) + { + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + // TODO-RootMotionSource: Allow this to happen during partial override Velocity, but only set allowed axes? + Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick); + MaintainHorizontalGroundVelocity(); + } + + RestorePreAdditiveVRMotionVelocity(); + } + } + + // If we didn't move at all this iteration then abort (since future iterations will also be stuck). + if (UpdatedComponent->GetComponentLocation() == OldLocation) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime = 0.f; + break; + } + } + + if (IsMovingOnGround()) + { + MaintainHorizontalGroundVelocity(); + } +} + +void UVRCharacterMovementComponent::CapsuleTouched(UPrimitiveComponent* OverlappedComp, AActor* Other, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) +{ + if (!bEnablePhysicsInteraction) + { + return; + } + + if (OtherComp != NULL && OtherComp->IsAnySimulatingPhysics()) + { + /*const*/FVector OtherLoc = OtherComp->GetComponentLocation(); + if (UVRRootComponent * rCap = Cast<UVRRootComponent>(OtherComp)) + { + OtherLoc = rCap->OffsetComponentToWorld.GetLocation(); + } + + const FVector Loc = VRRootCapsule->OffsetComponentToWorld.GetLocation();//UpdatedComponent->GetComponentLocation(); + FVector ImpulseDir = FVector(OtherLoc.X - Loc.X, OtherLoc.Y - Loc.Y, 0.25f).GetSafeNormal(); + ImpulseDir = (ImpulseDir + Velocity.GetSafeNormal2D()) * 0.5f; + ImpulseDir.Normalize(); + + FName BoneName = NAME_None; + if (OtherBodyIndex != INDEX_NONE) + { + BoneName = ((USkinnedMeshComponent*)OtherComp)->GetBoneName(OtherBodyIndex); + } + + float TouchForceFactorModified = TouchForceFactor; + + if (bTouchForceScaledToMass) + { + FBodyInstance* BI = OtherComp->GetBodyInstance(BoneName); + TouchForceFactorModified *= BI ? BI->GetBodyMass() : 1.0f; + } + + float ImpulseStrength = FMath::Clamp(Velocity.Size2D() * TouchForceFactorModified, + MinTouchForce > 0.0f ? MinTouchForce : -FLT_MAX, + MaxTouchForce > 0.0f ? MaxTouchForce : FLT_MAX); + + FVector Impulse = ImpulseDir * ImpulseStrength; + + OtherComp->AddImpulse(Impulse, BoneName); + } +} + + +void UVRCharacterMovementComponent::ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) +{ + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementReplicateMoveToServer); + check(CharacterOwner != NULL); + + // Can only start sending moves if our controllers are synced up over the network, otherwise we flood the reliable buffer. + APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController()); + if (PC && PC->AcknowledgedPawn != CharacterOwner) + { + return; + } + + // Bail out if our character's controller doesn't have a Player. This may be the case when the local player + // has switched to another controller, such as a debug camera controller. + if (PC && PC->Player == nullptr) + { + return; + } + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + if (!ClientData) + { + return; + } + + // Update our delta time for physics simulation. + DeltaTime = ClientData->UpdateTimeStampAndDeltaTime(DeltaTime, *CharacterOwner, *this); + + // Find the oldest (unacknowledged) important move (OldMove). + // Don't include the last move because it may be combined with the next new move. + // A saved move is interesting if it differs significantly from the last acknowledged move + FSavedMovePtr OldMove = NULL; + if (ClientData->LastAckedMove.IsValid()) + { + for (int32 i = 0; i < ClientData->SavedMoves.Num() - 1; i++) + { + const FSavedMovePtr& CurrentMove = ClientData->SavedMoves[i]; + if (CurrentMove->IsImportantMove(ClientData->LastAckedMove)) + { + OldMove = CurrentMove; + break; + } + } + } + + // Get a SavedMove object to store the movement in. + FSavedMovePtr NewMovePtr = ClientData->CreateSavedMove(); + FSavedMove_Character* const NewMove = NewMovePtr.Get(); + if (NewMove == nullptr) + { + return; + } + + NewMove->SetMoveFor(CharacterOwner, DeltaTime, NewAcceleration, *ClientData); + const UWorld* MyWorld = GetWorld(); + // Causing really bad crash when using vr offset location, rather remove for now than have it merge move improperly. + + // see if the two moves could be combined + // do not combine moves which have different TimeStamps (before and after reset). + if (const FSavedMove_Character* PendingMove = ClientData->PendingMove.Get()) + { + if (bAllowMovementMerging && PendingMove->CanCombineWith(NewMovePtr, CharacterOwner, ClientData->MaxMoveDeltaTime * CharacterOwner->GetActorTimeDilation(*MyWorld))) + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_VRCharacterMovementComponent_CombineNetMove); + //SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCombineNetMove); + + // Only combine and move back to the start location if we don't move back in to a spot that would make us collide with something new. + /*const */FVector OldStartLocation = PendingMove->GetRevertedLocation(); + + // Modifying the location to account for capsule loc + FVector OverlapLocation = OldStartLocation; + if (VRRootCapsule) + OverlapLocation += VRRootCapsule->OffsetComponentToWorld.GetLocation() - VRRootCapsule->GetComponentLocation(); + + const bool bAttachedToObject = (NewMovePtr->StartAttachParent != nullptr); + if (bAttachedToObject || !OverlapTest(OverlapLocation, PendingMove->StartRotation.Quaternion(), UpdatedComponent->GetCollisionObjectType(), GetPawnCapsuleCollisionShape(SHRINK_None), CharacterOwner)) + { + // Avoid updating Mesh bones to physics during the teleport back, since PerformMovement() will update it right away anyway below. + // Note: this must be before the FScopedMovementUpdate below, since that scope is what actually moves the character and mesh. + //AVRBaseCharacter * BaseCharacter = Cast<AVRBaseCharacter>(CharacterOwner); + + FScopedMeshBoneUpdateOverrideVR ScopedNoMeshBoneUpdate(CharacterOwner->GetMesh(), EKinematicBonesUpdateToPhysics::SkipAllBones); + + // Accumulate multiple transform updates until scope ends. + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates); + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("CombineMove: add delta %f + %f and revert from %f %f to %f %f"), DeltaTime, ClientData->PendingMove->DeltaTime, UpdatedComponent->GetComponentLocation().X, UpdatedComponent->GetComponentLocation().Y, /*OldStartLocation.X*/OverlapLocation.X, /*OldStartLocation.Y*/OverlapLocation.Y); + + NewMove->CombineWith(PendingMove, CharacterOwner, PC, OldStartLocation); + + /************************/ + if (PC) + { + // We reverted position to that at the start of the pending move (above), however some code paths expect rotation to be set correctly + // before character movement occurs (via FaceRotation), so try that now. The bOrientRotationToMovement path happens later as part of PerformMovement() and PhysicsRotation(). + CharacterOwner->FaceRotation(PC->GetControlRotation(), NewMove->DeltaTime); + } + + SaveBaseLocation(); + NewMove->SetInitialPosition(CharacterOwner); + + // Remove pending move from move list. It would have to be the last move on the list. + if (ClientData->SavedMoves.Num() > 0 && ClientData->SavedMoves.Last() == ClientData->PendingMove) + { + const bool bAllowShrinking = false; + ClientData->SavedMoves.Pop(bAllowShrinking); + } + ClientData->FreeMove(ClientData->PendingMove); + ClientData->PendingMove = nullptr; + PendingMove = nullptr; // Avoid dangling reference, it's deleted above. + } + else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Not combining move [would collide at start location]")); + } + } + /*else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Not combining move [not allowed by CanCombineWith()]")); + }*/ + } + + // Acceleration should match what we send to the server, plus any other restrictions the server also enforces (see MoveAutonomous). + Acceleration = NewMove->Acceleration.GetClampedToMaxSize(GetMaxAcceleration()); + AnalogInputModifier = ComputeAnalogInputModifier(); // recompute since acceleration may have changed. + + // Perform the move locally + CharacterOwner->ClientRootMotionParams.Clear(); + CharacterOwner->SavedRootMotion.Clear(); + PerformMovement(NewMove->DeltaTime); + + NewMove->PostUpdate(CharacterOwner, FSavedMove_Character::PostUpdate_Record); + + // Add NewMove to the list + if (CharacterOwner->IsReplicatingMovement()) + { + check(NewMove == NewMovePtr.Get()); + ClientData->SavedMoves.Push(NewMovePtr); + + static const auto CVarNetEnableMoveCombining = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableMoveCombining")); + const bool bCanDelayMove = (CVarNetEnableMoveCombining->GetInt() != 0) && CanDelaySendingMove(NewMovePtr); + + if (bCanDelayMove && ClientData->PendingMove.IsValid() == false) + { + // Decide whether to hold off on move + // Decide whether to hold off on move + const float NetMoveDelta = FMath::Clamp(GetClientNetSendDeltaTime(PC, ClientData, NewMovePtr), 1.f / 120.f, 1.f / 5.f); + + if ((MyWorld->TimeSeconds - ClientData->ClientUpdateTime) * MyWorld->GetWorldSettings()->GetEffectiveTimeDilation() < NetMoveDelta) + { + // Delay sending this move. + ClientData->PendingMove = NewMovePtr; + return; + } + } + + ClientData->ClientUpdateTime = MyWorld->TimeSeconds; + + UE_CLOG(CharacterOwner&& UpdatedComponent, LogVRCharacterMovement, VeryVerbose, TEXT("ClientMove Time %f Acceleration %s Velocity %s Position %s Rotation %s DeltaTime %f Mode %s MovementBase %s.%s (Dynamic:%d) DualMove? %d"), + NewMove->TimeStamp, *NewMove->Acceleration.ToString(), *Velocity.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *UpdatedComponent->GetComponentRotation().ToCompactString(), NewMove->DeltaTime, *GetMovementName(), + *GetNameSafe(NewMove->EndBase.Get()), *NewMove->EndBoneName.ToString(), MovementBaseUtility::IsDynamicBase(NewMove->EndBase.Get()) ? 1 : 0, ClientData->PendingMove.IsValid() ? 1 : 0); + + bool bSendServerMove = true; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + + // Testing options: Simulated packet loss to server + const float TimeSinceLossStart = (MyWorld->RealTimeSeconds - ClientData->DebugForcedPacketLossTimerStart); + + static const auto CVarNetForceClientServerMoveLossDuration = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossDuration")); + static const auto CVarNetForceClientServerMoveLossPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientServerMoveLossPercent")); + if (ClientData->DebugForcedPacketLossTimerStart > 0.f && (TimeSinceLossStart < CVarNetForceClientServerMoveLossDuration->GetFloat())) + { + bSendServerMove = false; + UE_LOG(LogVRCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat() - TimeSinceLossStart); + } + else if (CVarNetForceClientServerMoveLossPercent->GetFloat() != 0.f && (RandomStream.FRand() < CVarNetForceClientServerMoveLossPercent->GetFloat())) + { + bSendServerMove = false; + ClientData->DebugForcedPacketLossTimerStart = (CVarNetForceClientServerMoveLossDuration->GetFloat() > 0) ? MyWorld->RealTimeSeconds : 0.0f; + UE_LOG(LogVRCharacterMovement, Log, TEXT("Drop ServerMove, %.2f time remains"), CVarNetForceClientServerMoveLossDuration->GetFloat()); + } + else + { + ClientData->DebugForcedPacketLossTimerStart = 0.f; + } +#endif + + // Send move to server if this character is replicating movement + if (bSendServerMove) + { + SCOPE_CYCLE_COUNTER(STAT_CharacterMovementCallServerMove); + if (ShouldUsePackedMovementRPCs()) + { + CallServerMovePacked(NewMove, ClientData->PendingMove.Get(), OldMove.Get()); + } + /*else + { + CallServerMove(NewMove, OldMove.Get()); + }*/ + } + } + + ClientData->PendingMove = NULL; +} + +/* +* +* +* END TEST AREA +* +* +*/ + +UVRCharacterMovementComponent::UVRCharacterMovementComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PostPhysicsTickFunction.bCanEverTick = true; + PostPhysicsTickFunction.bStartWithTickEnabled = false; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + VRRootCapsule = NULL; + //VRCameraCollider = NULL; + // 0.1f is low slide and still impacts surfaces well + // This variable is a bit of a hack, it reduces the movement of the pawn in the direction of relative movement + //WallRepulsionMultiplier = 0.01f; + bUseClientControlRotation = false; + bAllowMovementMerging = true; + bRequestedMoveUseAcceleration = false; +} + + +void UVRCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + if (!HasValidData()) + { + return; + } + + if (CharacterOwner && CharacterOwner->IsLocallyControlled()) + { + // Root capsule is now throwing out the difference itself, I use the difference for multiplayer sends + if (VRRootCapsule) + { + AdditionalVRInputVector = VRRootCapsule->DifferenceFromLastFrame; + } + else + { + AdditionalVRInputVector = FVector::ZeroVector; + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + +// No support for crouching code yet +bool UVRCharacterMovementComponent::CanCrouch() +{ + return false; +} + + +void UVRCharacterMovementComponent::ApplyRepulsionForce(float DeltaSeconds) +{ + if (UpdatedPrimitive && RepulsionForce > 0.0f && CharacterOwner != nullptr) + { + const TArray<FOverlapInfo>& Overlaps = UpdatedPrimitive->GetOverlapInfos(); + if (Overlaps.Num() > 0) + { + FCollisionQueryParams QueryParams; + QueryParams.bReturnFaceIndex = false; + QueryParams.bReturnPhysicalMaterial = false; + + float CapsuleRadius = 0.f; + float CapsuleHalfHeight = 0.f; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(CapsuleRadius, CapsuleHalfHeight); + const float RepulsionForceRadius = CapsuleRadius * 1.2f; + const float StopBodyDistance = 2.5f; + FVector MyLocation; + + if (VRRootCapsule) + MyLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + MyLocation = UpdatedPrimitive->GetComponentLocation(); + + for (int32 i = 0; i < Overlaps.Num(); i++) + { + const FOverlapInfo& Overlap = Overlaps[i]; + + UPrimitiveComponent* OverlapComp = Overlap.OverlapInfo.Component.Get(); + if (!OverlapComp || OverlapComp->Mobility < EComponentMobility::Movable) + { + continue; + } + + // Use the body instead of the component for cases where we have multi-body overlaps enabled + FBodyInstance* OverlapBody = nullptr; + const int32 OverlapBodyIndex = Overlap.GetBodyIndex(); + const USkeletalMeshComponent* SkelMeshForBody = (OverlapBodyIndex != INDEX_NONE) ? Cast<USkeletalMeshComponent>(OverlapComp) : nullptr; + if (SkelMeshForBody != nullptr) + { + OverlapBody = SkelMeshForBody->Bodies.IsValidIndex(OverlapBodyIndex) ? SkelMeshForBody->Bodies[OverlapBodyIndex] : nullptr; + } + else + { + OverlapBody = OverlapComp->GetBodyInstance(); + } + + if (!OverlapBody) + { + UE_LOG(LogVRCharacterMovement, Warning, TEXT("%s could not find overlap body for body index %d"), *GetName(), OverlapBodyIndex); + continue; + } + + if (!OverlapBody->IsInstanceSimulatingPhysics()) + { + continue; + } + + FTransform BodyTransform = OverlapBody->GetUnrealWorldTransform(); + + FVector BodyVelocity = OverlapBody->GetUnrealWorldVelocity(); + FVector BodyLocation = BodyTransform.GetLocation(); + + // Trace to get the hit location on the capsule + FHitResult Hit; + bool bHasHit = UpdatedPrimitive->LineTraceComponent(Hit, BodyLocation, + FVector(MyLocation.X, MyLocation.Y, BodyLocation.Z), + QueryParams); + + FVector HitLoc = Hit.ImpactPoint; + bool bIsPenetrating = Hit.bStartPenetrating || Hit.PenetrationDepth > StopBodyDistance; + + // If we didn't hit the capsule, we're inside the capsule + if (!bHasHit) + { + HitLoc = BodyLocation; + bIsPenetrating = true; + } + + const float DistanceNow = (HitLoc - BodyLocation).SizeSquared2D(); + const float DistanceLater = (HitLoc - (BodyLocation + BodyVelocity * DeltaSeconds)).SizeSquared2D(); + + if (bHasHit && DistanceNow < StopBodyDistance && !bIsPenetrating) + { + OverlapBody->SetLinearVelocity(FVector(0.0f, 0.0f, 0.0f), false); + } + else if (DistanceLater <= DistanceNow || bIsPenetrating) + { + FVector ForceCenter = MyLocation; + + if (bHasHit) + { + ForceCenter.Z = HitLoc.Z; + } + else + { + ForceCenter.Z = FMath::Clamp(BodyLocation.Z, MyLocation.Z - CapsuleHalfHeight, MyLocation.Z + CapsuleHalfHeight); + } + + OverlapBody->AddRadialForceToBody(ForceCenter, RepulsionForceRadius, RepulsionForce * Mass, ERadialImpulseFalloff::RIF_Constant); + } + } + } + } +} + + +void UVRCharacterMovementComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent) +{ + Super::SetUpdatedComponent(NewUpdatedComponent); + + if (UpdatedComponent) + { + // Fill the VRRootCapsule if we can + VRRootCapsule = Cast<UVRRootComponent>(UpdatedComponent); + + // Fill in the camera collider if we can + /*if (AVRCharacter * vrOwner = Cast<AVRCharacter>(this->GetOwner())) + { + VRCameraCollider = vrOwner->VRCameraCollider; + }*/ + + // Stop the tick forcing + UpdatedComponent->PrimaryComponentTick.RemovePrerequisite(this, PrimaryComponentTick); + + // Start forcing the root to tick before this, the actor tick will still tick after the movement component + // We want the root component to tick first because it is setting its offset location based off of tick + this->PrimaryComponentTick.AddPrerequisite(UpdatedComponent, UpdatedComponent->PrimaryComponentTick); + } +} + +FORCEINLINE_DEBUGGABLE bool UVRCharacterMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FRotator& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport) +{ + return SafeMoveUpdatedComponent(Delta, NewRotation.Quaternion(), bSweep, OutHit, Teleport); +} + +bool UVRCharacterMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport) +{ + if (UpdatedComponent == NULL) + { + OutHit.Reset(1.f); + return false; + } + + bool bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &OutHit, Teleport); + + // Handle initial penetrations + if (OutHit.bStartPenetrating && UpdatedComponent) + { + const FVector RequestedAdjustment = GetPenetrationAdjustment(OutHit); + if (ResolvePenetration(RequestedAdjustment, OutHit, NewRotation)) + { + FHitResult TempHit; + // Retry original move + bMoveResult = MoveUpdatedComponent(Delta, NewRotation, bSweep, &TempHit, Teleport); + + // Remove start penetrating if this is a clean move, otherwise use the last moves hit as the result so step up actually attempts to work. + if (TempHit.bStartPenetrating) + OutHit = TempHit; + else + OutHit.bStartPenetrating = TempHit.bStartPenetrating; + } + } + + return bMoveResult; +} + +void UVRCharacterMovementComponent::MoveAlongFloor(const FVector& InVelocity, float DeltaSeconds, FStepDownResult* OutStepDownResult) +{ + if (!CurrentFloor.IsWalkableFloor()) + { + return; + } + + // Move along the current floor + const FVector Delta = FVector(InVelocity.X, InVelocity.Y, 0.f) * DeltaSeconds; + FHitResult Hit(1.f); + FVector RampVector = ComputeGroundMovementDelta(Delta, CurrentFloor.HitResult, CurrentFloor.bLineTrace); + SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit); + float LastMoveTimeSlice = DeltaSeconds; + + if (Hit.bStartPenetrating) + { + // Allow this hit to be used as an impact we can deflect off, otherwise we do nothing the rest of the update and appear to hitch. + HandleImpact(Hit); + SlideAlongSurface(Delta, 1.f, Hit.Normal, Hit, true); + + if (Hit.bStartPenetrating) + { + OnCharacterStuckInGeometry(&Hit); + } + } + else if (Hit.IsValidBlockingHit()) + { + // We impacted something (most likely another ramp, but possibly a barrier). + float PercentTimeApplied = Hit.Time; + if ((Hit.Time > 0.f) && (Hit.Normal.Z > KINDA_SMALL_NUMBER) && IsWalkable(Hit)) + { + // Another walkable ramp. + const float InitialPercentRemaining = 1.f - PercentTimeApplied; + RampVector = ComputeGroundMovementDelta(Delta * InitialPercentRemaining, Hit, false); + LastMoveTimeSlice = InitialPercentRemaining * LastMoveTimeSlice; + SafeMoveUpdatedComponent(RampVector, UpdatedComponent->GetComponentQuat(), true, Hit); + + const float SecondHitPercent = Hit.Time * InitialPercentRemaining; + PercentTimeApplied = FMath::Clamp(PercentTimeApplied + SecondHitPercent, 0.f, 1.f); + } + + if (Hit.IsValidBlockingHit()) + { + if (CanStepUp(Hit) || (CharacterOwner->GetMovementBase() != nullptr && Hit.HitObjectHandle == CharacterOwner->GetMovementBase()->GetOwner())) + { + // hit a barrier, try to step up + const FVector PreStepUpLocation = UpdatedComponent->GetComponentLocation(); + const FVector GravDir(0.f, 0.f, -1.f); + + // I add in the HMD difference from last frame to the step up check to enforce it stepping up + if (!StepUp(GravDir, (Delta * (1.f - PercentTimeApplied)) /*+ AdditionalVRInputVector.GetSafeNormal2D()*/, Hit, OutStepDownResult)) + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("- StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString()); + HandleImpact(Hit, LastMoveTimeSlice, RampVector); + SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true); + + } + else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("+ StepUp (ImpactNormal %s, Normal %s"), *Hit.ImpactNormal.ToString(), *Hit.Normal.ToString()); + if (!bMaintainHorizontalGroundVelocity) + { + // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. Only consider horizontal movement. + bJustTeleported = true; + const float StepUpTimeSlice = (1.f - PercentTimeApplied) * DeltaSeconds; + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && StepUpTimeSlice >= KINDA_SMALL_NUMBER) + { + Velocity = (UpdatedComponent->GetComponentLocation() - PreStepUpLocation) / StepUpTimeSlice; + Velocity.Z = 0; + } + } + } + } + else if (Hit.Component.IsValid() && !Hit.Component.Get()->CanCharacterStepUp(CharacterOwner)) + { + HandleImpact(Hit, LastMoveTimeSlice, RampVector); + SlideAlongSurface(Delta, 1.f - PercentTimeApplied, Hit.Normal, Hit, true); + + } + } + } +} + +bool UVRCharacterMovementComponent::StepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult) +{ + SCOPE_CYCLE_COUNTER(STAT_CharStepUp); + + if (!CanStepUp(InHit) || MaxStepHeight <= 0.f) + { + return false; + } + + FVector OldLocation; + + if (VRRootCapsule) + OldLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + OldLocation = UpdatedComponent->GetComponentLocation(); + + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + + // Don't bother stepping up if top of capsule is hitting something. + const float InitialImpactZ = InHit.ImpactPoint.Z; + if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius)) + { + return false; + } + + if (GravDir.IsZero()) + { + return false; + } + + // Gravity should be a normalized direction + ensure(GravDir.IsNormalized()); + + float StepTravelUpHeight = MaxStepHeight; + float StepTravelDownHeight = StepTravelUpHeight; + const float StepSideZ = -1.f * FVector::DotProduct(InHit.ImpactNormal, GravDir);//const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir); + float PawnInitialFloorBaseZ = OldLocation.Z -PawnHalfHeight; + float PawnFloorPointZ = PawnInitialFloorBaseZ; + + if (IsMovingOnGround() && CurrentFloor.IsWalkableFloor()) + { + // Since we float a variable amount off the floor, we need to enforce max step height off the actual point of impact with the floor. + const float FloorDist = FMath::Max(0.f, CurrentFloor.GetDistanceToFloor()); + PawnInitialFloorBaseZ -= FloorDist; + StepTravelUpHeight = FMath::Max(StepTravelUpHeight - FloorDist, 0.f); + StepTravelDownHeight = (MaxStepHeight + MAX_FLOOR_DIST*2.f); + + const bool bHitVerticalFace = !IsWithinEdgeTolerance(InHit.Location, InHit.ImpactPoint, PawnRadius); + if (!CurrentFloor.bLineTrace && !bHitVerticalFace) + { + PawnFloorPointZ = CurrentFloor.HitResult.ImpactPoint.Z; + } + else + { + // Base floor point is the base of the capsule moved down by how far we are hovering over the surface we are hitting. + PawnFloorPointZ -= CurrentFloor.FloorDist; + } + } + + // Don't step up if the impact is below us, accounting for distance from floor. + if (InitialImpactZ <= PawnInitialFloorBaseZ) + { + return false; + } + + // Scope our movement updates, and do not apply them until all intermediate moves are completed. + /*FScopedMovementUpdate*/ FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates); + + // step up - treat as vertical wall + FHitResult SweepUpHit(1.f); + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit); + + if (SweepUpHit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + + // step fwd + FHitResult Hit(1.f); + MoveUpdatedComponent(Delta, PawnRotation, true, &Hit); + + // Check result of forward movement + if (Hit.bBlockingHit) + { + if (Hit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If we hit something above us and also something ahead of us, we should notify about the upward hit as well. + // The forward hit will be handled later (in the bSteppedOver case below). + // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification. + if (SweepUpHit.bBlockingHit && Hit.bBlockingHit) + { + HandleImpact(SweepUpHit); + } + + // pawn ran into a wall + HandleImpact(Hit); + if (IsFalling()) + { + return true; + } + + // adjust and try again + const float ForwardHitTime = Hit.Time; + const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true); + + if (IsFalling()) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If both the forward hit and the deflection got us nowhere, there is no point in this step up. + if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + // Step down + MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit); + + // If step down was initially penetrating abort the step up + if (Hit.bStartPenetrating) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + FStepDownResult StepDownResult; + if (Hit.IsValidBlockingHit()) + { + // See if this step sequence would have allowed us to travel higher than our max step height allows. + const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ; + if (DeltaZ > MaxStepHeight) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f"), DeltaZ, PawnInitialFloorBaseZ); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Reject unwalkable surface normals here. + if (!IsWalkable(Hit)) + { + // Reject if normal opposes movement direction + const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f; + if (bNormalTowardsMe) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Also reject if we would end up being higher than our starting location by stepping down. + // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge. + if (Hit.Location.Z > OldLocation.Z) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well. + if (!IsWithinEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius)) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Don't step up onto invalid surfaces if traveling higher. + if (DeltaZ > 0.f && !CanStepUp(Hit)) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method. + if (OutStepDownResult != NULL) + { + FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit); + + // Reject unwalkable normals if we end up higher than our initial height. + // It's fine to walk down onto an unwalkable surface, don't reject those moves. + if (Hit.Location.Z > OldLocation.Z) + { + // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare). + // In those cases we should instead abort the step up and try to slide along the stair. + if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < CharacterMovementConstants::MAX_STEP_SIDE_ZVR) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + StepDownResult.bComputedFloor = true; + } + } + + // Copy step down result. + if (OutStepDownResult != NULL) + { + *OutStepDownResult = StepDownResult; + } + + // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. + bJustTeleported |= !bMaintainHorizontalGroundVelocity; + + return true; +} + +bool UVRCharacterMovementComponent::IsWithinEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const +{ + const float DistFromCenterSq = (TestImpactPoint - CapsuleLocation).SizeSquared2D(); + const float ReducedRadiusSq = FMath::Square(FMath::Max(VREdgeRejectDistance + KINDA_SMALL_NUMBER, CapsuleRadius - VREdgeRejectDistance)); + return DistFromCenterSq < ReducedRadiusSq; +} + +bool UVRCharacterMovementComponent::IsWithinClimbingEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const +{ + const float DistFromCenterSq = (TestImpactPoint - CapsuleLocation).SizeSquared2D(); + const float ReducedRadiusSq = FMath::Square(FMath::Max(VRClimbingEdgeRejectDistance + KINDA_SMALL_NUMBER, CapsuleRadius - VRClimbingEdgeRejectDistance)); + return DistFromCenterSq < ReducedRadiusSq; +} + +bool UVRCharacterMovementComponent::VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult) +{ + SCOPE_CYCLE_COUNTER(STAT_CharStepUp); + + if (!CanStepUp(InHit) || MaxStepHeight <= 0.f) + { + return false; + } + + FVector OldLocation; + + if (VRRootCapsule) + OldLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + OldLocation = UpdatedComponent->GetComponentLocation(); + + float PawnRadius, PawnHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnRadius, PawnHalfHeight); + + // Don't bother stepping up if top of capsule is hitting something. + const float InitialImpactZ = InHit.ImpactPoint.Z; + if (InitialImpactZ > OldLocation.Z + (PawnHalfHeight - PawnRadius)) + { + return false; + } + + // Don't step up if the impact is below us + if (InitialImpactZ <= OldLocation.Z - PawnHalfHeight) + { + return false; + } + + if (GravDir.IsZero()) + { + return false; + } + + // Gravity should be a normalized direction + ensure(GravDir.IsNormalized()); + + float StepTravelUpHeight = MaxStepHeight; + float StepTravelDownHeight = StepTravelUpHeight; + const float StepSideZ = -1.f * (InHit.ImpactNormal | GravDir); + float PawnInitialFloorBaseZ = OldLocation.Z - PawnHalfHeight; + float PawnFloorPointZ = PawnInitialFloorBaseZ; + + // Scope our movement updates, and do not apply them until all intermediate moves are completed. + FVRCharacterScopedMovementUpdate ScopedStepUpMovement(UpdatedComponent, EScopedUpdate::DeferredUpdates); + + // step up - treat as vertical wall + FHitResult SweepUpHit(1.f); + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit); + + if (SweepUpHit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // step fwd + FHitResult Hit(1.f); + + + // Adding in the directional difference of the last HMD movement to promote stepping up + // Probably entirely wrong as Delta is divided by movement ticks but I want the effect to be stronger anyway + // This won't effect control based movement unless stepping forward at the same time, but gives RW movement + // the extra boost to get up over a lip + // #TODO test this more, currently appears to be needed for walking, but is harmful for other modes +// if (VRRootCapsule) +// MoveUpdatedComponent(Delta + VRRootCapsule->DifferenceFromLastFrame.GetSafeNormal2D(), PawnRotation, true, &Hit); +// else + MoveUpdatedComponent(Delta, PawnRotation, true, &Hit); + + //MoveUpdatedComponent(Delta, PawnRotation, true, &Hit); + + // Check result of forward movement + if (Hit.bBlockingHit) + { + if (Hit.bStartPenetrating) + { + // Undo movement + ScopedStepUpMovement.RevertMove(); + return false; + } + + // If we hit something above us and also something ahead of us, we should notify about the upward hit as well. + // The forward hit will be handled later (in the bSteppedOver case below). + // In the case of hitting something above but not forward, we are not blocked from moving so we don't need the notification. + if (SweepUpHit.bBlockingHit && Hit.bBlockingHit) + { + + HandleImpact(SweepUpHit); + } + + // pawn ran into a wall + HandleImpact(Hit); + if (IsFalling()) + { + return true; + } + + //Don't adjust for VR, it doesn't work correctly + ScopedStepUpMovement.RevertMove(); + return false; + + // adjust and try again + //const float ForwardHitTime = Hit.Time; + //const float ForwardSlideAmount = SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true); + + //if (IsFalling()) + //{ + // ScopedStepUpMovement.RevertMove(); + // return false; + //} + + // If both the forward hit and the deflection got us nowhere, there is no point in this step up. + //if (ForwardHitTime == 0.f && ForwardSlideAmount == 0.f) + //{ + // ScopedStepUpMovement.RevertMove(); + // return false; + //} + } + + // Step down + MoveUpdatedComponent(GravDir * StepTravelDownHeight, UpdatedComponent->GetComponentQuat(), true, &Hit); + + // If step down was initially penetrating abort the step up + if (Hit.bStartPenetrating) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + + FStepDownResult StepDownResult; + if (Hit.IsValidBlockingHit()) + { + // See if this step sequence would have allowed us to travel higher than our max step height allows. + const float DeltaZ = Hit.ImpactPoint.Z - PawnFloorPointZ; + if (DeltaZ > MaxStepHeight) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (too high Height %.3f) up from floor base %f"), DeltaZ, PawnInitialFloorBaseZ); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Reject unwalkable surface normals here. + if (!IsWalkable(Hit)) + { + // Reject if normal opposes movement direction + const bool bNormalTowardsMe = (Delta | Hit.ImpactNormal) < 0.f; + if (bNormalTowardsMe) + { + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s opposed to movement)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Also reject if we would end up being higher than our starting location by stepping down. + // It's fine to step down onto an unwalkable normal below us, we will just slide off. Rejecting those moves would prevent us from being able to walk off the edge. + if (Hit.Location.Z > OldLocation.Z) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (unwalkable normal %s above old position)"), *Hit.ImpactNormal.ToString()); + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + // Reject moves where the downward sweep hit something very close to the edge of the capsule. This maintains consistency with FindFloor as well. + if (!IsWithinClimbingEdgeTolerance(Hit.Location, Hit.ImpactPoint, PawnRadius)) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (outside edge tolerance)")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // Don't step up onto invalid surfaces if traveling higher. + if (DeltaZ > 0.f && !CanStepUp(Hit)) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("- Reject StepUp (up onto surface with !CanStepUp())")); + ScopedStepUpMovement.RevertMove(); + return false; + } + + // See if we can validate the floor as a result of this step down. In almost all cases this should succeed, and we can avoid computing the floor outside this method. + if (OutStepDownResult != NULL) + { + FindFloor(UpdatedComponent->GetComponentLocation(), StepDownResult.FloorResult, false, &Hit); + + // Reject unwalkable normals if we end up higher than our initial height. + // It's fine to walk down onto an unwalkable surface, don't reject those moves. + if (Hit.Location.Z > OldLocation.Z) + { + // We should reject the floor result if we are trying to step up an actual step where we are not able to perch (this is rare). + // In those cases we should instead abort the step up and try to slide along the stair. + if (!StepDownResult.FloorResult.bBlockingHit && StepSideZ < CharacterMovementConstants::MAX_STEP_SIDE_ZVR) + { + ScopedStepUpMovement.RevertMove(); + return false; + } + } + + StepDownResult.bComputedFloor = true; + } + } + + // Copy step down result. + if (OutStepDownResult != NULL) + { + *OutStepDownResult = StepDownResult; + } + + // Don't recalculate velocity based on this height adjustment, if considering vertical adjustments. + bJustTeleported |= !bMaintainHorizontalGroundVelocity; + return true; +} + + +void UVRCharacterMovementComponent::UpdateBasedMovement(float DeltaSeconds) +{ + if (!HasValidData()) + { + return; + } + + const UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + if (!MovementBaseUtility::UseRelativeLocation(MovementBase)) + { + return; + } + + if (!IsValid(MovementBase) || !IsValid(MovementBase->GetOwner())) + { + SetBase(NULL); + return; + } + + // Ignore collision with bases during these movements. + TGuardValue<EMoveComponentFlags> ScopedFlagRestore(MoveComponentFlags, MoveComponentFlags | MOVECOMP_IgnoreBases); + + FQuat DeltaQuat = FQuat::Identity; + FVector DeltaPosition = FVector::ZeroVector; + + FQuat NewBaseQuat; + FVector NewBaseLocation; + if (!MovementBaseUtility::GetMovementBaseTransform(MovementBase, CharacterOwner->GetBasedMovement().BoneName, NewBaseLocation, NewBaseQuat)) + { + return; + } + + // Find change in rotation + const bool bRotationChanged = !OldBaseQuat.Equals(NewBaseQuat, 1e-8f); + if (bRotationChanged) + { + DeltaQuat = NewBaseQuat * OldBaseQuat.Inverse(); + } + + // only if base moved + if (bRotationChanged || (OldBaseLocation != NewBaseLocation)) + { + // Calculate new transform matrix of base actor (ignoring scale). + const FQuatRotationTranslationMatrix OldLocalToWorld(OldBaseQuat, OldBaseLocation); + const FQuatRotationTranslationMatrix NewLocalToWorld(NewBaseQuat, NewBaseLocation); + + FQuat FinalQuat = UpdatedComponent->GetComponentQuat(); + + if (bRotationChanged && !bIgnoreBaseRotation) + { + // Apply change in rotation and pipe through FaceRotation to maintain axis restrictions + const FQuat PawnOldQuat = UpdatedComponent->GetComponentQuat(); + const FQuat TargetQuat = DeltaQuat * FinalQuat; + FRotator TargetRotator(TargetQuat); + CharacterOwner->FaceRotation(TargetRotator, 0.f); + FinalQuat = UpdatedComponent->GetComponentQuat(); + + if (PawnOldQuat.Equals(FinalQuat, 1e-6f)) + { + // Nothing changed. This means we probably are using another rotation mechanism (bOrientToMovement etc). We should still follow the base object. + // @todo: This assumes only Yaw is used, currently a valid assumption. This is the only reason FaceRotation() is used above really, aside from being a virtual hook. + if (bOrientRotationToMovement || (bUseControllerDesiredRotation && CharacterOwner->Controller)) + { + TargetRotator.Pitch = 0.f; + TargetRotator.Roll = 0.f; + MoveUpdatedComponent(FVector::ZeroVector, TargetRotator, false); + FinalQuat = UpdatedComponent->GetComponentQuat(); + } + } + + // Pipe through ControlRotation, to affect camera. + if (CharacterOwner->Controller) + { + const FQuat PawnDeltaRotation = FinalQuat * PawnOldQuat.Inverse(); + FRotator FinalRotation = FinalQuat.Rotator(); + UpdateBasedRotation(FinalRotation, PawnDeltaRotation.Rotator()); + FinalQuat = UpdatedComponent->GetComponentQuat(); + } + } + + // We need to offset the base of the character here, not its origin, so offset by half height + float HalfHeight, Radius; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(Radius, HalfHeight); + + FVector const BaseOffset(0.0f, 0.0f, 0.0f);//(0.0f, 0.0f, HalfHeight); + FVector const LocalBasePos = OldLocalToWorld.InverseTransformPosition(UpdatedComponent->GetComponentLocation() - BaseOffset); + FVector const NewWorldPos = ConstrainLocationToPlane(NewLocalToWorld.TransformPosition(LocalBasePos) + BaseOffset); + DeltaPosition = ConstrainDirectionToPlane(NewWorldPos - UpdatedComponent->GetComponentLocation()); + + // move attached actor + if (bFastAttachedMove) + { + // we're trusting no other obstacle can prevent the move here + UpdatedComponent->SetWorldLocationAndRotation(NewWorldPos, FinalQuat, false); + } + else + { + // hack - transforms between local and world space introducing slight error FIXMESTEVE - discuss with engine team: just skip the transforms if no rotation? + FVector BaseMoveDelta = NewBaseLocation - OldBaseLocation; + if (!bRotationChanged && (BaseMoveDelta.X == 0.f) && (BaseMoveDelta.Y == 0.f)) + { + DeltaPosition.X = 0.f; + DeltaPosition.Y = 0.f; + } + + FHitResult MoveOnBaseHit(1.f); + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + MoveUpdatedComponent(DeltaPosition, FinalQuat, true, &MoveOnBaseHit); + if ((UpdatedComponent->GetComponentLocation() - (OldLocation + DeltaPosition)).IsNearlyZero() == false) + { + OnUnableToFollowBaseMove(DeltaPosition, OldLocation, MoveOnBaseHit); + } + } + + if (MovementBase->IsSimulatingPhysics() && CharacterOwner->GetMesh()) + { + CharacterOwner->GetMesh()->ApplyDeltaToAllPhysicsTransforms(DeltaPosition, DeltaQuat); + } + } +} + +FVector UVRCharacterMovementComponent::GetImpartedMovementBaseVelocity() const +{ + FVector Result = FVector::ZeroVector; + + if (CharacterOwner) + { + UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + if (MovementBaseUtility::IsDynamicBase(MovementBase)) + { + FVector BaseVelocity = MovementBaseUtility::GetMovementBaseVelocity(MovementBase, CharacterOwner->GetBasedMovement().BoneName); + + if (bImpartBaseAngularVelocity) + { + // Base position should be the bottom of the actor since I offset the capsule now + const FVector CharacterBasePosition = (UpdatedComponent->GetComponentLocation()/* - FVector(0.f, 0.f, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight())*/); + const FVector BaseTangentialVel = MovementBaseUtility::GetMovementBaseTangentialVelocity(MovementBase, CharacterOwner->GetBasedMovement().BoneName, CharacterBasePosition); + BaseVelocity += BaseTangentialVel; + } + + if (bImpartBaseVelocityX) + { + Result.X = BaseVelocity.X; + } + if (bImpartBaseVelocityY) + { + Result.Y = BaseVelocity.Y; + } + if (bImpartBaseVelocityZ) + { + Result.Z = BaseVelocity.Z; + } + } + } + + return Result; +} + + + +void UVRCharacterMovementComponent::FindFloor(const FVector& CapsuleLocation, FFindFloorResult& OutFloorResult, bool bCanUseCachedLocation, const FHitResult* DownwardSweepResult) const +{ + SCOPE_CYCLE_COUNTER(STAT_CharFindFloor); + //UE_LOG(LogVRCharacterMovement, Warning, TEXT("Find Floor")); + // No collision, no floor... + if (!HasValidData() || !UpdatedComponent->IsQueryCollisionEnabled()) + { + OutFloorResult.Clear(); + return; + } + + //UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("[Role:%d] FindFloor: %s at location %s"), (int32)CharacterOwner->Role, *GetNameSafe(CharacterOwner), *CapsuleLocation.ToString()); + check(CharacterOwner->GetCapsuleComponent()); + + FVector UseCapsuleLocation = CapsuleLocation; + if (VRRootCapsule) + UseCapsuleLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + + // Increase height check slightly if walking, to prevent floor height adjustment from later invalidating the floor result. + const float HeightCheckAdjust = ((IsMovingOnGround() || IsClimbing()) ? MAX_FLOOR_DIST + KINDA_SMALL_NUMBER : -MAX_FLOOR_DIST); + + float FloorSweepTraceDist = FMath::Max(MAX_FLOOR_DIST, MaxStepHeight + HeightCheckAdjust); + float FloorLineTraceDist = FloorSweepTraceDist; + bool bNeedToValidateFloor = true; + + // For reverting + FFindFloorResult LastFloor = CurrentFloor; + + // Sweep floor + if (FloorLineTraceDist > 0.f || FloorSweepTraceDist > 0.f) + { + UCharacterMovementComponent* MutableThis = const_cast<UCharacterMovementComponent*>((UCharacterMovementComponent*)this); + + if (bAlwaysCheckFloor || !bCanUseCachedLocation || bForceNextFloorCheck || bJustTeleported) + { + MutableThis->bForceNextFloorCheck = false; + ComputeFloorDist(UseCapsuleLocation, FloorLineTraceDist, FloorSweepTraceDist, OutFloorResult, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius(), DownwardSweepResult); + } + else + { + // Force floor check if base has collision disabled or if it does not block us. + UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + const AActor* BaseActor = MovementBase ? MovementBase->GetOwner() : NULL; + + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + + if (MovementBase != NULL) + { + MutableThis->bForceNextFloorCheck = !MovementBase->IsQueryCollisionEnabled() + || MovementBase->GetCollisionResponseToChannel(CollisionChannel) != ECR_Block + || MovementBaseUtility::IsDynamicBase(MovementBase); + } + + const bool IsActorBasePendingKill = !IsValid(BaseActor); + + if (!bForceNextFloorCheck && !IsActorBasePendingKill && MovementBase) + { + //UE_LOG(LogVRCharacterMovement, Log, TEXT("%s SKIP check for floor"), *CharacterOwner->GetName()); + OutFloorResult = CurrentFloor; + bNeedToValidateFloor = false; + } + else + { + MutableThis->bForceNextFloorCheck = false; + ComputeFloorDist(UseCapsuleLocation, FloorLineTraceDist, FloorSweepTraceDist, OutFloorResult, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius(), DownwardSweepResult); + } + } + } + + // #TODO: Modify the floor compute floor distance instead? Or just run movement logic differently for the bWalkingCollisionOverride setup. + // #VR Specific - ignore floor traces that are negative, this can be caused by a failed floor check while starting in penetration + if (VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride && OutFloorResult.bBlockingHit && OutFloorResult.FloorDist <= 0.0f) + { + + if (OutFloorResult.FloorDist <= -FMath::Max(MAX_FLOOR_DIST, CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleRadius())) + { + // This was a negative trace result, the game wants us to pull out of penetration + // But with walking collision override we don't want to do that, so check for the correct channel and throw away + // the new floor if it matches + ECollisionResponse FloorResponse; + if (OutFloorResult.HitResult.Component.IsValid()) + { + FloorResponse = OutFloorResult.HitResult.Component->GetCollisionResponseToChannel(VRRootCapsule->WalkingCollisionOverride); + if (FloorResponse == ECR_Ignore || FloorResponse == ECR_Overlap) + OutFloorResult = LastFloor; // Move back to the last floor value, we are in penetration with a walking override + } + } + } + + // OutFloorResult.HitResult is now the result of the vertical floor check. + // See if we should try to "perch" at this location. + if (bNeedToValidateFloor && OutFloorResult.bBlockingHit && !OutFloorResult.bLineTrace) + { + const bool bCheckRadius = true; + if (ShouldComputePerchResult(OutFloorResult.HitResult, bCheckRadius)) + { + float MaxPerchFloorDist = FMath::Max(MAX_FLOOR_DIST, MaxStepHeight + HeightCheckAdjust); + if (IsMovingOnGround() || IsClimbing()) + { + MaxPerchFloorDist += FMath::Max(0.f, PerchAdditionalHeight); + } + + FFindFloorResult PerchFloorResult; + if (ComputePerchResult(GetValidPerchRadius(), OutFloorResult.HitResult, MaxPerchFloorDist, PerchFloorResult)) + { + // Don't allow the floor distance adjustment to push us up too high, or we will move beyond the perch distance and fall next time. + const float AvgFloorDist = (MIN_FLOOR_DIST + MAX_FLOOR_DIST) * 0.5f; + const float MoveUpDist = (AvgFloorDist - OutFloorResult.FloorDist); + if (MoveUpDist + PerchFloorResult.FloorDist >= MaxPerchFloorDist) + { + OutFloorResult.FloorDist = AvgFloorDist; + } + + // If the regular capsule is on an unwalkable surface but the perched one would allow us to stand, override the normal to be one that is walkable. + if (!OutFloorResult.bWalkableFloor) + { + OutFloorResult.SetFromLineTrace(PerchFloorResult.HitResult, OutFloorResult.FloorDist, FMath::Max(OutFloorResult.FloorDist, MIN_FLOOR_DIST), true); + } + } + else + { + // We had no floor (or an invalid one because it was unwalkable), and couldn't perch here, so invalidate floor (which will cause us to start falling). + OutFloorResult.bWalkableFloor = false; + } + } + } +} + +float UVRCharacterMovementComponent::ImmersionDepth() const +{ + float depth = 0.f; + + if (CharacterOwner && GetPhysicsVolume()->bWaterVolume) + { + const float CollisionHalfHeight = CharacterOwner->GetSimpleCollisionHalfHeight(); + + if ((CollisionHalfHeight == 0.f) || (Buoyancy == 0.f)) + { + depth = 1.f; + } + else + { + UBrushComponent* VolumeBrushComp = GetPhysicsVolume()->GetBrushComponent(); + FHitResult Hit(1.f); + if (VolumeBrushComp) + { + FVector TraceStart; + FVector TraceEnd; + + if (VRRootCapsule) + { + TraceStart = VRRootCapsule->OffsetComponentToWorld.GetLocation() + FVector(0.f, 0.f, CollisionHalfHeight); + TraceEnd = VRRootCapsule->OffsetComponentToWorld.GetLocation() - FVector(0.f, 0.f, CollisionHalfHeight); + } + else + { + TraceStart = UpdatedComponent->GetComponentLocation() + FVector(0.f, 0.f, CollisionHalfHeight); + TraceEnd = UpdatedComponent->GetComponentLocation() -FVector(0.f, 0.f, CollisionHalfHeight); + } + + FCollisionQueryParams NewTraceParams(CharacterMovementComponentStatics::ImmersionDepthName, true); + VolumeBrushComp->LineTraceComponent(Hit, TraceStart, TraceEnd, NewTraceParams); + } + + depth = (Hit.Time == 1.f) ? 1.f : (1.f - Hit.Time); + } + } + return depth; +} + +/////////////////////////// +// Navigation Functions +/////////////////////////// + +bool UVRCharacterMovementComponent::TryToLeaveNavWalking() +{ + SetNavWalkingPhysics(false); + + bool bCanTeleport = true; + if (CharacterOwner) + { + FVector CollisionFreeLocation; + if (VRRootCapsule) + CollisionFreeLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + else + CollisionFreeLocation = UpdatedComponent->GetComponentLocation(); + + // Think I need to create a custom "FindTeleportSpot" function, it is using ComponentToWorld location + bCanTeleport = GetWorld()->FindTeleportSpot(CharacterOwner, CollisionFreeLocation, UpdatedComponent->GetComponentRotation()); + if (bCanTeleport) + { + + if (VRRootCapsule) + { + // Technically the same actor but i am keepign the usage convention for clarity. + // Subtracting actor location from capsule to get difference in worldspace, then removing from collision free location + // So that it uses the correct location. + CharacterOwner->SetActorLocation(CollisionFreeLocation - (VRRootCapsule->OffsetComponentToWorld.GetLocation() - UpdatedComponent->GetComponentLocation())); + } + else + CharacterOwner->SetActorLocation(CollisionFreeLocation); + } + else + { + SetNavWalkingPhysics(true); + } + } + + bWantsToLeaveNavWalking = !bCanTeleport; + return bCanTeleport; +} + +void UVRCharacterMovementComponent::PhysFlying(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + if (bCheatFlying && Acceleration.IsZero()) + { + Velocity = FVector::ZeroVector; + } + const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction; + CalcVelocity(deltaTime, Friction, true, GetMaxBrakingDeceleration()); + } + + ApplyRootMotionToVelocity(deltaTime); + //ApplyVRMotionToVelocity(deltaTime); + + // Manually handle the velocity setup + LastPreAdditiveVRVelocity = (AdditionalVRInputVector) / deltaTime; + bool bExtremeInput = false; + if (LastPreAdditiveVRVelocity.SizeSquared() > FMath::Square(TrackingLossThreshold)) + { + // Default to always holding position during flight to avoid too much velocity injection + AdditionalVRInputVector = FVector::ZeroVector; + LastPreAdditiveVRVelocity = FVector::ZeroVector; + } + + Iterations++; + bJustTeleported = false; + + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector Adjusted = Velocity * deltaTime; + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted + AdditionalVRInputVector, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.Time < 1.f) + { + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + float stepZ = UpdatedComponent->GetComponentLocation().Z; + bSteppedUp = StepUp(GravDir, (Adjusted + AdditionalVRInputVector) * (1.f - Hit.Time) /*+ AdditionalVRInputVector.GetSafeNormal2D()*/, Hit, nullptr); + if (bSteppedUp) + { + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + } + + if (!bJustTeleported) + { + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + //Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation) - AdditionalVRInputVector) / deltaTime; + Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation)) / deltaTime; + } + + RestorePreAdditiveVRMotionVelocity(); + } +} + +void UVRCharacterMovementComponent::PhysFalling(float deltaTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharPhysFalling); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + FVector FallAcceleration = GetFallingLateralAcceleration(deltaTime); + FallAcceleration.Z = 0.f; + const bool bHasLimitedAirControl = ShouldLimitAirControl(deltaTime, FallAcceleration); + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + float remainingTime = deltaTime; + while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations)) + { + Iterations++; + float timeTick = GetSimulationTimeStep(remainingTime, Iterations); + remainingTime -= timeTick; + + const FVector OldLocation = UpdatedComponent->GetComponentLocation(); + const FVector OldCapsuleLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : OldLocation; + + const FQuat PawnRotation = UpdatedComponent->GetComponentQuat(); + bJustTeleported = false; + + const FVector OldVelocityWithRootMotion = Velocity; + + RestorePreAdditiveRootMotionVelocity(); + // RestorePreAdditiveVRMotionVelocity(); + + const FVector OldVelocity = Velocity; + + // Apply input + const float MaxDecel = GetMaxBrakingDeceleration(); + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + // Compute Velocity + { + // Acceleration = FallAcceleration for CalcVelocity(), but we restore it after using it. + TGuardValue<FVector> RestoreAcceleration(Acceleration, FallAcceleration); + Velocity.Z = 0.f; + CalcVelocity(timeTick, FallingLateralFriction, false, BrakingDecelerationFalling); + Velocity.Z = OldVelocity.Z; + } + } + + //Velocity += CustomVRInputVector / deltaTime; + + // Compute current gravity + const FVector Gravity(0.f, 0.f, GetGravityZ()); + + float GravityTime = timeTick; + + // If jump is providing force, gravity may be affected. + bool bEndingJumpForce = false; + if (CharacterOwner->JumpForceTimeRemaining > 0.0f) + { + // Consume some of the force time. Only the remaining time (if any) is affected by gravity when bApplyGravityWhileJumping=false. + const float JumpForceTime = FMath::Min(CharacterOwner->JumpForceTimeRemaining, timeTick); + GravityTime = bApplyGravityWhileJumping ? timeTick : FMath::Max(0.0f, timeTick - JumpForceTime); + + // Update Character state + CharacterOwner->JumpForceTimeRemaining -= JumpForceTime; + if (CharacterOwner->JumpForceTimeRemaining <= 0.0f) + { + CharacterOwner->ResetJumpState(); + bEndingJumpForce = true; + } + } + + // Apply gravity + Velocity = NewFallVelocity(Velocity, Gravity, GravityTime); + + //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) OldVelocityWithRootMotion=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *OldVelocityWithRootMotion.ToString(), *Velocity.ToString()); + ApplyRootMotionToVelocity(timeTick); + DecayFormerBaseVelocity(timeTick); + + // See if we need to sub-step to exactly reach the apex. This is important for avoiding "cutting off the top" of the trajectory as framerate varies. + static const auto CVarForceJumpPeakSubstep = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ForceJumpPeakSubstep")); + if (CVarForceJumpPeakSubstep->GetInt() != 0 && OldVelocityWithRootMotion.Z > 0.f && Velocity.Z <= 0.f && NumJumpApexAttempts < MaxJumpApexAttemptsPerSimulation) + { + const FVector DerivedAccel = (Velocity - OldVelocityWithRootMotion) / timeTick; + if (!FMath::IsNearlyZero(DerivedAccel.Z)) + { + const float TimeToApex = -OldVelocityWithRootMotion.Z / DerivedAccel.Z; + + // The time-to-apex calculation should be precise, and we want to avoid adding a substep when we are basically already at the apex from the previous iteration's work. + const float ApexTimeMinimum = 0.0001f; + if (TimeToApex >= ApexTimeMinimum && TimeToApex < timeTick) + { + const FVector ApexVelocity = OldVelocityWithRootMotion + (DerivedAccel * TimeToApex); + Velocity = ApexVelocity; + Velocity.Z = 0.f; // Should be nearly zero anyway, but this makes apex notifications consistent. + + // We only want to move the amount of time it takes to reach the apex, and refund the unused time for next iteration. + const float TimeToRefund = (timeTick - TimeToApex); + + remainingTime += TimeToRefund; + timeTick = TimeToApex; + Iterations--; + NumJumpApexAttempts++; + + // Refund time to any active Root Motion Sources as well + for (TSharedPtr<FRootMotionSource> RootMotionSource : CurrentRootMotion.RootMotionSources) + { + const float RewoundRMSTime = FMath::Max(0.0f, RootMotionSource->GetTime() - TimeToRefund); + RootMotionSource->SetTime(RewoundRMSTime); + } + } + } + } + + //UE_LOG(LogCharacterMovement, Log, TEXT("dt=(%.6f) OldLocation=(%s) OldVelocity=(%s) NewVelocity=(%s)"), timeTick, *(UpdatedComponent->GetComponentLocation()).ToString(), *OldVelocity.ToString(), *Velocity.ToString()); + + ApplyRootMotionToVelocity(timeTick); + //ApplyVRMotionToVelocity(deltaTime); + + if (bNotifyApex && (Velocity.Z < 0.f)) + { + // Just passed jump apex since now going down + bNotifyApex = false; + NotifyJumpApex(); + } + + // Compute change in position (using midpoint integration method). + FVector Adjusted = (0.5f * (OldVelocityWithRootMotion + Velocity) * timeTick) + ((AdditionalVRInputVector / deltaTime) * timeTick); + + ApplyVRMotionToVelocity(deltaTime); + + // Special handling if ending the jump force where we didn't apply gravity during the jump. + if (bEndingJumpForce && !bApplyGravityWhileJumping) + { + // We had a portion of the time at constant speed then a portion with acceleration due to gravity. + // Account for that here with a more correct change in position. + const float NonGravityTime = FMath::Max(0.f, timeTick - GravityTime); + Adjusted = ((OldVelocityWithRootMotion * NonGravityTime) + (0.5f * (OldVelocityWithRootMotion + Velocity) * GravityTime)) /*+ ((AdditionalVRInputVector / deltaTime) * timeTick)*/; + } + + // Move + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Adjusted, PawnRotation, true, Hit); + + if (!HasValidData()) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + + float LastMoveTimeSlice = timeTick; + float subTimeTickRemaining = timeTick * (1.f - Hit.Time); + + if (IsSwimming()) //just entered water + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + StartSwimmingVR(OldCapsuleLocation, OldVelocity, timeTick, remainingTime, Iterations); + return; + } + else if (Hit.bBlockingHit) + { + if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit)) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + else + { + // Compute impact deflection based on final velocity, not integration step. + // This allows us to compute a new velocity from the deflected vector, and ensures the full gravity effect is included in the slide result. + Adjusted = Velocity * timeTick; + + // See if we can convert a normally invalid landing spot (based on the hit result) to a usable one. + if (!Hit.bStartPenetrating && ShouldCheckForValidLandingSpot(timeTick, Adjusted, Hit)) + { + /*const */FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + if (VRRootCapsule) + PawnLocation = VRRootCapsule->OffsetComponentToWorld.GetLocation(); + + FFindFloorResult FloorResult; + FindFloor(PawnLocation, FloorResult, false, NULL); + if (FloorResult.IsWalkableFloor() && IsValidLandingSpot(PawnLocation, FloorResult.HitResult)) + { + //RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + ProcessLanded(FloorResult.HitResult, remainingTime, Iterations); + return; + } + } + + HandleImpact(Hit, LastMoveTimeSlice, Adjusted); + + // If we've changed physics mode, abort. + if (!HasValidData() || !IsFalling()) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + + // Limit air control based on what we hit. + // We moved to the impact point using air control, but may want to deflect from there based on a limited air control acceleration. + FVector VelocityNoAirControl = OldVelocity; + FVector AirControlAccel = Acceleration; + if (bHasLimitedAirControl) + { + // Compute VelocityNoAirControl + { + // Find velocity *without* acceleration. + TGuardValue<FVector> RestoreAcceleration(Acceleration, FVector::ZeroVector); + TGuardValue<FVector> RestoreVelocity(Velocity, OldVelocity); + Velocity.Z = 0.f; + CalcVelocity(timeTick, FallingLateralFriction, false, MaxDecel); + VelocityNoAirControl = FVector(Velocity.X, Velocity.Y, OldVelocity.Z); + VelocityNoAirControl = NewFallVelocity(VelocityNoAirControl, Gravity, GravityTime); + } + + const bool bCheckLandingSpot = false; // we already checked above. + AirControlAccel = (Velocity - VelocityNoAirControl) / timeTick; + const FVector AirControlDeltaV = LimitAirControl(LastMoveTimeSlice, AirControlAccel, Hit, bCheckLandingSpot) * LastMoveTimeSlice; + Adjusted = (VelocityNoAirControl + AirControlDeltaV) * LastMoveTimeSlice; + } + + const FVector OldHitNormal = Hit.Normal; + const FVector OldHitImpactNormal = Hit.ImpactNormal; + FVector Delta = ComputeSlideVector(Adjusted, 1.f - Hit.Time, OldHitNormal, Hit); + + // Compute velocity after deflection (only gravity component for RootMotion) + const UPrimitiveComponent* HitComponent = Hit.GetComponent(); + static const auto CVarUseTargetVelocityOnImpact = IConsoleManager::Get().FindConsoleVariable(TEXT("p.UseTargetVelocityOnImpact")); + if (CVarUseTargetVelocityOnImpact->GetInt() && !Velocity.IsNearlyZero() && MovementBaseUtility::IsSimulatedBase(HitComponent)) + { + const FVector ContactVelocity = MovementBaseUtility::GetMovementBaseVelocity(HitComponent, NAME_None) + MovementBaseUtility::GetMovementBaseTangentialVelocity(HitComponent, NAME_None, Hit.ImpactPoint); + const FVector NewVelocity = Velocity - Hit.ImpactNormal * FVector::DotProduct(Velocity - ContactVelocity, Hit.ImpactNormal); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + else if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported) + { + const FVector NewVelocity = (Delta / subTimeTickRemaining); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + + if (subTimeTickRemaining > KINDA_SMALL_NUMBER && (Delta | Adjusted) > 0.f) + { + // Move in deflected direction. + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + + if (Hit.bBlockingHit) + { + // hit second wall + LastMoveTimeSlice = subTimeTickRemaining; + subTimeTickRemaining = subTimeTickRemaining * (1.f - Hit.Time); + + + if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit)) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime += subTimeTickRemaining; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + + HandleImpact(Hit, LastMoveTimeSlice, Delta); + + // If we've changed physics mode, abort. + if (!HasValidData() || !IsFalling()) + { + RestorePreAdditiveVRMotionVelocity(); + return; + } + + // Act as if there was no air control on the last move when computing new deflection. + if (bHasLimitedAirControl && Hit.Normal.Z > CharacterMovementConstants::VERTICAL_SLOPE_NORMAL_ZVR) + { + const FVector LastMoveNoAirControl = VelocityNoAirControl * LastMoveTimeSlice; + Delta = ComputeSlideVector(LastMoveNoAirControl, 1.f, OldHitNormal, Hit); + } + + FVector PreTwoWallDelta = Delta; + TwoWallAdjust(Delta, Hit, OldHitNormal); + + // Limit air control, but allow a slide along the second wall. + if (bHasLimitedAirControl) + { + const bool bCheckLandingSpot = false; // we already checked above. + const FVector AirControlDeltaV = LimitAirControl(subTimeTickRemaining, AirControlAccel, Hit, bCheckLandingSpot) * subTimeTickRemaining; + + // Only allow if not back in to first wall + if (FVector::DotProduct(AirControlDeltaV, OldHitNormal) > 0.f) + { + Delta += (AirControlDeltaV * subTimeTickRemaining); + } + } + + // Compute velocity after deflection (only gravity component for RootMotion) + if (subTimeTickRemaining > KINDA_SMALL_NUMBER && !bJustTeleported) + { + const FVector NewVelocity = (Delta / subTimeTickRemaining); + Velocity = HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocityWithIgnoreZAccumulate() ? FVector(Velocity.X, Velocity.Y, NewVelocity.Z) : NewVelocity; + } + + // bDitch=true means that pawn is straddling two slopes, neither of which he can stand on + bool bDitch = ((OldHitImpactNormal.Z > 0.f) && (Hit.ImpactNormal.Z > 0.f) && (FMath::Abs(Delta.Z) <= KINDA_SMALL_NUMBER) && ((Hit.ImpactNormal | OldHitImpactNormal) < 0.f)); + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + if (Hit.Time == 0.f) + { + // if we are stuck then try to side step + FVector SideDelta = (OldHitNormal + Hit.ImpactNormal).GetSafeNormal2D(); + if (SideDelta.IsNearlyZero()) + { + SideDelta = FVector(OldHitNormal.Y, -OldHitNormal.X, 0).GetSafeNormal(); + } + SafeMoveUpdatedComponent(SideDelta, PawnRotation, true, Hit); + } + + if (bDitch || IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, Hit) || Hit.Time == 0.f) + { + RestorePreAdditiveVRMotionVelocity(); + remainingTime = 0.f; + ProcessLanded(Hit, remainingTime, Iterations); + return; + } + else if (GetPerchRadiusThreshold() > 0.f && Hit.Time == 1.f && OldHitImpactNormal.Z >= GetWalkableFloorZ()) + { + // We might be in a virtual 'ditch' within our perch radius. This is rare. + const FVector PawnLocation = UpdatedComponent->GetComponentLocation(); + const float ZMovedDist = FMath::Abs(PawnLocation.Z - OldLocation.Z); + const float MovedDist2DSq = (PawnLocation - OldLocation).SizeSquared2D(); + if (ZMovedDist <= 0.2f * timeTick && MovedDist2DSq <= 4.f * timeTick) + { + Velocity.X += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + Velocity.Y += 0.25f * GetMaxSpeed() * (RandomStream.FRand() - 0.5f); + Velocity.Z = FMath::Max<float>(JumpZVelocity * 0.25f, 1.f); + Delta = Velocity * timeTick; + SafeMoveUpdatedComponent(Delta, PawnRotation, true, Hit); + } + } + } + } + } + } + else + { + // We are finding the floor and adjusting here now + // This matches the final falling Z up to the PhysWalking floor offset to prevent the visible hitch when going + // From falling to walking. + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false, NULL); + + if (CurrentFloor.IsWalkableFloor()) + { + // If the current floor distance is within the physwalking required floor offset + if (CurrentFloor.GetDistanceToFloor() < (MIN_FLOOR_DIST + MAX_FLOOR_DIST) / 2) + { + // Adjust to correct height + AdjustFloorHeight(); + + SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + + // If this is a valid landing spot, stop falling now so that we land correctly + if (IsValidLandingSpot(VRRootCapsule->OffsetComponentToWorld.GetLocation()/*UpdatedComponent->GetComponentLocation()*/, CurrentFloor.HitResult)) + { + remainingTime += subTimeTickRemaining; + ProcessLanded(CurrentFloor.HitResult, remainingTime, Iterations); + return; + } + } + } + else if (CurrentFloor.HitResult.bStartPenetrating) + { + // The floor check failed because it started in penetration + // We do not want to try to move downward because the downward sweep failed, rather we'd like to try to pop out of the floor. + FHitResult Hitt(CurrentFloor.HitResult); + Hit.TraceEnd = Hit.TraceStart + FVector(0.f, 0.f, MAX_FLOOR_DIST); + const FVector RequestedAdjustment = GetPenetrationAdjustment(Hit); + ResolvePenetration(RequestedAdjustment, Hitt, UpdatedComponent->GetComponentQuat()); + bForceNextFloorCheck = true; + } + } + + if (Velocity.SizeSquared2D() <= KINDA_SMALL_NUMBER * 10.f) + { + Velocity.X = 0.f; + Velocity.Y = 0.f; + } + + RestorePreAdditiveVRMotionVelocity(); + } +} + + +void UVRCharacterMovementComponent::PhysNavWalking(float deltaTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharPhysNavWalking); + + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Root motion not for VR + if ((!CharacterOwner || !CharacterOwner->Controller) && !bRunPhysicsWithNoController && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + Acceleration = FVector::ZeroVector; + Velocity = FVector::ZeroVector; + return; + } + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + // Ensure velocity is horizontal. + MaintainHorizontalGroundVelocity(); + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN before CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + + //bound acceleration + Acceleration.Z = 0.f; + //if (!HasRootMotion()) + //{ + CalcVelocity(deltaTime, GroundFriction, false, BrakingDecelerationWalking); + devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("PhysNavWalking: Velocity contains NaN after CalcVelocity (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString())); + //} + + ApplyRootMotionToVelocity(deltaTime); + ApplyVRMotionToVelocity(deltaTime); + + /*if (IsFalling()) + { + // Root motion could have put us into Falling + StartNewPhysics(deltaTime, Iterations); + return; + }*/ + + Iterations++; + + FVector DesiredMove = Velocity; + DesiredMove.Z = 0.f; + + //const FVector OldPlayerLocation = GetActorFeetLocation(); + const FVector OldLocation = GetActorFeetLocationVR(); + const FVector DeltaMove = DesiredMove * deltaTime; + const bool bDeltaMoveNearlyZero = DeltaMove.IsNearlyZero(); + + FVector AdjustedDest = OldLocation + DeltaMove; + FNavLocation DestNavLocation; + + bool bSameNavLocation = false; + if (CachedNavLocation.NodeRef != INVALID_NAVNODEREF) + { + if (bProjectNavMeshWalking) + { + const float DistSq2D = (OldLocation - CachedNavLocation.Location).SizeSquared2D(); + const float DistZ = FMath::Abs(OldLocation.Z - CachedNavLocation.Location.Z); + + const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f; + const float ProjectionScale = (OldLocation.Z > CachedNavLocation.Location.Z) ? NavMeshProjectionHeightScaleUp : NavMeshProjectionHeightScaleDown; + const float DistZThr = TotalCapsuleHeight * FMath::Max(0.f, ProjectionScale); + + bSameNavLocation = (DistSq2D <= KINDA_SMALL_NUMBER) && (DistZ < DistZThr); + } + else + { + bSameNavLocation = CachedNavLocation.Location.Equals(OldLocation); + } + + if (bDeltaMoveNearlyZero && bSameNavLocation) + { + if (const INavigationDataInterface * NavData = GetNavData()) + { + if (!NavData->IsNodeRefValid(CachedNavLocation.NodeRef)) + { + CachedNavLocation.NodeRef = INVALID_NAVNODEREF; + bSameNavLocation = false; + } + } + } + } + + if (bDeltaMoveNearlyZero && bSameNavLocation) + { + DestNavLocation = CachedNavLocation; + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("%s using cached navmesh location! (bProjectNavMeshWalking = %d)"), *GetNameSafe(CharacterOwner), bProjectNavMeshWalking); + } + else + { + SCOPE_CYCLE_COUNTER(STAT_CharNavProjectPoint); + + // Start the trace from the Z location of the last valid trace. + // Otherwise if we are projecting our location to the underlying geometry and it's far above or below the navmesh, + // we'll follow that geometry's plane out of range of valid navigation. + if (bSameNavLocation && bProjectNavMeshWalking) + { + AdjustedDest.Z = CachedNavLocation.Location.Z; + } + + // Find the point on the NavMesh + const bool bHasNavigationData = FindNavFloor(AdjustedDest, DestNavLocation); + if (!bHasNavigationData) + { + RestorePreAdditiveVRMotionVelocity(); + SetMovementMode(MOVE_Walking); + return; + } + + CachedNavLocation = DestNavLocation; + } + + if (DestNavLocation.NodeRef != INVALID_NAVNODEREF) + { + FVector NewLocation(AdjustedDest.X, AdjustedDest.Y, DestNavLocation.Location.Z); + if (bProjectNavMeshWalking) + { + SCOPE_CYCLE_COUNTER(STAT_CharNavProjectLocation); + const float TotalCapsuleHeight = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2.0f; + const float UpOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleUp); + const float DownOffset = TotalCapsuleHeight * FMath::Max(0.f, NavMeshProjectionHeightScaleDown); + NewLocation = ProjectLocationFromNavMesh(deltaTime, OldLocation, NewLocation, UpOffset, DownOffset); + } + + FVector AdjustedDelta = NewLocation - OldLocation; + + if (!AdjustedDelta.IsNearlyZero()) + { + // 4.16 UNCOMMENT + FHitResult HitResult; + SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweepWhileNavWalking, HitResult); + + /* 4.16 Delete*/ + //const bool bSweep = UpdatedPrimitive ? UpdatedPrimitive->bGenerateOverlapEvents : false; + //FHitResult HitResult; + //SafeMoveUpdatedComponent(AdjustedDelta, UpdatedComponent->GetComponentQuat(), bSweep, HitResult); + // End 4.16 delete + } + + // Update velocity to reflect actual move + if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasVelocity()) + { + Velocity = (GetActorFeetLocationVR() - OldLocation) / deltaTime; + MaintainHorizontalGroundVelocity(); + } + + bJustTeleported = false; + } + else + { + StartFalling(Iterations, deltaTime, deltaTime, DeltaMove, OldLocation); + } + + RestorePreAdditiveVRMotionVelocity(); +} + +void UVRCharacterMovementComponent::PhysSwimming(float deltaTime, int32 Iterations) +{ + if (deltaTime < MIN_TICK_TIME) + { + return; + } + + // Rewind the players position by the new capsule location + RewindVRRelativeMovement(); + + RestorePreAdditiveRootMotionVelocity(); + //RestorePreAdditiveVRMotionVelocity(); + + float NetFluidFriction = 0.f; + float Depth = ImmersionDepth(); + float NetBuoyancy = Buoyancy * Depth; + float OriginalAccelZ = Acceleration.Z; + bool bLimitedUpAccel = false; + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (Velocity.Z > 0.33f * MaxSwimSpeed) && (NetBuoyancy != 0.f)) + { + //damp positive Z out of water + Velocity.Z = FMath::Max<FVector::FReal>(0.33f * MaxSwimSpeed, Velocity.Z * Depth * Depth); + } + else if (Depth < 0.65f) + { + bLimitedUpAccel = (Acceleration.Z > 0.f); + Acceleration.Z = FMath::Min<FVector::FReal>(0.1f, Acceleration.Z); + } + + Iterations++; + FVector OldLocation = UpdatedComponent->GetComponentLocation(); + bJustTeleported = false; + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity()) + { + const float Friction = 0.5f * GetPhysicsVolume()->FluidFriction * Depth; + CalcVelocity(deltaTime, Friction, true, GetMaxBrakingDeceleration()); + Velocity.Z += GetGravityZ() * deltaTime * (1.f - NetBuoyancy); + } + + ApplyRootMotionToVelocity(deltaTime); + ApplyVRMotionToVelocity(deltaTime); + + FVector Adjusted = Velocity * deltaTime; + FHitResult Hit(1.f); + float remainingTime = deltaTime * SwimVR(Adjusted/* + AdditionalVRInputVector*/, Hit); + + //may have left water - if so, script might have set new physics mode + if (!IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + + if (Hit.Time < 1.f && CharacterOwner) + { + HandleSwimmingWallHit(Hit, deltaTime); + if (bLimitedUpAccel && (Velocity.Z >= 0.f)) + { + // allow upward velocity at surface if against obstacle + Velocity.Z += OriginalAccelZ * deltaTime; + Adjusted = Velocity * (1.f - Hit.Time)*deltaTime; + SwimVR(Adjusted, Hit); + if (!IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + } + + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector VelDir = Velocity.GetSafeNormal(); + const float UpDown = GravDir | VelDir; + + bool bSteppedUp = false; + if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit)) + { + float stepZ = UpdatedComponent->GetComponentLocation().Z; + const FVector RealVelocity = Velocity; + Velocity.Z = 1.f; // HACK: since will be moving up, in case pawn leaves the water + bSteppedUp = StepUp(GravDir, (Adjusted/* + AdditionalVRInputVector*/) * (1.f - Hit.Time), Hit); + if (bSteppedUp) + { + //may have left water - if so, script might have set new physics mode + if (!IsSwimming()) + { + RestorePreAdditiveVRMotionVelocity(); + StartNewPhysics(remainingTime, Iterations); + return; + } + OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ); + } + Velocity = RealVelocity; + } + + if (!bSteppedUp) + { + //adjust and try again + HandleImpact(Hit, deltaTime, Adjusted); + SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true); + } + } + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && !bJustTeleported && ((deltaTime - remainingTime) > KINDA_SMALL_NUMBER) && CharacterOwner) + { + bool bWaterJump = !GetPhysicsVolume()->bWaterVolume; + float velZ = Velocity.Z; + Velocity = ((UpdatedComponent->GetComponentLocation() - OldLocation)/* - AdditionalVRInputVector*/) / (deltaTime - remainingTime); + if (bWaterJump) + { + Velocity.Z = velZ; + } + } + + if (!GetPhysicsVolume()->bWaterVolume && IsSwimming()) + { + SetMovementMode(MOVE_Falling); //in case script didn't change it (w/ zone change) + } + + RestorePreAdditiveVRMotionVelocity(); + + //may have left water - if so, script might have set new physics mode + if (!IsSwimming()) + { + StartNewPhysics(remainingTime, Iterations); + } +} + + +void UVRCharacterMovementComponent::StartSwimmingVR(FVector OldLocation, FVector OldVelocity, float timeTick, float remainingTime, int32 Iterations) +{ + if (remainingTime < MIN_TICK_TIME || timeTick < MIN_TICK_TIME) + { + return; + } + + FVector NewLocation = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && !bJustTeleported) + { + Velocity = (NewLocation - OldLocation) / timeTick; //actual average velocity + Velocity = 2.f*Velocity - OldVelocity; //end velocity has 2* accel of avg + Velocity = Velocity.GetClampedToMaxSize(GetPhysicsVolume()->TerminalVelocity); + } + const FVector End = FindWaterLine(NewLocation, OldLocation); + float waterTime = 0.f; + if (End != NewLocation) + { + const float ActualDist = (NewLocation - OldLocation).Size(); + if (ActualDist > KINDA_SMALL_NUMBER) + { + waterTime = timeTick * (End - NewLocation).Size() / ActualDist; + remainingTime += waterTime; + } + MoveUpdatedComponent(End - NewLocation, UpdatedComponent->GetComponentQuat(), true); + } + if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity() && (Velocity.Z > 2.f* CharacterMovementConstants::SWIMBOBSPEEDVR) && (Velocity.Z < 0.f)) //allow for falling out of water + { + Velocity.Z = CharacterMovementConstants::SWIMBOBSPEEDVR - Velocity.Size2D() * 0.7f; //smooth bobbing + } + if ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations)) + { + PhysSwimming(remainingTime, Iterations); + } +} + +float UVRCharacterMovementComponent::SwimVR(FVector Delta, FHitResult& Hit) +{ + FVector Start = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + float airTime = 0.f; + SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (!GetPhysicsVolume()->bWaterVolume) //then left water + { + FVector NewLoc = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + const FVector End = FindWaterLine(Start, NewLoc); + const float DesiredDist = Delta.Size(); + if (End != NewLoc && DesiredDist > KINDA_SMALL_NUMBER) + { + airTime = (End - NewLoc).Size() / DesiredDist; + if (((NewLoc - Start) | (End - NewLoc)) > 0.f) + { + airTime = 0.f; + } + SafeMoveUpdatedComponent(End - NewLoc, UpdatedComponent->GetComponentQuat(), true, Hit); + } + } + return airTime; +} + +bool UVRCharacterMovementComponent::CheckWaterJump(FVector CheckPoint, FVector& WallNormal) +{ + if (!HasValidData()) + { + return false; + } + FVector currentLoc = VRRootCapsule ? VRRootCapsule->OffsetComponentToWorld.GetLocation() : UpdatedComponent->GetComponentLocation(); + + // check if there is a wall directly in front of the swimming pawn + CheckPoint.Z = 0.f; + FVector CheckNorm = CheckPoint.GetSafeNormal(); + float PawnCapsuleRadius, PawnCapsuleHalfHeight; + CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleSize(PawnCapsuleRadius, PawnCapsuleHalfHeight); + CheckPoint = currentLoc + 1.2f * PawnCapsuleRadius * CheckNorm; + FVector Extent(PawnCapsuleRadius, PawnCapsuleRadius, PawnCapsuleHalfHeight); + FHitResult HitInfo(1.f); + FCollisionQueryParams CapsuleParams(SCENE_QUERY_STAT(CheckWaterJump), false, CharacterOwner); + FCollisionResponseParams ResponseParam; + InitCollisionParams(CapsuleParams, ResponseParam); + FCollisionShape CapsuleShape = GetPawnCapsuleCollisionShape(SHRINK_None); + const ECollisionChannel CollisionChannel = UpdatedComponent->GetCollisionObjectType(); + bool bHit = GetWorld()->SweepSingleByChannel(HitInfo, currentLoc, CheckPoint, FQuat::Identity, CollisionChannel, CapsuleShape, CapsuleParams, ResponseParam); + + if (bHit && !HitInfo.HitObjectHandle.DoesRepresentClass(APawn::StaticClass())) + { + // hit a wall - check if it is low enough + WallNormal = -1.f * HitInfo.ImpactNormal; + FVector Start = currentLoc;//UpdatedComponent->GetComponentLocation(); + Start.Z += MaxOutOfWaterStepHeight; + CheckPoint = Start + 3.2f * PawnCapsuleRadius * WallNormal; + FCollisionQueryParams LineParams(SCENE_QUERY_STAT(CheckWaterJump), true, CharacterOwner); + FCollisionResponseParams LineResponseParam; + InitCollisionParams(LineParams, LineResponseParam); + bHit = GetWorld()->LineTraceSingleByChannel(HitInfo, Start, CheckPoint, CollisionChannel, LineParams, LineResponseParam); + // if no high obstruction, or it's a valid floor, then pawn can jump out of water + return !bHit || IsWalkable(HitInfo); + } + return false; +} + +FBasedPosition UVRCharacterMovementComponent::GetActorFeetLocationBased() const +{ + return FBasedPosition(NULL, GetActorFeetLocationVR()); +} + +void UVRCharacterMovementComponent::ProcessLanded(const FHitResult& Hit, float remainingTime, int32 Iterations) +{ + SCOPE_CYCLE_COUNTER(STAT_CharProcessLanded); + + if (CharacterOwner && CharacterOwner->ShouldNotifyLanded(Hit)) + { + CharacterOwner->Landed(Hit); + } + if (IsFalling()) + { + + if (GetGroundMovementMode() == MOVE_NavWalking) + { + // verify navmesh projection and current floor + // otherwise movement will be stuck in infinite loop: + // navwalking -> (no navmesh) -> falling -> (standing on something) -> navwalking -> .... + + const FVector TestLocation = GetActorFeetLocationVR(); + FNavLocation NavLocation; + + const bool bHasNavigationData = FindNavFloor(TestLocation, NavLocation); + if (!bHasNavigationData || NavLocation.NodeRef == INVALID_NAVNODEREF) + { + SetGroundMovementMode(MOVE_Walking); + //GroundMovementMode = MOVE_Walking; + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("ProcessLanded(): %s tried to go to NavWalking but couldn't find NavMesh! Using Walking instead."), *GetNameSafe(CharacterOwner)); + } + } + + SetPostLandedPhysics(Hit); + } + + IPathFollowingAgentInterface* PFAgent = GetPathFollowingAgent(); + if (PFAgent) + { + PFAgent->OnLanded(); + } + + StartNewPhysics(remainingTime, Iterations); +} + +/////////////////////////// +// End Navigation Functions +/////////////////////////// + +void UVRCharacterMovementComponent::PostPhysicsTickComponent(float DeltaTime, FCharacterMovementComponentPostPhysicsTickFunction& ThisTickFunction) +{ + if (bDeferUpdateBasedMovement) + { + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + UpdateBasedMovement(DeltaTime); + SaveBaseLocation(); + bDeferUpdateBasedMovement = false; + } +} + + +void UVRCharacterMovementComponent::SimulateMovement(float DeltaSeconds) +{ + if (!HasValidData() || UpdatedComponent->Mobility != EComponentMobility::Movable || UpdatedComponent->IsSimulatingPhysics()) + { + return; + } + + const bool bIsSimulatedProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + const FRepMovement& ConstRepMovement = CharacterOwner->GetReplicatedMovement(); + + // Workaround for replication not being updated initially + if (bIsSimulatedProxy && + ConstRepMovement.Location.IsZero() && + ConstRepMovement.Rotation.IsZero() && + ConstRepMovement.LinearVelocity.IsZero()) + { + return; + } + + // If base is not resolved on the client, we should not try to simulate at all + if (CharacterOwner->GetReplicatedBasedMovement().IsBaseUnresolved()) + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Base for simulated character '%s' is not resolved on client, skipping SimulateMovement"), *CharacterOwner->GetName()); + return; + } + + FVector OldVelocity; + FVector OldLocation; + + // Scoped updates can improve performance of multiple MoveComponent calls. + { + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + bool bHandledNetUpdate = false; + if (bIsSimulatedProxy) + { + // Handle network changes + if (bNetworkUpdateReceived) + { + bNetworkUpdateReceived = false; + bHandledNetUpdate = true; + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s received net update"), *GetNameSafe(CharacterOwner)); + if (bNetworkMovementModeChanged) + { + ApplyNetworkMovementMode(CharacterOwner->GetReplicatedMovementMode()); + bNetworkMovementModeChanged = false; + } + else if (bJustTeleported || bForceNextFloorCheck) + { + // Make sure floor is current. We will continue using the replicated base, if there was one. + bJustTeleported = false; + UpdateFloorFromAdjustment(); + } + } + else if (bForceNextFloorCheck) + { + UpdateFloorFromAdjustment(); + } + } + + UpdateCharacterStateBeforeMovement(DeltaSeconds); + + if (MovementMode != MOVE_None) + { + //TODO: Also ApplyAccumulatedForces()? + HandlePendingLaunch(); + } + ClearAccumulatedForces(); + + if (MovementMode == MOVE_None) + { + return; + } + + const bool bSimGravityDisabled = (bIsSimulatedProxy && CharacterOwner->bSimGravityDisabled); + const bool bZeroReplicatedGroundVelocity = (bIsSimulatedProxy && IsMovingOnGround() && ConstRepMovement.LinearVelocity.IsZero()); + + // bSimGravityDisabled means velocity was zero when replicated and we were stuck in something. Avoid external changes in velocity as well. + // Being in ground movement with zero velocity, we cannot simulate proxy velocities safely because we might not get any further updates from the server. + if (bSimGravityDisabled || bZeroReplicatedGroundVelocity) + { + Velocity = FVector::ZeroVector; + } + + + MaybeUpdateBasedMovement(DeltaSeconds); + + // simulated pawns predict location + OldVelocity = Velocity; + OldLocation = UpdatedComponent->GetComponentLocation(); + + UpdateProxyAcceleration(); + + static const auto CVarNetEnableSkipProxyPredictionOnNetUpdate = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetEnableSkipProxyPredictionOnNetUpdate")); + // May only need to simulate forward on frames where we haven't just received a new position update. + if (!bHandledNetUpdate || !bNetworkSkipProxyPredictionOnNetUpdate || !CVarNetEnableSkipProxyPredictionOnNetUpdate->GetInt()) + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s simulating movement"), *GetNameSafe(CharacterOwner)); + FStepDownResult StepDownResult; + + // Skip the estimated movement when movement simulation is off, but keep the floor find + if(!bDisableSimulatedTickWhenSmoothingMovement) + { + MoveSmooth(Velocity, DeltaSeconds, &StepDownResult); + } + + // find floor and check if falling + if (IsMovingOnGround() || MovementMode == MOVE_Falling) + { + if (StepDownResult.bComputedFloor) + { + CurrentFloor = StepDownResult.FloorResult; + } + else if (bDisableSimulatedTickWhenSmoothingMovement || Velocity.Z <= 0.f) + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, Velocity.IsZero(), NULL); + } + else + { + CurrentFloor.Clear(); + } + + if (!CurrentFloor.IsWalkableFloor()) + { + if (!bSimGravityDisabled) + { + // No floor, must fall. + if (Velocity.Z <= 0.f || bApplyGravityWhileJumping || !CharacterOwner->IsJumpProvidingForce()) + { + Velocity = NewFallVelocity(Velocity, FVector(0.f, 0.f, GetGravityZ()), DeltaSeconds); + } + } + SetMovementMode(MOVE_Falling); + } + else + { + // Walkable floor + if (IsMovingOnGround()) + { + AdjustFloorHeight(); + SetBase(CurrentFloor.HitResult.Component.Get(), CurrentFloor.HitResult.BoneName); + } + else if (MovementMode == MOVE_Falling) + { + if (CurrentFloor.FloorDist <= MIN_FLOOR_DIST || (bSimGravityDisabled && CurrentFloor.FloorDist <= MAX_FLOOR_DIST)) + { + // Landed + SetPostLandedPhysics(CurrentFloor.HitResult); + } + else + { + if (!bSimGravityDisabled) + { + // Continue falling. + Velocity = NewFallVelocity(Velocity, FVector(0.f, 0.f, GetGravityZ()), DeltaSeconds); + } + CurrentFloor.Clear(); + } + } + } + } + } + else + { + UE_LOG(LogVRCharacterMovement, Verbose, TEXT("Proxy %s SKIPPING simulate movement"), *GetNameSafe(CharacterOwner)); + } + + UpdateCharacterStateAfterMovement(DeltaSeconds); + + // consume path following requested velocity + bHasRequestedVelocity = false; + + OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity); + } // End scoped movement update + + // Call custom post-movement events. These happen after the scoped movement completes in case the events want to use the current state of overlaps etc. + CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity); + + static const auto CVarBasedMovementMode = IConsoleManager::Get().FindConsoleVariable(TEXT("p.BasedMovementMode")); + if (CVarBasedMovementMode->GetInt() == 0) + { + SaveBaseLocation(); // behaviour before implementing this fix + } + else + { + MaybeSaveBaseLocation(); + } + + UpdateComponentVelocity(); + bJustTeleported = false; + + LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + LastUpdateVelocity = Velocity; +} + +void UVRCharacterMovementComponent::MoveSmooth(const FVector& InVelocity, const float DeltaSeconds, FStepDownResult* OutStepDownResult) +{ + if (!HasValidData()) + { + return; + } + + // Custom movement mode. + // Custom movement may need an update even if there is zero velocity. + if (MovementMode == MOVE_Custom) + { + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + PhysCustom(DeltaSeconds, 0); + return; + } + + FVector Delta = InVelocity * DeltaSeconds; + if (Delta.IsZero()) + { + return; + } + + FVRCharacterScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates); + + if (IsMovingOnGround()) + { + MoveAlongFloor(InVelocity, DeltaSeconds, OutStepDownResult); + } + else + { + FHitResult Hit(1.f); + SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit); + + if (Hit.IsValidBlockingHit()) + { + bool bSteppedUp = false; + + if (IsFlying()) + { + if (CanStepUp(Hit)) + { + OutStepDownResult = NULL; // No need for a floor when not walking. + if (FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) + { + const FVector GravDir = FVector(0.f, 0.f, -1.f); + const FVector DesiredDir = Delta.GetSafeNormal(); + const float UpDown = GravDir | DesiredDir; + if ((UpDown < 0.5f) && (UpDown > -0.2f)) + { + bSteppedUp = StepUp(GravDir, Delta * (1.f - Hit.Time), Hit, OutStepDownResult); + } + } + } + } + + // If StepUp failed, try sliding. + if (!bSteppedUp) + { + SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, false); + } + } + } +} + +void UVRCharacterMovementComponent::ClientHandleMoveResponse(const FCharacterMoveResponseDataContainer& MoveResponse) +{ + if (MoveResponse.IsGoodMove()) + { + ClientAckGoodMove_Implementation(MoveResponse.ClientAdjustment.TimeStamp); + } + else + { + // Wrappers to old RPC handlers, to maintain compatibility. If overrides need additional serialized data, they can access GetMoveResponseDataContainer() + if (MoveResponse.bRootMotionSourceCorrection) + { + if (FRootMotionSourceGroup* RootMotionSourceGroup = MoveResponse.GetRootMotionSourceGroup(*this)) + { + ClientAdjustRootMotionSourcePosition_Implementation( + MoveResponse.ClientAdjustment.TimeStamp, + *RootMotionSourceGroup, + MoveResponse.bRootMotionMontageCorrection, + MoveResponse.RootMotionTrackPosition, + MoveResponse.ClientAdjustment.NewLoc, + MoveResponse.RootMotionRotation, + MoveResponse.ClientAdjustment.NewVel.Z, + MoveResponse.ClientAdjustment.NewBase, + MoveResponse.ClientAdjustment.NewBaseBoneName, + MoveResponse.bHasBase, + MoveResponse.ClientAdjustment.bBaseRelativePosition, + MoveResponse.ClientAdjustment.MovementMode); + } + } + else if (MoveResponse.bRootMotionMontageCorrection) + { + ClientAdjustRootMotionPosition_Implementation( + MoveResponse.ClientAdjustment.TimeStamp, + MoveResponse.RootMotionTrackPosition, + MoveResponse.ClientAdjustment.NewLoc, + MoveResponse.RootMotionRotation, + MoveResponse.ClientAdjustment.NewVel.Z, + MoveResponse.ClientAdjustment.NewBase, + MoveResponse.ClientAdjustment.NewBaseBoneName, + MoveResponse.bHasBase, + MoveResponse.ClientAdjustment.bBaseRelativePosition, + MoveResponse.ClientAdjustment.MovementMode); + } + else + { + ClientAdjustPositionVR_Implementation( + MoveResponse.ClientAdjustment.TimeStamp, + MoveResponse.ClientAdjustment.NewLoc, + FRotator::CompressAxisToShort(MoveResponse.ClientAdjustment.NewRot.Yaw), + MoveResponse.ClientAdjustment.NewVel, + MoveResponse.ClientAdjustment.NewBase, + MoveResponse.ClientAdjustment.NewBaseBoneName, + MoveResponse.bHasBase, + MoveResponse.ClientAdjustment.bBaseRelativePosition, + MoveResponse.ClientAdjustment.MovementMode); + } + } +} + +void UVRCharacterMovementComponent::ClientAdjustPositionVR_Implementation +( + float TimeStamp, + FVector NewLocation, + uint16 NewYaw, + FVector NewVelocity, + UPrimitiveComponent* NewBase, + FName NewBaseBoneName, + bool bHasBase, + bool bBaseRelativePosition, + uint8 ServerMovementMode +) +{ + if (!HasValidData() || !IsActive()) + { + return; + } + + + FNetworkPredictionData_Client_Character* ClientData = GetPredictionData_Client_Character(); + check(ClientData); + + // Make sure the base actor exists on this client. + const bool bUnresolvedBase = bHasBase && (NewBase == NULL); + if (bUnresolvedBase) + { + if (bBaseRelativePosition) + { + UE_LOG(LogNetPlayerMovement, Warning, TEXT("ClientAdjustPosition_Implementation could not resolve the new relative movement base actor, ignoring server correction! Client currently at world location %s on base %s"), + *UpdatedComponent->GetComponentLocation().ToString(), *GetNameSafe(GetMovementBase())); + return; + } + else + { + UE_LOG(LogNetPlayerMovement, Verbose, TEXT("ClientAdjustPosition_Implementation could not resolve the new absolute movement base actor, but WILL use the position!")); + } + } + + // Ack move if it has not expired. + int32 MoveIndex = ClientData->GetSavedMoveIndex(TimeStamp); + if (MoveIndex == INDEX_NONE) + { + if (ClientData->LastAckedMove.IsValid()) + { + UE_LOG(LogNetPlayerMovement, Log, TEXT("ClientAdjustPosition_Implementation could not find Move for TimeStamp: %f, LastAckedTimeStamp: %f, CurrentTimeStamp: %f"), TimeStamp, ClientData->LastAckedMove->TimeStamp, ClientData->CurrentTimeStamp); + } + return; + } + + if (!bUseClientControlRotation) + { + float YawValue = FRotator::DecompressAxisFromShort(NewYaw); + // Trust the server's control yaw + if (ClientData->LastAckedMove.IsValid() && !FMath::IsNearlyEqual(ClientData->LastAckedMove->SavedControlRotation.Yaw, YawValue)) + { + if (BaseVRCharacterOwner) + { + if (BaseVRCharacterOwner->bUseControllerRotationYaw) + { + AController * myController = BaseVRCharacterOwner->GetController(); + if (myController) + { + //FRotator newRot = myController->GetControlRotation(); + myController->SetControlRotation(FRotator(0.f, YawValue, 0.f));//(ClientData->LastAckedMove->SavedControlRotation); + } + } + + BaseVRCharacterOwner->SetActorRotation(FRotator(0.f, YawValue, 0.f)); + } + } + } + + ClientData->AckMove(MoveIndex, *this); + + FVector WorldShiftedNewLocation; + // Received Location is relative to dynamic base + if (bBaseRelativePosition) + { + FVector BaseLocation; + FQuat BaseRotation; + MovementBaseUtility::GetMovementBaseTransform(NewBase, NewBaseBoneName, BaseLocation, BaseRotation); // TODO: error handling if returns false + WorldShiftedNewLocation = NewLocation + BaseLocation; + } + else + { + WorldShiftedNewLocation = FRepMovement::RebaseOntoLocalOrigin(NewLocation, this); + } + + + // Trigger event + OnClientCorrectionReceived(*ClientData, TimeStamp, WorldShiftedNewLocation, NewVelocity, NewBase, NewBaseBoneName, bHasBase, bBaseRelativePosition, ServerMovementMode); + + // Trust the server's positioning. + if (UpdatedComponent) + { + UpdatedComponent->SetWorldLocation(WorldShiftedNewLocation, false, nullptr, ETeleportType::TeleportPhysics); + } + Velocity = NewVelocity; + + // Trust the server's movement mode + UPrimitiveComponent* PreviousBase = CharacterOwner->GetMovementBase(); + ApplyNetworkMovementMode(ServerMovementMode); + + // Set base component + UPrimitiveComponent* FinalBase = NewBase; + FName FinalBaseBoneName = NewBaseBoneName; + if (bUnresolvedBase) + { + check(NewBase == NULL); + check(!bBaseRelativePosition); + + // We had an unresolved base from the server + // If walking, we'd like to continue walking if possible, to avoid falling for a frame, so try to find a base where we moved to. + if (PreviousBase && UpdatedComponent) + { + FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, false); + if (CurrentFloor.IsWalkableFloor()) + { + FinalBase = CurrentFloor.HitResult.Component.Get(); + FinalBaseBoneName = CurrentFloor.HitResult.BoneName; + } + else + { + FinalBase = nullptr; + FinalBaseBoneName = NAME_None; + } + } + } + SetBase(FinalBase, FinalBaseBoneName); + + // Update floor at new location + UpdateFloorFromAdjustment(); + bJustTeleported = true; + + // Even if base has not changed, we need to recompute the relative offsets (since we've moved). + SaveBaseLocation(); + + LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + LastUpdateVelocity = Velocity; + + UpdateComponentVelocity(); + ClientData->bUpdatePosition = true; +} + +bool UVRCharacterMovementComponent::ServerCheckClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& ClientWorldLocation, float ClientYaw, const FVector& RelativeClientLocation, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode) +{ + // Check location difference against global setting + if (!bIgnoreClientMovementErrorChecksAndCorrection) + { + //const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientWorldLocation; + +#if ROOT_MOTION_DEBUG + if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnAnyThread() == 1) + { + const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientWorldLocation; + FString AdjustedDebugString = FString::Printf(TEXT("ServerCheckClientError LocDiff(%.1f) ExceedsAllowablePositionError(%d) TimeStamp(%f)"), + LocDiff.Size(), GetDefault<AGameNetworkManager>()->ExceedsAllowablePositionError(LocDiff), ClientTimeStamp); + RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString); + } +#endif + + if (ServerExceedsAllowablePositionError(ClientTimeStamp, DeltaTime, Accel, ClientWorldLocation, RelativeClientLocation, ClientMovementBase, ClientBaseBoneName, ClientMovementMode)) + { + return true; + } + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + static const auto CVarNetForceClientAdjustmentPercent = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetForceClientAdjustmentPercent")); + if (CVarNetForceClientAdjustmentPercent->GetFloat() > SMALL_NUMBER) + { + if (RandomStream.FRand() < CVarNetForceClientAdjustmentPercent->GetFloat()) + { + UE_LOG(LogVRCharacterMovement, VeryVerbose, TEXT("** ServerCheckClientError forced by p.NetForceClientAdjustmentPercent")); + return true; + } + } +#endif + } + else + { +#if !UE_BUILD_SHIPPING + static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections")); + if (CVarNetShowCorrections->GetInt() != 0) + { + UE_LOG(LogVRCharacterMovement, Warning, TEXT("*** Server: %s is set to ignore error checks and corrections."), *GetNameSafe(CharacterOwner)); + } +#endif // !UE_BUILD_SHIPPING + } + + // If we are rolling back client rotation + if (!bUseClientControlRotation && !FMath::IsNearlyEqual(FRotator::ClampAxis(ClientYaw), FRotator::ClampAxis(UpdatedComponent->GetComponentRotation().Yaw), CharacterMovementComponentStatics::fRotationCorrectionThreshold)) + { + return true; + } + + return false; +} + +void UVRCharacterMovementComponent::ServerMoveHandleClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& RelativeClientLoc, float ClientYaw, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode) +{ + if (!ShouldUsePackedMovementRPCs()) + { + if (RelativeClientLoc == FVector(1.f, 2.f, 3.f)) // first part of double servermove + { + return; + } + } + + FNetworkPredictionData_Server_Character* ServerData = GetPredictionData_Server_Character(); + check(ServerData); + + // Don't prevent more recent updates from being sent if received this frame. + // We're going to send out an update anyway, might as well be the most recent one. + APlayerController* PC = Cast<APlayerController>(CharacterOwner->GetController()); + if ((ServerData->LastUpdateTime != GetWorld()->TimeSeconds)) + { + const AGameNetworkManager* GameNetworkManager = (const AGameNetworkManager*)(AGameNetworkManager::StaticClass()->GetDefaultObject()); + if (GameNetworkManager->WithinUpdateDelayBounds(PC, ServerData->LastUpdateTime)) + { + return; + } + } + + // Offset may be relative to base component + FVector ClientLoc = RelativeClientLoc; + if (MovementBaseUtility::UseRelativeLocation(ClientMovementBase)) + { + FVector BaseLocation; + FQuat BaseRotation; + MovementBaseUtility::GetMovementBaseTransform(ClientMovementBase, ClientBaseBoneName, BaseLocation, BaseRotation); + ClientLoc += BaseLocation; + } + else + { + ClientLoc = FRepMovement::RebaseOntoLocalOrigin(ClientLoc, this); + } + + FVector ServerLoc = UpdatedComponent->GetComponentLocation(); + + // Client may send a null movement base when walking on bases with no relative location (to save bandwidth). + // In this case don't check movement base in error conditions, use the server one (which avoids an error based on differing bases). Position will still be validated. + if (ClientMovementBase == nullptr) + { + TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None); + TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(ClientMovementMode, NetMovementMode, NetCustomMode, NetGroundMode); + if (NetMovementMode == MOVE_Walking) + { + ClientMovementBase = CharacterOwner->GetBasedMovement().MovementBase; + ClientBaseBoneName = CharacterOwner->GetBasedMovement().BoneName; + } + } + + // If base location is out of sync on server and client, changing base can result in a jarring correction. + // So in the case that the base has just changed on server or client, server trusts the client (within a threshold) + UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + FName MovementBaseBoneName = CharacterOwner->GetBasedMovement().BoneName; + const bool bServerIsFalling = IsFalling(); + const bool bClientIsFalling = ClientMovementMode == MOVE_Falling; + const bool bServerJustLanded = bLastServerIsFalling && !bServerIsFalling; + const bool bClientJustLanded = bLastClientIsFalling && !bClientIsFalling; + + FVector RelativeLocation = ServerLoc; + FVector RelativeVelocity = Velocity; + bool bUseLastBase = false; + bool bFallingWithinAcceptableError = false; + + // Potentially trust the client a little when landing + static const auto CVarClientAuthorityThresholdOnBaseChange = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ClientAuthorityThresholdOnBaseChange")); + static const auto CVarMaxFallingCorrectionLeash = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxFallingCorrectionLeash")); + + const float ClientAuthorityThreshold = CVarClientAuthorityThresholdOnBaseChange->GetFloat(); + const float MaxFallingCorrectionLeash = CVarMaxFallingCorrectionLeash->GetFloat(); + const bool bDeferServerCorrectionsWhenFalling = ClientAuthorityThreshold > 0.f || MaxFallingCorrectionLeash > 0.f; + if (bDeferServerCorrectionsWhenFalling) + { + // Teleports and other movement modes mean we should just trust the server like we normally would + if (bTeleportedSinceLastUpdate || (MovementMode != MOVE_Walking && MovementMode != MOVE_Falling)) + { + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + + // MaxFallingCorrectionLeash indicates we'll use a variable correction size based on the error on take-off and the direction of movement. + // ClientAuthorityThreshold is an static client-trusting correction upon landing. + // If both are set, use the smaller of the two. If only one is set, use that. If neither are set, we wouldn't even be inside this block. + float MaxLandingCorrection = 0.f; + if (ClientAuthorityThreshold > 0.f && MaxFallingCorrectionLeash > 0.f) + { + MaxLandingCorrection = FMath::Min(ClientAuthorityThreshold, MaxServerClientErrorWhileFalling); + } + else + { + MaxLandingCorrection = FMath::Max(ClientAuthorityThreshold, MaxServerClientErrorWhileFalling); + } + + if (bCanTrustClientOnLanding && MaxLandingCorrection > 0.f && (bClientJustLanded || bServerJustLanded)) + { + // no longer falling; server should trust client up to a point to finish the landing as the client sees it + const FVector LocDiff = ServerLoc - ClientLoc; + + if (!LocDiff.IsNearlyZero(KINDA_SMALL_NUMBER)) + { + if (LocDiff.SizeSquared() < FMath::Square(MaxLandingCorrection)) + { + ServerLoc = ClientLoc; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + else + { + const FVector ClampedDiff = LocDiff.GetSafeNormal() * MaxLandingCorrection; + ServerLoc -= ClampedDiff; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + } + + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + + if (bServerIsFalling && bLastServerIsWalking && !bTeleportedSinceLastUpdate) + { + float ClientForwardFactor = 1.f; + UPrimitiveComponent* LastServerMovementBaseVRPtr = LastServerMovementBaseVR.Get(); + if (IsValid(LastServerMovementBaseVRPtr) && MovementBaseUtility::IsDynamicBase(LastServerMovementBaseVRPtr) && MaxWalkSpeed > KINDA_SMALL_NUMBER) + { + const FVector LastBaseVelocity = MovementBaseUtility::GetMovementBaseVelocity(LastServerMovementBaseVRPtr, LastServerMovementBaseBoneName); + RelativeVelocity = Velocity - LastBaseVelocity; + const FVector BaseDirection = LastBaseVelocity.GetSafeNormal2D(); + const FVector RelativeDirection = RelativeVelocity * (1.f / MaxWalkSpeed); + + ClientForwardFactor = FMath::Clamp(FVector::DotProduct(BaseDirection, RelativeDirection), 0.f, 1.f); + + // To improve position syncing, use old base for take-off + if (MovementBaseUtility::UseRelativeLocation(LastServerMovementBaseVRPtr)) + { + FVector BaseLocation; + FQuat BaseQuat; + MovementBaseUtility::GetMovementBaseTransform(LastServerMovementBaseVRPtr, LastServerMovementBaseBoneName, BaseLocation, BaseQuat); + + // Relative Location + RelativeLocation = UpdatedComponent->GetComponentLocation() - BaseLocation; + bUseLastBase = true; + } + } + + if (ClientAuthorityThreshold > 0.f && ClientForwardFactor < 1.f) + { + const float AdjustedClientAuthorityThreshold = ClientAuthorityThreshold * (1.f - ClientForwardFactor); + const FVector LocDiff = ServerLoc - ClientLoc; + + // Potentially trust the client a little when taking off in the opposite direction to the base (to help not get corrected back onto the base) + if (!LocDiff.IsNearlyZero(KINDA_SMALL_NUMBER)) + { + if (LocDiff.SizeSquared() < FMath::Square(AdjustedClientAuthorityThreshold)) + { + ServerLoc = ClientLoc; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + else + { + const FVector ClampedDiff = LocDiff.GetSafeNormal() * AdjustedClientAuthorityThreshold; + ServerLoc -= ClampedDiff; + UpdatedComponent->MoveComponent(ServerLoc - UpdatedComponent->GetComponentLocation(), UpdatedComponent->GetComponentQuat(), true, nullptr, EMoveComponentFlags::MOVECOMP_NoFlags, ETeleportType::TeleportPhysics); + bJustTeleported = true; + } + } + } + + if (ClientForwardFactor < 1.f) + { + MaxServerClientErrorWhileFalling = FMath::Min((ServerLoc - ClientLoc).Size() * (1.f - ClientForwardFactor), MaxFallingCorrectionLeash); + bCanTrustClientOnLanding = true; + } + else + { + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + } + else if (!bServerIsFalling && bCanTrustClientOnLanding) + { + MaxServerClientErrorWhileFalling = 0.f; + bCanTrustClientOnLanding = false; + } + + if (MaxServerClientErrorWhileFalling > 0.f && (bServerIsFalling || bClientIsFalling)) + { + const FVector LocDiff = ServerLoc - ClientLoc; + if (LocDiff.SizeSquared() <= FMath::Square(MaxServerClientErrorWhileFalling)) + { + ServerLoc = ClientLoc; + // Still want a velocity update when we first take off + bFallingWithinAcceptableError = true; + } + else + { + // Change ServerLoc to be on the edge of the acceptable error rather than doing a full correction. + // This is not actually changing the server position, but changing it as far as corrections are concerned. + // This means we're just holding the client on a longer leash while we're falling. + static const auto CVarMaxFallingCorrectionLeashBuffer = IConsoleManager::Get().FindConsoleVariable(TEXT("p.MaxFallingCorrectionLeashBuffer")); + ServerLoc = ServerLoc - LocDiff.GetSafeNormal() * FMath::Clamp(MaxServerClientErrorWhileFalling - CVarMaxFallingCorrectionLeashBuffer->GetFloat(), 0.f, MaxServerClientErrorWhileFalling); + } + } + } + + bool bInClientAuthoritativeMovementMode = false; + // Custom movement modes aren't going to be rolled back as they are client authed for our pawns + + TEnumAsByte<EMovementMode> NetMovementMode(MOVE_None); + TEnumAsByte<EMovementMode> NetGroundMode(MOVE_None); + uint8 NetCustomMode(0); + UnpackNetworkMovementMode(ClientMovementMode, NetMovementMode, NetCustomMode, NetGroundMode); + if (NetMovementMode == EMovementMode::MOVE_Custom) + { + if (NetCustomMode == (uint8)EVRCustomMovementMode::VRMOVE_Climbing) + bInClientAuthoritativeMovementMode = true; + } + + // Compute the client error from the server's position + // If client has accumulated a noticeable positional error, correct them. + bNetworkLargeClientCorrection = ServerData->bForceClientUpdate; + if (!bInClientAuthoritativeMovementMode && (ServerData->bForceClientUpdate || (!bFallingWithinAcceptableError && ServerCheckClientErrorVR(ClientTimeStamp, DeltaTime, Accel, ClientLoc, ClientYaw, RelativeClientLoc, ClientMovementBase, ClientBaseBoneName, ClientMovementMode)))) + { + //UPrimitiveComponent* MovementBase = CharacterOwner->GetMovementBase(); + ServerData->PendingAdjustment.NewVel = Velocity; + ServerData->PendingAdjustment.NewBase = MovementBase; + ServerData->PendingAdjustment.NewBaseBoneName = MovementBaseBoneName; + ServerData->PendingAdjustment.NewLoc = FRepMovement::RebaseOntoZeroOrigin(ServerLoc, this); + ServerData->PendingAdjustment.NewRot = UpdatedComponent->GetComponentRotation(); + + ServerData->PendingAdjustment.bBaseRelativePosition = (bDeferServerCorrectionsWhenFalling && bUseLastBase) || MovementBaseUtility::UseRelativeLocation(MovementBase); + if (ServerData->PendingAdjustment.bBaseRelativePosition) + { + // Relative location + if (bDeferServerCorrectionsWhenFalling && bUseLastBase) + { + ServerData->PendingAdjustment.NewVel = RelativeVelocity; + ServerData->PendingAdjustment.NewBase = LastServerMovementBaseVR.Get(); + ServerData->PendingAdjustment.NewBaseBoneName = LastServerMovementBaseBoneName; + ServerData->PendingAdjustment.NewLoc = RelativeLocation; + } + else + { + ServerData->PendingAdjustment.NewLoc = CharacterOwner->GetBasedMovement().Location; + } + + // TODO: this could be a relative rotation, but all client corrections ignore rotation right now except the root motion one, which would need to be updated. + //ServerData->PendingAdjustment.NewRot = CharacterOwner->GetBasedMovement().Rotation; + } + +#if !UE_BUILD_SHIPPING + static const auto CVarNetShowCorrections = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetShowCorrections")); + static const auto CVarNetCorrectionLifetime = IConsoleManager::Get().FindConsoleVariable(TEXT("p.NetCorrectionLifetime")); + if (CVarNetShowCorrections->GetInt() != 0) + { + const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientLoc; + const FString BaseString = MovementBase ? MovementBase->GetPathName(MovementBase->GetOutermost()) : TEXT("None"); + UE_LOG(LogVRCharacterMovement, Warning, TEXT("*** Server: Error for %s at Time=%.3f is %3.3f LocDiff(%s) ClientLoc(%s) ServerLoc(%s) Base: %s Bone: %s Accel(%s) Velocity(%s)"), + *GetNameSafe(CharacterOwner), ClientTimeStamp, LocDiff.Size(), *LocDiff.ToString(), *ClientLoc.ToString(), *UpdatedComponent->GetComponentLocation().ToString(), *BaseString, *ServerData->PendingAdjustment.NewBaseBoneName.ToString(), *Accel.ToString(), *Velocity.ToString()); + const float DebugLifetime = CVarNetCorrectionLifetime->GetFloat(); + DrawDebugCapsule(GetWorld(), UpdatedComponent->GetComponentLocation(), CharacterOwner->GetSimpleCollisionHalfHeight(), CharacterOwner->GetSimpleCollisionRadius(), FQuat::Identity, FColor(100, 255, 100), false, DebugLifetime); + DrawDebugCapsule(GetWorld(), ClientLoc, CharacterOwner->GetSimpleCollisionHalfHeight(), CharacterOwner->GetSimpleCollisionRadius(), FQuat::Identity, FColor(255, 100, 100), false, DebugLifetime); + } +#endif + + ServerData->LastUpdateTime = GetWorld()->TimeSeconds; + ServerData->PendingAdjustment.DeltaTime = DeltaTime; + ServerData->PendingAdjustment.TimeStamp = ClientTimeStamp; + ServerData->PendingAdjustment.bAckGoodMove = false; + ServerData->PendingAdjustment.MovementMode = PackNetworkMovementMode(); + + //PerfCountersIncrement(PerfCounter_NumServerMoveCorrections); + } + else + { + if (bInClientAuthoritativeMovementMode || ServerShouldUseAuthoritativePosition(ClientTimeStamp, DeltaTime, Accel, ClientLoc, RelativeClientLoc, ClientMovementBase, ClientBaseBoneName, ClientMovementMode)) + { + const FVector LocDiff = UpdatedComponent->GetComponentLocation() - ClientLoc; //-V595 + if (!LocDiff.IsZero() || ClientMovementMode != PackNetworkMovementMode() || GetMovementBase() != ClientMovementBase || (CharacterOwner && CharacterOwner->GetBasedMovement().BoneName != ClientBaseBoneName)) + { + // Just set the position. On subsequent moves we will resolve initially overlapping conditions. + UpdatedComponent->SetWorldLocation(ClientLoc, false); //-V595 + + // Trust the client's movement mode. + ApplyNetworkMovementMode(ClientMovementMode); + + // Update base and floor at new location. + SetBase(ClientMovementBase, ClientBaseBoneName); + UpdateFloorFromAdjustment(); + + // Even if base has not changed, we need to recompute the relative offsets (since we've moved). + SaveBaseLocation(); + + LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector; + LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity; + LastUpdateVelocity = Velocity; + } + } + + // acknowledge receipt of this successful servermove() + ServerData->PendingAdjustment.TimeStamp = ClientTimeStamp; + ServerData->PendingAdjustment.bAckGoodMove = true; + } + + //PerfCountersIncrement(PerfCounter_NumServerMoves); + + ServerData->bForceClientUpdate = false; + + LastServerMovementBaseVR = MovementBase; + LastServerMovementBaseBoneName = MovementBaseBoneName; + bLastClientIsFalling = bClientIsFalling; + bLastServerIsFalling = bServerIsFalling; + bLastServerIsWalking = MovementMode == MOVE_Walking; +} + +FVector UVRCharacterMovementComponent::GetPenetrationAdjustment(const FHitResult& Hit) const +{ + // This checks for a walking collision override on the penetrated object + // If found then it stops penetration adjustments. + if (MovementMode == EMovementMode::MOVE_Walking && VRRootCapsule && VRRootCapsule->bUseWalkingCollisionOverride && Hit.Component.IsValid()) + { + ECollisionResponse WalkingResponse; + WalkingResponse = Hit.Component->GetCollisionResponseToChannel(VRRootCapsule->WalkingCollisionOverride); + + if (WalkingResponse == ECR_Ignore || WalkingResponse == ECR_Overlap) + { + return FVector::ZeroVector; + } + } + + FVector Result = Super::GetPenetrationAdjustment(Hit); + + if (CharacterOwner) + { + const bool bIsProxy = (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy); + float MaxDistance = bIsProxy ? MaxDepenetrationWithGeometryAsProxy : MaxDepenetrationWithGeometry; + if (Hit.HitObjectHandle.DoesRepresentClass(APawn::StaticClass())) + { + MaxDistance = bIsProxy ? MaxDepenetrationWithPawnAsProxy : MaxDepenetrationWithPawn; + } + + Result = Result.GetClampedToMaxSize(MaxDistance); + } + + return Result; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionFunctionLibrary.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionFunctionLibrary.cpp new file mode 100644 index 0000000..c45bfa4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionFunctionLibrary.cpp @@ -0,0 +1,738 @@ +// Fill out your copyright notice in the Description page of Project Settings. +#include "VRExpansionFunctionLibrary.h" +#include "DrawDebugHelpers.h" +#include "Engine/Engine.h" +#include "IXRTrackingSystem.h" +#include "IHeadMountedDisplay.h" +#include "Grippables/HandSocketComponent.h" +#include "Misc/CollisionIgnoreSubsystem.h" +#include "Components/SplineComponent.h" +#include "Components/SplineMeshComponent.h" +#include "Components/PrimitiveComponent.h" +#include "GripMotionControllerComponent.h" +//#include "IMotionController.h" +//#include "HeadMountedDisplayFunctionLibrary.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GameplayTagContainer.h" +//#include "IHeadMountedDisplay.h" + +#if WITH_CHAOS +#include "Chaos/ParticleHandle.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "Chaos/ParticleHandle.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#include "PBDRigidsSolver.h" +#endif + +#if WITH_EDITOR +#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h" +#endif + +//General Log +DEFINE_LOG_CATEGORY(VRExpansionFunctionLibraryLog); + +UGameViewportClient* UVRExpansionFunctionLibrary::GetGameViewportClient(UObject* WorldContextObject) +{ + if (WorldContextObject) + { + return WorldContextObject->GetWorld()->GetGameViewport(); + } + + return nullptr; +} + +void UVRExpansionFunctionLibrary::SetActorsIgnoreAllCollision(UObject* WorldContextObject, AActor* Actor1, AActor* Actor2, bool bIgnoreCollision) +{ + TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents1; + Actor1->GetComponents<UPrimitiveComponent>(PrimitiveComponents1); + + TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents2; + Actor2->GetComponents<UPrimitiveComponent>(PrimitiveComponents2); + + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem) + { + // Just a temp flag to only check state on the first component pair + bool bIgnorefirst = true; + for (int i = 0; i < PrimitiveComponents1.Num(); ++i) + { + for (int j = 0; j < PrimitiveComponents2.Num(); ++j) + { + + // Don't ignore collision if one is already invalid + if (!IsValid(PrimitiveComponents1[i]) || !IsValid(PrimitiveComponents2[j])) + { + continue; + } + + // Don't ignore collision if no collision on at least one of the objects + if (PrimitiveComponents1[i]->GetCollisionEnabled() == ECollisionEnabled::NoCollision || PrimitiveComponents2[j]->GetCollisionEnabled() == ECollisionEnabled::NoCollision) + { + if (!bIgnoreCollision) + { + if (CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(PrimitiveComponents1[i], PrimitiveComponents2[j])) + { + UE_LOG(VRExpansionFunctionLibraryLog, Error, TEXT("Set Actors Ignore Collision called with at least one object set to no collision that are ignoring collision already!! %s, %s"),*PrimitiveComponents1[i]->GetName(), *PrimitiveComponents2[j]->GetName()); + } + } + continue; + } + + CollisionIgnoreSubsystem->SetComponentCollisionIgnoreState(true, true, PrimitiveComponents1[i], NAME_None, PrimitiveComponents2[j], NAME_None, bIgnoreCollision, bIgnorefirst); + bIgnorefirst = false; + } + } + } +} + +void UVRExpansionFunctionLibrary::SetObjectsIgnoreCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1, FName OptionalBoneName1, bool bAddChildBones1, UPrimitiveComponent* Prim2, FName OptionalBoneName2, bool bAddChildBones2, bool bIgnoreCollision) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem) + { + CollisionIgnoreSubsystem->SetComponentCollisionIgnoreState(bAddChildBones1, bAddChildBones2, Prim1, OptionalBoneName1, Prim2, OptionalBoneName2, bIgnoreCollision, true); + } +} + + +bool UVRExpansionFunctionLibrary::IsComponentIgnoringCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem) + { + return CollisionIgnoreSubsystem->IsComponentIgnoringCollision(Prim1); + } + + return false; +} + +bool UVRExpansionFunctionLibrary::AreComponentsIgnoringCollisions(UObject* WorldContextObject, UPrimitiveComponent* Prim1, UPrimitiveComponent * Prim2) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem && CollisionIgnoreSubsystem->CollisionTrackedPairs.Num() > 0) + { + return CollisionIgnoreSubsystem->AreComponentsIgnoringCollisions(Prim1, Prim2); + } + + return false; +} + +void UVRExpansionFunctionLibrary::RemoveObjectCollisionIgnore(UObject* WorldContextObject, UPrimitiveComponent* Prim1) +{ + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem) + { + CollisionIgnoreSubsystem->RemoveComponentCollisionIgnoreState(Prim1); + } +} + +void UVRExpansionFunctionLibrary::RemoveActorCollisionIgnore(UObject* WorldContextObject, AActor* Actor1) +{ + TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents1; + Actor1->GetComponents<UPrimitiveComponent>(PrimitiveComponents1); + + UCollisionIgnoreSubsystem* CollisionIgnoreSubsystem = WorldContextObject->GetWorld()->GetSubsystem<UCollisionIgnoreSubsystem>(); + + if (CollisionIgnoreSubsystem) + { + for (int i = 0; i < PrimitiveComponents1.Num(); ++i) + { + CollisionIgnoreSubsystem->RemoveComponentCollisionIgnoreState(PrimitiveComponents1[i]); + } + } +} + +void UVRExpansionFunctionLibrary::LowPassFilter_RollingAverage(FVector lastAverage, FVector newSample, FVector& newAverage, int32 numSamples) +{ + newAverage = lastAverage; + newAverage -= newAverage / numSamples; + newAverage += newSample / numSamples; +} + +void UVRExpansionFunctionLibrary::LowPassFilter_Exponential(FVector lastAverage, FVector newSample, FVector& newAverage, float sampleFactor) +{ + newAverage = (newSample * sampleFactor) + ((1 - sampleFactor) * lastAverage); +} + +bool UVRExpansionFunctionLibrary::GetIsActorMovable(AActor* ActorToCheck) +{ + if (!ActorToCheck) + return false; + + if (USceneComponent* rootComp = ActorToCheck->GetRootComponent()) + { + return rootComp->Mobility == EComponentMobility::Movable; + } + + return false; +} + +void UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName(FName SlotType, AActor* Actor, FVector WorldLocation, float MaxRange, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* QueryController) +{ + bHadSlotInRange = false; + SlotWorldTransform = FTransform::Identity; + SlotName = NAME_None; + UHandSocketComponent* TargetHandSocket = nullptr; + + if (!Actor) + return; + + if (USceneComponent* rootComp = Actor->GetRootComponent()) + { + GetGripSlotInRangeByTypeName_Component(SlotType, rootComp, WorldLocation, MaxRange, bHadSlotInRange, SlotWorldTransform, SlotName, QueryController); + } +} + +void UVRExpansionFunctionLibrary::GetGripSlotInRangeByTypeName_Component(FName SlotType, USceneComponent* Component, FVector WorldLocation, float MaxRange, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* QueryController) +{ + bHadSlotInRange = false; + SlotWorldTransform = FTransform::Identity; + SlotName = NAME_None; + UHandSocketComponent* TargetHandSocket = nullptr; + + if (!Component) + return; + + FVector RelativeWorldLocation = Component->GetComponentTransform().InverseTransformPosition(WorldLocation); + MaxRange = FMath::Square(MaxRange); + + float ClosestSlotDistance = -0.1f; + + TArray<FName> SocketNames = Component->GetAllSocketNames(); + + FString GripIdentifier = SlotType.ToString(); + + int foundIndex = 0; + + for (int i = 0; i < SocketNames.Num(); ++i) + { + if (SocketNames[i].ToString().Contains(GripIdentifier, ESearchCase::IgnoreCase, ESearchDir::FromStart)) + { + float vecLen = FVector::DistSquared(RelativeWorldLocation, Component->GetSocketTransform(SocketNames[i], ERelativeTransformSpace::RTS_Component).GetLocation()); + + if (MaxRange >= vecLen && (ClosestSlotDistance < 0.0f || vecLen < ClosestSlotDistance)) + { + ClosestSlotDistance = vecLen; + bHadSlotInRange = true; + foundIndex = i; + } + } + } + + TArray<USceneComponent*> AttachChildren = Component->GetAttachChildren(); + + TArray<UHandSocketComponent*> RotationallyMatchingHandSockets; + for (USceneComponent* AttachChild : AttachChildren) + { + if (AttachChild && AttachChild->IsA<UHandSocketComponent>()) + { + if (UHandSocketComponent* SocketComp = Cast<UHandSocketComponent>(AttachChild)) + { + if (!SocketComp->bDisabled && SocketComp->SlotPrefix.ToString().Contains(GripIdentifier, ESearchCase::IgnoreCase, ESearchDir::FromStart)) + { + FVector SocketRelativeLocation = Component->GetComponentTransform().InverseTransformPosition(SocketComp->GetHandSocketTransform(QueryController, true).GetLocation()); + float vecLen = FVector::DistSquared(RelativeWorldLocation, SocketRelativeLocation); + //float vecLen = FVector::DistSquared(RelativeWorldLocation, SocketComp->GetRelativeLocation()); + if (SocketComp->bAlwaysInRange) + { + if (SocketComp->bMatchRotation) + { + RotationallyMatchingHandSockets.Add(SocketComp); + } + else + { + TargetHandSocket = SocketComp; + ClosestSlotDistance = vecLen; + bHadSlotInRange = true; + } + } + else + { + float RangeVal = (SocketComp->OverrideDistance > 0.0f ? FMath::Square(SocketComp->OverrideDistance) : MaxRange); + if (RangeVal >= vecLen && (ClosestSlotDistance < 0.0f || vecLen < ClosestSlotDistance)) + { + if (SocketComp->bMatchRotation) + { + RotationallyMatchingHandSockets.Add(SocketComp); + } + else + { + TargetHandSocket = SocketComp; + ClosestSlotDistance = vecLen; + bHadSlotInRange = true; + } + } + } + } + } + } + } + + // Try and sort through any hand sockets flagged as rotationally matched + if (RotationallyMatchingHandSockets.Num() > 0) + { + FQuat ControllerRot = QueryController->GetPivotTransform().GetRotation(); + //FQuat ClosestQuat = RotationallyMatchingHandSockets[0]->GetComponentTransform().GetRotation(); + FQuat ClosestQuat = RotationallyMatchingHandSockets[0]->GetHandSocketTransform(QueryController, true).GetRotation(); + + TargetHandSocket = RotationallyMatchingHandSockets[0]; + bHadSlotInRange = true; + ClosestSlotDistance = ControllerRot.AngularDistance(ClosestQuat); + for (int i = 1; i < RotationallyMatchingHandSockets.Num(); i++) + { + //float CheckDistance = ControllerRot.AngularDistance(RotationallyMatchingHandSockets[i]->GetComponentTransform().GetRotation()); + float CheckDistance = ControllerRot.AngularDistance(RotationallyMatchingHandSockets[i]->GetHandSocketTransform(QueryController, true).GetRotation()); + if (CheckDistance < ClosestSlotDistance) + { + TargetHandSocket = RotationallyMatchingHandSockets[i]; + ClosestSlotDistance = CheckDistance; + } + } + } + + if (bHadSlotInRange) + { + if (TargetHandSocket) + { + SlotWorldTransform = TargetHandSocket->GetHandSocketTransform(QueryController); + SlotName = TargetHandSocket->GetFName(); + SlotWorldTransform.SetScale3D(FVector(1.0f)); + } + else + { + SlotWorldTransform = Component->GetSocketTransform(SocketNames[foundIndex]); + SlotName = SocketNames[foundIndex]; + SlotWorldTransform.SetScale3D(FVector(1.0f)); + } + } +} + +FRotator UVRExpansionFunctionLibrary::GetHMDPureYaw(FRotator HMDRotation) +{ + return GetHMDPureYaw_I(HMDRotation); +} + +EBPHMDWornState UVRExpansionFunctionLibrary::GetIsHMDWorn() +{ + + if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice()) + { + return (EBPHMDWornState)GEngine->XRSystem->GetHMDDevice()->GetHMDWornState(); + } + + return EBPHMDWornState::Unknown; +} + +bool UVRExpansionFunctionLibrary::GetIsHMDConnected() +{ + return GEngine->XRSystem.IsValid() && GEngine->XRSystem->GetHMDDevice() && GEngine->XRSystem->GetHMDDevice()->IsHMDConnected(); +} + +EBPHMDDeviceType UVRExpansionFunctionLibrary::GetHMDType() +{ + if (GEngine && GEngine->XRSystem.IsValid()) + { + /* + if (GEngine && GEngine->XRSystem.IsValid()) + { + Ar.Logf(*GEngine->XRSystem->GetVersionString()); + } + */ + + // #TODO 4.19: Figure out a way to replace this...its broken now + /*IHeadMountedDisplay* HMDDevice = GEngine->XRSystem->GetHMDDevice(); + if (HMDDevice) + { + EHMDDeviceType::Type HMDDeviceType = HMDDevice->GetHMDDeviceType(); + + switch (HMDDeviceType) + { + case EHMDDeviceType::DT_ES2GenericStereoMesh: return EBPHMDDeviceType::DT_ES2GenericStereoMesh; break; + case EHMDDeviceType::DT_GearVR: return EBPHMDDeviceType::DT_GearVR; break; + case EHMDDeviceType::DT_Morpheus: return EBPHMDDeviceType::DT_Morpheus; break; + case EHMDDeviceType::DT_OculusRift: return EBPHMDDeviceType::DT_OculusRift; break; + case EHMDDeviceType::DT_SteamVR: return EBPHMDDeviceType::DT_SteamVR; break; + case EHMDDeviceType::DT_GoogleVR: return EBPHMDDeviceType::DT_GoogleVR; break; + } + + }*/ + + // There are no device type entries for these now.... + // Does the device type go away soon leaving only FNames? + // #TODO: 4.19? + // GearVR doesn't even return anything gut OculusHMD in FName currently. + + static const FName SteamVRSystemName(TEXT("SteamVR")); + static const FName OculusSystemName(TEXT("OculusHMD")); + static const FName PSVRSystemName(TEXT("PSVR")); + static const FName OSVRSystemName(TEXT("OSVR")); + static const FName GoogleARCoreSystemName(TEXT("FGoogleARCoreHMD")); + static const FName AppleARKitSystemName(TEXT("AppleARKit")); + static const FName GoogleVRHMDSystemName(TEXT("FGoogleVRHMD")); + + FName DeviceName(NAME_None); + DeviceName = GEngine->XRSystem->GetSystemName(); + + + if (DeviceName == FName(TEXT("SimpleHMD"))) + return EBPHMDDeviceType::DT_ES2GenericStereoMesh; + else if (DeviceName == SteamVRSystemName) + return EBPHMDDeviceType::DT_SteamVR; + else if (DeviceName == OculusSystemName) + return EBPHMDDeviceType::DT_OculusHMD; + else if (DeviceName == PSVRSystemName) + return EBPHMDDeviceType::DT_PSVR; + else if (DeviceName == OSVRSystemName) + return EBPHMDDeviceType::DT_SteamVR; + else if (DeviceName == GoogleARCoreSystemName) + return EBPHMDDeviceType::DT_GoogleARCore; + else if (DeviceName == AppleARKitSystemName) + return EBPHMDDeviceType::DT_AppleARKit; + else if (DeviceName == GoogleVRHMDSystemName) + return EBPHMDDeviceType::DT_GoogleVR; + } + + // Default to unknown + return EBPHMDDeviceType::DT_Unknown; +} + + +bool UVRExpansionFunctionLibrary::IsInVREditorPreviewOrGame() +{ +#if WITH_EDITOR + if (GIsEditor) + { + if (UEditorEngine* EdEngine = Cast<UEditorEngine>(GEngine)) + { + TOptional<FPlayInEditorSessionInfo> PlayInfo = EdEngine->GetPlayInEditorSessionInfo(); + if (PlayInfo.IsSet()) + { + return PlayInfo->OriginalRequestParams.SessionPreviewTypeOverride == EPlaySessionPreviewType::VRPreview; + } + else + { + return false; + } + } + } +#endif + + // Is not an editor build, default to true here + return true; +} + +bool UVRExpansionFunctionLibrary::IsInVREditorPreview() +{ +#if WITH_EDITOR + if (GIsEditor) + { + if (UEditorEngine* EdEngine = Cast<UEditorEngine>(GEngine)) + { + TOptional<FPlayInEditorSessionInfo> PlayInfo = EdEngine->GetPlayInEditorSessionInfo(); + if (PlayInfo.IsSet()) + { + return PlayInfo->OriginalRequestParams.SessionPreviewTypeOverride == EPlaySessionPreviewType::VRPreview; + } + else + { + return false; + } + } + } +#endif + + // Is not an editor build, default to false here + return false; +} + +void UVRExpansionFunctionLibrary::NonAuthorityMinimumAreaRectangle(class UObject* WorldContextObject, const TArray<FVector>& InVerts, const FVector& SampleSurfaceNormal, FVector& OutRectCenter, FRotator& OutRectRotation, float& OutSideLengthX, float& OutSideLengthY, bool bDebugDraw) +{ + float MinArea = -1.f; + float CurrentArea = -1.f; + FVector SupportVectorA, SupportVectorB; + FVector RectSideA, RectSideB; + float MinDotResultA, MinDotResultB, MaxDotResultA, MaxDotResultB; + FVector TestEdge; + float TestEdgeDot = 0.f; + FVector PolyNormal(0.f, 0.f, 1.f); + TArray<int32> PolyVertIndices; + + // Bail if we receive an empty InVerts array + if (InVerts.Num() == 0) + { + return; + } + + // Compute the approximate normal of the poly, using the direction of SampleSurfaceNormal for guidance + PolyNormal = (InVerts[InVerts.Num() / 3] - InVerts[0]) ^ (InVerts[InVerts.Num() * 2 / 3] - InVerts[InVerts.Num() / 3]); + if ((PolyNormal | SampleSurfaceNormal) < 0.f) + { + PolyNormal = -PolyNormal; + } + + // Transform the sample points to 2D + FMatrix SurfaceNormalMatrix = FRotationMatrix::MakeFromZX(PolyNormal, FVector(1.f, 0.f, 0.f)); + TArray<FVector> TransformedVerts; + OutRectCenter = FVector(0.f); + for (int32 Idx = 0; Idx < InVerts.Num(); ++Idx) + { + OutRectCenter += InVerts[Idx]; + TransformedVerts.Add(SurfaceNormalMatrix.InverseTransformVector(InVerts[Idx])); + } + OutRectCenter /= InVerts.Num(); + + // Compute the convex hull of the sample points + ConvexHull2D::ComputeConvexHull(TransformedVerts, PolyVertIndices); + + // Minimum area rectangle as computed by http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf + for (int32 Idx = 1; Idx < PolyVertIndices.Num() - 1; ++Idx) + { + SupportVectorA = (TransformedVerts[PolyVertIndices[Idx]] - TransformedVerts[PolyVertIndices[Idx - 1]]).GetSafeNormal(); + SupportVectorA.Z = 0.f; + SupportVectorB.X = -SupportVectorA.Y; + SupportVectorB.Y = SupportVectorA.X; + SupportVectorB.Z = 0.f; + MinDotResultA = MinDotResultB = MaxDotResultA = MaxDotResultB = 0.f; + + for (int TestVertIdx = 1; TestVertIdx < PolyVertIndices.Num(); ++TestVertIdx) + { + TestEdge = TransformedVerts[PolyVertIndices[TestVertIdx]] - TransformedVerts[PolyVertIndices[0]]; + TestEdgeDot = SupportVectorA | TestEdge; + if (TestEdgeDot < MinDotResultA) + { + MinDotResultA = TestEdgeDot; + } + else if (TestEdgeDot > MaxDotResultA) + { + MaxDotResultA = TestEdgeDot; + } + + TestEdgeDot = SupportVectorB | TestEdge; + if (TestEdgeDot < MinDotResultB) + { + MinDotResultB = TestEdgeDot; + } + else if (TestEdgeDot > MaxDotResultB) + { + MaxDotResultB = TestEdgeDot; + } + } + + CurrentArea = (MaxDotResultA - MinDotResultA) * (MaxDotResultB - MinDotResultB); + if (MinArea < 0.f || CurrentArea < MinArea) + { + MinArea = CurrentArea; + RectSideA = SupportVectorA * (MaxDotResultA - MinDotResultA); + RectSideB = SupportVectorB * (MaxDotResultB - MinDotResultB); + } + } + + RectSideA = SurfaceNormalMatrix.TransformVector(RectSideA); + RectSideB = SurfaceNormalMatrix.TransformVector(RectSideB); + OutRectRotation = FRotationMatrix::MakeFromZX(PolyNormal, RectSideA).Rotator(); + OutSideLengthX = RectSideA.Size(); + OutSideLengthY = RectSideB.Size(); + +#if ENABLE_DRAW_DEBUG + if (bDebugDraw) + { + UWorld* World = (WorldContextObject) ? GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull) : nullptr; + if (World != nullptr) + { + DrawDebugSphere(World, OutRectCenter, 10.f, 12, FColor::Yellow, true); + DrawDebugCoordinateSystem(World, OutRectCenter, SurfaceNormalMatrix.Rotator(), 100.f, true); + DrawDebugLine(World, OutRectCenter - RectSideA * 0.5f + FVector(0, 0, 10.f), OutRectCenter + RectSideA * 0.5f + FVector(0, 0, 10.f), FColor::Green, true, -1, 0, 5.f); + DrawDebugLine(World, OutRectCenter - RectSideB * 0.5f + FVector(0, 0, 10.f), OutRectCenter + RectSideB * 0.5f + FVector(0, 0, 10.f), FColor::Blue, true, -1, 0, 5.f); + } + else + { + FFrame::KismetExecutionMessage(TEXT("WorldContext required for MinimumAreaRectangle to draw a debug visualization."), ELogVerbosity::Warning); + } + } +#endif +} + +bool UVRExpansionFunctionLibrary::EqualEqual_FBPActorGripInformation(const FBPActorGripInformation& A, const FBPActorGripInformation& B) +{ + return A == B; +} + +bool UVRExpansionFunctionLibrary::IsActiveGrip(const FBPActorGripInformation& Grip) +{ + return Grip.IsActive(); +} + +FTransform_NetQuantize UVRExpansionFunctionLibrary::MakeTransform_NetQuantize(FVector Translation, FRotator Rotation, FVector Scale) +{ + return FTransform_NetQuantize(Rotation, Translation, Scale); +} + +void UVRExpansionFunctionLibrary::BreakTransform_NetQuantize(const FTransform_NetQuantize& InTransform, FVector& Translation, FRotator& Rotation, FVector& Scale) +{ + Translation = InTransform.GetLocation(); + Rotation = InTransform.Rotator(); + Scale = InTransform.GetScale3D(); +} + +FTransform_NetQuantize UVRExpansionFunctionLibrary::Conv_TransformToTransformNetQuantize(const FTransform& InTransform) +{ + return FTransform_NetQuantize(InTransform); +} + +UGripMotionControllerComponent* UVRExpansionFunctionLibrary::Conv_GripPairToMotionController(const FBPGripPair& GripPair) +{ + return GripPair.HoldingController; +} + +uint8 UVRExpansionFunctionLibrary::Conv_GripPairToGripID(const FBPGripPair& GripPair) +{ + return GripPair.GripID; +} + +FVector_NetQuantize UVRExpansionFunctionLibrary::Conv_FVectorToFVectorNetQuantize(const FVector& InVector) +{ + return FVector_NetQuantize(InVector); +} + +FVector_NetQuantize UVRExpansionFunctionLibrary::MakeVector_NetQuantize(FVector InVector) +{ + return FVector_NetQuantize(InVector); +} + +FVector_NetQuantize10 UVRExpansionFunctionLibrary::Conv_FVectorToFVectorNetQuantize10(const FVector& InVector) +{ + return FVector_NetQuantize10(InVector); +} + +FVector_NetQuantize10 UVRExpansionFunctionLibrary::MakeVector_NetQuantize10(FVector InVector) +{ + return FVector_NetQuantize10(InVector); +} + +FVector_NetQuantize100 UVRExpansionFunctionLibrary::Conv_FVectorToFVectorNetQuantize100(const FVector& InVector) +{ + return FVector_NetQuantize100(InVector); +} + +FVector_NetQuantize100 UVRExpansionFunctionLibrary::MakeVector_NetQuantize100(FVector InVector) +{ + return FVector_NetQuantize100(InVector); +} + +USceneComponent* UVRExpansionFunctionLibrary::AddSceneComponentByClass(UObject* Outer, TSubclassOf<USceneComponent> Class, const FTransform& ComponentRelativeTransform) +{ + if (Class != nullptr && Outer != nullptr) + { + USceneComponent* Component = NewObject<USceneComponent>(Outer, *Class); + if (Component != nullptr) + { + if (USceneComponent* ParentComp = Cast<USceneComponent>(Outer)) + Component->SetupAttachment(ParentComp); + + Component->RegisterComponent(); + Component->SetRelativeTransform(ComponentRelativeTransform); + + return Component; + } + else + { + return nullptr; + } + } + + return nullptr; +} + +void UVRExpansionFunctionLibrary::SmoothUpdateLaserSpline(USplineComponent* LaserSplineComponent, TArray<USplineMeshComponent*> LaserSplineMeshComponents, FVector InStartLocation, FVector InEndLocation, FVector InForward, float LaserRadius) +{ + if (LaserSplineComponent == nullptr) + return; + + LaserSplineComponent->ClearSplinePoints(); + + const FVector SmoothLaserDirection = InEndLocation - InStartLocation; + float Distance = SmoothLaserDirection.Size(); + const FVector StraightLaserEndLocation = InStartLocation + (InForward * Distance); + const int32 NumLaserSplinePoints = LaserSplineMeshComponents.Num(); + + LaserSplineComponent->AddSplinePoint(InStartLocation, ESplineCoordinateSpace::World, false); + for (int32 Index = 1; Index < NumLaserSplinePoints; Index++) + { + float Alpha = (float)Index / (float)NumLaserSplinePoints; + Alpha = FMath::Sin(Alpha * PI * 0.5f); + const FVector PointOnStraightLaser = FMath::Lerp(InStartLocation, StraightLaserEndLocation, Alpha); + const FVector PointOnSmoothLaser = FMath::Lerp(InStartLocation, InEndLocation, Alpha); + const FVector PointBetweenLasers = FMath::Lerp(PointOnStraightLaser, PointOnSmoothLaser, Alpha); + LaserSplineComponent->AddSplinePoint(PointBetweenLasers, ESplineCoordinateSpace::World, false); + } + LaserSplineComponent->AddSplinePoint(InEndLocation, ESplineCoordinateSpace::World, false); + + // Update all the segments of the spline + LaserSplineComponent->UpdateSpline(); + + const float LaserPointerRadius = LaserRadius; + Distance *= 0.0001f; + for (int32 Index = 0; Index < NumLaserSplinePoints; Index++) + { + USplineMeshComponent* SplineMeshComponent = LaserSplineMeshComponents[Index]; + check(SplineMeshComponent != nullptr); + + FVector StartLoc, StartTangent, EndLoc, EndTangent; + LaserSplineComponent->GetLocationAndTangentAtSplinePoint(Index, StartLoc, StartTangent, ESplineCoordinateSpace::Local); + LaserSplineComponent->GetLocationAndTangentAtSplinePoint(Index + 1, EndLoc, EndTangent, ESplineCoordinateSpace::Local); + + const float AlphaIndex = (float)Index / (float)NumLaserSplinePoints; + const float AlphaDistance = Distance * AlphaIndex; + float Radius = LaserPointerRadius * ((AlphaIndex * AlphaDistance) + 1); + FVector2D LaserScale(Radius, Radius); + SplineMeshComponent->SetStartScale(LaserScale, false); + + const float NextAlphaIndex = (float)(Index + 1) / (float)NumLaserSplinePoints; + const float NextAlphaDistance = Distance * NextAlphaIndex; + Radius = LaserPointerRadius * ((NextAlphaIndex * NextAlphaDistance) + 1); + LaserScale = FVector2D(Radius, Radius); + SplineMeshComponent->SetEndScale(LaserScale, false); + + SplineMeshComponent->SetStartAndEnd(StartLoc, StartTangent, EndLoc, EndTangent, true); + } + +} + +bool UVRExpansionFunctionLibrary::MatchesAnyTagsWithDirectParentTag(FGameplayTag DirectParentTag, const FGameplayTagContainer& BaseContainer, const FGameplayTagContainer& OtherContainer) +{ + TArray<FGameplayTag> BaseContainerTags; + BaseContainer.GetGameplayTagArray(BaseContainerTags); + + for (const FGameplayTag& OtherTag : BaseContainerTags) + { + if (OtherTag.RequestDirectParent().MatchesTagExact(DirectParentTag)) + { + if (OtherContainer.HasTagExact(OtherTag)) + return true; + } + } + + return false; +} + +bool UVRExpansionFunctionLibrary::GetFirstGameplayTagWithExactParent(FGameplayTag DirectParentTag, const FGameplayTagContainer& BaseContainer, FGameplayTag& FoundTag) +{ + TArray<FGameplayTag> BaseContainerTags; + BaseContainer.GetGameplayTagArray(BaseContainerTags); + + for (const FGameplayTag& OtherTag : BaseContainerTags) + { + if (OtherTag.RequestDirectParent().MatchesTagExact(DirectParentTag)) + { + FoundTag = OtherTag; + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPlugin.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPlugin.cpp new file mode 100644 index 0000000..ebc1072 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPlugin.cpp @@ -0,0 +1,58 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRExpansionPlugin.h" + +#include "Grippables/GrippablePhysicsReplication.h" + +#include "VRGlobalSettings.h" +#include "ISettingsContainer.h" +#include "ISettingsModule.h" +#include "ISettingsSection.h" + +#define LOCTEXT_NAMESPACE "FVRExpansionPluginModule" + +void FVRExpansionPluginModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + RegisterSettings(); + +#if WITH_CHAOS + FPhysScene_Chaos::PhysicsReplicationFactory = MakeShared<IPhysicsReplicationFactoryVR>(); +#endif +} + +void FVRExpansionPluginModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + UnregisterSettings(); +} + +void FVRExpansionPluginModule::RegisterSettings() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings")) + { + // Create the new category + ISettingsContainerPtr SettingsContainer = SettingsModule->GetContainer("Project"); + + + SettingsModule->RegisterSettings("Project", "Plugins", "VRExpansionPlugin", + LOCTEXT("VRExpansionSettingsName", "VRExpansion Settings"), + LOCTEXT("VRExpansionSettingsDescription", "Configure global settings for the VRExpansionPlugin"), + GetMutableDefault<UVRGlobalSettings>()); + } +} + +void FVRExpansionPluginModule::UnregisterSettings() +{ + // Ensure to unregister all of your registered settings here, hot-reload would + // otherwise yield unexpected results. + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "VRExpansionPlugin"); + } +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FVRExpansionPluginModule, VRExpansionPlugin) \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPluginPrivatePCH.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPluginPrivatePCH.h new file mode 100644 index 0000000..7abba6d --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRExpansionPluginPrivatePCH.h @@ -0,0 +1,20 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +//#include "VRExpansionPlugin.h" +//#include "EngineMinimal.h" +//#include "VRBPDatatypes.h" +//#include "GripMotionControllerComponent.h" +//#include "VRExpansionFunctionLibrary.h" +//#include "ReplicatedVRCameraComponent.h" +//#include "ParentRelativeAttachmentComponent.h" +//#include "VRRootComponent.h" +//#include "VRBaseCharacterMovementComponent.h" +//#include "VRCharacterMovementComponent.h" +//#include "VRCharacter.h" +//#include "VRPathFollowingComponent.h" +//#include "VRPlayerController.h" +//#include "VRGripInterface.h" +//#include "Runtime/Launch/Resources/Version.h" + +// You should place include statements to your module's private header files here. You only need to +// add includes for headers that are used in most of your module's source files though. \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGestureComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGestureComponent.cpp new file mode 100644 index 0000000..ffd191b --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGestureComponent.cpp @@ -0,0 +1,738 @@ +#include "VRGestureComponent.h" +#include "VRBaseCharacter.h" +#include "Components/SplineMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Components/LineBatchComponent.h" +#include "DrawDebugHelpers.h" +#include "Algo/Reverse.h" +#include "TimerManager.h" + +DECLARE_CYCLE_STAT(TEXT("TickGesture ~ TickingGesture"), STAT_TickGesture, STATGROUP_TickGesture); + +UVRGestureComponent::UVRGestureComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = false; + //PrimaryComponentTick.bStartWithTickEnabled = false; + //PrimaryComponentTick.TickGroup = TG_PrePhysics; + //PrimaryComponentTick.bTickEvenWhenPaused = false; + + maxSlope = 3;// INT_MAX; + //globalThreshold = 10.0f; + SameSampleTolerance = 0.1f; + bGestureChanged = false; + MirroringHand = EVRGestureMirrorMode::GES_NoMirror; + bDrawSplinesCurved = true; + bGetGestureInWorldSpace = true; + SplineMeshScaler = FVector2D(1.f); +} + +void UGesturesDatabase::FillSplineWithGesture(FVRGesture &Gesture, USplineComponent * SplineComponent, bool bCenterPointsOnSpline, bool bScaleToBounds, float OptionalBounds, bool bUseCurvedPoints, bool bFillInSplineMeshComponents, UStaticMesh * Mesh, UMaterial * MeshMat) +{ + if (!SplineComponent || Gesture.Samples.Num() < 2) + return; + + UWorld* InWorld = GEngine->GetWorldFromContextObject(SplineComponent, EGetWorldErrorMode::LogAndReturnNull); + + if (!InWorld) + return; + + SplineComponent->ClearSplinePoints(false); + + FVector PointOffset = FVector::ZeroVector; + float Scaler = 1.0f; + if (bScaleToBounds && OptionalBounds > 0.0f) + { + Scaler = OptionalBounds / Gesture.GestureSize.GetSize().GetMax(); + } + + if (bCenterPointsOnSpline) + { + PointOffset = -Gesture.GestureSize.GetCenter(); + } + + int curIndex = 0; + for (int i = Gesture.Samples.Num() - 1; i >= 0; --i) + { + SplineComponent->AddSplinePoint((Gesture.Samples[i] + PointOffset) * Scaler, ESplineCoordinateSpace::Local, false); + curIndex++; + SplineComponent->SetSplinePointType(curIndex, bUseCurvedPoints ? ESplinePointType::Curve : ESplinePointType::Linear, false); + } + + // Update spline now + SplineComponent->UpdateSpline(); + + if (bFillInSplineMeshComponents && Mesh != nullptr && MeshMat != nullptr) + { + TArray<USplineMeshComponent *> CurrentSplineChildren; + + TArray<USceneComponent*> Children; + SplineComponent->GetChildrenComponents(false, Children); + for (auto Child : Children) + { + USplineMeshComponent* SplineMesh = Cast<USplineMeshComponent>(Child); + if (SplineMesh != nullptr && IsValid(SplineMesh)) + { + CurrentSplineChildren.Add(SplineMesh); + } + } + + if (CurrentSplineChildren.Num() > SplineComponent->GetNumberOfSplinePoints() - 1) + { + int diff = CurrentSplineChildren.Num() - (CurrentSplineChildren.Num() - (SplineComponent->GetNumberOfSplinePoints() -1)); + + for (int i = CurrentSplineChildren.Num()- 1; i >= diff; --i) + { + if (!CurrentSplineChildren[i]->IsBeingDestroyed()) + { + CurrentSplineChildren[i]->SetVisibility(false); + CurrentSplineChildren[i]->Modify(); + CurrentSplineChildren[i]->DestroyComponent(); + CurrentSplineChildren.RemoveAt(i); + } + } + } + else + { + for (int i = CurrentSplineChildren.Num(); i < SplineComponent->GetNumberOfSplinePoints() -1; ++i) + { + USplineMeshComponent * newSplineMesh = NewObject<USplineMeshComponent>(SplineComponent); + + newSplineMesh->RegisterComponentWithWorld(InWorld); + newSplineMesh->SetMobility(EComponentMobility::Movable); + CurrentSplineChildren.Add(newSplineMesh); + newSplineMesh->SetStaticMesh(Mesh); + newSplineMesh->SetMaterial(0, (UMaterialInterface*)MeshMat); + + newSplineMesh->AttachToComponent(SplineComponent, FAttachmentTransformRules::SnapToTargetIncludingScale); + newSplineMesh->SetVisibility(true); + } + } + + + for(int i=0; i<SplineComponent->GetNumberOfSplinePoints() - 1; i++) + { + CurrentSplineChildren[i]->SetStartAndEnd(SplineComponent->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::Local), + SplineComponent->GetTangentAtSplinePoint(i, ESplineCoordinateSpace::Local), + SplineComponent->GetLocationAtSplinePoint(i + 1, ESplineCoordinateSpace::Local), + SplineComponent->GetTangentAtSplinePoint(i + 1, ESplineCoordinateSpace::Local), + true); + } + } + +} + +void UVRGestureComponent::BeginRecording(bool bRunDetection, bool bFlattenGesture, bool bDrawGesture, bool bDrawAsSpline, int SamplingHTZ, int SampleBufferSize, float ClampingTolerance) +{ + RecordingBufferSize = SampleBufferSize; + RecordingDelta = 1.0f / SamplingHTZ; + RecordingClampingTolerance = ClampingTolerance; + bDrawRecordingGesture = bDrawGesture; + bDrawRecordingGestureAsSpline = bDrawAsSpline; + bRecordingFlattenGesture = bFlattenGesture; + GestureLog.GestureSize.Init(); + + // Reinit the drawing spline + if (!bDrawAsSpline || !bDrawGesture) + RecordingGestureDraw.Clear(); // Not drawing or not as a spline, remove the components if they exist + else + { + RecordingGestureDraw.Reset(); // Otherwise just clear points and hide mesh components + + if (RecordingGestureDraw.SplineComponent == nullptr) + { + RecordingGestureDraw.SplineComponent = NewObject<USplineComponent>(GetAttachParent()); + RecordingGestureDraw.SplineComponent->RegisterComponentWithWorld(GetWorld()); + RecordingGestureDraw.SplineComponent->SetMobility(EComponentMobility::Movable); + RecordingGestureDraw.SplineComponent->AttachToComponent(GetAttachParent(), FAttachmentTransformRules::KeepRelativeTransform); + RecordingGestureDraw.SplineComponent->ClearSplinePoints(true); + } + } + + // Reset does the reserve already + GestureLog.Samples.Reset(RecordingBufferSize); + + CurrentState = bRunDetection ? EVRGestureState::GES_Detecting : EVRGestureState::GES_Recording; + + if (TargetCharacter != nullptr) + { + OriginatingTransform = TargetCharacter->OffsetComponentToWorld; + } + else if (AVRBaseCharacter * own = Cast<AVRBaseCharacter>(GetOwner())) + { + TargetCharacter = own; + OriginatingTransform = TargetCharacter->OffsetComponentToWorld; + } + else + OriginatingTransform = this->GetComponentTransform(); + + StartVector = OriginatingTransform.InverseTransformPosition(this->GetComponentLocation()); + this->SetComponentTickEnabled(true); + + if (!TickGestureTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().SetTimer(TickGestureTimer_Handle, this, &UVRGestureComponent::TickGesture, RecordingDelta, true); +} + +void UVRGestureComponent::CaptureGestureFrame() +{ + FVector NewSample = OriginatingTransform.InverseTransformPosition(this->GetComponentLocation()) - StartVector; + + if (bRecordingFlattenGesture) + NewSample.X = 0; + + if (RecordingClampingTolerance > 0.0f) + { + NewSample.X = FMath::GridSnap(NewSample.X, RecordingClampingTolerance); + NewSample.Y = FMath::GridSnap(NewSample.Y, RecordingClampingTolerance); + NewSample.Z = FMath::GridSnap(NewSample.Z, RecordingClampingTolerance); + } + + // Add in newest sample at beginning (reverse order) + if (NewSample != FVector::ZeroVector && (GestureLog.Samples.Num() < 1 || !GestureLog.Samples[0].Equals(NewSample, SameSampleTolerance))) + { + bool bClearLatestSpline = false; + // Pop off oldest sample + if (GestureLog.Samples.Num() >= RecordingBufferSize) + { + GestureLog.Samples.Pop(false); + bClearLatestSpline = true; + } + + GestureLog.GestureSize.Max.X = FMath::Max(NewSample.X, GestureLog.GestureSize.Max.X); + GestureLog.GestureSize.Max.Y = FMath::Max(NewSample.Y, GestureLog.GestureSize.Max.Y); + GestureLog.GestureSize.Max.Z = FMath::Max(NewSample.Z, GestureLog.GestureSize.Max.Z); + + GestureLog.GestureSize.Min.X = FMath::Min(NewSample.X, GestureLog.GestureSize.Min.X); + GestureLog.GestureSize.Min.Y = FMath::Min(NewSample.Y, GestureLog.GestureSize.Min.Y); + GestureLog.GestureSize.Min.Z = FMath::Min(NewSample.Z, GestureLog.GestureSize.Min.Z); + + + if (bDrawRecordingGesture && bDrawRecordingGestureAsSpline && SplineMesh != nullptr && SplineMaterial != nullptr) + { + + if (bClearLatestSpline) + RecordingGestureDraw.ClearLastPoint(); + + RecordingGestureDraw.SplineComponent->AddSplinePoint(NewSample, ESplineCoordinateSpace::Local, false); + int SplineIndex = RecordingGestureDraw.SplineComponent->GetNumberOfSplinePoints() - 1; + RecordingGestureDraw.SplineComponent->SetSplinePointType(SplineIndex, bDrawSplinesCurved ? ESplinePointType::Curve : ESplinePointType::Linear, true); + + bool bFoundEmptyMesh = false; + USplineMeshComponent * MeshComp = nullptr; + int MeshIndex = 0; + + for (int i = 0; i < RecordingGestureDraw.SplineMeshes.Num(); i++) + { + MeshIndex = i; + MeshComp = RecordingGestureDraw.SplineMeshes[i]; + if (MeshComp == nullptr) + { + RecordingGestureDraw.SplineMeshes[i] = NewObject<USplineMeshComponent>(RecordingGestureDraw.SplineComponent); + MeshComp = RecordingGestureDraw.SplineMeshes[i]; + + MeshComp->RegisterComponentWithWorld(GetWorld()); + MeshComp->SetMobility(EComponentMobility::Movable); + MeshComp->SetStaticMesh(SplineMesh); + MeshComp->SetMaterial(0, (UMaterialInterface*)SplineMaterial); + bFoundEmptyMesh = true; + break; + } + else if (!MeshComp->IsVisible()) + { + bFoundEmptyMesh = true; + break; + } + } + + if (!bFoundEmptyMesh) + { + USplineMeshComponent * newSplineMesh = NewObject<USplineMeshComponent>(RecordingGestureDraw.SplineComponent); + MeshComp = newSplineMesh; + MeshComp->RegisterComponentWithWorld(GetWorld()); + MeshComp->SetMobility(EComponentMobility::Movable); + RecordingGestureDraw.SplineMeshes.Add(MeshComp); + MeshIndex = RecordingGestureDraw.SplineMeshes.Num() - 1; + MeshComp->SetStaticMesh(SplineMesh); + MeshComp->SetMaterial(0, (UMaterialInterface*)SplineMaterial); + if (!bGetGestureInWorldSpace && TargetCharacter) + MeshComp->AttachToComponent(TargetCharacter->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + } + + if (MeshComp != nullptr) + { + // Fill in last mesh component tangent and end pos + if (RecordingGestureDraw.LastIndexSet != MeshIndex && RecordingGestureDraw.SplineMeshes[RecordingGestureDraw.LastIndexSet] != nullptr) + { + RecordingGestureDraw.SplineMeshes[RecordingGestureDraw.LastIndexSet]->SetEndPosition(NewSample, false); + RecordingGestureDraw.SplineMeshes[RecordingGestureDraw.LastIndexSet]->SetEndTangent(RecordingGestureDraw.SplineComponent->GetTangentAtSplinePoint(SplineIndex, ESplineCoordinateSpace::Local), true); + } + + MeshComp->SetStartScale(SplineMeshScaler); + MeshComp->SetEndScale(SplineMeshScaler); + + MeshComp->SetStartAndEnd(NewSample, + RecordingGestureDraw.SplineComponent->GetTangentAtSplinePoint(SplineIndex, ESplineCoordinateSpace::Local), + NewSample, + FVector::ZeroVector, + true); + + if (bGetGestureInWorldSpace) + MeshComp->SetWorldLocationAndRotation(OriginatingTransform.TransformPosition(StartVector), OriginatingTransform.GetRotation()); + else + MeshComp->SetRelativeLocationAndRotation(/*OriginatingTransform.TransformPosition(*/StartVector/*)*/, FQuat::Identity/*OriginatingTransform.GetRotation()*/); + + RecordingGestureDraw.LastIndexSet = MeshIndex; + MeshComp->SetVisibility(true); + } + + } + + GestureLog.Samples.Insert(NewSample, 0); + bGestureChanged = true; + } +} + +void UVRGestureComponent::TickGesture() +{ + SCOPE_CYCLE_COUNTER(STAT_TickGesture); + + switch (CurrentState) + { + case EVRGestureState::GES_Detecting: + { + CaptureGestureFrame(); + RecognizeGesture(GestureLog); + bGestureChanged = false; + }break; + + case EVRGestureState::GES_Recording: + { + CaptureGestureFrame(); + }break; + + case EVRGestureState::GES_None: + default: {}break; + } + + if (bDrawRecordingGesture) + { + if (!bDrawRecordingGestureAsSpline) + { + FTransform DrawTransform = FTransform(StartVector) * OriginatingTransform; + // Setting the lifetime to the recording htz now, should remove the flicker. + DrawDebugGesture(this, DrawTransform, GestureLog, FColor::White, false, 0, RecordingDelta, 0.0f); + } + } +} + +void UVRGestureComponent::RecognizeGesture(FVRGesture inputGesture) +{ + if (!GesturesDB || inputGesture.Samples.Num() < 1 || !bGestureChanged) + return; + + float minDist = MAX_FLT; + + int OutGestureIndex = -1; + bool bMirrorGesture = false; + + FVector Size = inputGesture.GestureSize.GetSize(); + float Scaler = GesturesDB->TargetGestureScale / Size.GetMax(); + float FinalScaler = Scaler; + + for (int i = 0; i < GesturesDB->Gestures.Num(); i++) + { + FVRGesture &exampleGesture = GesturesDB->Gestures[i]; + + if (!exampleGesture.GestureSettings.bEnabled || exampleGesture.Samples.Num() < 1 || inputGesture.Samples.Num() < exampleGesture.GestureSettings.Minimum_Gesture_Length) + continue; + + FinalScaler = exampleGesture.GestureSettings.bEnableScaling ? Scaler : 1.f; + + bMirrorGesture = (MirroringHand != EVRGestureMirrorMode::GES_NoMirror && MirroringHand != EVRGestureMirrorMode::GES_MirrorBoth && MirroringHand == exampleGesture.GestureSettings.MirrorMode); + + if (GetGestureDistance(inputGesture.Samples[0] * FinalScaler, exampleGesture.Samples[0], bMirrorGesture) < FMath::Square(exampleGesture.GestureSettings.firstThreshold)) + { + float d = dtw(inputGesture, exampleGesture, bMirrorGesture, FinalScaler) / (exampleGesture.Samples.Num()); + if (d < minDist && d < FMath::Square(exampleGesture.GestureSettings.FullThreshold)) + { + minDist = d; + OutGestureIndex = i; + } + } + else if (exampleGesture.GestureSettings.MirrorMode == EVRGestureMirrorMode::GES_MirrorBoth) + { + bMirrorGesture = true; + if (GetGestureDistance(inputGesture.Samples[0] * FinalScaler, exampleGesture.Samples[0], bMirrorGesture) < FMath::Square(exampleGesture.GestureSettings.firstThreshold)) + { + float d = dtw(inputGesture, exampleGesture, bMirrorGesture, FinalScaler) / (exampleGesture.Samples.Num()); + if (d < minDist && d < FMath::Square(exampleGesture.GestureSettings.FullThreshold)) + { + minDist = d; + OutGestureIndex = i; + } + } + } + + /*if (exampleGesture.MirrorMode == EVRGestureMirrorMode::GES_MirrorBoth) + { + bMirrorGesture = true; + + if (GetGestureDistance(inputGesture.Samples[0], exampleGesture.Samples[0], bMirrorGesture) < FMath::Square(exampleGesture.GestureSettings.firstThreshold)) + { + float d = dtw(inputGesture, exampleGesture, bMirrorGesture) / (exampleGesture.Samples.Num()); + if (d < minDist && d < FMath::Square(exampleGesture.GestureSettings.FullThreshold)) + { + minDist = d; + OutGestureIndex = i; + } + } + }*/ + } + + if (/*minDist < FMath::Square(globalThreshold) && */OutGestureIndex != -1) + { + OnGestureDetected(GesturesDB->Gestures[OutGestureIndex].GestureType, /*minDist,*/ GesturesDB->Gestures[OutGestureIndex].Name, OutGestureIndex, GesturesDB, Size); + OnGestureDetected_Bind.Broadcast(GesturesDB->Gestures[OutGestureIndex].GestureType, /*minDist,*/ GesturesDB->Gestures[OutGestureIndex].Name, OutGestureIndex, GesturesDB, Size); + ClearRecording(); // Clear the recording out, we don't want to detect this gesture again with the same data + RecordingGestureDraw.Reset(); + } +} + +float UVRGestureComponent::dtw(FVRGesture seq1, FVRGesture seq2, bool bMirrorGesture, float Scaler) +{ + + // #TODO: Skip copying the array and reversing it in the future, we only ever use the reversed value. + // So pre-reverse it and keep it stored like that on init. When we do the initial sample we can check off of the first index instead of last then + + // Should also be able to get SizeSquared for values and compared to squared thresholds instead of doing the full SQRT calc. + + // Getting number of average samples recorded over of a gesture (top down) may be able to achieve a basic % completed check + // to see how far into detecting a gesture we are, this would require ignoring the last position threshold though.... + + int RowCount = seq1.Samples.Num() + 1; + int ColumnCount = seq2.Samples.Num() + 1; + + TArray<float> LookupTable; + LookupTable.AddZeroed(ColumnCount * RowCount); + + TArray<int> SlopeI; + SlopeI.AddZeroed(ColumnCount * RowCount); + TArray<int> SlopeJ; + SlopeJ.AddZeroed(ColumnCount * RowCount); + + for (int i = 1; i < (ColumnCount * RowCount); i++) + { + LookupTable[i] = MAX_FLT; + } + // Don't need to do this, it is already handled by add zeroed + //tab[0, 0] = 0; + + int icol = 0, icolneg = 0; + + // Dynamic computation of the DTW matrix. + for (int i = 1; i < RowCount; i++) + { + for (int j = 1; j < ColumnCount; j++) + { + icol = i * ColumnCount; + icolneg = icol - ColumnCount;// (i - 1) * ColumnCount; + + if ( + LookupTable[icol + (j - 1)] < LookupTable[icolneg + (j - 1)] && + LookupTable[icol + (j - 1)] < LookupTable[icolneg + j] && + SlopeI[icol + (j - 1)] < maxSlope) + { + LookupTable[icol + j] = GetGestureDistance(seq1.Samples[i - 1] * Scaler, seq2.Samples[j - 1], bMirrorGesture) + LookupTable[icol + j - 1]; + SlopeI[icol + j] = SlopeJ[icol + j - 1] + 1; + SlopeJ[icol + j] = 0; + } + else if ( + LookupTable[icolneg + j] < LookupTable[icolneg + j - 1] && + LookupTable[icolneg + j] < LookupTable[icol + j - 1] && + SlopeJ[icolneg + j] < maxSlope) + { + LookupTable[icol + j] = GetGestureDistance(seq1.Samples[i - 1] * Scaler, seq2.Samples[j - 1], bMirrorGesture) + LookupTable[icolneg + j]; + SlopeI[icol + j] = 0; + SlopeJ[icol + j] = SlopeJ[icolneg + j] + 1; + } + else + { + LookupTable[icol + j] = GetGestureDistance(seq1.Samples[i - 1] * Scaler, seq2.Samples[j - 1], bMirrorGesture) + LookupTable[icolneg + j - 1]; + SlopeI[icol + j] = 0; + SlopeJ[icol + j] = 0; + } + } + } + + // Find best between seq2 and an ending (postfix) of seq1. + float bestMatch = FLT_MAX; + + for (int i = 1; i < seq1.Samples.Num() + 1/* - seq2.Minimum_Gesture_Length*/; i++) + { + if (LookupTable[(i*ColumnCount) + seq2.Samples.Num()] < bestMatch) + bestMatch = LookupTable[(i*ColumnCount) + seq2.Samples.Num()]; + } + + return bestMatch; +} + +void UVRGestureComponent::DrawDebugGesture(UObject* WorldContextObject, FTransform &StartTransform, FVRGesture GestureToDraw, FColor const& Color, bool bPersistentLines, uint8 DepthPriority, float LifeTime, float Thickness) +{ +#if ENABLE_DRAW_DEBUG + + UWorld* InWorld = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + + if (InWorld != nullptr) + { + // no debug line drawing on dedicated server + if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer && GestureToDraw.Samples.Num() > 1) + { + bool bMirrorGesture = (MirroringHand != EVRGestureMirrorMode::GES_NoMirror && MirroringHand == GestureToDraw.GestureSettings.MirrorMode); + FVector MirrorVector = FVector(1.f, -1.f, 1.f); // Only mirroring on Y axis to flip Left/Right + + // this means foreground lines can't be persistent + ULineBatchComponent* const LineBatcher = (InWorld ? ((DepthPriority == SDPG_Foreground) ? InWorld->ForegroundLineBatcher : ((bPersistentLines || (LifeTime > 0.f)) ? InWorld->PersistentLineBatcher : InWorld->LineBatcher)) : NULL); + + if (LineBatcher != NULL) + { + float const LineLifeTime = (LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime; + + TArray<FBatchedLine> Lines; + FBatchedLine Line; + Line.Color = Color; + Line.Thickness = Thickness; + Line.RemainingLifeTime = LineLifeTime; + Line.DepthPriority = DepthPriority; + + FVector FirstLoc = bMirrorGesture ? GestureToDraw.Samples[GestureToDraw.Samples.Num() - 1] * MirrorVector : GestureToDraw.Samples[GestureToDraw.Samples.Num() - 1]; + + for (int i = GestureToDraw.Samples.Num() - 2; i >= 0; --i) + { + Line.Start = bMirrorGesture ? GestureToDraw.Samples[i] * MirrorVector : GestureToDraw.Samples[i]; + + Line.End = FirstLoc; + FirstLoc = Line.Start; + + Line.End = StartTransform.TransformPosition(Line.End); + Line.Start = StartTransform.TransformPosition(Line.Start); + + Lines.Add(Line); + } + + LineBatcher->DrawLines(Lines); + } + } + } +#endif +} + +void UGesturesDatabase::RecalculateGestures(bool bScaleToDatabase) +{ + for (int i = 0; i < Gestures.Num(); ++i) + { + Gestures[i].CalculateSizeOfGesture(bScaleToDatabase, TargetGestureScale); + } +} + +bool UGesturesDatabase::ImportSplineAsGesture(USplineComponent * HostSplineComponent, FString GestureName, bool bKeepSplineCurves, float SegmentLen, bool bScaleToDatabase) +{ + FVRGesture NewGesture; + + if (HostSplineComponent->GetNumberOfSplinePoints() < 2) + return false; + + NewGesture.Name = GestureName; + + FVector FirstPointPos = HostSplineComponent->GetLocationAtSplinePoint(0, ESplineCoordinateSpace::Local); + + float LastDistance = 0.f; + float ThisDistance = 0.f; + FVector LastDistanceV; + FVector ThisDistanceV; + FVector DistNormal; + float DistAlongSegment = 0.f; + + // Realign to xForward on the gesture, normally splines lay out as X to the right + FTransform Realignment = FTransform(FRotator(0.f, 90.f, 0.f), -FirstPointPos); + + // Prefill the first point + NewGesture.Samples.Add(Realignment.TransformPosition(HostSplineComponent->GetLocationAtSplinePoint(HostSplineComponent->GetNumberOfSplinePoints() - 1, ESplineCoordinateSpace::Local))); + + // Inserting in reverse order -2 so we start one down + for (int i = HostSplineComponent->GetNumberOfSplinePoints() - 2; i >= 0; --i) + { + if (bKeepSplineCurves) + { + LastDistance = HostSplineComponent->GetDistanceAlongSplineAtSplinePoint(i + 1); + ThisDistance = HostSplineComponent->GetDistanceAlongSplineAtSplinePoint(i); + + DistAlongSegment = FMath::Abs(ThisDistance - LastDistance); + } + else + { + LastDistanceV = Realignment.TransformPosition(HostSplineComponent->GetLocationAtSplinePoint(i + 1, ESplineCoordinateSpace::Local)); + ThisDistanceV = Realignment.TransformPosition(HostSplineComponent->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::Local)); + + DistAlongSegment = FVector::Dist(ThisDistanceV, LastDistanceV); + DistNormal = ThisDistanceV - LastDistanceV; + DistNormal.Normalize(); + } + + + float SegmentCount = FMath::FloorToFloat(DistAlongSegment / SegmentLen); + float OverFlow = FMath::Fmod(DistAlongSegment, SegmentLen); + + if (SegmentCount < 1) + { + SegmentCount++; + } + + float DistPerSegment = (DistAlongSegment / SegmentCount); + + for (int j = 0; j < SegmentCount; j++) + { + if (j == SegmentCount - 1 && i > 0) + DistPerSegment += OverFlow; + + if (bKeepSplineCurves) + { + LastDistance -= DistPerSegment; + if (j == SegmentCount - 1 && i > 0) + { + LastDistance = ThisDistance; + } + FVector loc = Realignment.TransformPosition(HostSplineComponent->GetLocationAtDistanceAlongSpline(LastDistance, ESplineCoordinateSpace::Local)); + + if (!loc.IsNearlyZero()) + NewGesture.Samples.Add(loc); + } + else + { + LastDistanceV += DistPerSegment * DistNormal; + + if (j == SegmentCount - 1 && i > 0) + { + LastDistanceV = ThisDistanceV; + } + + if (!LastDistanceV.IsNearlyZero()) + NewGesture.Samples.Add(LastDistanceV); + } + } + } + + NewGesture.CalculateSizeOfGesture(bScaleToDatabase, this->TargetGestureScale); + Gestures.Add(NewGesture); + return true; +} + +void FVRGestureSplineDraw::ClearLastPoint() +{ + SplineComponent->RemoveSplinePoint(0, false); + + if (SplineMeshes.Num() < NextIndexCleared + 1) + NextIndexCleared = 0; + + SplineMeshes[NextIndexCleared]->SetVisibility(false); + NextIndexCleared++; +} + +void FVRGestureSplineDraw::Reset() +{ + if (SplineComponent != nullptr) + SplineComponent->ClearSplinePoints(true); + + for (int i = SplineMeshes.Num() - 1; i >= 0; --i) + { + if (SplineMeshes[i] != nullptr) + SplineMeshes[i]->SetVisibility(false); + else + SplineMeshes.RemoveAt(i); + } + + LastIndexSet = 0; + NextIndexCleared = 0; +} + +void FVRGestureSplineDraw::Clear() +{ + for (int i = 0; i < SplineMeshes.Num(); ++i) + { + if (SplineMeshes[i] != nullptr && !SplineMeshes[i]->IsBeingDestroyed()) + { + SplineMeshes[i]->Modify(); + SplineMeshes[i]->DestroyComponent(); + } + } + SplineMeshes.Empty(); + + if (SplineComponent != nullptr) + { + SplineComponent->DestroyComponent(); + SplineComponent = nullptr; + } + + LastIndexSet = 0; + NextIndexCleared = 0; +} + +FVRGestureSplineDraw::FVRGestureSplineDraw() +{ + SplineComponent = nullptr; + NextIndexCleared = 0; + LastIndexSet = 0; +} + +FVRGestureSplineDraw::~FVRGestureSplineDraw() +{ + Clear(); +} + +void UVRGestureComponent::BeginDestroy() +{ + Super::BeginDestroy(); + RecordingGestureDraw.Clear(); + if (TickGestureTimer_Handle.IsValid()) + { + GetWorld()->GetTimerManager().ClearTimer(TickGestureTimer_Handle); + } +} + +void UVRGestureComponent::RecalculateGestureSize(FVRGesture & InputGesture, UGesturesDatabase * GestureDB) +{ + if (GestureDB != nullptr) + InputGesture.CalculateSizeOfGesture(true, GestureDB->TargetGestureScale); + else + InputGesture.CalculateSizeOfGesture(false); +} + +FVRGesture UVRGestureComponent::EndRecording() +{ + if (TickGestureTimer_Handle.IsValid()) + { + GetWorld()->GetTimerManager().ClearTimer(TickGestureTimer_Handle); + } + + this->SetComponentTickEnabled(false); + CurrentState = EVRGestureState::GES_None; + + // Reset the recording gesture + RecordingGestureDraw.Reset(); + + return GestureLog; +} + +void UVRGestureComponent::ClearRecording() +{ + GestureLog.Samples.Reset(RecordingBufferSize); +} + +void UVRGestureComponent::SaveRecording(FVRGesture &Recording, FString RecordingName, bool bScaleRecordingToDatabase) +{ + if (GesturesDB) + { + Recording.CalculateSizeOfGesture(bScaleRecordingToDatabase, GesturesDB->TargetGestureScale); + Recording.Name = RecordingName; + GesturesDB->Gestures.Add(Recording); + } +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGlobalSettings.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGlobalSettings.cpp new file mode 100644 index 0000000..3a41db8 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGlobalSettings.cpp @@ -0,0 +1,445 @@ + +#include "VRGlobalSettings.h" + +#if WITH_CHAOS +#include "Chaos/ChaosConstraintSettings.h" +#endif + +UVRGlobalSettings::UVRGlobalSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer), + bUseGlobalLerpToHand(false), + bSkipLerpToHandIfHeld(false), + MinDistanceForLerp(10.0f), + LerpDuration(0.25f), + MinSpeedForLerp(100.f), + MaxSpeedForLerp(500.f), + LerpInterpolationMode(EVRLerpInterpolationMode::QuatInterp), + bUseCurve(false), + OneEuroMinCutoff(0.1f), + OneEuroCutoffSlope(10.0f), + OneEuroDeltaCutoff(10.0f), + CurrentControllerProfileInUse(NAME_None), + CurrentControllerProfileTransform(FTransform::Identity), + bUseSeperateHandTransforms(false), + CurrentControllerProfileTransformRight(FTransform::Identity) +{ +#if WITH_CHAOS + bUseChaosTranslationScalers = true; + bSetEngineChaosScalers = true; + LinearDriveStiffnessScale = 1.0f;// Chaos::ConstraintSettings::LinearDriveStiffnessScale(); + LinearDriveDampingScale = 1.0f;// Chaos::ConstraintSettings::LinearDriveDampingScale(); + AngularDriveStiffnessScale = 1.5f;// Chaos::ConstraintSettings::AngularDriveStiffnessScale(); + AngularDriveDampingScale = 1.5f;// Chaos::ConstraintSettings::AngularDriveDampingScale(); + + // Constraint settings + JointStiffness = 1.0f;// Chaos::ConstraintSettings::JointStiffness(); + SoftLinearStiffnessScale = 1.5f;// Chaos::ConstraintSettings::SoftLinearStiffnessScale(); + SoftLinearDampingScale = 1.2f;// Chaos::ConstraintSettings::SoftLinearDampingScale(); + SoftAngularStiffnessScale = 100000.f;// Chaos::ConstraintSettings::SoftAngularStiffnessScale(); + SoftAngularDampingScale = 1000.f;// Chaos::ConstraintSettings::SoftAngularDampingScale(); + JointLinearBreakScale = 1.0f; //Chaos::ConstraintSettings::LinearBreakScale(); + JointAngularBreakScale = 1.0f; //Chaos::ConstraintSettings::AngularBreakScale(); + +#endif +} + +bool UVRGlobalSettings::IsGlobalLerpEnabled() +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + return VRSettings.bUseGlobalLerpToHand; +} + +FTransform UVRGlobalSettings::AdjustTransformByControllerProfile(FName OptionalControllerProfileName, const FTransform& SocketTransform, bool bIsRightHand) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + if (OptionalControllerProfileName == NAME_None) + { + if (VRSettings.CurrentControllerProfileInUse != NAME_None) + { + // Use currently loaded transform + return SocketTransform * (((bIsRightHand && VRSettings.bUseSeperateHandTransforms) ? VRSettings.CurrentControllerProfileTransformRight : VRSettings.CurrentControllerProfileTransform)); + } + + // No override and no default, return base transform back + return SocketTransform; + } + + // Had an override, find it if possible and use its transform + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([OptionalControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == OptionalControllerProfileName; + }); + + if (FoundProfile) + { + return SocketTransform * (((bIsRightHand && VRSettings.bUseSeperateHandTransforms) ? FoundProfile->SocketOffsetTransformRightHand : FoundProfile->SocketOffsetTransform)); + } + + // Couldn't find it, return base transform + return SocketTransform; +} + + +FTransform UVRGlobalSettings::AdjustTransformByGivenControllerProfile(UPARAM(ref) FBPVRControllerProfile& ControllerProfile, const FTransform& SocketTransform, bool bIsRightHand) +{ + // Use currently loaded transform + return SocketTransform * (((bIsRightHand && ControllerProfile.bUseSeperateHandOffsetTransforms) ? ControllerProfile.SocketOffsetTransformRightHand : ControllerProfile.SocketOffsetTransform)); + + // Couldn't find it, return base transform + return SocketTransform; +} + +TArray<FBPVRControllerProfile> UVRGlobalSettings::GetControllerProfiles() +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + return VRSettings.ControllerProfiles; +} + +void UVRGlobalSettings::OverwriteControllerProfile(UPARAM(ref)FBPVRControllerProfile& OverwritingProfile, bool bSaveOutToConfig) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault<UVRGlobalSettings>(); + + for (int i = 0; i < VRSettings.ControllerProfiles.Num(); ++i) + { + if (VRSettings.ControllerProfiles[i].ControllerName == OverwritingProfile.ControllerName) + { + VRSettings.ControllerProfiles[i] = OverwritingProfile; + } + } + + if (bSaveOutToConfig) + SaveControllerProfiles(); +} + +void UVRGlobalSettings::AddControllerProfile(UPARAM(ref)FBPVRControllerProfile& NewProfile, bool bSaveOutToConfig) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault<UVRGlobalSettings>(); + + VRSettings.ControllerProfiles.Add(NewProfile); + + if (bSaveOutToConfig) + SaveControllerProfiles(); +} + +void UVRGlobalSettings::DeleteControllerProfile(FName ControllerProfileName, bool bSaveOutToConfig) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault<UVRGlobalSettings>(); + + for (int i = VRSettings.ControllerProfiles.Num() - 1; i >= 0; --i) + { + if (VRSettings.ControllerProfiles[i].ControllerName == ControllerProfileName) + { + VRSettings.ControllerProfiles.RemoveAt(i); + } + } + + if (bSaveOutToConfig) + SaveControllerProfiles(); +} + +void UVRGlobalSettings::SaveControllerProfiles() +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault<UVRGlobalSettings>(); + VRSettings.SaveConfig(); + + //VRSettings.SaveConfig(CPF_Config, *VRSettings.GetGlobalUserConfigFilename());//VRSettings.GetDefaultConfigFilename()); +} + + +FName UVRGlobalSettings::GetCurrentProfileName(bool& bHadLoadedProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + bHadLoadedProfile = VRSettings.CurrentControllerProfileInUse != NAME_None; + return VRSettings.CurrentControllerProfileInUse; +} + +FBPVRControllerProfile UVRGlobalSettings::GetCurrentProfile(bool& bHadLoadedProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + FName ControllerProfileName = VRSettings.CurrentControllerProfileInUse; + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([ControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == ControllerProfileName; + }); + + bHadLoadedProfile = FoundProfile != nullptr; + + if (bHadLoadedProfile) + { + return *FoundProfile; + } + else + return FBPVRControllerProfile(); +} + +bool UVRGlobalSettings::GetControllerProfile(FName ControllerProfileName, FBPVRControllerProfile& OutProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([ControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == ControllerProfileName; + }); + + if (FoundProfile) + { + OutProfile = *FoundProfile; + return true; + } + + return false; +} + +bool UVRGlobalSettings::LoadControllerProfileByName(FName ControllerProfileName, bool bSetAsCurrentProfile) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + const FBPVRControllerProfile* FoundProfile = VRSettings.ControllerProfiles.FindByPredicate([ControllerProfileName](const FBPVRControllerProfile& ArrayItem) + { + return ArrayItem.ControllerName == ControllerProfileName; + }); + + if (FoundProfile) + { + return LoadControllerProfile(*FoundProfile, bSetAsCurrentProfile); + } + + + UE_LOG(LogTemp, Warning, TEXT("Could not find controller profile!: %s"), *ControllerProfileName.ToString()); + return false; +} + +bool UVRGlobalSettings::LoadControllerProfile(const FBPVRControllerProfile& ControllerProfile, bool bSetAsCurrentProfile) +{ + + /* + UInputSettings* InputSettings = GetMutableDefault<UInputSettings>(); + if (false)//InputSettings != nullptr) + { + if (ControllerProfile.ActionOverrides.Num() > 0) + { + // Load button mappings + for (auto& Elem : ControllerProfile.ActionOverrides) + { + FName ActionName = Elem.Key; + FActionMappingDetails Mapping = Elem.Value; + + // We allow for 0 mapped actions here in case you want to delete one + if (ActionName == NAME_None) + continue; + + const TArray<FInputActionKeyMapping>& ActionMappings = InputSettings->GetActionMappings(); + for (int32 ActionIndex = ActionMappings.Num() - 1; ActionIndex >= 0; --ActionIndex) + { + if (ActionMappings[ActionIndex].ActionName == ActionName) + { + InputSettings->RemoveActionMapping(ActionMappings[ActionIndex], false); + // we don't break because the mapping may have been in the array twice + } + } + + // Then add the new bindings + for (FInputActionKeyMapping &KeyMapping : Mapping.ActionMappings) + { + // By default the key mappings don't have an action name, add them here + KeyMapping.ActionName = ActionName; + InputSettings->AddActionMapping(KeyMapping, false); + } + } + } + + if (ControllerProfile.AxisOverrides.Num() > 0) + { + // Load axis mappings + for (auto& Elem : ControllerProfile.AxisOverrides) + { + FName AxisName = Elem.Key; + FAxisMappingDetails Mapping = Elem.Value; + + // We allow for 0 mapped Axis's here in case you want to delete one + if (AxisName == NAME_None) + continue; + + const TArray<FInputAxisKeyMapping>& AxisMappings = InputSettings->GetAxisMappings(); + + // Clear all Axis's that use our Axis name first + for (int32 AxisIndex = AxisMappings.Num() - 1; AxisIndex >= 0; --AxisIndex) + { + if (AxisMappings[AxisIndex].AxisName == AxisName) + { + InputSettings->RemoveAxisMapping(AxisMappings[AxisIndex], false); + // we don't break because the mapping may have been in the array twice + } + } + + // Then add the new bindings + for (FInputAxisKeyMapping &KeyMapping : Mapping.AxisMappings) + { + // By default the key mappings don't have an Axis name, add them here + KeyMapping.AxisName = AxisName; + InputSettings->AddAxisMapping(KeyMapping, false); + } + } + } + + if (ControllerProfile.ActionOverrides.Num() > 0 || ControllerProfile.AxisOverrides.Num() > 0) + { + // Tell all players to use the new keymappings + InputSettings->ForceRebuildKeymaps(); + } + }*/ + + if (bSetAsCurrentProfile) + { + UVRGlobalSettings* VRSettings = GetMutableDefault<UVRGlobalSettings>(); + if (VRSettings) + { + VRSettings->CurrentControllerProfileInUse = ControllerProfile.ControllerName; + VRSettings->CurrentControllerProfileTransform = ControllerProfile.SocketOffsetTransform; + ensure(!VRSettings->CurrentControllerProfileTransform.ContainsNaN()); + VRSettings->bUseSeperateHandTransforms = ControllerProfile.bUseSeperateHandOffsetTransforms; + VRSettings->CurrentControllerProfileTransformRight = ControllerProfile.SocketOffsetTransformRightHand; + ensure(!VRSettings->CurrentControllerProfileTransformRight.ContainsNaN()); + VRSettings->OnControllerProfileChangedEvent.Broadcast(); + } + else + return false; + } + + + // Not saving key mapping in purpose, app will revert to default on next load and profiles will load custom changes + return true; +} + +void UVRGlobalSettings::PostInitProperties() +{ +#if WITH_EDITOR + // Not doing this currently, loading defaults is cool, and I may go back to it later when i get + // controller offsets for vive/touch/MR vs each other. + /*if (ControllerProfiles.Num() == 0) + { + ControllerProfiles.Add(FBPVRControllerProfile(TEXT("Vive_Wands"))); + ControllerProfiles.Add(FBPVRControllerProfile(TEXT("Oculus_Touch"), ControllerProfileStatics::OculusTouchStaticOffset)); + this->SaveConfig(CPF_Config, *this->GetDefaultConfigFilename()); + }*/ +#endif + + SetScalers(); + + Super::PostInitProperties(); +} + +#if WITH_EDITOR + +void UVRGlobalSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty* PropertyThatChanged = PropertyChangedEvent.Property; + + if (PropertyThatChanged != nullptr) + { +#if WITH_EDITORONLY_DATA + if ( + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, bUseChaosTranslationScalers) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, bSetEngineChaosScalers) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, LinearDriveStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, LinearDriveDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, AngularDriveStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, AngularDriveDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, JointStiffness) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftLinearStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftLinearDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftAngularStiffnessScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, SoftAngularDampingScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, JointLinearBreakScale) || + PropertyThatChanged->GetFName() == GET_MEMBER_NAME_CHECKED(UVRGlobalSettings, JointAngularBreakScale) + ) + { + SetScalers(); + } +#endif + } +} +#endif + +void UVRGlobalSettings::SetScalers() +{ +#if WITH_CHAOS + auto CVarLinearDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinearDriveStiffnessScale")); + auto CVarLinearDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinaearDriveDampingScale")); + auto CVarAngularDriveStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveStiffnessScale")); + auto CVarAngularDriveDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularDriveDampingScale")); + + // Constraint settings + auto CVarJointStiffness = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.JointStiffness")); + auto CVarSoftLinearStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftLinearStiffnessScale")); + auto CVarSoftLinearDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftLinearDampingScale")); + auto CVarSoftAngularStiffnessScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftAngularStiffnessScale")); + auto CVarSoftAngularDampingScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.SoftAngularDampingScale")); + auto CVarJointLinearBreakScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.LinearBreakScale")); + auto CVarJointAngularBreakScale = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Chaos.JointConstraint.AngularBreakScale")); + + if (bUseChaosTranslationScalers && bSetEngineChaosScalers) + { + CVarLinearDriveStiffnessScale->Set(LinearDriveStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarLinearDriveDampingScale->Set(LinearDriveDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveStiffnessScale->Set(AngularDriveStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveDampingScale->Set(AngularDriveDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + + // Constraint settings + CVarJointStiffness->Set(JointStiffness, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearStiffnessScale->Set(SoftLinearStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearDampingScale->Set(SoftLinearDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularStiffnessScale->Set(SoftAngularStiffnessScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularDampingScale->Set(SoftAngularDampingScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointLinearBreakScale->Set(JointLinearBreakScale, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointAngularBreakScale->Set(JointAngularBreakScale, EConsoleVariableFlags::ECVF_SetByCode); + } + else if (!bSetEngineChaosScalers) + { + CVarLinearDriveStiffnessScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarLinearDriveDampingScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveStiffnessScale->Set(1.5f, EConsoleVariableFlags::ECVF_SetByCode); + CVarAngularDriveDampingScale->Set(1.5f, EConsoleVariableFlags::ECVF_SetByCode); + + // Constraint settings + CVarJointStiffness->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearStiffnessScale->Set(1.5f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftLinearDampingScale->Set(1.2f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularStiffnessScale->Set(100000.f, EConsoleVariableFlags::ECVF_SetByCode); + CVarSoftAngularDampingScale->Set(1000.f, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointLinearBreakScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + CVarJointAngularBreakScale->Set(1.0f, EConsoleVariableFlags::ECVF_SetByCode); + } +#endif +} + +void UVRGlobalSettings::GetMeleeSurfaceGlobalSettings(TArray<FBPHitSurfaceProperties>& OutMeleeSurfaceSettings) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + OutMeleeSurfaceSettings = VRSettings.MeleeSurfaceSettings; +} + +void UVRGlobalSettings::GetVirtualStockGlobalSettings(FBPVirtualStockSettings& OutVirtualStockSettings) +{ + const UVRGlobalSettings& VRSettings = *GetDefault<UVRGlobalSettings>(); + + OutVirtualStockSettings.bUseDistanceBasedStockSnapping = VRSettings.VirtualStockSettings.bUseDistanceBasedStockSnapping; + OutVirtualStockSettings.StockSnapDistance = VRSettings.VirtualStockSettings.StockSnapDistance; + OutVirtualStockSettings.StockSnapOffset = VRSettings.VirtualStockSettings.StockSnapOffset; + OutVirtualStockSettings.bSmoothStockHand = VRSettings.VirtualStockSettings.bSmoothStockHand; + OutVirtualStockSettings.SmoothingValueForStock = VRSettings.VirtualStockSettings.SmoothingValueForStock; +} + +void UVRGlobalSettings::SaveVirtualStockGlobalSettings(FBPVirtualStockSettings NewVirtualStockSettings) +{ + UVRGlobalSettings& VRSettings = *GetMutableDefault<UVRGlobalSettings>(); + VRSettings.VirtualStockSettings = NewVirtualStockSettings; + + VRSettings.SaveConfig(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGripInterface.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGripInterface.cpp new file mode 100644 index 0000000..2264e2b --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRGripInterface.cpp @@ -0,0 +1,18 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "VRGripInterface.h" +#include "UObject/ObjectMacros.h" +#include "GripScripts/VRGripScriptBase.h" +#include "UObject/Interface.h" + +UVRGripInterface::UVRGripInterface(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + + +void IVRGripInterface::Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed) +{ + +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPathFollowingComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPathFollowingComponent.cpp new file mode 100644 index 0000000..12124e2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPathFollowingComponent.cpp @@ -0,0 +1,441 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRPathFollowingComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + +//#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION >= 13 +#include "Navigation/MetaNavMeshPath.h" +#include "NavLinkCustomInterface.h" +//#endif + +// Force to use new movement comp + +DEFINE_LOG_CATEGORY(LogPathFollowingVR); + +void UVRPathFollowingComponent::SetMovementComponent(UNavMovementComponent* MoveComp) +{ + Super::SetMovementComponent(MoveComp); + + VRMovementComp = Cast<UVRBaseCharacterMovementComponent>(MovementComp); + + if (VRMovementComp) + { + OnRequestFinished.AddUObject(VRMovementComp, &UVRBaseCharacterMovementComponent::OnMoveCompleted); + } +} + + +void UVRPathFollowingComponent::GetDebugStringTokens(TArray<FString>& Tokens, TArray<EPathFollowingDebugTokens::Type>& Flags) const +{ + Tokens.Add(GetStatusDesc()); + Flags.Add(EPathFollowingDebugTokens::Description); + + if (Status != EPathFollowingStatus::Moving) + { + return; + } + + FString& StatusDesc = Tokens[0]; + if (Path.IsValid()) + { + const int32 NumMoveSegments = (Path.IsValid() && Path->IsValid()) ? Path->GetPathPoints().Num() : -1; + const bool bIsDirect = (Path->CastPath<FAbstractNavigationPath>() != NULL); + const bool bIsCustomLink = CurrentCustomLinkOb.IsValid(); + + if (!bIsDirect) + { + StatusDesc += FString::Printf(TEXT(" (%d..%d/%d)%s"), MoveSegmentStartIndex + 1, MoveSegmentEndIndex + 1, NumMoveSegments, + bIsCustomLink ? TEXT(" (custom NavLink)") : TEXT("")); + } + else + { + StatusDesc += TEXT(" (direct)"); + } + } + else + { + StatusDesc += TEXT(" (invalid path)"); + } + + // add debug params + float CurrentDot = 0.0f, CurrentDistance = 0.0f, CurrentHeight = 0.0f; + uint8 bFailedDot = 0, bFailedDistance = 0, bFailedHeight = 0; + DebugReachTest(CurrentDot, CurrentDistance, CurrentHeight, bFailedHeight, bFailedDistance, bFailedHeight); + + Tokens.Add(TEXT("dot")); + Flags.Add(EPathFollowingDebugTokens::ParamName); + Tokens.Add(FString::Printf(TEXT("%.2f"), CurrentDot)); + Flags.Add(bFailedDot ? EPathFollowingDebugTokens::FailedValue : EPathFollowingDebugTokens::PassedValue); + + Tokens.Add(TEXT("dist2D")); + Flags.Add(EPathFollowingDebugTokens::ParamName); + Tokens.Add(FString::Printf(TEXT("%.0f"), CurrentDistance)); + Flags.Add(bFailedDistance ? EPathFollowingDebugTokens::FailedValue : EPathFollowingDebugTokens::PassedValue); + + Tokens.Add(TEXT("distZ")); + Flags.Add(EPathFollowingDebugTokens::ParamName); + Tokens.Add(FString::Printf(TEXT("%.0f"), CurrentHeight)); + Flags.Add(bFailedHeight ? EPathFollowingDebugTokens::FailedValue : EPathFollowingDebugTokens::PassedValue); +} + +void UVRPathFollowingComponent::PauseMove(FAIRequestID RequestID, EPathFollowingVelocityMode VelocityMode) +{ + //UE_VLOG(GetOwner(), LogPathFollowing, Log, TEXT("PauseMove: RequestID(%u)"), RequestID); + if (Status == EPathFollowingStatus::Paused) + { + return; + } + + if (RequestID.IsEquivalent(GetCurrentRequestId())) + { + if ((VelocityMode == EPathFollowingVelocityMode::Reset) && MovementComp && HasMovementAuthority()) + { + MovementComp->StopMovementKeepPathing(); + } + + LocationWhenPaused = MovementComp ? (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()) : FVector::ZeroVector; + PathTimeWhenPaused = Path.IsValid() ? Path->GetTimeStamp() : 0.0f; + Status = EPathFollowingStatus::Paused; + + UpdateMoveFocus(); + } +} + + + +bool UVRPathFollowingComponent::ShouldCheckPathOnResume() const +{ + bool bCheckPath = true; + if (MovementComp != NULL) + { + float AgentRadius = 0.0f, AgentHalfHeight = 0.0f; + MovementComp->GetOwner()->GetSimpleCollisionCylinder(AgentRadius, AgentHalfHeight); + + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocation() : MovementComp->GetActorFeetLocation()); + const float DeltaMove2DSq = (CurrentLocation - LocationWhenPaused).SizeSquared2D(); + const float DeltaZ = FMath::Abs(CurrentLocation.Z - LocationWhenPaused.Z); + if (DeltaMove2DSq < FMath::Square(AgentRadius) && DeltaZ < (AgentHalfHeight * 0.5f)) + { + bCheckPath = false; + } + } + + return bCheckPath; +} + +int32 UVRPathFollowingComponent::DetermineStartingPathPoint(const FNavigationPath* ConsideredPath) const +{ + int32 PickedPathPoint = INDEX_NONE; + + if (ConsideredPath && ConsideredPath->IsValid()) + { + // if we already have some info on where we were on previous path + // we can find out if there's a segment on new path we're currently at + if (MoveSegmentStartRef != INVALID_NAVNODEREF && + MoveSegmentEndRef != INVALID_NAVNODEREF && + ConsideredPath->GetNavigationDataUsed() != NULL) + { + // iterate every new path node and see if segment match + for (int32 PathPoint = 0; PathPoint < ConsideredPath->GetPathPoints().Num() - 1; ++PathPoint) + { + if (ConsideredPath->GetPathPoints()[PathPoint].NodeRef == MoveSegmentStartRef && + ConsideredPath->GetPathPoints()[PathPoint + 1].NodeRef == MoveSegmentEndRef) + { + PickedPathPoint = PathPoint; + break; + } + } + } + + if (MovementComp && PickedPathPoint == INDEX_NONE) + { + if (ConsideredPath->GetPathPoints().Num() > 2) + { + // check if is closer to first or second path point (don't assume AI's standing) + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + const FVector PathPt0 = *ConsideredPath->GetPathPointLocation(0); + const FVector PathPt1 = *ConsideredPath->GetPathPointLocation(1); + // making this test in 2d to avoid situation where agent's Z location not being in "navmesh plane" + // would influence the result + const float SqDistToFirstPoint = (CurrentLocation - PathPt0).SizeSquared2D(); + const float SqDistToSecondPoint = (CurrentLocation - PathPt1).SizeSquared2D(); + PickedPathPoint = FMath::IsNearlyEqual(SqDistToFirstPoint, SqDistToSecondPoint) ? + ((FMath::Abs(CurrentLocation.Z - PathPt0.Z) < FMath::Abs(CurrentLocation.Z - PathPt1.Z)) ? 0 : 1) : + ((SqDistToFirstPoint < SqDistToSecondPoint) ? 0 : 1); + } + else + { + // If there are only two point we probably should start from the beginning + PickedPathPoint = 0; + } + } + } + + return PickedPathPoint; +} + +bool UVRPathFollowingComponent::UpdateBlockDetection() +{ + const float GameTime = GetWorld()->GetTimeSeconds(); + if (bUseBlockDetection && + MovementComp && + GameTime > (LastSampleTime + BlockDetectionInterval) && + BlockDetectionSampleCount > 0) + { + LastSampleTime = GameTime; + + if (LocationSamples.Num() == NextSampleIdx) + { + LocationSamples.AddZeroed(1); + } + + LocationSamples[NextSampleIdx] = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationBased() : MovementComp->GetActorFeetLocationBased()); + NextSampleIdx = (NextSampleIdx + 1) % BlockDetectionSampleCount; + return true; + } + + return false; +} + +void UVRPathFollowingComponent::UpdatePathSegment() +{ +#if !UE_BUILD_SHIPPING + DEBUG_bMovingDirectlyToGoal = false; +#endif // !UE_BUILD_SHIPPING + + if ((Path.IsValid() == false) || (MovementComp == nullptr)) + { + //UE_CVLOG(Path.IsValid() == false, this, LogPathFollowing, Log, TEXT("Aborting move due to not having a valid path object")); + OnPathFinished(EPathFollowingResult::Aborted, FPathFollowingResultFlags::InvalidPath); + return; + } + + if (!Path->IsValid()) + { + if (!Path->IsWaitingForRepath()) + { + //UE_VLOG(this, LogPathFollowing, Log, TEXT("Aborting move due to path being invelid and not waiting for repath")); + OnPathFinished(EPathFollowingResult::Aborted, FPathFollowingResultFlags::InvalidPath); + } + + return; + } + + FMetaNavMeshPath* MetaNavPath = bIsUsingMetaPath ? Path->CastPath<FMetaNavMeshPath>() : nullptr; + + // if agent has control over its movement, check finish conditions + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + const bool bCanUpdateState = HasMovementAuthority(); + if (bCanUpdateState && Status == EPathFollowingStatus::Moving) + { + const int32 LastSegmentEndIndex = Path->GetPathPoints().Num() - 1; + const bool bFollowingLastSegment = (MoveSegmentEndIndex >= LastSegmentEndIndex); + const bool bLastPathChunk = (MetaNavPath == nullptr || MetaNavPath->IsLastSection()); + + if (bCollidedWithGoal) + { + // check if collided with goal actor + OnSegmentFinished(); + OnPathFinished(EPathFollowingResult::Success, FPathFollowingResultFlags::None); + } + else if (HasReachedDestination(CurrentLocation)) + { + // always check for destination, acceptance radius may cause it to pass before reaching last segment + OnSegmentFinished(); + OnPathFinished(EPathFollowingResult::Success, FPathFollowingResultFlags::None); + } + else if (bFollowingLastSegment && bMoveToGoalOnLastSegment && bLastPathChunk) + { + // use goal actor for end of last path segment + // UNLESS it's partial path (can't reach goal) + if (DestinationActor.IsValid() && Path->IsPartial() == false) + { + const FVector AgentLocation = DestinationAgent ? DestinationAgent->GetNavAgentLocation() : DestinationActor->GetActorLocation(); + // note that the condition below requires GoalLocation to be in world space. + const FVector GoalLocation = FQuatRotationTranslationMatrix(DestinationActor->GetActorQuat(), AgentLocation).TransformPosition(MoveOffset); + + CurrentDestination.Set(NULL, GoalLocation); + + //UE_VLOG(this, LogPathFollowing, Log, TEXT("Moving directly to move goal rather than following last path segment")); + //UE_VLOG_LOCATION(this, LogPathFollowing, VeryVerbose, GoalLocation, 30, FColor::Green, TEXT("Last-segment-to-actor")); + //UE_VLOG_SEGMENT(this, LogPathFollowing, VeryVerbose, CurrentLocation, GoalLocation, FColor::Green, TEXT_EMPTY); + } + + UpdateMoveFocus(); + +#if !UE_BUILD_SHIPPING + DEBUG_bMovingDirectlyToGoal = true; +#endif // !UE_BUILD_SHIPPING + } + // check if current move segment is finished + else if (HasReachedCurrentTarget(CurrentLocation)) + { + OnSegmentFinished(); + SetNextMoveSegment(); + } + } + + if (bCanUpdateState && Status == EPathFollowingStatus::Moving) + { + // check waypoint switch condition in meta paths + if (MetaNavPath && Status == EPathFollowingStatus::Moving) + { + MetaNavPath->ConditionalMoveToNextSection(CurrentLocation, EMetaPathUpdateReason::MoveTick); + } + + // gather location samples to detect if moving agent is blocked + const bool bHasNewSample = UpdateBlockDetection(); + if (bHasNewSample && IsBlocked()) + { + if (Path->GetPathPoints().IsValidIndex(MoveSegmentEndIndex) && Path->GetPathPoints().IsValidIndex(MoveSegmentStartIndex)) + { + //LogBlockHelper(GetOwner(), MovementComp, MinAgentRadiusPct, MinAgentHalfHeightPct, + //*Path->GetPathPointLocation(MoveSegmentStartIndex), + //*Path->GetPathPointLocation(MoveSegmentEndIndex)); + } + else + { + if ((GetOwner() != NULL) && (MovementComp != NULL)) + { + // UE_VLOG(GetOwner(), LogPathFollowing, Verbose, TEXT("Path blocked, but move segment indices are not valid: start %d, end %d of %d"), MoveSegmentStartIndex, MoveSegmentEndIndex, Path->GetPathPoints().Num()); + } + } + OnPathFinished(EPathFollowingResult::Blocked, FPathFollowingResultFlags::None); + } + } +} + +void UVRPathFollowingComponent::FollowPathSegment(float DeltaTime) +{ + if (!Path.IsValid() || MovementComp == nullptr) + { + return; + } + + const FVector CurrentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + const FVector CurrentTarget = GetCurrentTargetLocation(); + + // set to false by default, we will set set this back to true if appropriate + bIsDecelerating = false; + + const bool bAccelerationBased = MovementComp->UseAccelerationForPathFollowing(); + if (bAccelerationBased) + { + CurrentMoveInput = (CurrentTarget - CurrentLocation).GetSafeNormal(); + + if (bStopMovementOnFinish && (MoveSegmentStartIndex >= DecelerationSegmentIndex)) + { + const FVector PathEnd = Path->GetEndLocation(); + const float DistToEndSq = FVector::DistSquared(CurrentLocation, PathEnd); + const bool bShouldDecelerate = DistToEndSq < FMath::Square(CachedBrakingDistance); + if (bShouldDecelerate) + { + bIsDecelerating = true; + + const float SpeedPct = FMath::Clamp(FMath::Sqrt(DistToEndSq) / CachedBrakingDistance, 0.0f, 1.0f); + CurrentMoveInput *= SpeedPct; + } + } + + PostProcessMove.ExecuteIfBound(this, CurrentMoveInput); + MovementComp->RequestPathMove(CurrentMoveInput); + } + else + { + FVector MoveVelocity = (CurrentTarget - CurrentLocation) / DeltaTime; + + const int32 LastSegmentStartIndex = Path->GetPathPoints().Num() - 2; + const bool bNotFollowingLastSegment = (MoveSegmentStartIndex < LastSegmentStartIndex); + + PostProcessMove.ExecuteIfBound(this, MoveVelocity); + MovementComp->RequestDirectMove(MoveVelocity, bNotFollowingLastSegment); + } +} + +bool UVRPathFollowingComponent::HasReachedCurrentTarget(const FVector& CurrentLocation) const +{ + if (MovementComp == NULL) + { + return false; + } + + const FVector CurrentTarget = GetCurrentTargetLocation(); + const FVector CurrentDirection = GetCurrentDirection(); + + // check if moved too far + const FVector ToTarget = (CurrentTarget - (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation())); + const float SegmentDot = FVector::DotProduct(ToTarget, CurrentDirection); + if (SegmentDot < 0.0) + { + return true; + } + + // or standing at target position + // don't use acceptance radius here, it has to be exact for moving near corners (2D test < 5% of agent radius) + const float GoalRadius = 0.0f; + const float GoalHalfHeight = 0.0f; + + return HasReachedInternal(CurrentTarget, GoalRadius, GoalHalfHeight, CurrentLocation, CurrentAcceptanceRadius, 0.05f); +} + +void UVRPathFollowingComponent::DebugReachTest(float& CurrentDot, float& CurrentDistance, float& CurrentHeight, uint8& bDotFailed, uint8& bDistanceFailed, uint8& bHeightFailed) const +{ + if (!Path.IsValid() || MovementComp == NULL) + { + return; + } + + const int32 LastSegmentEndIndex = Path->GetPathPoints().Num() - 1; + const bool bFollowingLastSegment = (MoveSegmentEndIndex >= LastSegmentEndIndex); + + float GoalRadius = 0.0f; + float GoalHalfHeight = 0.0f; + float RadiusThreshold = 0.0f; + float AgentRadiusPct = 0.05f; + + FVector AgentLocation = (VRMovementComp != nullptr ? VRMovementComp->GetActorFeetLocationVR() : MovementComp->GetActorFeetLocation()); + FVector GoalLocation = GetCurrentTargetLocation(); + RadiusThreshold = CurrentAcceptanceRadius; + + if (bFollowingLastSegment) + { + GoalLocation = *Path->GetPathPointLocation(Path->GetPathPoints().Num() - 1); + AgentRadiusPct = MinAgentRadiusPct; + + // take goal's current location, unless path is partial or last segment doesn't reach goal actor (used by tethered AI) + if (DestinationActor.IsValid() && !Path->IsPartial() && bMoveToGoalOnLastSegment) + { + if (DestinationAgent) + { + FVector GoalOffset; + + const AActor* OwnerActor = GetOwner(); + DestinationAgent->GetMoveGoalReachTest(OwnerActor, MoveOffset, GoalOffset, GoalRadius, GoalHalfHeight); + GoalLocation = FQuatRotationTranslationMatrix(DestinationActor->GetActorQuat(), DestinationAgent->GetNavAgentLocation()).TransformPosition(GoalOffset); + } + else + { + GoalLocation = DestinationActor->GetActorLocation(); + } + } + } + + const FVector ToGoal = (GoalLocation - AgentLocation); + const FVector CurrentDirection = GetCurrentDirection(); + CurrentDot = FVector::DotProduct(ToGoal.GetSafeNormal(), CurrentDirection); + bDotFailed = (CurrentDot < 0.0f) ? 1 : 0; + + // get cylinder of moving agent + float AgentRadius = 0.0f; + float AgentHalfHeight = 0.0f; + AActor* MovingAgent = MovementComp->GetOwner(); + MovingAgent->GetSimpleCollisionCylinder(AgentRadius, AgentHalfHeight); + + CurrentDistance = ToGoal.Size2D(); + const float UseRadius = FMath::Max(RadiusThreshold, GoalRadius + (AgentRadius * AgentRadiusPct)); + bDistanceFailed = (CurrentDistance > UseRadius) ? 1 : 0; + + CurrentHeight = FMath::Abs(ToGoal.Z); + const float UseHeight = GoalHalfHeight + (AgentHalfHeight * MinAgentHalfHeightPct); + bHeightFailed = (CurrentHeight > UseHeight) ? 1 : 0; +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPlayerController.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPlayerController.cpp new file mode 100644 index 0000000..a786ec5 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRPlayerController.cpp @@ -0,0 +1,120 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRPlayerController.h" +#include "AI/NavigationSystemBase.h" +#include "VRBaseCharacterMovementComponent.h" +#include "VRPathFollowingComponent.h" +//#include "VRBPDatatypes.h" +#include "Engine/Player.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" + + +AVRPlayerController::AVRPlayerController(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bDisableServerUpdateCamera = true; +} + +void AVRPlayerController::SpawnPlayerCameraManager() +{ + Super::SpawnPlayerCameraManager(); + + // Turn off the default FOV and position replication of the camera manager, most functions should be sending values anyway and I am replicating + // the actual camera position myself so this is just wasted bandwidth + if(PlayerCameraManager != NULL && bDisableServerUpdateCamera) + PlayerCameraManager->bUseClientSideCameraUpdates = false; +} + +// #TODO 4.20: This was removed +/*void AVRPlayerController::InitNavigationControl(UPathFollowingComponent*& PathFollowingComp) +{ + PathFollowingComp = FindComponentByClass<UPathFollowingComponent>(); + if (PathFollowingComp == NULL) + { + PathFollowingComp = NewObject<UVRPathFollowingComponent>(this); + PathFollowingComp->RegisterComponentWithWorld(GetWorld()); + PathFollowingComp->Initialize(); + } +}*/ + +/*IPathFollowingAgentInterface* AVRPlayerController::GetPathFollowingAgent() const +{ + // Moved spawning the path following component into the path finding logic instead + return FNavigationSystem::FindPathFollowingAgentForActor(*this); +}*/ + +void AVRPlayerController::PlayerTick(float DeltaTime) +{ + + // #TODO: Should I be only doing this if ticking CMC and CMC is active? + if (AVRBaseCharacter * VRChar = Cast<AVRBaseCharacter>(GetPawn())) + { + // Keep from calling multiple times + UVRBaseCharacterMovementComponent * BaseCMC = Cast<UVRBaseCharacterMovementComponent>(VRChar->GetMovementComponent()); + + if (!BaseCMC || !BaseCMC->bRunControlRotationInMovementComponent) + return Super::PlayerTick(DeltaTime); + + if (!bShortConnectTimeOut) + { + bShortConnectTimeOut = true; + ServerShortTimeout(); + } + + TickPlayerInput(DeltaTime, DeltaTime == 0.f); + LastRotationInput = RotationInput; + + if ((Player != NULL) && (Player->PlayerController == this)) + { + // Validate current state + bool bUpdateRotation = false; + if (IsInState(NAME_Playing)) + { + if (GetPawn() == NULL) + { + ChangeState(NAME_Inactive); + } + else if (Player && GetPawn() == AcknowledgedPawn && (!BaseCMC || (BaseCMC && !BaseCMC->IsActive()))) + { + bUpdateRotation = true; + } + } + + if (IsInState(NAME_Inactive)) + { + if (GetLocalRole() < ROLE_Authority) + { + SafeServerCheckClientPossession(); + } + + //bUpdateRotation = !IsFrozen(); + } + else if (IsInState(NAME_Spectating)) + { + if (GetLocalRole() < ROLE_Authority) + { + SafeServerUpdateSpectatorState(); + } + + // Keep it when spectating + bUpdateRotation = true; + } + + // Update rotation + if (bUpdateRotation) + { + UpdateRotation(DeltaTime); + } + } + } + else + { + // Not our character, forget it + Super::PlayerTick(DeltaTime); + } +} + +UVRLocalPlayer::UVRLocalPlayer(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRRootComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRRootComponent.cpp new file mode 100644 index 0000000..6908c33 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRRootComponent.cpp @@ -0,0 +1,1644 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRRootComponent.h" +//#include "Runtime/Engine/Private/EnginePrivate.h" +#include "WorldCollision.h" +#include "PhysicsPublic.h" +#include "DrawDebugHelpers.h" +#include "IHeadMountedDisplay.h" +#include "IXRTrackingSystem.h" +#include "VRCharacter.h" +#include "Algo/Copy.h" + +#include "Components/PrimitiveComponent.h" + +DEFINE_LOG_CATEGORY(LogVRRootComponent); +#define LOCTEXT_NAMESPACE "VRRootComponent" + +DECLARE_CYCLE_STAT(TEXT("VRRootMovement"), STAT_VRRootMovement, STATGROUP_VRRootComponent); +DECLARE_CYCLE_STAT(TEXT("PerformOverlapQueryVR Time"), STAT_PerformOverlapQueryVR, STATGROUP_VRRootComponent); +DECLARE_CYCLE_STAT(TEXT("UpdateOverlapsVRRoot Time"), STAT_UpdateOverlapsVRRoot, STATGROUP_VRRootComponent); + +typedef TArray<const FOverlapInfo*, TInlineAllocator<8>> TInlineOverlapPointerArray; + +// Helper to see if two components can possibly generate overlaps with each other. +FORCEINLINE_DEBUGGABLE static bool CanComponentsGenerateOverlap(const UPrimitiveComponent* MyComponent, /*const*/ UPrimitiveComponent* OtherComp) +{ + return OtherComp + && OtherComp->GetGenerateOverlapEvents() + && MyComponent + && MyComponent->GetGenerateOverlapEvents() + && MyComponent->GetCollisionResponseToComponent(OtherComp) == ECR_Overlap; +} + +// Predicate to identify components from overlaps array that can overlap +struct FPredicateFilterCanOverlap +{ + FPredicateFilterCanOverlap(const UPrimitiveComponent& OwningComponent) + : MyComponent(OwningComponent) + { + } + + bool operator() (const FOverlapInfo& Info) const + { + return CanComponentsGenerateOverlap(&MyComponent, Info.OverlapInfo.GetComponent()); + } + +private: + const UPrimitiveComponent& MyComponent; +}; + +// Predicate to remove components from overlaps array that can no longer overlap +struct FPredicateFilterCannotOverlap +{ + FPredicateFilterCannotOverlap(const UPrimitiveComponent& OwningComponent) + : MyComponent(OwningComponent) + { + } + + bool operator() (const FOverlapInfo& Info) const + { + return !CanComponentsGenerateOverlap(&MyComponent, Info.OverlapInfo.GetComponent()); + } + +private: + const UPrimitiveComponent& MyComponent; +}; + +// Helper to initialize an array to point to data members in another array. +template <class ElementType, class AllocatorType1, class AllocatorType2> +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayData(TArray<const ElementType*, AllocatorType1>& Pointers, const TArray<ElementType, AllocatorType2>& DataArray) +{ + const int32 NumItems = DataArray.Num(); + Pointers.SetNumUninitialized(NumItems); + for (int32 i = 0; i < NumItems; i++) + { + Pointers[i] = &(DataArray[i]); + } +} + +template <class ElementType, class AllocatorType1> +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayData(TArray<const ElementType*, AllocatorType1>& Pointers, const TArrayView<const ElementType>& DataArray) +{ + const int32 NumItems = DataArray.Num(); + Pointers.SetNumUninitialized(NumItems); + for (int32 i = 0; i < NumItems; i++) + { + Pointers[i] = &(DataArray[i]); + } +} + +// Helper to initialize an array to point to data members in another array which satisfy a predicate. +template <class ElementType, class AllocatorType1, class AllocatorType2, typename PredicateT> +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayDataByPredicate(TArray<const ElementType*, AllocatorType1>& Pointers, const TArray<ElementType, AllocatorType2>& DataArray, PredicateT Predicate) +{ + Pointers.Reserve(Pointers.Num() + DataArray.Num()); + for (const ElementType& Item : DataArray) + { + if (Invoke(Predicate, Item)) + { + Pointers.Add(&Item); + } + } +} + +template <class ElementType, class AllocatorType1, typename PredicateT> +FORCEINLINE_DEBUGGABLE static void GetPointersToArrayDataByPredicate(TArray<const ElementType*, AllocatorType1>& Pointers, const TArrayView<const ElementType>& DataArray, PredicateT Predicate) +{ + Pointers.Reserve(Pointers.Num() + DataArray.Num()); + for (const ElementType& Item : DataArray) + { + if (Invoke(Predicate, Item)) + { + Pointers.Add(&Item); + } + } +} + +static int32 bEnableFastOverlapCheck = 1; + +// Returns true if we should check the GetGenerateOverlapEvents() flag when gathering overlaps, otherwise we'll always just do it. +static bool ShouldCheckOverlapFlagToQueueOverlaps(const UPrimitiveComponent& ThisComponent) +{ + const FScopedMovementUpdate* CurrentUpdate = ThisComponent.GetCurrentScopedMovement(); + if (CurrentUpdate) + { + return CurrentUpdate->RequiresOverlapsEventFlag(); + } + // By default we require the GetGenerateOverlapEvents() to queue up overlaps, since we require it to trigger events. + return true; +} + +// LOOKING_FOR_PERF_ISSUES +#define PERF_MOVECOMPONENT_STATS 0 + +namespace PrimitiveComponentStatics +{ + //static const FText MobilityWarnText = LOCTEXT("InvalidMove", "move"); + static const FName MoveComponentName(TEXT("MoveComponent")); + static const FName UpdateOverlapsName(TEXT("UpdateOverlaps")); +} + +// Predicate to determine if an overlap is with a certain AActor. +struct FPredicateOverlapHasSameActor +{ + FPredicateOverlapHasSameActor(const AActor& Owner) + : MyOwnerPtr(&Owner) + { + } + + bool operator() (const FOverlapInfo& Info) + { + // MyOwnerPtr is always valid, so we don't need the IsValid() checks in the WeakObjectPtr comparison operator. + return MyOwnerPtr.HasSameIndexAndSerialNumber(Info.OverlapInfo.HitObjectHandle.FetchActor()); + } + +private: + const TWeakObjectPtr<const AActor> MyOwnerPtr; +}; + +// Predicate to determine if an overlap is *NOT* with a certain AActor. +struct FPredicateOverlapHasDifferentActor +{ + FPredicateOverlapHasDifferentActor(const AActor& Owner) + : MyOwnerPtr(&Owner) + { + } + + bool operator() (const FOverlapInfo& Info) + { + // MyOwnerPtr is always valid, so we don't need the IsValid() checks in the WeakObjectPtr comparison operator. + return !MyOwnerPtr.HasSameIndexAndSerialNumber(Info.OverlapInfo.HitObjectHandle.FetchActor()); + } + +private: + const TWeakObjectPtr<const AActor> MyOwnerPtr; +}; + +// Helper for finding the index of an FOverlapInfo in an Array using the FFastOverlapInfoCompare predicate, knowing that at least one overlap is valid (non-null). +template<class AllocatorType> +FORCEINLINE_DEBUGGABLE int32 IndexOfOverlapFast(const TArray<FOverlapInfo, AllocatorType>& OverlapArray, const FOverlapInfo& SearchItem) +{ + return OverlapArray.IndexOfByPredicate(FFastOverlapInfoCompare(SearchItem)); +} + +// Version that works with arrays of pointers and pointers to search items. +template<class AllocatorType> +FORCEINLINE_DEBUGGABLE int32 IndexOfOverlapFast(const TArray<const FOverlapInfo*, AllocatorType>& OverlapPtrArray, const FOverlapInfo* SearchItem) +{ + return OverlapPtrArray.IndexOfByPredicate(FFastOverlapInfoCompare(*SearchItem)); +} + +// Helper for adding an FOverlapInfo uniquely to an Array, using IndexOfOverlapFast and knowing that at least one overlap is valid (non-null). +template<class AllocatorType> +FORCEINLINE_DEBUGGABLE void AddUniqueOverlapFast(TArray<FOverlapInfo, AllocatorType>& OverlapArray, FOverlapInfo& NewOverlap) +{ + if (IndexOfOverlapFast(OverlapArray, NewOverlap) == INDEX_NONE) + { + OverlapArray.Add(NewOverlap); + } +} + +template<class AllocatorType> +FORCEINLINE_DEBUGGABLE void AddUniqueOverlapFast(TArray<FOverlapInfo, AllocatorType>& OverlapArray, FOverlapInfo&& NewOverlap) +{ + if (IndexOfOverlapFast(OverlapArray, NewOverlap) == INDEX_NONE) + { + OverlapArray.Add(NewOverlap); + } +} + +static void PullBackHit(FHitResult& Hit, const FVector& Start, const FVector& End, const float Dist) +{ + const float DesiredTimeBack = FMath::Clamp(0.1f, 0.1f / Dist, 1.f / Dist) + 0.001f; + Hit.Time = FMath::Clamp(Hit.Time - DesiredTimeBack, 0.f, 1.f); +} + +static bool ShouldIgnoreHitResult(const UWorld* InWorld, bool bAllowSimulatingCollision, FHitResult const& TestHit, FVector const& MovementDirDenormalized, const AActor* MovingActor, EMoveComponentFlags MoveFlags) +{ + if (TestHit.bBlockingHit) + { + // VR Pawns need to totally ignore simulating components with movement to prevent sickness + if (!bAllowSimulatingCollision && TestHit.Component.IsValid() && TestHit.Component->IsSimulatingPhysics()) + return true; + + // check "ignore bases" functionality + if ((MoveFlags & MOVECOMP_IgnoreBases) && MovingActor) //we let overlap components go through because their overlap is still needed and will cause beginOverlap/endOverlap events + { + // ignore if there's a base relationship between moving actor and hit actor + AActor const* const HitActor = TestHit.HitObjectHandle.FetchActor(); + if (HitActor) + { + if (MovingActor->IsBasedOnActor(HitActor) || HitActor->IsBasedOnActor(MovingActor)) + { + return true; + } + } + } + + // If we started penetrating, we may want to ignore it if we are moving out of penetration. + // This helps prevent getting stuck in walls. + static const auto CVarHitDistanceTolerance = IConsoleManager::Get().FindConsoleVariable(TEXT("p.HitDistanceTolerance")); + if ((TestHit.Distance < CVarHitDistanceTolerance->GetFloat() || TestHit.bStartPenetrating) && !(MoveFlags & MOVECOMP_NeverIgnoreBlockingOverlaps)) + { + static const auto CVarInitialOverlapTolerance = IConsoleManager::Get().FindConsoleVariable(TEXT("p.InitialOverlapTolerance")); + const float DotTolerance = CVarInitialOverlapTolerance->GetFloat(); + + // Dot product of movement direction against 'exit' direction + const FVector MovementDir = MovementDirDenormalized.GetSafeNormal(); + const float MoveDot = (TestHit.ImpactNormal | MovementDir); + + const bool bMovingOut = MoveDot > DotTolerance; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + + static const auto CVarShowInitialOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.ShowInitialOverlaps")); + if (CVarShowInitialOverlaps->GetInt() != 0) + { + UE_LOG(LogVRRootComponent, Log, TEXT("Overlapping %s Dir %s Dot %f Normal %s Depth %f"), *GetNameSafe(TestHit.Component.Get()), *MovementDir.ToString(), MoveDot, *TestHit.ImpactNormal.ToString(), TestHit.PenetrationDepth); + DrawDebugDirectionalArrow(InWorld, TestHit.TraceStart, TestHit.TraceStart + 30.f * TestHit.ImpactNormal, 5.f, bMovingOut ? FColor(64, 128, 255) : FColor(255, 64, 64), true, 4.f); + if (TestHit.PenetrationDepth > KINDA_SMALL_NUMBER) + { + DrawDebugDirectionalArrow(InWorld, TestHit.TraceStart, TestHit.TraceStart + TestHit.PenetrationDepth * TestHit.Normal, 5.f, FColor(64, 255, 64), true, 4.f); + } + } + // } +#endif + + // If we are moving out, ignore this result! + if (bMovingOut) + { + return true; + } + } + } + + return false; +} +static FORCEINLINE_DEBUGGABLE bool ShouldIgnoreOverlapResult(const UWorld* World, const AActor* ThisActor, const UPrimitiveComponent& ThisComponent, const AActor* OtherActor, const UPrimitiveComponent& OtherComponent, bool bCheckOverlapFlags) +{ + // Don't overlap with self + if (&ThisComponent == &OtherComponent) + { + return true; + } + + if (bCheckOverlapFlags) + { + // Both components must set GetGenerateOverlapEvents() + if (!ThisComponent.GetGenerateOverlapEvents() || !OtherComponent.GetGenerateOverlapEvents()) + { + return true; + } + } + + if (!ThisActor || !OtherActor) + { + return true; + } + + if (!World || OtherActor == (AActor*)World->GetWorldSettings() || !OtherActor->IsActorInitialized()) + { + return true; + } + + return false; +} + + +UVRRootComponent::UVRRootComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + + bWantsInitializeComponent = true; + + this->SetRelativeScale3D(FVector(1.f)); + this->SetRelativeLocation(FVector::ZeroVector); + + // 2.15f is ((MIN_FLOOR_DIST + MAX_FLOOR_DIST) / 2), same value that walking attempts to retain + // 1.9f is MIN_FLOOR_DIST, this would not go below ledges when hanging off + VRCapsuleOffset = FVector(-8.0f, 0.0f, 2.15f /*0.0f*/); + + bCenterCapsuleOnHMD = false; + bPauseTracking = false; + + + ShapeColor = FColor(223, 149, 157, 255); + + CapsuleRadius = 20.0f; + CapsuleHalfHeight = 96.0f; + bUseEditorCompositing = true; + OffsetComponentToWorld = FTransform(FQuat(0.0f,0.0f,0.0f,1.0f), FVector::ZeroVector, FVector(1.0f)); + + // Fixes a problem where headset stays at 0,0,0 + lastCameraLoc = FVector::ZeroVector; + lastCameraRot = FRotator::ZeroRotator; + curCameraRot = FRotator::ZeroRotator; + curCameraLoc = FVector::ZeroVector; + StoredCameraRotOffset = FRotator::ZeroRotator; + TargetPrimitiveComponent = NULL; + owningVRChar = NULL; + //VRCameraCollider = NULL; + + bAllowSimulatingCollision = false; + bUseWalkingCollisionOverride = false; + WalkingCollisionOverride = ECollisionChannel::ECC_Pawn; + + bCalledUpdateTransform = false; + + CanCharacterStepUpOn = ECB_No; + //bShouldUpdatePhysicsVolume = true; +// bCheckAsyncSceneOnMove = false; + SetCanEverAffectNavigation(false); + bDynamicObstacle = true; + + //bOffsetByHMD = false; +} + +/** Represents a UVRRootComponent to the scene manager. */ +class FDrawVRCylinderSceneProxy final : public FPrimitiveSceneProxy +{ +public: + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast<size_t>(&UniquePointer); + } + + FDrawVRCylinderSceneProxy(const UVRRootComponent* InComponent) + : FPrimitiveSceneProxy(InComponent) + , bDrawOnlyIfSelected(InComponent->bDrawOnlyIfSelected) + , CapsuleRadius(InComponent->GetScaledCapsuleRadius()) + , CapsuleHalfHeight(InComponent->GetScaledCapsuleHalfHeight()) + , ShapeColor(InComponent->ShapeColor) + , VRCapsuleOffset(InComponent->VRCapsuleOffset) + , bSimulating(false) + //, OffsetComponentToWorld(InComponent->OffsetComponentToWorld) + , LocalToWorld(InComponent->OffsetComponentToWorld.ToMatrixWithScale()) + { + bWillEverBeLit = false; + } + + virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_GetDynamicMeshElements_DrawDynamicElements); + + //const FMatrix& LocalToWorld = OffsetComponentToWorld.ToMatrixWithScale();//GetLocalToWorld(); + const int32 CapsuleSides = FMath::Clamp<int32>(CapsuleRadius / 4.f, 16, 64); + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + + if (VisibilityMap & (1 << ViewIndex)) + { + const FSceneView* View = Views[ViewIndex]; + const FLinearColor DrawCapsuleColor = GetViewSelectionColor(ShapeColor, *View, IsSelected(), IsHovered(), false, IsIndividuallySelected()); + + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + + // If in editor views, lets offset the capsule upwards so that it views correctly + + if (bSimulating) + { + DrawWireCapsule(PDI, LocalToWorld.GetOrigin() - FVector(0.f, 0.f, CapsuleHalfHeight), LocalToWorld.GetScaledAxis(EAxis::X), LocalToWorld.GetScaledAxis(EAxis::Y), LocalToWorld.GetScaledAxis(EAxis::Z), DrawCapsuleColor, CapsuleRadius, CapsuleHalfHeight, CapsuleSides, SDPG_World); + } + else if (UseEditorCompositing(View)) + { + DrawWireCapsule(PDI, LocalToWorld.GetOrigin() /*+ FVector(0.f, 0.f, CapsuleHalfHeight)*/, LocalToWorld.GetScaledAxis(EAxis::X), LocalToWorld.GetScaledAxis(EAxis::Y), LocalToWorld.GetScaledAxis(EAxis::Z), DrawCapsuleColor, CapsuleRadius, CapsuleHalfHeight, CapsuleSides, SDPG_World, 1.25f); + } + else + DrawWireCapsule(PDI, LocalToWorld.GetOrigin(), LocalToWorld.GetScaledAxis(EAxis::X), LocalToWorld.GetScaledAxis(EAxis::Y), LocalToWorld.GetScaledAxis(EAxis::Z), DrawCapsuleColor, CapsuleRadius, CapsuleHalfHeight, CapsuleSides, SDPG_World, 1.25f); + } + } + } + + /** Called on render thread to assign new dynamic data */ + void UpdateTransform_RenderThread(const FTransform &NewTransform, float NewHalfHeight, bool bIsSimulating) + { + check(IsInRenderingThread()); + LocalToWorld = NewTransform.ToMatrixWithScale(); + //OffsetComponentToWorld = NewTransform; + CapsuleHalfHeight = NewHalfHeight; + bSimulating = bIsSimulating; + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + const bool bProxyVisible = !bDrawOnlyIfSelected || IsSelected(); + + // Should we draw this because collision drawing is enabled, and we have collision + const bool bShowForCollision = View->Family->EngineShowFlags.Collision && IsCollisionEnabled(); + + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = (IsShown(View) && bProxyVisible) || bShowForCollision; + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + return Result; + } + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + +private: + const uint32 bDrawOnlyIfSelected : 1; + const float CapsuleRadius; + float CapsuleHalfHeight; + FColor ShapeColor; + const FVector VRCapsuleOffset; + bool bSimulating; + //FTransform OffsetComponentToWorld; + FMatrix LocalToWorld; +}; + +FPrimitiveSceneProxy* UVRRootComponent::CreateSceneProxy() +{ + //GenerateOffsetToWorld(); + return new FDrawVRCylinderSceneProxy(this); +} + +void UVRRootComponent::InitializeComponent() +{ + Super::InitializeComponent(); + GenerateOffsetToWorld(); +} + +void UVRRootComponent::BeginPlay() +{ + Super::BeginPlay(); + + if(AVRBaseCharacter * vrOwner = Cast<AVRBaseCharacter>(this->GetOwner())) + { + TargetPrimitiveComponent = vrOwner->VRReplicatedCamera; + owningVRChar = vrOwner; + //VRCameraCollider = vrOwner->VRCameraCollider; + return; + } + else + { + TArray<USceneComponent*> children = this->GetAttachChildren(); + + for (int i = 0; i < children.Num(); i++) + { + if (children[i]->IsA(UCameraComponent::StaticClass())) + { + TargetPrimitiveComponent = children[i]; + owningVRChar = NULL; + return; + } + } + } + + //VRCameraCollider = NULL; + TargetPrimitiveComponent = NULL; + owningVRChar = NULL; +} + +void UVRRootComponent::SetTrackingPaused(bool bPaused) +{ + bPauseTracking = bPaused; +} + +void UVRRootComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + + if (this->IsSimulatingPhysics()) + { + return Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + } + + // Skip updates and stay in place if we have paused tracking to the HMD + if (bPauseTracking) + { + bHadRelativeMovement = false; + DifferenceFromLastFrame = FVector::ZeroVector; + return; + } + + UVRBaseCharacterMovementComponent * CharMove = nullptr; + + // Need these for passing physics updates to character movement + if (ACharacter * OwningCharacter = Cast<ACharacter>(GetOwner())) + { + CharMove = Cast<UVRBaseCharacterMovementComponent>(OwningCharacter->GetCharacterMovement()); + } + + if (IsLocallyControlled()) + { + if (owningVRChar && owningVRChar->bTrackingPaused) + { + curCameraLoc = owningVRChar->PausedTrackingLoc; + curCameraRot = FRotator(0.f, owningVRChar->PausedTrackingRot, 0.f); + } + else if (OptionalWaistTrackingParent.IsValid()) + { + FTransform NewTrans = IVRTrackedParentInterface::Default_GetWaistOrientationAndPosition(OptionalWaistTrackingParent); + curCameraLoc = NewTrans.GetTranslation(); + curCameraRot = NewTrans.Rotator(); + } + else if (GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowedForWorld(*GetWorld())) + { + FQuat curRot; + if (!GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, curRot, curCameraLoc)) + { + curCameraLoc = lastCameraLoc; + curCameraRot = lastCameraRot; + } + else + { + if (owningVRChar && owningVRChar->VRReplicatedCamera) + { + owningVRChar->VRReplicatedCamera->ApplyTrackingParameters(curCameraLoc); + } + + curCameraRot = curRot.Rotator(); + } + } + else if (TargetPrimitiveComponent) + { + curCameraRot = TargetPrimitiveComponent->GetRelativeRotation(); + curCameraLoc = TargetPrimitiveComponent->GetRelativeLocation(); + } + else + { + curCameraRot = FRotator::ZeroRotator; + curCameraLoc = FVector::ZeroVector; + } + + // Store a leveled yaw value here so it is only calculated once + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curCameraRot); + + // Pre-Process this for network sends + curCameraLoc.X = FMath::RoundToFloat(curCameraLoc.X * 100.f) / 100.f; + curCameraLoc.Y = FMath::RoundToFloat(curCameraLoc.Y * 100.f) / 100.f; + curCameraLoc.Z = FMath::RoundToFloat(curCameraLoc.Z * 100.f) / 100.f; + + // Can adjust the relative tolerances to remove jitter and some update processing + if (!curCameraLoc.Equals(lastCameraLoc, 0.01f) || !curCameraRot.Equals(lastCameraRot, 0.01f)) + { + // Also calculate vector of movement for the movement component + FVector LastPosition = OffsetComponentToWorld.GetLocation(); + + bCalledUpdateTransform = false; + + // If the character movement doesn't exist or is not active/ticking + if (!CharMove || !CharMove->IsComponentTickEnabled() || !CharMove->IsActive()) + { + OnUpdateTransform(EUpdateTransformFlags::None, ETeleportType::None); + } + else // Let the character movement move the capsule instead + { + // Skip physics update, let the movement component handle it instead + OnUpdateTransform(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + } + + // Get the correct next transform to use + /*FTransform NextTransform; + if (bOffsetByHMD) // Manually generate it, the current isn't correct + { + FVector Camdiff = curCameraLoc - lastCameraLoc; + NextTransform = FTransform(StoredCameraRotOffset.Quaternion(), FVector(Camdiff.X, Camdiff.Y, bCenterCapsuleOnHMD ? curCameraLoc.Z : CapsuleHalfHeight) + StoredCameraRotOffset.RotateVector(VRCapsuleOffset), FVector(1.0f)) * GetComponentTransform(); + } + else + NextTransform = OffsetComponentToWorld;*/ + + FHitResult OutHit; + FCollisionQueryParams Params("RelativeMovementSweep", false, GetOwner()); + FCollisionResponseParams ResponseParam; + + InitSweepCollisionParams(Params, ResponseParam); + Params.bFindInitialOverlaps = true; + bool bBlockingHit = false; + + + if (bUseWalkingCollisionOverride) + { + bool bAllowWalkingCollision = false; + if (CharMove != nullptr) + { + if (CharMove->MovementMode == EMovementMode::MOVE_Walking || CharMove->MovementMode == EMovementMode::MOVE_NavWalking) + bAllowWalkingCollision = true; + } + + if (bAllowWalkingCollision) + bBlockingHit = GetWorld()->SweepSingleByChannel(OutHit, LastPosition, OffsetComponentToWorld.GetLocation()/*NextTransform.GetLocation()*/, FQuat::Identity, WalkingCollisionOverride, GetCollisionShape(), Params, ResponseParam); + + if (bBlockingHit && OutHit.Component.IsValid()) + { + if (CharMove != nullptr && CharMove->bIgnoreSimulatingComponentsInFloorCheck && OutHit.Component->IsSimulatingPhysics()) + bHadRelativeMovement = false; + else + bHadRelativeMovement = true; + } + else + bHadRelativeMovement = false; + } + else + bHadRelativeMovement = true; + + if (bHadRelativeMovement) + { + DifferenceFromLastFrame = OffsetComponentToWorld.GetLocation() - LastPosition; + //DifferenceFromLastFrame = (NextTransform.GetLocation() - LastPosition);// .GetSafeNormal2D(); + DifferenceFromLastFrame.X = FMath::RoundToFloat(DifferenceFromLastFrame.X * 100.f) / 100.f; + DifferenceFromLastFrame.Y = FMath::RoundToFloat(DifferenceFromLastFrame.Y * 100.f) / 100.f; + DifferenceFromLastFrame.Z = 0.0f; // Reset Z to zero, its not used anyway and this lets me reuse the Z component for capsule half height + } + else // Zero it out so we don't process off of the change (multiplayer sends this) + DifferenceFromLastFrame = FVector::ZeroVector; + } + else + { + bHadRelativeMovement = false; + DifferenceFromLastFrame = FVector::ZeroVector; + } + + lastCameraLoc = curCameraLoc; + lastCameraRot = curCameraRot; + } + else + { + if (owningVRChar && owningVRChar->bTrackingPaused) + { + curCameraLoc = owningVRChar->PausedTrackingLoc; + curCameraRot = FRotator(0.f, owningVRChar->PausedTrackingRot, 0.f); + } + else if (TargetPrimitiveComponent) + { + curCameraRot = TargetPrimitiveComponent->GetRelativeRotation(); + curCameraLoc = TargetPrimitiveComponent->GetRelativeLocation(); + } + else + { + curCameraRot = FRotator(0.0f, 0.0f, 0.0f);// = FRotator::ZeroRotator; + curCameraLoc = FVector(0.0f, 0.0f, 0.0f);//FVector::ZeroVector; + } + + // Store a leveled yaw value here so it is only calculated once + StoredCameraRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curCameraRot); + + // Can adjust the relative tolerances to remove jitter and some update processing + if (!curCameraLoc.Equals(lastCameraLoc, 0.01f) || !curCameraRot.Equals(lastCameraRot, 0.01f)) + { + bCalledUpdateTransform = false; + + // If the character movement doesn't exist or is not active/ticking + if (!CharMove || !CharMove->IsActive()) + { + OnUpdateTransform(EUpdateTransformFlags::None, ETeleportType::None); + if (bNavigationRelevant && bRegistered) + { + UpdateNavigationData(); + PostUpdateNavigationData(); + } + } + else // Let the character movement move the capsule instead + { + // Skip physics update, let the movement component handle it instead + OnUpdateTransform(EUpdateTransformFlags::SkipPhysicsUpdate, ETeleportType::None); + + // This is an edge case, need to check if the nav data needs updated client side + if (this->GetOwner()->GetLocalRole() == ENetRole::ROLE_SimulatedProxy) + { + if (bNavigationRelevant && bRegistered) + { + UpdateNavigationData(); + PostUpdateNavigationData(); + } + } + } + + + lastCameraRot = curCameraRot; + lastCameraLoc = curCameraLoc; + } + } + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); +} + + +void UVRRootComponent::SendPhysicsTransform(ETeleportType Teleport) +{ + BodyInstance.SetBodyTransform(OffsetComponentToWorld, Teleport); + BodyInstance.UpdateBodyScale(OffsetComponentToWorld.GetScale3D()); +} + +void UVRRootComponent::SetSimulatePhysics(bool bSimulate) +{ + Super::SetSimulatePhysics(bSimulate); + + if (bSimulate) + { + if (AVRCharacter* OwningCharacter = Cast<AVRCharacter>(GetOwner())) + { + OwningCharacter->NetSmoother->SetRelativeLocation(FVector(0.f,0.f, -this->GetUnscaledCapsuleHalfHeight())); + } + this->AddWorldOffset(this->GetComponentRotation().RotateVector(FVector(0.f, 0.f, this->GetScaledCapsuleHalfHeight())), false, nullptr, ETeleportType::TeleportPhysics); + } + else + { + if (AVRCharacter* OwningCharacter = Cast<AVRCharacter>(GetOwner())) + { + OwningCharacter->NetSmoother->SetRelativeLocation(FVector(0.f, 0.f, 0)); + } + this->AddWorldOffset(this->GetComponentRotation().RotateVector(FVector(0.f, 0.f, -this->GetScaledCapsuleHalfHeight())), false, nullptr, ETeleportType::TeleportPhysics); + } +} + +// Override this so that the physics representation is in the correct location +void UVRRootComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + if (this->IsSimulatingPhysics()) + { + if (this->ShouldRender() && this->SceneProxy) + { + FTransform lOffsetComponentToWorld = OffsetComponentToWorld; + float lCapsuleHalfHeight = CapsuleHalfHeight; + bool bIsSimulating = this->IsSimulatingPhysics(); + FDrawVRCylinderSceneProxy* CylinderSceneProxy = (FDrawVRCylinderSceneProxy*)SceneProxy; + ENQUEUE_RENDER_COMMAND(VRRootComponent_SendNewDebugTransform)( + [CylinderSceneProxy, lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating](FRHICommandList& RHICmdList) + { + CylinderSceneProxy->UpdateTransform_RenderThread(lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating); + }); + } + + return Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + } + + GenerateOffsetToWorld(); + // Using the physics flag for all of this anyway, no reason for a custom flag, it handles it fine + if (!(UpdateTransformFlags & EUpdateTransformFlags::SkipPhysicsUpdate)) + { + bCalledUpdateTransform = true; + + // Just using the + if (this->ShouldRender() && this->SceneProxy) + { + /*ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER( + FDrawCylinderTransformUpdate, + FDrawVRCylinderSceneProxy*, CylinderSceneProxy, (FDrawVRCylinderSceneProxy*)SceneProxy, + FTransform, OffsetComponentToWorld, OffsetComponentToWorld, float, CapsuleHalfHeight, CapsuleHalfHeight, + { + CylinderSceneProxy->UpdateTransform_RenderThread(OffsetComponentToWorld, CapsuleHalfHeight); + } + );*/ + + FTransform lOffsetComponentToWorld = OffsetComponentToWorld; + float lCapsuleHalfHeight = CapsuleHalfHeight; + bool bIsSimulating = this->IsSimulatingPhysics(); + FDrawVRCylinderSceneProxy* CylinderSceneProxy = (FDrawVRCylinderSceneProxy*)SceneProxy; + ENQUEUE_RENDER_COMMAND(VRRootComponent_SendNewDebugTransform)( + [CylinderSceneProxy, lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating](FRHICommandList& RHICmdList) + { + CylinderSceneProxy->UpdateTransform_RenderThread(lOffsetComponentToWorld, lCapsuleHalfHeight, bIsSimulating); + }); + + } + + // Don't want to call primitives version, and the scenecomponents version does nothing + //Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + // Always send new transform to physics + if (bPhysicsStateCreated) + { + //If we update transform of welded bodies directly (i.e. on the actual component) we need to update the shape transforms of the parent. + //If the parent is updated, any welded shapes are automatically updated so we don't need to do this physx update. + //If the parent is updated and we are NOT welded, the child still needs to update physics + const bool bTransformSetDirectly = !(UpdateTransformFlags & EUpdateTransformFlags::PropagateFromParent); + if (bTransformSetDirectly || !IsWelded()) + { + SendPhysicsTransform(Teleport); + } + } + } +} + +FBoxSphereBounds UVRRootComponent::CalcBounds(const FTransform& LocalToWorld) const +{ + FVector BoxPoint = FVector(CapsuleRadius, CapsuleRadius, CapsuleHalfHeight); + //FRotator CamRotOffset(0.0f, curCameraRot.Yaw, 0.0f); + + //FRotator CamRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw(curCameraRot); + /*if(bOffsetByHMD) + return FBoxSphereBounds(FVector(0, 0, CapsuleHalfHeight) + StoredCameraRotOffset.RotateVector(VRCapsuleOffset), BoxPoint, BoxPoint.Size()).TransformBy(LocalToWorld); + else*/ + return FBoxSphereBounds(FVector(curCameraLoc.X, curCameraLoc.Y, CapsuleHalfHeight) + StoredCameraRotOffset.RotateVector(VRCapsuleOffset), BoxPoint, BoxPoint.Size()).TransformBy(LocalToWorld); + +} + +void UVRRootComponent::GetNavigationData(FNavigationRelevantData& Data) const +{ + if (bDynamicObstacle) + { + //Data.Modifiers.CreateAreaModifiers(this, AreaClass); + UBodySetup* BodySetup = ((UPrimitiveComponent*)this)->GetBodySetup(); + if (BodySetup == nullptr) + { + return; + } + + for (int32 Idx = 0; Idx < BodySetup->AggGeom.BoxElems.Num(); Idx++) + { + const FKBoxElem& BoxElem = BodySetup->AggGeom.BoxElems[Idx]; + const FBox BoxSize = BoxElem.CalcAABB(FTransform::Identity, 1.0f); + + FAreaNavModifier AreaMod(BoxSize, OffsetComponentToWorld, AreaClass); + Data.Modifiers.Add(AreaMod); + } + + for (int32 Idx = 0; Idx < BodySetup->AggGeom.SphylElems.Num(); Idx++) + { + const FKSphylElem& SphylElem = BodySetup->AggGeom.SphylElems[Idx]; + const FTransform AreaOffset(FVector(0, 0, -SphylElem.Length)); + + FAreaNavModifier AreaMod(SphylElem.Radius, SphylElem.Length * 2.0f, AreaOffset * OffsetComponentToWorld, AreaClass); + Data.Modifiers.Add(AreaMod); + } + + for (int32 Idx = 0; Idx < BodySetup->AggGeom.ConvexElems.Num(); Idx++) + { + const FKConvexElem& ConvexElem = BodySetup->AggGeom.ConvexElems[Idx]; + + FAreaNavModifier AreaMod(ConvexElem.VertexData, 0, ConvexElem.VertexData.Num(), ENavigationCoordSystem::Unreal, OffsetComponentToWorld, AreaClass); + Data.Modifiers.Add(AreaMod); + } + + for (int32 Idx = 0; Idx < BodySetup->AggGeom.SphereElems.Num(); Idx++) + { + const FKSphereElem& SphereElem = BodySetup->AggGeom.SphereElems[Idx]; + const FTransform AreaOffset(FVector(0, 0, -SphereElem.Radius)); + + FAreaNavModifier AreaMod(SphereElem.Radius, SphereElem.Radius * 2.0f, AreaOffset * OffsetComponentToWorld, AreaClass); + Data.Modifiers.Add(AreaMod); + } + } +} + +#if WITH_EDITOR +void UVRRootComponent::PreEditChange(FProperty* PropertyThatWillChange) +{ + // This is technically not correct at all to do...however when overloading a root component the preedit gets called twice for some reason. + // Calling it twice attempts to double register it in the list and causes an assert to be thrown. + if (this->GetOwner()->IsA(AVRCharacter::StaticClass())) + return; + else + Super::PreEditChange(PropertyThatWillChange); +} + +void UVRRootComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None; + + // We only want to modify the property that was changed at this point + // things like propagation from CDO to instances don't work correctly if changing one property causes a different property to change + if (PropertyName == GET_MEMBER_NAME_CHECKED(UVRRootComponent, CapsuleHalfHeight)) + { + CapsuleHalfHeight = FMath::Max3(0.f, CapsuleHalfHeight, CapsuleRadius); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UVRRootComponent, CapsuleRadius)) + { + CapsuleRadius = FMath::Clamp(CapsuleRadius, 0.f, CapsuleHalfHeight); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UVRRootComponent, VRCapsuleOffset)) + { + } + + if (!IsTemplate()) + { + //UpdateBodySetup(); // do this before reregistering components so that new values are used for collision + } + + return; + + // Overrode the defaults for this, don't call the parent + //Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif // WITH_EDITOR + + +// This overrides the movement logic to use the offset location instead of the default location for sweeps. +bool UVRRootComponent::MoveComponentImpl(const FVector& Delta, const FQuat& NewRotationQuat, bool bSweep, FHitResult* OutHit, EMoveComponentFlags MoveFlags, ETeleportType Teleport) +{ + SCOPE_CYCLE_COUNTER(STAT_VRRootMovement); + //CSV_SCOPED_TIMING_STAT(PrimitiveComponent, MoveComponentTime); + + // static things can move before they are registered (e.g. immediately after streaming), but not after. + if (!IsValid(this) || (this->Mobility == EComponentMobility::Static && IsRegistered()))//|| CheckStaticMobilityAndWarn(PrimitiveComponentStatics::MobilityWarnText)) + { + if (OutHit) + { + OutHit->Init(); + } + return false; + } + + const bool bSkipPhysicsMove = ((MoveFlags & MOVECOMP_SkipPhysicsMove) != MOVECOMP_NoFlags); + + if (!this->IsSimulatingPhysics() && bSkipPhysicsMove) + { + // Phys thread is updating this when we don't want it to, stop it chaos! + return false; + } + + ConditionalUpdateComponentToWorld(); + + // Init HitResult + //FHitResult BlockingHit(1.f); + const FVector TraceStart = OffsetComponentToWorld.GetLocation();// .GetLocation();//GetComponentLocation(); + const FVector TraceEnd = TraceStart + Delta; + //BlockingHit.TraceStart = TraceStart; + //BlockingHit.TraceEnd = TraceEnd; + float DeltaSizeSq = (TraceEnd - TraceStart).SizeSquared(); // Recalc here to account for precision loss of float addition + + // Set up. +// float DeltaSizeSq = Delta.SizeSquared(); + const FQuat InitialRotationQuat = GetComponentTransform().GetRotation();//ComponentToWorld.GetRotation(); + + // ComponentSweepMulti does nothing if moving < KINDA_SMALL_NUMBER in distance, so it's important to not try to sweep distances smaller than that. + const float MinMovementDistSq = (bSweep ? FMath::Square(4.f*KINDA_SMALL_NUMBER) : 0.f); + if (DeltaSizeSq <= MinMovementDistSq) + { + // Skip if no vector or rotation. + if (NewRotationQuat.Equals(InitialRotationQuat, SCENECOMPONENT_QUAT_TOLERANCE)) + { + // copy to optional output param + if (OutHit) + { + OutHit->Init(TraceStart, TraceEnd); + } + return true; + } + DeltaSizeSq = 0.f; + } + + //const bool bSkipPhysicsMove = ((MoveFlags & MOVECOMP_SkipPhysicsMove) != MOVECOMP_NoFlags); + + // WARNING: HitResult is only partially initialized in some paths. All data is valid only if bFilledHitResult is true. + FHitResult BlockingHit(NoInit); + BlockingHit.bBlockingHit = false; + BlockingHit.Time = 1.f; + bool bFilledHitResult = false; + bool bMoved = false; + bool bIncludesOverlapsAtEnd = false; + bool bRotationOnly = false; + TInlineOverlapInfoArray PendingOverlaps; + AActor* const Actor = GetOwner(); + FVector OrigLocation = GetComponentLocation(); + + if (!bSweep) + { + // not sweeping, just go directly to the new transform + bMoved = InternalSetWorldLocationAndRotation(/*TraceEnd*/OrigLocation + Delta, NewRotationQuat, bSkipPhysicsMove, Teleport); + GenerateOffsetToWorld(); + bRotationOnly = (DeltaSizeSq == 0); + bIncludesOverlapsAtEnd = bRotationOnly && (AreSymmetricRotations(InitialRotationQuat, NewRotationQuat, GetComponentScale())) && IsCollisionEnabled(); + } + else + { + TArray<FHitResult> Hits; + FVector NewLocation = OrigLocation;//TraceStart; + // Perform movement collision checking if needed for this actor. + const bool bCollisionEnabled = IsQueryCollisionEnabled(); + UWorld* const MyWorld = GetWorld(); + if (MyWorld && bCollisionEnabled && (DeltaSizeSq > 0.f)) + { +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (!IsRegistered() && !MyWorld->bIsTearingDown) + { + if (Actor) + { + ensureMsgf(IsRegistered(), TEXT("%s MovedComponent %s not registered during sweep (IsValid %d)"), *Actor->GetName(), *GetName(), IsValid(Actor)); + } + else + { //-V523 + ensureMsgf(IsRegistered(), TEXT("Non-actor MovedComponent %s not registered during sweep"), *GetFullName()); + } + } +#endif +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) && PERF_MOVECOMPONENT_STATS + MoveTimer.bDidLineCheck = true; +#endif + static const FName TraceTagName = TEXT("MoveComponent"); + const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); + FComponentQueryParams Params(/*PrimitiveComponentStatics::MoveComponentName*//*"MoveComponent"*/SCENE_QUERY_STAT(MoveComponent), Actor); + FCollisionResponseParams ResponseParam; + InitSweepCollisionParams(Params, ResponseParam); + Params.bIgnoreTouches |= !(GetGenerateOverlapEvents() || bForceGatherOverlaps); + Params.TraceTag = TraceTagName; + bool const bHadBlockingHit = MyWorld->ComponentSweepMulti(Hits, this, TraceStart, TraceEnd, InitialRotationQuat, Params); + //bool const bHadBlockingHit = MyWorld->SweepMultiByChannel(Hits, TraceStart, TraceEnd, InitialRotationQuat, this->GetCollisionObjectType(), this->GetCollisionShape(), Params, ResponseParam); + + if (Hits.Num() > 0) + { + const float DeltaSize = FMath::Sqrt(DeltaSizeSq); + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + PullBackHit(Hits[HitIdx], TraceStart, TraceEnd, DeltaSize); + } + } + + // If we had a valid blocking hit, store it. + // If we are looking for overlaps, store those as well. + int32 FirstNonInitialOverlapIdx = INDEX_NONE; + if (bHadBlockingHit || (GetGenerateOverlapEvents() || bForceGatherOverlaps)) + { + int32 BlockingHitIndex = INDEX_NONE; + float BlockingHitNormalDotDelta = BIG_NUMBER; + for (int32 HitIdx = 0; HitIdx < Hits.Num(); HitIdx++) + { + const FHitResult& TestHit = Hits[HitIdx]; + + if (TestHit.bBlockingHit) + { + if (!ShouldIgnoreHitResult(MyWorld, bAllowSimulatingCollision, TestHit, Delta, Actor, MoveFlags)) + { + if (TestHit.bStartPenetrating) + { + // We may have multiple initial hits, and want to choose the one with the normal most opposed to our movement. + const float NormalDotDelta = (TestHit.ImpactNormal | Delta); + if (NormalDotDelta < BlockingHitNormalDotDelta) + { + BlockingHitNormalDotDelta = NormalDotDelta; + BlockingHitIndex = HitIdx; + } + } + else if (BlockingHitIndex == INDEX_NONE) + { + // First non-overlapping blocking hit should be used, if an overlapping hit was not. + // This should be the only non-overlapping blocking hit, and last in the results. + BlockingHitIndex = HitIdx; + break; + } + } + } + else if (GetGenerateOverlapEvents() || bForceGatherOverlaps) + { + UPrimitiveComponent* OverlapComponent = TestHit.Component.Get(); + if (OverlapComponent && (OverlapComponent->GetGenerateOverlapEvents() || bForceGatherOverlaps)) + { + if (!ShouldIgnoreOverlapResult(MyWorld, Actor, *this, TestHit.HitObjectHandle.FetchActor(), *OverlapComponent,!bForceGatherOverlaps)) + { + // don't process touch events after initial blocking hits + if (BlockingHitIndex >= 0 && TestHit.Time > Hits[BlockingHitIndex].Time) + { + break; + } + + if (FirstNonInitialOverlapIdx == INDEX_NONE && TestHit.Time > 0.f) + { + // We are about to add the first non-initial overlap. + FirstNonInitialOverlapIdx = PendingOverlaps.Num(); + } + + // cache touches + AddUniqueOverlapFast(PendingOverlaps, FOverlapInfo(TestHit)); + } + } + } + } + + // Update blocking hit, if there was a valid one. + if (BlockingHitIndex >= 0) + { + BlockingHit = Hits[BlockingHitIndex]; + bFilledHitResult = true; + } + } + + // Update NewLocation based on the hit result + if (!BlockingHit.bBlockingHit) + { + NewLocation += (TraceEnd - TraceStart); + } + else + { + check(bFilledHitResult); + NewLocation += (BlockingHit.Time * (TraceEnd - TraceStart)); + + // Sanity check + const FVector ToNewLocation = (NewLocation - OrigLocation/*TraceStart*/); + if (ToNewLocation.SizeSquared() <= MinMovementDistSq) + { + // We don't want really small movements to put us on or inside a surface. + NewLocation = OrigLocation;//TraceStart; + BlockingHit.Time = 0.f; + + // Remove any pending overlaps after this point, we are not going as far as we swept. + if (FirstNonInitialOverlapIdx != INDEX_NONE) + { + const bool bAllowShrinking = false; + PendingOverlaps.SetNum(FirstNonInitialOverlapIdx, bAllowShrinking); + } + } + } + + bIncludesOverlapsAtEnd = AreSymmetricRotations(InitialRotationQuat, NewRotationQuat, GetComponentScale()); + } + else if (DeltaSizeSq > 0.f) + { + // apply move delta even if components has collisions disabled + NewLocation += Delta; + bIncludesOverlapsAtEnd = false; + } + else if (DeltaSizeSq == 0.f && bCollisionEnabled) + { + bIncludesOverlapsAtEnd = AreSymmetricRotations(InitialRotationQuat, NewRotationQuat, GetComponentScale()); + bRotationOnly = true; + } + + // Update the location. This will teleport any child components as well (not sweep). + bMoved = InternalSetWorldLocationAndRotation(NewLocation, NewRotationQuat, bSkipPhysicsMove, Teleport); + GenerateOffsetToWorld(); + } + + // Handle overlap notifications. + if (bMoved) + { + if (IsDeferringMovementUpdates()) + { + // Defer UpdateOverlaps until the scoped move ends. + FScopedMovementUpdate* ScopedUpdate = GetCurrentScopedMovement(); + if (bRotationOnly && bIncludesOverlapsAtEnd) + { + ScopedUpdate->KeepCurrentOverlapsAfterRotation(bSweep); + } + else + { + ScopedUpdate->AppendOverlapsAfterMove(PendingOverlaps, bSweep, bIncludesOverlapsAtEnd); + } + } + else + { + if (bIncludesOverlapsAtEnd) + { + TInlineOverlapInfoArray OverlapsAtEndLocation; + bool bHasEndOverlaps = false; + if (bRotationOnly) + { + // bHasEndOverlaps = ConvertRotationOverlapsToCurrentOverlaps(OverlapsAtEndLocation, OverlappingComponents); + } + else + { + // bHasEndOverlaps = ConvertSweptOverlapsToCurrentOverlaps(OverlapsAtEndLocation, PendingOverlaps, 0, OffsetComponentToWorld.GetLocation(), GetComponentQuat()); + } + TOverlapArrayView PendingOverlapsView(PendingOverlaps); + TOverlapArrayView OverlapsAtEndView(OverlapsAtEndLocation); + UpdateOverlaps(&PendingOverlapsView, true, bHasEndOverlaps ? &OverlapsAtEndView : nullptr); + } + else + { + TOverlapArrayView PendingOverlapsView(PendingOverlaps); + UpdateOverlaps(&PendingOverlapsView, true, nullptr); + } + } + } + + // Handle blocking hit notifications. Avoid if pending kill (which could happen after overlaps). + const bool bAllowHitDispatch = !BlockingHit.bStartPenetrating || !(MoveFlags & MOVECOMP_DisableBlockingOverlapDispatch); + if (BlockingHit.bBlockingHit && bAllowHitDispatch && IsValid(this)) + { + check(bFilledHitResult); + if (IsDeferringMovementUpdates()) + { + FScopedMovementUpdate* ScopedUpdate = GetCurrentScopedMovement(); + ScopedUpdate->AppendBlockingHitAfterMove(BlockingHit); + } + else + { + DispatchBlockingHit(*Actor, BlockingHit); + } + } + + // copy to optional output param + if (OutHit) + { + if (bFilledHitResult) + { + *OutHit = BlockingHit; + } + else + { + OutHit->Init(TraceStart, TraceEnd); + } + } + + // Return whether we moved at all. + return bMoved; +} + +bool UVRRootComponent::UpdateOverlapsImpl(const TOverlapArrayView* NewPendingOverlaps, bool bDoNotifies, const TOverlapArrayView* OverlapsAtEndLocation) +{ + //SCOPE_CYCLE_COUNTER(STAT_UpdateOverlaps); + SCOPE_CYCLE_COUNTER(STAT_UpdateOverlapsVRRoot); + SCOPE_CYCLE_UOBJECT(ComponentScope, this); + + // if we haven't begun play, we're still setting things up (e.g. we might be inside one of the construction scripts) + // so we don't want to generate overlaps yet. There is no need to update children yet either, they will update once we are allowed to as well. + const AActor* const MyActor = GetOwner(); + if (MyActor && !MyActor->HasActorBegunPlay() && !MyActor->IsActorBeginningPlay()) + { + return false; + } + + bool bCanSkipUpdateOverlaps = true; + + // first, dispatch any pending overlaps + if (GetGenerateOverlapEvents() && IsQueryCollisionEnabled()) //TODO: should modifying query collision remove from mayoverlapevents? + { + bCanSkipUpdateOverlaps = false; + + if (MyActor) + { + const FTransform PrevTransform = GetComponentTransform(); + // If we are the root component we ignore child components. Those children will update their overlaps when we descend into the child tree. + // This aids an optimization in MoveComponent. + const bool bIgnoreChildren = (MyActor->GetRootComponent() == this); + + if (NewPendingOverlaps) + { + // Note: BeginComponentOverlap() only triggers overlaps where GetGenerateOverlapEvents() is true on both components. + const int32 NumNewPendingOverlaps = NewPendingOverlaps->Num(); + for (int32 Idx = 0; Idx < NumNewPendingOverlaps; ++Idx) + { + BeginComponentOverlap((*NewPendingOverlaps)[Idx], bDoNotifies); + } + } + + const TOverlapArrayView* OverlapsAtEndLocationPtr = OverlapsAtEndLocation; + + // #TODO: Filter this better so it runs even less often? + // Its not that bad currently running off of NewPendingOverlaps + // It forces checking for end location overlaps again if none are registered, just in case + // the capsule isn't setting things correctly. + + TArray<FOverlapInfo> OverlapsAtEnd; + TOverlapArrayView OverlapsAtEndLoc; + if (/*(!OverlapsAtEndLocation || OverlapsAtEndLocation->Num() < 1) &&*/ NewPendingOverlaps && NewPendingOverlaps->Num() > 0) + { + ConvertSweptOverlapsToCurrentOverlaps(OverlapsAtEnd, *NewPendingOverlaps, -1, OffsetComponentToWorld.GetLocation(), GetComponentQuat()); + OverlapsAtEndLoc = TOverlapArrayView(OverlapsAtEnd); + OverlapsAtEndLocationPtr = &OverlapsAtEndLoc; + } + + // now generate full list of new touches, so we can compare to existing list and determine what changed + + TInlineOverlapInfoArray OverlapMultiResult; + TInlineOverlapPointerArray NewOverlappingComponentPtrs; + + // If pending kill, we should not generate any new overlaps. Also not if overlaps were just disabled during BeginComponentOverlap. + if (IsValid(this) && GetGenerateOverlapEvents()) + { + // 4.17 converted to auto cvar + static const auto CVarAllowCachedOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AllowCachedOverlaps")); + // Might be able to avoid testing for new overlaps at the end location. + if (OverlapsAtEndLocationPtr != nullptr && CVarAllowCachedOverlaps->GetInt() > 0 && PrevTransform.Equals(GetComponentTransform())) + { + UE_LOG(LogVRRootComponent, VeryVerbose, TEXT("%s->%s Skipping overlap test!"), *GetNameSafe(GetOwner()), *GetName()); + const bool bCheckForInvalid = (NewPendingOverlaps && NewPendingOverlaps->Num() > 0); + if (bCheckForInvalid) + { + // BeginComponentOverlap may have disabled what we thought were valid overlaps at the end (collision response or overlap flags could change). + GetPointersToArrayDataByPredicate(NewOverlappingComponentPtrs, *OverlapsAtEndLocationPtr, FPredicateFilterCanOverlap(*this)); + } + else + { + GetPointersToArrayData(NewOverlappingComponentPtrs, *OverlapsAtEndLocationPtr); + } + } + else + { + SCOPE_CYCLE_COUNTER(STAT_PerformOverlapQueryVR); + UE_LOG(LogVRRootComponent, VeryVerbose, TEXT("%s->%s Performing overlaps!"), *GetNameSafe(GetOwner()), *GetName()); + UWorld* const MyWorld = GetWorld(); + TArray<FOverlapResult> Overlaps; + // note this will optionally include overlaps with components in the same actor (depending on bIgnoreChildren). + + FComponentQueryParams Params(SCENE_QUERY_STAT(UpdateOverlaps), bIgnoreChildren ? MyActor : nullptr); //(PrimitiveComponentStatics::UpdateOverlapsName, bIgnoreChildren ? MyActor : nullptr); + + Params.bIgnoreBlocks = true; //We don't care about blockers since we only route overlap events to real overlaps + FCollisionResponseParams ResponseParam; + InitSweepCollisionParams(Params, ResponseParam); + ComponentOverlapMulti(Overlaps, MyWorld, OffsetComponentToWorld.GetTranslation(), GetComponentQuat(), GetCollisionObjectType(), Params); + + for (int32 ResultIdx = 0; ResultIdx < Overlaps.Num(); ResultIdx++) + { + const FOverlapResult& Result = Overlaps[ResultIdx]; + + UPrimitiveComponent* const HitComp = Result.Component.Get(); + if (HitComp && (HitComp != this) && HitComp->GetGenerateOverlapEvents()) + { + const bool bCheckOverlapFlags = false; // Already checked above + if (!ShouldIgnoreOverlapResult(MyWorld, MyActor, *this, Result.OverlapObjectHandle.FetchActor(), *HitComp, bCheckOverlapFlags)) + { + OverlapMultiResult.Emplace(HitComp, Result.ItemIndex); // don't need to add unique unless the overlap check can return dupes + } + } + } + + // Fill pointers to overlap results. We ensure below that OverlapMultiResult stays in scope so these pointers remain valid. + GetPointersToArrayData(NewOverlappingComponentPtrs, OverlapMultiResult); + } + } + + // If we have any overlaps from BeginComponentOverlap() (from now or in the past), see if anything has changed by filtering NewOverlappingComponents + if (OverlappingComponents.Num() > 0) + { + TInlineOverlapPointerArray OldOverlappingComponentPtrs; + if (bIgnoreChildren) + { + GetPointersToArrayDataByPredicate(OldOverlappingComponentPtrs, OverlappingComponents, FPredicateOverlapHasDifferentActor(*MyActor)); + } + else + { + GetPointersToArrayData(OldOverlappingComponentPtrs, OverlappingComponents); + } + + // Now we want to compare the old and new overlap lists to determine + // what overlaps are in old and not in new (need end overlap notifies), and + // what overlaps are in new and not in old (need begin overlap notifies). + // We do this by removing common entries from both lists, since overlapping status has not changed for them. + // What is left over will be what has changed. + for (int32 CompIdx = 0; CompIdx < OldOverlappingComponentPtrs.Num() && NewOverlappingComponentPtrs.Num() > 0; ++CompIdx) + { + // RemoveAtSwap is ok, since it is not necessary to maintain order + const bool bAllowShrinking = false; + + const FOverlapInfo* SearchItem = OldOverlappingComponentPtrs[CompIdx]; + const int32 NewElementIdx = IndexOfOverlapFast(NewOverlappingComponentPtrs, SearchItem); + if (NewElementIdx != INDEX_NONE) + { + NewOverlappingComponentPtrs.RemoveAtSwap(NewElementIdx, 1, bAllowShrinking); + OldOverlappingComponentPtrs.RemoveAtSwap(CompIdx, 1, bAllowShrinking); + --CompIdx; + } + } + + const int32 NumOldOverlaps = OldOverlappingComponentPtrs.Num(); + if (NumOldOverlaps > 0) + { + // Now we have to make a copy of the overlaps because we can't keep pointers to them, that list is about to be manipulated in EndComponentOverlap(). + TInlineOverlapInfoArray OldOverlappingComponents; + OldOverlappingComponents.SetNumUninitialized(NumOldOverlaps); + for (int32 i = 0; i < NumOldOverlaps; i++) + { + OldOverlappingComponents[i] = *(OldOverlappingComponentPtrs[i]); + } + + // OldOverlappingComponents now contains only previous overlaps that are confirmed to no longer be valid. + for (const FOverlapInfo& OtherOverlap : OldOverlappingComponents) + { + if (OtherOverlap.OverlapInfo.Component.IsValid()) + { + EndComponentOverlap(OtherOverlap, bDoNotifies, false); + } + else + { + // Remove stale item. Reclaim memory only if it's getting large, to try to avoid churn but avoid bloating component's memory usage. + const bool bAllowShrinking = (OverlappingComponents.Max() >= 24); + const int32 StaleElementIndex = IndexOfOverlapFast(OverlappingComponents, OtherOverlap); + if (StaleElementIndex != INDEX_NONE) + { + OverlappingComponents.RemoveAtSwap(StaleElementIndex, 1, bAllowShrinking); + } + } + } + } + } + + // Ensure these arrays are still in scope, because we kept pointers to them in NewOverlappingComponentPtrs. + static_assert(sizeof(OverlapMultiResult) != 0, "Variable must be in this scope"); + static_assert(sizeof(*OverlapsAtEndLocation) != 0, "Variable must be in this scope"); + + // NewOverlappingComponents now contains only new overlaps that didn't exist previously. + for (const FOverlapInfo* NewOverlap : NewOverlappingComponentPtrs) + { + BeginComponentOverlap(*NewOverlap, bDoNotifies); + } + } + } + else + { + // GetGenerateOverlapEvents() is false or collision is disabled + // End all overlaps that exist, in case GetGenerateOverlapEvents() was true last tick (i.e. was just turned off) + if (OverlappingComponents.Num() > 0) + { + const bool bSkipNotifySelf = false; + ClearComponentOverlaps(bDoNotifies, bSkipNotifySelf); + } + } + + // now update any children down the chain. + // since on overlap events could manipulate the child array we need to take a copy + // of it to avoid missing any children if one is removed from the middle + TInlineComponentArray<USceneComponent*> AttachedChildren; + AttachedChildren.Append(GetAttachChildren()); + + for (USceneComponent* const ChildComp : AttachedChildren) + { + if (ChildComp) + { + // Do not pass on OverlapsAtEndLocation, it only applied to this component. + bCanSkipUpdateOverlaps &= ChildComp->UpdateOverlaps(nullptr, bDoNotifies, nullptr); + } + } + + // Update physics volume using most current overlaps + if (GetShouldUpdatePhysicsVolume()) + { + UpdatePhysicsVolume(bDoNotifies); + bCanSkipUpdateOverlaps = false; + } + + return bCanSkipUpdateOverlaps; +} + + +template<typename AllocatorType> +bool UVRRootComponent::ConvertSweptOverlapsToCurrentOverlaps( + TArray<FOverlapInfo, AllocatorType>& OverlapsAtEndLocation, const TOverlapArrayView& SweptOverlaps, int32 SweptOverlapsIndex, + const FVector& EndLocation, const FQuat& EndRotationQuat) +{ + if (SweptOverlapsIndex == -1) + { + SweptOverlapsIndex = 0; + } + else + { + return false; + } + + checkSlow(SweptOverlapsIndex >= 0); + + // Override location check with our own + //GenerateOffsetToWorld(); + FVector EndLocationVR = OffsetComponentToWorld.GetLocation(); + + + bool bResult = false; + const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); + + static const auto CVarAllowCachedOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AllowCachedOverlaps")); + if ((GetGenerateOverlapEvents() || bForceGatherOverlaps) && CVarAllowCachedOverlaps->GetInt()) + { + const AActor* Actor = GetOwner(); + if (Actor && Actor->GetRootComponent() == this) + { + // We know we are not overlapping any new components at the end location. Children are ignored here (see note below). + if (bEnableFastOverlapCheck) + { + //SCOPE_CYCLE_COUNTER(STAT_MoveComponent_FastOverlap); + + // Check components we hit during the sweep, keep only those still overlapping + const FCollisionQueryParams UnusedQueryParams(NAME_None, FCollisionQueryParams::GetUnknownStatId()); + const int32 NumSweptOverlaps = SweptOverlaps.Num(); + OverlapsAtEndLocation.Reserve(OverlapsAtEndLocation.Num() + NumSweptOverlaps); + for (int32 Index = SweptOverlapsIndex; Index < NumSweptOverlaps; ++Index) + { + const FOverlapInfo& OtherOverlap = SweptOverlaps[Index]; + UPrimitiveComponent* OtherPrimitive = OtherOverlap.OverlapInfo.GetComponent(); + if (OtherPrimitive && (OtherPrimitive->GetGenerateOverlapEvents() || bForceGatherOverlaps)) + { + if (OtherPrimitive->bMultiBodyOverlap) + { + // Not handled yet. We could do it by checking every body explicitly and track each body index in the overlap test, but this seems like a rare need. + return false; + } + else if (Cast<USkeletalMeshComponent>(OtherPrimitive) || Cast<USkeletalMeshComponent>(this)) + { + // SkeletalMeshComponent does not support this operation, and would return false in the test when an actual query could return true. + return false; + } + else if (OtherPrimitive->ComponentOverlapComponent(this, EndLocationVR, EndRotationQuat, UnusedQueryParams)) + { + OverlapsAtEndLocation.Add(OtherOverlap); + } + } + } + + // Note: we don't worry about adding any child components here, because they are not included in the sweep results. + // Children test for their own overlaps after we update our own, and we ignore children in our own update. + checkfSlow(OverlapsAtEndLocation.FindByPredicate(FPredicateOverlapHasSameActor(*Actor)) == nullptr, + TEXT("Child overlaps should not be included in the SweptOverlaps() array in UPrimitiveComponent::ConvertSweptOverlapsToCurrentOverlaps().")); + + bResult = true; + } + else + { + if (SweptOverlaps.Num() == 0 && AreAllCollideableDescendantsRelative()) + { + // Add overlaps with components in this actor. + GetOverlapsWithActor_Template(Actor, OverlapsAtEndLocation); + bResult = true; + } + } + } + } + + return bResult; +} + + +template<typename AllocatorType> +bool UVRRootComponent::GetOverlapsWithActor_Template(const AActor* Actor, TArray<FOverlapInfo, AllocatorType>& OutOverlaps) const +{ + const int32 InitialCount = OutOverlaps.Num(); + if (Actor) + { + for (int32 OverlapIdx = 0; OverlapIdx < OverlappingComponents.Num(); ++OverlapIdx) + { + UPrimitiveComponent const* const PrimComp = OverlappingComponents[OverlapIdx].OverlapInfo.Component.Get(); + if (PrimComp && (PrimComp->GetOwner() == Actor)) + { + OutOverlaps.Add(OverlappingComponents[OverlapIdx]); + } + } + } + + return InitialCount != OutOverlaps.Num(); +} + +template<typename AllocatorType> +bool UVRRootComponent::ConvertRotationOverlapsToCurrentOverlaps(TArray<FOverlapInfo, AllocatorType>& OutOverlapsAtEndLocation, const TOverlapArrayView& CurrentOverlaps) +{ + bool bResult = false; + const bool bForceGatherOverlaps = !ShouldCheckOverlapFlagToQueueOverlaps(*this); + + static const auto CVarAllowCachedOverlaps = IConsoleManager::Get().FindConsoleVariable(TEXT("p.AllowCachedOverlaps")); + + if ((GetGenerateOverlapEvents() || bForceGatherOverlaps) && /*bAllowCachedOverlapsCVar*/ CVarAllowCachedOverlaps->GetInt()) + { + const AActor* Actor = GetOwner(); + if (Actor && Actor->GetRootComponent() == this) + { + if (bEnableFastOverlapCheck) + { + // Add all current overlaps that are not children. Children test for their own overlaps after we update our own, and we ignore children in our own update. + OutOverlapsAtEndLocation.Reserve(OutOverlapsAtEndLocation.Num() + CurrentOverlaps.Num()); + Algo::CopyIf(CurrentOverlaps, OutOverlapsAtEndLocation, FPredicateOverlapHasDifferentActor(*Actor)); + bResult = true; + } + } + } + + return bResult; +} + +bool UVRRootComponent::IsLocallyControlled() const +{ + // I like epics implementation better than my own + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast<APawn>(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner->Role == ENetRole::ROLE_Authority); +} + +void UVRRootComponent::UpdatePhysicsVolume(bool bTriggerNotifiers) +{ + if (GetShouldUpdatePhysicsVolume() && IsValid(this)) + { + // SCOPE_CYCLE_COUNTER(STAT_UpdatePhysicsVolume); + if (UWorld * MyWorld = GetWorld()) + { + if (MyWorld->GetNonDefaultPhysicsVolumeCount() == 0) + { + SetPhysicsVolume(MyWorld->GetDefaultPhysicsVolume(), bTriggerNotifiers); + } + else if (GetGenerateOverlapEvents() && IsQueryCollisionEnabled()) + { + APhysicsVolume* BestVolume = MyWorld->GetDefaultPhysicsVolume(); + int32 BestPriority = BestVolume->Priority; + + for (auto CompIt = OverlappingComponents.CreateIterator(); CompIt; ++CompIt) + { + const FOverlapInfo& Overlap = *CompIt; + UPrimitiveComponent* OtherComponent = Overlap.OverlapInfo.Component.Get(); + if (OtherComponent && OtherComponent->GetGenerateOverlapEvents()) + { + APhysicsVolume* V = Cast<APhysicsVolume>(OtherComponent->GetOwner()); + if (V && V->Priority > BestPriority) + { + //if (V->IsOverlapInVolume(*this)) + if (AreWeOverlappingVolume(V)) + { + BestPriority = V->Priority; + BestVolume = V; + } + } + } + } + + SetPhysicsVolume(BestVolume, bTriggerNotifiers); + } + else + { + Super::UpdatePhysicsVolume(bTriggerNotifiers); + } + } + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRStereoWidgetComponent.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRStereoWidgetComponent.cpp new file mode 100644 index 0000000..ff41a3f --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRStereoWidgetComponent.cpp @@ -0,0 +1,1165 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#include "VRStereoWidgetComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "IXRTrackingSystem.h" +#include "VRBaseCharacter.h" +#include "TextureResource.h" +#include "Engine/Texture.h" +#include "IStereoLayers.h" +#include "IHeadMountedDisplay.h" +#include "PrimitiveViewRelevance.h" +#include "PrimitiveSceneProxy.h" +#include "UObject/ConstructorHelpers.h" +#include "EngineGlobals.h" +#include "MaterialShared.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Engine/Engine.h" +//#include "Widgets/SWindow.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Framework/Application/SlateApplication.h" +#include "Kismet/KismetSystemLibrary.h" +//#include "Input/HittestGrid.h" +//#include "SceneManagement.h" +#include "DynamicMeshBuilder.h" +//#include "PhysicsEngine/BoxElem.h" +#include "PhysicsEngine/BodySetup.h" +#include "Slate/SGameLayerManager.h" +#include "Slate/SWorldWidgetScreenLayer.h" +#include "Widgets/SViewport.h" +#include "Widgets/SViewport.h" +#include "Slate/WidgetRenderer.h" +#include "Blueprint/UserWidget.h" +#include "Engine/TextureRenderTarget2D.h" +#include "StereoLayerShapes.h" + +// CVars +namespace StereoWidgetCvars +{ + static int32 ForceNoStereoWithVRWidgets = 0; + FAutoConsoleVariableRef CVarForceNoStereoWithVRWidgets( + TEXT("vr.ForceNoStereoWithVRWidgets"), + ForceNoStereoWithVRWidgets, + TEXT("When set to 0, will render stereo layer widgets as stereo by default.\n") + TEXT("When set to 1, will not allow stereo widget components to use stereo layers, will instead fall back to default widget rendering.\n") + TEXT("When set to 2, will render stereo layer widgets as both stereo and in game.\n") + TEXT("0: Default, 1: Force no stereo, 2: Render both at once"), + ECVF_Default); +} + +UVRStereoWidgetRenderComponent::UVRStereoWidgetRenderComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + Widget = nullptr; + WidgetRenderScale = 1.0f; + WidgetRenderGamma = 1.0f; + bUseGammaCorrection = false; + WidgetRenderer = nullptr; + RenderTarget = nullptr; + bDrawAtDesiredSize = true; + RenderTargetClearColor = FLinearColor::Black; + bDrawWithoutStereo = false; + DrawRate = 60.0f; + DrawCounter = 0.0f; + bLiveTexture = true; +} + +void UVRStereoWidgetRenderComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + + IStereoLayers* StereoLayers; + if (!GetVisibleFlag() || (!bDrawWithoutStereo && (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr))) + { + } + else + { + DrawCounter += DeltaTime; + + if (DrawRate > 0.0f && DrawCounter >= (1.0f / DrawRate)) + { + if (!IsRunningDedicatedServer()) + { + RenderWidget(DeltaTime); + } + + if (!bLiveTexture) + { + MarkStereoLayerDirty(); + } + + DrawCounter = 0.0f; + } + } + + if (bDrawWithoutStereo) + { + // Skip the stereo comps setup, we are just drawing to the texture + Super::Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + } + else + { + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + } +} + +void UVRStereoWidgetRenderComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (WidgetClass.Get() != nullptr) + { + InitWidget(); + + IStereoLayers* StereoLayers; + if (!GetVisibleFlag() || (!bDrawWithoutStereo && (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr))) + { + } + else + { + // Initial render + RenderWidget(0.0f); + } + } +} + +void UVRStereoWidgetRenderComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + ReleaseResources(); +} + +void UVRStereoWidgetRenderComponent::ReleaseResources() +{ + +#if !UE_SERVER + FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this); +#endif + if (Widget) + { + Widget = nullptr; + } + + if (SlateWidget) + { + SlateWidget = nullptr; + } + + if (WidgetRenderer) + { + BeginCleanup(WidgetRenderer); + WidgetRenderer = nullptr; + } + + Texture = nullptr; + + if (SlateWindow.IsValid()) + { + if (/*!CanReceiveHardwareInput() && */FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().UnregisterVirtualWindow(SlateWindow.ToSharedRef()); + } + + SlateWindow.Reset(); + } +} + +void UVRStereoWidgetRenderComponent::DestroyComponent(bool bPromoteChildren/*= false*/) +{ + Super::DestroyComponent(bPromoteChildren); + + ReleaseResources(); +} + +void UVRStereoWidgetRenderComponent::SetWidgetAndInit(TSubclassOf<UUserWidget> NewWidgetClass) +{ + WidgetClass = NewWidgetClass; + InitWidget(); +} + +void UVRStereoWidgetRenderComponent::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) +{ + // If the InLevel is null, it's a signal that the entire world is about to disappear, so + // go ahead and remove this widget from the viewport, it could be holding onto too many + // dangerous actor references that won't carry over into the next world. + if (InLevel == nullptr && InWorld == GetWorld()) + { + ReleaseResources(); + } +} + +void UVRStereoWidgetRenderComponent::InitWidget() +{ + if (IsTemplate()) + return; + +#if !UE_SERVER + FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &ThisClass::OnLevelRemovedFromWorld); +#endif + if (IsRunningDedicatedServer()) + return; + + if (Widget && Widget->GetClass() == WidgetClass) + return; + + if (Widget != nullptr) + { + Widget->MarkAsGarbage(); + Widget = nullptr; + } + + if (SlateWidget) + { + SlateWidget = nullptr; + } + + // Don't do any work if Slate is not initialized + if (FSlateApplication::IsInitialized()) + { + UWorld* World = GetWorld(); + + if (WidgetClass && Widget == nullptr && World && !World->bIsTearingDown) + { + Widget = CreateWidget(GetWorld(), WidgetClass); + Widget->SetRenderScale(FVector2D(1.0f, 1.0f)); + } + +#if WITH_EDITOR + if (Widget && !World->IsGameWorld())// && !bEditTimeUsable) + { + if (!GEnableVREditorHacks) + { + // Prevent native ticking of editor component previews + Widget->SetDesignerFlags(EWidgetDesignFlags::Designing); + } + } +#endif + + if (Widget) + { + SlateWidget = Widget->TakeWidget(); + } + + // Create the SlateWindow if it doesn't exists + if (!SlateWindow.IsValid()) + { + FVector2D DrawSize = this->GetQuadSize(); + SlateWindow = SNew(SVirtualWindow).Size(DrawSize); + SlateWindow->SetIsFocusable(false); + SlateWindow->SetVisibility(EVisibility::Visible); + SlateWindow->SetContentScale(FVector2D(1.0f, 1.0f)); + + if (Widget && !Widget->IsDesignTime()) + { + if (UWorld* LocalWorld = GetWorld()) + { + if (LocalWorld->IsGameWorld()) + { + UGameInstance* GameInstance = LocalWorld->GetGameInstance(); + check(GameInstance); + + UGameViewportClient* GameViewportClient = GameInstance->GetGameViewportClient(); + if (GameViewportClient) + { + SlateWindow->AssignParentWidget(GameViewportClient->GetGameViewportWidget()); + } + } + } + } + + } + + if (SlateWindow) + { + TSharedRef<SWidget> MyWidget = SlateWidget ? SlateWidget.ToSharedRef() : Widget->TakeWidget(); + SlateWindow->SetContent(MyWidget); + } + } +} + +void UVRStereoWidgetRenderComponent::RenderWidget(float DeltaTime) +{ + if (!Widget) + return; + + if (WidgetRenderer == nullptr) + { + WidgetRenderer = new FWidgetRenderer(bUseGammaCorrection); + check(WidgetRenderer); + } + + FVector2D DrawSize = this->GetQuadSize(); + FVector2D TextureSize = DrawSize; + + const int32 MaxAllowedDrawSize = GetMax2DTextureDimension(); + if (DrawSize.X <= 0 || DrawSize.Y <= 0 || DrawSize.X > MaxAllowedDrawSize || DrawSize.Y > MaxAllowedDrawSize) + { + return; + } + + TSharedRef<SWidget> MyWidget = SlateWidget ? SlateWidget.ToSharedRef() : Widget->TakeWidget(); + + if (bDrawAtDesiredSize) + { + SlateWindow->SlatePrepass(WidgetRenderScale); + + FVector2D DesiredSize = SlateWindow->GetDesiredSize(); + DesiredSize.X = FMath::RoundToInt(DesiredSize.X); + DesiredSize.Y = FMath::RoundToInt(DesiredSize.Y); + + if (!DesiredSize.IsNearlyZero()) + { + TextureSize = DesiredSize;// .IntPoint(); + + WidgetRenderer->SetIsPrepassNeeded(false); + + if (SlateWindow->GetSizeInScreen() != DesiredSize) + { + SlateWindow->Resize(TextureSize); + } + } + else + { + WidgetRenderer->SetIsPrepassNeeded(true); + } + } + else + { + WidgetRenderer->SetIsPrepassNeeded(true); + } + + if (RenderTarget == nullptr) + { + const EPixelFormat requestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat(); + RenderTarget = NewObject<UTextureRenderTarget2D>(); + check(RenderTarget); + RenderTarget->AddToRoot(); + RenderTarget->ClearColor = RenderTargetClearColor; + RenderTarget->TargetGamma = WidgetRenderGamma; + RenderTarget->InitCustomFormat(TextureSize.X, TextureSize.Y, requestedFormat /*PF_B8G8R8A8*/, false); + MarkStereoLayerDirty(); + } + else if (RenderTarget->GetResource()->GetSizeX() != TextureSize.X || RenderTarget->GetResource()->GetSizeY() != TextureSize.Y) + { + const EPixelFormat requestedFormat = FSlateApplication::Get().GetRenderer()->GetSlateRecommendedColorFormat(); + RenderTarget->InitCustomFormat(TextureSize.X, TextureSize.Y, requestedFormat /*PF_B8G8R8A8*/, false); + RenderTarget->UpdateResourceImmediate(); + MarkStereoLayerDirty(); + } + + WidgetRenderer->DrawWidget(RenderTarget, MyWidget, WidgetRenderScale, TextureSize, DeltaTime);//DeltaTime); + + if (Texture != RenderTarget) + { + Texture = RenderTarget; + } +} + + //============================================================================= +UVRStereoWidgetComponent::UVRStereoWidgetComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +// , bLiveTexture(false) + , bSupportsDepth(false) + , bNoAlphaChannel(false) + //, Texture(nullptr) + //, LeftTexture(nullptr) + , bQuadPreserveTextureRatio(false) + //, StereoLayerQuadSize(FVector2D(500.0f, 500.0f)) + , UVRect(FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f))) + //, CylinderRadius(100) + //, CylinderOverlayArc(100)EWidgetGeometryMode + //, CylinderHeight(50) + //, StereoLayerType(SLT_TrackerLocked) + //, StereoLayerShape(SLSH_QuadLayer) + , Priority(0) + , bIsDirty(true) + , bTextureNeedsUpdate(false) + , LayerId(0) + , LastTransform(FTransform::Identity) + , bLastVisible(false) +{ + bShouldCreateProxy = true; + bUseEpicsWorldLockedStereo = false; + // Replace quad size with DrawSize instead + //StereoLayerQuadSize = DrawSize; + + PrimaryComponentTick.TickGroup = TG_DuringPhysics; + + bIsDirty = true; + bDirtyRenderTarget = false; + bRenderBothStereoAndWorld = false; + bDrawWithoutStereo = false; + bDelayForRenderThread = false; + bIsSleeping = false; + //Texture = nullptr; +} + +//============================================================================= +UVRStereoWidgetComponent::~UVRStereoWidgetComponent() +{ +} + +void UVRStereoWidgetComponent::BeginDestroy() +{ + IStereoLayers* StereoLayers; + if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr) + { + StereoLayers->DestroyLayer(LayerId); + LayerId = 0; + } + + Super::BeginDestroy(); +} + + +void UVRStereoWidgetComponent::OnUnregister() +{ + IStereoLayers* StereoLayers; + if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr) + { + StereoLayers->DestroyLayer(LayerId); + LayerId = 0; + } + + Super::OnUnregister(); +} + +void UVRStereoWidgetComponent::DrawWidgetToRenderTarget(float DeltaTime) +{ + Super::DrawWidgetToRenderTarget(DeltaTime); + + bDirtyRenderTarget = true; +} + +void UVRStereoWidgetComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ + + // Precaching what the widget uses for draw time here as it gets modified in the super tick + bool bWidgetDrew = ShouldDrawWidget(); + + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + + if (IsRunningDedicatedServer()) + { + return; + } + + //bool bIsCurVis = IsWidgetVisible(); + + bool bIsVisible = IsVisible() && IsWidgetVisible() && !bIsSleeping;// && ((GetWorld()->TimeSince(GetLastRenderTime()) <= 0.5f)); + + // If we are set to not use stereo layers or we don't have a valid stereo layer device + if ( + StereoWidgetCvars::ForceNoStereoWithVRWidgets == 1 || + bDrawWithoutStereo || + !UVRExpansionFunctionLibrary::IsInVREditorPreviewOrGame() || + !GEngine->StereoRenderingDevice.IsValid() || + (GEngine->StereoRenderingDevice->GetStereoLayers() == nullptr) + ) + { + if (!bShouldCreateProxy) + { + bShouldCreateProxy = true; + //MarkRenderStateDirty(); // Recreate + + if (LayerId) + { + if (GEngine->StereoRenderingDevice.IsValid()) + { + IStereoLayers* StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers(); + if (StereoLayers) + StereoLayers->DestroyLayer(LayerId); + } + LayerId = 0; + } + } + + return; + } + else if (bRenderBothStereoAndWorld || StereoWidgetCvars::ForceNoStereoWithVRWidgets == 2) // Forcing both modes at once + { + if (!bShouldCreateProxy) + { + bShouldCreateProxy = true; + MarkRenderStateDirty(); // Recreate + } + } + else // Stereo only + { + if (bShouldCreateProxy) + { + bShouldCreateProxy = false; + MarkRenderStateDirty(); // Recreate + } + } + +#if !UE_SERVER + + // Same check that the widget runs prior to ticking + if (IsRunningDedicatedServer() || !GetSlateWindow() || GetSlateWindow()->GetContent() == SNullWidget::NullWidget) + { + return; + } + + IStereoLayers* StereoLayers; + if (!UVRExpansionFunctionLibrary::IsInVREditorPreviewOrGame() || !GEngine->StereoRenderingDevice.IsValid() || !RenderTarget) + { + return; + } + + StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers(); + + if (StereoLayers == nullptr) + return; + + FTransform Transform = LastTransform; + // Never true until epic fixes back end code + if (false)//StereoLayerType == SLT_WorldLocked) + { + Transform = GetComponentTransform(); + } + else if (Space == EWidgetSpace::Screen) + { + Transform = GetRelativeTransform(); + } + else if(bIsVisible) // World locked here now + { + + if (bUseEpicsWorldLockedStereo) + { + // Its incorrect......even in 4.17 + Transform = FTransform(FRotator(0.f,-180.f, 0.f)) * GetComponentTransform(); + //Transform.ConcatenateRotation(FRotator(0.0f, -180.0f, 0.0f).Quaternion()); + } + else + { + // Fix this when stereo world locked works again + // Thanks to mitch for the temp work around idea + + APlayerController* PC = nullptr; + if (UWorld * CurWorld = GetWorld()) + { + const ULocalPlayer* FirstPlayer = GEngine->GetFirstGamePlayer(CurWorld); + PC = FirstPlayer ? FirstPlayer->GetPlayerController(CurWorld) : nullptr; + } + + if (PC) + { + APawn * mpawn = PC->GetPawnOrSpectator(); + //bTextureNeedsUpdate = true; + if (mpawn) + { + + // Offset the transform by the widget pivot. + float DeltaY = (Pivot.X - 0.5f) * DrawSize.X; + float DeltaZ = (Pivot.Y - 0.5f) * DrawSize.Y; + FTransform OffsetTransform = FTransform(FVector(0.f, DeltaY, DeltaZ)); + OffsetTransform = OffsetTransform * GetComponentTransform(); + + // Set transform to this relative transform + + bool bHandledTransform = false; + if (AVRBaseCharacter* BaseVRChar = Cast<AVRBaseCharacter>(mpawn)) + { + if (USceneComponent* CameraParent = BaseVRChar->VRReplicatedCamera->GetAttachParent()) + { + Transform = OffsetTransform.GetRelativeTransform(CameraParent->GetComponentTransform()); + Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform; + bHandledTransform = true; + } + } + else if (UCameraComponent* Camera = mpawn->FindComponentByClass<UCameraComponent>()) + { + // Look for generic camera comp and use its attach parent + if (USceneComponent* CameraParent = Camera->GetAttachParent()) + { + Transform = OffsetTransform.GetRelativeTransform(CameraParent->GetComponentTransform()); + Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform; + bHandledTransform = true; + } + } + + if(!bHandledTransform) // Just use the pawn as we don't know the heirarchy + { + Transform = OffsetTransform.GetRelativeTransform(mpawn->GetTransform()); + Transform = FTransform(FRotator(0.f, -180.f, 0.f)) * Transform; + } + + // OpenVR y+ Up, +x Right, -z Going away + // UE4 z+ up, +y right, +x forward + + //Transform.ConcatenateRotation(FRotator(0.0f, -180.0f, 0.0f).Quaternion()); + // I might need to inverse X axis here to get it facing the correct way, we'll see + + //Transform = mpawn->GetActorTransform().GetRelativeTransform(GetComponentTransform()); + } + } + else + { + // No PC, destroy the layer and enable drawing it normally. + bShouldCreateProxy = true; + + if (LayerId) + { + StereoLayers->DestroyLayer(LayerId); + LayerId = 0; + } + return; + } + // + //Transform = GetRelativeTransform(); + } + } + + // If the transform changed dirty the layer and push the new transform + + if (!bIsDirty) + { + if (bLastVisible != bIsVisible) + { + bIsDirty = true; + } + else if (bDirtyRenderTarget || FMemory::Memcmp(&LastTransform, &Transform, sizeof(Transform)) != 0) + { + bIsDirty = true; + } + } + + bool bCurrVisible = bIsVisible; + if (!RenderTarget || !RenderTarget->GetResource()) + { + bCurrVisible = false; + } + + if (bIsDirty) + { + // OpenXR doesn't take the transforms scale component into account for the stereo layer, so we need to scale the buffer instead + bool bScaleBuffer = false; + static FName SystemName(TEXT("OpenXR")); + if (GEngine->XRSystem.IsValid() && (GEngine->XRSystem->GetSystemName() == SystemName)) + { + bScaleBuffer = true; + } + + IStereoLayers::FLayerDesc LayerDsec; + LayerDsec.Priority = Priority; + LayerDsec.QuadSize = FVector2D(DrawSize); + LayerDsec.UVRect = UVRect; + + if (bDelayForRenderThread && !LastTransform.Equals(FTransform::Identity)) + { + LayerDsec.Transform = LastTransform; + if (bScaleBuffer) + { + LayerDsec.QuadSize = FVector2D(DrawSize) * FVector2D(LastTransform.GetScale3D()); + } + } + else + { + LayerDsec.Transform = Transform; + if (bScaleBuffer) + { + LayerDsec.QuadSize = FVector2D(DrawSize) * FVector2D(Transform.GetScale3D()); + } + } + + if (RenderTarget) + { + LayerDsec.Texture = RenderTarget->GetResource()->TextureRHI; + LayerDsec.Flags |= (RenderTarget->GetMaterialType() == MCT_TextureExternal) ? IStereoLayers::LAYER_FLAG_TEX_EXTERNAL : 0; + } + // Forget the left texture implementation + //if (LeftTexture) + //{ + // LayerDsec.LeftTexture = LeftTexture->Resource->TextureRHI; + //} + + LayerDsec.Flags |= IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE;// (/*bLiveTexture*/true) ? IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE : 0; + LayerDsec.Flags |= (bNoAlphaChannel) ? IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL : 0; + LayerDsec.Flags |= (bQuadPreserveTextureRatio) ? IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO : 0; + LayerDsec.Flags |= (bSupportsDepth) ? IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH : 0; + LayerDsec.Flags |= (!bCurrVisible) ? IStereoLayers::LAYER_FLAG_HIDDEN : 0; + + // Fix this later when WorldLocked is no longer wrong. + switch (Space) + { + case EWidgetSpace::World: + { + if(bUseEpicsWorldLockedStereo) + LayerDsec.PositionType = IStereoLayers::WorldLocked; + else + LayerDsec.PositionType = IStereoLayers::TrackerLocked; + + //LayerDsec.Flags |= IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH; + }break; + + case EWidgetSpace::Screen: + default: + { + LayerDsec.PositionType = IStereoLayers::FaceLocked; + }break; + } + + switch (GeometryMode) + { + case EWidgetGeometryMode::Cylinder: + { + UStereoLayerShapeCylinder* Cylinder = Cast<UStereoLayerShapeCylinder>(Shape); + + if (!Cylinder) + { + if (Shape) + { + Shape->MarkAsGarbage(); + } + + Cylinder = NewObject<UStereoLayerShapeCylinder>(this, NAME_None, RF_Public); + Shape = Cylinder; + } + + if (Cylinder) + { + const float ArcAngleRadians = FMath::DegreesToRadians(CylinderArcAngle); + const float Radius = GetDrawSize().X / ArcAngleRadians; + + Cylinder->Height = GetDrawSize().Y;//CylinderHeight_DEPRECATED; + Cylinder->OverlayArc = CylinderArcAngle;// CylinderOverlayArc_DEPRECATED; + Cylinder->Radius = Radius;// CylinderRadius_DEPRECATED; + } + break; + + //LayerDsec.ShapeType = IStereoLayers::CylinderLayer; + + }break; + case EWidgetGeometryMode::Plane: + default: + { + UStereoLayerShapeQuad* Quad = Cast<UStereoLayerShapeQuad>(Shape); + + if (!Quad) + { + if (Shape) + { + Shape->MarkAsGarbage(); + } + Shape = NewObject<UStereoLayerShapeQuad>(this, NAME_None, RF_Public); + } + //LayerDsec.ShapeType = IStereoLayers::QuadLayer; + }break; + } + + if(Shape) + Shape->ApplyShape(LayerDsec); + + if (LayerId) + { + StereoLayers->SetLayerDesc(LayerId, LayerDsec); + } + else + { + LayerId = StereoLayers->CreateLayer(LayerDsec); + } + + } + + LastTransform = Transform; + bLastVisible = bCurrVisible; + bIsDirty = false; + bDirtyRenderTarget = false; +#endif +} + + +void UVRStereoWidgetComponent::SetPriority(int32 InPriority) +{ + if (Priority == InPriority) + { + return; + } + + Priority = InPriority; + bIsDirty = true; +} + +void UVRStereoWidgetComponent::UpdateRenderTarget(FIntPoint DesiredRenderTargetSize) +{ + Super::UpdateRenderTarget(DesiredRenderTargetSize); +} + +/** Represents a billboard sprite to the scene manager. */ +class FStereoWidget3DSceneProxy final : public FPrimitiveSceneProxy +{ +public: + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast<size_t>(&UniquePointer); + } + /** Initialization constructor. */ + FStereoWidget3DSceneProxy(UVRStereoWidgetComponent* InComponent, ISlate3DRenderer& InRenderer) + : FPrimitiveSceneProxy(InComponent) + , Pivot(InComponent->GetPivot()) + , Renderer(InRenderer) + , RenderTarget(InComponent->GetRenderTarget()) + , MaterialInstance(InComponent->GetMaterialInstance()) + , BodySetup(InComponent->GetBodySetup()) + , BlendMode(InComponent->GetBlendMode()) + , GeometryMode(InComponent->GetGeometryMode()) + , ArcAngle(FMath::DegreesToRadians(InComponent->GetCylinderArcAngle())) + { + bWillEverBeLit = false; + bCreateSceneProxy = InComponent->bShouldCreateProxy; + MaterialRelevance = MaterialInstance->GetRelevance(GetScene().GetFeatureLevel()); + } + + // FPrimitiveSceneProxy interface. + virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + if(!bCreateSceneProxy) + return; + +#if WITH_EDITOR + const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe; + + auto WireframeMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, + FLinearColor(0, 0.5f, 1.f) + ); + + Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); + + FMaterialRenderProxy* ParentMaterialProxy = nullptr; + if (bWireframe) + { + ParentMaterialProxy = WireframeMaterialInstance; + } + else + { + ParentMaterialProxy = MaterialInstance->GetRenderProxy(); + } +#else + FMaterialRenderProxy* ParentMaterialProxy = MaterialInstance->GetRenderProxy(); +#endif + + //FSpriteTextureOverrideRenderProxy* TextureOverrideMaterialProxy = new FSpriteTextureOverrideRenderProxy(ParentMaterialProxy, + + const FMatrix& ViewportLocalToWorld = GetLocalToWorld(); + + FMatrix PreviousLocalToWorld; + + if (!GetScene().GetPreviousLocalToWorld(GetPrimitiveSceneInfo(), PreviousLocalToWorld)) + { + PreviousLocalToWorld = GetLocalToWorld(); + } + + if (RenderTarget)//false)//RenderTarget) + { + FTextureResource* TextureResource = RenderTarget->GetResource(); + if (TextureResource) + { + if (GeometryMode == EWidgetGeometryMode::Plane) + { + float U = -RenderTarget->SizeX * Pivot.X; + float V = -RenderTarget->SizeY * Pivot.Y; + float UL = RenderTarget->SizeX * (1.0f - Pivot.X); + float VL = RenderTarget->SizeY * (1.0f - Pivot.Y); + + int32 VertexIndices[4]; + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FDynamicMeshBuilder MeshBuilder(Views[ViewIndex]->GetFeatureLevel()); + + if (VisibilityMap & (1 << ViewIndex)) + { + VertexIndices[0] = MeshBuilder.AddVertex(-FVector3f(0, U, V), FVector2f(0, 0), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + VertexIndices[1] = MeshBuilder.AddVertex(-FVector3f(0, U, VL), FVector2f(0, 1), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + VertexIndices[2] = MeshBuilder.AddVertex(-FVector3f(0, UL, VL), FVector2f(1, 1), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + VertexIndices[3] = MeshBuilder.AddVertex(-FVector3f(0, UL, V), FVector2f(1, 0), FVector3f(0, -1, 0), FVector3f(0, 0, -1), FVector3f(1, 0, 0), FColor::White); + + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[1], VertexIndices[2]); + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[2], VertexIndices[3]); + + FDynamicMeshBuilderSettings Settings; + Settings.bDisableBackfaceCulling = false; + Settings.bReceivesDecals = true; + Settings.bUseSelectionOutline = true; + MeshBuilder.GetMesh(ViewportLocalToWorld, PreviousLocalToWorld, ParentMaterialProxy, SDPG_World, Settings, nullptr, ViewIndex, Collector, FHitProxyId()); + } + } + } + else + { + ensure(GeometryMode == EWidgetGeometryMode::Cylinder); + + const int32 NumSegments = FMath::Lerp(4, 32, ArcAngle / PI); + + + const float Radius = RenderTarget->SizeX / ArcAngle; + const float Apothem = Radius * FMath::Cos(0.5f*ArcAngle); + const float ChordLength = 2.0f * Radius * FMath::Sin(0.5f*ArcAngle); + + const float PivotOffsetX = ChordLength * (0.5 - Pivot.X); + const float V = -RenderTarget->SizeY * Pivot.Y; + const float VL = RenderTarget->SizeY * (1.0f - Pivot.Y); + + int32 VertexIndices[4]; + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + FDynamicMeshBuilder MeshBuilder(Views[ViewIndex]->GetFeatureLevel()); + + if (VisibilityMap & (1 << ViewIndex)) + { + const float RadiansPerStep = ArcAngle / NumSegments; + + FVector LastTangentX; + FVector LastTangentY; + FVector LastTangentZ; + + for (int32 Segment = 0; Segment < NumSegments; Segment++) + { + const float Angle = -ArcAngle / 2 + Segment * RadiansPerStep; + const float NextAngle = Angle + RadiansPerStep; + + // Polar to Cartesian + const float X0 = Radius * FMath::Cos(Angle) - Apothem; + const float Y0 = Radius * FMath::Sin(Angle); + const float X1 = Radius * FMath::Cos(NextAngle) - Apothem; + const float Y1 = Radius * FMath::Sin(NextAngle); + + const float U0 = static_cast<float>(Segment) / NumSegments; + const float U1 = static_cast<float>(Segment + 1) / NumSegments; + + const FVector Vertex0 = -FVector(X0, PivotOffsetX + Y0, V); + const FVector Vertex1 = -FVector(X0, PivotOffsetX + Y0, VL); + const FVector Vertex2 = -FVector(X1, PivotOffsetX + Y1, VL); + const FVector Vertex3 = -FVector(X1, PivotOffsetX + Y1, V); + + FVector TangentX = Vertex3 - Vertex0; + TangentX.Normalize(); + FVector TangentY = Vertex1 - Vertex0; + TangentY.Normalize(); + FVector TangentZ = FVector::CrossProduct(TangentX, TangentY); + + if (Segment == 0) + { + LastTangentX = TangentX; + LastTangentY = TangentY; + LastTangentZ = TangentZ; + } + + VertexIndices[0] = MeshBuilder.AddVertex((FVector3f)Vertex0, FVector2f(U0, 0), (FVector3f)LastTangentX, (FVector3f)LastTangentY, (FVector3f)LastTangentZ, FColor::White); + VertexIndices[1] = MeshBuilder.AddVertex((FVector3f)Vertex1, FVector2f(U0, 1), (FVector3f)LastTangentX, (FVector3f)LastTangentY, (FVector3f)LastTangentZ, FColor::White); + VertexIndices[2] = MeshBuilder.AddVertex((FVector3f)Vertex2, FVector2f(U1, 1), (FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)TangentZ, FColor::White); + VertexIndices[3] = MeshBuilder.AddVertex((FVector3f)Vertex3, FVector2f(U1, 0), (FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)TangentZ, FColor::White); + + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[1], VertexIndices[2]); + MeshBuilder.AddTriangle(VertexIndices[0], VertexIndices[2], VertexIndices[3]); + + LastTangentX = TangentX; + LastTangentY = TangentY; + LastTangentZ = TangentZ; + } + + FDynamicMeshBuilderSettings Settings; + Settings.bDisableBackfaceCulling = false; + Settings.bReceivesDecals = true; + Settings.bUseSelectionOutline = true; + MeshBuilder.GetMesh(ViewportLocalToWorld, PreviousLocalToWorld, ParentMaterialProxy, SDPG_World, Settings, nullptr, ViewIndex, Collector, FHitProxyId()); + } + } + } + } + } + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap & (1 << ViewIndex)) + { + RenderCollision(BodySetup, Collector, ViewIndex, ViewFamily.EngineShowFlags, GetBounds(), IsSelected()); + RenderBounds(Collector.GetPDI(ViewIndex), ViewFamily.EngineShowFlags, GetBounds(), IsSelected()); + } + } +#endif + } + + void RenderCollision(UBodySetup* InBodySetup, FMeshElementCollector& Collector, int32 ViewIndex, const FEngineShowFlags& EngineShowFlags, const FBoxSphereBounds& InBounds, bool bRenderInEditor) const + { + if (InBodySetup) + { + bool bDrawCollision = EngineShowFlags.Collision && IsCollisionEnabled(); + + if (bDrawCollision && AllowDebugViewmodes()) + { + // Draw simple collision as wireframe if 'show collision', collision is enabled, and we are not using the complex as the simple + const bool bDrawSimpleWireframeCollision = InBodySetup->CollisionTraceFlag != ECollisionTraceFlag::CTF_UseComplexAsSimple; + + if (FMath::Abs(GetLocalToWorld().Determinant()) < SMALL_NUMBER) + { + // Catch this here or otherwise GeomTransform below will assert + // This spams so commented out + //UE_LOG(LogStaticMesh, Log, TEXT("Zero scaling not supported (%s)"), *StaticMesh->GetPathName()); + } + else + { + const bool bDrawSolid = !bDrawSimpleWireframeCollision; + const bool bProxyIsSelected = IsSelected(); + + if (bDrawSolid) + { + // Make a material for drawing solid collision stuff + auto SolidMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->ShadedLevelColorationUnlitMaterial->GetRenderProxy(), + GetWireframeColor() + ); + + Collector.RegisterOneFrameMaterialProxy(SolidMaterialInstance); + + FTransform GeomTransform(GetLocalToWorld()); + InBodySetup->AggGeom.GetAggGeom(GeomTransform, GetWireframeColor().ToFColor(true), SolidMaterialInstance, false, true, DrawsVelocity(), ViewIndex, Collector); + } + // wireframe + else + { + FColor CollisionColor = FColor(157, 149, 223, 255); + FTransform GeomTransform(GetLocalToWorld()); + InBodySetup->AggGeom.GetAggGeom(GeomTransform, GetSelectionColor(CollisionColor, bProxyIsSelected, IsHovered()).ToFColor(true), nullptr, false, false, DrawsVelocity(), ViewIndex, Collector); + } + } + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + bool bVisible = true; + + FPrimitiveViewRelevance Result; + + MaterialRelevance.SetPrimitiveViewRelevance(Result); + + Result.bDrawRelevance = IsShown(View) && bVisible && View->Family->EngineShowFlags.WidgetComponents; + Result.bDynamicRelevance = true; + Result.bShadowRelevance = IsShadowCast(View); + Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; + Result.bEditorPrimitiveRelevance = false; + Result.bVelocityRelevance = DrawsVelocity() && Result.bOpaque && Result.bRenderInMainPass; + + return Result; + } + + virtual void GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const override + { + bDynamic = false; + bRelevant = false; + bLightMapped = false; + bShadowMapped = false; + } + + virtual void OnTransformChanged() override + { + Origin = GetLocalToWorld().GetOrigin(); + } + + virtual bool CanBeOccluded() const override + { + return !MaterialRelevance.bDisableDepthTest; + } + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + +private: + FVector Origin; + FVector2D Pivot; + ISlate3DRenderer& Renderer; + UTextureRenderTarget2D* RenderTarget; + UMaterialInstanceDynamic* MaterialInstance; + FMaterialRelevance MaterialRelevance; + UBodySetup* BodySetup; + EWidgetBlendMode BlendMode; + EWidgetGeometryMode GeometryMode; + float ArcAngle; + bool bCreateSceneProxy; +}; + + +FPrimitiveSceneProxy* UVRStereoWidgetComponent::CreateSceneProxy() +{ + if (Space == EWidgetSpace::Screen) + { + return nullptr; + } + + if (WidgetRenderer && GetSlateWindow() && GetSlateWindow()->GetContent() != SNullWidget::NullWidget) + { + RequestRedraw(); + LastWidgetRenderTime = 0; + + return new FStereoWidget3DSceneProxy(this, *WidgetRenderer->GetSlateRenderer()); + } + + return nullptr; +} + +class FVRStereoWidgetComponentInstanceData : public FActorComponentInstanceData +{ +public: + FVRStereoWidgetComponentInstanceData(const UVRStereoWidgetComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) + , RenderTarget(SourceComponent->GetRenderTarget()) + {} + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + FActorComponentInstanceData::ApplyToComponent(Component, CacheApplyPhase); + CastChecked<UVRStereoWidgetComponent>(Component)->ApplyVRComponentInstanceData(this); + } + + /*virtual void AddReferencedObjects(FReferenceCollector& Collector) override + { + FActorComponentInstanceData::AddReferencedObjects(Collector); + + UClass* WidgetUClass = *WidgetClass; + Collector.AddReferencedObject(WidgetUClass); + Collector.AddReferencedObject(RenderTarget); + }*/ + +public: + UTextureRenderTarget2D* RenderTarget; +}; + +TStructOnScope<FActorComponentInstanceData> UVRStereoWidgetComponent::GetComponentInstanceData() const +{ + return MakeStructOnScope<FActorComponentInstanceData, FVRStereoWidgetComponentInstanceData>(this); +} + +void UVRStereoWidgetComponent::ApplyVRComponentInstanceData(FVRStereoWidgetComponentInstanceData* WidgetInstanceData) +{ + check(WidgetInstanceData); + + // Note: ApplyComponentInstanceData is called while the component is registered so the rendering thread is already using this component + // That means all component state that is modified here must be mirrored on the scene proxy, which will be recreated to receive the changes later due to MarkRenderStateDirty. + + if (GetWidgetClass() != WidgetClass) + { + return; + } + + RenderTarget = WidgetInstanceData->RenderTarget; + + // Also set the texture + //Texture = RenderTarget; + // Not needed anymore, just using the render target directly now + + if (MaterialInstance && RenderTarget) + { + MaterialInstance->SetTextureParameterValue("SlateUI", RenderTarget); + } + + MarkRenderStateDirty(); +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRTrackedParentInterface.cpp b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRTrackedParentInterface.cpp new file mode 100644 index 0000000..5874c99 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Private/VRTrackedParentInterface.cpp @@ -0,0 +1,11 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "VRTrackedParentInterface.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" + +UVRTrackedParentInterface::UVRTrackedParentInterface(const class FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/CharacterMovementCompTypes.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/CharacterMovementCompTypes.h new file mode 100644 index 0000000..8f795b6 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/CharacterMovementCompTypes.h @@ -0,0 +1,695 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "GameFramework/Character.h" +#include "Components/SkeletalMeshComponent.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "CharacterMovementCompTypes.generated.h" + + +class AVRBaseCharacter; +class UVRBaseCharacterMovementComponent; + +UENUM(Blueprintable) +enum class EVRMoveAction : uint8 +{ + VRMOVEACTION_None = 0x00, + VRMOVEACTION_SnapTurn = 0x01, + VRMOVEACTION_Teleport = 0x02, + VRMOVEACTION_StopAllMovement = 0x03, + VRMOVEACTION_SetRotation = 0x04, + VRMOVEACTION_PauseTracking = 0x14, // Reserved from here up to 0x40 + VRMOVEACTION_CUSTOM1 = 0x05, + VRMOVEACTION_CUSTOM2 = 0x06, + VRMOVEACTION_CUSTOM3 = 0x07, + VRMOVEACTION_CUSTOM4 = 0x08, + VRMOVEACTION_CUSTOM5 = 0x09, + VRMOVEACTION_CUSTOM6 = 0x0A, + VRMOVEACTION_CUSTOM7 = 0x0B, + VRMOVEACTION_CUSTOM8 = 0x0C, + VRMOVEACTION_CUSTOM9 = 0x0D, + VRMOVEACTION_CUSTOM10 = 0x0E, + VRMOVEACTION_CUSTOM11 = 0x0F, + VRMOVEACTION_CUSTOM12 = 0x10, + VRMOVEACTION_CUSTOM13 = 0x11, + VRMOVEACTION_CUSTOM14 = 0x12, + VRMOVEACTION_CUSTOM15 = 0x13, + // Up to 0x20 currently allowed for +}; + +// What to do with the players velocity when specific move actions are called +// Default of none leaves it as is, for people with 0 ramp up time on acelleration +// This likely won't be that useful. +UENUM(Blueprintable) +enum class EVRMoveActionVelocityRetention : uint8 +{ + // Leaves velocity as is + VRMOVEACTION_Velocity_None = 0x00, + + // Clears velocity entirely + VRMOVEACTION_Velocity_Clear = 0x01, + + // Rotates the velocity to match new heading + VRMOVEACTION_Velocity_Turn = 0x02 +}; + +UENUM(Blueprintable) +enum class EVRMoveActionDataReq : uint8 +{ + VRMOVEACTIONDATA_None = 0x00, + VRMOVEACTIONDATA_LOC = 0x01, + VRMOVEACTIONDATA_ROT = 0x02, + VRMOVEACTIONDATA_LOC_AND_ROT = 0x03 +}; + + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRMoveActionContainer +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY() + EVRMoveAction MoveAction; + UPROPERTY() + EVRMoveActionDataReq MoveActionDataReq; + UPROPERTY() + FVector MoveActionLoc; + UPROPERTY() + FVector MoveActionVel; + UPROPERTY() + FRotator MoveActionRot; + UPROPERTY() + uint8 MoveActionFlags; + UPROPERTY() + TArray<UObject*> MoveActionObjectReferences; + UPROPERTY() + EVRMoveActionVelocityRetention VelRetentionSetting; + + FVRMoveActionContainer() + { + Clear(); + } + + void Clear() + { + MoveAction = EVRMoveAction::VRMOVEACTION_None; + MoveActionDataReq = EVRMoveActionDataReq::VRMOVEACTIONDATA_None; + MoveActionLoc = FVector::ZeroVector; + MoveActionVel = FVector::ZeroVector; + MoveActionRot = FRotator::ZeroRotator; + MoveActionFlags = 0; + VelRetentionSetting = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None; + MoveActionObjectReferences.Empty(); + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + Ar.SerializeBits(&MoveAction, 6); // 64 elements, only allowing 1 per frame, they aren't flags + + switch (MoveAction) + { + case EVRMoveAction::VRMOVEACTION_None: break; + case EVRMoveAction::VRMOVEACTION_SetRotation: + case EVRMoveAction::VRMOVEACTION_SnapTurn: + { + uint16 Yaw = 0; + uint16 Pitch = 0; + + if (Ar.IsSaving()) + { + bool bUseLocOnly = MoveActionFlags & 0x04; + Ar.SerializeBits(&bUseLocOnly, 1); + + if (!bUseLocOnly) + { + Yaw = FRotator::CompressAxisToShort(MoveActionRot.Yaw); + Ar << Yaw; + } + else + { + Ar << MoveActionLoc; + } + + bool bTeleportGrips = MoveActionFlags & 0x01;// MoveActionRot.Roll > 0.0f && MoveActionRot.Roll < 1.5f; + Ar.SerializeBits(&bTeleportGrips, 1); + + if (!bTeleportGrips) + { + bool bTeleportCharacter = MoveActionFlags & 0x02;// MoveActionRot.Roll > 1.5f; + Ar.SerializeBits(&bTeleportCharacter, 1); + } + + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + //Pitch = FRotator::CompressAxisToShort(MoveActionRot.Pitch); + //Ar << Pitch; + } + } + else + { + + bool bUseLocOnly = false; + Ar.SerializeBits(&bUseLocOnly, 1); + MoveActionFlags |= (bUseLocOnly << 2); + + if (!bUseLocOnly) + { + Ar << Yaw; + MoveActionRot.Yaw = FRotator::DecompressAxisFromShort(Yaw); + } + else + { + Ar << MoveActionLoc; + } + + bool bTeleportGrips = false; + Ar.SerializeBits(&bTeleportGrips, 1); + MoveActionFlags |= (uint8)bTeleportGrips; //.Roll = bTeleportGrips ? 1.0f : 0.0f; + + if (!bTeleportGrips) + { + bool bTeleportCharacter = false; + Ar.SerializeBits(&bTeleportCharacter, 1); + MoveActionFlags |= ((uint8)bTeleportCharacter << 1); + //MoveActionRot.Roll = 2.0f; + } + + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + //Ar << Pitch; + //MoveActionRot.Pitch = FRotator::DecompressAxisFromShort(Pitch); + } + } + + //bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + }break; + case EVRMoveAction::VRMOVEACTION_Teleport: // Not replicating rot as Control rot does that already + { + uint16 Yaw = 0; + uint16 Pitch = 0; + + if (Ar.IsSaving()) + { + Yaw = FRotator::CompressAxisToShort(MoveActionRot.Yaw); + Ar << Yaw; + + bool bSkipEncroachment = MoveActionFlags & 0x01;// MoveActionRot.Roll > 0.0f; + Ar.SerializeBits(&bSkipEncroachment, 1); + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + //Pitch = FRotator::CompressAxisToShort(MoveActionRot.Pitch); + //Ar << Pitch; + } + } + else + { + Ar << Yaw; + MoveActionRot.Yaw = FRotator::DecompressAxisFromShort(Yaw); + + bool bSkipEncroachment = false; + Ar.SerializeBits(&bSkipEncroachment, 1); + MoveActionFlags |= (uint8)bSkipEncroachment; + //MoveActionRot.Roll = bSkipEncroachment ? 1.0f : 0.0f; + Ar.SerializeBits(&VelRetentionSetting, 2); + + if (VelRetentionSetting == EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_Turn) + { + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionVel, Ar); + //Ar << Pitch; + //MoveActionRot.Pitch = FRotator::DecompressAxisFromShort(Pitch); + } + } + + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + }break; + case EVRMoveAction::VRMOVEACTION_StopAllMovement: + {}break; + case EVRMoveAction::VRMOVEACTION_PauseTracking: + { + + Ar.SerializeBits(&MoveActionFlags, 1); + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + + uint16 Yaw = 0; + // Loc and rot for capsule should also be sent here + if (Ar.IsSaving()) + { + Yaw = FRotator::CompressAxisToShort(MoveActionRot.Yaw); + Ar << Yaw; + } + else + { + Ar << Yaw; + MoveActionRot.Yaw = FRotator::DecompressAxisFromShort(Yaw); + } + + }break; + default: // Everything else + { + // Defines how much to replicate - only 4 possible values, 0 - 3 so only send 2 bits + Ar.SerializeBits(&MoveActionDataReq, 2); + + if (((uint8)MoveActionDataReq & (uint8)EVRMoveActionDataReq::VRMOVEACTIONDATA_LOC) != 0) + bOutSuccess &= SerializePackedVector<100, 30>(MoveActionLoc, Ar); + + if (((uint8)MoveActionDataReq & (uint8)EVRMoveActionDataReq::VRMOVEACTIONDATA_ROT) != 0) + MoveActionRot.SerializeCompressedShort(Ar); + + bool bSerializeObjects = MoveActionObjectReferences.Num() > 0; + Ar.SerializeBits(&bSerializeObjects, 1); + if (bSerializeObjects) + { + Ar << MoveActionObjectReferences; + } + + bool bSerializeFlags = MoveActionFlags != 0x00; + Ar.SerializeBits(&bSerializeFlags, 1); + if (bSerializeFlags) + { + Ar << MoveActionFlags; + } + + }break; + } + + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRMoveActionContainer > : public TStructOpsTypeTraitsBase2<FVRMoveActionContainer> +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRMoveActionArray +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY() + TArray<FVRMoveActionContainer> MoveActions; + + void Clear() + { + MoveActions.Empty(); + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + uint8 MoveActionCount = (uint8)MoveActions.Num(); + bool bHasAMoveAction = MoveActionCount > 0; + Ar.SerializeBits(&bHasAMoveAction, 1); + + if (bHasAMoveAction) + { + bool bHasMoreThanOneMoveAction = MoveActionCount > 1; + Ar.SerializeBits(&bHasMoreThanOneMoveAction, 1); + + if (Ar.IsSaving()) + { + if (bHasMoreThanOneMoveAction) + { + Ar << MoveActionCount; + + for (int i = 0; i < MoveActionCount; i++) + { + bOutSuccess &= MoveActions[i].NetSerialize(Ar, Map, bOutSuccess); + } + } + else + { + bOutSuccess &= MoveActions[0].NetSerialize(Ar, Map, bOutSuccess); + } + } + else + { + if (bHasMoreThanOneMoveAction) + { + Ar << MoveActionCount; + } + else + MoveActionCount = 1; + + for (int i = 0; i < MoveActionCount; i++) + { + FVRMoveActionContainer MoveAction; + bOutSuccess &= MoveAction.NetSerialize(Ar, Map, bOutSuccess); + MoveActions.Add(MoveAction); + } + } + } + + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRMoveActionArray > : public TStructOpsTypeTraitsBase2<FVRMoveActionArray> +{ + enum + { + WithNetSerializer = true + }; +}; + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRConditionalMoveRep +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY(Transient) + FVector CustomVRInputVector; + UPROPERTY(Transient) + FVector RequestedVelocity; + UPROPERTY(Transient) + FVRMoveActionArray MoveActionArray; + //FVRMoveActionContainer MoveAction; + + FVRConditionalMoveRep() + { + CustomVRInputVector = FVector::ZeroVector; + RequestedVelocity = FVector::ZeroVector; + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + bool bIsLoading = Ar.IsLoading(); + + bool bHasVRinput = !CustomVRInputVector.IsZero(); + bool bHasRequestedVelocity = !RequestedVelocity.IsZero(); + bool bHasMoveAction = MoveActionArray.MoveActions.Num() > 0;//MoveAction.MoveAction != EVRMoveAction::VRMOVEACTION_None; + + bool bHasAnyProperties = bHasVRinput || bHasRequestedVelocity || bHasMoveAction; + Ar.SerializeBits(&bHasAnyProperties, 1); + + if (bHasAnyProperties) + { + Ar.SerializeBits(&bHasVRinput, 1); + Ar.SerializeBits(&bHasRequestedVelocity, 1); + //Ar.SerializeBits(&bHasMoveAction, 1); + + if (bHasVRinput) + { + bOutSuccess &= SerializePackedVector<100, 22/*30*/>(CustomVRInputVector, Ar); + } + else if (bIsLoading) + { + CustomVRInputVector = FVector::ZeroVector; + } + + if (bHasRequestedVelocity) + { + bOutSuccess &= SerializePackedVector<100, 22/*30*/>(RequestedVelocity, Ar); + } + else if (bIsLoading) + { + RequestedVelocity = FVector::ZeroVector; + } + + //if (bHasMoveAction) + MoveActionArray.NetSerialize(Ar, Map, bOutSuccess); + } + else if (bIsLoading) + { + CustomVRInputVector = FVector::ZeroVector; + RequestedVelocity = FVector::ZeroVector; + MoveActionArray.Clear(); + } + + return bOutSuccess; + } + +}; + +template<> +struct TStructOpsTypeTraits< FVRConditionalMoveRep > : public TStructOpsTypeTraitsBase2<FVRConditionalMoveRep> +{ + enum + { + WithNetSerializer = true + }; +}; + +// #TODO: DELETE THIS +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRConditionalMoveRep2 +{ + GENERATED_USTRUCT_BODY() +public: + + // Moved these here to avoid having to duplicate tons of properties + UPROPERTY(Transient) + UPrimitiveComponent* ClientMovementBase; + UPROPERTY(Transient) + FName ClientBaseBoneName; + + UPROPERTY(Transient) + uint16 ClientYaw; + + UPROPERTY(Transient) + uint16 ClientPitch; + + UPROPERTY(Transient) + uint8 ClientRoll; + + FVRConditionalMoveRep2() + { + ClientMovementBase = nullptr; + ClientBaseBoneName = NAME_None; + ClientRoll = 0; + ClientPitch = 0; + ClientYaw = 0; + } + + void UnpackAndSetINTRotations(uint32 Rotation32) + { + // Reversed the order of these so it costs less to replicate + ClientYaw = (Rotation32 & 65535); + ClientPitch = (Rotation32 >> 16); + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + bool bRepRollAndPitch = (ClientRoll != 0 || ClientPitch != 0); + Ar.SerializeBits(&bRepRollAndPitch, 1); + + if (bRepRollAndPitch) + { + // Reversed the order of these + uint32 Rotation32 = (((uint32)ClientPitch) << 16) | ((uint32)ClientYaw); + Ar.SerializeIntPacked(Rotation32); + Ar << ClientRoll; + + if (Ar.IsLoading()) + { + UnpackAndSetINTRotations(Rotation32); + } + } + else + { + uint32 Yaw32 = ClientYaw; + Ar.SerializeIntPacked(Yaw32); + ClientYaw = (uint16)Yaw32; + } + + bool bHasMovementBase = MovementBaseUtility::IsDynamicBase(ClientMovementBase); + Ar.SerializeBits(&bHasMovementBase, 1); + + if (bHasMovementBase) + { + Ar << ClientMovementBase; + + bool bValidName = ClientBaseBoneName != NAME_None; + Ar.SerializeBits(&bValidName, 1); + + // This saves 9 bits on average, we almost never have a valid bone name and default rep goes to 9 bits for hardcoded + // total of 6 bits savings as we use 3 extra for our flags in here. + if (bValidName) + { + Ar << ClientBaseBoneName; + } + } + + return bOutSuccess; + } + +}; + +template<> +struct TStructOpsTypeTraits< FVRConditionalMoveRep2 > : public TStructOpsTypeTraitsBase2<FVRConditionalMoveRep2> +{ + enum + { + WithNetSerializer = true + }; +}; + +/** +* Helper to change mesh bone updates within a scope. +* Example usage: +* { +* FScopedPreventMeshBoneUpdate ScopedNoMeshBoneUpdate(CharacterOwner->GetMesh(), EKinematicBonesUpdateToPhysics::SkipAllBones); +* // Do something to move mesh, bones will not update +* } +* // Movement of mesh at this point will use previous setting. +*/ +struct FScopedMeshBoneUpdateOverrideVR +{ + FScopedMeshBoneUpdateOverrideVR(USkeletalMeshComponent* Mesh, EKinematicBonesUpdateToPhysics::Type OverrideSetting); + + ~FScopedMeshBoneUpdateOverrideVR(); + +private: + USkeletalMeshComponent* MeshRef; + EKinematicBonesUpdateToPhysics::Type SavedUpdateSetting; +}; + + +class VREXPANSIONPLUGIN_API FSavedMove_VRBaseCharacter : public FSavedMove_Character +{ + +public: + + EVRConjoinedMovementModes VRReplicatedMovementMode; + + FVector VRCapsuleLocation; + FVector LFDiff; + FRotator VRCapsuleRotation; + FVRConditionalMoveRep ConditionalValues; + + void Clear(); + virtual void SetInitialPosition(ACharacter* C); + virtual void PrepMoveFor(ACharacter* Character) override; + virtual void CombineWith(const FSavedMove_Character* OldMove, ACharacter* InCharacter, APlayerController* PC, const FVector& OldStartLocation) override; + + /** Set the properties describing the final position, etc. of the moved pawn. */ + virtual void PostUpdate(ACharacter* C, EPostUpdateMode PostUpdateMode) override; + + FSavedMove_VRBaseCharacter(); + + virtual uint8 GetCompressedFlags() const override; + virtual bool CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const override; + virtual bool IsImportantMove(const FSavedMovePtr& LastAckedMove) const override; +}; + +// Using this fixes the problem where the character capsule isn't reset after a scoped movement update revert (pretty much just in StepUp operations) +class VREXPANSIONPLUGIN_API FVRCharacterScopedMovementUpdate : public FScopedMovementUpdate +{ +public: + + FVRCharacterScopedMovementUpdate(USceneComponent* Component, EScopedUpdate::Type ScopeBehavior = EScopedUpdate::DeferredUpdates, bool bRequireOverlapsEventFlagToQueueOverlaps = true); + + FTransform InitialVRTransform; + + /** Revert movement to the initial location of the Component at the start of the scoped update. Also clears pending overlaps and sets bHasMoved to false. */ + void RevertMove(); +}; + + +/** Shared pointer for easy memory management of FSavedMove_Character, for accumulating and replaying network moves. */ +//typedef TSharedPtr<class FSavedMove_Character> FSavedMovePtr; +struct VREXPANSIONPLUGIN_API FVRCharacterNetworkMoveData : public FCharacterNetworkMoveData +{ +public: + + FVector_NetQuantize100 VRCapsuleLocation; + FVector_NetQuantize100 LFDiff; + uint16 VRCapsuleRotation; + EVRConjoinedMovementModes ReplicatedMovementMode; + FVRConditionalMoveRep ConditionalMoveReps; + + FVRCharacterNetworkMoveData(); + + virtual ~FVRCharacterNetworkMoveData(); + virtual void ClientFillNetworkMoveData(const FSavedMove_Character& ClientMove, ENetworkMoveType MoveType) override; + virtual bool Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap, ENetworkMoveType MoveType) override; +}; + +struct VREXPANSIONPLUGIN_API FVRCharacterNetworkMoveDataContainer : public FCharacterNetworkMoveDataContainer +{ +public: + + /** + * Default constructor. Sets data storage (NewMoveData, PendingMoveData, OldMoveData) to point to default data members. Override those pointers to instead point to custom data if you want to use derived classes. + */ + FVRCharacterNetworkMoveDataContainer() : FCharacterNetworkMoveDataContainer() + { + NewMoveData = &VRBaseDefaultMoveData[0]; + PendingMoveData = &VRBaseDefaultMoveData[1]; + OldMoveData = &VRBaseDefaultMoveData[2]; + } + + virtual ~FVRCharacterNetworkMoveDataContainer() + { + } + + /** + * Passes through calls to ClientFillNetworkMoveData on each FCharacterNetworkMoveData matching the client moves. Note that ClientNewMove will never be null, but others may be. + */ + //virtual void ClientFillNetworkMoveData(const FSavedMove_Character* ClientNewMove, const FSavedMove_Character* ClientPendingMove, const FSavedMove_Character* ClientOldMove); + + /** + * Serialize movement data. Passes Serialize calls to each FCharacterNetworkMoveData as applicable, based on bHasPendingMove and bHasOldMove. + */ + //virtual bool Serialize(UCharacterMovementComponent& CharacterMovement, FArchive& Ar, UPackageMap* PackageMap); + + + +protected: + + + FVRCharacterNetworkMoveData VRBaseDefaultMoveData[3]; + +}; + +struct VREXPANSIONPLUGIN_API FVRCharacterMoveResponseDataContainer : public FCharacterMoveResponseDataContainer +{ +public: + + FVRCharacterMoveResponseDataContainer() : FCharacterMoveResponseDataContainer() + { + } + + virtual ~FVRCharacterMoveResponseDataContainer() + { + } + + /** + * Copy the FClientAdjustment and set a few flags relevant to that data. + */ + virtual void ServerFillResponseData(const UCharacterMovementComponent& CharacterMovement, const FClientAdjustment& PendingAdjustment) override; + + //bool bHasRotation; // By default ClientAdjustment.NewRot is not serialized. Set this to true after base ServerFillResponseData if you want Rotation to be serialized. + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripMotionControllerComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripMotionControllerComponent.h new file mode 100644 index 0000000..0c263ff --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripMotionControllerComponent.h @@ -0,0 +1,1533 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +//#include "Engine/EngineBaseTypes.h" +#include "SceneViewExtension.h" +#include "VRBPDatatypes.h" +#include "MotionControllerComponent.h" +#include "VRGripInterface.h" +#include "GripScripts/VRGripScriptBase.h" +#include "GripMotionControllerComponent.generated.h" + +class AVRBaseCharacter; +struct FXRDeviceId; + +/** +* +*/ + +/** Override replication control variable for inherited properties that are private. Be careful since it removes a compile-time error when the variable doesn't exist */ +// This is a temp macro until epic adds their own equivalent +#define DOREPLIFETIME_ACTIVE_OVERRIDE_PRIVATE_PROPERTY(c,v,active) \ +{ \ + static FProperty* sp##v = GetReplicatedProperty(StaticClass(), c::StaticClass(),FName(TEXT(#v))); \ + for (int32 i = 0; i < sp##v->ArrayDim; i++) \ + { \ + ChangedPropertyTracker.SetCustomIsActiveOverride(this, sp##v->RepIndex + i, active); \ + } \ +} + +#define RESET_REPLIFETIME_CONDITION_PRIVATE_PROPERTY(c,v,cond) ResetReplicatedLifetimeProperty(StaticClass(), c::StaticClass(), FName(TEXT(#v)), cond, OutLifetimeProps); + +DECLARE_LOG_CATEGORY_EXTERN(LogVRMotionController, Log, All); +//For UE4 Profiler ~ Stat Group +DECLARE_STATS_GROUP(TEXT("TICKGrip"), STATGROUP_TickGrip, STATCAT_Advanced); + +/** Delegate for notification when the controllers tracking changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRGripControllerOnTrackingEventSignature, const ETrackingStatus &, NewTrackingStatus); + +/** Delegate for notification when the controller grips a new object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVROnControllerGripSignature, const FBPActorGripInformation &, GripInformation); + +/** Delegate for notification when the controller drops a gripped object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVROnControllerDropSignature, const FBPActorGripInformation &, GripInformation, bool, bWasSocketed); + +/** Delegate for notification when the controller sockets a gripped object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FVROnControllerSocketSignature, const FBPActorGripInformation&, GripInformation, const USceneComponent*, NewParentComp, FName, OptionalSocketName, FTransform, RelativeTransformToParent, bool, bWeldingBodies); + +/** Delegate for notification when the controller teleports its grips. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVROnControllerTeleportedGripsSignature); + +/** Delegate for notification when an interactive grip goes out of range and isn't set to auto handle it. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRGripControllerOnGripOutOfRange, const FBPActorGripInformation &, GripInformation, float, Distance); + +/** Delegate for notification when the controller profile transform changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRGripControllerOnProfileTransformChanged, const FTransform &, NewRelTransForProcComps, const FTransform &, NewProfileTransform); + +/** Delegate for notification when the controller handled a local auth grip conflict. Only called on the server. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVROnClientAuthGripConflict, UObject *, Object, EVRClientAuthConflictResolutionMode, ResolutionMethod); + +/** +* Utility class for applying an offset to a hierarchy of components in the renderer thread. +*/ +class VREXPANSIONPLUGIN_API FExpandedLateUpdateManager +{ +public: + FExpandedLateUpdateManager(); + + virtual ~FExpandedLateUpdateManager() {} + + /** Setup state for applying the render thread late update */ + void Setup(const FTransform& ParentToWorld, UGripMotionControllerComponent* Component, bool bSkipLateUpdate); + + /** Apply the late update delta to the cached components */ + void Apply_RenderThread(FSceneInterface* Scene, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform); + // #TODO: UE5 is missing this pull + //void Apply_RenderThread(FSceneInterface* Scene, const int32 FrameNumber, const FTransform& OldRelativeTransform, const FTransform& NewRelativeTransform); + + /** Returns true if the LateUpdateSetup data is stale. */ + bool GetSkipLateUpdate_RenderThread() const { return UpdateStates[LateUpdateRenderReadIndex].bSkip; } + +public: + + /** A utility method that calls CacheSceneInfo on ParentComponent and all of its descendants */ + void GatherLateUpdatePrimitives(USceneComponent* ParentComponent); + void ProcessGripArrayLateUpdatePrimitives(UGripMotionControllerComponent* MotionController, TArray<FBPActorGripInformation> & GripArray); + + /** Generates a LateUpdatePrimitiveInfo for the given component if it has a SceneProxy and appends it to the current LateUpdatePrimitives array */ + void CacheSceneInfo(USceneComponent* Component); + + struct FLateUpdateState + { + FLateUpdateState() + : ParentToWorld(FTransform::Identity) + , bSkip(false) + , TrackingNumber(-1) + {} + + /** Parent world transform used to reconstruct new world transforms for late update scene proxies */ + FTransform ParentToWorld; + /** Primitives that need late update before rendering */ + TMap<FPrimitiveSceneInfo*, int32> Primitives; + /** Late Update Info Stale, if this is found true do not late update */ + bool bSkip; + /** Frame tracking number - used to flag if the game and render threads get badly out of sync */ + int64 TrackingNumber; + }; + + FLateUpdateState UpdateStates[2]; + int32 LateUpdateGameWriteIndex; + int32 LateUpdateRenderReadIndex; +}; + +/** +* Tick function that does post physics work. This executes in EndPhysics (after physics is done) +**/ +USTRUCT() +struct FGripComponentEndPhysicsTickFunction : public FTickFunction +{ + GENERATED_USTRUCT_BODY() + + UGripMotionControllerComponent* Target; + + /** + * Abstract function to execute the tick. + * @param DeltaTime - frame time to advance, in seconds. + * @param TickType - kind of tick for this frame. + * @param CurrentThread - thread we are executing on, useful to pass along as new tasks are created. + * @param MyCompletionGraphEvent - completion event for this task. Useful for holding the completetion of this task until certain child tasks are complete. + */ + virtual void ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override; + /** Abstract function to describe this tick. Used to print messages about illegal cycles in the dependency graph. */ + virtual FString DiagnosticMessage() override; + /** Function used to describe this tick for active tick reporting. **/ + virtual FName DiagnosticContext(bool bDetailed) override; +}; + +template<> +struct TStructOpsTypeTraits<FGripComponentEndPhysicsTickFunction> : public TStructOpsTypeTraitsBase2<FGripComponentEndPhysicsTickFunction> +{ + enum + { + WithCopy = false + }; +}; + +/** +* An override of the MotionControllerComponent that implements position replication and Gripping with grip replication and controllable late updates per object. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = MotionController) +class VREXPANSIONPLUGIN_API UGripMotionControllerComponent : public UMotionControllerComponent//PrimitiveComponent +{ + +public: + + FGripComponentEndPhysicsTickFunction EndPhysicsTickFunction; + friend struct FGripComponentEndPhysicsTickFunction; + + /** Update systems after physics sim is done */ + void EndPhysicsTickComponent(FGripComponentEndPhysicsTickFunction& ThisTickFunction); + void RegisterEndPhysicsTick(bool bRegister); + + // If true then we will sample the post physics scene to get the relative location of this object. + // This lets us reproject that relative position prior to running the grip logic. + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GripMotionController|Advanced") + bool bProjectNonSimulatingGrips; + + + // The grip script that defines the default behaviors of grips + // Don't edit this unless you really know what you are doing, leave it empty + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced") + TSubclassOf<class UVRGripScriptBase> DefaultGripScriptClass; + + // This is the pointer to the default grip script + // It is here to access so if you want to set some variables on your override then you can + // Due to a bug with instanced variables and parent classes you can't directly edit this in subclass in the details panel + UPROPERTY(VisibleAnywhere, Transient, BlueprintReadOnly, Category = "GripMotionController|Advanced") + TObjectPtr<UVRGripScriptBase> DefaultGripScript; + + // Lerping functions and events + void InitializeLerpToHand(FBPActorGripInformation& GripInfo); + void HandleGlobalLerpToHand(FBPActorGripInformation& GripInformation, FTransform& WorldTransform, float DeltaTime); + + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void CancelGlobalLerpToHand(uint8 GripID); + + //UPROPERTY(BlueprintAssignable, Category = "Grip Events") + // FVROnControllerGripSignature OnLerpToHandBegin; + + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnLerpToHandFinished; + + + // If true we will scale the tracking of the motion controller by the TrackingScaler value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bScaleTracking; + + // A scale to be applied to the tracked positions of the controller if bScaleTracking is true + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bScaleTracking")) + FVector TrackingScaler; + + // If true we will use the minimum height value to clamp the Z too + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bLimitMinHeight; + + // The minimum height to allow for this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.0", UIMin = "0.0", EditCondition = "bLimitMinHeight")) + float MinimumHeight; + + // If true we will use the maximum height value to clamp the Z too + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bLimitMaxHeight; + + // The maximum height to allow for this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLimitMinHeight")) + float MaximumHeight; + + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bOffsetByHMD; + + // If true this controller will attempt to stay within its LeashRange distance from the HMD + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking") + bool bLeashToHMD; + + // How far away from the HMD the controller should stay max (vector distance) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLeashToHMD")) + float LeashRange; + + void ApplyTrackingParameters(FVector& OriginalPosition, bool bIsInGameThread); + bool HasTrackingParameters(); + + // When true any physics constraints will be attached to the grip pivot instead of a new kinematic actor in the scene + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Advanced") + bool bConstrainToPivot; + + UPROPERTY() + TObjectPtr<AVRBaseCharacter> AttachChar; + void UpdateTracking(float DeltaTime); + virtual void OnAttachmentChanged() override; + + FVector LastLocationForLateUpdate; + FTransform LastRelativePosition; + + // If true will smooth the hand tracking data with a TInterp function + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + bool bSmoothHandTracking; + + bool bWasSmoothingHand; + + // If true will smooth hand tracking with the Linear and Rotational 1 Euro low pass settings instead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + bool bSmoothWithEuroLowPassFunction; + + // The interp speed to use if smoothing is enabled and not using the 1 Euro smoothing + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + float SmoothingSpeed; + + // Smoothing parameters when using the 1 Euro low pass option + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|Smoothing") + FBPEuroLowPassFilterTrans EuroSmoothingParams; + + FTransform LastSmoothRelativeTransform; + + // Type of velocity calculation to use for the motion controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ComponentVelocity") + EVRVelocityType VelocityCalculationType; + + // If we should sample the velocity in world or local space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ComponentVelocity") + bool bSampleVelocityInWorldSpace; + + // If not using velocity mode "default" this is the number of sample to keep + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ComponentVelocity") + int32 VelocitySamples; + + FBPLowPassPeakFilter PeakFilter; + + virtual FVector GetComponentVelocity() const override; + + // If true will offset the tracked location of the controller by the controller profile that is currently loaded. + // Thows the event OnControllerProfileTransformChanged when it happens so that you can adjust specific components + // Like procedural ones for the offset (procedural meshes are already correctly offset for the controller and + // need rewound. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController") + bool bOffsetByControllerProfile; + + // Stores current transform so we don't have to keep casting + FTransform CurrentControllerProfileTransform; + + // Called when the controller profile changed and we have a new transform (only if bOffsetByControllerProfile is true) + UPROPERTY(BlueprintAssignable, Category = "GripMotionController") + FVRGripControllerOnProfileTransformChanged OnControllerProfileTransformChanged; + + // Called when the controller profile changed and we have a new transform (only if bOffsetByControllerProfile is true) + UPROPERTY(BlueprintAssignable, Category = "GripMotionController") + FVRGripControllerOnGripOutOfRange OnGripOutOfRange; + +private: + + GENERATED_BODY() + +public: + UGripMotionControllerComponent(const FObjectInitializer& ObjectInitializer); + + ~UGripMotionControllerComponent(); + + // Custom version of the component sweep function to remove that aggravating warning epic is throwing about skeletal mesh components. + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void InitializeComponent() override; + virtual void OnUnregister() override; + //virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + virtual void Deactivate() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void BeginDestroy() override; + virtual void BeginPlay() override; + + /** Post-physics tick function for this character */ + //UPROPERTY() + // FTickFunction PostPhysicsTickFunction; + +protected: + //~ Begin UActorComponent Interface. + virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; + virtual void SendRenderTransform_Concurrent() override; + //~ End UActorComponent Interface. + + FTransform GripRenderThreadRelativeTransform; + FVector GripRenderThreadComponentScale; + FTransform GripRenderThreadProfileTransform; + FVector GripRenderThreadLastLocationForLateUpdate; + + FDelegateHandle NewControllerProfileEvent_Handle; + UFUNCTION() + void NewControllerProfileLoaded(); + void GetCurrentProfileTransform(bool bBindToNoticationDelegate); + +public: + + // Called when a controller first gets a valid tracked frame + UPROPERTY(BlueprintAssignable, Category = "GripMotionController") + FVRGripControllerOnTrackingEventSignature OnTrackingChanged; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnGrippedObject; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerDropSignature OnDroppedObject; + + // Called when a object is being socketed, prior to OnDrop being called and prior to the actual socketing being performed + // Generally an early entry to detach hands and handle pre-socketing logic + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerSocketSignature OnSocketingObject; + + // Called when a gripped object has been teleported + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerTeleportedGripsSignature OnTeleportedGrips; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnSecondaryGripRemoved; + + // Storage for remote players for secondary grips specifically + // Its more than a little hacky, but ordering of drop and the secondaries being + // removed on the rep notify require this in rare cases. If Epic ever fixes the last array state not containing removed + // grips then this would be a lot cleaner. + TArray<uint8> SecondaryGripIDs; + + + // Called when an object we hold has its grip transform changed + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnControllerGripSignature OnGripTransformChanged; + + // Gets the hand enum + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "HandType", CompactNodeTitle = "HandType")) + void GetHandType(EControllerHand& Hand); + + // The component to use for basing the grip off of instead of the motion controller + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GripMotionController|CustomPivot") + TObjectPtr<USceneComponent> CustomPivotComponent; + + // The socket for the component to use for basing the grip off of instead of the motion controller + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GripMotionController|CustomPivot") + FName CustomPivotComponentSocketName; + + // If true then we will skip the pivot transform adjustment when gripping an object with the custom pivot + // This is here for legacy support for anyone not using "ConvertToControllerRelativeTransform". + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GripMotionController|CustomPivot") + bool bSkipPivotTransformAdjustment; + + // Set the custom pivot component, allows you to use remote grips easier + UFUNCTION(BlueprintCallable, Category = "GripMotionController|CustomPivot") + void SetCustomPivotComponent(USceneComponent * NewCustomPivotComponent, FName PivotSocketName = NAME_None); + + // Set the custom pivot component, allows you to use remote grips easier + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "GetPivotTransform")) + FTransform GetPivotTransform_BP(); + + // Set the custom pivot component, allows you to use remote grips easier + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "GetPivotLocation")) + FVector GetPivotLocation_BP(); + + FORCEINLINE FTransform GetPivotTransform() + { + return IsValid(CustomPivotComponent) ? CustomPivotComponent->GetSocketTransform(CustomPivotComponentSocketName) : this->GetComponentTransform(); + } + + FORCEINLINE FVector GetPivotLocation() + { + return IsValid(CustomPivotComponent) ? CustomPivotComponent->GetSocketLocation(CustomPivotComponentSocketName) : this->GetComponentLocation(); + } + + // Increments with each grip, wraps back to 0 after max due to modulo operation + // I don't think that a 254 (126 total grips) grip index is going to be used up and allow duplicates unless + // someone does something crazy + uint8 GripIDIncrementer; + + + // INVALID_VRGRIP_ID is 0, so + 1 is 1 + inline uint8 GetNextGripID(bool bIsLocalGrip) + { + // We are skipping index 0 to leave it for INVALID_GRIP_ID index; + + if (!bIsLocalGrip) // We need to split them between 0-126 for gripped objects server side + { + if (GripIDIncrementer < 127) + GripIDIncrementer++; + else + GripIDIncrementer = (INVALID_VRGRIP_ID + 1); + + return GripIDIncrementer; + } + else // And 128 - 254 for local grips client side, with half for server initiated and half for client + { + + if (!IsServer()) + { + if (GripIDIncrementer < 63) + GripIDIncrementer++; + else + GripIDIncrementer = (INVALID_VRGRIP_ID + 1); + + return GripIDIncrementer + 128; + } + else + { + if (GripIDIncrementer < 63) + GripIDIncrementer++; + else + GripIDIncrementer = (INVALID_VRGRIP_ID + 1); + + return GripIDIncrementer + 128 + 64; + } + } + } + + // When possible I suggest that you use GetAllGrips/GetGrippedObjects instead of directly referencing this + UPROPERTY(BlueprintReadOnly, Replicated, Category = "GripMotionController", ReplicatedUsing = OnRep_GrippedObjects) + TArray<FBPActorGripInformation> GrippedObjects; + + // When possible I suggest that you use GetAllGrips/GetGrippedObjects instead of directly referencing this + UPROPERTY(BlueprintReadOnly, Replicated, Category = "GripMotionController", ReplicatedUsing = OnRep_LocallyGrippedObjects) + TArray<FBPActorGripInformation> LocallyGrippedObjects; + + // Local Grip TransactionalBuffer to store server sided grips that need to be emplaced into the local buffer + UPROPERTY(BlueprintReadOnly, Replicated, Category = "GripMotionController", ReplicatedUsing = OnRep_LocalTransaction) + TArray<FBPActorGripInformation> LocalTransactionBuffer; + + // Locally Gripped Array functions + + // Notify a client that their local grip was bad + UFUNCTION(Reliable, Client, Category = "GripMotionController") + void Client_NotifyInvalidLocalGrip(UObject * LocallyGrippedObject, uint8 GripID, bool bWasAGripConflict = false); + + // Notify the server that we locally gripped something + UFUNCTION(Reliable, Server, WithValidation, Category = "GripMotionController") + void Server_NotifyLocalGripAddedOrChanged(const FBPActorGripInformation & newGrip); + + // Notify the server that we changed some secondary attachment information + UFUNCTION(Reliable, Server, WithValidation) + void Server_NotifySecondaryAttachmentChanged( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo); + + // Notify the server that we changed some secondary attachment information + // This one specifically sends out the new relative location for a retain secondary grip + UFUNCTION(Reliable, Server, WithValidation) + void Server_NotifySecondaryAttachmentChanged_Retain( + uint8 GripID, + const FBPSecondaryGripInfo& SecondaryGripInfo, const FTransform_NetQuantize & NewRelativeTransform); + + // Notify change on relative position editing as well, make RPCS callable in blueprint + // Notify the server that we locally gripped something + UFUNCTION(Reliable, Server, WithValidation) + void Server_NotifyLocalGripRemoved(uint8 GripID, const FTransform_NetQuantize &TransformAtDrop, FVector_NetQuantize100 AngularVelocity, FVector_NetQuantize100 LinearVelocity); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController|ClientAuth") + EVRClientAuthConflictResolutionMode ClientAuthConflictResolutionMethod; + + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnClientAuthGripConflict OnClientAuthGripConflict; + + // Handle a server initiated grip + UFUNCTION(Reliable, Server, WithValidation, Category = "GripMotionController") + void Server_NotifyHandledTransaction(uint8 GripID); + + // Enable this to send the TickGrip event every tick even for non custom grip types - has a slight performance hit + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController") + bool bAlwaysSendTickGrip; + + // Clean up a grip that is "bad", object is being destroyed or was a bad destructible mesh + void CleanUpBadGrip(TArray<FBPActorGripInformation> &GrippedObjectsArray, int GripIndex, bool bReplicatedArray); + void CleanUpBadPhysicsHandles(); + + // Recreates a grip physics handle bodies + // If FullRecreate is false then it will only set the COM and actors, otherwise will re-init the entire grip + bool UpdatePhysicsHandle(uint8 GripID, bool bFullyRecreate = true); + bool UpdatePhysicsHandle(const FBPActorGripInformation & GripInfo, bool bFullyRecreate = true); + + inline void NotifyGripTransformChanged(const FBPActorGripInformation & GripInfo) + { + if (OnGripTransformChanged.IsBound()) + { + FBPActorGripInformation CurrentGrip; + EBPVRResultSwitch Result; + GetGripByID(CurrentGrip, GripInfo.GripID, Result); + if (Result == EBPVRResultSwitch::OnSucceeded) + { + OnGripTransformChanged.Broadcast(CurrentGrip); + } + } + } + + // Recreates a grip in situations where the collision type or movement replication type may have been changed + inline void ReCreateGrip(FBPActorGripInformation & GripInfo) + { + int HandleIndex = 0; + if (GetPhysicsGripIndex(GripInfo, HandleIndex)) + { + + DestroyPhysicsHandle(&PhysicsGrips[HandleIndex]); + PhysicsGrips.RemoveAt(HandleIndex); + } + + // Grip Type or replication was changed + NotifyGrip(GripInfo, true); + } + + // Handles variable state changes and specific actions on a grip replication + inline bool HandleGripReplication(FBPActorGripInformation & Grip, FBPActorGripInformation * OldGripInfo = nullptr) + { + if (Grip.ValueCache.bWasInitiallyRepped && Grip.GripID != Grip.ValueCache.CachedGripID) + { + // There appears to be a bug with TArray replication where if you replace an index with another value of that + // Index, it doesn't fully re-init the object, this is a workaround to re-zero non replicated variables + // when that happens. + Grip.ClearNonReppingItems(); + } + + // Ignore server down no rep grips, this is kind of unavoidable unless I make yet another list which I don't want to do + if (Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + // skip init + Grip.ValueCache.bWasInitiallyRepped = true; + + // null ptr so this doesn't block grip operations + Grip.GrippedObject = nullptr; + + // Set to paused so iteration skips it + Grip.bIsPaused = true; + } + + if (!Grip.ValueCache.bWasInitiallyRepped) // Hasn't already been initialized + { + Grip.ValueCache.bWasInitiallyRepped = NotifyGrip(Grip); // Grip it + + // Tick will keep checking from here on out locally + if (!Grip.ValueCache.bWasInitiallyRepped) + { + //UE_LOG(LogVRMotionController, Warning, TEXT("Replicated grip Notify grip failed, was grip called before the object was replicated to the client?")); + return false; + } + + if(Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + // Reset the secondary grip distance + Grip.SecondaryGripInfo.SecondaryGripDistance = 0.0f; + + if (FMath::IsNearlyZero(Grip.SecondaryGripInfo.LerpToRate)) // Zero, could use IsNearlyZero instead + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + else + { + Grip.SecondaryGripInfo.curLerp = Grip.SecondaryGripInfo.LerpToRate; + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::StartLerp; + } + + if (Grip.GrippedObject && Grip.GrippedObject->IsValidLowLevelFast() && Grip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + SecondaryGripIDs.Add(Grip.GripID); + IVRGripInterface::Execute_OnSecondaryGrip(Grip.GrippedObject, this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(Grip.GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGrip(this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + } + } + } + } + + OnSecondaryGripAdded.Broadcast(Grip); + } + //Grip.ValueCache.bWasInitiallyRepped = true; // Set has been initialized + } + else if(OldGripInfo != nullptr) // Check for changes from cached information if we aren't skipping the delta check + { + // Manage lerp states + if ((OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment != Grip.SecondaryGripInfo.bHasSecondaryAttachment) || + (OldGripInfo->SecondaryGripInfo.SecondaryAttachment != Grip.SecondaryGripInfo.SecondaryAttachment) || + (!OldGripInfo->SecondaryGripInfo.SecondaryRelativeTransform.Equals(Grip.SecondaryGripInfo.SecondaryRelativeTransform))) + { + // Reset the secondary grip distance + Grip.SecondaryGripInfo.SecondaryGripDistance = 0.0f; + + if (FMath::IsNearlyZero(Grip.SecondaryGripInfo.LerpToRate)) // Zero, could use IsNearlyZero instead + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::NotLerping; + else + { + // New lerp + if (Grip.SecondaryGripInfo.bHasSecondaryAttachment) + { + Grip.SecondaryGripInfo.curLerp = Grip.SecondaryGripInfo.LerpToRate; + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::StartLerp; + } + else // Post Lerp + { + Grip.SecondaryGripInfo.curLerp = Grip.SecondaryGripInfo.LerpToRate; + Grip.SecondaryGripInfo.GripLerpState = EGripLerpState::EndLerp; + } + } + + bool bSendReleaseEvent = ((!Grip.SecondaryGripInfo.bHasSecondaryAttachment && OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment) || + ((Grip.SecondaryGripInfo.bHasSecondaryAttachment && OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment) && + (OldGripInfo->SecondaryGripInfo.SecondaryAttachment != Grip.SecondaryGripInfo.SecondaryAttachment))); + + bool bSendGripEvent = (Grip.SecondaryGripInfo.bHasSecondaryAttachment && + (!OldGripInfo->SecondaryGripInfo.bHasSecondaryAttachment || (OldGripInfo->SecondaryGripInfo.SecondaryAttachment != Grip.SecondaryGripInfo.SecondaryAttachment))); + + if (bSendReleaseEvent) + { + if (Grip.GrippedObject && Grip.GrippedObject->IsValidLowLevelFast() && Grip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + IVRGripInterface::Execute_OnSecondaryGripRelease(Grip.GrippedObject, this, OldGripInfo->SecondaryGripInfo.SecondaryAttachment, Grip); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(Grip.GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGripRelease(this, OldGripInfo->SecondaryGripInfo.SecondaryAttachment, Grip); + } + } + } + } + + SecondaryGripIDs.Remove(Grip.GripID); + OnSecondaryGripRemoved.Broadcast(Grip); + } + + if (bSendGripEvent) + { + if (Grip.GrippedObject && Grip.GrippedObject->IsValidLowLevelFast() && Grip.GrippedObject->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass())) + { + SecondaryGripIDs.Add(Grip.GripID); + IVRGripInterface::Execute_OnSecondaryGrip(Grip.GrippedObject, this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + + TArray<UVRGripScriptBase*> GripScripts; + if (IVRGripInterface::Execute_GetGripScripts(Grip.GrippedObject, GripScripts)) + { + for (UVRGripScriptBase* Script : GripScripts) + { + if (Script) + { + Script->OnSecondaryGrip(this, Grip.SecondaryGripInfo.SecondaryAttachment, Grip); + } + } + } + } + + OnSecondaryGripAdded.Broadcast(Grip); + } + } + + if (OldGripInfo->GripCollisionType != Grip.GripCollisionType || + OldGripInfo->GripMovementReplicationSetting != Grip.GripMovementReplicationSetting || + OldGripInfo->GrippedBoneName != Grip.GrippedBoneName || + OldGripInfo->AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings != Grip.AdvancedGripSettings.PhysicsSettings.bUsePhysicsSettings + ) + { + ReCreateGrip(Grip); // Need to re-create grip + } + else // If re-creating the grip anyway we don't need to do the below + { + bool bTransformChanged = !OldGripInfo->RelativeTransform.Equals(Grip.RelativeTransform); + + // If physics settings got changed server side + if (!FMath::IsNearlyEqual(OldGripInfo->Stiffness, Grip.Stiffness) || + !FMath::IsNearlyEqual(OldGripInfo->Damping, Grip.Damping) || + OldGripInfo->AdvancedGripSettings.PhysicsSettings != Grip.AdvancedGripSettings.PhysicsSettings || + bTransformChanged + ) + { + UpdatePhysicsHandle(Grip); + + if (bTransformChanged) + { + NotifyGripTransformChanged(Grip); + } + } + } + } + + // Set caches now for next rep + Grip.ValueCache.CachedGripID = Grip.GripID; + + return true; + } + + inline void CheckTransactionBuffer() + { + if (LocalTransactionBuffer.Num()) + { + for (int i = LocalTransactionBuffer.Num() - 1; i >= 0; --i) + { + if (LocalTransactionBuffer[i].ValueCache.bWasInitiallyRepped && LocalTransactionBuffer[i].GripID != LocalTransactionBuffer[i].ValueCache.CachedGripID) + { + // There appears to be a bug with TArray replication where if you replace an index with another value of that + // Index, it doesn't fully re-init the object, this is a workaround to re-zero non replicated variables + // when that happens. + LocalTransactionBuffer[i].ClearNonReppingItems(); + } + + if (!LocalTransactionBuffer[i].ValueCache.bWasInitiallyRepped && LocalTransactionBuffer[i].GrippedObject->IsValidLowLevelFast()) + { + LocalTransactionBuffer[i].ValueCache.bWasInitiallyRepped = true; + LocalTransactionBuffer[i].ValueCache.CachedGripID = LocalTransactionBuffer[i].GripID; + + int32 Index = LocallyGrippedObjects.Add(LocalTransactionBuffer[i]); + + if (Index != INDEX_NONE) + { + NotifyGrip(LocallyGrippedObjects[Index]); + } + + Server_NotifyHandledTransaction(LocalTransactionBuffer[i].GripID); + } + } + } + } + + UFUNCTION() + virtual void OnRep_LocalTransaction(TArray<FBPActorGripInformation> OriginalArrayState) // Original array state is useless without full serialize, it just hold last delta + { + CheckTransactionBuffer(); + } + + UFUNCTION() + virtual void OnRep_GrippedObjects(TArray<FBPActorGripInformation> OriginalArrayState) // Original array state is useless without full serialize, it just hold last delta + { + // Need to think about how best to handle the simulating flag here, don't handle for now + // Check for removed gripped actors + // This might actually be better left as an RPC multicast + + for (int i = GrippedObjects.Num() - 1; i >= 0; --i) + { + HandleGripReplication(GrippedObjects[i], OriginalArrayState.FindByKey(GrippedObjects[i].GripID)); + } + } + + UFUNCTION() + virtual void OnRep_LocallyGrippedObjects(TArray<FBPActorGripInformation> OriginalArrayState) + { + for (int i = LocallyGrippedObjects.Num() - 1; i >= 0; --i) + { + HandleGripReplication(LocallyGrippedObjects[i], OriginalArrayState.FindByKey(LocallyGrippedObjects[i].GripID)); + } + } + + UPROPERTY(BlueprintReadWrite, Category = "GripMotionController") + TArray<UPrimitiveComponent *> AdditionalLateUpdateComponents; + + // Movement Replication + // Actor needs to be replicated for this to work + + UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedControllerTransform, Category = "GripMotionController|Networking") + FBPVRComponentPosRep ReplicatedControllerTransform; + + FVector LastUpdatesRelativePosition; + FRotator LastUpdatesRelativeRotation; + + bool bLerpingPosition; + bool bReppedOnce; + + UFUNCTION() + virtual void OnRep_ReplicatedControllerTransform() + { + //ReplicatedControllerTransform.Unpack(); + + if (GetNetMode() < ENetMode::NM_Client && HasTrackingParameters()) + { + // Ensure that the client is sending valid boundries + ApplyTrackingParameters(ReplicatedControllerTransform.Position, true); + } + + if (bSmoothReplicatedMotion) + { + if (bReppedOnce) + { + bLerpingPosition = true; + ControllerNetUpdateCount = 0.0f; + LastUpdatesRelativePosition = this->GetRelativeLocation(); + LastUpdatesRelativeRotation = this->GetRelativeRotation(); + } + else + { + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + bReppedOnce = true; + } + } + else + SetRelativeLocationAndRotation(ReplicatedControllerTransform.Position, ReplicatedControllerTransform.Rotation); + } + + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "GripMotionController|Networking", meta = (ClampMin = "0", UIMin = "0")) + float ControllerNetUpdateRate; + + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case, also used for remotes to lerp position + float ControllerNetUpdateCount; + + // Whether to smooth (lerp) between ticks for the replicated motion, DOES NOTHING if update rate is larger than FPS! + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "GripMotionController|Networking") + bool bSmoothReplicatedMotion; + + // Whether to replicate even if no tracking (FPS or test characters) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "GripMotionController|Networking") + bool bReplicateWithoutTracking; + + // I'm sending it unreliable because it is being resent pretty often + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendControllerTransform(FBPVRComponentPosRep NewTransform); + + // Pointer to an override to call from the owning character - this saves 7 bits a rep avoiding component IDs on the RPC + typedef void (AVRBaseCharacter::*VRBaseCharTransformRPC_Pointer)(FBPVRComponentPosRep NewTransform); + VRBaseCharTransformRPC_Pointer OverrideSendTransform; + + // Need this as I can't think of another way for an actor component to make sure it isn't on the server + inline bool IsLocallyControlled() const + { + // I like epics new authority check more than mine + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast<APawn>(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner && MyOwner->Role == ENetRole::ROLE_Authority); + } + + // Returns if this is the owning connection for the motion controller + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "IsLocallyControlled")) + bool BP_IsLocallyControlled(); + + // Checks if the controllers own is torn off on the network, used to skip some RPCS + inline bool IsTornOff() const + { + const AActor* MyOwner = GetOwner(); + return MyOwner ? MyOwner->GetTearOff() : false; + } + + + inline bool IsServer() const + { + if (GEngine != nullptr && GWorld != nullptr) + { + return GEngine->GetNetMode(GWorld) < NM_Client; + } + + return false; + } + + // Drops a gripped object and sockets it to the given component at the given relative transform. + // *Note*: If both the parent and the child are simulating it also delays a single tick and then re-applies the relative transform. + // This is to avoid a race condition where we need to wait for the next physics update. + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropAndSocketObject(const FTransform_NetQuantize & RelativeTransformToParent, UObject * ObjectToDrop = nullptr, uint8 GripIDToDrop = 0, USceneComponent * SocketingParent = nullptr, FName OptionalSocketName = NAME_None, bool bWeldBodies = true); + + // Drops a grip and sockets it to the given component at the given relative transform. + // *Note*: If both the parent and the child are simulating it also delays a single tick and then re-applies the relative transform. + // This is to avoid a race condition where we need to wait for the next physics update. + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropAndSocketGrip(const FBPActorGripInformation &GripToDrop, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies = true); + bool DropAndSocketGrip_Implementation(const FBPActorGripInformation& GripToDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies = true, bool bSkipServerNotify = false); + + // Notify the server about a new drop and socket + UFUNCTION(Reliable, Server, WithValidation, Category = "GripMotionController") + void Server_NotifyDropAndSocketGrip(uint8 GripID, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies = true); + + UFUNCTION(Reliable, NetMulticast) + void NotifyDropAndSocket(const FBPActorGripInformation &NewDrop, USceneComponent* SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize& RelativeTransformToParent, bool bWeldBodies = true); + + void DropAndSocket_Implementation(const FBPActorGripInformation &NewDrop); + void Socket_Implementation(UObject * ObjectToSocket, bool bWasSimulating, USceneComponent * SocketingParent, FName OptionalSocketName, const FTransform_NetQuantize & RelativeTransformToParent, bool bWeldBodies = true); + + // Keep a hard reference to the drop to avoid deletion errors + UPROPERTY() + TArray<TObjectPtr<UObject>> ObjectsWaitingForSocketUpdate; + + // Resets the transform of a socketed grip 1 tick later, this is to avoid a race condition with simulating grips. + // Their constraint can change the transform before or after the attachment happens if the parent and the child are both simulating. + UFUNCTION() + void SetSocketTransform(UObject* ObjectToSocket, /*USceneComponent * SocketingParent,*/ const FTransform_NetQuantize RelativeTransformToParent/*, FName OptionalSocketName, bool bWeldBodies*/); + + /* Auto grip any uobject that is/root is a primitive component and has the VR Grip Interface + these are stored in a Tarray that will prevent destruction of the object, you MUST ungrip an actor if you want to kill it + The WorldOffset is the transform that it will remain away from the controller, if you use the world position of the actor then it will grab + at the point of intersection. + + If WorldOffsetIsRelative is true then it will not convert the transform from world space but will instead use that offset directly. + You could pass in a socket relative transform with this set for snapping or an empty transform to snap the object at its 0,0,0 point. + + If you declare a valid OptionSnapToSocketName then it will instead snap the actor to the relative offset + location that the socket is to its parent actor. + + It will only do this if the WorldOffset value is left default, if it is not, then it will treat this as the name of the slot + that you already have the transform for. + + If you declare a valid OptionalBoneToGripName then it will grip that physics body with physics grips (It will expect a bone worldspace transform then, + if you pass in the normal actor/root component world space transform then the grip will not be positioned correctly). + */ + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripObject( + UObject * ObjectToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative = false, + FName OptionalSnapToSocketName = NAME_None, + FName OptionalBoneToGripName = NAME_None, + EGripCollisionType GripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics, + EGripLateUpdateSettings GripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping, + EGripMovementReplicationSettings GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement, + float GripStiffness = 1500.0f, + float GripDamping = 200.0f, bool bIsSlotGrip = false); + + + // Auto drop any uobject that is/root is a primitive component and has the VR Grip Interface + // If an object is passed in it will attempt to drop it, otherwise it will attempt to find and drop the given grip id + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropObject( + UObject * ObjectToDrop = nullptr, + uint8 GripIdToDrop = 0, + bool bSimulate = false, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector); + + // Auto grip any uobject that is/root is a primitive component + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripObjectByInterface(UObject * ObjectToGrip, const FTransform &WorldOffset, bool bWorldOffsetIsRelative = false, FName OptionalBoneToGripName = NAME_None, FName OptionalSnapToSocketName = NAME_None, bool bIsSlotGrip = false); + + // Auto drop any uobject that is/root is a primitive component and has the VR Grip Interface + // If an object is passed in it will attempt to drop it, otherwise it will attempt to find and drop the given grip id + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropObjectByInterface(UObject * ObjectToDrop = nullptr, uint8 GripIDToDrop = 0, FVector OptionalAngularVelocity = FVector::ZeroVector, FVector OptionalLinearVelocity = FVector::ZeroVector); + + /* Grip an actor, these are stored in a Tarray that will prevent destruction of the object, you MUST ungrip an actor if you want to kill it + The WorldOffset is the transform that it will remain away from the controller, if you use the world position of the actor then it will grab + at the point of intersection. + + If WorldOffsetIsRelative is true then it will not convert the transform from world space but will instead use that offset directly. + You could pass in a socket relative transform with this set for snapping or an empty transform to snap the object at its 0,0,0 point. + + If you declare a valid OptionSnapToSocketName then it will instead snap the actor to the relative offset + location that the socket is to its parent actor. + + It will only do this if the WorldOffset value is left default, if it is not, then it will treat this as the name of the slot + that you already have the transform for. + + If you declare a valid OptionalBoneToGripName then it will grip that physics body with physics grips (It will expect a bone worldspace transform then, + if you pass in the normal actor/root component world space transform then the grip will not be positioned correctly). + */ + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripActor( + AActor* ActorToGrip, + const FTransform &WorldOffset, + bool bWorldOffsetIsRelative = false, + FName OptionalSnapToSocketName = NAME_None, + FName OptionalBoneToGripName = NAME_None, + EGripCollisionType GripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics, + EGripLateUpdateSettings GripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping, + EGripMovementReplicationSettings GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement, + float GripStiffness = 1500.0f, + float GripDamping = 200.0f, + bool bIsSlotGrip = false); + + // Drop a gripped actor + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropActor( + AActor* ActorToDrop, + bool bSimulate, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector + ); + + // Grip a component + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GripComponent( + UPrimitiveComponent* ComponentToGrip, + const FTransform &WorldOffset, bool bWorldOffsetIsRelative = false, + FName OptionalsnapToSocketName = NAME_None, + FName OptionalBoneToGripName = NAME_None, + EGripCollisionType GripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics, + EGripLateUpdateSettings GripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping, + EGripMovementReplicationSettings GripMovementReplicationSetting = EGripMovementReplicationSettings::ForceClientSideMovement, + float GripStiffness = 1500.0f, + float GripDamping = 200.0f, + bool bIsSlotGrip = false); + + // Drop a gripped component + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropComponent( + UPrimitiveComponent* ComponentToDrop, + bool bSimulate, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector + ); + + // Master function for dropping a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool DropGrip( + const FBPActorGripInformation &Grip, + bool bSimulate = false, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector); + + bool DropGrip_Implementation( + const FBPActorGripInformation& Grip, + bool bSimulate = false, + FVector OptionalAngularVelocity = FVector::ZeroVector, + FVector OptionalLinearVelocity = FVector::ZeroVector, + bool bSkipNotify = false); + + // No Longer replicated, called via on rep now instead. + //UFUNCTION(Reliable, NetMulticast) + bool NotifyGrip(FBPActorGripInformation &NewGrip, bool bIsReInit = false); + + UFUNCTION(Reliable, NetMulticast) + void NotifyDrop(const FBPActorGripInformation &NewDrop, bool bSimulate); + + // Used so drop logic can be filtered + void Drop_Implementation(const FBPActorGripInformation &NewDrop, bool bSimulate); + + // Get a grip by actor + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByActor(FBPActorGripInformation &Grip, AActor * ActorToLookForGrip, EBPVRResultSwitch &Result); + + // Get a grip by component + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByComponent(FBPActorGripInformation &Grip, UPrimitiveComponent * ComponentToLookForGrip, EBPVRResultSwitch &Result); + + // Gets a grip by object, will auto use ByComponent or ByActor + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByObject(FBPActorGripInformation &Grip, UObject * ObjectToLookForGrip, EBPVRResultSwitch &Result); + + // Gets a grip by its grip ID *NOTE*: Grip IDs are only unique to their controller, do NOT use them as cross controller identifiers + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetGripByID(FBPActorGripInformation &Grip, uint8 IDToLookForGrip, EBPVRResultSwitch &Result); + + // Gets a grip by its grip ID *NOTE*: Grip IDs are only unique to their controller, do NOT use them as cross controller identifiers + FBPActorGripInformation * GetGripPtrByID(uint8 IDToLookForGrip); + + // Get the physics velocities of a grip + UFUNCTION(BlueprintPure, Category = "GripMotionController") + void GetPhysicsVelocities(const FBPActorGripInformation &Grip, FVector &AngularVelocity, FVector &LinearVelocity); + + // Get the physics constraint force of a simulating grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GetPhysicsConstraintForce(const FBPActorGripInformation& Grip, FVector& AngularForce, FVector& LinearForce); + + // Get the root components mass of a grip + UFUNCTION(BlueprintPure, Category = "GripMotionController") + static void GetGripMass(const FBPActorGripInformation& Grip, float& Mass); + + /* Returns the world transform of a gripped object from the grip structure */ + UFUNCTION(BlueprintPure, Category = "GripMotionController") + static FTransform GetGrippedObjectTransform(const FBPActorGripInformation& Grip); + + // Sets whether an active grip is paused or not (is not replicated by default as it is likely you will want to pass variables with this setting). + // If you want it server authed you should RPC a bool down with any additional information (ie: attach location). + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripPaused( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + bool bIsPaused = false, + bool bNoConstraintWhenPaused = false + ); + + // Sets whether an active hybrid grip is locked to its soft setting (is not replicated by default as it is likely you will want to pass variables with this setting). + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripHybridLock( + const FBPActorGripInformation& Grip, + EBPVRResultSwitch& Result, + bool bIsLocked = false + ); + + // Sets the transform to stay at during pause + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void SetPausedTransform( + const FBPActorGripInformation &Grip, + const FTransform & PausedTransform, + bool bTeleport = false + ); + + // Set the Grip Collision Type of a grip, call server side if not a local grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripCollisionType( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + EGripCollisionType NewGripCollisionType = EGripCollisionType::InteractiveCollisionWithPhysics + ); + + + // Set the late update setting of a grip, call server side if not a local grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripLateUpdateSetting( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + EGripLateUpdateSettings NewGripLateUpdateSetting = EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping + ); + + // Set the relative transform of a grip, call server side if not a local grip + // Can check HasGripAuthority to decide if callable locally + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripRelativeTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewRelativeTransform + ); + + // Set the addition transform of a grip, CALL LOCALLY, not server side, Addition transform is not replicated + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripAdditionTransform( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + const FTransform & NewAdditionTransform, bool bMakeGripRelative = false + ); + + // Set the constraint stiffness and dampening of a grip, call server side if not a local grip + // Can check HasGripAuthority to decide if callable locally + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void SetGripStiffnessAndDamping( + const FBPActorGripInformation &Grip, + EBPVRResultSwitch &Result, + float NewStiffness, float NewDamping, bool bAlsoSetAngularValues = false, float OptionalAngularStiffness = 0.0f, float OptionalAngularDamping = 0.0f + ); + + // Used to convert an offset transform to grip relative, useful for storing an initial offset and then lerping back to 0 without re-calculating every tick + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "CreateGripRelativeAdditionTransform")) + FTransform CreateGripRelativeAdditionTransform_BP( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative = false + ); + + inline FTransform CreateGripRelativeAdditionTransform( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative = false + ); + + + // Checks if we have grip authority + inline bool HasGripAuthority(const FBPActorGripInformation &Grip); + + // Checks if we have grip authority over a given object + inline bool HasGripAuthority(const UObject * ObjToCheck); + + // Returns if we have grip authority (can call drop / grip on this grip) + // Mostly for networked games as local grips are client authed and all others are server authed + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "HasGripAuthority")) + bool BP_HasGripAuthority(const FBPActorGripInformation & Grip); + + // Returns if we have grip authority (can call drop / grip on this grip) + // Mostly for networked games as local grips are client authed and all others are server authed + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "HasGripAuthorityForObject")) + bool BP_HasGripAuthorityForObject(const UObject* ObjToCheck); + + // Checks if we should be handling the movement of a grip based on settings for it + inline bool HasGripMovementAuthority(const FBPActorGripInformation & Grip); + + // Returns if we have grip movement authority (we handle movement of the grip) + // Mostly for networked games where ClientSide will be true for all and ServerSide will be true for server only + UFUNCTION(BlueprintPure, Category = "GripMotionController", meta = (DisplayName = "HasGripMovementAuthority")) + bool BP_HasGripMovementAuthority(const FBPActorGripInformation & Grip); + + // Running the gripping logic in its own function as the main tick was getting bloated + void TickGrip(float DeltaTime); + + // Splitting logic into separate function + void HandleGripArray(TArray<FBPActorGripInformation> &GrippedObjectsArray, const FTransform & ParentTransform, float DeltaTime, bool bReplicatedArray = false); + + // Gets the world transform of a grip, modified by secondary grips, returns if it has a valid transform, if not then this tick will be skipped for the object + bool GetGripWorldTransform(TArray<UVRGripScriptBase*>& GripScripts, float DeltaTime,FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport, bool &bForceADrop); + + // Calculate component to world without the protected tag, doesn't set it, just returns it + inline FTransform CalcControllerComponentToWorld(FRotator Orientation, FVector Position) + { + return this->CalcNewComponentToWorld(FTransform(Orientation, Position)); + } + + // Converts a worldspace transform into being relative to this motion controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + FTransform ConvertToControllerRelativeTransform(const FTransform & InTransform); + + // Creates a secondary grip relative transform + UFUNCTION(BlueprintPure, Category = "GripMotionController") + static FTransform ConvertToGripRelativeTransform(const FTransform& GrippedActorTransform, const FTransform & InTransform); + + // Gets if the given object is held by this controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GetIsObjectHeld(const UObject * ObjectToCheck); + + // Gets if the given actor is held by this controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GetIsHeld(const AActor * ActorToCheck); + + // Gets if the given component is held by this controller + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GetIsComponentHeld(const UPrimitiveComponent * ComponentToCheck); + + // Gets if the given Component is a secondary attach point to a gripped actor + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool GetIsSecondaryAttachment(const USceneComponent * ComponentToCheck, FBPActorGripInformation & Grip); + + // Get if we have gripped objects, local or replicated + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool HasGrippedObjects(); + + // Get the first active and valid grip (local and remote auth both, priority remote) + // Returns false is there is none + UFUNCTION(BlueprintCallable, meta = (Keywords = "Grip", DisplayName = "GetFirstActiveGrip", ScriptName = "GetFirstActiveGrip"), Category = "GripMotionController") + bool K2_GetFirstActiveGrip(FBPActorGripInformation& FirstActiveGrip); + + // Get the first active and valid grip (local and remote auth both, priority remote) + // Returns nullptr if there is none + FBPActorGripInformation* GetFirstActiveGrip(); + + // Get list of all gripped objects grip info structures (local and normal both) + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetAllGrips(TArray<FBPActorGripInformation> &GripArray); + + // Get list of all gripped actors + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetGrippedActors(TArray<AActor*> &GrippedActorArray); + + // Get list of all gripped objects + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetGrippedObjects(TArray<UObject*> &GrippedObjectsArray); + + // Get list of all gripped components + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void GetGrippedComponents(TArray<UPrimitiveComponent*> &GrippedComponentsArray); + + // After teleporting a pawn you NEED to call this, otherwise gripped objects will travel with a sweeped move and can get caught on geometry + // The base Teleport() function automatically calls this already, but when you manually set location you should do it yourself. + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void PostTeleportMoveGrippedObjects(); + + bool bIsPostTeleport; + + // Move a single gripped item back into position ignoring collision in the way + // bTeleportPhysicsGrips says whether we should teleport any physics grips as well + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool TeleportMoveGrippedActor(AActor * GrippedActorToMove, bool bTeleportPhysicsGrips = true); + + // Move a single gripped item back into position ignoring collision in the way + // bTeleportPhysicsGrips says whether we should teleport any physics grips as well + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool TeleportMoveGrippedComponent(UPrimitiveComponent * ComponentToMove, bool bTeleportPhysicsGrips = true); + + // Move a single grip back into position ignoring collision in the way + // bTeleportPhysicsGrips says whether we should teleport any physics grips as well + // bIsForPostTeleport says whether we shuld allow the DropOnTeleport logic to apply or not + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool TeleportMoveGrip(UPARAM(ref)FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips = true, bool bIsForPostTeleport = false); + bool TeleportMoveGrip_Impl(FBPActorGripInformation &Grip, bool bTeleportPhysicsGrips, bool bIsForPostTeleport, FTransform & OptionalTransform); + + // Moves all grips back into position immediately + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + void TeleportMoveGrips(bool bTeleportPhysicsGrips = true, bool bIsForPostTeleport = false); + + // Adds a secondary attachment point to the grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool AddSecondaryAttachmentPoint(UObject * GrippedObjectToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform &OriginalTransform, bool bTransformIsAlreadyRelative = false, float LerpToTime = 0.25f, bool bIsSlotGrip = false, FName SecondarySlotName = NAME_None); + + // Adds a secondary attachment point to the grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool AddSecondaryAttachmentToGrip(const FBPActorGripInformation & GripToAddAttachment, USceneComponent * SecondaryPointComponent, const FTransform &OriginalTransform, bool bTransformIsAlreadyRelative = false, float LerpToTime = 0.25f, bool bIsSlotGrip = false, FName SecondarySlotName = NAME_None); + + // Adds a secondary attachment point to the grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool AddSecondaryAttachmentToGripByID(const uint8 GripID, USceneComponent* SecondaryPointComponent, const FTransform& OriginalTransform, bool bTransformIsAlreadyRelative = false, float LerpToTime = 0.25f, bool bIsSlotGrip = false, FName SecondarySlotName = NAME_None); + + // Removes a secondary attachment point from a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool RemoveSecondaryAttachmentPoint(UObject * GrippedObjectToRemoveAttachment, float LerpToTime = 0.25f); + + // Removes a secondary attachment point from a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool RemoveSecondaryAttachmentFromGrip(const FBPActorGripInformation & GripToRemoveAttachment, float LerpToTime = 0.25f); + + // Removes a secondary attachment point from a grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController") + bool RemoveSecondaryAttachmentFromGripByID(const uint8 GripID = 0, float LerpToTime = 0.25f); + + // This is for testing, setting it to true allows you to test grip with a non VR enabled pawn + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripMotionController") + bool bUseWithoutTracking; + + bool CheckComponentWithSweep(UPrimitiveComponent * ComponentToCheck, FVector Move, FRotator newOrientation, bool bSkipSimulatingComponents/*, bool & bHadBlockingHitOut*/); + + // For physics handle operations + void OnGripMassUpdated(FBodyInstance* GripBodyInstance); + bool SetUpPhysicsHandle(const FBPActorGripInformation &NewGrip, TArray<UVRGripScriptBase*> * GripScripts = nullptr); + bool DestroyPhysicsHandle(const FBPActorGripInformation &Grip, bool bSkipUnregistering = false); + void UpdatePhysicsHandleTransform(const FBPActorGripInformation &GrippedActor, const FTransform& NewTransform); + bool SetGripConstraintStiffnessAndDamping(const FBPActorGripInformation *Grip, bool bUseHybridMultiplier = false); + bool GetPhysicsJointLength(const FBPActorGripInformation &GrippedActor, UPrimitiveComponent * rootComp, FVector & LocOut); + + TArray<FBPActorPhysicsHandleInformation> PhysicsGrips; + FBPActorPhysicsHandleInformation * GetPhysicsGrip(const FBPActorGripInformation & GripInfo); + FBPActorPhysicsHandleInformation * GetPhysicsGrip(const uint8 GripID); + bool GetPhysicsGripIndex(const FBPActorGripInformation & GripInfo, int & index); + FBPActorPhysicsHandleInformation * CreatePhysicsGrip(const FBPActorGripInformation & GripInfo); + bool DestroyPhysicsHandle(FBPActorPhysicsHandleInformation * HandleInfo); + bool PausePhysicsHandle(FBPActorPhysicsHandleInformation* HandleInfo); + bool UnPausePhysicsHandle(FBPActorGripInformation& GripInfo, FBPActorPhysicsHandleInformation* HandleInfo); + + // Gets the advanced physics handle settings + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "GetPhysicsHandleSettings")) + bool GetPhysicsHandleSettings(UPARAM(ref)const FBPActorGripInformation & Grip, FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsOut); + + // Sets the advanced physics handle settings, also automatically updates it + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "SetPhysicsHandleSettings")) + bool SetPhysicsHandleSettings(UPARAM(ref)const FBPActorGripInformation& Grip, UPARAM(ref) const FBPAdvancedPhysicsHandleSettings& PhysicsHandleSettingsIn); + + // Creates a physics handle for this grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "SetUpPhysicsHandle")) + bool SetUpPhysicsHandle_BP(UPARAM(ref)const FBPActorGripInformation &Grip); + + // Destroys a physics handle for this grip + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "DestroyPhysicsHandle")) + bool DestroyPhysicsHandle_BP(UPARAM(ref)const FBPActorGripInformation &Grip); + + // Re-creates a physics handle for this grip + // If bFullyRecreate is true then it will set all of the handle properties, if not then it will only reset the physics actors and COM positions + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "UpdatePhysicsHandle")) + bool UpdatePhysicsHandle_BP(UPARAM(ref)const FBPActorGripInformation& Grip, bool bFullyRecreate = true); + + // Update the location of the physics handle + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "UpdatePhysicsHandleTransform")) + void UpdatePhysicsHandleTransform_BP(UPARAM(ref)const FBPActorGripInformation &GrippedActor, UPARAM(ref)const FTransform& NewTransform); + + // Get the grip distance of either the physics handle if there is one, or the difference from the hand to the root component if there isn't + UFUNCTION(BlueprintCallable, Category = "GripMotionController|Custom", meta = (DisplayName = "GetGripDistance")) + bool GetGripDistance_BP(UPARAM(ref)FBPActorGripInformation &Grip, FVector ExpectedLocation, float & CurrentDistance); + + /** If true, the Position and Orientation args will contain the most recent controller state */ + virtual bool GripPollControllerState(FVector& Position, FRotator& Orientation, float WorldToMetersScale); + + /** Whether or not this component had a valid tracked controller associated with it this frame*/ + bool bTracked; + + /** Whether or not this component had a valid tracked device this frame + * + * Use this instead of the normal IsTracked() for the motion controller which will not return the correct information. + * This is messy but I have no access to the various private members of the motion controller. + */ + UFUNCTION(BlueprintPure, Category = "GripMotionController") + bool GripControllerIsTracked() const; + + /** Returns the first valid device ID for this motion controller (across enabled XR systems) + * + * If bCheckOpenVROnly is true, then it only checks for OpenVR IDs (for use with my openVR nodes). + */ + UFUNCTION(BlueprintCallable, Category = "GripMotionController", meta = (ExpandEnumAsExecs = "Result")) + void GetControllerDeviceID(FXRDeviceId & DeviceID, EBPVRResultSwitch &Result, bool bCheckOpenVROnly = false); + + /** Whether or not this component has authority within the frame*/ + bool bHasAuthority; + +private: + /** Whether or not this component is currently on the network server*/ + //bool bIsServer; + + /** View extension object that can persist on the render thread without the motion controller component */ + class FGripViewExtension : public FSceneViewExtensionBase + { + + // #TODO: 4.18 - Uses an auto register base now, revise declaration and implementation + public: + FGripViewExtension(const FAutoRegister& AutoRegister, UGripMotionControllerComponent* InMotionControllerComponent); + + virtual ~FGripViewExtension() {} + + /** ISceneViewExtension interface */ + virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override {} + virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override {} + virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override; + virtual void PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) override {} + virtual void PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) override; + //virtual void LateLatchingViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) override; + + virtual int32 GetPriority() const override { return -10; } + + private: + friend class UGripMotionControllerComponent; + + /** Motion controller component associated with this view extension */ + UGripMotionControllerComponent* MotionControllerComponent; + + FExpandedLateUpdateManager LateUpdate; + }; + TSharedPtr< FGripViewExtension, ESPMode::ThreadSafe > GripViewExtension; + +}; + +FTransform inline UGripMotionControllerComponent::CreateGripRelativeAdditionTransform( + const FBPActorGripInformation &GripToSample, + const FTransform & AdditionTransform, + bool bGripRelative +) +{ + + FTransform FinalTransform; + + if (bGripRelative) + { + FinalTransform = FTransform(AdditionTransform.GetRotation(), GripToSample.RelativeTransform.GetRotation().RotateVector(AdditionTransform.GetLocation()), AdditionTransform.GetScale3D()); + } + else + { + const FTransform PivotToWorld = FTransform(FQuat::Identity, GripToSample.RelativeTransform.GetLocation()); + const FTransform WorldToPivot = FTransform(FQuat::Identity, -GripToSample.RelativeTransform.GetLocation()); + + // Create a transform from it + FTransform RotationOffsetTransform(AdditionTransform.GetRotation(), FVector::ZeroVector); + FinalTransform = FTransform(FQuat::Identity, AdditionTransform.GetLocation(), AdditionTransform.GetScale3D()) * WorldToPivot * RotationOffsetTransform * PivotToWorld; + } + + return FinalTransform; +} + +bool inline UGripMotionControllerComponent::HasGripAuthority(const FBPActorGripInformation &Grip) +{ + if (((Grip.GripMovementReplicationSetting != EGripMovementReplicationSettings::ClientSide_Authoritive && + Grip.GripMovementReplicationSetting != EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && IsServer()) || + ((Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && bHasAuthority)) + { + return true; + } + + return false; +} + +bool inline UGripMotionControllerComponent::HasGripAuthority(const UObject * ObjToCheck) +{ + if (!ObjToCheck) + return false; + + // If it isn't interfaced and we are the server, then allow gripping it + if (!ObjToCheck->GetClass()->ImplementsInterface(UVRGripInterface::StaticClass()) && IsServer()) + return true; + + // I know that it is bad practice to const_cast here, but I want the object to be passed in const + EGripMovementReplicationSettings MovementRepType = IVRGripInterface::Execute_GripMovementReplicationType(const_cast<UObject*>(ObjToCheck)); + + if (((MovementRepType != EGripMovementReplicationSettings::ClientSide_Authoritive && + MovementRepType != EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && IsServer()) || + ((MovementRepType == EGripMovementReplicationSettings::ClientSide_Authoritive || + MovementRepType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) && bHasAuthority)) + { + return true; + } + + return false; +} + +bool inline UGripMotionControllerComponent::HasGripMovementAuthority(const FBPActorGripInformation &Grip) +{ + if (IsServer()) + { + return true; + } + else + { + if (Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceClientSideMovement || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || + Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return true; + } + else if (Grip.GripMovementReplicationSetting == EGripMovementReplicationSettings::ForceServerSideMovement) + { + return false; + } + + // Use original movement type is overridden when initializing the grip and shouldn't happen + check(Grip.GripMovementReplicationSetting != EGripMovementReplicationSettings::KeepOriginalMovement); + } + + return false; +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Default.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Default.h new file mode 100644 index 0000000..376ede0 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Default.h @@ -0,0 +1,48 @@ +#pragma once + +#include "CoreMinimal.h" +#include "VRGripScriptBase.h" +#include "GS_Default.generated.h" + + +/** +* The default grip transform logic for the motion controllers +*/ +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGS_Default : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + UGS_Default(const FObjectInitializer& ObjectInitializer); + + //virtual void BeginPlay_Implementation() override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + + virtual void GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform); + virtual void ApplySmoothingAndLerp(FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, float DeltaTime); + + virtual void CalculateSecondaryLocation(FVector & frontLoc, const FVector& BasePoint, FBPActorGripInformation& Grip, UGripMotionControllerComponent * GrippingController); +}; + +// An extended default grip script that adds less common grip features that were moved out of the default implementation +UCLASS(BlueprintType, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_ExtendedDefault : public UGS_Default +{ + GENERATED_BODY() +public: + + // Whether clamp the grip scaling in scaling grips + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings") + bool bLimitGripScaling; + + // Minimum size to allow scaling in double grip to reach + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings", meta = (editcondition = "bLimitGripScaling")) + FVector_NetQuantize100 MinimumGripScaling; + + // Maximum size to allow scaling in double grip to reach + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings", meta = (editcondition = "bLimitGripScaling")) + FVector_NetQuantize100 MaximumGripScaling; + + virtual void GetAnyScaling(FVector& Scaler, FBPActorGripInformation& Grip, FVector& frontLoc, FVector& frontLocOrig, ESecondaryGripType SecondaryType, FTransform& SecondaryTransform) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_GunTools.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_GunTools.h new file mode 100644 index 0000000..19d8aee --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_GunTools.h @@ -0,0 +1,318 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRGripScriptBase.h" +#include "GripScripts/GS_Default.h" +#include "GS_GunTools.generated.h" + +class UGripMotionControllerComponent; + +// Event thrown when we enter into virtual stock mode +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRVirtualStockModeChangedSignature, bool, IsVirtualStockEngaged); + +// Global settings for this player +USTRUCT(BlueprintType, Category = "GunSettings") +struct VREXPANSIONPLUGIN_API FBPVirtualStockSettings +{ + GENERATED_BODY() +public: + + // *Global Value* Should we auto snap to the virtual stock by a set distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bUseDistanceBasedStockSnapping; + + // *Global Value* The distance before snapping to the stock / unsnapping + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + float StockSnapDistance; + + // *Global Value* The distance from the edge of the stock snap distance where it will be at 100% influence + // Prior to this threshold being hit it will lerp from standard hold to the virtual stock version. + // A value of 0.0f will leave it always off + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock", meta = (ClampMin = "0.00", UIMin = "0.00")) + float StockSnapLerpThreshold; + + // Current lerp value of the stock from zero influence to full influence + UPROPERTY(BlueprintReadOnly, Category = "VirtualStock") + float StockLerpValue; + + // *Global Value* An offset to apply to the HMD location to be considered the neck / mount pivot + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + FVector_NetQuantize100 StockSnapOffset; + + // *Global Value* If we want to have the stock location adjust to follow the primary hands Z value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bAdjustZOfStockToPrimaryHand; + + // *Global Value* Whether we should lerp the location of the rearmost (stock side) hand, mostly used for snipers. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock|Smoothing") + bool bSmoothStockHand; + + // *Global Value* How much influence the virtual stock smoothing should have, 0.0f is zero smoothing, 1.0f is full smoothing, you should test with full smoothing to get the amount you + // want and then set the smoothing value up until it feels right between the fully smoothed and unsmoothed values. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock|Smoothing", meta = (editcondition = "bSmoothStockHand", ClampMin = "0.00", UIMin = "0.00", ClampMax = "1.00", UIMax = "1.00")) + float SmoothingValueForStock; + + // Used to smooth filter the virtual stocks primary hand location + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GunSettings|VirtualStock|Smoothing") + FBPEuroLowPassFilterTrans StockHandSmoothing; + + // Draw debug elements showing the virtual stock location and angles to interacting components + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GunSettings|VirtualStock|Debug") + bool bDebugDrawVirtualStock; + + void CopyFrom(FBPVirtualStockSettings & B) + { + bUseDistanceBasedStockSnapping = B.bUseDistanceBasedStockSnapping; + StockSnapDistance = B.StockSnapDistance; + StockSnapLerpThreshold = B.StockSnapLerpThreshold; + StockSnapOffset = B.StockSnapOffset; + bAdjustZOfStockToPrimaryHand = B.bAdjustZOfStockToPrimaryHand; + bSmoothStockHand = B.bSmoothStockHand; + SmoothingValueForStock = B.SmoothingValueForStock; + StockHandSmoothing = B.StockHandSmoothing; + } + + FBPVirtualStockSettings() + { + StockSnapOffset = FVector(0.f, 0.f, 0.f); + bAdjustZOfStockToPrimaryHand = true; + StockSnapDistance = 35.f; + StockSnapLerpThreshold = 20.0f; + StockLerpValue = 0.0f; + bUseDistanceBasedStockSnapping = true; + SmoothingValueForStock = 0.0f; + bSmoothStockHand = false; + + // Speed up the lerp on fast movements for this + StockHandSmoothing.DeltaCutoff = 20.0f; + StockHandSmoothing.MinCutoff = 5.0f; + + bDebugDrawVirtualStock = false; + } +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FGunTools_AdvSecondarySettings +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings") + bool bUseAdvancedSecondarySettings; + + // Scaler used for handling the smoothing amount, 0.0f is zero smoothing, 1.0f is full smoothing, you should test with full smoothing to get the amount you + // want and then set the smoothing value up until it feels right between the fully smoothed and unsmoothed values. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing", meta = (editcondition = "bUseAdvancedSecondarySettings", ClampMin = "0.00", UIMin = "0.00", ClampMax = "1.00", UIMax = "1.00")) + float SecondaryGripScaler; + + // If true we will constantly be lerping with the grip scaler instead of only sometimes. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing", meta = (editcondition = "bUseAdvancedSecondarySettings")) + bool bUseConstantGripScaler; + + // If true will override custom settings for the smoothing values with the global settings in VRSettings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing", meta = (editcondition = "bUseAdvancedSecondarySettings")) + bool bUseGlobalSmoothingSettings; + + // Used to smooth filter the secondary influence + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|Smoothing") + FBPEuroLowPassFilter SecondarySmoothing; + + // Whether to scale the secondary hand influence off of distance from grip point + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|DistanceInfluence", meta = (editcondition = "bUseAdvancedSecondarySettings")) + bool bUseSecondaryGripDistanceInfluence; + + // If true, will use the GripInfluenceDeadZone as a constant value instead of calculating the distance and lerping, lets you define a static influence amount. + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "SecondaryGripSettings", meta = (editcondition = "bUseSecondaryGripDistanceInfluence")) + // bool bUseGripInfluenceDeadZoneAsConstant; + + // Distance from grip point in local space where there is 100% influence + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|DistanceInfluence", meta = (editcondition = "bUseSecondaryGripDistanceInfluence", ClampMin = "0.00", UIMin = "0.00", ClampMax = "256.00", UIMax = "256.00")) + float GripInfluenceDeadZone; + + // Distance from grip point in local space before all influence is lost on the secondary grip (1.0f - 0.0f influence over this range) + // this comes into effect outside of the deadzone + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvSecondarySettings|DistanceInfluence", meta = (editcondition = "bUseSecondaryGripDistanceInfluence", ClampMin = "1.00", UIMin = "1.00", ClampMax = "256.00", UIMax = "256.00")) + float GripInfluenceDistanceToZero; + + FGunTools_AdvSecondarySettings() + { + bUseAdvancedSecondarySettings = false; + SecondaryGripScaler = 0.0f; + bUseGlobalSmoothingSettings = true; + bUseSecondaryGripDistanceInfluence = false; + //bUseGripInfluenceDeadZoneAsConstant(false), + GripInfluenceDeadZone = 50.0f; + GripInfluenceDistanceToZero = 100.0f; + bUseConstantGripScaler = false; + } +}; + + +// A grip script that adds useful fire-arm related features to grips +// Just adding it to the grippable object provides the features without removing standard +// Grip features. +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_GunTools : public UGS_Default +{ + GENERATED_BODY() +public: + + UGS_GunTools(const FObjectInitializer& ObjectInitializer); + + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) override; + virtual void OnBeginPlay_Implementation(UObject* CallingOwner) override; + virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) override; + //virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) override; + + + // The name of the component that is used to orient the weapon along its primary axis + // If it does not exist then the weapon is assumed to be X+ facing. + // Also used to perform some calculations, make sure it is parented to the gripped object (root component for actors), + // and that the X+ vector of the orientation component is facing the forward direction of the weapon (gun tip for guns, ect). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FName WeaponRootOrientationComponent; + FTransform OrientationComponentRelativeFacing; + FQuat StoredRootOffset; + + // (default false) If true will run through the entire simulation that the owning client uses for the gun. If false, does a lighter and more performant approximation. + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "GunSettings") + bool bUseHighQualityRemoteSimulation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GunSettings") + FGunTools_AdvSecondarySettings AdvSecondarySettings; + + // Offset to apply to the pivot (good for centering pivot into the palm ect). + // For this to apply to the physical center of mass as well an OrientationComponent needs to be defined + // So that we have a valid directional vector to work off of, otherwise the pivot will be in component space and you + // will have a harder time aligning it if the weapon is off axis (still works, just less intuitive). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pivot") + FVector_NetQuantize100 PivotOffset; + + UFUNCTION(BlueprintCallable, Category = "VirtualStock") + void SetVirtualStockComponent(USceneComponent * NewStockComponent) + { + VirtualStockComponent = NewStockComponent; + } + + UFUNCTION(BlueprintCallable, Category = "VirtualStock") + void SetVirtualStockEnabled(bool bAllowVirtualStock) + { + if (!bUseVirtualStock && bAllowVirtualStock) + ResetStockVariables(); + + bUseVirtualStock = bAllowVirtualStock; + } + + void ResetStockVariables() + { + VirtualStockSettings.StockHandSmoothing.ResetSmoothingFilter(); + } + + void GetVirtualStockTarget(UGripMotionControllerComponent * GrippingController); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VirtualStock") + FVRVirtualStockModeChangedSignature OnVirtualStockModeChanged; + + // Overrides the pivot location to be at this component instead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bUseVirtualStock; + + FTransform MountWorldTransform; + bool bIsMounted; + FTransform RelativeTransOnSecondaryRelease; + TObjectPtr<USceneComponent> CameraComponent; + + // Overrides the default behavior of using the HMD location for the stock and uses this component instead + UPROPERTY(BlueprintReadWrite, Category = "VirtualStock") + TObjectPtr<USceneComponent> VirtualStockComponent; + + // Loads the global virtual stock settings on grip (only if locally controlled, you need to manually replicate and store the global settings + // In the character if networked). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock") + bool bUseGlobalVirtualStockSettings; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VirtualStock", meta = (editcondition = "!bUseGlobalVirtualStockSettings")) + FBPVirtualStockSettings VirtualStockSettings; + + // If this gun has recoil + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil") + bool bHasRecoil; + + // If true then the recoil will be added as a physical force instead of logical blend + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil") + bool bApplyRecoilAsPhysicalForce; + + // Maximum recoil addition + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + FVector_NetQuantize100 MaxRecoilTranslation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + FVector_NetQuantize100 MaxRecoilRotation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + FVector_NetQuantize100 MaxRecoilScale; + + // Recoil decay rate, how fast it decays back to baseline + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + float DecayRate; + + // Recoil lerp rate, how long it takes to lerp to the target recoil amount (0.0f would be instant) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Recoil", meta = (editcondition = "bHasRecoil")) + float LerpRate; + + // Stores the current amount of recoil + FTransform BackEndRecoilStorage; + + // Stores the target amount of recoil + FTransform BackEndRecoilTarget; + + bool bHasActiveRecoil; + + // Adds a recoil instance to the gun tools, the option location is for if using the physical recoil mode + // Physical recoil is in world space and positional only, logical recoil is in relative space to the mesh itself and uses all + // of the transforms properties. + UFUNCTION(BlueprintCallable, Category = "Recoil") + void AddRecoilInstance(const FTransform & RecoilAddition, FVector Optional_Location = FVector::ZeroVector); + + UFUNCTION(BlueprintCallable, Category = "Recoil") + void ResetRecoil(); + + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + + // Applies the two hand modifier, broke this out into a function so that we can handle late updates + static void ApplyTwoHandModifier(FTransform & OriginalTransform) + { + + + } + + // Returns the smoothed value now + inline FVector GunTools_ApplySmoothingAndLerp(FBPActorGripInformation & Grip, FVector &frontLoc, FVector & frontLocOrig, float DeltaTime, bool bSkipHighQualitySimulations) + { + FVector SmoothedValue = frontLoc; + + if (Grip.SecondaryGripInfo.GripLerpState == EGripLerpState::StartLerp) // Lerp into the new grip to smooth the transition + { + if (!bSkipHighQualitySimulations && AdvSecondarySettings.SecondaryGripScaler < 1.0f) + { + SmoothedValue = AdvSecondarySettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + frontLoc = FMath::Lerp(frontLoc, SmoothedValue, AdvSecondarySettings.SecondaryGripScaler); + + } + //Default_ApplySmoothingAndLerp(Grip, frontLoc, frontLocOrig, DeltaTime); + } + else if (!bSkipHighQualitySimulations && AdvSecondarySettings.bUseAdvancedSecondarySettings && AdvSecondarySettings.bUseConstantGripScaler) // If there is a frame by frame lerp + { + SmoothedValue = AdvSecondarySettings.SecondarySmoothing.RunFilterSmoothing(frontLoc, DeltaTime); + frontLoc = FMath::Lerp(frontLoc, SmoothedValue, AdvSecondarySettings.SecondaryGripScaler); + } + + return SmoothedValue; + } +}; + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_InteractibleSettings.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_InteractibleSettings.h new file mode 100644 index 0000000..ca62911 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_InteractibleSettings.h @@ -0,0 +1,114 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRGripScriptBase.h" +#include "GS_InteractibleSettings.generated.h" + +class UGripMotionControllerComponent; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPGS_InteractionSettings +{ + GENERATED_BODY() +public: + + bool bHasValidBaseTransform; // So we don't have to equals the transform + FTransform BaseTransform; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + uint32 bLimitsInLocalSpace : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + uint32 bGetInitialPositionsOnBeginPlay : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + uint32 bLimitX : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + uint32 bLimitY : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + uint32 bLimitZ : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bLimitPitch : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bLimitYaw : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bLimitRoll : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + uint32 bIgnoreHandRotation : 1; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear", meta = (editcondition = "!bGetInitialPositionsOnBeginPlay")) + FVector_NetQuantize100 InitialLinearTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + FVector_NetQuantize100 MinLinearTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Linear") + FVector_NetQuantize100 MaxLinearTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular", meta = (editcondition = "!bGetInitialPositionsOnBeginPlay")) + FRotator InitialAngularTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + FRotator MinAngularTranslation; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings|Angular") + FRotator MaxAngularTranslation; + + FBPGS_InteractionSettings() : + bLimitsInLocalSpace(true), + bGetInitialPositionsOnBeginPlay(true), + bLimitX(false), + bLimitY(false), + bLimitZ(false), + bLimitPitch(false), + bLimitYaw(false), + bLimitRoll(false), + bIgnoreHandRotation(false), + InitialLinearTranslation(FVector::ZeroVector), + MinLinearTranslation(FVector::ZeroVector), + MaxLinearTranslation(FVector::ZeroVector), + InitialAngularTranslation(FRotator::ZeroRotator), + MinAngularTranslation(FRotator::ZeroRotator), + MaxAngularTranslation(FRotator::ZeroRotator) + { + BaseTransform = FTransform::Identity; + bHasValidBaseTransform = false; + } +}; + +// A Grip script that overrides the default grip behavior and adds custom clamping logic instead, +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_InteractibleSettings : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + UGS_InteractibleSettings(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "InteractionSettings") + FBPGS_InteractionSettings InteractionSettings; + + virtual void OnBeginPlay_Implementation(UObject * CallingOwner) override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false) override; + + // Flags the the interaction settings so that it will regenerate removing the hand rotation. + // Use this if you just changed the relative hand transform. + UFUNCTION(BlueprintCallable, Category = "InteractionSettings") + void RemoveHandRotation() + { + // Flag the base transform to be re-applied + InteractionSettings.bHasValidBaseTransform = false; + } + + void RemoveRelativeRotation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation); +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_LerpToHand.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_LerpToHand.h new file mode 100644 index 0000000..78d83c2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_LerpToHand.h @@ -0,0 +1,69 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRGripScriptBase.h" +#include "VRBPDatatypes.h" +#include "Curves/CurveFloat.h" +#include "GS_LerpToHand.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRLerpToHandFinishedSignature); + + +// A grip script that causes new grips to lerp to the hand (from their current position to where they are supposed to sit). +// It turns off when the lerp is complete. +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_LerpToHand : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + UGS_LerpToHand(const FObjectInitializer& ObjectInitializer); + + float CurrentLerpTime; + float LerpSpeed; + + // If the initial grip distance is closer than this value then the lerping will not be performed. + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float MinDistanceForLerp; + + // How many seconds the lerp should take + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float LerpDuration; + + // The minimum speed (in UU per second) that that the lerp should have across the initial grip distance + // Will speed the LerpSpeed up to try and maintain this initial speed if required + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float MinSpeedForLerp; + + // The maximum speed (in UU per second) that the lerp should have across the initial grip distance + // Will slow the LerpSpeed down to try and maintain this initial speed if required + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + float MaxSpeedForLerp; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpSettings") + EVRLerpInterpolationMode LerpInterpolationMode; + + UPROPERTY(BlueprintAssignable, Category = "LerpEvents") + FVRLerpToHandFinishedSignature OnLerpToHandBegin; + + UPROPERTY(BlueprintAssignable, Category = "LerpEvents") + FVRLerpToHandFinishedSignature OnLerpToHandFinished; + + // Whether to use a curve map to map the lerp to + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "LerpCurve") + bool bUseCurve; + + // The curve to follow when using a curve map, only uses from 0.0 - 1.0 of the curve timeline and maps it across the entire duration + UPROPERTY(Category = "LerpCurve", EditAnywhere, meta = (editcondition = "bUseCurve")) + FRuntimeFloatCurve OptionalCurveToFollow; + + FTransform OnGripTransform; + uint8 TargetGrip; + + //virtual void BeginPlay_Implementation() override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed) override; +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Melee.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Melee.h new file mode 100644 index 0000000..e47c659 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Melee.h @@ -0,0 +1,315 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Engine.h" +#include "VRGripScriptBase.h" +#include "GripScripts/GS_Default.h" +#include "GS_Melee.generated.h" + + +// The type of melee hit zone we are +UENUM(BlueprintType) +enum class EVRMeleeZoneType : uint8 +{ + // This zone is only valid for stabs + VRPMELLE_ZONETYPE_Stab UMETA(DisplayName = "Stab"), + + // This zone is only valid for hits + VRPMELLE_ZONETYPE_Hit UMETA(DisplayName = "Hit"), + + // This zone is valid for both stabs and hits + VRPMELLE_ZONETYPE_StabAndHit UMETA(DisplayName = "StabAndHit") + +}; + +// The type of COM selection to use +UENUM(BlueprintType) +enum class EVRMeleeComType : uint8 +{ + // Does not set COM + VRPMELEECOM_Normal UMETA(DisplayName = "Normal"), + + // Sets COM to between hands + VRPMELEECOM_BetweenHands UMETA(DisplayName = "BetweenHands"), + + // Uses the primary hand as com location + VRPMELEECOM_PrimaryHand UMETA(DisplayName = "PrimaryHand") +}; + +// The type of primary hand selection to use +UENUM(BlueprintType) +enum class EVRMeleePrimaryHandType : uint8 +{ + // Uses the rearmost hand as the primary hand + VRPHAND_Rear UMETA(DisplayName = "Rear"), + + // Uses the foremost hand as the primary hand + VRPHAND_Front UMETA(DisplayName = "Front"), + + // Uses the first slotted hand as the primary hand + // If neither are slotted then its first come first serve and both hannds load the secondary settings + VRPHAND_Slotted UMETA(DisplayName = "Slotted") +}; + +// A Lodge component data struct +USTRUCT(BlueprintType, Category = "Lodging") +struct VREXPANSIONPLUGIN_API FBPHitSurfaceProperties +{ + GENERATED_BODY() +public: + + // Does this surface type allow penetration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + bool bSurfaceAllowsPenetration; + + // Scaler to damage applied from hitting this surface with blunt damage + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + float BluntDamageScaler; + + // Scaler to damage applied from hitting this surface with sharp damage + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + float SharpDamageScaler; + + // Alters the stab velocity to let you make it harder or easier to stab this surface + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + float StabVelocityScaler; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface Property") + TEnumAsByte<EPhysicalSurface> SurfaceType; + + FBPHitSurfaceProperties() + { + // Default to true on this + bSurfaceAllowsPenetration = true; + BluntDamageScaler = 1.f; + SharpDamageScaler = 1.f; + StabVelocityScaler = 1.f; + SurfaceType = EPhysicalSurface::SurfaceType_Default; + } +}; + +// A Lodge component data struct +USTRUCT(BlueprintType, Category = "Lodging") +struct VREXPANSIONPLUGIN_API FBPLodgeComponentInfo +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + FName ComponentName; + + // Type of collision zone we are + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + EVRMeleeZoneType ZoneType; + + // If true than we will calculate hit impulse off of its total value and not just off of it axially aligned to the forward of this body + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + bool bIgnoreForwardVectorForHitImpulse; + + // For end users to provide a base damage per zone if they want + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + float DamageScaler; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + float PenetrationDepth; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "LodgeComponentInfo") + bool bAllowPenetrationInReverseAsWell; + + // This is the impulse velocity (along forward axis of component) required to throw an OnPenetrated event from a PenetrationNotifierComponent + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float PenetrationVelocity; + + // This is the impulse velocity required to throw an OnHit event from a PenetrationNotifierComponent (If a stab didn't take place) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float MinimumHitVelocity; + + // The acceptable range of the dot product of the forward vector and the impact normal to define a valid facing + // Subtracted from the 1.0f forward facing value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float AcceptableForwardProductRange; + + // The acceptable range of the dot product of the forward vector and the impact normal to define a valid facing + // Subtracted from the 1.0f forward facing value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + float AcceptableForwardProductRangeForHits; + + FBPLodgeComponentInfo() + { + ComponentName = NAME_None; + ZoneType = EVRMeleeZoneType::VRPMELLE_ZONETYPE_StabAndHit; + bIgnoreForwardVectorForHitImpulse = false; + DamageScaler = 0.f; + PenetrationDepth = 100.f; + bAllowPenetrationInReverseAsWell = false; + PenetrationVelocity = 8000.f; + MinimumHitVelocity = 1000.f; + AcceptableForwardProductRange = 0.1f; + AcceptableForwardProductRangeForHits = 0.1f; + TargetComponent = nullptr; + } + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "LodgeComponentInfo") + TObjectPtr<UPrimitiveComponent> TargetComponent; + + FORCEINLINE bool operator==(const FName& Other) const + { + return (ComponentName == Other); + } + +}; + + +// Event thrown when we the melee weapon becomes lodged +DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FVROnMeleeShouldLodgeSignature, FBPLodgeComponentInfo, LogComponent, AActor *, OtherActor, UPrimitiveComponent *, OtherComp, ECollisionChannel, OtherCompCollisionChannel, FBPHitSurfaceProperties, HitSurfaceProperties, FVector, NormalImpulse, const FHitResult&, Hit); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_SevenParams(FVROnMeleeOnHit, FBPLodgeComponentInfo, LogComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, ECollisionChannel, OtherCompCollisionChannel, FBPHitSurfaceProperties, HitSurfaceProperties, FVector, NormalImpulse, const FHitResult&, Hit); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FVROnMeleeInvalidHitSignature, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, FVector, NormalImpulse, const FHitResult&, Hit); + +/** +* A Melee grip script that hands multi hand interactions and penetration notifications* +* The per surface damage and penetration options have been moved to the project settings unless the per script override is set +*/ +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_Melee : public UGS_Default +{ + GENERATED_BODY() +public: + + UGS_Melee(const FObjectInitializer& ObjectInitializer); + + UFUNCTION() + void OnLodgeHitCallback(AActor* SelfActor, AActor* OtherActor, FVector NormalImpulse, const FHitResult& Hit); + + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void SetIsLodged(bool IsLodged, UPrimitiveComponent * LodgeComponent) + { + bIsLodged = IsLodged; + LodgedComponent = LodgeComponent; + } + + bool bIsLodged; + TWeakObjectPtr<UPrimitiveComponent> LodgedComponent; + + //virtual void Tick(float DeltaTime) override; + + // Thrown if we should lodge into a hit object + UPROPERTY(BlueprintAssignable, Category = "Melee|Lodging") + FVROnMeleeShouldLodgeSignature OnShouldLodgeInObject; + + // Thrown if we hit something we can damage + UPROPERTY(BlueprintAssignable, Category = "Melee|Hit") + FVROnMeleeOnHit OnMeleeHit; + + // Fired when a hit is invalid (hit something that isn't flagged for damage or stabbing or was below the damage or stab threshold) + UPROPERTY(BlueprintAssignable, Category = "Melee|Hit") + FVROnMeleeInvalidHitSignature OnMeleeInvalidHit; + + // Always tick for penetration + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Melee|Lodging") + bool bAlwaysTickPenetration; + + // Only penetrate with two hands on the weapon + // Mostly for very large weapons + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Melee|Lodging") + bool bOnlyPenetrateWithTwoHands; + + // A list of surface types that allow penetration and their properties + // If empty then the script will use the global settings, if filled with anything then it will override the global settings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Melee|Lodging") + TArray<FBPHitSurfaceProperties> OverrideMeleeSurfaceSettings; + +// FVector RollingVelocityAverage; + //FVector RollingAngVelocityAverage; + + // The name of the component that is used to orient the weapon along its primary axis + // If it does not exist then the weapon is assumed to be X+ facing. + // Also used to perform some calculations, make sure it is parented to the gripped object (root component for actors). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FName WeaponRootOrientationComponent; + FTransform OrientationComponentRelativeFacing; + + // UpdateHand location on the shaft in the X axis + // If primary hand is false then it will do the secondary hand + // World location is of the pivot generally, I have it passing in so people can snap + // LocDifference returns the relative distance of the change in position (or zero if there was none). + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void UpdateHandPosition(FBPGripPair HandPair, FVector HandWorldPosition, FVector & LocDifference); + + // UpdateHand location and rotation on the shaft in the X axis + // If primary hand is false then it will do the secondary hand + // World location is of the pivot generally, I have it passing in so people can snap + // LocDifference returns the relative distance of the change in position (or zero if there was none). + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void UpdateHandPositionAndRotation(FBPGripPair HandPair, FTransform HandWorldTransform, FVector& LocDifference, float& RotDifference, bool bUpdateLocation = true, bool bUpdateRotation = true); + + + // This is a built list of components that act as penetration notifiers, they will have their OnHit bound too and we will handle penetration logic + // off of it. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + TArray<FBPLodgeComponentInfo> PenetrationNotifierComponents; + + bool bCheckLodge; + bool bIsHeld; + + FVector LastRelativePos; + FVector RelativeBetweenGripsCenterPos; + + // When true, will auto set the primary and secondary hands by the WeaponRootOrientationComponents X Axis distance. + // Smallest value along the X Axis will be considered the primary hand. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + bool bAutoSetPrimaryAndSecondaryHands; + + // If we couldn't decide on a true valid primary hand then this will be false and we will load secondary settings for both + bool bHasValidPrimaryHand; + + UFUNCTION(BlueprintCallable, Category = "Weapon Settings") + void SetPrimaryAndSecondaryHands(FBPGripPair & PrimaryGrip, FBPGripPair & SecondaryGrip); + + // Which method of primary hand to select + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + EVRMeleePrimaryHandType PrimaryHandSelectionType; + + UPROPERTY(BlueprintReadOnly, Category = "Weapon Settings") + FBPGripPair PrimaryHand; + + UPROPERTY(BlueprintReadOnly, Category = "Weapon Settings") + FBPGripPair SecondaryHand; + + // To select the type of com setting to use + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + EVRMeleeComType COMType; + + FTransform ObjectRelativeGripCenter; + + void SetComBetweenHands(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo); + + + // Grip settings to use on the primary hand when multiple grips are active + // Falls back to the standard grip settings when only one grip is active + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FBPAdvancedPhysicsHandleSettings PrimaryHandPhysicsSettings; + + // Grip settings to use on the secondary hand when multiple grips are active + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Weapon Settings") + FBPAdvancedPhysicsHandleSettings SecondaryHandPhysicsSettings; + + + void UpdateDualHandInfo(); + + virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) override; + virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) override; + virtual void OnBeginPlay_Implementation(UObject* CallingOwner) override; + virtual void OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason) override; + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* Controller, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + virtual bool Wants_DenyTeleport_Implementation(UGripMotionControllerComponent* Controller) override; + + //virtual void BeginPlay_Implementation() override; + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * GrippingController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override; + + +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Physics.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Physics.h new file mode 100644 index 0000000..c300eee --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/GS_Physics.h @@ -0,0 +1,38 @@ +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRGripScriptBase.h" +#include "GameFramework/WorldSettings.h" +#include "GripScripts/GS_Default.h" +#include "GS_Physics.generated.h" + +/** +* A pure physics multi hand interaction grip script, expects that bAllowMultiGrips is set on the parent object* +*/ +UCLASS(NotBlueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = TickSettings) +class VREXPANSIONPLUGIN_API UGS_Physics : public UGS_Default +{ + GENERATED_BODY() +public: + + UGS_Physics(const FObjectInitializer& ObjectInitializer); + + // Grip settings to use when a single hand is gripping, overrides interface defaults + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics Settings") + FBPAdvancedPhysicsHandleSettings SingleHandPhysicsSettings; + + // Grip settings to use when multiple hands are gripping + // Overrides interface defaults + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics Settings") + FBPAdvancedPhysicsHandleSettings MultiHandPhysicsSettings; + + + void UpdateDualHandInfo(UGripMotionControllerComponent* GrippingController = nullptr, bool bRecreate = true); + + virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation* HandleInfo) override; + //virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation* HandleInfo, FTransform& KinPose) override; + + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/VRGripScriptBase.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/VRGripScriptBase.h new file mode 100644 index 0000000..36989ab --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/GripScripts/VRGripScriptBase.h @@ -0,0 +1,273 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "UObject/Object.h" +#include "VRBPDatatypes.h" +#include "Tickable.h" + +#include "VRGripScriptBase.generated.h" + +class UGripMotionControllerComponent; +class UVRGripInterface; +class UPrimitiveComponent; +class AActor; + + +UENUM(Blueprintable) +enum class EGSTransformOverrideType : uint8 +{ + /** Does not alter the world transform */ + None, + + /* Overrides the world transform */ + OverridesWorldTransform, + + /* Modifies the world transform*/ + ModifiesWorldTransform +}; + +UCLASS(NotBlueprintable, BlueprintType, EditInlineNew, DefaultToInstanced, Abstract, ClassGroup = (VRExpansionPlugin), HideCategories = DefaultSettings) +class VREXPANSIONPLUGIN_API UVRGripScriptBase : public UObject, public FTickableGameObject +{ + GENERATED_BODY() +public: + + UVRGripScriptBase(const FObjectInitializer& ObjectInitializer); + + // Gets the first grip script of the specified type in this object, do NOT call this on tick, save out and store the reference given + UFUNCTION(BlueprintCallable, Category = "VRGripScript|Functions", meta = (WorldContext = "WorldContextObject", bIgnoreSelf = "true", DisplayName = "GetGripScriptByClass", ExpandEnumAsExecs = "Result")) + static UVRGripScriptBase* GetGripScriptByClass(UObject* WorldContextObject, TSubclassOf<UVRGripScriptBase> GripScriptClass, EBPVRResultSwitch& Result); + + bool IsSupportedForNetworking() const override + { + return true; + //return bRequiresReplicationSupport || Super::IsSupportedForNetworking(); + } + // I don't need to do this, there should be no dynamic script spawning and they are all name stable by default + + // Returns if the script is currently active and should be used + bool IsScriptActive(); + + // Is currently active helper variable, returned from IsScriptActive() + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bIsActive; + + // Returns if the script is going to modify the world transform of the grip + EGSTransformOverrideType GetWorldTransformOverrideType(); + + // Whether this script overrides or modifies the world transform + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + EGSTransformOverrideType WorldTransformOverrideType; + + // Returns if the script wants auto drop to be ignored + FORCEINLINE bool Wants_DenyAutoDrop() + { + return bDenyAutoDrop; + } + + // Returns if we want to deny auto dropping + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bDenyAutoDrop; + + // Returns if the script wants to force a drop + FORCEINLINE bool Wants_ToForceDrop() + { + return bForceDrop; + } + + // Returns if we want to force a drop + UPROPERTY(BlueprintReadWrite, Category = "GSSettings") + bool bForceDrop; + + // Flags the grip to be dropped as soon as possible + UFUNCTION(BlueprintCallable, Category = "VRGripScript") + void ForceGripToDrop() + { + bForceDrop = true; + } + + // Returns if the script wants to deny late updates + FORCEINLINE bool Wants_DenyLateUpdates() + { + return bDenyLateUpdates; + } + + // Returns if we want to inject changes prior to the physics handle + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bDenyLateUpdates; + + // Returns if the script wants auto drop to be ignored + FORCEINLINE bool InjectPrePhysicsHandle() + { + return bInjectPrePhysicsHandle; + } + + // Returns if we want to inject changes prior to the physics handle + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bInjectPrePhysicsHandle; + + virtual void HandlePrePhysicsHandle(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation &GripInfo, FBPActorPhysicsHandleInformation * HandleInfo, FTransform & KinPose); + + // Returns if we want to inject changes after the physics handle + FORCEINLINE bool InjectPostPhysicsHandle() + { + return bInjectPostPhysicsHandle; + } + + // Returns if we want to inject changes after the physics handle + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "GSSettings") + bool bInjectPostPhysicsHandle; + + virtual void HandlePostPhysicsHandle(UGripMotionControllerComponent* GrippingController, FBPActorPhysicsHandleInformation * HandleInfo); + + // Returns if the script is currently active and should be used + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripScript") + bool Wants_DenyTeleport(UGripMotionControllerComponent * Controller); + virtual bool Wants_DenyTeleport_Implementation(UGripMotionControllerComponent* Controller); + + virtual void GetLifetimeReplicatedProps(TArray< class FLifetimeProperty > & OutLifetimeProps) const override; + + // doesn't currently compile in editor builds, not sure why the linker is screwing up there but works elsewhere + //virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker); + virtual bool CallRemoteFunction(UFunction * Function, void * Parms, FOutParmRec * OutParms, FFrame * Stack) override; + virtual int32 GetFunctionCallspace(UFunction * Function, FFrame * Stack) override; + + // FTickableGameObject functions + + + // If true then this scrip can tick when bAllowticking is true + UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category = "TickSettings") + bool bCanEverTick; + + // If true and we bCanEverTick, then will fire off the tick function + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "TickSettings") + bool bAllowTicking; + + // Set whether the grip script can tick or not + UFUNCTION(BlueprintCallable, Category = "TickSettings") + void SetTickEnabled(bool bTickEnabled); + + /** + * Function called every frame on this GripScript. Override this function to implement custom logic to be executed every frame. + * Only executes if bCanEverTick is true and bAllowTicking is true + * + * @param DeltaTime - The time since the last tick. + */ + virtual void Tick(float DeltaTime) override; + virtual bool IsTickable() const override; + virtual UWorld* GetTickableGameObjectWorld() const override; + virtual bool IsTickableInEditor() const; + virtual bool IsTickableWhenPaused() const override; + virtual ETickableTickType GetTickableTickType() const; + virtual TStatId GetStatId() const override; + virtual UWorld* GetWorld() const override; + + // End tickable object information + + + // Returns the expected grip transform (relative * controller + addition) + UFUNCTION(BlueprintPure, Category = "VRGripScript") + FTransform GetGripTransform(const FBPActorGripInformation &Grip, const FTransform & ParentTransform); + + // Returns the current world transform of the owning object (or root comp of if it is an actor) + UFUNCTION(BlueprintPure, Category = "VRGripScript") + FTransform GetParentTransform(bool bGetWorldTransform = true, FName BoneName = NAME_None); + + // Returns the scene component of the parent, either being the parent itself or the root comp of it. + // Nullptr if there is no valid scene component + UFUNCTION(BlueprintCallable, Category = "VRGripScript") + USceneComponent* GetParentSceneComp(); + + // Returns the root body instance of the parent + FBodyInstance * GetParentBodyInstance(FName OptionalBoneName = NAME_None); + + // Returns the parent component or actor to this + UFUNCTION(BlueprintPure, Category = "VRGripScript") + UObject * GetParent(); + + // Returns the owning actor + UFUNCTION(BlueprintPure, Category = "VRGripScript") + AActor * GetOwner(); + + // If the owning actor has authority on this connection + UFUNCTION(BlueprintPure, Category = "VRGripScript") + bool HasAuthority(); + + // If the owning actor is on the server on this connection + UFUNCTION(BlueprintPure, Category = "VRGripScript") + bool IsServer(); + + void EndPlay(const EEndPlayReason::Type EndPlayReason); + + // Not all scripts will require this function, specific ones that use things like Lever logic however will. Best to call it. + // Grippables will automatically call this, however if you manually spawn a grip script during play or you make your own + // Interfaced grip object and give it grippables, YOU will be required to call this event on them. + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnEndPlay(const EEndPlayReason::Type EndPlayReason); + virtual void OnEndPlay_Implementation(const EEndPlayReason::Type EndPlayReason); + + void BeginPlay(UObject * CallingOwner); + bool bAlreadyNotifiedPlay = false; + virtual void PostInitProperties() override; + + // Not all scripts will require this function, specific ones that use things like Lever logic however will. Best to call it. + // Grippables will automatically call this, however if you manually spawn a grip script during play or you make your own + // Interfaced grip object and give it grippables, YOU will be required to call this event on them. + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnBeginPlay(UObject * CallingOwner); + virtual void OnBeginPlay_Implementation(UObject * CallingOwner); + + // Overrides or Modifies the world transform with this grip script + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + bool GetWorldTransform(UGripMotionControllerComponent * GrippingController, float DeltaTime, UPARAM(ref) FTransform & WorldTransform, const FTransform &ParentTransform, UPARAM(ref) FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport); + virtual bool GetWorldTransform_Implementation(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport); + + // Event triggered on the interfaced object when gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + virtual void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripScript") + void OnGripRelease(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + + // Event triggered on the interfaced object when secondary gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGrip(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation); + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when secondary grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGripRelease(UGripMotionControllerComponent * Controller, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation); + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * Controller, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + + + virtual bool CallCorrect_GetWorldTransform(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) + { + return GetWorldTransform_Implementation(OwningController, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + } +}; + + +UCLASS(Blueprintable, Abstract, ClassGroup = (VRExpansionPlugin), ShowCategories = DefaultSettings) +class VREXPANSIONPLUGIN_API UVRGripScriptBaseBP : public UVRGripScriptBase +{ + GENERATED_BODY() +public: + + virtual bool CallCorrect_GetWorldTransform(UGripMotionControllerComponent * OwningController, float DeltaTime, FTransform & WorldTransform, const FTransform &ParentTransform, FBPActorGripInformation &Grip, AActor * actor, UPrimitiveComponent * root, bool bRootHasInterface, bool bActorHasInterface, bool bIsForTeleport) override + { + return GetWorldTransform(OwningController, DeltaTime, WorldTransform, ParentTransform, Grip, actor, root, bRootHasInterface, bActorHasInterface, bIsForTeleport); + } + + virtual void Tick(float DeltaTime) override; + + /** Event called every frame if ticking is enabled */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Tick")) + void ReceiveTick(float DeltaSeconds); +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableActor.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableActor.h new file mode 100644 index 0000000..68fbcce --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableActor.h @@ -0,0 +1,272 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +//#include "Engine/Engine.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "Grippables/GrippableDataTypes.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GrippableActor.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +/** +* +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AGrippableActor : public AActor, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AGrippableActor(const FObjectInitializer& ObjectInitializer); + + ~AGrippableActor(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_AttachmentReplication) + FRepAttachmentWithWeld AttachmentWeldReplication; + + virtual void GatherCurrentMovement() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*>& ObjList) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Client Auth Throwing Data and functions + // ------------------------------------------------ + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + FVRClientAuthReplicationData ClientAuthReplicationData; + + // Add this to client side physics replication (until coming to rest or timeout period is hit) + UFUNCTION(BlueprintCallable, Category = "Networking") + bool AddToClientReplicationBucket(); + + // Remove this from client side physics replication + UFUNCTION(BlueprintCallable, Category = "Networking") + bool RemoveFromClientReplicationBucket(); + + UFUNCTION() + bool PollReplicationEvent(); + + UFUNCTION(Category = "Networking") + void CeaseReplicationBlocking(); + + // Notify the server that we are no longer trying to run the throwing auth + UFUNCTION(Reliable, Server, WithValidation, Category = "Networking") + void Server_EndClientAuthReplication(); + + // Notify the server about a new movement rep + UFUNCTION(UnReliable, Server, WithValidation, Category = "Networking") + void Server_GetClientAuthReplication(const FRepMovementVR & newMovement); + + // Returns if this object is currently client auth throwing + UFUNCTION(BlueprintPure, Category = "Networking") + FORCEINLINE bool IsCurrentlyClientAuthThrowing() + { + return ClientAuthReplicationData.bIsCurrentlyClientAuth; + } + + // End client auth throwing data and functions // + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // Skips the attachment replication if we are locally owned and our grip settings say that we are a client authed grip. + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bAllowIgnoringAttachOnOwner; + + // Should we skip attachment replication (vr settings say we are a client auth grip and our owner is locally controlled) + inline bool ShouldWeSkipAttachmentReplication(bool bConsiderHeld = true) const + { + if ((bConsiderHeld && !VRGripInterfaceSettings.bWasHeld) || GetNetMode() < ENetMode::NM_Client) + return false; + + if (VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive || + VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return HasLocalNetOwner(); + } + else + return false; + } + + // Handle fixing some bugs and issues with ReplicateMovement being off + virtual void OnRep_AttachmentReplication() override; + virtual void OnRep_ReplicateMovement() override; + virtual void OnRep_ReplicatedMovement() override; + virtual void PostNetReceivePhysicState() override; + + // Debug printing of when the object is replication destroyed + /*virtual void OnSubobjectDestroyFromReplication(UObject *Subobject) override + { + Super::OnSubobjectDestroyFromReplication(Subobject); + + GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("Killed Object On Actor: x: %s"), *Subobject->GetName())); + }*/ + + // This isn't called very many places but it does come up + virtual void MarkComponentsAsPendingKill() override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + // On Destroy clean up our objects + virtual void BeginDestroy() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGripSettingsAndGameplayTags; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableBoxComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableBoxComponent.h new file mode 100644 index 0000000..f1a94e5 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableBoxComponent.h @@ -0,0 +1,202 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "Components/BoxComponent.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "GrippableBoxComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableBoxComponent : public UBoxComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableBoxComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableBoxComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCapsuleComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCapsuleComponent.h new file mode 100644 index 0000000..573e324 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCapsuleComponent.h @@ -0,0 +1,199 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/CapsuleComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableCapsuleComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableCapsuleComponent : public UCapsuleComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableCapsuleComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableCapsuleComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCharacter.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCharacter.h new file mode 100644 index 0000000..68aaf3b --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableCharacter.h @@ -0,0 +1,28 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "GrippableCharacter.generated.h" + +class UGrippableSkeletalMeshComponent; + +UCLASS() +class VREXPANSIONPLUGIN_API AGrippableCharacter : public ACharacter +{ + GENERATED_BODY() + +public: + AGrippableCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // A reference to the grippable character that can be used instead of casting the root, BP doesn't like the class override. + UPROPERTY(Category = GrippableCharacter, VisibleAnywhere, Transient, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<UGrippableSkeletalMeshComponent> GrippableMeshReference; + + // A Custom bone to use on the character mesh as the originator for the perception systems sight sense + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI") + FName ViewOriginationSocket; + + virtual void GetActorEyesViewPoint(FVector& Location, FRotator& Rotation) const override; + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableDataTypes.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableDataTypes.h new file mode 100644 index 0000000..d6d8ab0 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableDataTypes.h @@ -0,0 +1,46 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +//#include "Engine/EngineTypes.h" +#include "GrippableDataTypes.generated.h" + +// A version of the attachment structure that include welding data +USTRUCT() +struct FRepAttachmentWithWeld : public FRepAttachment +{ +public: + GENERATED_BODY() + + // Add in the is welded property + UPROPERTY() + bool bIsWelded; + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + // Our additional weld bit is here + Ar.SerializeBits(&bIsWelded, 1); + Ar << AttachParent; + LocationOffset.NetSerialize(Ar, Map, bOutSuccess); + RelativeScale3D.NetSerialize(Ar, Map, bOutSuccess); + RotationOffset.SerializeCompressedShort(Ar); + Ar << AttachSocket; + Ar << AttachComponent; + return true; + } + + FRepAttachmentWithWeld() + { + bIsWelded = false; + } +}; + +template<> +struct TStructOpsTypeTraits< FRepAttachmentWithWeld > : public TStructOpsTypeTraitsBase2<FRepAttachmentWithWeld> +{ + enum + { + WithNetSerializer = true//, + //WithNetSharedSerialization = true, + }; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippablePhysicsReplication.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippablePhysicsReplication.h new file mode 100644 index 0000000..890fae9 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippablePhysicsReplication.h @@ -0,0 +1,122 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Physics/PhysicsInterfaceUtils.h" +#include "PhysicsReplication.h" + + + +#include "GrippablePhysicsReplication.generated.h" +//#include "GrippablePhysicsReplication.generated.h" + + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPhysicsReplicationDelegate, void, Return); + +/*static TAutoConsoleVariable<int32> CVarEnableCustomVRPhysicsReplication( + TEXT("vr.VRExpansion.EnableCustomVRPhysicsReplication"), + 0, + TEXT("Enable valves input controller that overrides legacy input.\n") + TEXT(" 0: use the engines default input mapping (default), will also be default if vr.SteamVR.EnableVRInput is enabled\n") + TEXT(" 1: use the valve input controller. You will have to define input bindings for the controllers you want to support."), + ECVF_ReadOnly);*/ + +//#if PHYSICS_INTERFACE_PHYSX +struct FAsyncPhysicsRepCallbackDataVR; +class FPhysicsReplicationAsyncCallbackVR; + +class FPhysicsReplicationVR : public FPhysicsReplication +{ +public: + + FPhysScene* PhysSceneVR; + + FPhysicsReplicationVR(FPhysScene* PhysScene); + ~FPhysicsReplicationVR(); + static bool IsInitialized(); + + virtual void OnTick(float DeltaSeconds, TMap<TWeakObjectPtr<UPrimitiveComponent>, FReplicatedPhysicsTarget>& ComponentsToTargets) override; + virtual bool ApplyRigidBodyState(float DeltaSeconds, FBodyInstance* BI, FReplicatedPhysicsTarget& PhysicsTarget, const FRigidBodyErrorCorrection& ErrorCorrection, const float PingSecondsOneWay) override; +#if WITH_CHAOS + + static void ApplyAsyncDesiredStateVR(float DeltaSeconds, const FAsyncPhysicsRepCallbackDataVR* Input); + + FPhysicsReplicationAsyncCallbackVR* AsyncCallbackServer; + + void PrepareAsyncData_ExternalVR(const FRigidBodyErrorCorrection& ErrorCorrection); //prepare async data for writing. Call on external thread (i.e. game thread) + FAsyncPhysicsRepCallbackDataVR* CurAsyncDataVR; //async data being written into before we push into callback + friend FPhysicsReplicationAsyncCallback; +#endif +}; + +class IPhysicsReplicationFactoryVR : public IPhysicsReplicationFactory +{ +public: + + virtual FPhysicsReplication* Create(FPhysScene* OwningPhysScene) + { + return new FPhysicsReplicationVR(OwningPhysScene); + } + + virtual void Destroy(FPhysicsReplication* PhysicsReplication) + { + if (PhysicsReplication) + delete PhysicsReplication; + } +}; + +//#endif + +USTRUCT() +struct VREXPANSIONPLUGIN_API FRepMovementVR : public FRepMovement +{ + GENERATED_USTRUCT_BODY() +public: + + FRepMovementVR(); + FRepMovementVR(FRepMovement& other); + void CopyTo(FRepMovement& other) const; + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + bool GatherActorsMovement(AActor* OwningActor); +}; + +template<> +struct TStructOpsTypeTraits<FRepMovementVR> : public TStructOpsTypeTraitsBase2<FRepMovementVR> +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +USTRUCT(BlueprintType) +struct VREXPANSIONPLUGIN_API FVRClientAuthReplicationData +{ + GENERATED_BODY() +public: + + // If True and we are using a client auth grip type then we will replicate our throws on release + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRReplication") + bool bUseClientAuthThrowing; + + // Rate that we will be sending throwing events to the server, not replicated, only serialized + UPROPERTY(EditAnywhere, NotReplicated, BlueprintReadOnly, Category = "VRReplication", meta = (ClampMin = "0", UIMin = "0", ClampMax = "100", UIMax = "100")) + int32 UpdateRate; + + FTimerHandle ResetReplicationHandle; + FTransform LastActorTransform; + float TimeAtInitialThrow; + bool bIsCurrentlyClientAuth; + + FVRClientAuthReplicationData() : + bUseClientAuthThrowing(false), + UpdateRate(30), + LastActorTransform(FTransform::Identity), + TimeAtInitialThrow(0.0f), + bIsCurrentlyClientAuth(false) + { + + } +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshActor.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshActor.h new file mode 100644 index 0000000..c2eeaa2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshActor.h @@ -0,0 +1,296 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "Animation/SkeletalMeshActor.h" +#include "Components/SkeletalMeshComponent.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "Grippables/GrippableDataTypes.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GrippableSkeletalMeshActor.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +/** +* A component specifically for being able to turn off movement replication in the component at will +* Has the upside of also being a blueprintable base since UE4 doesn't allow that with std ones +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UOptionalRepSkeletalMeshComponent : public USkeletalMeshComponent +{ + GENERATED_BODY() + +public: + UOptionalRepSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer); + +public: + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; + +}; + +/** +* +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AGrippableSkeletalMeshActor : public ASkeletalMeshActor, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer); + + + ~AGrippableSkeletalMeshActor(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_AttachmentReplication) + FRepAttachmentWithWeld AttachmentWeldReplication; + + virtual void GatherCurrentMovement() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch* Bunch, FReplicationFlags* RepFlags) override; + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*>& ObjList) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Client Auth Throwing Data and functions + // ------------------------------------------------ + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + FVRClientAuthReplicationData ClientAuthReplicationData; + + // Add this to client side physics replication (until coming to rest or timeout period is hit) + UFUNCTION(BlueprintCallable, Category = "Networking") + bool AddToClientReplicationBucket(); + + // Remove this from client side physics replication + UFUNCTION(BlueprintCallable, Category = "Networking") + bool RemoveFromClientReplicationBucket(); + + UFUNCTION() + bool PollReplicationEvent(); + + UFUNCTION(Category = "Networking") + void CeaseReplicationBlocking(); + + // Notify the server that we are no longer trying to run the throwing auth + UFUNCTION(Reliable, Server, WithValidation, Category = "Networking") + void Server_EndClientAuthReplication(); + + // Notify the server about a new movement rep + UFUNCTION(UnReliable, Server, WithValidation, Category = "Networking") + void Server_GetClientAuthReplication(const FRepMovementVR& newMovement); + + // Returns if this object is currently client auth throwing + UFUNCTION(BlueprintPure, Category = "Networking") + FORCEINLINE bool IsCurrentlyClientAuthThrowing() + { + return ClientAuthReplicationData.bIsCurrentlyClientAuth; + } + + // End client auth throwing data and functions // + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; + + // Skips the attachment replication if we are locally owned and our grip settings say that we are a client authed grip. + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bAllowIgnoringAttachOnOwner; + + // Should we skip attachment replication (vr settings say we are a client auth grip and our owner is locally controlled) + inline bool ShouldWeSkipAttachmentReplication(bool bConsiderHeld = true) const + { + if ((bConsiderHeld && !VRGripInterfaceSettings.bWasHeld) || GetNetMode() < ENetMode::NM_Client) + return false; + + if (VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive || + VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return HasLocalNetOwner(); + } + else + return false; + } + + // Fix bugs with replication and bReplicateMovement + virtual void OnRep_AttachmentReplication() override; + virtual void OnRep_ReplicateMovement() override; + virtual void OnRep_ReplicatedMovement() override; + virtual void PostNetReceivePhysicState() override; + + // Debug printing of when the object is replication destroyed + /*virtual void OnSubobjectDestroyFromReplication(UObject *Subobject) override + { + Super::OnSubobjectDestroyFromReplication(Subobject); + + GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("Killed Object On Actor: x: %s"), *Subobject->GetName())); + }*/ + + // This isn't called very many places but it does come up + virtual void MarkComponentsAsPendingKill() override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + // On Destroy clean up our objects + virtual void BeginDestroy() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGripSettingsAndGameplayTags; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshComponent.h new file mode 100644 index 0000000..58a4c7f --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSkeletalMeshComponent.h @@ -0,0 +1,201 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/SkeletalMeshComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableSkeletalMeshComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableSkeletalMeshComponent : public USkeletalMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableSkeletalMeshComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSphereComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSphereComponent.h new file mode 100644 index 0000000..81fd861 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableSphereComponent.h @@ -0,0 +1,201 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/SphereComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableSphereComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableSphereComponent : public USphereComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableSphereComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableSphereComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshActor.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshActor.h new file mode 100644 index 0000000..e5d6a03 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshActor.h @@ -0,0 +1,298 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "Engine/StaticMeshActor.h" +#include "Components/StaticMeshComponent.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Engine/ActorChannel.h" +#include "Grippables/GrippableDataTypes.h" +#include "Grippables/GrippablePhysicsReplication.h" +#include "GrippableStaticMeshActor.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + + +/** +* A component specifically for being able to turn off movement replication in the component at will +* Has the upside of also being a blueprintable base since UE4 doesn't allow that with std ones +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent,ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UOptionalRepStaticMeshComponent : public UStaticMeshComponent +{ + GENERATED_BODY() + +public: + UOptionalRepStaticMeshComponent(const FObjectInitializer& ObjectInitializer); + + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; +}; + + +/** +* +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AGrippableStaticMeshActor : public AStaticMeshActor, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + AGrippableStaticMeshActor(const FObjectInitializer& ObjectInitializer); + + ~AGrippableStaticMeshActor(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_AttachmentReplication) + FRepAttachmentWithWeld AttachmentWeldReplication; + + virtual void GatherCurrentMovement() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*>& ObjList) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + + // ------------------------------------------------ + // Client Auth Throwing Data and functions + // ------------------------------------------------ + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + FVRClientAuthReplicationData ClientAuthReplicationData; + + // Add this to client side physics replication (until coming to rest or timeout period is hit) + UFUNCTION(BlueprintCallable, Category = "Networking") + bool AddToClientReplicationBucket(); + + // Remove this from client side physics replication + UFUNCTION(BlueprintCallable, Category = "Networking") + bool RemoveFromClientReplicationBucket(); + + // From IVRReplicationInterface + UFUNCTION() + bool PollReplicationEvent(); + + UFUNCTION(Category = "Networking") + void CeaseReplicationBlocking(); + + // Notify the server that we are no longer trying to run the throwing auth + UFUNCTION(Reliable, Server, WithValidation, Category = "Networking") + void Server_EndClientAuthReplication(); + + // Notify the server about a new movement rep + UFUNCTION(UnReliable, Server, WithValidation, Category = "Networking") + void Server_GetClientAuthReplication(const FRepMovementVR & newMovement); + + // Returns if this object is currently client auth throwing + UFUNCTION(BlueprintPure, Category = "Networking") + FORCEINLINE bool IsCurrentlyClientAuthThrowing() + { + return ClientAuthReplicationData.bIsCurrentlyClientAuth; + } + + // End client auth throwing data and functions // + + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // Skips the attachment replication if we are locally owned and our grip settings say that we are a client authed grip. + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bAllowIgnoringAttachOnOwner; + + // Should we skip attachment replication (vr settings say we are a client auth grip and our owner is locally controlled) + inline bool ShouldWeSkipAttachmentReplication(bool bConsiderHeld = true) const + { + if((bConsiderHeld && !VRGripInterfaceSettings.bWasHeld) || GetNetMode() < ENetMode::NM_Client) + return false; + + if (VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive || + VRGripInterfaceSettings.MovementReplicationType == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep) + { + return HasLocalNetOwner(); + } + else + return false; + } + + // Fix bugs with replication and bReplicateMovement + virtual void OnRep_AttachmentReplication() override; + virtual void OnRep_ReplicateMovement() override; + virtual void OnRep_ReplicatedMovement() override; + virtual void PostNetReceivePhysicState() override; + + // Debug printing of when the object is replication destroyed + /*virtual void OnSubobjectDestroyFromReplication(UObject *Subobject) override + { + Super::OnSubobjectDestroyFromReplication(Subobject); + + GEngine->AddOnScreenDebugMessage(-1, 15.f, FColor::Red, FString::Printf(TEXT("Killed Object On Actor: x: %s"), *Subobject->GetName())); + }*/ + + // This isn't called very many places but it does come up + virtual void MarkComponentsAsPendingKill() override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + // On Destroy clean up our objects + virtual void BeginDestroy() override; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGripSettingsAndGameplayTags; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshComponent.h new file mode 100644 index 0000000..a16ea6d --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/GrippableStaticMeshComponent.h @@ -0,0 +1,200 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRGripInterface.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/ActorChannel.h" +#include "GrippableStaticMeshComponent.generated.h" + +class UVRGripScriptBase; +class UGripMotionControllerComponent; + +/** +* +*/ + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent,ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UGrippableStaticMeshComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UGrippableStaticMeshComponent(const FObjectInitializer& ObjectInitializer); + + + ~UGrippableStaticMeshComponent(); + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadOnly, Instanced, Category = "VRGripInterface") + TArray<TObjectPtr<UVRGripScriptBase>> GripLogicScripts; + + // If true then the grip script array will be considered for replication, if false then it will not + // This is an optimization for when you have a lot of grip scripts in use, you can toggle this off in cases + // where the object will never have a replicating script + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bReplicateGripScripts; + + bool ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags) override; + + // Sets the Deny Gripping variable on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetDenyGripping(bool bDenyGripping); + + // Sets the grip priority on the FBPInterfaceSettings struct + UFUNCTION(BlueprintCallable, Category = "VRGripInterface") + void SetGripPriority(int NewGripPriority); + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Called when an object we hold is secondary gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripAdded; + + // Called when an object we hold is secondary dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnSecondaryGripRemoved; + + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Called right before being marked for destruction due to network replication */ + // Clean up our objects so that they aren't sitting around for GC + virtual void PreDestroyFromReplication() override; + + virtual void GetSubobjectsWithStableNamesForNetworking(TArray<UObject*> &ObjList) override; + + // This one is for components to clean up + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGripSettingsAndGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + bool bOriginalReplicatesMovement; + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + FBPInterfaceProperties VRGripInterfaceSettings; + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + virtual bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + virtual EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + virtual bool SimulateOnDrop_Implementation() override; + + // Grip type to use + virtual EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + virtual ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + virtual EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + virtual EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + virtual void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + virtual FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + virtual float GripBreakDistance_Implementation() override; + + // Get closest primary slot in range + virtual void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + virtual bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + virtual void IsHeld_Implementation(TArray<FBPGripPair>& HoldingControllers, bool& bIsHeld) override; + + // Sets is held, used by the plugin + virtual void SetHeld_Implementation(UGripMotionControllerComponent* HoldingController, uint8 GripID, bool bIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Returns if the object wants to be socketed + virtual bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + virtual bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + virtual void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + virtual void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + virtual void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + virtual void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + virtual void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + virtual void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + virtual void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + virtual void OnUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndUsed_Implementation() override; + + // Call to use an object + virtual void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + virtual void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + virtual void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h new file mode 100644 index 0000000..823f411 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h @@ -0,0 +1,425 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/SceneComponent.h" +#include "Animation/AnimInstance.h" +#include "Misc/Guid.h" +#include "HandSocketComponent.generated.h" + +class USkeletalMeshComponent; +class UPoseableMeshComponent; +class USkeletalMesh; +class UGripMotionControllerComponent; +class UAnimSequence; +struct FPoseSnapshot; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRHandSocketComponent, Log, All); + +// Custom serialization version for the hand socket component +struct VREXPANSIONPLUGIN_API FVRHandSocketCustomVersion +{ + enum Type + { + // Before any version changes were made in the plugin + BeforeCustomVersionWasAdded = 0, + + // Added a set state tracker to handle in editor construction edge cases + HandSocketStoringSetState = 1, + + // -----<new versions can be added above this line>------------------------------------------------- + VersionPlusOne, + LatestVersion = VersionPlusOne - 1 + }; + + // The GUID for this custom version number + const static FGuid GUID; + +private: + FVRHandSocketCustomVersion() {} +}; + + +UENUM() +namespace EVRAxis +{ + enum Type + { + X, + Y, + Z + }; +} + +/** +* A base class for custom hand socket objects +* Not directly blueprint spawnable as you are supposed to subclass this to add on top your own custom data +*/ + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRHandPoseBonePair +{ + GENERATED_BODY() +public: + + // Distance to offset to get center of waist from tracked parent location + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + FName BoneName; + + // Initial "Resting" location of the tracker parent, assumed to be the calibration zero + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + FQuat DeltaPose; + + FBoneReference ReferenceToConstruct; + + FBPVRHandPoseBonePair() + { + BoneName = NAME_None; + DeltaPose = FQuat::Identity; + } + + FORCEINLINE bool operator==(const FName& Other) const + { + return (BoneName == Other); + } +}; + +UCLASS(Blueprintable, ClassGroup = (VRExpansionPlugin), hideCategories = ("Component Tick", Events, Physics, Lod, "Asset User Data", Collision)) +class VREXPANSIONPLUGIN_API UHandSocketComponent : public USceneComponent, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + + UHandSocketComponent(const FObjectInitializer& ObjectInitializer); + ~UHandSocketComponent(); + + //static get socket compoonent + + //Axis to mirror on for this socket + UPROPERTY(EditDefaultsOnly, Category = "Hand Socket Data|Mirroring|Advanced") + TEnumAsByte<EVRAxis::Type> MirrorAxis; + + // Axis to flip on when mirroring this socket + UPROPERTY(VisibleDefaultsOnly, Category = "Hand Socket Data|Mirroring|Advanced") + TEnumAsByte<EVRAxis::Type> FlipAxis; + + // Relative placement of the hand to this socket + UPROPERTY(EditAnywhere, BlueprintReadWrite, /*DuplicateTransient,*/ Category = "Hand Socket Data") + FTransform HandRelativePlacement; + + // Target Slot Prefix + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + FName SlotPrefix; + + // If true the hand meshes relative transform will be de-coupled from the hand socket + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Hand Socket Data") + bool bDecoupleMeshPlacement; + + // If true we should only be used to snap mesh to us, not for the actual socket transform + // Will act like free gripping but the mesh will snap into position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bOnlySnapMesh; + + // If true then this socket is left hand dominant and will flip for the right hand instead + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Hand Socket Data") + bool bLeftHandDominant; + + // If true we will mirror ourselves automatically for the off hand + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data|Mirroring", meta = (DisplayName = "Flip For Off Hand")) + bool bFlipForLeftHand; + + // If true, when we mirror the hand socket it will only mirror rotation, not position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data|Mirroring", meta = (editcondition = "bFlipForLeftHand")) + bool bOnlyFlipRotation; + + // If true then this hand socket will always be considered "in range" and checked against others for lowest distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bAlwaysInRange; + + // If true and there are multiple hand socket components in range with this setting + // Then the default behavior will compare closest rotation on them all to pick one + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bMatchRotation; + + // If true then the hand socket will not be considered for search operations + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + bool bDisabled; + + // Snap distance to use if you want to override the defaults. + // Will be ignored if == 0.0f or bAlwaysInRange is true + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + float OverrideDistance; + + // If true we are expected to have a list of custom deltas for bones to overlay onto our base pose + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + bool bUseCustomPoseDeltas; + + // Custom rotations that are added on top of an animations bone rotation to make a final transform + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + TArray<FBPVRHandPoseBonePair> CustomPoseDeltas; + + // Primary hand animation, for both hands if they share animations, right hand if they don't + // If using a custom pose delta this is expected to be the base pose + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation") + TObjectPtr<UAnimSequence> HandTargetAnimation; + + // Scale to apply when mirroring the hand, adjust to visualize your off hand correctly + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Socket Data") + FVector MirroredScale; + +#if WITH_EDITORONLY_DATA + FTransform GetBoneTransformAtTime(UAnimSequence* MyAnimSequence, /*float AnimTime,*/ int BoneIdx, FName BoneName, bool bUseRawDataOnly); +#endif + + // Returns the base target animation of the hand (if there is one) + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + UAnimSequence* GetTargetAnimation(); + + /** + * Returns the target animation of the hand blended with the delta rotations if there are any + * @param PoseSnapShot - Snapshot generated by this function + * @param TargetMesh - Targetmesh to check the skeleton of + * @param bSkipRootBone - If true we will skip the root bone (IE: Hand_r) and only apply the children poses (Full body) + * @param bFlipHand - If true we will mirror the pose, this is primarily to apply to a left hand from a right + */ + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + bool GetBlendedPoseSnapShot(FPoseSnapshot& PoseSnapShot, USkeletalMeshComponent* TargetMesh = nullptr, bool bSkipRootBone = false, bool bFlipHand = false); + + /** + * Converts an animation sequence into a pose snapshot + * @param InAnimationSequence - Sequence to convert to a pose snapshot + * @param OutPoseSnapShot - Snapshot returned by this function + * @param TargetMesh - Targetmesh to check the skeleton of + * @param bSkipRootBone - If true we will skip the root bone (IE: Hand_r) and only apply the children poses (Full body) + * @param bFlipHand - If true we will mirror the pose, this is primarily to apply to a left hand from a right + */ + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data", meta = (bIgnoreSelf = "true")) + static bool GetAnimationSequenceAsPoseSnapShot(UAnimSequence * InAnimationSequence, FPoseSnapshot& OutPoseSnapShot, USkeletalMeshComponent* TargetMesh = nullptr, bool bSkipRootBone = false, bool bFlipHand = false); + + // Returns the target relative transform of the hand + //UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + FTransform GetHandRelativePlacement(); + + inline void MirrorHandTransform(FTransform& ReturnTrans, FTransform& relTrans) + { + if (bOnlyFlipRotation) + { + ReturnTrans.SetTranslation(ReturnTrans.GetTranslation() - relTrans.GetTranslation()); + ReturnTrans.Mirror(GetAsEAxis(MirrorAxis), GetCrossAxis()); + ReturnTrans.SetTranslation(ReturnTrans.GetTranslation() + relTrans.GetTranslation()); + } + else + { + ReturnTrans.Mirror(GetAsEAxis(MirrorAxis), GetCrossAxis()); + } + } + + inline TEnumAsByte<EAxis::Type> GetAsEAxis(TEnumAsByte<EVRAxis::Type> InAxis) + { + switch (InAxis) + { + case EVRAxis::X: + { + return EAxis::X; + }break; + case EVRAxis::Y: + { + return EAxis::Y; + }break; + case EVRAxis::Z: + { + return EAxis::Z; + }break; + } + + return EAxis::X; + } + + + inline FVector GetMirrorVector() + { + switch (MirrorAxis) + { + case EVRAxis::Y: + { + return FVector::RightVector; + }break; + case EVRAxis::Z: + { + return FVector::UpVector; + }break; + case EVRAxis::X: + default: + { + return FVector::ForwardVector; + }break; + } + } + + inline FVector GetFlipVector() + { + switch (FlipAxis) + { + case EVRAxis::Y: + { + return FVector::RightVector; + }break; + case EVRAxis::Z: + { + return FVector::UpVector; + }break; + case EVRAxis::X: + default: + { + return FVector::ForwardVector; + }break; + } + } + + inline TEnumAsByte<EAxis::Type> GetCrossAxis() + { + // Checking against the sign now to avoid possible mobile precision issues + FVector SignVec = MirroredScale.GetSignVector(); + + if (SignVec.X < 0) + { + return EAxis::X; + } + else if (SignVec.Z < 0) + { + return EAxis::Z; + } + else if (SignVec.Y < 0) + { + return EAxis::Y; + } + + return GetAsEAxis(FlipAxis); + + /*if (FlipAxis == EVRAxis::Y) + { + return EAxis::Z; + } + else if (FlipAxis == EVRAxis::Z) + { + return EAxis::X; + } + else if (FlipAxis == EVRAxis::X) + { + return EAxis::Y; + }*/ + + //return EAxis::None; + } + // Returns the target relative transform of the hand to the gripped object + // If you want the transform mirrored you need to pass in which hand is requesting the information + // If UseParentScale is true then we will scale the value by the parent scale (generally only for when not using absolute hand scale) + // If UseMirrorScale is true then we will mirror the scale on the hand by the hand sockets mirror scale when appropriate (not for fully body!) + // if UseMirrorScale is false than the resulting transform will not have mirroring scale added so you may have to break the transform. + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + FTransform GetMeshRelativeTransform(bool bIsRightHand, bool bUseParentScale = false, bool bUseMirrorScale = false); + + // Returns the defined hand socket component (if it exists, you need to valid check the return! + // If it is a valid return you can then cast to your projects base socket class and handle whatever logic you want + UFUNCTION(BlueprintCallable, Category = "Hand Socket Data") + static UHandSocketComponent* GetHandSocketComponentFromObject(UObject* ObjectToCheck, FName SocketName); + + virtual FTransform GetHandSocketTransform(UGripMotionControllerComponent* QueryController, bool bIgnoreOnlySnapMesh = false); + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif +#if WITH_EDITORONLY_DATA + static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + void PoseVisualizationToAnimation(bool bForceRefresh = false); + bool bTickedPose; + + UPROPERTY() + bool bDecoupled; + +#endif + virtual void Serialize(FArchive& Ar) override; + virtual void OnRegister() override; + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + /** mesh component to indicate hand placement */ +#if WITH_EDITORONLY_DATA + //UPROPERTY(EditAnywhere, BlueprintReadOnly, Transient, Category = "Hand Visualization") + //class USkeletalMeshComponent* HandVisualizerComponent; + class UPoseableMeshComponent* HandVisualizerComponent; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Transient, Category = "Hand Visualization") + TObjectPtr<USkeletalMesh> VisualizationMesh; + + // If we should show the visualization mesh + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Hand Visualization") + bool bShowVisualizationMesh; + + // Show the visualization mirrored + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Hand Visualization") + bool bMirrorVisualizationMesh; + + // If we should show the grip range of this socket (shows text if always in range) + // If override distance is zero then it attempts to infer the value from the parent architecture + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Hand Visualization") + bool bShowRangeVisualization; + + void PositionVisualizationMesh(); + void HideVisualizationMesh(); + +#endif + +#if WITH_EDITORONLY_DATA + // Material to apply to the hand + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Visualization") + TObjectPtr<UMaterial> HandPreviewMaterial; + +#endif +}; + +UCLASS(transient, Blueprintable, hideCategories = AnimInstance, BlueprintType) +class VREXPANSIONPLUGIN_API UHandSocketAnimInstance : public UAnimInstance +{ + GENERATED_BODY() + +public: + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, transient, Category = "Socket Data") + TObjectPtr<UHandSocketComponent> OwningSocket; + + virtual void NativeInitializeAnimation() override + { + Super::NativeInitializeAnimation(); + + OwningSocket = Cast<UHandSocketComponent>(GetOwningComponent()->GetAttachParent()); + } +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRButtonComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRButtonComponent.h new file mode 100644 index 0000000..5b06704 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRButtonComponent.h @@ -0,0 +1,189 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/StaticMeshComponent.h" +#include "VRInteractibleFunctionLibrary.h" +#include "VRButtonComponent.generated.h" + +/** +* +*/ + +// VR Button Types +UENUM(Blueprintable) +enum class EVRButtonType : uint8 +{ + Btn_Press, + Btn_Toggle_Return, + Btn_Toggle_Stay +}; + +// VR Button SyncOptions +UENUM(Blueprintable) +enum class EVRStateChangeAuthorityType : uint8 +{ + /* Button state can be changed on all connections */ + CanChangeState_All, + /* Button state can be changed only on the server */ + CanChangeState_Server, + /* Button state can be changed only on the owner of the interacting primitive */ + CanChangeState_Owner +}; + +/** Delegate for notification when the button state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FVRButtonStateChangedSignature, bool, ButtonState, AActor *, InteractingActor, UPrimitiveComponent *, InteractingComponent); + +/** Delegate for notification when the begins a new interaction. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRButtonStartedInteractionSignature, AActor *, InteractingActor, UPrimitiveComponent *, InteractingComponent); + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRButtonComponent : public UStaticMeshComponent +{ + GENERATED_BODY() + +public: + UVRButtonComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRButtonComponent(); + + UFUNCTION() + void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); + + UFUNCTION() + void OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UFUNCTION(BlueprintPure, Category = "VRButtonComponent") + bool IsButtonInUse(); + + // Should be called after the button is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRButtonComponent") + void ResetInitialButtonLocation(); + + // Sets the button state outside of interaction, bSnapIntoPosition is for Toggle_Stay mode, it will lerp into the new position if this is false + UFUNCTION(BlueprintCallable, Category = "VRButtonComponent") + void SetButtonState(bool bNewButtonState, bool bCallButtonChangedEvent = true, bool bSnapIntoPosition = false); + + // Resets the button to its resting location (mostly for Toggle_Stay) + UFUNCTION(BlueprintCallable, Category = "VRButtonComponent") + void SetButtonToRestingPosition(bool bLerpToPosition = false); + + // On the button state changing, keep in mind that InteractingActor can be invalid if manually setting the state + UPROPERTY(BlueprintAssignable, Category = "VRButtonComponent") + FVRButtonStateChangedSignature OnButtonStateChanged; + + // On the button state changing, keep in mind that InteractingActor can be invalid if manually setting the state + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Button State Changed")) + void ReceiveButtonStateChanged(bool bCurButtonState, AActor * LastInteractingActor, UPrimitiveComponent * InteractingComponent); + + // On Button beginning interaction (may spam a bit depending on if overlap is jittering) + UPROPERTY(BlueprintAssignable, Category = "VRButtonComponent") + FVRButtonStartedInteractionSignature OnButtonBeginInteraction; + + // On Button ending interaction (may spam a bit depending on if overlap is jittering) + UPROPERTY(BlueprintAssignable, Category = "VRButtonComponent") + FVRButtonStartedInteractionSignature OnButtonEndInteraction; + + // On Button beginning interaction (may spam a bit depending on if overlap is jittering) + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Button Started Interaction")) + void ReceiveButtonBeginInteraction(AActor * InteractingActor, UPrimitiveComponent * InteractingComponent); + + // On Button ending interaction (may spam a bit depending on if overlap is jittering) + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Button Ended Interaction")) + void ReceiveButtonEndInteraction(AActor * LastInteractingActor, UPrimitiveComponent * LastInteractingComponent); + + // On the button state changing, keep in mind that InteractingActor can be invalid if manually setting the state + UPROPERTY(BlueprintReadOnly, Category = "VRButtonComponent") + TObjectPtr<UPrimitiveComponent> LocalInteractingComponent; + + UPROPERTY(BlueprintReadOnly, Category = "VRButtonComponent") + TObjectPtr<AActor> LocalLastInteractingActor; + + UPROPERTY(BlueprintReadOnly, Category = "VRButtonComponent") + TObjectPtr<UPrimitiveComponent> LocalLastInteractingComponent; + + // Whether the button is enabled or not (can be interacted with) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + bool bIsEnabled; + + // Current state of the button, writable to set initial value + UPROPERTY(EditAnywhere,BlueprintReadWrite, Replicated, Category = "VRButtonComponent") + bool bButtonState; + + // Who is allowed to change the button state + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "VRButtonComponent|Replication") + EVRStateChangeAuthorityType StateChangeAuthorityType; + + // Speed that the button de-presses when no longer interacted with + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float DepressSpeed; + + // Distance that the button depresses + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float DepressDistance; + + // Type of button this is + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + EVRButtonType ButtonType; + + // Negative on this axis is the depress direction + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + EVRInteractibleAxis ButtonAxis; + + // Depth at which the button engages (switches) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float ButtonEngageDepth; + + // Minimum time before the button can be switched again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + float MinTimeBetweenEngaging; + + // Skips filtering overlaps on the button and lets you manage it yourself, this is the alternative to overriding IsValidOverlap + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRButtonComponent") + bool bSkipOverlapFiltering; + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRButtonComponent") + bool IsValidOverlap(UPrimitiveComponent * OverlapComponent); + + // Sets the Last interacting actor variable + void SetLastInteractingActor(); + + virtual FVector GetTargetRelativeLocation(); + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRButtonComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + SetButtonToRestingPosition(); + } + +protected: + + // Control variables + FVector InitialLocation; + bool bToggledThisTouch; + FVector InitialComponentLoc; + float LastToggleTime; + + float GetAxisValue(FVector CheckLocation); + + FVector SetAxisValue(float SetValue); + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRDialComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRDialComponent.h new file mode 100644 index 0000000..291f3fd --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRDialComponent.h @@ -0,0 +1,307 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "VRGripInterface.h" +#include "Components/StaticMeshComponent.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "VRDialComponent.generated.h" + +class UGripMotionControllerComponent; + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRDialStateChangedSignature, float, DialMilestoneAngle); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRDialFinishedLerpingSignature); + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRDialComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRDialComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRDialComponent(); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRDialComponent") + FVRDialStateChangedSignature OnDialHitSnapAngle; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Dial Hit Snap Angle")) + void ReceiveDialHitSnapAngle(float DialMilestoneAngle); + + // If true the dial will lerp back to zero on release + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent|Lerping") + bool bLerpBackOnRelease; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent|Lerping") + bool bSendDialEventsDuringLerp; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent|Lerping") + float DialReturnSpeed; + + UPROPERTY(BlueprintReadOnly, Category = "VRDialComponent|Lerping") + bool bIsLerping; + + UPROPERTY(BlueprintAssignable, Category = "VRDialComponent|Lerping") + FVRDialFinishedLerpingSignature OnDialFinishedLerping; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Dial Finished Lerping")) + void ReceiveDialFinishedLerping(); + + UPROPERTY(BlueprintReadOnly, Category = "VRDialComponent") + float CurrentDialAngle; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent")//, meta = (ClampMin = "0.0", ClampMax = "360.0", UIMin = "0.0", UIMax = "360.0")) + float ClockwiseMaximumDialAngle; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent")//, meta = (ClampMin = "0.0", ClampMax = "360.0", UIMin = "0.0", UIMax = "360.0")) + float CClockwiseMaximumDialAngle; + + // If true then the dial can "roll over" past 360/0 degrees in a direction + // Allowing unlimited dial angle values + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bUseRollover; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bDialUsesAngleSnap; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bDialUseSnapAngleList; + + // Optional list of snap angles for the dial + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "bDialUseSnapAngleList")) + TArray<float> DialSnapAngleList; + + // Angle that the dial snaps to on release and when within the threshold distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "!bDialUseSnapAngleList", ClampMin = "0.0", ClampMax = "180.0", UIMin = "0.0", UIMax = "180.0")) + float SnapAngleIncrement; + + // Threshold distance that when within the dial will stay snapped to its closest snap increment + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "!bDialUseSnapAngleList", ClampMin = "0.0", ClampMax = "180.0", UIMin = "0.0", UIMax = "180.0")) + float SnapAngleThreshold; + + // Scales rotational input to speed up or slow down the rotation + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (ClampMin = "0.0", ClampMax = "180.0", UIMin = "0.0", UIMax = "180.0")) + float RotationScaler; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + EVRInteractibleAxis DialRotationAxis; + + // If true then the dial will directly sample the hands rotation instead of using its movement around it. + // This is good for roll specific dials but is fairly bad elsewhere. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent") + bool bDialUseDirectHandRotation; + + float LastGripRot; + float InitialGripRot; + float InitialRotBackEnd; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRDialComponent", meta = (editcondition = "bDialUseDirectHandRotation")) + EVRInteractibleAxis InteractorRotationAxis; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRDialComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + CalculateDialProgress(); + } + + void CalculateDialProgress(); + + //FTransform InitialRelativeTransform; + FVector InitialInteractorLocation; + FVector InitialDropLocation; + float CurRotBackEnd; + FRotator LastRotation; + float LastSnapAngle; + + // Should be called after the dial is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + void ResetInitialDialLocation(); + + // Can be called to recalculate the dial angle after you move it if you want different values + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + void AddDialAngle(float DialAngleDelta, bool bCallEvents = false, bool bSkipSettingRot = false); + + // Directly sets the dial angle, still obeys maximum limits and snapping though + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + void SetDialAngle(float DialAngle, bool bCallEvents = false); + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float &GripStiffnessOut, float &GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray<FBPGripPair>& CurHoldingControllers, bool & bCurIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent * NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*> & ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + + protected: + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRInteractibleFunctionLibrary.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRInteractibleFunctionLibrary.h new file mode 100644 index 0000000..580ec33 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRInteractibleFunctionLibrary.h @@ -0,0 +1,302 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +//#include "UObject/ObjectMacros.h" +//#include "Engine/EngineTypes.h" +//#include "UObject/ScriptInterface.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "GameplayTagContainer.h" + +#include "VRInteractibleFunctionLibrary.generated.h" + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(VRInteractibleFunctionLibraryLog, Log, All); + +// Declares our interactible axis's +UENUM(Blueprintable) +enum class EVRInteractibleAxis : uint8 +{ + Axis_X, + Axis_Y, + Axis_Z +}; + +// A data structure to hold important interactible data +// Should be init'd in Beginplay with BeginPlayInit as well as OnGrip with OnGripInit. +// Works in "static space", it records the original relative transform of the interactible on begin play +// so that calculations on the actual component can be done based off of it. +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRInteractibleBaseData +{ + GENERATED_BODY() +public: + + // Our initial relative transform to our parent "static space" - Set in BeginPlayInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FTransform InitialRelativeTransform; + + // Initial location in "static space" of the interactor on grip - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FVector InitialInteractorLocation; + + // Initial location of the interactible in the "static space" - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FVector InitialGripLoc; + + // Initial location on the interactible of the grip, used for drop calculations - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FVector InitialDropLocation; + + // The initial transform in relative space of the grip to us - Set in OnGripInit + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "InteractibleData") + FTransform ReversedRelativeTransform; + + FBPVRInteractibleBaseData() + { + InitialInteractorLocation = FVector::ZeroVector; + InitialGripLoc = FVector::ZeroVector; + InitialDropLocation = FVector::ZeroVector; + } +}; + +UCLASS() +class VREXPANSIONPLUGIN_API UVRInteractibleFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + + static float GetAtan2Angle(EVRInteractibleAxis AxisToCalc, FVector CurInteractorLocation, float OptionalInitialRotation = 0.0f) + { + switch (AxisToCalc) + { + case EVRInteractibleAxis::Axis_X: + { + return FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.Z)) - OptionalInitialRotation; + }break; + case EVRInteractibleAxis::Axis_Y: + { + return FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Z, CurInteractorLocation.X)) - OptionalInitialRotation; + }break; + case EVRInteractibleAxis::Axis_Z: + { + return FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.X)) - OptionalInitialRotation; + }break; + default: + {}break; + } + + return 0.0f; + } + + static float GetDeltaAngleFromTransforms(EVRInteractibleAxis RotAxis, FTransform & InitialRelativeTransform, FTransform &CurrentRelativeTransform) + { + return GetDeltaAngle(RotAxis, (CurrentRelativeTransform.GetRelativeTransform(InitialRelativeTransform).GetRotation()).GetNormalized()); + } + + static float GetDeltaAngle(EVRInteractibleAxis RotAxis, FQuat DeltaQuat) + { + FVector Axis; + float Angle; + DeltaQuat.ToAxisAndAngle(Axis, Angle); + + if (RotAxis == EVRInteractibleAxis::Axis_Z) + return FRotator::NormalizeAxis(FMath::RadiansToDegrees(Angle)) * (FMath::Sign(GetAxisValue(RotAxis, Axis))); + else + return FRotator::NormalizeAxis(FMath::RadiansToDegrees(Angle)) * (-FMath::Sign(GetAxisValue(RotAxis, Axis))); + } + + static float GetAxisValue(EVRInteractibleAxis RotAxis, FRotator CheckRotation) + { + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + return CheckRotation.Roll; break; + case EVRInteractibleAxis::Axis_Y: + return CheckRotation.Pitch; break; + case EVRInteractibleAxis::Axis_Z: + return CheckRotation.Yaw; break; + default:return 0.0f; break; + } + } + + static float GetAxisValue(EVRInteractibleAxis RotAxis, FVector CheckAxis) + { + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + return CheckAxis.X; break; + case EVRInteractibleAxis::Axis_Y: + return CheckAxis.Y; break; + case EVRInteractibleAxis::Axis_Z: + return CheckAxis.Z; break; + default:return 0.0f; break; + } + } + + static FVector SetAxisValueVec(EVRInteractibleAxis RotAxis, float SetValue) + { + FVector vec = FVector::ZeroVector; + + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.X = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Y = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Z = SetValue; break; + default:break; + } + + return vec; + } + + static FRotator SetAxisValueRot(EVRInteractibleAxis RotAxis, float SetValue) + { + FRotator vec = FRotator::ZeroRotator; + + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.Roll = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Pitch = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Yaw = SetValue; break; + default:break; + } + + return vec; + } + + static FRotator SetAxisValueRot(EVRInteractibleAxis RotAxis, float SetValue, FRotator Var) + { + FRotator vec = Var; + switch (RotAxis) + { + case EVRInteractibleAxis::Axis_X: + vec.Roll = SetValue; break; + case EVRInteractibleAxis::Axis_Y: + vec.Pitch = SetValue; break; + case EVRInteractibleAxis::Axis_Z: + vec.Yaw = SetValue; break; + default:break; + } + + return vec; + } + + // Get current parent transform + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static FTransform Interactible_GetCurrentParentTransform(USceneComponent * SceneComponentToCheck) + { + if (SceneComponentToCheck) + { + if (USceneComponent * AttachParent = SceneComponentToCheck->GetAttachParent()) + { + return AttachParent->GetComponentTransform(); + } + } + + return FTransform::Identity; + } + + // Get current relative transform (original transform we were at on grip for the current parent transform) + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static FTransform Interactible_GetCurrentRelativeTransform(USceneComponent * SceneComponentToCheck, UPARAM(ref)FBPVRInteractibleBaseData & BaseData) + { + FTransform ParentTransform = FTransform::Identity; + if (SceneComponentToCheck) + { + if (USceneComponent * AttachParent = SceneComponentToCheck->GetAttachParent()) + { + // during grip there is no parent so we do this, might as well do it anyway for lerping as well + ParentTransform = AttachParent->GetComponentTransform(); + } + } + + return BaseData.InitialRelativeTransform * ParentTransform; + } + + // Inits the initial relative transform of an interactible on begin play + UFUNCTION(BlueprintCallable, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static void Interactible_BeginPlayInit(USceneComponent * InteractibleComp, UPARAM(ref) FBPVRInteractibleBaseData & BaseDataToInit) + { + if (!InteractibleComp) + return; + + BaseDataToInit.InitialRelativeTransform = InteractibleComp->GetRelativeTransform(); + } + + // Inits the calculated values of a VR Interactible Base Data Structure on a grip event + UFUNCTION(BlueprintCallable, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static void Interactible_OnGripInit(USceneComponent * InteractibleComp, UPARAM(ref) FBPActorGripInformation& GripInformation, UPARAM(ref) FBPVRInteractibleBaseData & BaseDataToInit) + { + if (!InteractibleComp) + return; + + BaseDataToInit.ReversedRelativeTransform = FTransform(GripInformation.RelativeTransform.ToInverseMatrixWithScale()); + BaseDataToInit.InitialDropLocation = BaseDataToInit.ReversedRelativeTransform.GetTranslation(); // Technically a duplicate, but will be more clear + + FTransform RelativeToGripTransform = BaseDataToInit.ReversedRelativeTransform * InteractibleComp->GetComponentTransform(); + FTransform CurrentRelativeTransform = BaseDataToInit.InitialRelativeTransform * UVRInteractibleFunctionLibrary::Interactible_GetCurrentParentTransform(InteractibleComp); + BaseDataToInit.InitialInteractorLocation = CurrentRelativeTransform.InverseTransformPosition(RelativeToGripTransform.GetTranslation()); + + BaseDataToInit.InitialGripLoc = BaseDataToInit.InitialRelativeTransform.InverseTransformPosition(InteractibleComp->GetRelativeLocation()); + } + + // Returns (in degrees) the angle around the axis of a location + // Expects the CurInteractorLocation to be in relative space already + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static float Interactible_GetAngleAroundAxis(EVRInteractibleAxis AxisToCalc, FVector CurInteractorLocation) + { + float ReturnAxis = 0.0f; + + switch (AxisToCalc) + { + case EVRInteractibleAxis::Axis_X: + { + ReturnAxis = FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.Z)); + }break; + case EVRInteractibleAxis::Axis_Y: + { + ReturnAxis = FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Z, CurInteractorLocation.X)); + }break; + case EVRInteractibleAxis::Axis_Z: + { + ReturnAxis = FMath::RadiansToDegrees(FMath::Atan2(CurInteractorLocation.Y, CurInteractorLocation.X)); + }break; + default: + {}break; + } + + return ReturnAxis; + } + + // Returns (in degrees) the delta rotation from the initial angle at grip to the current interactor angle around the axis + // Expects CurInteractorLocation to be in relative space already + // You can add this to an initial rotation and clamp the result to rotate over time based on hand position + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static float Interactible_GetAngleAroundAxisDelta(EVRInteractibleAxis AxisToCalc, FVector CurInteractorLocation, float InitialAngle) + { + return FRotator::NormalizeAxis(Interactible_GetAngleAroundAxis(AxisToCalc, CurInteractorLocation) - InitialAngle); + } + + + // Returns a value that is snapped to the given settings, taking into account the threshold and increment + UFUNCTION(BlueprintPure, Category = "VRInteractibleFunctions", meta = (bIgnoreSelf = "true")) + static float Interactible_GetThresholdSnappedValue(float ValueToSnap, float SnapIncrement, float SnapThreshold) + { + if (SnapIncrement > 0.f && FMath::Fmod(ValueToSnap, SnapIncrement) <= FMath::Min(SnapIncrement, SnapThreshold)) + { + return FMath::GridSnap(ValueToSnap, SnapIncrement); + } + + return ValueToSnap; + } + +}; + + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRLeverComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRLeverComponent.h new file mode 100644 index 0000000..2efdd89 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRLeverComponent.h @@ -0,0 +1,416 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "VRGripInterface.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "Components/StaticMeshComponent.h" +#include "VRLeverComponent.generated.h" + +class UGripMotionControllerComponent; + +UENUM(Blueprintable) +enum class EVRInteractibleLeverAxis : uint8 +{ + /* Rotates only towards the X Axis */ + Axis_X, + /* Rotates only towards the Y Axis */ + Axis_Y, + /* Rotates only towards the Z Axis */ + Axis_Z, + /* Rotates freely on the XY Axis' */ + Axis_XY, + /* Acts like a flight stick, with AllCurrentLeverAngles being the positive / negative of the current full angle (yaw based on initial grip delta) */ + FlightStick_XY, +}; + +UENUM(Blueprintable) +enum class EVRInteractibleLeverEventType : uint8 +{ + LeverPositive, + LeverNegative +}; + +UENUM(Blueprintable) +enum class EVRInteractibleLeverReturnType : uint8 +{ + /** Stays in place on drop */ + Stay, + + /** Returns to zero on drop (lerps) */ + ReturnToZero, + + /** Lerps to closest max (only works with X/Y/Z axis levers) */ + LerpToMax, + + /** Lerps to closest max if over the toggle threshold (only works with X/Y/Z axis levers) */ + LerpToMaxIfOverThreshold, + + /** Retains momentum on release (only works with X/Y/Z axis levers) */ + RetainMomentum +}; + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FVRLeverStateChangedSignature, bool, LeverStatus, EVRInteractibleLeverEventType, LeverStatusType, float, LeverAngleAtTime, float, FullLeverAngleAtTime); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRLeverFinishedLerpingSignature, float, FinalAngle); + +/** +* A Lever component, can act like a lever, door, wheel, joystick. +* If set to replicates it will replicate its values to the clients. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRLeverComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRLeverComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRLeverComponent(); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRLeverComponent") + FVRLeverStateChangedSignature OnLeverStateChanged; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Lever State Changed")) + void ReceiveLeverStateChanged(bool LeverStatus, EVRInteractibleLeverEventType LeverStatusType, float LeverAngleAtTime, float FullLeverAngleAttime); + + UPROPERTY(BlueprintAssignable, Category = "VRLeverComponent") + FVRLeverFinishedLerpingSignature OnLeverFinishedLerping; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Lever Finished Lerping")) + void ReceiveLeverFinishedLerping(float LeverFinalAngle); + + // Primary axis angle only + UPROPERTY(BlueprintReadOnly, Category = "VRLeverComponent") + float CurrentLeverAngle; + + // Writes out all current angles to this rotator, useful mostly for XY and Flight stick modes + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + FRotator AllCurrentLeverAngles; + + // Bearing Direction, for X/Y is their signed direction, for XY mode it is an actual 2D directional vector + UPROPERTY(BlueprintReadOnly, Category = "VRLeverComponent") + FVector CurrentLeverForwardVector; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bIsPhysicsLever; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bUngripAtTargetRotation; + + // Rotation axis to use, XY is combined X and Y, only LerpToZero and PositiveLimits work with this mode + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + EVRInteractibleLeverAxis LeverRotationAxis; + + // The percentage of the angle at witch the lever will toggle + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent", meta = (ClampMin = "0.01", ClampMax = "1.0", UIMin = "0.01", UIMax = "1.0")) + float LeverTogglePercentage; + + // The max angle of the lever in the positive direction + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent", meta = (ClampMin = "0.0", ClampMax = "179.9", UIMin = "0.0", UIMax = "180.0")) + float LeverLimitPositive; + + // The max angle of the lever in the negative direction + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent", meta = (ClampMin = "0.0", ClampMax = "179.9", UIMin = "0.0", UIMax = "180.0")) + float LeverLimitNegative; + + // The max angle of the flightsticks yaw in either direction off of 0 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Flight Stick Settings", meta = (ClampMin = "0.0", ClampMax = "179.9", UIMin = "0.0", UIMax = "180.0")) + float FlightStickYawLimit; + + // If true then this lever is locked in place until unlocked again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bIsLocked; + + // If true then this lever will auto drop even when locked + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bAutoDropWhenLocked; + + // Sets if the lever is locked or not + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetIsLocked(bool bNewLockedState); + + // Checks and applies auto drop if we need too + bool CheckAutoDrop(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + EVRInteractibleLeverReturnType LeverReturnTypeWhenReleased; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + bool bSendLeverEventsDuringLerp; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent") + float LeverReturnSpeed; + + // If true then we will blend the values of the XY axis' by the AngleThreshold, lowering Pitch/Yaw influence based on how far from leaning into the axis that the lever is + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Flight Stick Settings") + bool bBlendAxisValuesByAngleThreshold; + + // The angle threshold to blend around, default of 90.0 blends 0.0 to 1.0 smoothly across entire sweep + // A value of 45 would blend it to 0 halfway rotated to the other axis, while 180 would still leave half the influence when fully rotated out of facing + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Flight Stick Settings", meta = (ClampMin = "1.0", ClampMax = "360.0", UIMin = "1.0", UIMax = "360.0")) + float AngleThreshold; + + // Number of frames to average momentum across for the release momentum (avoids quick waggles) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0", ClampMax = "12", UIMin = "0", UIMax = "12")) + int FramesToAverage; + + // Units in degrees per second to slow a momentum lerp down + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "180", UIMin = "0.0", UIMax = "180.0")) + float LeverMomentumFriction; + + // % of elasticity on reaching the end 0 - 1.0 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float LeverRestitution; + + // Maximum momentum of the lever in degrees per second + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRLeverComponent|Momentum Settings", meta = (ClampMin = "0.0", UIMin = "0.0")) + float MaxLeverMomentum; + + UPROPERTY(BlueprintReadOnly, Category = "VRLeverComponent") + bool bIsLerping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + // Full precision current angle + float FullCurrentAngle; + + float LastDeltaAngle; + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRLeverComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + ReCalculateCurrentAngle(); + } + + // Flight stick variables + FTransform InteractorOffsetTransform; + + FVector InitialInteractorLocation; + FVector InitialInteractorDropLocation; + float InitialGripRot; + float RotAtGrab; + FQuat qRotAtGrab; + bool bLeverState; + bool bIsInFirstTick; + + // For momentum retention + float MomentumAtDrop; + float LastLeverAngle; + + float CalcAngle(EVRInteractibleLeverAxis AxisToCalc, FVector CurInteractorLocation, bool bSkipLimits = false); + + void LerpAxis(float CurrentAngle, float DeltaTime); + + void CalculateCurrentAngle(FTransform & CurrentTransform); + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Stiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Damping; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + TWeakObjectPtr<USceneComponent> ParentComponent; + + // Should be called after the lever is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + void ResetInitialLeverLocation(bool bAllowThrowingEvents = true); + + /** + * Sets the angle of the lever forcefully + * @param NewAngle The new angle to use, for single axis levers the sign of the angle will be used + * @param DualAxisForwardVector Only used with dual axis levers, you need to define the forward axis for the angle to apply too + */ + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + void SetLeverAngle(float NewAngle, FVector DualAxisForwardVector, bool bAllowThrowingEvents = true); + + // ReCalculates the current angle, sets it on the back end, and returns it + // If allow throwing events is true then it will trigger the callbacks for state changes as well + UFUNCTION(BlueprintCallable, Category = "VRLeverComponent") + float ReCalculateCurrentAngle(bool bAllowThrowingEvents = true); + + void ProccessCurrentState(bool bWasLerping = false, bool bThrowEvents = true, bool bCheckAutoDrop = true); + + virtual void OnUnregister() override; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + bool DestroyConstraint(); + bool SetupConstraint(); + + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray<FBPGripPair>& CurHoldingControllers, bool& bCurIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent* NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + + protected: + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRMountComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRMountComponent.h new file mode 100644 index 0000000..5655040 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRMountComponent.h @@ -0,0 +1,254 @@ + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "GameplayTagAssetInterface.h" +#include "Components/StaticMeshComponent.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "VRGripInterface.h" +#include "VRMountComponent.generated.h" + +class UGripMotionControllerComponent; + + +UENUM(Blueprintable) +enum class EVRInteractibleMountAxis : uint8 +{ + /** Limit Rotation to Yaw and Roll */ + Axis_XZ +}; + +// A mounted lever/interactible implementation - Created by SpaceHarry - Merged into the plugin 01/29/2018 +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRMountComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRMountComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRMountComponent(); + + + // Rotation axis to use, XY is combined X and Y, only LerpToZero and PositiveLimits work with this mode + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMountComponent") + EVRInteractibleMountAxis MountRotationAxis; + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + FTransform InitialRelativeTransform; + FVector InitialInteractorLocation; + FVector InitialInteractorDropLocation; + float InitialGripRot; + FQuat qRotAtGrab; + + // If the mount is swirling around a 90 degree pitch increase this number by 0.1 steps. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMountComponent") + float FlipingZone; + + // If the mount feels lagging behind in yaw at some point after 90 degree pitch increase this number by 0.1 steps + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMountComponent") + float FlipReajustYawSpeed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + bool GrippedOnBack; + + bool bIsInsideFrontFlipingZone; + bool bIsInsideBackFlipZone; + FVector CurInterpGripLoc; + + float TwistDiff; + FVector InitialGripToForwardVec; + FVector InitialForwardVector; + FVector EntryUpXYNeg; + FVector EntryUpVec; + FVector EntryRightVec; + + bool bFirstEntryToHalfFlipZone; + bool bLerpingOutOfFlipZone; + bool bIsFlipped; + + FPlane FlipPlane; + FPlane ForwardPullPlane; + FVector LastPointOnForwardPlane; + FVector CurPointOnForwardPlane; + + float LerpOutAlpha; + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Stiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float Damping; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + // Should be called after the Mount is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRMountComponent") + void ResetInitialMountLocation() + { + // Get our initial relative transform to our parent (or not if un-parented). + InitialRelativeTransform = this->GetRelativeTransform(); + } + + virtual void OnUnregister() override;; + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray<FBPGripPair>& CurHoldingControllers, bool& bCurIsHeld) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent* NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + +protected: + + + +}; + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRSliderComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRSliderComponent.h new file mode 100644 index 0000000..f0aaa1e --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Interactibles/VRSliderComponent.h @@ -0,0 +1,406 @@ +// Fill out your copyright notice in the Description page of Project Settings. +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameplayTagContainer.h" +#include "VRGripInterface.h" +#include "GameplayTagAssetInterface.h" +#include "Interactibles/VRInteractibleFunctionLibrary.h" +#include "Components/StaticMeshComponent.h" +#include "VRSliderComponent.generated.h" + +class UGripMotionControllerComponent; +class USplineComponent; + +UENUM(Blueprintable) +enum class EVRInteractibleSliderLerpType : uint8 +{ + Lerp_None, + Lerp_Interp, + Lerp_InterpConstantTo +}; + +UENUM(Blueprintable) +enum class EVRInteractibleSliderDropBehavior : uint8 +{ + /** Stays in place on drop */ + Stay, + + /** Retains momentum on release*/ + RetainMomentum +}; + +/** Delegate for notification when the slider state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRSliderHitPointSignature, float, SliderProgressPoint); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRSliderFinishedLerpingSignature, float, FinalProgress); + +/** +* A slider component, can act like a scroll bar, or gun bolt, or spline following component +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRSliderComponent : public UStaticMeshComponent, public IVRGripInterface, public IGameplayTagAssetInterface +{ + GENERATED_BODY() + +public: + UVRSliderComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRSliderComponent(); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRSliderComponent") + FVRSliderHitPointSignature OnSliderHitPoint; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Slider State Changed")) + void ReceiveSliderHitPoint(float SliderProgressPoint); + + UPROPERTY(BlueprintAssignable, Category = "VRSliderComponent") + FVRSliderFinishedLerpingSignature OnSliderFinishedLerping; + + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "Slider Finished Lerping")) + void ReceiveSliderFinishedLerping(float FinalProgress); + + // If true then this slider will only update in its tick event instead of normally using the controllers update event + // Keep in mind that you then must adjust the tick group in order to make sure it happens after the gripping controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bUpdateInTick; + bool bPassThrough; + + float LastSliderProgressState; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + FVector MaxSlideDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + FVector MinSlideDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + EVRInteractibleSliderDropBehavior SliderBehaviorWhenReleased; + + // Number of frames to average momentum across for the release momentum (avoids quick waggles) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0", ClampMax = "12", UIMin = "0", UIMax = "12")) + int FramesToAverage; + + // Units in % of total length per second to slow a momentum lerp down + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "10.0", UIMin = "0.0", UIMax = "10.0")) + float SliderMomentumFriction; + + // % of elasticity on reaching the end 0 - 1.0 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float SliderRestitution; + + // Maximum momentum of the slider in units of the total distance per second (0.0 - 1.0) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent|Momentum Settings", meta = (ClampMin = "0.0", UIMin = "0.0")) + float MaxSliderMomentum; + + UPROPERTY(BlueprintReadOnly, Category = "VRSliderComponent") + bool bIsLerping; + + // For momentum retention + FVector MomentumAtDrop; + FVector LastSliderProgress; + float SplineMomentumAtDrop; + float SplineLastSliderProgress; + + // Gets filled in with the current slider location progress + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "VRSliderComponent") + float CurrentSliderProgress; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bSlideDistanceIsInParentSpace; + + // If true then this slider is locked in place until unlocked again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bIsLocked; + + // If true then this slider will auto drop even when locked + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bAutoDropWhenLocked; + + // Sets if the slider is locked or not + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetIsLocked(bool bNewLockedState); + + // Uses the legacy slider logic that doesn't ABS the min and max values + // Retains compatibility with some older projects + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bUseLegacyLogic; + + // How far away from an event state before the slider allows throwing the same state again, default of 1.0 means it takes a full toggle + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float EventThrowThreshold; + bool bHitEventThreshold; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripSettings") + int GripPriority; + + // Sets the grip priority + UFUNCTION(BlueprintCallable, Category = "GripSettings") + void SetGripPriority(int NewGripPriority); + + // Set this to assign a spline component to the slider + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Replicated/*Using = OnRep_SplineComponentToFollow*/, Category = "VRSliderComponent") + TObjectPtr<USplineComponent> SplineComponentToFollow; + + /*UFUNCTION() + virtual void OnRep_SplineComponentToFollow() + { + CalculateSliderProgress(); + }*/ + + // Where the slider should follow the rotation and scale of the spline as well + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bFollowSplineRotationAndScale; + + // Does not allow the slider to skip past nodes on the spline, it requires it to progress from node to node + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bEnforceSplineLinearity; + float LastInputKey; + float LerpedKey; + + // Type of lerp to use when following a spline + // For lerping I would suggest using ConstantTo in general as it will be the smoothest. + // Normal Interp will change speed based on distance, that may also have its uses. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + EVRInteractibleSliderLerpType SplineLerpType; + + // Lerp Value for the spline, when in InterpMode it is the speed of interpolation + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (ClampMin = "0", UIMin = "0")) + float SplineLerpValue; + + // Uses snap increments to move between, not compatible with retain momentum. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent") + bool bSliderUsesSnapPoints; + + // Portion of the slider that the slider snaps to on release and when within the threshold distance + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (editcondition = "bSliderUsesSnapPoints", ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float SnapIncrement; + + // Threshold distance that when within the slider will stay snapped to its current snap increment + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (editcondition = "bSliderUsesSnapPoints", ClampMin = "0.0", ClampMax = "1.0", UIMin = "0.0", UIMax = "1.0")) + float SnapThreshold; + + // If true then the slider progress will keep incrementing between snap points if outside of the threshold + // However events will not be thrown + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRSliderComponent", meta = (editcondition = "bSliderUsesSnapPoints") ) + bool bIncrementProgressBetweenSnapPoints; + + + // Resetting the initial transform here so that it comes in prior to BeginPlay and save loading. + virtual void OnRegister() override; + + // Now replicating this so that it works correctly over the network + UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_InitialRelativeTransform, Category = "VRSliderComponent") + FTransform_NetQuantize InitialRelativeTransform; + + UFUNCTION() + virtual void OnRep_InitialRelativeTransform() + { + CalculateSliderProgress(); + } + + FVector InitialInteractorLocation; + FVector InitialGripLoc; + FVector InitialDropLocation; + + // Checks if we should throw some events + void CheckSliderProgress(); + + /* + Credit to: + https://github.com/Seurabimn + + Who had this function in an engine pull request: + https://github.com/EpicGames/UnrealEngine/pull/6646 + */ + float GetDistanceAlongSplineAtSplineInputKey(float InKey) const; + + + // Calculates the current slider progress + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + float CalculateSliderProgress(); + + // Forcefully sets the slider progress to the defined value + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + void SetSliderProgress(float NewSliderProgress); + + // Should be called after the slider is moved post begin play + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + void ResetInitialSliderLocation(); + + // Sets the spline component to follow, if empty, just reinitializes the transform and removes the follow component + UFUNCTION(BlueprintCallable, Category = "VRSliderComponent") + void SetSplineComponentToFollow(USplineComponent * SplineToFollow); + + void ResetToParentSplineLocation(); + + void GetLerpedKey(float &ClosestKey, float DeltaTime); + float GetCurrentSliderProgress(FVector CurLocation, bool bUseKeyInstead = false, float CurKey = 0.f); + FVector ClampSlideVector(FVector ValueToClamp); + + // ------------------------------------------------ + // Gameplay tag interface + // ------------------------------------------------ + + /** Overridden to return requirements tags */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override + { + TagContainer = GameplayTags; + } + + /** Tags that are set on this object */ + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "GameplayTags") + FGameplayTagContainer GameplayTags; + + // End Gameplay Tag Interface + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + + // Requires bReplicates to be true for the component + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface") + bool bRepGameplayTags; + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRGripInterface|Replication") + bool bReplicateMovement; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void BeginPlay() override; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationSetting; + + // Distance before the object will break out of the hand, 0.0f == never will + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float BreakDistance; + + // Checks and applies auto drop if we need too + bool CheckAutoDrop(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation); + + // Should we deny gripping on this object + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface", meta = (ScriptName = "IsDenyGripping")) + bool bDenyGripping; + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface", meta = (ScriptName = "IsCurrentlyHeld")) + bool bIsHeld; // Set on grip notify, not net serializing + + UPROPERTY(BlueprintReadOnly, Category = "VRGripInterface") + FBPGripPair HoldingGrip; // Set on grip notify, not net serializing + bool bOriginalReplicatesMovement; + + // Called when a object is gripped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnGripSignature OnGripped; + + // Called when a object is dropped + UPROPERTY(BlueprintAssignable, Category = "Grip Events") + FVROnDropSignature OnDropped; + + // Grip interface setup + + // Set up as deny instead of allow so that default allows for gripping + // The GripInitiator is not guaranteed to be valid, check it for validity + bool DenyGripping_Implementation(UGripMotionControllerComponent * GripInitiator = nullptr) override; + + // How an interfaced object behaves when teleporting + EGripInterfaceTeleportBehavior TeleportBehavior_Implementation() override; + + // Should this object simulate on drop + bool SimulateOnDrop_Implementation() override; + + // Grip type to use + EGripCollisionType GetPrimaryGripType_Implementation(bool bIsSlot) override; + + // Secondary grip type + ESecondaryGripType SecondaryGripType_Implementation() override; + + // Define which movement repliation setting to use + EGripMovementReplicationSettings GripMovementReplicationType_Implementation() override; + + // Define the late update setting + EGripLateUpdateSettings GripLateUpdateSetting_Implementation() override; + + // What grip stiffness and damping to use if using a physics constraint + void GetGripStiffnessAndDamping_Implementation(float& GripStiffnessOut, float& GripDampingOut) override; + + // Get the advanced physics settings for this grip + FBPAdvGripSettings AdvancedGripSettings_Implementation() override; + + // What distance to break a grip at (only relevent with physics enabled grips + float GripBreakDistance_Implementation() override; + + // Get grip slot in range + void ClosestGripSlotInRange_Implementation(FVector WorldLocation, bool bSecondarySlot, bool& bHadSlotInRange, FTransform& SlotWorldTransform, FName& SlotName, UGripMotionControllerComponent* CallingController = nullptr, FName OverridePrefix = NAME_None) override; + + // Check if an object allows multiple grips at one time + bool AllowsMultipleGrips_Implementation() override; + + // Returns if the object is held and if so, which controllers are holding it + void IsHeld_Implementation(TArray<FBPGripPair>& CurHoldingControllers, bool& bCurIsHeld) override; + + // Interface function used to throw the delegates that is invisible to blueprints so that it can't be overridden + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Sets is held, used by the plugin + void SetHeld_Implementation(UGripMotionControllerComponent* NewHoldingController, uint8 GripID, bool bNewIsHeld) override; + + // Returns if the object wants to be socketed + bool RequestsSocketing_Implementation(USceneComponent*& ParentToSocketTo, FName& OptionalSocketName, FTransform_NetQuantize& RelativeTransform) override; + + // Get grip scripts + bool GetGripScripts_Implementation(TArray<UVRGripScriptBase*>& ArrayReference) override; + + + // Events // + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + void TickGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation, float DeltaTime) override; + + // Event triggered on the interfaced object when gripped + void OnGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when grip is released + void OnGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when child component is gripped + void OnChildGrip_Implementation(UGripMotionControllerComponent* GrippingController, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when child component is released + void OnChildGripRelease_Implementation(UGripMotionControllerComponent* ReleasingController, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false) override; + + // Event triggered on the interfaced object when secondary gripped + void OnSecondaryGrip_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* SecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Event triggered on the interfaced object when secondary grip is released + void OnSecondaryGripRelease_Implementation(UGripMotionControllerComponent* GripOwningController, USceneComponent* ReleasingSecondaryGripComponent, const FBPActorGripInformation& GripInformation) override; + + // Interaction Functions + + // Call to use an object + void OnUsed_Implementation() override; + + // Call to stop using an object + void OnEndUsed_Implementation() override; + + // Call to use an object + void OnSecondaryUsed_Implementation() override; + + // Call to stop using an object + void OnEndSecondaryUsed_Implementation() override; + + // Call to send an action event to the object + void OnInput_Implementation(FKey Key, EInputEvent KeyEvent) override; + + protected: + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/BucketUpdateSubsystem.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/BucketUpdateSubsystem.h new file mode 100644 index 0000000..4dacd14 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/BucketUpdateSubsystem.h @@ -0,0 +1,169 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "Tickable.h" +#include "BucketUpdateSubsystem.generated.h" +//#include "GrippablePhysicsReplication.generated.h" + + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPhysicsReplicationDelegate, void, Return); + + +DECLARE_DELEGATE_RetVal(bool, FBucketUpdateTickSignature); +DECLARE_DYNAMIC_DELEGATE(FDynamicBucketUpdateTickSignature); + +USTRUCT() +struct VREXPANSIONPLUGIN_API FUpdateBucketDrop +{ + GENERATED_BODY() +public: + FBucketUpdateTickSignature NativeCallback; + FDynamicBucketUpdateTickSignature DynamicCallback; + + FName FunctionName; + + bool ExecuteBoundCallback(); + bool IsBoundToObjectFunction(UObject * Obj, FName & FuncName); + bool IsBoundToObjectDelegate(FDynamicBucketUpdateTickSignature & DynEvent); + bool IsBoundToObject(UObject * Obj); + FUpdateBucketDrop(); + FUpdateBucketDrop(FDynamicBucketUpdateTickSignature & DynCallback); + FUpdateBucketDrop(UObject * Obj, FName FuncName); +}; + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FUpdateBucket +{ + GENERATED_BODY() + +public: + + float nUpdateRate; + float nUpdateCount; + + TArray<FUpdateBucketDrop> Callbacks; + + bool Update(float DeltaTime); + + FUpdateBucket() {} + + FUpdateBucket(uint32 UpdateHTZ) : + nUpdateRate(1.0f / UpdateHTZ), + nUpdateCount(0.0f) + { + } +}; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FUpdateBucketContainer +{ + GENERATED_BODY() +public: + + + bool bNeedsUpdate; + TMap<uint32, FUpdateBucket> ReplicationBuckets; + + void UpdateBuckets(float DeltaTime); + + bool AddBucketObject(uint32 UpdateHTZ, UObject* InObject, FName FunctionName); + bool AddBucketObject(uint32 UpdateHTZ, FDynamicBucketUpdateTickSignature &Delegate); + + /* + template<typename classType> + bool AddReplicatingObject(uint32 UpdateHTZ, classType* InObject, void(classType::* _Func)()) + { + } + */ + + bool RemoveBucketObject(UObject * ObjectToRemove, FName FunctionName); + bool RemoveBucketObject(FDynamicBucketUpdateTickSignature &DynEvent); + bool RemoveObjectFromAllBuckets(UObject * ObjectToRemove); + + bool IsObjectInBucket(UObject * ObjectToRemove); + bool IsObjectFunctionInBucket(UObject * ObjectToRemove, FName FunctionName); + bool IsObjectDelegateInBucket(FDynamicBucketUpdateTickSignature &DynEvent); + + FUpdateBucketContainer() + { + bNeedsUpdate = false; + }; + +}; + +UCLASS() +class VREXPANSIONPLUGIN_API UBucketUpdateSubsystem : public UTickableWorldSubsystem +{ + GENERATED_BODY() + +public: + UBucketUpdateSubsystem() : + Super() + { + + } + + virtual bool DoesSupportWorldType(EWorldType::Type WorldType) const override + { + return WorldType == EWorldType::Game || WorldType == EWorldType::PIE; + // Not allowing for editor type as this is a replication subsystem + } + + //UPROPERTY() + FUpdateBucketContainer BucketContainer; + + // Adds an object to an update bucket with the set HTZ, calls the passed in UFUNCTION name + // If one of the bucket contains an entry with the function already then the existing one is removed and the new one is added + bool AddObjectToBucket(int32 UpdateHTZ, UObject* InObject, FName FunctionName); + + // Adds an object to an update bucket with the set HTZ, calls the passed in UFUNCTION name + // If one of the bucket contains an entry with the function already then the existing one is removed and the new one is added + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Object to Bucket Updates", ScriptName = "AddObjectToBucket"), Category = "BucketUpdateSubsystem") + bool K2_AddObjectToBucket(int32 UpdateHTZ = 100, UObject* InObject = nullptr, FName FunctionName = NAME_None); + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Object to Bucket Updates by Event", ScriptName = "AddBucketObjectEvent"), Category = "BucketUpdateSubsystem") + bool K2_AddObjectEventToBucket(UPARAM(DisplayName = "Event") FDynamicBucketUpdateTickSignature Delegate, int32 UpdateHTZ = 100); + + // Remove the entry in the bucket updates with the passed in UFUNCTION name + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Object From Bucket Updates By Function", ScriptName = "RemoveObjectFromBucketByFunction"), Category = "BucketUpdateSubsystem") + bool RemoveObjectFromBucketByFunctionName(UObject* InObject = nullptr, FName FunctionName = NAME_None); + + // Remove the entry in the bucket updates with the passed in UFUNCTION name + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Object From Bucket Updates By Event", ScriptName = "RemoveObjectFromBucketByEvent"), Category = "BucketUpdateSubsystem") + bool RemoveObjectFromBucketByEvent(UPARAM(DisplayName = "Event") FDynamicBucketUpdateTickSignature Delegate); + + // Removes ALL entries in the bucket update system with the specified object + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Object From All Bucket Updates", ScriptName = "RemoveObjectFromAllBuckets"), Category = "BucketUpdateSubsystem") + bool RemoveObjectFromAllBuckets(UObject* InObject = nullptr); + + // Returns if an update bucket contains an entry with the passed in function + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is Object In Bucket", ScriptName = "IsObjectInBucket"), Category = "BucketUpdateSubsystem") + bool IsObjectFunctionInBucket(UObject* InObject = nullptr, FName FunctionName = NAME_None); + + // Returns if an update bucket contains an entry with the passed in function + UFUNCTION(BlueprintPure, Category = "BucketUpdateSubsystem") + bool IsActive(); + + // FTickableGameObject functions + /** + * Function called every frame on this GripScript. Override this function to implement custom logic to be executed every frame. + * Only executes if bCanEverTick is true and bAllowTicking is true + * + * @param DeltaTime - The time since the last tick. + */ + + virtual void Tick(float DeltaTime) override; + virtual bool IsTickable() const override; + virtual UWorld* GetTickableGameObjectWorld() const override; + virtual bool IsTickableInEditor() const; + virtual bool IsTickableWhenPaused() const override; + virtual ETickableTickType GetTickableTickType() const; + virtual TStatId GetStatId() const override; + + // End tickable object information + +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/CollisionIgnoreSubsystem.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/CollisionIgnoreSubsystem.h new file mode 100644 index 0000000..e4a8f1f --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/CollisionIgnoreSubsystem.h @@ -0,0 +1,181 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "TimerManager.h" +#include "Components/PrimitiveComponent.h" +//#include "Grippables/GrippablePhysicsReplication.h" +#include "CollisionIgnoreSubsystem.generated.h" +//#include "GrippablePhysicsReplication.generated.h" + + +DECLARE_LOG_CATEGORY_EXTERN(VRE_CollisionIgnoreLog, Log, All); + +USTRUCT() +struct FCollisionPrimPair +{ + GENERATED_BODY() +public: + + UPROPERTY() + TObjectPtr<UPrimitiveComponent> Prim1; + UPROPERTY() + TObjectPtr<UPrimitiveComponent> Prim2; + + FCollisionPrimPair() + { + Prim1 = nullptr; + Prim2 = nullptr; + } + + FORCEINLINE bool operator==(const FCollisionPrimPair& Other) const + { + /*if (!Prim1.IsValid() || !Prim2.IsValid()) + return false; + + if (!Other.Prim1.IsValid() || !Other.Prim2.IsValid()) + return false;*/ + + return( + (Prim1.Get() == Other.Prim1.Get() || Prim1.Get() == Other.Prim2.Get()) && + (Prim2.Get() == Other.Prim1.Get() || Prim2.Get() == Other.Prim2.Get()) + ); + } + + friend uint32 GetTypeHash(const FCollisionPrimPair& InKey) + { + return GetTypeHash(InKey.Prim1) ^ GetTypeHash(InKey.Prim2); + } + +}; + +USTRUCT() +struct FCollisionIgnorePair +{ + GENERATED_BODY() +public: + + ///FCollisionPrimPair PrimitivePair; + FPhysicsActorHandle Actor1; + UPROPERTY() + FName BoneName1; + FPhysicsActorHandle Actor2; + UPROPERTY() + FName BoneName2; + + // Flip our elements to retain a default ordering in an array + void FlipElements() + { + FPhysicsActorHandle tH = Actor1; + Actor1 = Actor2; + Actor2 = tH; + + FName tN = BoneName1; + BoneName1 = BoneName2; + BoneName2 = tN; + } + + FORCEINLINE bool operator==(const FCollisionIgnorePair& Other) const + { + return ( + (BoneName1 == Other.BoneName1 || BoneName1 == Other.BoneName2) && + (BoneName2 == Other.BoneName2 || BoneName2 == Other.BoneName1) + ); + } + + FORCEINLINE bool operator==(const FName& Other) const + { + return (BoneName1 == Other || BoneName2 == Other); + } +}; + +USTRUCT() +struct FCollisionIgnorePairArray +{ + GENERATED_BODY() +public: + + UPROPERTY() + TArray<FCollisionIgnorePair> PairArray; +}; + +UCLASS() +class VREXPANSIONPLUGIN_API UCollisionIgnoreSubsystem : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + + + UCollisionIgnoreSubsystem() : + Super() + { + } + + virtual bool DoesSupportWorldType(EWorldType::Type WorldType) const override + { + return WorldType == EWorldType::Game || WorldType == EWorldType::PIE; + // Not allowing for editor type as this is a replication subsystem + } + + /** Implement this for initialization of instances of the system */ + virtual void Initialize(FSubsystemCollectionBase& Collection) override + { + Super::Initialize(Collection); + } + + virtual void Deinitialize() override + { + Super::Deinitialize(); + + if (UpdateHandle.IsValid()) + { + GetWorld()->GetTimerManager().ClearTimer(UpdateHandle); + } + } + + UPROPERTY() + TMap<FCollisionPrimPair, FCollisionIgnorePairArray> CollisionTrackedPairs; + //TArray<FCollisionIgnorePair> CollisionTrackedPairs; + + UPROPERTY() + TMap<FCollisionPrimPair, FCollisionIgnorePairArray> RemovedPairs; + //TArray<FCollisionIgnorePair> RemovedPairs; + + // + void UpdateTimer() + { + RemovedPairs.Reset(); + + if (CollisionTrackedPairs.Num() > 0) + { + if (!UpdateHandle.IsValid()) + { + // Setup the heartbeat on 1htz checks + GetWorld()->GetTimerManager().SetTimer(UpdateHandle, this, &UCollisionIgnoreSubsystem::CheckActiveFilters, 1.0f, true, 1.0f); + } + } + else if (UpdateHandle.IsValid()) + { + GetWorld()->GetTimerManager().ClearTimer(UpdateHandle); + } + } + + UFUNCTION(Category = "Collision") + void CheckActiveFilters(); + + // #TODO implement this, though it should be rare + void InitiateIgnore(); + + void SetComponentCollisionIgnoreState(bool bIterateChildren1, bool bIterateChildren2, UPrimitiveComponent* Prim1, FName OptionalBoneName1, UPrimitiveComponent* Prim2, FName OptionalBoneName2, bool bIgnoreCollision, bool bCheckFilters = false); + void RemoveComponentCollisionIgnoreState(UPrimitiveComponent* Prim1); + bool IsComponentIgnoringCollision(UPrimitiveComponent* Prim1); + bool AreComponentsIgnoringCollisions(UPrimitiveComponent* Prim1, UPrimitiveComponent* Prim2); + bool HasCollisionIgnorePairs(); +private: + + FTimerHandle UpdateHandle; + +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/OptionalRepSkeletalMeshActor.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/OptionalRepSkeletalMeshActor.h new file mode 100644 index 0000000..c8b72e1 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/OptionalRepSkeletalMeshActor.h @@ -0,0 +1,128 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GripMotionControllerComponent.h" +//#include "Engine/Engine.h" +#include "Animation/SkeletalMeshActor.h" +#include "Components/SkeletalMeshComponent.h" +#include "Components/SphereComponent.h" +#include "Engine/ActorChannel.h" +#include "OptionalRepSkeletalMeshActor.generated.h" + +// Temp comp to avoid some engine issues, exists only until a bug fix happens +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UNoRepSphereComponent : public USphereComponent +{ + GENERATED_BODY() + +public: + UNoRepSphereComponent(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + virtual void PreReplication(IRepChangedPropertyTracker& ChangedPropertyTracker) override; +}; + +/** +* A component specifically for being able to turn off movement replication in the component at will +* Has the upside of also being a blueprintable base since UE4 doesn't allow that with std ones +*/ + +USTRUCT() +struct FSkeletalMeshComponentEndPhysicsTickFunctionVR : public FTickFunction +{ + GENERATED_USTRUCT_BODY() + + UInversePhysicsSkeletalMeshComponent* TargetVR; + + virtual void ExecuteTick(float DeltaTime, enum ELevelTick TickType, ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent) override; + + virtual FString DiagnosticMessage() override; + + virtual FName DiagnosticContext(bool bDetailed) override; + +}; +template<> +struct TStructOpsTypeTraits<FSkeletalMeshComponentEndPhysicsTickFunctionVR> : public TStructOpsTypeTraitsBase2<FSkeletalMeshComponentEndPhysicsTickFunctionVR> +{ + enum + { + WithCopy = false + }; +}; + +// A base skeletal mesh component that has been added to temp correct an engine bug with inversed scale and physics +UCLASS(Blueprintable, meta = (ChildCanTick, BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UInversePhysicsSkeletalMeshComponent : public USkeletalMeshComponent +{ + GENERATED_BODY() + +public: + UInversePhysicsSkeletalMeshComponent(const FObjectInitializer& ObjectInitializer); + +public: + + // Overrides the default of : true and allows for controlling it like in an actor, should be default of off normally with grippable components + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Component Replication") + bool bReplicateMovement; + + // This is all overrides to fix the skeletal mesh inverse simulation bug + // WILL BE REMOVED LATER when the engine is fixed + FSkeletalMeshComponentEndPhysicsTickFunctionVR EndPhysicsTickFunctionVR; + friend struct FSkeletalMeshComponentEndPhysicsTickFunctionVR; + void EndPhysicsTickComponentVR(FSkeletalMeshComponentEndPhysicsTickFunctionVR& ThisTickFunction); + void BlendInPhysicsInternalVR(FTickFunction& ThisTickFunction); + void FinalizeAnimationUpdateVR(); + + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override + { + // Get rid of inverse issues + FTransform newLocalToWorld = LocalToWorld; + newLocalToWorld.SetScale3D(newLocalToWorld.GetScale3D().GetAbs()); + + return Super::CalcBounds(newLocalToWorld); + } + + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions") + FBoxSphereBounds GetLocalBounds() const + { + return this->GetCachedLocalBounds(); + } + + void PerformBlendPhysicsBonesVR(const TArray<FBoneIndexType>& InRequiredBones, TArray<FTransform>& InOutComponentSpaceTransforms, TArray<FTransform>& InOutBoneSpaceTransforms); + virtual void RegisterEndPhysicsTick(bool bRegister) override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + // END INVERSED MESH FIX + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + +}; + +/** +* +* A class specifically for turning off default physics replication with a skeletal mesh and fixing an +* engine bug with inversed scale on skeletal meshes. Generally used for the physical hand setup. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, ChildCanTick), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API AOptionalRepGrippableSkeletalMeshActor : public ASkeletalMeshActor +{ + GENERATED_BODY() + +public: + AOptionalRepGrippableSkeletalMeshActor(const FObjectInitializer& ObjectInitializer); + + // Skips the attachment replication + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bIgnoreAttachmentReplication; + + // Skips the physics replication + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "Replication") + bool bIgnorePhysicsReplication; + + // Fix bugs with replication and bReplicateMovement + virtual void OnRep_ReplicateMovement() override; + virtual void PostNetReceivePhysicState() override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRAIPerceptionOverrides.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRAIPerceptionOverrides.h new file mode 100644 index 0000000..c63185a --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRAIPerceptionOverrides.h @@ -0,0 +1,286 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "VRBaseCharacter.h" +#include "AIModule/Classes/GenericTeamAgentInterface.h" +#include "AIModule/Classes/Perception/AISense.h" +#include "AIModule/Classes/Perception/AISenseConfig.h" + +#include "VRAIPerceptionOverrides.generated.h" + +class IAISightTargetInterface; +class UAISense_Sight_VR; + +class FGameplayDebuggerCategory; +class UAIPerceptionComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogAIPerceptionVR, Warning, All); + +UCLASS(meta = (DisplayName = "AI Sight VR config")) +class VREXPANSIONPLUGIN_API UAISenseConfig_Sight_VR : public UAISenseConfig +{ + GENERATED_UCLASS_BODY() + +public: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", NoClear, config) + TSubclassOf<UAISense_Sight_VR> Implementation; + + /** Maximum sight distance to notice a target. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0)) + float SightRadius; + + /** Maximum sight distance to see target that has been already seen. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0)) + float LoseSightRadius; + + /** How far to the side AI can see, in degrees. Use SetPeripheralVisionAngle to change the value at runtime. + * The value represents the angle measured in relation to the forward vector, not the whole range. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0.0, ClampMin = 0.0, UIMax = 180.0, ClampMax = 180.0, DisplayName = "PeripheralVisionHalfAngleDegrees")) + float PeripheralVisionAngleDegrees; + + /** */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config) + FAISenseAffiliationFilter DetectionByAffiliation; + + /** If not an InvalidRange (which is the default), we will always be able to see the target that has already been seen if they are within this range of their last seen location. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config) + float AutoSuccessRangeFromLastSeenLocation; + + virtual TSubclassOf<UAISense> GetSenseImplementation() const override; + +#if WITH_EDITOR + virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif // WITH_EDITOR + +#if WITH_GAMEPLAY_DEBUGGER + virtual void DescribeSelfToGameplayDebugger(const UAIPerceptionComponent* PerceptionComponent, FGameplayDebuggerCategory* DebuggerCategory) const; +#endif // WITH_GAMEPLAY_DEBUGGER + +}; + + +namespace ESightPerceptionEventNameVR +{ + enum Type + { + Undefined, + GainedSight, + LostSight + }; +} + +USTRUCT() +struct VREXPANSIONPLUGIN_API FAISightEventVR +{ + GENERATED_USTRUCT_BODY() + + typedef UAISense_Sight_VR FSenseClass; + + float Age; + ESightPerceptionEventNameVR::Type EventType; + + UPROPERTY() + TObjectPtr<AActor> SeenActor; + + UPROPERTY() + TObjectPtr<AActor> Observer; + + FAISightEventVR() : SeenActor(nullptr), Observer(nullptr) {} + + FAISightEventVR(AActor* InSeenActor, AActor* InObserver, ESightPerceptionEventNameVR::Type InEventType) + : Age(0.f), EventType(InEventType), SeenActor(InSeenActor), Observer(InObserver) + { + } +}; + +struct FAISightTargetVR +{ + typedef uint32 FTargetId; + static const FTargetId InvalidTargetId; + + TWeakObjectPtr<AActor> Target; + IAISightTargetInterface* SightTargetInterface; + FGenericTeamId TeamId; + FTargetId TargetId; + + FAISightTargetVR(AActor* InTarget = NULL, FGenericTeamId InTeamId = FGenericTeamId::NoTeam); + + FORCEINLINE FVector GetLocationSimple() const + { + // Changed this up to support my VR Characters + const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(Target); + return Target.IsValid() ? (VRChar != nullptr ? VRChar->GetVRLocation_Inline() : Target->GetActorLocation()) : FVector::ZeroVector; + } + + FORCEINLINE const AActor* GetTargetActor() const { return Target.Get(); } +}; + +struct FAISightQueryVR +{ + FPerceptionListenerID ObserverId; + FAISightTargetVR::FTargetId TargetId; + + float Score; + float Importance; + + FVector LastSeenLocation; + + /** User data that can be used inside the IAISightTargetInterface::TestVisibilityFrom method to store a persistence state */ + mutable int32 UserData; + + uint64 bLastResult : 1; + uint64 LastProcessedFrameNumber : 63; + + FAISightQueryVR(FPerceptionListenerID ListenerId = FPerceptionListenerID::InvalidID(), FAISightTargetVR::FTargetId Target = FAISightTargetVR::InvalidTargetId) + : ObserverId(ListenerId), TargetId(Target), Score(0), Importance(0), LastSeenLocation(FAISystem::InvalidLocation), UserData(0), bLastResult(false), LastProcessedFrameNumber(GFrameCounter) + { + } + + float GetAge() const + { + return (float)(GFrameCounter - LastProcessedFrameNumber); + } + + void RecalcScore() + { + Score = GetAge() + Importance; + } + + void OnProcessed() + { + LastProcessedFrameNumber = GFrameCounter; + } + + void ForgetPreviousResult() + { + LastSeenLocation = FAISystem::InvalidLocation; + bLastResult = false; + } + + class FSortPredicate + { + public: + FSortPredicate() + {} + + bool operator()(const FAISightQueryVR& A, const FAISightQueryVR& B) const + { + return A.Score > B.Score; + } + }; +}; + +UCLASS(ClassGroup = AI, config = Game) +class VREXPANSIONPLUGIN_API UAISense_Sight_VR : public UAISense +{ + GENERATED_UCLASS_BODY() + +public: + struct FDigestedSightProperties + { + float PeripheralVisionAngleCos; + float SightRadiusSq; + float AutoSuccessRangeSqFromLastSeenLocation; + float LoseSightRadiusSq; + uint8 AffiliationFlags; + + FDigestedSightProperties(); + FDigestedSightProperties(const UAISenseConfig_Sight_VR& SenseConfig); + }; + + typedef TMap<FAISightTargetVR::FTargetId, FAISightTargetVR> FTargetsContainer; + FTargetsContainer ObservedTargets; + TMap<FPerceptionListenerID, FDigestedSightProperties> DigestedProperties; + + /** The SightQueries are a n^2 problem and to reduce the sort time, they are now split between in range and out of range */ + /** Since the out of range queries only age as the distance component of the score is always 0, there is few need to sort them */ + /** In the majority of the cases most of the queries are out of range, so the sort time is greatly reduced as we only sort the in range queries */ + int32 NextOutOfRangeIndex = 0; + bool bSightQueriesOutOfRangeDirty = true; + TArray<FAISightQueryVR> SightQueriesOutOfRange; + TArray<FAISightQueryVR> SightQueriesInRange; + +protected: + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + int32 MaxTracesPerTick; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + int32 MinQueriesPerTimeSliceCheck; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + double MaxTimeSlicePerTick; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + float HighImportanceQueryDistanceThreshold; + + float HighImportanceDistanceSquare; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + float MaxQueryImportance; + + UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) + float SightLimitQueryImportance; + + ECollisionChannel DefaultSightCollisionChannel; + +public: + + virtual void PostInitProperties() override; + + void RegisterEvent(const FAISightEventVR& Event); + + virtual void RegisterSource(AActor& SourceActors) override; + virtual void UnregisterSource(AActor& SourceActor) override; + + virtual void OnListenerForgetsActor(const FPerceptionListener& Listener, AActor& ActorToForget) override; + virtual void OnListenerForgetsAll(const FPerceptionListener& Listener) override; + +protected: + virtual float Update() override; + + virtual bool ShouldAutomaticallySeeTarget(const FDigestedSightProperties& PropDigest, FAISightQueryVR* SightQuery, FPerceptionListener& Listener, AActor* TargetActor, float& OutStimulusStrength) const; + + void OnNewListenerImpl(const FPerceptionListener& NewListener); + void OnListenerUpdateImpl(const FPerceptionListener& UpdatedListener); + void OnListenerRemovedImpl(const FPerceptionListener& RemovedListener); + virtual void OnListenerConfigUpdated(const FPerceptionListener& UpdatedListener) override; + + void GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, const TFunction<void(FAISightQueryVR&)>& OnAddedFunc = nullptr); + + + void RemoveAllQueriesByListener(const FPerceptionListener& Listener, const TFunction<void(const FAISightQueryVR&)>& OnRemoveFunc = nullptr); + void RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId& TargetId, const TFunction<void(const FAISightQueryVR&)>& OnRemoveFunc = nullptr); + + /** returns information whether new LoS queries have been added */ + bool RegisterTarget(AActor& TargetActor, const TFunction<void(FAISightQueryVR&)>& OnAddedFunc = nullptr); + + float CalcQueryImportance(const FPerceptionListener& Listener, const FVector& TargetLocation, const float SightRadiusSq) const; + + // Deprecated methods +public: + UE_DEPRECATED(4.25, "Not needed anymore done automatically at the beginning of each update.") + FORCEINLINE void SortQueries() {} + + enum FQueriesOperationPostProcess + { + DontSort, + Sort + }; + UE_DEPRECATED(4.25, "Use RemoveAllQueriesByListener without unneeded PostProcess parameter.") + void RemoveAllQueriesByListener(const FPerceptionListener& Listener, FQueriesOperationPostProcess PostProcess) { RemoveAllQueriesByListener(Listener); } + UE_DEPRECATED(4.25, "Use RemoveAllQueriesByListener without unneeded PostProcess parameter.") + void RemoveAllQueriesByListener(const FPerceptionListener& Listener, FQueriesOperationPostProcess PostProcess, TFunctionRef<void(const FAISightQueryVR&)> OnRemoveFunc) { RemoveAllQueriesByListener(Listener, [&](const FAISightQueryVR& query) { OnRemoveFunc(query); }); } + UE_DEPRECATED(4.25, "Use RemoveAllQueriesToTarget without unneeded PostProcess parameter.") + void RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId& TargetId, FQueriesOperationPostProcess PostProcess) { RemoveAllQueriesToTarget(TargetId); } + UE_DEPRECATED(4.25, "Use RemoveAllQueriesToTarget without unneeded PostProcess parameter.") + void RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId& TargetId, FQueriesOperationPostProcess PostProcess, TFunctionRef<void(const FAISightQueryVR&)> OnRemoveFunc) { RemoveAllQueriesToTarget(TargetId, [&](const FAISightQueryVR& query) { OnRemoveFunc(query); }); } + + + UE_DEPRECATED(4.25, "Use RegisterTarget without unneeded PostProcess parameter.") + bool RegisterTarget(AActor& TargetActor, FQueriesOperationPostProcess PostProcess) { return RegisterTarget(TargetActor); } + UE_DEPRECATED(4.25, "Use RegisterTarget without unneeded PostProcess parameter.") + bool RegisterTarget(AActor& TargetActor, FQueriesOperationPostProcess PostProcess, TFunctionRef<void(FAISightQueryVR&)> OnAddedFunc) { return RegisterTarget(TargetActor, [&](FAISightQueryVR& query) { OnAddedFunc(query); }); } + +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicalAnimationComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicalAnimationComponent.h new file mode 100644 index 0000000..170a3ce --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicalAnimationComponent.h @@ -0,0 +1,89 @@ +#pragma once + +#include "PhysicsEngine/PhysicalAnimationComponent.h" +#include "CoreMinimal.h" +//#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "EngineDefines.h" +#include "VREPhysicalAnimationComponent.generated.h" + +struct FReferenceSkeleton; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FWeldedBoneDriverData +{ + GENERATED_BODY() +public: + FTransform RelativeTransform; + FName BoneName; + FPhysicsShapeHandle ShapeHandle; + + FTransform LastLocal; + + FWeldedBoneDriverData() : + RelativeTransform(FTransform::Identity), + BoneName(NAME_None) + { + } + + FORCEINLINE bool operator==(const FPhysicsShapeHandle& Other) const + { + return (ShapeHandle == Other); + } +}; + +UCLASS(meta = (BlueprintSpawnableComponent), ClassGroup = Physics) +class VREXPANSIONPLUGIN_API UVREPhysicalAnimationComponent : public UPhysicalAnimationComponent +{ + GENERATED_UCLASS_BODY() + +public: + + /** Is the welded bone driver paused */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + bool bIsPaused; + + /** IF true then we will auto adjust the sleep settings of the body so that it won't rest during welded bone driving */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + bool bAutoSetPhysicsSleepSensitivity; + + /** The threshold multiplier to set on the body */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + float SleepThresholdMultiplier; + + /** The Base bone to use as the bone driver root */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = WeldedBoneDriver) + TArray<FName> BaseWeldedBoneDriverNames; + + UPROPERTY() + TArray<FWeldedBoneDriverData> BoneDriverMap; + + // Call to setup the welded body driver, initializes all mappings and caches shape contexts + // Requires that SetSkeletalMesh be called first + UFUNCTION(BlueprintCallable, Category = PhysicalAnimation) + void SetupWeldedBoneDriver(TArray<FName> BaseBoneNames); + + // Refreshes the welded bone driver, use this in cases where the body may have changed (such as welding to another body or switching physics) + UFUNCTION(BlueprintCallable, Category = PhysicalAnimation) + void RefreshWeldedBoneDriver(); + + // Sets the welded bone driver to be paused, you can also stop the component from ticking but that will also stop any physical animations going on + UFUNCTION(BlueprintCallable, Category = PhysicalAnimation) + void SetWeldedBoneDriverPaused(bool bPaused); + + UFUNCTION(BlueprintPure, Category = PhysicalAnimation) + bool IsWeldedBoneDriverPaused(); + + void SetupWeldedBoneDriver_Implementation(bool bReInit = false); + + //void OnWeldedMassUpdated(FBodyInstance* BodyInstance); + void UpdateWeldedBoneDriver(float DeltaTime); + + FTransform GetWorldSpaceRefBoneTransform(FReferenceSkeleton& RefSkel, int32 BoneIndex, int32 ParentBoneIndex); + FTransform GetRefPoseBoneRelativeTransform(USkeletalMeshComponent* SkeleMesh, FName BoneName, FName ParentBoneName); + + //FCalculateCustomPhysics OnCalculateCustomPhysics; + //void CustomPhysics(float DeltaTime, FBodyInstance* BodyInstance); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicsConstraintComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicsConstraintComponent.h new file mode 100644 index 0000000..386f7ee --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VREPhysicsConstraintComponent.h @@ -0,0 +1,180 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "PhysicsEngine/PhysicsConstraintComponent.h" + +// Delete this eventually when the physics interface is fixed +#if WITH_CHAOS +#include "Chaos/ParticleHandle.h" +#include "Chaos/KinematicGeometryParticles.h" +#include "Chaos/PBDJointConstraintTypes.h" +#include "Chaos/PBDJointConstraintData.h" +#include "Chaos/Sphere.h" +#include "PhysicsProxy/SingleParticlePhysicsProxy.h" +#endif + +#include "VREPhysicsConstraintComponent.generated.h" + +/** +* A custom constraint component subclass that exposes additional missing functionality from the default one +*/ +UCLASS(ClassGroup = Physics, meta = (BlueprintSpawnableComponent), HideCategories = (Activation, "Components|Activation", Physics, Mobility), ShowCategories = ("Physics|Components|PhysicsConstraint", "VRE Constraint Settings")) +class VREXPANSIONPLUGIN_API UVREPhysicsConstraintComponent : public UPhysicsConstraintComponent +{ + + GENERATED_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + void SetConstraintToForceBased(bool bUseForceConstraint) + { +#if WITH_CHAOS + + if (!ConstraintInstance.ConstraintHandle.IsValid()) + return; + + if (ConstraintInstance.ConstraintHandle->IsType(Chaos::EConstraintType::JointConstraintType)) + { + if (Chaos::FJointConstraint* Constraint = static_cast<Chaos::FJointConstraint*>(ConstraintInstance.ConstraintHandle.Constraint)) + { + Constraint->SetLinearDriveForceMode(bUseForceConstraint ? Chaos::EJointForceMode::Force : Chaos::EJointForceMode::Acceleration); + Constraint->SetAngularDriveForceMode(bUseForceConstraint ? Chaos::EJointForceMode::Force : Chaos::EJointForceMode::Acceleration); + } + } + +#endif + //#endif + } + + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + void GetConstraintReferenceFrame(EConstraintFrame::Type Frame, FTransform& RefFrame) + { + RefFrame = ConstraintInstance.GetRefFrame(Frame); + } + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + FTransform GetLocalPose(EConstraintFrame::Type ConstraintFrame) + { + if (ConstraintInstance.IsValidConstraintInstance()) + { + if (ConstraintFrame == EConstraintFrame::Frame1) + { + return FTransform(ConstraintInstance.PriAxis1, ConstraintInstance.SecAxis1, ConstraintInstance.PriAxis1 ^ ConstraintInstance.SecAxis1, ConstraintInstance.Pos1); + } + else + { + return FTransform(ConstraintInstance.PriAxis2, ConstraintInstance.SecAxis2, ConstraintInstance.PriAxis2 ^ ConstraintInstance.SecAxis2, ConstraintInstance.Pos2); + } + + } + + return FTransform::Identity; + } + + UFUNCTION(BlueprintCallable, Category = "VRE Physics Constraint Component") + void GetGlobalPose(EConstraintFrame::Type ConstraintFrame, FTransform& GlobalPose) + { + if (ConstraintInstance.IsValidConstraintInstance()) + { + GlobalPose = FPhysicsInterface::GetGlobalPose(ConstraintInstance.ConstraintHandle, ConstraintFrame); + } + else + GlobalPose = FTransform::Identity; + } + + // Gets the current linear distance in world space on the joint in +/- from the initial reference frame + UFUNCTION(BlueprintPure, Category = "VRE Physics Constraint Component") + FVector GetCurrentLinearDistance(EConstraintFrame::Type FrameOfReference) + { + EConstraintFrame::Type Frame2 = FrameOfReference; + EConstraintFrame::Type Frame1 = (FrameOfReference == EConstraintFrame::Frame1) ? EConstraintFrame::Frame2 : EConstraintFrame::Frame1; + + FTransform Frame1Trans = this->GetBodyTransform(Frame1); + FTransform Frame2Trans = this->GetBodyTransform(Frame2); + + FTransform LocalPose = GetLocalPose(Frame1); + FTransform LocalPose2 = GetLocalPose(Frame2); + + Frame1Trans.SetScale3D(FVector(1.f)); + Frame1Trans = LocalPose * Frame1Trans; + + FVector OffsetLoc = Frame1Trans.GetRotation().UnrotateVector(Frame1Trans.GetLocation() - Frame2Trans.GetLocation()); + FVector OffsetLoc2 = LocalPose2.GetRotation().UnrotateVector(LocalPose2.GetLocation()); + FVector FinalVec = OffsetLoc2 - OffsetLoc; + + return FinalVec; + } + + // Gets the angular offset on the constraint + UFUNCTION(BlueprintPure, Category = "VRE Physics Constraint Component") + FRotator GetAngularOffset() + { + return ConstraintInstance.AngularRotationOffset; + } + + // Sets the angular offset on the constraint and re-initializes it + UFUNCTION(BlueprintCallable, Category="VRE Physics Constraint Component") + void SetAngularOffset(FRotator NewAngularOffset) + { + + // If the constraint is broken then there is no reason to do everything below + // Just early out of it. + if (!ConstraintInstance.IsValidConstraintInstance() || ConstraintInstance.IsBroken()) + { + ConstraintInstance.AngularRotationOffset = NewAngularOffset; + return; + } + + // I could remove a full step if I calc delta in Frame2 local and then apply to the new + // Values. However I am keeping it like this for now, would require an extra inverse / relative calc, this may not even be slower + + FVector RefPos = ConstraintInstance.Pos2; + const float RefScale = FMath::Max(GetConstraintScale(), 0.01f); + if (GetBodyInstance(EConstraintFrame::Frame2)) + { + RefPos *= RefScale; + } + + FQuat AngRotOffset = ConstraintInstance.AngularRotationOffset.Quaternion(); + FQuat newAngRotOffset = NewAngularOffset.Quaternion(); + + FTransform A2Transform = GetBodyTransform(EConstraintFrame::Frame2); + A2Transform.RemoveScaling(); + + FTransform CurrentLocalFrame(ConstraintInstance.PriAxis2, ConstraintInstance.SecAxis2, ConstraintInstance.PriAxis2 ^ ConstraintInstance.SecAxis2, ConstraintInstance.Pos2); + FTransform WorldLocalFrame = (CurrentLocalFrame * A2Transform); + + FVector WPri21 = GetComponentTransform().TransformVectorNoScale(AngRotOffset.GetForwardVector()); + FVector WOrth21 = GetComponentTransform().TransformVectorNoScale(AngRotOffset.GetRightVector()); + + FTransform OriginalRotOffset(WPri21, WOrth21, WPri21 ^ WOrth21, FVector::ZeroVector); + FQuat DeltaRot = WorldLocalFrame.GetRotation() * OriginalRotOffset.GetRotation().Inverse(); + DeltaRot.Normalize(); + + FVector WPri2 = GetComponentTransform().TransformVectorNoScale(newAngRotOffset.GetForwardVector()); + FVector WOrth2 = GetComponentTransform().TransformVectorNoScale(newAngRotOffset.GetRightVector()); + + WPri2 = DeltaRot.RotateVector(WPri2); + WOrth2 = DeltaRot.RotateVector(WOrth2); + + ConstraintInstance.PriAxis2 = A2Transform.InverseTransformVectorNoScale(WPri2); + ConstraintInstance.SecAxis2 = A2Transform.InverseTransformVectorNoScale(WOrth2); + ConstraintInstance.AngularRotationOffset = NewAngularOffset; + + FPhysicsInterface::ExecuteOnUnbrokenConstraintReadWrite(ConstraintInstance.ConstraintHandle, [&](const FPhysicsConstraintHandle& InUnbrokenConstraint) + { + FTransform URefTransform = FTransform(ConstraintInstance.PriAxis2, ConstraintInstance.SecAxis2, ConstraintInstance.PriAxis2 ^ ConstraintInstance.SecAxis2, RefPos); + FPhysicsInterface::SetLocalPose(InUnbrokenConstraint, URefTransform, EConstraintFrame::Frame2); + }); + + return; + } + + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRE Constraint Settings") + //bool bSetAndMaintainCOMOnFrame2; + + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRE Constraint Settings") + // bool bUseForceConstraint; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRFullScreenUserWidget.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRFullScreenUserWidget.h new file mode 100644 index 0000000..95920da --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRFullScreenUserWidget.h @@ -0,0 +1,424 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "GameFramework/Info.h" +#include "Components/WidgetComponent.h" +#include "ISpectatorScreenController.h" +//#include "CompositingElement.h" +#include "VRFullScreenUserWidget.generated.h" + +class FWidgetRenderer; +class FVRWidgetPostProcessHitTester; +class SConstraintCanvas; +class SVirtualWindow; +class SViewport; +class SWidget; +class ULevel; +class UMaterialInstanceDynamic; +class UMaterialInterface; +class UPostProcessComponent; +class UTextureRenderTarget2D; +class UWorld; + +#if WITH_EDITOR +class SLevelViewport; +#endif + + +UENUM(BlueprintType) +enum class EVRWidgetDisplayType : uint8 +{ + /** Do not display. */ + Inactive, + /** Display on a game viewport. */ + Viewport, + /** Display as a post process. */ + PostProcess, + /** Render to a texture and send to composure. */ + // Composure, +}; + + +USTRUCT() +struct FVRFullScreenUserWidget_Viewport +{ + GENERATED_BODY() + +public: + FVRFullScreenUserWidget_Viewport(); + bool Display(UWorld* World, UUserWidget* Widget, float InDPIScale); + void Hide(UWorld* World); + void Tick(UWorld* World, float DeltaSeconds); + +#if WITH_EDITOR + /** If set, use this viewport instead of GetFirstActiveLevelViewport() */ + TWeakPtr<SLevelViewport> TargetViewport; +#endif + +private: + bool bAddedToGameViewport; + + /** Constraint widget that contains the widget we want to display. */ + TWeakPtr<SConstraintCanvas> FullScreenCanvasWidget; + +#if WITH_EDITOR + /** Level viewport the widget was added to. */ + TWeakPtr<SLevelViewport> OverlayWidgetLevelViewport; +#endif +}; + +USTRUCT() +struct FVRFullScreenUserWidget_PostProcess +{ + GENERATED_BODY() + +public: + FVRFullScreenUserWidget_PostProcess(); + bool Display(UWorld* World, UUserWidget* Widget, bool bInRenderToTextureOnly, float InDPIScale); + void Hide(UWorld* World); + void Tick(UWorld* World, float DeltaSeconds); + + TSharedPtr<SVirtualWindow> VREXPANSIONPLUGIN_API GetSlateWindow() const; + +private: + //bool CreatePostProcessComponent(UWorld* World); + //void ReleasePostProcessComponent(); + + bool CreateRenderer(UWorld* World, UUserWidget* Widget, float InDPIScale); + void ReleaseRenderer(); + void TickRenderer(UWorld* World, float DeltaSeconds); + + FIntPoint CalculateWidgetDrawSize(UWorld* World); + bool IsTextureSizeValid(FIntPoint Size) const; + + void RegisterHitTesterWithViewport(UWorld* World); + void UnRegisterHitTesterWithViewport(); + +public: + /** + * Post process material used to display the widget. + * SlateUI [Texture] + * TintColorAndOpacity [Vector] + * OpacityFromTexture [Scalar] + */ + //UPROPERTY(EditAnywhere, Category = PostProcess) + //UMaterialInterface* PostProcessMaterial; + + /** Tint color and opacity for this component. */ + //UPROPERTY(EditAnywhere, Category = PostProcess) + //FLinearColor PostProcessTintColorAndOpacity; + + /** Sets the amount of opacity from the widget's UI texture to use when rendering the translucent or masked UI to the viewport (0.0-1.0). */ + //UPROPERTY(EditAnywhere, Category = PostProcess, meta = (ClampMin = 0.0f, ClampMax = 1.0f)) + //float PostProcessOpacityFromTexture; + + /** The size of the rendered widget. */ + UPROPERTY(EditAnywhere, Category = PostProcess, meta=(InlineEditConditionToggle)) + bool bWidgetDrawSize; + + /** The size of the rendered widget. */ + UPROPERTY(EditAnywhere, Category = PostProcess, meta=(EditCondition= bWidgetDrawSize)) + FIntPoint WidgetDrawSize; + + /** Is the virtual window created to host the widget focusable? */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + bool bWindowFocusable; + + /** The visibility of the virtual window created to host the widget. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + EWindowVisibility WindowVisibility; + + /** Register with the viewport for hardware input from the mouse and keyboard. It can and will steal focus from the viewport. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category= PostProcess) + bool bReceiveHardwareInput; + + /** The background color of the render target */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + FLinearColor RenderTargetBackgroundColor; + + /** The blend mode for the widget. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + EWidgetBlendMode RenderTargetBlendMode; + + /** List of composure layers that are expecting to use the WidgetRenderTarget. */ + //UPROPERTY(EditAnywhere, Category= PostProcess) + //TArray<ACompositingElement*> ComposureLayerTargets; + + /** The target to which the user widget is rendered. */ + UPROPERTY(Transient) + UTextureRenderTarget2D* WidgetRenderTarget; + + /** Only render to the UTextureRenderTarget2D - do not output to the final viewport. Unless DrawtoVRPreview is active */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + bool bRenderToTextureOnly; + + /** If we should automatically try to draw and manage this to the VR Preview */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = PostProcess) + bool bDrawToVRPreview; + + // VR Spectator mode to use when active + UPROPERTY(EditAnywhere, Category = "User Interface") + ESpectatorScreenMode VRDisplayType; + + // VR Spectator mode to use when not active + UPROPERTY(EditAnywhere, Category = "User Interface") + ESpectatorScreenMode PostVRDisplayType; + +#if WITH_EDITOR + /** If set, use this viewport instead of GetFirstActiveLevelViewport() */ + TWeakPtr<SLevelViewport> TargetViewport; +#endif +private: + /** Post process component used to add the material to the post process chain. */ + //UPROPERTY(Transient) + //UPostProcessComponent* PostProcessComponent; + + /** The dynamic instance of the material that the render target is attached to. */ + //UPROPERTY(Transient) + //UMaterialInstanceDynamic* PostProcessMaterialInstance; + + /** The slate window that contains the user widget content. */ + TSharedPtr<SVirtualWindow> SlateWindow; + + /** The slate viewport we are registered to. */ + TWeakPtr<SViewport> ViewportWidget; + + /** Helper class for drawing widgets to a render target. */ + FWidgetRenderer* WidgetRenderer; + + /** The size of the rendered widget */ + FIntPoint CurrentWidgetDrawSize; + + /** Hit tester when we want the hardware input. */ + TSharedPtr<FVRWidgetPostProcessHitTester> CustomHitTestPath; + +}; + +/** + * Will set the Widgets on a viewport either by Widgets are first rendered to a render target, then that render target is displayed in the world. + */ +UCLASS(BlueprintType, meta=(ShowOnlyInnerProperties)) +class VREXPANSIONPLUGIN_API UVRFullScreenUserWidget : public UObject +{ + GENERATED_BODY() + +public: + UVRFullScreenUserWidget(const FObjectInitializer& ObjectInitializer); + + //~ Begin UObject interface + virtual void BeginDestroy() override; +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + //~ End UObject Interface + + bool ShouldDisplay(UWorld* World) const; + EVRWidgetDisplayType GetDisplayType(UWorld* World) const; + bool IsDisplayed() const; + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + bool IsDisplayRequested() + { + return bDisplayRequested; + } + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + virtual bool Display(UWorld* World); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + virtual void Hide(); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + void SetIsHidden(bool bNewHidden) + { + if (bNewHidden) + { + Hide(); + } + else + { + if (World.IsValid()) + { + Display(World.Get()); + } + else + { + UWorld* myWorld = this->GetWorld(); + if (myWorld) + { + Display(myWorld); + } + } + } + } + + virtual void Tick(float DeltaTime); + + void SetDisplayTypes(EVRWidgetDisplayType InEditorDisplayType, EVRWidgetDisplayType InGameDisplayType, EVRWidgetDisplayType InPIEDisplayType); + +protected: + void InitWidget(); + void ReleaseWidget(); + + FVector2D FindSceneViewportSize(); + float GetViewportDPIScale(); + +private: + void OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld); + void OnWorldCleanup(UWorld* InWorld, bool bSessionEnded, bool bCleanupResources); + + +protected: + + +public: + + /** The display type when the world is an editor world. */ + UPROPERTY(EditAnywhere, Category = "User Interface") + EVRWidgetDisplayType EditorDisplayType; + + /** The display type when the world is a game world. */ + UPROPERTY(EditAnywhere, Category = "User Interface") + EVRWidgetDisplayType GameDisplayType; + + /** The display type when the world is a PIE world. */ + UPROPERTY(EditAnywhere, Category = "User Interface", meta = (DisplayName = "PIE Display Type")) + EVRWidgetDisplayType PIEDisplayType; + + /** Behavior when the widget should be display by the slate attached to the viewport. */ + UPROPERTY(EditAnywhere, Category = "Viewport", meta = (ShowOnlyInnerProperties)) + FVRFullScreenUserWidget_Viewport ViewportDisplayType; + + /** The class of User Widget to create and display an instance of */ + UPROPERTY(EditAnywhere, Category = "User Interface") + TSubclassOf<UUserWidget> WidgetClass; + + /** Behavior when the widget should be display by a post process. */ + UPROPERTY(EditAnywhere, Category = "Post Process", meta = (ShowOnlyInnerProperties)) + FVRFullScreenUserWidget_PostProcess PostProcessDisplayType; + + // Get a pointer to the inner widget. + // Note: This should not be stored! + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + UUserWidget* GetWidget() const { return Widget; }; + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetComp") + UTextureRenderTarget2D* GetPostProcessRenderTarget() const { return PostProcessDisplayType.WidgetRenderTarget; }; + +#if WITH_EDITOR + /** If set, use this viewport instead of GetFirstActiveLevelViewport() */ + TWeakPtr<SLevelViewport> TargetViewport; + + /** Sets the TargetViewport to use on both the Viewport and the PostProcess class */ + void SetAllTargetViewports(TWeakPtr<SLevelViewport> InTargetViewport); + + /** Resets the TargetViewport */ + void ResetAllTargetViewports(); +#endif + +private: + /** The User Widget object displayed and managed by this component */ + UPROPERTY(Transient, DuplicateTransient) + UUserWidget* Widget; + + /** The world the widget is attached to. */ + TWeakObjectPtr<UWorld> World; + + /** How we currently displaying the widget. */ + EVRWidgetDisplayType CurrentDisplayType; + + /** The user requested the widget to be displayed. It's possible that some setting are invalid and the widget will not be displayed. */ + bool bDisplayRequested; +}; + +/** + * Widgets are first rendered to a render target, then that render target is displayed in the world. + */ +UCLASS(Blueprintable, ClassGroup = "UserInterface", HideCategories = (Actor, Input, Movement, Collision, Rendering, "Utilities|Transformation", LOD), ShowCategories = ("Input|MouseInput", "Input|TouchInput")) +class VREXPANSIONPLUGIN_API AVRFullScreenUserWidgetActor : public AInfo +{ + GENERATED_BODY() + +public: + AVRFullScreenUserWidgetActor(const FObjectInitializer& ObjectInitializer); + + //~ Begin AActor interface + virtual void PostInitializeComponents() override; + virtual void PostLoad() override; + virtual void PostActorCreated() override; + virtual void Destroyed() override; + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void Tick(float DeltaSeconds) override; + + /** If true the widget will be shown right away, if false you will need to set SetWidgetVisible(true) to show it */ + UPROPERTY(EditAnywhere, Category = "User Interface") + bool bShowOnInit; + + /** */ + UPROPERTY(VisibleAnywhere, Instanced, NoClear, Category = "User Interface", meta = (ShowOnlyInnerProperties)) + UVRFullScreenUserWidget* ScreenUserWidget; + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + UVRFullScreenUserWidget* GetPreviewWidgetComp(); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + void SetPIEDisplayType(EVRWidgetDisplayType NewDisplayType) + { + if (IsValid(ScreenUserWidget)) + { + ScreenUserWidget->PIEDisplayType = NewDisplayType; + ScreenUserWidget->Hide(); + RequestGameDisplay(); + } + } + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + void SetGameDisplayType(EVRWidgetDisplayType NewDisplayType) + { + if (IsValid(ScreenUserWidget)) + { + ScreenUserWidget->GameDisplayType = NewDisplayType; + ScreenUserWidget->Hide(); + RequestGameDisplay(); + } + } + + // Set the widget to visible or not, this will be overriden by any changed to the actors hidden state + // IE: Setting actor to hidden will force this hidden as well, also setting the actor to visible will do the opposite + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + void SetWidgetVisible(bool bIsVisible); + + virtual void SetActorHiddenInGame(bool bNewHidden) override + { + SetWidgetVisible(bNewHidden); + Super::SetActorHiddenInGame(bNewHidden); + } + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + UUserWidget* GetWidget(); + + UFUNCTION(BlueprintCallable, Category = "FullScreenWidgetActor") + UTextureRenderTarget2D* GetPostProcessRenderTarget(); + +#if WITH_EDITOR + virtual bool ShouldTickIfViewportsOnly() const override { return true; } +#endif //WITH_EDITOR + //~ End AActor Interface + +private: + void RequestEditorDisplay(); + void RequestGameDisplay(); + +protected: + + +#if WITH_EDITORONLY_DATA + /** Display requested and will be executed on the first frame because we can't call BP function in the loading phase */ + bool bEditorDisplayRequested; +#endif +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRGameViewportClient.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRGameViewportClient.h new file mode 100644 index 0000000..9a1247a --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRGameViewportClient.h @@ -0,0 +1,164 @@ +// This class is intended to provide support for Local Mixed play between a mouse and keyboard player +// and a VR player. It is not needed outside of that use. + +#pragma once +#include "Engine/GameViewportClient.h" +//#include "Engine/Engine.h" +#include "CoreMinimal.h" + +#include "VRGameViewportClient.generated.h" + +UENUM(Blueprintable) +enum class EVRGameInputMethod : uint8 +{ + GameInput_Default, + GameInput_SharedKeyboardAndMouse, + GameInput_KeyboardAndMouseToPlayer2, +}; + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVROnWindowCloseRequested); + + +/** +* Subclass this in a blueprint to overwrite how default input is passed around in engine between local characters. +* Generally used for passing keyboard / mouse input to a secondary local player for local mixed gameplay in VR +*/ +UCLASS(Blueprintable) +class VREXPANSIONPLUGIN_API UVRGameViewportClient : public UGameViewportClient +{ + GENERATED_UCLASS_BODY() + +public: + + // Event thrown when the window is closed + UPROPERTY(BlueprintAssignable, Category = "VRExpansionPlugin") + FVROnWindowCloseRequested BPOnWindowCloseRequested; + + // If true then forced window closing will be canceled (alt-f4, ect) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + bool bIgnoreWindowCloseCommands; + + // Input Method for the viewport + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + EVRGameInputMethod GameInputMethod; + + // If true we will also shuffle gamepad input according to the GameInputMethod + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + bool bAlsoChangeGamepPadInput; + + // A List of input categories to consider as valid gamepad ones if bIsGamepad is true on the input event + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRExpansionPlugin") + TArray<FName> GamepadInputCategories; + + bool IsValidGamePadKey(const FKey & InputKey) + { + if (!bAlsoChangeGamepPadInput) + return false; + + FName KeyCategory = InputKey.GetMenuCategory(); + + return GamepadInputCategories.Contains(KeyCategory); + } + + UFUNCTION() + bool EventWindowClosing() + { + if (BPOnWindowCloseRequested.IsBound()) + { + BPOnWindowCloseRequested.Broadcast(); + } + + if (bIgnoreWindowCloseCommands) + { + return false; + } + + return true; + } + + virtual void PostInitProperties() override + { + Super::PostInitProperties(); + + if (GamepadInputCategories.Num() < 1) + { + GamepadInputCategories.Add(FName(TEXT("Gamepad"))); + GamepadInputCategories.Add(FName(TEXT("PS4"))); + GamepadInputCategories.Add(FName(TEXT("XBox One"))); + GamepadInputCategories.Add(FName(TEXT("Touch"))); + GamepadInputCategories.Add(FName(TEXT("Gesture"))); + } + + OnWindowCloseRequested().BindUObject(this, &UVRGameViewportClient::EventWindowClosing); + } + + + virtual bool InputKey(const FInputKeyEventArgs& EventArgs) override + { + // Early out if a gamepad event or ignoring input or is default setup / no GEngine + if(GameInputMethod == EVRGameInputMethod::GameInput_Default || IgnoreInput() || (EventArgs.IsGamepad() && !IsValidGamePadKey(EventArgs.Key))) + return Super::InputKey(EventArgs); + + const int32 NumLocalPlayers = World->GetGameInstance()->GetNumLocalPlayers(); + + // Also early out if number of players is less than 2 + if (NumLocalPlayers < 2) + return Super::InputKey(EventArgs); + + // Its const so have to copy and send a new one in now that the function signature has changed + FInputKeyEventArgs NewStruct = EventArgs; + + if (GameInputMethod == EVRGameInputMethod::GameInput_KeyboardAndMouseToPlayer2) + { + // keyboard / mouse always go to player 0, so + 1 will be player 2 + NewStruct.ControllerId++; + return Super::InputKey(NewStruct); + } + else // Shared keyboard and mouse + { + bool bRetVal = false; + for (int32 i = 0; i < NumLocalPlayers; i++) + { + NewStruct.ControllerId = i; + bRetVal = Super::InputKey(NewStruct) || bRetVal; + } + + return bRetVal; + } + } + + virtual bool InputAxis(FViewport* tViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples = 1, bool bGamepad = false) override + { + + const int32 NumLocalPlayers = World->GetGameInstance()->GetNumLocalPlayers(); + + // Early out if a gamepad or not a mouse event (vr controller) or ignoring input or is default setup / no GEngine + if (((!Key.IsMouseButton() && !bGamepad) || (bGamepad && !IsValidGamePadKey(Key))) || NumLocalPlayers < 2 || GameInputMethod == EVRGameInputMethod::GameInput_Default || IgnoreInput()) + return Super::InputAxis(tViewport, ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad); + + if (GameInputMethod == EVRGameInputMethod::GameInput_KeyboardAndMouseToPlayer2) + { + // keyboard / mouse always go to player 0, so + 1 will be player 2 + ++ControllerId; + return Super::InputAxis(tViewport, ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad); + } + else // Shared keyboard and mouse + { + bool bRetVal = false; + for (int32 i = 0; i < NumLocalPlayers; i++) + { + bRetVal = Super::InputAxis(tViewport, i, Key, Delta, DeltaTime, NumSamples, bGamepad) || bRetVal; + } + + return bRetVal; + } + + } +}; + +UVRGameViewportClient::UVRGameViewportClient(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + GameInputMethod = EVRGameInputMethod::GameInput_Default; + bAlsoChangeGamepPadInput = false; +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRLogComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRLogComponent.h new file mode 100644 index 0000000..099ee74 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRLogComponent.h @@ -0,0 +1,242 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Canvas.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Engine/Console.h" +#include "Containers/UnrealString.h" +#include "Core/Public/Misc/OutputDeviceHelper.h" +#include "VRLogComponent.generated.h" + +/** +* +*/ +UENUM(BlueprintType) +enum class EBPVRConsoleDrawType : uint8 +{ + VRConsole_Draw_ConsoleOnly, + VRConsole_Draw_OutputLogOnly +// VRConsole_Draw_ConsoleAndOutputLog +}; + + +/** +* A single log message for the output log, holding a message and +* a style, for color and bolding of the message. +*/ +struct FVRLogMessage +{ + TSharedRef<FString> Message; + ELogVerbosity::Type Verbosity; + FName Category; + FName Style; + + FVRLogMessage(const TSharedRef<FString>& NewMessage, FName NewCategory, FName NewStyle = NAME_None) + : Message(NewMessage) + , Verbosity(ELogVerbosity::Log) + , Category(NewCategory) + , Style(NewStyle) + { + } + + FVRLogMessage(const TSharedRef<FString>& NewMessage, ELogVerbosity::Type NewVerbosity, FName NewCategory, FName NewStyle = NAME_None) + : Message(NewMessage) + , Verbosity(NewVerbosity) + , Category(NewCategory) + , Style(NewStyle) + { + } +}; + +// Custom Log output history class to hold the VR logs. +/** This class is to capture all log output even if the log window is closed */ +class FVROutputLogHistory : public FOutputDevice +{ +public: + + int32 MaxStoredMessages; + bool bIsDirty; + int32 MaxLineLength; + + FVROutputLogHistory() + { + MaxLineLength = 130; + bIsDirty = false; + MaxStoredMessages = 1000; + GLog->AddOutputDevice(this); + GLog->SerializeBacklog(this); + } + + ~FVROutputLogHistory() + { + // At shutdown, GLog may already be null + if (GLog != NULL) + { + GLog->RemoveOutputDevice(this); + } + } + + /** Gets all captured messages */ + const TArray< TSharedPtr<FVRLogMessage> >& GetMessages() const + { + return Messages; + } + +protected: + + virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override + { + // Capture all incoming messages and store them in history + CreateLogMessages(V, Verbosity, Category, Messages); + } + + bool CreateLogMessages(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category, TArray< TSharedPtr<FVRLogMessage> >& OutMessages) + { + if (Verbosity == ELogVerbosity::SetColor) + { + // Skip Color Events + return false; + } + else + { + FName Style; + if (Category == NAME_Cmd) + { + Style = FName(TEXT("Log.Command")); + } + else if (Verbosity == ELogVerbosity::Error) + { + Style = FName(TEXT("Log.Error")); + } + else if (Verbosity == ELogVerbosity::Warning) + { + Style = FName(TEXT("Log.Warning")); + } + else + { + Style = FName(TEXT("Log.Normal")); + } + + // Forget timestamps, I don't care about them and we have limited texture space to draw too + // Determine how to format timestamps + static ELogTimes::Type LogTimestampMode = ELogTimes::None; + /*if (UObjectInitialized() && !GExitPurge) + { + // Logging can happen very late during shutdown, even after the UObject system has been torn down, hence the init check above + LogTimestampMode = GetDefault<UEditorStyleSettings>()->LogTimestampMode; + }*/ + + const int32 OldNumMessages = OutMessages.Num(); + + // handle multiline strings by breaking them apart by line + TArray<FTextRange> LineRanges; + FString CurrentLogDump = V; + FTextRange::CalculateLineRangesFromString(CurrentLogDump, LineRanges); + + bool bIsFirstLineInMessage = true; + for (const FTextRange& LineRange : LineRanges) + { + if (!LineRange.IsEmpty()) + { + FString Line = CurrentLogDump.Mid(LineRange.BeginIndex, LineRange.Len()); + Line = Line.ConvertTabsToSpaces(4); + + // Hard-wrap lines to avoid them being too long + /*static const */int32 HardWrapLen = MaxLineLength; + for (int32 CurrentStartIndex = 0; CurrentStartIndex < Line.Len();) + { + int32 HardWrapLineLen = 0; + if (bIsFirstLineInMessage) + { + FString MessagePrefix = FOutputDeviceHelper::FormatLogLine(Verbosity, Category, nullptr, LogTimestampMode); + + HardWrapLineLen = FMath::Min(HardWrapLen - MessagePrefix.Len(), Line.Len() - CurrentStartIndex); + FString HardWrapLine = Line.Mid(CurrentStartIndex, HardWrapLineLen); + + OutMessages.Add(MakeShareable(new FVRLogMessage(MakeShareable(new FString(MessagePrefix + HardWrapLine)), Verbosity, Category, Style))); + } + else + { + HardWrapLineLen = FMath::Min(HardWrapLen, Line.Len() - CurrentStartIndex); + FString HardWrapLine = Line.Mid(CurrentStartIndex, HardWrapLineLen); + + OutMessages.Add(MakeShareable(new FVRLogMessage(MakeShareable(new FString(MoveTemp(HardWrapLine))), Verbosity, Category, Style))); + } + + bIsFirstLineInMessage = false; + CurrentStartIndex += HardWrapLineLen; + } + } + } + + int numMessages = OutMessages.Num(); + if (numMessages > MaxStoredMessages) + { + OutMessages.RemoveAt(0, numMessages - MaxStoredMessages, true); + } + if (OldNumMessages != numMessages) + bIsDirty = true; + + return OldNumMessages != numMessages;//OutMessages.Num(); + } + } + +private: + + /** All log messages since this module has been started */ + TArray< TSharedPtr<FVRLogMessage> > Messages; +}; + +/** +* This class taps into the output log and console and renders them to textures so they can be viewed in levels. +* Generally used for debugging and testing in VR, also allows sending input to the console. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRLogComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UVRLogComponent(const FObjectInitializer& ObjectInitializer); + + + ~UVRLogComponent(); + + FVROutputLogHistory OutputLogHistory; + + virtual void PostInitProperties() override + { + Super::PostInitProperties(); + OutputLogHistory.MaxStoredMessages = FMath::Clamp(MaxStoredMessages, 100, 100000); + OutputLogHistory.MaxLineLength = FMath::Clamp(MaxLineLength, 50, 1000); + } + + UPROPERTY(BlueprintReadWrite,EditAnywhere, Category = "VRLogComponent|Console") + int32 MaxLineLength; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRLogComponent|Console") + int32 MaxStoredMessages; + + // Sets the console input text, can be used to clear the console or enter full or partial commands + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true")) + void SetConsoleText(FString Text); + + // Sends a key to the console - Console considers Released as final, flashes the cursor + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true")) + void SendKeyEventToConsole(FKey Key, EInputEvent KeyEvent); + + // Sends text to the console - Optionally returns at the end to "enter" the text, end flashes the cursor + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true")) + void AppendTextToConsole(FString Text, bool bReturnAtEnd = false); + + // Draw the console to a render target 2D + UFUNCTION(BlueprintCallable, Category = "VRLogComponent|Console", meta = (bIgnoreSelf = "true", DisplayName = "DrawConsoleToCanvasRenderTarget2D")) + bool DrawConsoleToRenderTarget2D(EBPVRConsoleDrawType DrawType, UTextureRenderTarget2D * Texture, float ScrollOffset, bool bForceDraw); + + + void DrawConsole(bool bLowerHalfOnly, UCanvas* Canvas); + void DrawOutputLog(bool bUpperHalfOnly, UCanvas* Canvas, float ScrollOffset); + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRPlayerStart.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRPlayerStart.h new file mode 100644 index 0000000..6d35b66 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRPlayerStart.h @@ -0,0 +1,150 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "GameFramework/PlayerStart.h" +#include "Engine/NavigationObjectBase.h" +#include "Components/SceneComponent.h" +#include "Components/CapsuleComponent.h" +#include "Components/BillboardComponent.h" +#include "VRPlayerStart.generated.h" + +/** +* A normal player start except I replaced the root component with a scene component so that the spawn +* transform will match our VR characters. +*/ +UCLASS(Blueprintable, ClassGroup = Common, hidecategories = Collision) +class VREXPANSIONPLUGIN_API AVRPlayerStart : public APlayerStart +{ + GENERATED_BODY() + +private: + UPROPERTY() + TObjectPtr<USceneComponent> VRRootComp; +public: + + AVRPlayerStart(const FObjectInitializer& ObjectInitializer); + + /** Returns VRRootComp subobject **/ + class USceneComponent* GetVRRootComponent() const { return VRRootComp; } + + // Override this to use capsule even if it isn't the root component + virtual void GetSimpleCollisionCylinder(float& CollisionRadius, float& CollisionHalfHeight) const override + { + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (CapsuleComp != nullptr && CapsuleComp->IsRegistered() && CapsuleComp->IsCollisionEnabled()) + { + // Note: assuming vertical orientation + CapsuleComp->GetScaledCapsuleSize(CollisionRadius, CollisionHalfHeight); + } + else + { + Super::GetSimpleCollisionCylinder(CollisionRadius, CollisionHalfHeight); + } + } + + void FindBase() override + { + if (GetWorld()->HasBegunPlay()) + { + return; + } + + if (ShouldBeBased()) + { + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (!CapsuleComp) + return; + + // not using find base, because don't want to fail if LD has navigationpoint slightly interpenetrating floor + FHitResult Hit(1.f); + + const float Radius = CapsuleComp->GetScaledCapsuleRadius(); + FVector const CollisionSlice(Radius, Radius, 1.f); + + // check for placement + float ScaledHalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight(); + const FVector TraceStart = GetActorLocation() + FVector(0.f, 0.f, ScaledHalfHeight); + const FVector TraceEnd = GetActorLocation() - FVector(0.f, 0.f, 2.f * ScaledHalfHeight); + + GetWorld()->SweepSingleByObjectType(Hit, TraceStart, TraceEnd, FQuat::Identity, FCollisionObjectQueryParams(ECC_WorldStatic), FCollisionShape::MakeBox(CollisionSlice), FCollisionQueryParams(SCENE_QUERY_STAT(NavFindBase), false)); + + // @fixme, ensure object is on the navmesh? + // if( Hit.Actor != NULL ) + // { + // if (Hit.Normal.Z > Scout->WalkableFloorZ) + // { + // const FVector HitLocation = TraceStart + (TraceEnd - TraceStart) * Hit.Time; + // TeleportTo(HitLocation + FVector(0.f,0.f,CapsuleComponent->GetScaledCapsuleHalfHeight()-2.f), GetActorRotation(), false, true); + // } + // else + // { + // Hit.Actor = NULL; + // } + // } + + if (GetGoodSprite()) + { + GetGoodSprite()->SetVisibility(true); + } + if (GetBadSprite()) + { + GetBadSprite()->SetVisibility(false); + } + } + } + + + void Validate() override + { + if (ShouldBeBased() && (GetGoodSprite() || GetBadSprite())) + { + UCapsuleComponent * CapsuleComp = GetCapsuleComponent(); + if (!CapsuleComp) + return; + + FVector OrigLocation = GetActorLocation(); + const float Radius = CapsuleComp->GetScaledCapsuleRadius(); + FVector const Slice(Radius, Radius, 1.f); + + bool bResult = true; + + // Check for adjustment + FHitResult Hit(ForceInit); + float ScaledHalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight(); + const FVector TraceStart = GetActorLocation() + FVector(0.f, 0.f, ScaledHalfHeight); + const FVector TraceEnd = GetActorLocation() - FVector(0.f, 0.f, 4.f * ScaledHalfHeight); + GetWorld()->SweepSingleByChannel(Hit, TraceStart, TraceEnd, FQuat::Identity, ECC_Pawn, FCollisionShape::MakeBox(Slice), FCollisionQueryParams(SCENE_QUERY_STAT(NavObjectBase_Validate), false, this)); + if (Hit.bBlockingHit) + { + const FVector HitLocation = TraceStart + (TraceEnd - TraceStart) * Hit.Time; + FVector Dest = HitLocation - FVector(0.f, 0.f, /*CapsuleComponent->GetScaledCapsuleHalfHeight() -*/ 2.f); + + // Move actor (TEST ONLY) to see if navigation point moves + TeleportTo(Dest, GetActorRotation(), false, true); + + // If only adjustment was down towards the floor, then it is a valid placement + FVector NewLocation = GetActorLocation(); + bResult = (NewLocation.X == OrigLocation.X && + NewLocation.Y == OrigLocation.Y && + NewLocation.Z <= OrigLocation.Z); + + // Move actor back to original position + TeleportTo(OrigLocation, GetActorRotation(), false, true); + } + + // Update sprites by result + if (GetGoodSprite()) + { + GetGoodSprite()->SetVisibility(bResult); + } + if (GetBadSprite()) + { + GetBadSprite()->SetVisibility(!bResult); + } + } + + // Force update of icon + MarkComponentsRenderStateDirty(); + } +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRRenderTargetManager.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRRenderTargetManager.h new file mode 100644 index 0000000..27ff397 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRRenderTargetManager.h @@ -0,0 +1,389 @@ +#pragma once +#include "TimerManager.h" +#include "VRRenderTargetManager.generated.h" + +class UVRRenderTargetManager; +class UCanvasRenderTarget2D; +class UCanvas; +class UTexture2D; +class UMaterial; +class APlayerController; + + +// #TODO: Dirty rects so don't have to send entire texture? + + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRReplicatedTextureStore +{ + GENERATED_BODY() +public: + + // Not automatically replicated, we are skipping it so that the array isn't checked + // We manually copy the data into the serialization buffer during netserialize and keep + // a flip flop dirty flag + + UPROPERTY(Transient) + TArray<uint8> PackedData; + + UPROPERTY(Transient) + TArray<uint16> UnpackedData; + + UPROPERTY(Transient) + uint32 Width; + + UPROPERTY() + uint32 Height; + + UPROPERTY(Transient) + bool bIsZipped; + + //UPROPERTY() + // bool bJPG; + //UPROPERTY(Transient) + EPixelFormat PixelFormat; + + FBPVRReplicatedTextureStore() + { + Width = 0; + Height = 0; + bIsZipped = false; + } + + void Reset() + { + PackedData.Reset(); + UnpackedData.Reset(); + Width = 0; + Height = 0; + PixelFormat = (EPixelFormat)0; + bIsZipped = false; + //bJPG = false; + } + + void PackData(); + void UnPackData(); + + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + +}; + +template<> +struct TStructOpsTypeTraits< FBPVRReplicatedTextureStore > : public TStructOpsTypeTraitsBase2<FBPVRReplicatedTextureStore> +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + + +USTRUCT() +struct FRenderDataStore { + GENERATED_BODY() + + TArray<FColor> ColorData; + FRenderCommandFence RenderFence; + FIntPoint Size2D; + EPixelFormat PixelFormat; + + FRenderDataStore() { + } +}; + +UENUM(BlueprintType) +enum class ERenderManagerOperationType : uint8 +{ + Op_LineDraw = 0x00, + Op_TriDraw = 0x01, + Op_TexDraw = 0x02 +}; + + +USTRUCT() +struct FRenderManagerTri { + GENERATED_BODY() +public: + FVector2D P1; + FVector2D P2; + FVector2D P3; +}; + +USTRUCT() +struct FRenderManagerOperation { + GENERATED_BODY() +public: + + UPROPERTY() + uint32 OwnerID; + + UPROPERTY() + ERenderManagerOperationType OperationType; + + UPROPERTY() + FColor Color; + + UPROPERTY() + FVector2D P1; + + UPROPERTY() + FVector2D P2; + + UPROPERTY() + uint32 Thickness; + + UPROPERTY() + TArray<FRenderManagerTri> Tris; + + UPROPERTY() + TSoftObjectPtr<UTexture2D> Texture; + + UPROPERTY() + TSoftObjectPtr<UMaterial> Material; + + FRenderManagerOperation() + { + OwnerID = 0; + OperationType = ERenderManagerOperationType::Op_LineDraw; + Color = FColor::White; + P1 = FVector2D::ZeroVector; + P2 = FVector2D::ZeroVector; + Thickness = 0; + } + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); +}; +template<> +struct TStructOpsTypeTraits< FRenderManagerOperation > : public TStructOpsTypeTraitsBase2<FRenderManagerOperation> +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +/** +* This class is used as a proxy to send owner only RPCs +*/ +UCLASS(ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API ARenderTargetReplicationProxy : public AActor +{ + GENERATED_BODY() + +public: + ARenderTargetReplicationProxy(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + UPROPERTY(Replicated, ReplicatedUsing = OnRep_Manager) + TObjectPtr<UVRRenderTargetManager> OwningManager; + + UPROPERTY(Replicated) + uint32 OwnersID; + + UFUNCTION() + void OnRep_Manager(); + + UPROPERTY(Transient) + FBPVRReplicatedTextureStore TextureStore; + + UPROPERTY(Transient) + int32 BlobNum; + + bool bWaitingForManager; + + void SendInitMessage(); + + UFUNCTION() + void SendNextDataBlob(); + + FTimerHandle SendTimer_Handle; + FTimerHandle CheckManager_Handle; + + // Maximum size of texture blobs to use for sending (size of chunks that it gets broken down into) + UPROPERTY() + int32 TextureBlobSize; + + // Maximum bytes per second to send, you will want to play around with this and the + // MaxClientRate settings in config in order to balance the bandwidth and avoid saturation + // If you raise this above the max replication size of a 65k byte size then you will need + // To adjust the max size in engine network settings. + UPROPERTY() + int32 MaxBytesPerSecondRate; + + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override + { + if(SendTimer_Handle.IsValid()) + GetWorld()->GetTimerManager().ClearTimer(SendTimer_Handle); + + Super::EndPlay(EndPlayReason); + } + + + UFUNCTION(Reliable, Server, WithValidation) + void SendLocalDrawOperations(const TArray<FRenderManagerOperation>& LocalRenderOperationStoreList); + + UFUNCTION(Reliable, Client) + void InitTextureSend(int32 Width, int32 Height, int32 TotalDataCount, int32 BlobCount, EPixelFormat PixelFormat, bool bIsZipped/*, bool bIsJPG*/); + + UFUNCTION(Reliable, Server, WithValidation) + void Ack_InitTextureSend(int32 TotalDataCount); + + UFUNCTION(Reliable, Client) + void ReceiveTextureBlob(const TArray<uint8>& TextureBlob, int32 LocationInData, int32 BlobCount); + + UFUNCTION(Reliable, Server, WithValidation) + void Ack_ReceiveTextureBlob(int32 BlobCount); + + UFUNCTION(Reliable, Client) + void ReceiveTexture(const FBPVRReplicatedTextureStore&TextureData); + +}; + + + +USTRUCT() +struct FClientRepData { + GENERATED_BODY() + + UPROPERTY() + TObjectPtr<APlayerController> PC; + + UPROPERTY() + TObjectPtr<ARenderTargetReplicationProxy> ReplicationProxy; + + UPROPERTY() + bool bIsRelevant; + + UPROPERTY() + bool bIsDirty; + + FClientRepData() + { + PC = nullptr; + ReplicationProxy = nullptr; + bIsRelevant = false; + bIsDirty = false; + } +}; + + +/** +* This class stores reading requests for rendertargets and iterates over them +* It returns the pixel data at the end of processing +* It references code from: https://github.com/TimmHess/UnrealImageCapture +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRRenderTargetManager : public UActorComponent +{ + GENERATED_BODY() + +public: + + UVRRenderTargetManager(const FObjectInitializer& ObjectInitializer); + + uint32 OwnerIDCounter; + + UPROPERTY(Transient) + TObjectPtr<ARenderTargetReplicationProxy> LocalProxy; + + TArray<FRenderManagerOperation> RenderOperationStore; + TArray<FRenderManagerOperation> LocalRenderOperationStore; + + // Rate to poll for drawing new operations + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + float DrawRate; + + UFUNCTION(Reliable, NetMultiCast, WithValidation) + void SendDrawOperations(const TArray<FRenderManagerOperation>& RenderOperationStoreList); + + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|DrawingFunctions") + void AddLineDrawOperation(FVector2D Point1, FVector2D Point2, FColor Color, int32 Thickness); + + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|DrawingFunctions") + void AddTextureDrawOperation(FVector2D Position, UTexture2D* TextureToDisplay); + + // Adds a draw operation for a triangle list, only takes the first vertex's color + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|DrawingFunctions") + void AddMaterialTrianglesDrawOperation(TArray<FCanvasUVTri> Tris, UMaterial* Material); + + void DrawOperation(UCanvas* Canvas, const FRenderManagerOperation& Operation); + + UFUNCTION() + void DrawPoll(); + + void DrawOperations(); + + UPROPERTY() + FTimerHandle DrawHandle; + + UPROPERTY(Transient) + bool bIsStoringImage; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + bool bInitiallyReplicateTexture; + + UPROPERTY(Transient) + bool bIsLoadingTextureBuffer; + + // Maximum size of texture blobs to use for sending (size of chunks that it gets broken down into) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 TextureBlobSize; + + // Maximum bytes per second to send, you will want to play around with this and the + // MaxClientRate settings in config in order to balance the bandwidth and avoid saturation + // If you raise this above the max replication size of a 65k byte size then you will need + // To adjust the max size in engine network settings. + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 MaxBytesPerSecondRate; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "RenderTargetManager") + TObjectPtr<UCanvasRenderTarget2D> RenderTarget; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 RenderTargetWidth; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + int32 RenderTargetHeight; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + FColor ClearColor; + + UPROPERTY(Transient) + TArray<FClientRepData> NetRelevancyLog; + + // Rate to poll for actor relevancy + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RenderTargetManager") + float PollRelevancyTime; + + FTimerHandle NetRelevancyTimer_Handle; + + UPROPERTY(Transient) + FBPVRReplicatedTextureStore RenderTargetStore; + + UFUNCTION(BlueprintCallable, Category = "VRRenderTargetManager|UtilityFunctions") + bool GenerateTrisFromBoxPlaneIntersection(UPrimitiveComponent* PrimToBoxCheck, FTransform WorldTransformOfPlane, const FPlane& LocalProjectionPlane, FVector2D PlaneSize, FColor UVColor, TArray<FCanvasUVTri>& OutTris); + + // Create the render target that we are managing + void InitRenderTarget(); + + // Update the list of players that we are checking for relevancy + void UpdateRelevancyMap(); + + // Decompress the render target data to a texture and copy it to our managed render target + bool DeCompressRenderTarget2D(); + + // Queues storing the render target image to our buffer + void QueueImageStore(); + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + +protected: + TQueue<FRenderDataStore *> RenderDataQueue; + +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRVehiclePawn.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRVehiclePawn.h new file mode 100644 index 0000000..83afc09 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRVehiclePawn.h @@ -0,0 +1,158 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "GameFramework/Pawn.h" +#include "Engine/InputDelegateBinding.h" +#include "Components/InputComponent.h" +#include "GameFramework/PlayerController.h" +#include "VRVehiclePawn.generated.h" + + +/** +* This override of the base pawn allows for dual pawn usage in engine. +* It adds two new functions: SetBindToInput to bind input locally to the pawn and ForceSecondaryPossession which fakes possession so the +* player can control the pawn as if they were locally possessed into it in a multiplayer enviroment (no lag). +*/ +UCLASS(config = Game, BlueprintType) +class VREXPANSIONPLUGIN_API AVRVehiclePawn : public APawn +{ + GENERATED_BODY() + +public: + + /** Call this function to detach safely pawn from its controller, knowing that we will be destroyed soon. */ + /*UFUNCTION(BlueprintCallable, Category = "Pawn", meta = (Keywords = "Delete")) + virtual void DetachFromControllerPendingDestroy() override + { + if (Controller != NULL && Controller->GetPawn() == this) + { + Controller->PawnPendingDestroy(this); + if (Controller != NULL) + { + Controller->UnPossess(); + Controller = NULL; + } + } + }*/ + + + //UFUNCTION() + virtual void OnRep_Controller() override + { + if ((Controller != NULL) && (Controller->GetPawn() == NULL)) + { + // This ensures that APawn::OnRep_Pawn is called. Since we cant ensure replication order of APawn::Controller and AController::Pawn, + // if APawn::Controller is repped first, it will set AController::Pawn locally. When AController::Pawn is repped, the rep value will not + // be different from the just set local value, and OnRep_Pawn will not be called. This can cause problems if OnRep_Pawn does anything important. + // + // It would be better to never ever set replicated properties locally, but this is pretty core in the gameplay framework and I think there are + // lots of assumptions made in the code base that the Pawn and Controller will always be linked both ways. + //Controller->SetPawnFromRep(this); + + /*APlayerController* const PC = Cast<APlayerController>(Controller); + if ((PC != NULL) && PC->bAutoManageActiveCameraTarget && (PC->PlayerCameraManager->ViewTarget.Target == Controller)) + { + PC->AutoManageActiveCameraTarget(this); + }*/ + } + + /*if (IsLocallyControlled()) + { + SetBindToInput(Controller, true); + }*/ + + } + + + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool SetBindToInput(AController * CController, bool bBindToInput) + { + APlayerController * playe = Cast<APlayerController>(CController); + + if (playe != NULL) + { + if(InputComponent) + playe->PopInputComponent(InputComponent); // Make sure it is off the stack + + if (!bBindToInput) + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return true; + } + else + { + // Set up player input component, if there isn't one already. + if (InputComponent == NULL) + { + InputComponent = CreatePlayerInputComponent(); + if (InputComponent) + { + SetupPlayerInputComponent(InputComponent); + InputComponent->RegisterComponent(); + + if (UInputDelegateBinding::SupportsInputDelegate(GetClass())) + { + InputComponent->bBlockInput = bBlockInput; + UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent); + } + } + } + + if (InputComponent) + { + playe->PushInputComponent(InputComponent); // Enforce input as top of stack so it gets input first and can consume it + return true; + } + } + } + else + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return false; + } + + return false; + } + + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool ForceSecondaryPossession(AController * NewController) + { + if (NewController) + { + PossessedBy(NewController); + } + else + { + UnPossessed(); + } + + return false; + //INetworkPredictionInterface* NetworkPredictionInterface = GetPawn() ? Cast<INetworkPredictionInterface>(GetPawn()->GetMovementComponent()) : NULL; + //if (NetworkPredictionInterface) + //{ + // NetworkPredictionInterface->ResetPredictionData_Server(); + // } + + + // Local PCs will have the Restart() triggered right away in ClientRestart (via PawnClientRestart()), but the server should call Restart() locally for remote PCs. + // We're really just trying to avoid calling Restart() multiple times. + // if (!IsLocalPlayerController()) + // { + // GetPawn()->Restart(); + // } + // ClientRestart(GetPawn()); + + //ChangeState(NAME_Playing); + //if (bAutoManageActiveCameraTarget) + //{ + // AutoManageActiveCameraTarget(GetPawn()); + // ResetCameraMode(); + //} + //UpdateNavigationComponents(); + } + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRWheeledVehicle.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRWheeledVehicle.h new file mode 100644 index 0000000..946d810 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Misc/VRWheeledVehicle.h @@ -0,0 +1,94 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#if WITH_CHAOS +#include "WheeledVehiclePawn.h" +#include "ChaosWheeledVehicleMovementComponent.h" +#endif + +#include "UObject/ObjectMacros.h" +#include "GameFramework/Pawn.h" +#include "Engine/InputDelegateBinding.h" +#include "Components/InputComponent.h" +#include "GameFramework/PlayerController.h" +#include "VRWheeledVehicle.generated.h" + + +/** +* This override of the base wheeled vehicle allows for dual pawn usage in engine. +*/ +UCLASS(config = Game, BlueprintType) +class VREXPANSIONPLUGIN_API AVRWheeledVehicle : public AWheeledVehiclePawn +//#endif +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool SetBindToInput(AController * CController, bool bBindToInput) + { + APlayerController * playe = Cast<APlayerController>(CController); + + if (playe != NULL) + { + if(InputComponent) + playe->PopInputComponent(InputComponent); // Make sure it is off the stack + + if (!bBindToInput) + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return true; + } + else + { + // Set up player input component, if there isn't one already. + if (InputComponent == NULL) + { + InputComponent = CreatePlayerInputComponent(); + if (InputComponent) + { + SetupPlayerInputComponent(InputComponent); + InputComponent->RegisterComponent(); + + if (UInputDelegateBinding::SupportsInputDelegate(GetClass())) + { + InputComponent->bBlockInput = bBlockInput; + UInputDelegateBinding::BindInputDelegates(GetClass(), InputComponent); + } + } + } + + if (InputComponent) + { + playe->PushInputComponent(InputComponent); // Enforce input as top of stack so it gets input first and can consume it + return true; + } + } + } + else + { + // Unregister input component if we created one + DestroyPlayerInputComponent(); + return false; + } + + return false; + } + + // Calls the movement components override controller function + UFUNCTION(BlueprintCallable, Category = "Pawn") + virtual bool SetOverrideController(AController * NewController) + { + if (UChaosWheeledVehicleMovementComponent* MoveComp = Cast<UChaosWheeledVehicleMovementComponent>(this->GetMovementComponent())) + { + MoveComp->SetOverrideController(NewController); + return true; + } + + return false; + } + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ParentRelativeAttachmentComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ParentRelativeAttachmentComponent.h new file mode 100644 index 0000000..e18858d --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ParentRelativeAttachmentComponent.h @@ -0,0 +1,361 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRExpansionFunctionLibrary.h" +#include "Components/ShapeComponent.h" +#include "VRTrackedParentInterface.h" +#include "ParentRelativeAttachmentComponent.generated.h" + +class AVRBaseCharacter; +class AVRCharacter; + +// Type of rotation sampling to use +UENUM(BlueprintType) +enum class EVR_PRC_RotationMethod : uint8 +{ + // Rotate purely to the HMD yaw, default mode + PRC_ROT_HMD UMETA(DisplayName = "HMD rotation"), + + // Rotate to a blend between the HMD and Controller facing + PRC_ROT_HMDControllerBlend UMETA(DisplayName = "ROT HMD Controller Blend"), + + // Rotate to the controllers with behind the back detection, clamp within neck limit + PRC_ROT_ControllerHMDClamped UMETA(DisplayName = "Controller Clamped to HMD") +}; + + +/** +* A component that will track the HMD/Cameras location and YAW rotation to allow for chest/waist attachements. +* This is intended to be parented to the root component of a pawn, it will then either find and track the camera +* or use the HMD's position if one is connected. This allows it to work in multiplayer since the camera will +* have its position replicated. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = VRExpansionLibrary) +class VREXPANSIONPLUGIN_API UParentRelativeAttachmentComponent : public USceneComponent, public IVRTrackedParentInterface +{ + GENERATED_BODY() + +public: + UParentRelativeAttachmentComponent(const FObjectInitializer& ObjectInitializer); + virtual void InitializeComponent() override; + + // Rotation method to use for facing calulations + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + //EVR_PRC_RotationMethod YawRotationMethod; + + // Angle tolerance before we lerp / snap to the new rotation + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary", meta = (ClampMin = "0", UIMin = "0")) + float YawTolerance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary", meta = (ClampMin = "0", UIMin = "0")) + float LerpSpeed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bLerpTransition; + + float LastRot; + float LastLerpVal; + float LerpTarget; + bool bWasSetOnce; + FTransform LeftControllerTrans; + FTransform RightControllerTrans; + + // If true uses feet/bottom of the capsule as the base Z position for this component instead of the HMD/Camera Z position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUseFeetLocation; + + // An additional value added to the relative position of the PRC + // Can be used to offset the floor, or component heights if needed + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + FVector CustomOffset; + + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bOffsetByHMD; + + // If true, will not set rotation every frame, only position + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bIgnoreRotationFromParent; + + // If true, this component will not perform logic in its tick, it will instead allow the character movement component to move it (unless the CMC is inactive, then it will go back to self managing) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUpdateInCharacterMovement; + + // If valid will use this as the tracked parent instead of the HMD / Parent + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRTrackedParentInterface") + FBPVRWaistTracking_Info OptionalWaistTrackingParent; + + virtual void SetTrackedParent(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode) override + { + IVRTrackedParentInterface::Default_SetTrackedParent_Impl(NewParentComponent, WaistRadius, WaistTrackingMode, OptionalWaistTrackingParent, this); + } + + UPROPERTY() + TObjectPtr<AVRCharacter> AttachChar; + UPROPERTY() + TObjectPtr<AVRBaseCharacter> AttachBaseChar; + + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + virtual void OnAttachmentChanged() override; + void UpdateTracking(float DeltaTime); + + bool IsLocallyControlled() const + { + // I like epics implementation better than my own + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast<APawn>(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : (MyOwner->Role == ENetRole::ROLE_Authority); + } + + // Sets the rotation and location depending on the control variables. Trying to remove some code duplication here + inline void SetRelativeRotAndLoc(FVector NewRelativeLocation, FRotator NewRelativeRotation, float DeltaTime) + { + + RunSampling(NewRelativeRotation, NewRelativeLocation); + + if (bUseFeetLocation) + { + if (!bIgnoreRotationFromParent) + { + SetRelativeLocationAndRotation( + FVector(NewRelativeLocation.X, NewRelativeLocation.Y, 0.0f) + CustomOffset, + GetCalculatedRotation(NewRelativeRotation, DeltaTime) + ); + } + else + { + SetRelativeLocation(FVector(NewRelativeLocation.X, NewRelativeLocation.Y, 0.0f) + CustomOffset); + } + } + else + { + if (!bIgnoreRotationFromParent) + { + SetRelativeLocationAndRotation( + NewRelativeLocation + CustomOffset, + GetCalculatedRotation(NewRelativeRotation, DeltaTime) + ); // Use the HMD height instead + } + else + { + SetRelativeLocation(NewRelativeLocation + CustomOffset); // Use the HMD height instead + } + } + } + + FQuat GetCalculatedRotation(FRotator InverseRot, float DeltaTime) + { + FRotator FinalRot = FRotator::ZeroRotator; + + if (FPlatformMath::Abs(FRotator::ClampAxis(InverseRot.Yaw) - LastRot) < YawTolerance) // This is never true with the default value of 0.0f + { + if (!bWasSetOnce) + { + LastRot = FRotator::ClampAxis(InverseRot.Yaw); + LastLerpVal = LastRot; + LerpTarget = LastRot; + bWasSetOnce = true; + } + + if (bLerpTransition && !FMath::IsNearlyEqual(LastLerpVal, LerpTarget)) + { + LastLerpVal = FMath::FixedTurn(LastLerpVal, LerpTarget, LerpSpeed * DeltaTime); + FinalRot = FRotator(0, LastLerpVal, 0); + } + else + { + FinalRot = FRotator(0, LastRot, 0); + LastLerpVal = LastRot; + } + } + else + { + // If we are using a snap threshold + if (!FMath::IsNearlyZero(YawTolerance)) + { + LerpTarget = FRotator::ClampAxis(InverseRot.Yaw); + LastLerpVal = FMath::FixedTurn(/*LastRot*/LastLerpVal, LerpTarget, LerpSpeed * DeltaTime); + FinalRot = FRotator(0, LastLerpVal, 0); + } + else // If we aren't then just directly set it to the correct rotation + { + FinalRot = FRotator(0, FRotator::ClampAxis(InverseRot.Yaw), 0); + } + + LastRot = FRotator::ClampAxis(InverseRot.Yaw); + } + + return FinalRot.Quaternion(); + } + + + + void RunSampling(FRotator &HMDRotation, FVector &HMDLocation) + { + /*switch(YawRotationMethod) + { + case EVR_PRC_RotationMethod::PRC_ROT_HMD: + { + return; + }break; + + case EVR_PRC_RotationMethod::PRC_ROT_HMDControllerBlend: + { + return; + }break; + + case EVR_PRC_RotationMethod::PRC_ROT_ControllerHMDClamped: + { + GetEstShoulderRotation(HMDRotation, HMDLocation); + return; + }break; + }*/ + + } + + // Get combined direction angle up + void GetEstShoulderRotation(FRotator &InputHMDRotation, FVector &InputHMDLocation) + { + /*float WorldToMeters = GetWorld() ? GetWorld()->GetWorldSettings()->WorldToMeters : 100.0f; + + // Position shoulder (neck) + FTransform shoulder = FTransform::Identity; + FVector headNeckDirectionVector = FVector(-.05f, 0.f, -1.f); + FVector neckShoulderDistance = FVector(-0.02f, 0.f, -.15f) * WorldToMeters; // World To Meters idealy + float headNeckDistance = 0.03f * WorldToMeters; // World To Meters idealy + + FVector headNeckOffset = InputHMDRotation.RotateVector(headNeckDirectionVector); + FVector targetPosition = InputHMDLocation + headNeckOffset * headNeckDistance; + shoulder.SetLocation(targetPosition + InputHMDRotation.RotateVector(neckShoulderDistance)); + + //DrawDebugSphere(GetWorld(), (shoulder * GetAttachParent()->GetComponentTransform()).GetLocation(), 4.0f, 32, FColor::White); + return; + */ + + /*if (IsLocallyControlled() && GEngine->XRSystem.IsValid()) + { + FVector Position = GetRelativeLocation(); + FRotator Orientation = GetRelativeRotation(); + + if (AttachBaseChar.IsValid()) + { + if (AttachBaseChar->LeftMotionController) + { + const bool bNewTrackedState = AttachBaseChar->LeftMotionController->GripPollControllerState(Position, Orientation, WorldToMeters); + bool bTracked = bNewTrackedState && CurrentTrackingStatus != ETrackingStatus::NotTracked; + + if (bTracked) + { + LeftControllerTrans = FTransform(Position, Orientation); + } + } + + if (AttachBaseChar->RightMotionController) + { + const bool bNewTrackedState = AttachBaseChar->RightMotionController->GripPollControllerState(Position, Orientation, WorldToMeters); + bool bTracked = bNewTrackedState && CurrentTrackingStatus != ETrackingStatus::NotTracked; + + if (bTracked) + { + RightControllerTrans = FTransform(Position, Orientation); + } + } + } + } + else if (AttachBaseChar.IsValid()) + { + LeftControllerTrans = AttachBaseChar->LeftMotionController->GetRelativeTransform(); + RightControllerTrans = AttachBaseChar->RightMotionController->GetRelativeTransform(); + } + + FVector LeftHand = LeftControllerTrans.GetLocation(); + FVector RightHand = RightControllerTrans.GetLocation(); + + //FRotator LeveledRel = CurrentTransforms.PureCameraYaw; + + FVector DistLeft = LeftHand - shoulder.Transform.GetLocation(); + FVector DistRight = RightHand - shoulder.Transform.GetLocation(); + + if (bIgnoreZPos) + { + DistLeft.Z = 0.0f; + DistRight.Z = 0.0f; + } + + FVector DirLeft = DistLeft.GetSafeNormal(); + FVector DirRight = DistRight.GetSafeNormal(); + + FVector CombinedDir = DirLeft + DirRight; + float FinalRot = FMath::RadiansToDegrees(FMath::Atan2(CombinedDir.Y, CombinedDir.X)); + + DetectHandsBehindHead(FinalRot, InputHMDRotation); + ClampHeadRotationDelta(FinalRot, InputHMDRotation); + + return FinalRot;*/ + } + + void DetectHandsBehindHead(float& TargetRot, FRotator HMDRotation) + { + /*float delta = FRotator::ClampAxis(FMath::FindDeltaAngleDegrees(TargetRot, LastTargetRot));// FRotator::ClampAxis(TargetRot - LastTargetRot); + + if (delta > 150.f && delta < 210.0f && !FMath::IsNearlyZero(LastTargetRot) && !bClampingHeadRotation) + { + bHandsBehindHead = !bHandsBehindHead; + if (bHandsBehindHead) + { + BehindHeadAngle = TargetRot; + } + else + { + BehindHeadAngle = 0.f; + } + } + else if (bHandsBehindHead) + { + float delta2 = FMath::Abs(FMath::FindDeltaAngleDegrees(TargetRot, BehindHeadAngle)); + + if (delta2 > 90.f) + { + bHandsBehindHead = !bHandsBehindHead; + BehindHeadAngle = 0.f; + } + } + + LastTargetRot = TargetRot; + + if (bHandsBehindHead) + { + TargetRot += 180.f; + }*/ + } + + // Clamp head rotation delta yaw + void ClampHeadRotationDelta(float& TargetRotation, FRotator HMDRotation) + { + /*float HeadRotation = FRotator::ClampAxis(CurrentTransforms.PureCameraYaw.Yaw); + float cTargetRotation = FRotator::ClampAxis(TargetRotation); + + float delta = HeadRotation - cTargetRotation; + + if ((delta > 80.f && delta < 180.f) || (delta < -180.f && delta >= -360.f + 80)) + { + TargetRotation = HeadRotation - 80.f; + bClampingHeadRotation = true; + } + else if ((delta < -80.f && delta > -180.f) || (delta > 180.f && delta < 360.f - 80.f)) + { + TargetRotation = HeadRotation + 80.f; + bClampingHeadRotation = true; + } + else + { + bClampingHeadRotation = false; + }*/ + } +}; + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ReplicatedVRCameraComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ReplicatedVRCameraComponent.h new file mode 100644 index 0000000..b1d9d7c --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/ReplicatedVRCameraComponent.h @@ -0,0 +1,167 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +//#include "Net/UnrealNetwork.h" +#include "Camera/CameraComponent.h" +#include "ReplicatedVRCameraComponent.generated.h" + +class AVRBaseCharacter; + +/** +* An overridden camera component that replicates its location in multiplayer +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = VRExpansionLibrary) +class VREXPANSIONPLUGIN_API UReplicatedVRCameraComponent : public UCameraComponent +{ + GENERATED_BODY() + +public: + UReplicatedVRCameraComponent(const FObjectInitializer& ObjectInitializer); + + + // If true, this component will not perform logic in its tick, it will instead allow the character movement component to move it (unless the CMC is inactive, then it will go back to self managing) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUpdateInCharacterMovement; + + UPROPERTY() + TObjectPtr<AVRBaseCharacter> AttachChar; + void UpdateTracking(float DeltaTime); + + virtual void OnAttachmentChanged() override; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + //virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + /** Whether or not this component has authority within the frame*/ + bool bHasAuthority; + + /** Whether or not this component is currently on the network server*/ + bool bIsServer; + + FTransform LastRelativePosition; + bool bHadValidFirstVelocity; + + // If we should sample the velocity in world or local space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|ComponentVelocity") + bool bSampleVelocityInWorldSpace; + + // For non view target positional updates + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera") + bool bSetPositionDuringTick; + + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera") + bool bOffsetByHMD; + + // If true will scale the tracking of the camera by TrackingScaler + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bScaleTracking; + + // A scale to be applied to the tracking of the camera + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bScaleTracking")) + FVector TrackingScaler; + + // If true we will use the minimum height value to clamp the Z too + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bLimitMinHeight; + + // The minimum height to allow for this camera + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.0", UIMin = "0.0", EditCondition = "bLimitMinHeight")) + float MinimumHeightAllowed; + + // If true will limit the max Z height that the camera is capable of reaching + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bLimitMaxHeight; + + // If we are limiting the max height, this is the maximum allowed value + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLimitMaxHeight")) + float MaxHeightAllowed; + + // If true will limit the maximum offset from center of the players tracked space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + bool bLimitBounds; + + // If we are limiting the maximum bounds, this is the maximum length of the vector from the center of the tracked space + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking", meta = (ClampMin = "0.1", UIMin = "0.1", EditCondition = "bLimitMaxHeight")) + float MaximumTrackedBounds; + + /** Sets lock to hmd automatically based on if the camera is currently locally controlled or not */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera|Advanced|Tracking") + uint32 bAutoSetLockToHmd : 1; + + void ApplyTrackingParameters(FVector & OriginalPosition); + bool HasTrackingParameters(); + + //UFUNCTION(BlueprintCallable, Category = Camera) + virtual void GetCameraView(float DeltaTime, FMinimalViewInfo& DesiredView) override; + + UPROPERTY(EditDefaultsOnly, ReplicatedUsing = OnRep_ReplicatedCameraTransform, Category = "ReplicatedCamera|Networking") + FBPVRComponentPosRep ReplicatedCameraTransform; + + FVector LastUpdatesRelativePosition; + FRotator LastUpdatesRelativeRotation; + + bool bLerpingPosition; + bool bReppedOnce; + + // Whether to smooth (lerp) between ticks for the replicated motion, DOES NOTHING if update rate is larger than FPS! + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "ReplicatedCamera|Networking") + bool bSmoothReplicatedMotion; + + UFUNCTION() + virtual void OnRep_ReplicatedCameraTransform() + { + if (GetNetMode() < ENetMode::NM_Client && HasTrackingParameters()) + { + // Ensure that we clamp to the expected values from the client + ApplyTrackingParameters(ReplicatedCameraTransform.Position); + } + + if (bSmoothReplicatedMotion) + { + if (bReppedOnce) + { + bLerpingPosition = true; + NetUpdateCount = 0.0f; + LastUpdatesRelativePosition = this->GetRelativeLocation(); + LastUpdatesRelativeRotation = this->GetRelativeRotation(); + } + else + { + SetRelativeLocationAndRotation(ReplicatedCameraTransform.Position, ReplicatedCameraTransform.Rotation); + bReppedOnce = true; + } + } + else + SetRelativeLocationAndRotation(ReplicatedCameraTransform.Position, ReplicatedCameraTransform.Rotation); + } + + // Rate to update the position to the server, 100htz is default (same as replication rate, should also hit every tick). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated, Category = "ReplicatedCamera|Networking") + float NetUpdateRate; + + // Used in Tick() to accumulate before sending updates, didn't want to use a timer in this case. + float NetUpdateCount; + + // I'm sending it unreliable because it is being resent pretty often + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendCameraTransform(FBPVRComponentPosRep NewTransform); + + // Pointer to an override to call from the owning character - this saves 7 bits a rep avoiding component IDs on the RPC + typedef void (AVRBaseCharacter::*VRBaseCharTransformRPC_Pointer)(FBPVRComponentPosRep NewTransform); + VRBaseCharTransformRPC_Pointer OverrideSendTransform; + + // Need this as I can't think of another way for an actor component to make sure it isn't on the server + inline bool IsLocallyControlled() const + { + // I like epics new authority check more than my own + const AActor* MyOwner = GetOwner(); + return MyOwner->HasLocalNetOwner(); + //const APawn* MyPawn = Cast<APawn>(MyOwner); + //return MyPawn ? MyPawn->IsLocallyControlled() : false;// (MyOwner->Role == ENetRole::ROLE_Authority); + } + + //bool IsServer(); +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacter.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacter.h new file mode 100644 index 0000000..ecccbd3 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacter.h @@ -0,0 +1,51 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "VRBPDatatypes.h" +#include "VRBaseCharacter.h" +#include "GripMotionControllerComponent.h" +#include "VRExpansionFunctionLibrary.h" +#include "VRSimpleCharacterMovementComponent.h" +#include "ParentRelativeAttachmentComponent.h" +#include "ReplicatedVRCameraComponent.h" +//#include "VRSimpleRootComponent.h" +#include "Runtime/Launch/Resources/Version.h" +#include "VRSimpleCharacter.generated.h" + + +UCLASS() +class VREXPANSIONPLUGIN_API AVRSimpleCharacter : public AVRBaseCharacter +{ + GENERATED_BODY() + +public: + AVRSimpleCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + FORCEINLINE void GenerateOffsetToWorld() + { + FRotator CamRotOffset = UVRExpansionFunctionLibrary::GetHMDPureYaw_I(VRReplicatedCamera->GetComponentRotation()); + OffsetComponentToWorld = FTransform(CamRotOffset.Quaternion(), this->GetActorLocation(), this->GetActorScale3D()); + } + + // Regenerates the base offsetcomponenttoworld that VR uses + //UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + virtual void RegenerateOffsetComponentToWorld(bool bUpdateBounds, bool bCalculatePureYaw) override + { + GenerateOffsetToWorld(); + } + + // Resetting if people turned off required settings here + virtual void BeginPlay() override; + + // Overriding teleport so that it auto calls my controllers re-positioning + virtual bool TeleportTo(const FVector& DestLocation, const FRotator& DestRotation, bool bIsATest = false, bool bNoCheck = false) override; + + UPROPERTY(Category = VRSimpleCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<USceneComponent> VRSceneComponent; + + //A helper function that offsets a given vector by the roots collision location + //pass in a teleport location and it provides the correct spot for it to be at your feet + + //UFUNCTION(BlueprintPure, Category = "VRGrip") + virtual FVector GetTeleportLocation(FVector OriginalLocation) override; +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacterMovementComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacterMovementComponent.h new file mode 100644 index 0000000..f5632b6 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/SimpleChar/VRSimpleCharacterMovementComponent.h @@ -0,0 +1,166 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "VRBaseCharacterMovementComponent.h" +#include "AI/Navigation/NavigationAvoidanceTypes.h" +#include "AI/RVOAvoidanceInterface.h" +#include "AITypes.h" +#include "Navigation/PathFollowingComponent.h" +#include "AI/Navigation/NavigationTypes.h" +#include "NavigationSystem.h" +#include "Animation/AnimationAsset.h" +#include "Engine/EngineBaseTypes.h" +#include "Camera/CameraComponent.h" +#include "Engine/EngineTypes.h" +#include "GameFramework/PawnMovementComponent.h" +#include "Interfaces/NetworkPredictionInterface.h" +#include "WorldCollision.h" +#include "Runtime/Launch/Resources/Version.h" +#include "VRSimpleCharacterMovementComponent.generated.h" + +class FDebugDisplayInfo; +class ACharacter; +class AVRSimpleCharacter; +//class UVRSimpleRootComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogSimpleCharacterMovement, Log, All); + +/** Shared pointer for easy memory management of FSavedMove_Character, for accumulating and replaying network moves. */ +//typedef TSharedPtr<class FSavedMove_Character> FSavedMovePtr; + + +//============================================================================= +/** + * VRSimpleCharacterMovementComponent handles movement logic for the associated Character owner. + * It supports various movement modes including: walking, falling, swimming, flying, custom. + * + * Movement is affected primarily by current Velocity and Acceleration. Acceleration is updated each frame + * based on the input vector accumulated thus far (see UPawnMovementComponent::GetPendingInputVector()). + * + * Networking is fully implemented, with server-client correction and prediction included. + * + * @see ACharacter, UPawnMovementComponent + * @see https://docs.unrealengine.com/latest/INT/Gameplay/Framework/Pawn/Character/ + */ + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAIMoveCompletedSignature, FAIRequestID, RequestID, EPathFollowingResult::Type, Result); + + +UCLASS() +class VREXPANSIONPLUGIN_API UVRSimpleCharacterMovementComponent : public UVRBaseCharacterMovementComponent +{ + GENERATED_BODY() +public: + + bool bIsFirstTick; + FVector curCameraLoc; + FRotator curCameraRot; + + FVector lastCameraLoc; + FRotator lastCameraRot; + + UPROPERTY(BlueprintReadOnly, Transient, Category = VRMovement) + UCapsuleComponent * VRRootCapsule; + + UPROPERTY(BlueprintReadOnly, Transient, Category = VRMovement) + UCameraComponent * VRCameraComponent; + + // Skips checking for the HMD location on tick, for 2D pawns when a headset is connected + UPROPERTY(BlueprintReadWrite, Category = VRMovement) + bool bSkipHMDChecks; + + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + void SetUpdatedComponent(USceneComponent* NewUpdatedComponent) override; + + void PhysWalking(float deltaTime, int32 Iterations) override; + void PhysFlying(float deltaTime, int32 Iterations) override; + void PhysFalling(float deltaTime, int32 Iterations) override; + void PhysNavWalking(float deltaTime, int32 Iterations) override; + /** + * Default UObject constructor. + */ + UVRSimpleCharacterMovementComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + virtual bool VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult = nullptr) override; + + /////////////////////////// + // Replication Functions + /////////////////////////// + //virtual void CallServerMove(const class FSavedMove_Character* NewMove, const class FSavedMove_Character* OldMove) override; + + virtual void ServerMove_PerformMovement(const FCharacterNetworkMoveData& MoveData) override; + + /** Default client to server move RPC data container. Can be bypassed via SetNetworkMoveDataContainer(). */ + //FCharacterNetworkMoveDataContainer VRNetworkMoveDataContainer; + //FCharacterMoveResponseDataContainer VRMoveResponseDataContainer; + + // Use ServerMoveVR instead + virtual void ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) override; + + FNetworkPredictionData_Client* GetPredictionData_Client() const override; + FNetworkPredictionData_Server* GetPredictionData_Server() const override; + + /////////////////////////// + // End Replication Functions + /////////////////////////// +}; + +class VREXPANSIONPLUGIN_API FSavedMove_VRSimpleCharacter : public FSavedMove_VRBaseCharacter +{ + +public: + + //FVector VRCapsuleLocation; + //FVector LFDiff; + //FVector CustomVRInputVector; + //FRotator VRCapsuleRotation; + //FVector RequestedVelocity; + + void Clear(); + virtual void SetInitialPosition(ACharacter* C); + virtual void PrepMoveFor(ACharacter* Character) override; + + FSavedMove_VRSimpleCharacter() : FSavedMove_VRBaseCharacter() + { + //VRCapsuleLocation = FVector::ZeroVector; + LFDiff = FVector::ZeroVector; + //CustomVRInputVector = FVector::ZeroVector; + //VRCapsuleRotation = FRotator::ZeroRotator; + //RequestedVelocity = FVector::ZeroVector; + } + +}; + +// Need this for capsule location replication +class VREXPANSIONPLUGIN_API FNetworkPredictionData_Client_VRSimpleCharacter : public FNetworkPredictionData_Client_Character +{ +public: + FNetworkPredictionData_Client_VRSimpleCharacter(const UCharacterMovementComponent& ClientMovement) + : FNetworkPredictionData_Client_Character(ClientMovement) + { + + } + + FSavedMovePtr AllocateNewMove() + { + return FSavedMovePtr(new FSavedMove_VRSimpleCharacter()); + } +}; + + +// Need this for capsule location replication????? +class VREXPANSIONPLUGIN_API FNetworkPredictionData_Server_VRSimpleCharacter : public FNetworkPredictionData_Server_Character +{ +public: + FNetworkPredictionData_Server_VRSimpleCharacter(const UCharacterMovementComponent& ClientMovement) + : FNetworkPredictionData_Server_Character(ClientMovement) + { + + } + + FSavedMovePtr AllocateNewMove() + { + return FSavedMovePtr(new FSavedMove_VRSimpleCharacter()); + } +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRAIController.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRAIController.h new file mode 100644 index 0000000..4405019 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRAIController.h @@ -0,0 +1,36 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "AIController.h" + +#include "VRAIController.generated.h" + + +UCLASS() +class VREXPANSIONPLUGIN_API AVRAIController : public AAIController +{ + GENERATED_BODY() + +public: + virtual FVector GetFocalPointOnActor(const AActor *Actor) const override; + + /** + * Checks line to center and top of other actor + * @param Other is the actor whose visibility is being checked. + * @param ViewPoint is eye position visibility is being checked from. If vect(0,0,0) passed in, uses current viewtarget's eye position. + * @param bAlternateChecks used only in AIController implementation + * @return true if controller's pawn can see Other actor. + */ + virtual bool LineOfSightTo(const AActor* Other, FVector ViewPoint = FVector(ForceInit), bool bAlternateChecks = false) const override; + //~ End AController Interface +}; + + +UCLASS() +class AVRDetourCrowdAIController : public AVRAIController +{ + GENERATED_BODY() +public: + AVRDetourCrowdAIController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBPDatatypes.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBPDatatypes.h new file mode 100644 index 0000000..53df3e4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBPDatatypes.h @@ -0,0 +1,1956 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Engine/NetSerialization.h" +#include "PhysicsPublic.h" +//#include "EngineMinimal.h" +//#include "Components/PrimitiveComponent.h" + +//#include "PhysicsPublic.h" +#include "PhysicsEngine/ConstraintDrives.h" +#include "VRBPDatatypes.generated.h" + +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +// Custom movement modes for the characters +UENUM(BlueprintType) +enum class EVRCustomMovementMode : uint8 +{ + VRMOVE_Climbing UMETA(DisplayName = "Climbing"), + VRMOVE_LowGrav UMETA(DisplayName = "LowGrav"), + VRMOVE_Seated UMETA(DisplayName = "Seated"), + VRMOVE_SplineFollow UMETA(DisplayName = "SplineFollow") +// VRMove_Spider UMETA(DisplayName = "Spider") +}; + +// We use 6 bits for this so a maximum of 64 elements +UENUM(BlueprintType) +enum class EVRConjoinedMovementModes : uint8 +{ + C_MOVE_None = 0x00 UMETA(DisplayName = "None"), + C_MOVE_Walking = 0x01 UMETA(DisplayName = "Walking"), + C_MOVE_NavWalking = 0x02 UMETA(DisplayName = "Navmesh Walking"), + C_MOVE_Falling = 0x03 UMETA(DisplayName = "Falling"), + C_MOVE_Swimming = 0x04 UMETA(DisplayName = "Swimming"), + C_MOVE_Flying = 0x05 UMETA(DisplayName = "Flying"), + //C_MOVE_Custom = 0x06 UMETA(DisplayName = "Custom"), // Skip this, could technically get a Custom7 out of using this slot but who needs 7? + C_MOVE_MAX = 0x07 UMETA(Hidden), + C_VRMOVE_Climbing = 0x08 UMETA(DisplayName = "Climbing"), + C_VRMOVE_LowGrav = 0x09 UMETA(DisplayName = "LowGrav"), + //C_VRMOVE_Spider = 0x0A UMETA(DisplayName = "Spider"), + C_VRMOVE_Seated = 0x0A UMETA(DisplayName = "Seated"), + C_VRMOVE_SplineFollow = 0x0B UMETA(DisplayName = "SplineFollow"), // + // 0x0C + // 0x0D + // 0x0E + // 0x0F + // 0x10 + // 0x11 + // 0x12 + // 0x13 + // 0x14 + // 0x15 + // 0x16 + // 0x17 + // 0x18 + // 0x19 + C_VRMOVE_Custom1 = 0x1A UMETA(DisplayName = "Custom1"), + C_VRMOVE_Custom2 = 0x1B UMETA(DisplayName = "Custom2"), + C_VRMOVE_Custom3 = 0x1C UMETA(DisplayName = "Custom3"), + C_VRMOVE_Custom4 = 0x1D UMETA(DisplayName = "Custom4"), + C_VRMOVE_Custom5 = 0x1E UMETA(DisplayName = "Custom5"), + C_VRMOVE_Custom6 = 0x1F UMETA(DisplayName = "Custom6"), + C_VRMOVE_Custom7 = 0x20 UMETA(DisplayName = "Custom7"), + C_VRMOVE_Custom8 = 0x21 UMETA(DisplayName = "Custom8"), + C_VRMOVE_Custom9 = 0x22 UMETA(DisplayName = "Custom9"), + C_VRMOVE_Custom10 = 0x23 UMETA(DisplayName = "Custom10") +}; + +// This makes a lot of the blueprint functions cleaner +UENUM() +enum class EBPVRResultSwitch : uint8 +{ + // On Success + OnSucceeded, + // On Failure + OnFailed +}; + +// Which method of handling gripping conflict to take with client auth +UENUM(BlueprintType) +enum class EVRClientAuthConflictResolutionMode : uint8 +{ + // Do nothing + VRGRIP_CONFLICT_None, + // Give to the first to arrive + VRGRIP_CONFLICT_First, + // Give to the last to arrive + VRGRIP_CONFLICT_Last, + // Force all ends to drop their grip + VRGRIP_CONFLICT_DropAll +}; + +// Wasn't needed when final setup was realized +// Tracked device waist location +UENUM(Blueprintable) +enum class EBPVRWaistTrackingMode : uint8 +{ + // Waist is tracked from the front + VRWaist_Tracked_Front, + // Waist is tracked from the rear + VRWaist_Tracked_Rear, + // Waist is tracked from the left (self perspective) + VRWaist_Tracked_Left, + // Waist is tracked from the right (self perspective) + VRWaist_Tracked_Right +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPVRWaistTracking_Info +{ + GENERATED_BODY() +public: + + // Initial "Resting" location of the tracker parent, assumed to be the calibration zero + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + FRotator RestingRotation; + + // Distance to offset to get center of waist from tracked parent location + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + float WaistRadius; + + // Controls forward vector + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + EBPVRWaistTrackingMode TrackingMode; + + // Tracked parent reference + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Settings") + TObjectPtr<UPrimitiveComponent> TrackedDevice; + + bool IsValid() + { + return TrackedDevice != nullptr; + } + + void Clear() + { + TrackedDevice = nullptr; + } + + FBPVRWaistTracking_Info(): + RestingRotation(FRotator::ZeroRotator), + WaistRadius(0.0f), + TrackingMode(EBPVRWaistTrackingMode::VRWaist_Tracked_Rear), + TrackedDevice(nullptr) + {} + +}; + + +/** Different methods for interpolating rotation between transforms */ +UENUM(BlueprintType) +enum class EVRLerpInterpolationMode : uint8 +{ + /** Shortest Path or Quaternion interpolation for the rotation. */ + QuatInterp, + + /** Rotor or Euler Angle interpolation. */ + EulerInterp, + + /** Dual quaternion interpolation, follows helix or screw-motion path between keyframes. */ + DualQuatInterp +}; + +template<class filterType> +class FBasicLowPassFilter +{ +public: + + /** Default constructor */ + FBasicLowPassFilter(filterType EmptyValueSet) + { + EmptyValue = EmptyValueSet; + Previous = EmptyValue; + PreviousRaw = EmptyValue; + bFirstTime = true; + } + + /** Calculate */ + filterType Filter(const filterType& InValue, const filterType& InAlpha) + { + + filterType Result = InValue; + if (!bFirstTime) + { + // This is unsafe in non float / float array data types, but I am not going to be using any like that + for (int i = 0; i < sizeof(filterType)/sizeof(float); i++) + { + ((float*)&Result)[i] = ((float*)&InAlpha)[i] * ((float*)&InValue)[i] + (1.0f - ((float*)&InAlpha)[i]) * ((float*)&Previous)[i]; + } + } + + bFirstTime = false; + Previous = Result; + PreviousRaw = InValue; + return Result; + } + + filterType EmptyValue; + + /** The previous filtered value */ + filterType Previous; + + /** The previous raw value */ + filterType PreviousRaw; + + /** If this is the first time doing a filter */ + bool bFirstTime; + +//private: + + const filterType CalculateCutoff(const filterType& InValue, float& MinCutoff, float& CutoffSlope) + { + filterType Result; + // This is unsafe in non float / float array data types, but I am not going to be using any like that + for (int i = 0; i < sizeof(filterType)/sizeof(float); i++) + { + ((float*)&Result)[i] = MinCutoff + CutoffSlope * FMath::Abs(((float*)&InValue)[i]); + } + return Result; + } + + const filterType CalculateAlpha(const filterType& InCutoff, const double InDeltaTime) + { + filterType Result; + // This is unsafe in non float / float array data types, but I am not going to be using any like that + for (int i = 0; i < sizeof(filterType)/sizeof(float); i++) + { + ((float*)&Result)[i] = CalculateAlphaTau(((float*)&InCutoff)[i], InDeltaTime); + } + return Result; + } + + inline const float CalculateAlphaTau(const float InCutoff, const double InDeltaTime) + { + const float tau = 1.0 / (2.0f * PI * InCutoff); + return 1.0f / (1.0f + tau / InDeltaTime); + } +}; + + +/************************************************************************/ +/* 1 Euro filter smoothing algorithm */ +/* http://cristal.univ-lille.fr/~casiez/1euro/ */ +/************************************************************************/ +// A re-implementation of the Euro Low Pass Filter that epic uses for the VR Editor, but for blueprints +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPEuroLowPassFilter +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPEuroLowPassFilter() : + MinCutoff(0.9f), + DeltaCutoff(1.0f), + CutoffSlope(0.007f), + RawFilter(FVector::ZeroVector), + DeltaFilter(FVector::ZeroVector) + {} + + FBPEuroLowPassFilter(const float InMinCutoff, const float InCutoffSlope, const float InDeltaCutoff) : + MinCutoff(InMinCutoff), + DeltaCutoff(InDeltaCutoff), + CutoffSlope(InCutoffSlope), + RawFilter(FVector::ZeroVector), + DeltaFilter(FVector::ZeroVector) + {} + + // The smaller the value the less jitter and the more lag with micro movements + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float MinCutoff; + + // If latency is too high with fast movements increase this value + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float DeltaCutoff; + + // This is the magnitude of adjustment + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float CutoffSlope; + + + void ResetSmoothingFilter(); + + /** Smooth vector */ + FVector RunFilterSmoothing(const FVector &InRawValue, const float &InDeltaTime); + +private: + + FBasicLowPassFilter<FVector> RawFilter; + FBasicLowPassFilter<FVector> DeltaFilter; + +}; + +/************************************************************************/ +/* 1 Euro filter smoothing algorithm */ +/* http://cristal.univ-lille.fr/~casiez/1euro/ */ +/************************************************************************/ +// A re-implementation of the Euro Low Pass Filter that epic uses for the VR Editor, but for blueprints +// This version is for Quaternions +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPEuroLowPassFilterQuat +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPEuroLowPassFilterQuat() : + MinCutoff(0.9f), + DeltaCutoff(1.0f), + CutoffSlope(0.007f), + RawFilter(FQuat::Identity), + DeltaFilter(FQuat::Identity) + {} + + FBPEuroLowPassFilterQuat(const float InMinCutoff, const float InCutoffSlope, const float InDeltaCutoff) : + MinCutoff(InMinCutoff), + DeltaCutoff(InDeltaCutoff), + CutoffSlope(InCutoffSlope), + RawFilter(FQuat::Identity), + DeltaFilter(FQuat::Identity) + {} + + // The smaller the value the less jitter and the more lag with micro movements + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float MinCutoff; + + // If latency is too high with fast movements increase this value + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float DeltaCutoff; + + // This is the magnitude of adjustment + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float CutoffSlope; + + void ResetSmoothingFilter(); + + /** Smooth vector */ + FQuat RunFilterSmoothing(const FQuat& InRawValue, const float& InDeltaTime); + +private: + + FBasicLowPassFilter<FQuat> RawFilter; + FBasicLowPassFilter<FQuat> DeltaFilter; + +}; + +/************************************************************************/ +/* 1 Euro filter smoothing algorithm */ +/* http://cristal.univ-lille.fr/~casiez/1euro/ */ +/************************************************************************/ +// A re-implementation of the Euro Low Pass Filter that epic uses for the VR Editor, but for blueprints +// This version is for Transforms +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPEuroLowPassFilterTrans +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPEuroLowPassFilterTrans() : + MinCutoff(0.1f), + DeltaCutoff(10.0f), + CutoffSlope(10.0f), + RawFilter(FTransform::Identity), + DeltaFilter(FTransform::Identity) + {} + + FBPEuroLowPassFilterTrans(const float InMinCutoff, const float InCutoffSlope, const float InDeltaCutoff) : + MinCutoff(InMinCutoff), + DeltaCutoff(InDeltaCutoff), + CutoffSlope(InCutoffSlope), + RawFilter(FTransform::Identity), + DeltaFilter(FTransform::Identity) + {} + + // The smaller the value the less jitter and the more lag with micro movements + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float MinCutoff; + + // If latency is too high with fast movements increase this value + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float DeltaCutoff; + + // This is the magnitude of adjustment + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "FilterSettings") + float CutoffSlope; + + void ResetSmoothingFilter(); + + /** Smooth vector */ + FTransform RunFilterSmoothing(const FTransform& InRawValue, const float& InDeltaTime); + +private: + + FBasicLowPassFilter<FTransform> RawFilter; + FBasicLowPassFilter<FTransform> DeltaFilter; + +}; + +// The type of velocity tracking to perform on the motion controllers +UENUM(BlueprintType) +enum class EVRVelocityType : uint8 +{ + // Gets the frame by frame velocity + VRLOCITY_Default UMETA(DisplayName = "Default"), + + // Gets a running average velocity across a sample duration + VRLOCITY_RunningAverage UMETA(DisplayName = "Running Average"), + + // Gets the peak velocity across a sample duration + VRLOCITY_SamplePeak UMETA(DisplayName = "Sampled Peak") +}; + +// A structure used to store and calculate velocities in different ways +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPLowPassPeakFilter +{ + GENERATED_BODY() +public: + + /** Default constructor */ + FBPLowPassPeakFilter() : + VelocitySamples(30), + VelocitySampleLogCounter(0) + {} + + // This is the number of samples to keep active + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Samples") + int32 VelocitySamples; + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Samples") + TArray<FVector>VelocitySampleLog; + + int32 VelocitySampleLogCounter; + + void Reset() + { + VelocitySampleLog.Reset(VelocitySamples); + } + + void AddSample(FVector NewSample) + { + if (VelocitySamples <= 0) + return; + + if (VelocitySampleLog.Num() != VelocitySamples) + { + VelocitySampleLog.Reset(VelocitySamples); + VelocitySampleLog.AddZeroed(VelocitySamples); + VelocitySampleLogCounter = 0; + } + + VelocitySampleLog[VelocitySampleLogCounter] = NewSample; + ++VelocitySampleLogCounter; + + if (VelocitySampleLogCounter >= VelocitySamples) + VelocitySampleLogCounter = 0; + } + + FVector GetPeak() const + { + FVector MaxValue = FVector::ZeroVector; + float ValueSizeSq = 0.f; + float CurSizeSq = 0.f; + + for (int i = 0; i < VelocitySampleLog.Num(); i++) + { + CurSizeSq = VelocitySampleLog[i].SizeSquared(); + if (CurSizeSq > ValueSizeSq) + { + MaxValue = VelocitySampleLog[i]; + ValueSizeSq = CurSizeSq; + } + } + + return MaxValue; + } +}; + +// Some static vars so we don't have to keep calculating these for our Smallest Three compression +namespace TransNetQuant +{ + static const float MinimumQ = -1.0f / 1.414214f; + static const float MaximumQ = +1.0f / 1.414214f; + static const float MinMaxQDiff = TransNetQuant::MaximumQ - TransNetQuant::MinimumQ; +} + +USTRUCT(/*noexport, */BlueprintType, Category = "VRExpansionLibrary|TransformNetQuantize", meta = (HasNativeMake = "VRExpansionPlugin.VRExpansionFunctionLibrary.MakeTransform_NetQuantize", HasNativeBreak = "VRExpansionPlugin.VRExpansionFunctionLibrary.BreakTransform_NetQuantize")) +struct FTransform_NetQuantize : public FTransform +{ + GENERATED_USTRUCT_BODY() + + FORCEINLINE FTransform_NetQuantize() : FTransform() + {} + + FORCEINLINE explicit FTransform_NetQuantize(ENoInit Init) : FTransform(Init) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FVector& InTranslation) : FTransform(InTranslation) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FQuat& InRotation) : FTransform(InRotation) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FRotator& InRotation) : FTransform(InRotation) + {} + + FORCEINLINE FTransform_NetQuantize(const FQuat& InRotation, const FVector& InTranslation, const FVector& InScale3D = FVector::OneVector) + : FTransform(InRotation, InTranslation, InScale3D) + {} + + FORCEINLINE FTransform_NetQuantize(const FRotator& InRotation, const FVector& InTranslation, const FVector& InScale3D = FVector::OneVector) + : FTransform(InRotation, InTranslation, InScale3D) + {} + + FORCEINLINE FTransform_NetQuantize(const FTransform& InTransform) : FTransform(InTransform) + {} + + FORCEINLINE explicit FTransform_NetQuantize(const FMatrix& InMatrix) : FTransform(InMatrix) + {} + + FORCEINLINE FTransform_NetQuantize(const FVector& InX, const FVector& InY, const FVector& InZ, const FVector& InTranslation) + : FTransform(InX, InY, InZ, InTranslation) + {} +public: + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + + // Serializes a quaternion with the Smallest Three alg + // Referencing the implementation from https://gafferongames.com/post/snapshot_compression/ + // Which appears to be the mostly widely referenced method + // Template variable is number of bits per element (IE: precision), lowest suggested is 9 + // While I wouldn't go to 16 as at that point it is 2 bits more expensive than FRotator::SerializeShort + // Due to the overhead 2 bits of sending out the largest index, a good default is likely 9-10 bits. + template <uint32 bits> + static bool SerializeQuat_SmallestThree(FArchive& Ar, FQuat &InQuat) + { + check(bits > 1 && bits <= 32); + + uint32 IntegerA = 0, IntegerB = 0, IntegerC = 0, LargestIndex = 0; + + // Get our scaler to not chop off the values + const float scale = float((1 << bits) - 1); + + if (Ar.IsSaving()) + { + InQuat.Normalize(); + const float abs_x = FMath::Abs(InQuat.X); + const float abs_y = FMath::Abs(InQuat.Y); + const float abs_z = FMath::Abs(InQuat.Z); + const float abs_w = FMath::Abs(InQuat.W); + + LargestIndex = 0; + float largest_value = abs_x; + + if (abs_y > largest_value) + { + LargestIndex = 1; + largest_value = abs_y; + } + + if (abs_z > largest_value) + { + LargestIndex = 2; + largest_value = abs_z; + } + + if (abs_w > largest_value) + { + LargestIndex = 3; + largest_value = abs_w; + } + + float a = 0.f; + float b = 0.f; + float c = 0.f; + + switch (LargestIndex) + { + case 0: + if (InQuat.X >= 0) + { + a = InQuat.Y; + b = InQuat.Z; + c = InQuat.W; + } + else + { + a = -InQuat.Y; + b = -InQuat.Z; + c = -InQuat.W; + } + break; + + case 1: + if (InQuat.Y >= 0) + { + a = InQuat.X; + b = InQuat.Z; + c = InQuat.W; + } + else + { + a = -InQuat.X; + b = -InQuat.Z; + c = -InQuat.W; + } + break; + + case 2: + if (InQuat.Z >= 0) + { + a = InQuat.X; + b = InQuat.Y; + c = InQuat.W; + } + else + { + a = -InQuat.X; + b = -InQuat.Y; + c = -InQuat.W; + } + break; + + case 3: + if (InQuat.W >= 0) + { + a = InQuat.X; + b = InQuat.Y; + c = InQuat.Z; + } + else + { + a = -InQuat.X; + b = -InQuat.Y; + c = -InQuat.Z; + } + break; + + default:break; + } + + const float normal_a = (a - TransNetQuant::MinimumQ) / (TransNetQuant::MinMaxQDiff); + const float normal_b = (b - TransNetQuant::MinimumQ) / (TransNetQuant::MinMaxQDiff); + const float normal_c = (c - TransNetQuant::MinimumQ) / (TransNetQuant::MinMaxQDiff); + + IntegerA = FMath::FloorToInt(normal_a * scale + 0.5f); + IntegerB = FMath::FloorToInt(normal_b * scale + 0.5f); + IntegerC = FMath::FloorToInt(normal_c * scale + 0.5f); + } + + // Serialize the bits + Ar.SerializeBits(&LargestIndex, 2); + Ar.SerializeBits(&IntegerA, bits); + Ar.SerializeBits(&IntegerB, bits); + Ar.SerializeBits(&IntegerC, bits); + + if (Ar.IsLoading()) + { + const float inverse_scale = 1.0f / scale; + + const float a = IntegerA * inverse_scale * (TransNetQuant::MinMaxQDiff) + TransNetQuant::MinimumQ; + const float b = IntegerB * inverse_scale * (TransNetQuant::MinMaxQDiff) + TransNetQuant::MinimumQ; + const float c = IntegerC * inverse_scale * (TransNetQuant::MinMaxQDiff) + TransNetQuant::MinimumQ; + + switch (LargestIndex) + { + case 0: + { + InQuat.X = FMath::Sqrt(1.f - a * a - b * b - c * c); + InQuat.Y = a; + InQuat.Z = b; + InQuat.W = c; + } + break; + + case 1: + { + InQuat.X = a; + InQuat.Y = FMath::Sqrt(1.f - a * a - b * b - c * c); + InQuat.Z = b; + InQuat.W = c; + } + break; + + case 2: + { + InQuat.X = a; + InQuat.Y = b; + InQuat.Z = FMath::Sqrt(1.f - a * a - b * b - c * c); + InQuat.W = c; + } + break; + + case 3: + { + InQuat.X = a; + InQuat.Y = b; + InQuat.Z = c; + InQuat.W = FMath::Sqrt(1.f - a * a - b * b - c * c); + } + break; + + default: + { + InQuat.X = 0.f; + InQuat.Y = 0.f; + InQuat.Z = 0.f; + InQuat.W = 1.f; + } + } + + InQuat.Normalize(); + } + + return true; + } +}; + +template<> +struct TStructOpsTypeTraits< FTransform_NetQuantize > : public TStructOpsTypeTraitsBase2<FTransform_NetQuantize> +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +UENUM() +enum class EVRVectorQuantization : uint8 +{ + /** Each vector component will be rounded, preserving one decimal place. */ + RoundOneDecimal = 0, + /** Each vector component will be rounded, preserving two decimal places. */ + RoundTwoDecimals = 1 +}; + +UENUM() +enum class EVRRotationQuantization : uint8 +{ + /** Each rotation component will be rounded to 10 bits (1024 values). */ + RoundTo10Bits = 0, + /** Each rotation component will be rounded to a short. */ + RoundToShort = 1 +}; + + +USTRUCT() +struct VREXPANSIONPLUGIN_API FBPVRComponentPosRep +{ + GENERATED_USTRUCT_BODY() +public: + + UPROPERTY(Transient) + FVector Position; + UPROPERTY(Transient) + FRotator Rotation; + + // The quantization level to use for the vector components + UPROPERTY(EditDefaultsOnly, Category = Replication, AdvancedDisplay) + EVRVectorQuantization QuantizationLevel; + + // The quantization level to use for the rotation components + // Using 10 bits mode saves approx 2.25 bytes per replication. + UPROPERTY(EditDefaultsOnly, Category = Replication, AdvancedDisplay) + EVRRotationQuantization RotationQuantizationLevel; + + FORCEINLINE uint16 CompressAxisTo10BitShort(float Angle) + { + // map [0->360) to [0->1024) and mask off any winding + return FMath::RoundToInt(Angle * 1024.f / 360.f) & 0xFFFF; + } + + + FORCEINLINE float DecompressAxisFrom10BitShort(uint16 Angle) + { + // map [0->1024) to [0->360) + return (Angle * 360.f / 1024.f); + } + + FBPVRComponentPosRep(): + QuantizationLevel(EVRVectorQuantization::RoundTwoDecimals), + RotationQuantizationLevel(EVRRotationQuantization::RoundToShort) + { + //QuantizationLevel = EVRVectorQuantization::RoundTwoDecimals; + Position = FVector::ZeroVector; + Rotation = FRotator::ZeroRotator; + } + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + // Defines the level of Quantization + //uint8 Flags = (uint8)QuantizationLevel; + Ar.SerializeBits(&QuantizationLevel, 1); // Only two values 0:1 + Ar.SerializeBits(&RotationQuantizationLevel, 1); // Only two values 0:1 + + // No longer using their built in rotation rep, as controllers will rarely if ever be at 0 rot on an axis and + // so the 1 bit overhead per axis is just that, overhead + //Rotation.SerializeCompressedShort(Ar); + + uint16 ShortPitch = 0; + uint16 ShortYaw = 0; + uint16 ShortRoll = 0; + + /** + * Valid range 100: 2^22 / 100 = +/- 41,943.04 (419.43 meters) + * Valid range 10: 2^18 / 10 = +/- 26,214.4 (262.144 meters) + * Pos rep is assumed to be in relative space for a tracked component, these numbers should be fine + */ + if (Ar.IsSaving()) + { + switch (QuantizationLevel) + { + case EVRVectorQuantization::RoundTwoDecimals: bOutSuccess &= SerializePackedVector<100, 22/*30*/>(Position, Ar); break; + case EVRVectorQuantization::RoundOneDecimal: bOutSuccess &= SerializePackedVector<10, 18/*24*/>(Position, Ar); break; + } + + switch (RotationQuantizationLevel) + { + case EVRRotationQuantization::RoundTo10Bits: + { + ShortPitch = CompressAxisTo10BitShort(Rotation.Pitch); + ShortYaw = CompressAxisTo10BitShort(Rotation.Yaw); + ShortRoll = CompressAxisTo10BitShort(Rotation.Roll); + + Ar.SerializeBits(&ShortPitch, 10); + Ar.SerializeBits(&ShortYaw, 10); + Ar.SerializeBits(&ShortRoll, 10); + }break; + + case EVRRotationQuantization::RoundToShort: + { + ShortPitch = FRotator::CompressAxisToShort(Rotation.Pitch); + ShortYaw = FRotator::CompressAxisToShort(Rotation.Yaw); + ShortRoll = FRotator::CompressAxisToShort(Rotation.Roll); + + Ar << ShortPitch; + Ar << ShortYaw; + Ar << ShortRoll; + }break; + } + } + else // If loading + { + //QuantizationLevel = (EVRVectorQuantization)Flags; + + switch (QuantizationLevel) + { + case EVRVectorQuantization::RoundTwoDecimals: bOutSuccess &= SerializePackedVector<100, 22/*30*/>(Position, Ar); break; + case EVRVectorQuantization::RoundOneDecimal: bOutSuccess &= SerializePackedVector<10, 18/*24*/>(Position, Ar); break; + } + + switch (RotationQuantizationLevel) + { + case EVRRotationQuantization::RoundTo10Bits: + { + Ar.SerializeBits(&ShortPitch, 10); + Ar.SerializeBits(&ShortYaw, 10); + Ar.SerializeBits(&ShortRoll, 10); + + Rotation.Pitch = DecompressAxisFrom10BitShort(ShortPitch); + Rotation.Yaw = DecompressAxisFrom10BitShort(ShortYaw); + Rotation.Roll = DecompressAxisFrom10BitShort(ShortRoll); + }break; + + case EVRRotationQuantization::RoundToShort: + { + Ar << ShortPitch; + Ar << ShortYaw; + Ar << ShortRoll; + + Rotation.Pitch = FRotator::DecompressAxisFromShort(ShortPitch); + Rotation.Yaw = FRotator::DecompressAxisFromShort(ShortYaw); + Rotation.Roll = FRotator::DecompressAxisFromShort(ShortRoll); + }break; + } + } + + return bOutSuccess; + } + +}; + +template<> +struct TStructOpsTypeTraits< FBPVRComponentPosRep > : public TStructOpsTypeTraitsBase2<FBPVRComponentPosRep> +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +UENUM(Blueprintable) +enum class EGripCollisionType : uint8 +{ + /** Held items can be offset by geometry, uses physics for the offset, pushes physics simulating objects with weight taken into account. */ + InteractiveCollisionWithPhysics, + + // InteractiveCollisionWithVelocity, + + /** Held items can be offset by geometry, uses sweep for the offset, pushes physics simulating objects, no weight. */ + InteractiveCollisionWithSweep, + + /** Uses Stiffness and damping settings on collision, on no collision uses stiffness values 10x stronger so it has less play. */ + InteractiveHybridCollisionWithPhysics, + + /** Swaps back and forth between physics grip and a sweep type grip depending on if the held object will be colliding this frame or not. */ + InteractiveHybridCollisionWithSweep, + + /** Only sweeps movement, will not be offset by geomtry, still pushes physics simulating objects, no weight. */ + SweepWithPhysics, + + /** Does not sweep at all (does not trigger OnHitEvents), still pushes physics simulating objects, no weight. */ + PhysicsOnly, + + /** Free constraint to controller base, no rotational drives. */ + ManipulationGrip, + + /** Free constraint to controller base with a twist drive. */ + ManipulationGripWithWristTwist, + + /** Attachment grips use native attachment and only sets location / rotation if they differ, this grip always late updates*/ + AttachmentGrip, + + /** Custom grip is to be handled by the object itself, it just sends the TickGrip event every frame but doesn't move the object. */ + CustomGrip, + + /** A grip that does not tick or move, used for drop / grip events only and uses least amount of processing. */ + EventsOnly, + + /** Uses a hard constraint with no softness to lock them together, best used with ConstrainToPivot enabled and a bone chain. */ + LockedConstraint + +}; + +// This needs to be updated as the original gets changed, that or hope they make the original blueprint accessible. +UENUM(Blueprintable) +enum class EBPHMDDeviceType : uint8 +{ + DT_OculusHMD,//Rift, + DT_PSVR, + //DT_Morpheus, + DT_ES2GenericStereoMesh, + DT_SteamVR, + DT_GearVR, + DT_GoogleVR, + DT_AppleARKit, + DT_GoogleARCore, + DT_Unknown +}; + +// Lerp states +UENUM(Blueprintable) +enum class EGripLerpState : uint8 +{ + StartLerp, + EndLerp, + //ConstantLerp_DEPRECATED, + NotLerping +}; + +// Secondary Grip Type +UENUM(Blueprintable) +enum class ESecondaryGripType : uint8 +{ + // No secondary grip + SG_None, + // Free secondary grip + SG_Free, + // Only secondary grip at a slot + SG_SlotOnly, + // Retain pos on drop + SG_Free_Retain, + // Retain pos on drop, slot only + SG_SlotOnly_Retain, + // Scaling with retain pos on drop + SG_FreeWithScaling_Retain, + // Scaling with retain pos on drop, slot only + SG_SlotOnlyWithScaling_Retain, + // Does nothing, just provides the events for personal use + SG_Custom, + // Does not track the hand, only scales the mesh with it + SG_ScalingOnly, +}; + +// Grip Late Update information +UENUM(Blueprintable) +enum class EGripLateUpdateSettings : uint8 +{ + LateUpdatesAlwaysOn, + LateUpdatesAlwaysOff, + NotWhenColliding, + NotWhenDoubleGripping, + NotWhenCollidingOrDoubleGripping +}; + +// Grip movement replication settings +// LocalOnly_Not_Replicated is useful for instant client grips +// that can be sent to the server and everyone locally grips it (IE: inventories that don't ever leave a player) +// Objects that need to be handled possibly by multiple players should be ran +// non locally gripped instead so that the server can validate grips instead. +// ClientSide_Authoritive will grip on the client instantly without server intervention and then send a notice to the server +// ClientSide_Authoritive_NoRep will grip on the client instantly without server intervention but will not rep the grip to the server +// that the grip was made +UENUM(Blueprintable) +enum class EGripMovementReplicationSettings : uint8 +{ + KeepOriginalMovement, + ForceServerSideMovement, + ForceClientSideMovement, + ClientSide_Authoritive, + ClientSide_Authoritive_NoRep +}; + +// Grip Target Type +UENUM(Blueprintable) +enum class EGripTargetType : uint8 +{ + ActorGrip, + ComponentGrip + //InteractibleActorGrip, + //InteractibleComponentGrip +}; + +// Lerp states +UENUM(Blueprintable) +enum class EGripInterfaceTeleportBehavior : uint8 +{ + /* Teleports entire actor */ + TeleportAllComponents, + /* Teleports by the location delta and not the calculated new position of the grip, useful for rag dolls*/ + DeltaTeleportation, + /* Only teleports an actor if the root component is held */ + OnlyTeleportRootComponent, + /* Just drop the grip on teleport */ + DropOnTeleport, + /* Teleporting is not allowed */ + DontTeleport +}; + +// Type of physics constraint to use +UENUM(Blueprintable) +enum class EPhysicsGripConstraintType : uint8 +{ + AccelerationConstraint = 0, + ForceConstraint = 1 +}; + +UENUM(Blueprintable) +enum class EPhysicsGripCOMType : uint8 +{ + /* Use the default setting for the specified grip type */ + COM_Default = 0, + /* Don't grip at center of mass (generally unstable as it grips at actor zero)*/ + COM_AtPivot = 1, + /* Set center of mass to grip location and grip there (default for interactible with physics) */ + COM_SetAndGripAt = 2, + /* Grip at center of mass but do not set it */ + COM_GripAt = 3, + /* Just grip at the controller location, but don't set COM (default for manipulation grips)*/ + COM_GripAtControllerLoc = 4 +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvGripPhysicsSettings +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings") + bool bUsePhysicsSettings; + + // Set the constraint force mode + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + EPhysicsGripConstraintType PhysicsConstraintType; + + // Set how the grips handle center of mass + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + EPhysicsGripCOMType PhysicsGripLocationSettings; + + // Turn off gravity during the grip, resolves the slight downward offset of the object with normal constraint strengths. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + bool bTurnOffGravityDuringGrip; + + // Don't automatically (un)simulate the component/root on grip/drop, let the end user set it up instead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + bool bSkipSettingSimulating; + + // A multiplier to add to the stiffness of a grip that is then set as the MaxForce of the grip + // It is clamped between 0.00 and 256.00 to save in replication cost, a value of 0 will mean max force is infinite as it will multiply it to zero (legacy behavior) + // If you want an exact value you can figure it out as a factor of the stiffness, also Max force can be directly edited with SetAdvancedConstraintSettings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings"), meta = (ClampMin = "0.00", UIMin = "0.00", ClampMax = "256.00", UIMax = "256.00")) + float LinearMaxForceCoefficient; + + // A multiplier to add to the stiffness of a grip that is then set as the MaxForce of the grip + // It is clamped between 0.00 and 256.00 to save in replication cost, a value of 0 will mean max force is infinite as it will multiply it to zero (legacy behavior) + // If you want an exact value you can figure it out as a factor of the stiffness, also Max force can be directly edited with SetAdvancedConstraintSettings + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings"), meta = (ClampMin = "0.00", UIMin = "0.00", ClampMax = "256.00", UIMax = "256.00")) + float AngularMaxForceCoefficient; + + // Use the custom angular values on this grip + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUsePhysicsSettings")) + bool bUseCustomAngularValues; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUseCustomAngularValues", ClampMin = "0.000", UIMin = "0.000")) + float AngularStiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (editcondition = "bUseCustomAngularValues", ClampMin = "0.000", UIMin = "0.000")) + float AngularDamping; + + FBPAdvGripPhysicsSettings(): + bUsePhysicsSettings(false), + PhysicsConstraintType(EPhysicsGripConstraintType::AccelerationConstraint), + PhysicsGripLocationSettings(EPhysicsGripCOMType::COM_Default), + bTurnOffGravityDuringGrip(false), + bSkipSettingSimulating(false), + LinearMaxForceCoefficient(0.f), + AngularMaxForceCoefficient(0.f), + bUseCustomAngularValues(false), + AngularStiffness(0.0f), + AngularDamping(0.0f)//, + //MaxForce(0.f) + {} + + FORCEINLINE bool operator==(const FBPAdvGripPhysicsSettings &Other) const + { + return (bUsePhysicsSettings == Other.bUsePhysicsSettings && + PhysicsGripLocationSettings == Other.PhysicsGripLocationSettings && + bTurnOffGravityDuringGrip == Other.bTurnOffGravityDuringGrip && + bSkipSettingSimulating == Other.bSkipSettingSimulating && + bUseCustomAngularValues == Other.bUseCustomAngularValues && + PhysicsConstraintType == Other.PhysicsConstraintType && + FMath::IsNearlyEqual(LinearMaxForceCoefficient, Other.LinearMaxForceCoefficient) && + FMath::IsNearlyEqual(AngularMaxForceCoefficient, Other.AngularMaxForceCoefficient) && + FMath::IsNearlyEqual(AngularStiffness, Other.AngularStiffness) && + FMath::IsNearlyEqual(AngularDamping, Other.AngularDamping) //&& + //FMath::IsNearlyEqual(MaxForce, Other.MaxForce) + ); + } + + FORCEINLINE bool operator!=(const FBPAdvGripPhysicsSettings &Other) const + { + return (bUsePhysicsSettings != Other.bUsePhysicsSettings || + PhysicsGripLocationSettings != Other.PhysicsGripLocationSettings || + bTurnOffGravityDuringGrip != Other.bTurnOffGravityDuringGrip || + bSkipSettingSimulating != Other.bSkipSettingSimulating || + bUseCustomAngularValues != Other.bUseCustomAngularValues || + PhysicsConstraintType != Other.PhysicsConstraintType || + !FMath::IsNearlyEqual(LinearMaxForceCoefficient, Other.LinearMaxForceCoefficient) || + !FMath::IsNearlyEqual(AngularMaxForceCoefficient, Other.AngularMaxForceCoefficient) || + !FMath::IsNearlyEqual(AngularStiffness, Other.AngularStiffness) || + !FMath::IsNearlyEqual(AngularDamping, Other.AngularDamping) //|| + //!FMath::IsNearlyEqual(MaxForce, Other.MaxForce) + ); + } + + /** Network serialization */ + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + //Ar << bUsePhysicsSettings; + Ar.SerializeBits(&bUsePhysicsSettings, 1); + + if (bUsePhysicsSettings) + { + //Ar << bDoNotSetCOMToGripLocation; + Ar.SerializeBits(&PhysicsGripLocationSettings, 3); // This only has four elements + + //Ar << PhysicsConstraintType; + Ar.SerializeBits(&PhysicsConstraintType, 1); // This only has two elements + + //Ar << bTurnOffGravityDuringGrip; + Ar.SerializeBits(&bTurnOffGravityDuringGrip, 1); + Ar.SerializeBits(&bSkipSettingSimulating, 1); + + + // This is 0.0 - 256.0, using compression to get it smaller, 8 bits = max 256 + 1 bit for sign and 7 bits precision for 128 / full 2 digit precision + if (Ar.IsSaving()) + { + bOutSuccess &= WriteFixedCompressedFloat<256, 16>(LinearMaxForceCoefficient, Ar); + bOutSuccess &= WriteFixedCompressedFloat<256, 16>(AngularMaxForceCoefficient, Ar); + } + else + { + bOutSuccess &= ReadFixedCompressedFloat<256, 16>(LinearMaxForceCoefficient, Ar); + bOutSuccess &= ReadFixedCompressedFloat<256, 16>(AngularMaxForceCoefficient, Ar); + } + + + + //Ar << bUseCustomAngularValues; + Ar.SerializeBits(&bUseCustomAngularValues, 1); + + if (bUseCustomAngularValues) + { + Ar << AngularStiffness; + Ar << AngularDamping; + } + + //Ar << MaxForce; + } + + bOutSuccess = true; + return bOutSuccess; + } +}; + +template<> +struct TStructOpsTypeTraits< FBPAdvGripPhysicsSettings > : public TStructOpsTypeTraitsBase2<FBPAdvGripPhysicsSettings> +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvGripSettings +{ + GENERATED_BODY() +public: + + // Priority of this item when being gripped, (Higher is more priority) + // This lets you prioritize whether an object should be gripped over another one when both + // collide with traces or overlaps. #Note: Currently not implemented in the plugin, here for your use. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + uint8 GripPriority; + + // If true, will set the owner of actor grips on grip + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + bool bSetOwnerOnGrip; + + // If true, we will be bypassed on global lerp operations + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + bool bDisallowLerping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AdvancedGripSettings") + FBPAdvGripPhysicsSettings PhysicsSettings; + + FBPAdvGripSettings() : + GripPriority(1), + bSetOwnerOnGrip(1), + bDisallowLerping(0) + {} + + FBPAdvGripSettings(int GripPrio) : + GripPriority(GripPrio), + bSetOwnerOnGrip(1), + bDisallowLerping(0) + {} +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPSecondaryGripInfo +{ + GENERATED_BODY() +public: + + // For multi grip situations + UPROPERTY(BlueprintReadOnly, Category = "SecondaryGripInfo") + bool bHasSecondaryAttachment; + + UPROPERTY(BlueprintReadOnly, Category = "SecondaryGripInfo") + TObjectPtr<USceneComponent> SecondaryAttachment; + + UPROPERTY(BlueprintReadOnly, Category = "SecondaryGripInfo") + FTransform_NetQuantize SecondaryRelativeTransform; + + UPROPERTY(BlueprintReadWrite, Category = "SecondaryGripInfo") + bool bIsSlotGrip; + + UPROPERTY(BlueprintReadWrite, Category = "SecondaryGripInfo") + FName SecondarySlotName; + + // Lerp transitions + // Max value is 16 seconds with two decimal precision, this is to reduce replication overhead + UPROPERTY() + float LerpToRate; + + // Filled in from the tick code so users can activate and deactivate grips based on this + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "SecondaryGripInfo") + float SecondaryGripDistance; + + // These are not replicated, they don't need to be + EGripLerpState GripLerpState; + float curLerp; + + // Store values for frame by frame changes of secondary grips + FVector LastRelativeLocation; + + void ClearNonReppingItems() + { + SecondaryGripDistance = 0.0f; + GripLerpState = EGripLerpState::NotLerping; + curLerp = 0.0f; + } + + FBPSecondaryGripInfo(): + bHasSecondaryAttachment(false), + SecondaryAttachment(nullptr), + SecondaryRelativeTransform(FTransform::Identity), + bIsSlotGrip(false), + SecondarySlotName(NAME_None), + LerpToRate(0.0f), + SecondaryGripDistance(0.0f), + GripLerpState(EGripLerpState::NotLerping), + curLerp(0.0f), + LastRelativeLocation(FVector::ZeroVector) + {} + + // Adding this override to handle the fact that repped versions don't send relative loc and slot grip + // We don't want to override relative loc with 0,0,0 when it is in end lerp as otherwise it lerps wrong + FORCEINLINE FBPSecondaryGripInfo& RepCopy(const FBPSecondaryGripInfo& Other) + { + this->bHasSecondaryAttachment = Other.bHasSecondaryAttachment; + this->SecondaryAttachment = Other.SecondaryAttachment; + + if (bHasSecondaryAttachment) + { + this->SecondaryRelativeTransform = Other.SecondaryRelativeTransform; + this->bIsSlotGrip = Other.bIsSlotGrip; + this->SecondarySlotName = Other.SecondarySlotName; + } + + this->LerpToRate = Other.LerpToRate; + return *this; + } + + /** Network serialization */ + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + //Ar << bHasSecondaryAttachment; + Ar.SerializeBits(&bHasSecondaryAttachment, 1); + + if (bHasSecondaryAttachment) + { + Ar << SecondaryAttachment; + //Ar << SecondaryRelativeLocation; + SecondaryRelativeTransform.NetSerialize(Ar, Map, bOutSuccess); + + //Ar << bIsSlotGrip; + Ar.SerializeBits(&bIsSlotGrip, 1); + + Ar << SecondarySlotName; + } + + // This is 0.0 - 16.0, using compression to get it smaller, 4 bits = max 16 + 1 bit for sign and 7 bits precision for 128 / full 2 digit precision + if (Ar.IsSaving()) + bOutSuccess &= WriteFixedCompressedFloat<16, 12>(LerpToRate, Ar); + else + bOutSuccess &= ReadFixedCompressedFloat<16, 12>(LerpToRate, Ar); + + //Ar << LerpToRate; + return true; + } +}; + +template<> +struct TStructOpsTypeTraits< FBPSecondaryGripInfo > : public TStructOpsTypeTraitsBase2<FBPSecondaryGripInfo> +{ + enum + { + WithNetSerializer = true + }; +}; + +#define INVALID_VRGRIP_ID 0 + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPActorGripInformation +{ + GENERATED_BODY() +public: + + // Hashed unique ID to identify this grip instance + UPROPERTY(BlueprintReadOnly, Category = "Settings") + uint8 GripID; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + EGripTargetType GripTargetType; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + TObjectPtr<UObject> GrippedObject; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + EGripCollisionType GripCollisionType; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + EGripLateUpdateSettings GripLateUpdateSetting; + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "Settings") + bool bColliding; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + FTransform_NetQuantize RelativeTransform; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + bool bIsSlotGrip; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + FName GrippedBoneName; + UPROPERTY(BlueprintReadWrite, Category = "Settings") + FName SlotName; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + EGripMovementReplicationSettings GripMovementReplicationSetting; + + // Whether the grip is currently paused + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "Settings") + bool bIsPaused; + + // Only true in one specific circumstance, when you are a simulated client + // and the grip has been dropped but replication on the array hasn't deleted + // the entry yet. We cannot remove the entry as it can corrupt the array. + // this lets end users check against the grip to ignore it. + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "Settings") + bool bIsPendingKill; + + // When true, will lock a hybrid grip into its collision state + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "Settings") + bool bLockHybridGrip; + + // I would have loved to have both of these not be replicated (and in normal grips they wouldn't have to be) + // However for serialization purposes and Client_Authority grips they need to be.... + UPROPERTY() + bool bOriginalReplicatesMovement; + UPROPERTY() + bool bOriginalGravity; + + UPROPERTY(BlueprintReadOnly, Category = "Settings") + float Damping; + UPROPERTY(BlueprintReadOnly, Category = "Settings") + float Stiffness; + + UPROPERTY(BlueprintReadOnly, Category = "Settings") + FBPAdvGripSettings AdvancedGripSettings; + + // For multi grip situations + UPROPERTY(BlueprintReadOnly, Category = "Settings") + FBPSecondaryGripInfo SecondaryGripInfo; + + // Optional Additive Transform for programmatic animation + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "Settings") + FTransform AdditionTransform; + + // Distance from the target point for the grip + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "Settings") + float GripDistance; + + // Locked transitions for swept movement so they don't just rotate in place on contact + bool bIsLocked; + FQuat LastLockedRotation; + + // For delta teleport and any future calculations we want to do + FTransform LastWorldTransform; + bool bSetLastWorldTransform; + + // Need to skip one frame of length check post teleport with constrained objects, the constraint may have not been updated yet. + bool bSkipNextTeleportCheck; + + // Need to skip one frame of length check post teleport with constrained objects, the constraint may have not been updated yet. + bool bSkipNextConstraintLengthCheck; + + // Lerp settings if we are using global lerping + float CurrentLerpTime; + float LerpSpeed; + FTransform OnGripTransform; + + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "Settings") + bool bIsLerping; + + bool IsLocalAuthGrip() + { + return GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive || GripMovementReplicationSetting == EGripMovementReplicationSettings::ClientSide_Authoritive_NoRep; + } + + // If the grip is valid + bool IsValid() const + { + return (!bIsPendingKill && GripID != INVALID_VRGRIP_ID && GrippedObject && IsValidChecked(GrippedObject)); + } + + // Both valid and is not paused + bool IsActive() const + { + return (!bIsPendingKill && GripID != INVALID_VRGRIP_ID && GrippedObject && IsValidChecked(GrippedObject) && !bIsPaused); + } + + // Cached values - since not using a full serialize now the old array state may not contain what i need to diff + // I set these in On_Rep now and check against them when new replications happen to control some actions. + struct FGripValueCache + { + bool bWasInitiallyRepped; + uint8 CachedGripID; + + FGripValueCache() : + bWasInitiallyRepped(false), + CachedGripID(INVALID_VRGRIP_ID) + {} + + }ValueCache; + + void ClearNonReppingItems() + { + ValueCache = FGripValueCache(); + bColliding = false; + bIsLocked = false; + LastLockedRotation = FQuat::Identity; + LastWorldTransform.SetIdentity(); + bSetLastWorldTransform = false; + bSkipNextTeleportCheck = false; + bSkipNextConstraintLengthCheck = false; + bIsPaused = false; + bIsPendingKill = false; + bLockHybridGrip = false; + AdditionTransform = FTransform::Identity; + GripDistance = 0.0f; + CurrentLerpTime = 0.f; + LerpSpeed = 0.f; + OnGripTransform = FTransform::Identity; + bIsLerping = false; + + // Clear out the secondary grip + SecondaryGripInfo.ClearNonReppingItems(); + } + + // Adding this override to keep un-repped variables from repping over from Client Auth grips + FORCEINLINE FBPActorGripInformation& RepCopy(const FBPActorGripInformation& Other) + { + this->GripID = Other.GripID; + this->GripTargetType = Other.GripTargetType; + this->GrippedObject = Other.GrippedObject; + this->GripCollisionType = Other.GripCollisionType; + this->GripLateUpdateSetting = Other.GripLateUpdateSetting; + this->RelativeTransform = Other.RelativeTransform; + this->bIsSlotGrip = Other.bIsSlotGrip; + this->GrippedBoneName = Other.GrippedBoneName; + this->SlotName = Other.SlotName; + this->GripMovementReplicationSetting = Other.GripMovementReplicationSetting; + //this->bOriginalReplicatesMovement = Other.bOriginalReplicatesMovement; + //this->bOriginalGravity = Other.bOriginalGravity; + this->Damping = Other.Damping; + this->Stiffness = Other.Stiffness; + this->AdvancedGripSettings = Other.AdvancedGripSettings; + this->SecondaryGripInfo.RepCopy(Other.SecondaryGripInfo); // Run the replication copy version so we don't overwrite vars + //this->SecondaryGripInfo = Other.SecondaryGripInfo; + + return *this; + } + + + FORCEINLINE AActor * GetGrippedActor() const + { + return Cast<AActor>(GrippedObject); + } + + FORCEINLINE UPrimitiveComponent * GetGrippedComponent() const + { + return Cast<UPrimitiveComponent>(GrippedObject); + } + + //Check if a grip is the same as another, the only things I check for are the actor / component + //This is here for the Find() function from TArray + FORCEINLINE bool operator==(const FBPActorGripInformation &Other) const + { + if ((GripID != INVALID_VRGRIP_ID) && (GripID == Other.GripID) ) + return true; + //if (GrippedObject && GrippedObject == Other.GrippedObject) + //return true; + + return false; + } + + FORCEINLINE bool operator==(const AActor * Other) const + { + if (Other && GrippedObject && GrippedObject == (const UObject*)Other) + return true; + + return false; + } + + FORCEINLINE bool operator==(const UPrimitiveComponent * Other) const + { + if (Other && GrippedObject && GrippedObject == (const UObject*)Other) + return true; + + return false; + } + + FORCEINLINE bool operator==(const UObject * Other) const + { + if (Other && GrippedObject == Other) + return true; + + return false; + } + + FORCEINLINE bool operator==(const uint8& Other) const + { + if ((GripID != INVALID_VRGRIP_ID) && (GripID == Other)) + return true; + + return false; + } + + FBPActorGripInformation() : + GripID(INVALID_VRGRIP_ID), + GripTargetType(EGripTargetType::ActorGrip), + GrippedObject(nullptr), + GripCollisionType(EGripCollisionType::InteractiveCollisionWithPhysics), + GripLateUpdateSetting(EGripLateUpdateSettings::NotWhenCollidingOrDoubleGripping), + bColliding(false), + RelativeTransform(FTransform::Identity), + bIsSlotGrip(false), + GrippedBoneName(NAME_None), + SlotName(NAME_None), + GripMovementReplicationSetting(EGripMovementReplicationSettings::ForceClientSideMovement), + bIsPaused(false), + bIsPendingKill(false), + bLockHybridGrip(false), + bOriginalReplicatesMovement(false), + bOriginalGravity(false), + Damping(200.0f), + Stiffness(1500.0f), + AdditionTransform(FTransform::Identity), + GripDistance(0.0f), + bIsLocked(false), + LastLockedRotation(FRotator::ZeroRotator), + LastWorldTransform(FTransform::Identity), + bSetLastWorldTransform(false), + bSkipNextTeleportCheck(false), + bSkipNextConstraintLengthCheck(false), + CurrentLerpTime(0.f), + LerpSpeed(0.f), + OnGripTransform(FTransform::Identity), + bIsLerping(false) + { + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPGripPair +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripPair") + TObjectPtr<UGripMotionControllerComponent> HoldingController; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GripPair") + uint8 GripID; + + FBPGripPair() : + HoldingController(nullptr), + GripID(INVALID_VRGRIP_ID) + {} + + FBPGripPair(UGripMotionControllerComponent * Controller, uint8 ID) : + HoldingController(Controller), + GripID(ID) + {} + + void Clear() + { + HoldingController = nullptr; + GripID = INVALID_VRGRIP_ID; + } + + bool IsValid() + { + return HoldingController != nullptr && GripID != INVALID_VRGRIP_ID; + } + + FORCEINLINE bool operator==(const FBPGripPair & Other) const + { + return (Other.HoldingController == HoldingController && ((GripID != INVALID_VRGRIP_ID) && (GripID == Other.GripID))); + } + + FORCEINLINE bool operator==(const UGripMotionControllerComponent * Other) const + { + return (Other == HoldingController); + } + + FORCEINLINE bool operator==(const uint8 & Other) const + { + return GripID == Other; + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPInterfaceProperties +{ + GENERATED_BODY() +public: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + bool bDenyGripping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + bool bAllowMultipleGrips; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripInterfaceTeleportBehavior OnTeleportBehavior; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + bool bSimulateOnDrop; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripCollisionType SlotDefaultGripType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripCollisionType FreeDefaultGripType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + ESecondaryGripType SecondaryGripType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripMovementReplicationSettings MovementReplicationType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + EGripLateUpdateSettings LateUpdateSetting; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float ConstraintStiffness; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float ConstraintDamping; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float ConstraintBreakDistance; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float SecondarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface") + float PrimarySlotRange; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRGripInterface|AdvancedGripSettings") + FBPAdvGripSettings AdvancedGripSettings; + + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "VRGripInterface") + bool bIsHeld; // Set on grip notify, not net serializing + + // If this grip was ever held + bool bWasHeld;; + + UPROPERTY(BlueprintReadWrite, NotReplicated, Category = "VRGripInterface") + TArray<FBPGripPair> HoldingControllers; // Set on grip notify, not net serializing + + FBPInterfaceProperties(): + bDenyGripping(false), + bAllowMultipleGrips(false), + OnTeleportBehavior(EGripInterfaceTeleportBehavior::DropOnTeleport), + bSimulateOnDrop(true), + SlotDefaultGripType(EGripCollisionType::ManipulationGrip), + FreeDefaultGripType(EGripCollisionType::ManipulationGrip), + SecondaryGripType(ESecondaryGripType::SG_None), + MovementReplicationType(EGripMovementReplicationSettings::ForceClientSideMovement), + LateUpdateSetting(EGripLateUpdateSettings::LateUpdatesAlwaysOff), + ConstraintStiffness(1500.0f), + ConstraintDamping(200.0f), + ConstraintBreakDistance(0.0f), + SecondarySlotRange(20.0f), + PrimarySlotRange(20.0f), + bIsHeld(false), + bWasHeld(false) + { + } +}; + + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPActorPhysicsHandleInformation +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadOnly, Category = "Settings") + TObjectPtr<UObject> HandledObject; + uint8 GripID; + bool bIsPaused; + + FPhysicsActorHandle KinActorData2; + FPhysicsConstraintHandle HandleData2; + FLinearDriveConstraint LinConstraint; + FAngularDriveConstraint AngConstraint; + + FTransform LastPhysicsTransform; + FTransform COMPosition; + FTransform RootBoneRotation; + + bool bSetCOM; + bool bSkipResettingCom; + bool bSkipDeletingKinematicActor; + bool bInitiallySetup; + + FBPActorPhysicsHandleInformation() + { + HandledObject = nullptr; + LastPhysicsTransform = FTransform::Identity; + COMPosition = FTransform::Identity; + GripID = INVALID_VRGRIP_ID; + bIsPaused = false; + RootBoneRotation = FTransform::Identity; + bSetCOM = false; + bSkipResettingCom = false; + bSkipDeletingKinematicActor = false; + bInitiallySetup = false; +#if WITH_CHAOS + KinActorData2 = nullptr; +#endif + } + + FORCEINLINE bool operator==(const FBPActorGripInformation & Other) const + { + return ((GripID != INVALID_VRGRIP_ID) && (GripID == Other.GripID)); + } + + FORCEINLINE bool operator==(const uint8 & Other) const + { + return ((GripID != INVALID_VRGRIP_ID) && (GripID == Other)); + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvancedPhysicsHandleAxisSettings +{ + GENERATED_BODY() +public: + /** The spring strength of the drive. Force proportional to the position error. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint, meta = (ClampMin = "0.0")) + float Stiffness; + + /** The damping strength of the drive. Force proportional to the velocity error. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint, meta = (ClampMin = "0.0")) + float Damping; + + // A multiplier to add to the stiffness that is then set as the MaxForce + // It is clamped between 0.00 and 256.00 to save in replication cost, a value of 0 will mean max force is infinite as it will multiply it to zero (legacy behavior) + // If you want an exact value you can figure it out as a factor of the stiffness + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PhysicsSettings", meta = (ClampMin = "0.00", UIMin = "0.00", ClampMax = "256.00", UIMax = "256.00")) + float MaxForceCoefficient; + + /** Enables/Disables position drive (orientation if using angular drive)*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint) + bool bEnablePositionDrive; + + /** Enables/Disables velocity drive (damping) (angular velocity if using angular drive) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Constraint) + bool bEnableVelocityDrive; + + FBPAdvancedPhysicsHandleAxisSettings() + { + Stiffness = 0.f; + Damping = 0.f; + MaxForceCoefficient = 0.f; + bEnablePositionDrive = false; + bEnableVelocityDrive = false; + } + + void FillFrom(FConstraintDrive& ConstraintDrive) + { + Damping = ConstraintDrive.Damping; + Stiffness = ConstraintDrive.Stiffness; + MaxForceCoefficient = ConstraintDrive.MaxForce / Stiffness; + bEnablePositionDrive = ConstraintDrive.bEnablePositionDrive; + bEnableVelocityDrive = ConstraintDrive.bEnableVelocityDrive; + } + + void FillTo(FConstraintDrive& ConstraintDrive) const + { + ConstraintDrive.Damping = Damping; + ConstraintDrive.Stiffness = Stiffness; + ConstraintDrive.MaxForce = MaxForceCoefficient * Stiffness; + ConstraintDrive.bEnablePositionDrive = bEnablePositionDrive; + ConstraintDrive.bEnableVelocityDrive = bEnableVelocityDrive; + } + +}; + +USTRUCT(BlueprintType, Category = "VRExpansionLibrary") +struct VREXPANSIONPLUGIN_API FBPAdvancedPhysicsHandleSettings +{ + GENERATED_BODY() +public: + + // The settings for the XAxis + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Linear Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings XAxisSettings; + + // The settings for the YAxis + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Linear Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings YAxisSettings; + + // The settings for the ZAxis + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Linear Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings ZAxisSettings; + + // The settings for the Orientation (Slerp only for now) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Angular Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings SlerpSettings; + + // The settings for the Orientation (Slerp only for now) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Angular Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings TwistSettings; + + // The settings for the Orientation (Slerp only for now) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Angular Constraint Settings") + FBPAdvancedPhysicsHandleAxisSettings SwingSettings; + + + // FConstraintSettings // settings for various things like distance limits + // Add a deletegate bindable in the motion controller + + bool FillFrom(FBPActorPhysicsHandleInformation* HandleInfo) + { + if (!HandleInfo) + return false; + + XAxisSettings.FillFrom(HandleInfo->LinConstraint.XDrive); + YAxisSettings.FillFrom(HandleInfo->LinConstraint.YDrive); + ZAxisSettings.FillFrom(HandleInfo->LinConstraint.ZDrive); + + SlerpSettings.FillFrom(HandleInfo->AngConstraint.SlerpDrive); + TwistSettings.FillFrom(HandleInfo->AngConstraint.TwistDrive); + SwingSettings.FillFrom(HandleInfo->AngConstraint.SwingDrive); + + return true; + } + + bool FillTo(FBPActorPhysicsHandleInformation* HandleInfo) const + { + if (!HandleInfo) + return false; + + XAxisSettings.FillTo(HandleInfo->LinConstraint.XDrive); + YAxisSettings.FillTo(HandleInfo->LinConstraint.YDrive); + ZAxisSettings.FillTo(HandleInfo->LinConstraint.ZDrive); + + if ((SlerpSettings.bEnablePositionDrive || SlerpSettings.bEnableVelocityDrive)) + { + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::SLERP; + SlerpSettings.FillTo(HandleInfo->AngConstraint.SlerpDrive); + } + else + { + HandleInfo->AngConstraint.AngularDriveMode = EAngularDriveMode::TwistAndSwing; + TwistSettings.FillTo(HandleInfo->AngConstraint.TwistDrive); + SwingSettings.FillTo(HandleInfo->AngConstraint.SwingDrive); + } + + return true; + } +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacter.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacter.h new file mode 100644 index 0000000..018e06b --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacter.h @@ -0,0 +1,621 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRBaseCharacterMovementComponent.h" +#include "ReplicatedVRCameraComponent.h" +#include "GameFramework/Character.h" +#include "Navigation/PathFollowingComponent.h" +#include "VRBaseCharacter.generated.h" + +class AVRPlayerController; +class UGripMotionControllerComponent; +class UParentRelativeAttachmentComponent; +class AController; + +DECLARE_LOG_CATEGORY_EXTERN(LogBaseVRCharacter, Log, All); + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVRSeatThresholdChangedSignature, bool, bIsWithinThreshold, float, ToThresholdScaler); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVRPlayerStateReplicatedSignature, const APlayerState *, NewPlayerState); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPlayerTeleportedSignature); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FVRPlayerNetworkCorrectedSignature); + +USTRUCT() +struct VREXPANSIONPLUGIN_API FRepMovementVRCharacter : public FRepMovement +{ + GENERATED_BODY() + +public: + + FRepMovementVRCharacter(); + + UPROPERTY(Transient) + bool bJustTeleported; + + UPROPERTY(Transient) + bool bJustTeleportedGrips; + + UPROPERTY(Transient) + bool bPausedTracking; + + UPROPERTY(Transient) + FVector_NetQuantize100 PausedTrackingLoc; + + UPROPERTY(Transient) + float PausedTrackingRot; + + UPROPERTY(Transient) + TObjectPtr<AActor> Owner; + + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + FRepMovement BaseSettings = Owner ? Owner->GetReplicatedMovement() : FRepMovement(); + + // pack bitfield with flags + uint8 Flags = (bSimulatedPhysicSleep << 0) | (bRepPhysics << 1) | (bJustTeleported << 2) | (bJustTeleportedGrips << 3) | (bPausedTracking << 4); + Ar.SerializeBits(&Flags, 5); + bSimulatedPhysicSleep = (Flags & (1 << 0)) ? 1 : 0; + bRepPhysics = (Flags & (1 << 1)) ? 1 : 0; + bJustTeleported = (Flags & (1 << 2)) ? 1 : 0; + bJustTeleportedGrips = (Flags & (1 << 3)) ? 1 : 0; + bPausedTracking = (Flags & (1 << 4)) ? 1 : 0; + + bOutSuccess = true; + + if (bPausedTracking) + { + bOutSuccess &= PausedTrackingLoc.NetSerialize(Ar, Map, bOutSuccess); + + uint16 Yaw = 0; + if (Ar.IsSaving()) + { + Yaw = FRotator::CompressAxisToShort(PausedTrackingRot); + Ar << Yaw; + } + else + { + Ar << Yaw; + PausedTrackingRot = Yaw; + } + + } + + // update location, rotation, linear velocity + bOutSuccess &= SerializeQuantizedVector(Ar, Location, BaseSettings.LocationQuantizationLevel); + + switch (BaseSettings.RotationQuantizationLevel) + { + case ERotatorQuantization::ByteComponents: + { + Rotation.SerializeCompressed(Ar); + break; + } + + case ERotatorQuantization::ShortComponents: + { + Rotation.SerializeCompressedShort(Ar); + break; + } + } + + bOutSuccess &= SerializeQuantizedVector(Ar, LinearVelocity, BaseSettings.VelocityQuantizationLevel); + + // update angular velocity if required + if (bRepPhysics) + { + bOutSuccess &= SerializeQuantizedVector(Ar, AngularVelocity, BaseSettings.VelocityQuantizationLevel); + } + + return true; + } +}; + +template<> +struct TStructOpsTypeTraits<FRepMovementVRCharacter> : public TStructOpsTypeTraitsBase2<FRepMovementVRCharacter> +{ + enum + { + WithNetSerializer = true, + WithNetSharedSerialization = true, + }; +}; + +USTRUCT(Blueprintable) +struct VREXPANSIONPLUGIN_API FVRSeatedCharacterInfo +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + bool bSitting; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + bool bZeroToHead; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + FTransform_NetQuantize StoredTargetTransform; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + FTransform_NetQuantize InitialRelCameraTransform; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + TObjectPtr<USceneComponent> SeatParent; + UPROPERTY(BlueprintReadOnly, Category = "CharacterSeatInfo") + EVRConjoinedMovementModes PostSeatedMovementMode; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, NotReplicated, Category = "CharacterSeatInfo", meta = (ClampMin = "1.000", UIMin = "1.000", ClampMax = "256.000", UIMax = "256.000")) + float AllowedRadius; + UPROPERTY(EditAnywhere, BlueprintReadWrite, NotReplicated, Category = "CharacterSeatInfo", meta = (ClampMin = "1.000", UIMin = "1.000", ClampMax = "256.000", UIMax = "256.000")) + float AllowedRadiusThreshold; + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "CharacterSeatInfo") + float CurrentThresholdScaler; + UPROPERTY(BlueprintReadOnly, NotReplicated, Category = "CharacterSeatInfo") + bool bIsOverThreshold; + + bool bWasSeated; + bool bOriginalControlRotation; + bool bWasOverLimit; + + FVRSeatedCharacterInfo() + { + Clear(); + } + + void Clear() + { + bSitting = false; + bIsOverThreshold = false; + bWasOverLimit = false; + bZeroToHead = true; + StoredTargetTransform = FTransform::Identity; + InitialRelCameraTransform = FTransform::Identity; + bWasSeated = false; + bOriginalControlRotation = false; + AllowedRadius = 40.0f; + AllowedRadiusThreshold = 20.0f; + CurrentThresholdScaler = 0.0f; + SeatParent = nullptr; + PostSeatedMovementMode = EVRConjoinedMovementModes::C_MOVE_Walking; + } + + void ClearTempVals() + { + bWasOverLimit = false; + bWasSeated = false; + bOriginalControlRotation = false; + CurrentThresholdScaler = 0.0f; + } + + + /** Network serialization */ + // Doing a custom NetSerialize here because this is sent via RPCs and should change on every update + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + + Ar.SerializeBits(&bSitting, 1); + Ar.SerializeBits(&bZeroToHead, 1); + + if (bSitting) + { + InitialRelCameraTransform.NetSerialize(Ar, Map, bOutSuccess); + + // Forcing a maximum value here so that we can compress it by making assumptions + // 256 max value = 8 bits + 1 bit for sign + 7 bits for precision (up to 128 on precision, so full range 2 digit precision). + if (Ar.IsSaving()) + { + bOutSuccess &= WriteFixedCompressedFloat<256, 16>(AllowedRadius, Ar); + bOutSuccess &= WriteFixedCompressedFloat<256, 16>(AllowedRadiusThreshold, Ar); + } + else + { + bOutSuccess &= ReadFixedCompressedFloat<256, 16>(AllowedRadius, Ar); + bOutSuccess &= ReadFixedCompressedFloat<256, 16>(AllowedRadiusThreshold, Ar); + } + } + + StoredTargetTransform.NetSerialize(Ar, Map, bOutSuccess); + Ar << SeatParent; + Ar << PostSeatedMovementMode; + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRSeatedCharacterInfo > : public TStructOpsTypeTraitsBase2<FVRSeatedCharacterInfo> +{ + enum + { + WithNetSerializer = true + }; +}; + +USTRUCT() +struct VREXPANSIONPLUGIN_API FVRReplicatedCapsuleHeight +{ + GENERATED_USTRUCT_BODY() +public: + UPROPERTY() + float CapsuleHeight; + + FVRReplicatedCapsuleHeight() : + CapsuleHeight(0.0f) + {} + + /** Network serialization */ + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) + { + bOutSuccess = true; + // Forcing a maximum value here so that we can compress it by making assumptions + // 1024 max value = 10 bits + 1 bit for sign + 7 bits for precision (up to 128 on precision, so full range 2 digit precision). + if (Ar.IsSaving()) + { + bOutSuccess &= WriteFixedCompressedFloat<1024, 18>(CapsuleHeight, Ar); + } + else + { + bOutSuccess &= ReadFixedCompressedFloat<1024, 18>(CapsuleHeight, Ar); + } + + return bOutSuccess; + } +}; +template<> +struct TStructOpsTypeTraits< FVRReplicatedCapsuleHeight > : public TStructOpsTypeTraitsBase2<FVRReplicatedCapsuleHeight> +{ + enum + { + WithNetSerializer = true + }; +}; + +UCLASS() +class VREXPANSIONPLUGIN_API AVRBaseCharacter : public ACharacter +{ + GENERATED_BODY() + +public: + AVRBaseCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** BaseVR Character movement component belongs to */ + UPROPERTY(Transient, DuplicateTransient) + AVRPlayerController* OwningVRPlayerController; + + //virtual void CacheInitialMeshOffset(FVector MeshRelativeLocation, FRotator MeshRelativeRotation) override; + virtual void PostInitializeComponents() override; + + virtual void PossessedBy(AController* NewController); + virtual void OnRep_Controller() override; + virtual void OnRep_PlayerState() override; + + /** Used for replication of our RootComponent's position and velocity */ + UPROPERTY(ReplicatedUsing = OnRep_ReplicatedMovement) + struct FRepMovementVRCharacter ReplicatedMovementVR; + + bool bFlagTeleported; + bool bFlagTeleportedGrips; + bool bTrackingPaused; + FVector PausedTrackingLoc; + float PausedTrackingRot; + + + // Injecting our custom teleport notification + virtual void OnRep_ReplicatedMovement() override; + virtual void GatherCurrentMovement() override; + + // Give my users direct access to an event for when the player has teleported + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVRPlayerTeleportedSignature OnCharacterTeleported_Bind; + + // Give my users direct access to an event for when the player has been network corrected + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVRPlayerNetworkCorrectedSignature OnCharacterNetworkCorrected_Bind; + + // Give my users direct access to an event for when the player state has changed + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVRPlayerStateReplicatedSignature OnPlayerStateReplicated_Bind; + + //These functions are now housed in the base character and used when possible, it saves about 7 bits of packet header overhead per send. + + // I'm sending it unreliable because it is being resent pretty often + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendTransformCamera(FBPVRComponentPosRep NewTransform); + + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendTransformLeftController(FBPVRComponentPosRep NewTransform); + + UFUNCTION(Unreliable, Server, WithValidation) + void Server_SendTransformRightController(FBPVRComponentPosRep NewTransform); + + virtual void PreReplication(IRepChangedPropertyTracker & ChangedPropertyTracker) override; + + // If true will replicate the capsule height on to clients, allows for dynamic capsule height changes in multiplayer + UPROPERTY(EditAnywhere, Replicated, BlueprintReadWrite, Category = "VRBaseCharacter") + bool VRReplicateCapsuleHeight; + + // OnlyReplicated to simulated clients + UPROPERTY(Replicated, ReplicatedUsing = OnRep_CapsuleHeight) + FVRReplicatedCapsuleHeight ReplicatedCapsuleHeight; + + UFUNCTION() + void OnRep_CapsuleHeight(); + + // Override this in c++ or blueprints to pass in an IK mesh to be used in some optimizations + // May be extended in the future + //UFUNCTION(BlueprintNativeEvent, Category = "BaseVRCharacter") + //USkeletalMeshComponent * GetIKMesh() const; + //virtual USkeletalMeshComponent * GetIKMesh_Implementation() const;1 + // #TODO: Work with the above, can do multiple things with it + + + // Called when the client is in climbing mode and is stepped up onto a platform + // Generally you should drop the climbing at this point and go into falling movement. + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRMovement") + void OnClimbingSteppedUp(); + virtual void OnClimbingSteppedUp_Implementation(); + + /** + * Event for adding to the climbing movement mode. Called by CharacterMovement if MovementMode is set to Climbing. + * @note C++ code should override PhysCustom_Climbing() instead. + */ + UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "UpdateLowGravMovement", ScriptName = "UpdateLowGravMovement")) + void UpdateLowGravMovement(float DeltaTime); + virtual void UpdateLowGravMovement_Implementation(float DeltaTime) {} // Do nothing by default + + /** + * Event for adding to the climbing movement mode. Called by CharacterMovement if MovementMode is set to Climbing. + * @note C++ code should override PhysCustom_Climbing() instead. + */ + UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "UpdateClimbingMovement", ScriptName = "UpdateClimbingMovement")) + void UpdateClimbingMovement(float DeltaTime); + virtual void UpdateClimbingMovement_Implementation(float DeltaTime){} // Do nothing by default + + // This is the offset location of the player, use this for when checking against player transform instead of the actors transform + UPROPERTY(BlueprintReadOnly, Transient, Category = "VRExpansionLibrary") + FTransform OffsetComponentToWorld; + + // Gets the forward vector of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRForwardVector() const + { + return OffsetComponentToWorld.GetRotation().GetForwardVector(); + } + + // Gets the right vector of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRRightVector() const + { + return OffsetComponentToWorld.GetRotation().GetRightVector(); + } + + // Gets the upvector of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRUpVector() const + { + return OffsetComponentToWorld.GetRotation().GetUpVector(); + } + + // Gets the location of the HMD offset capsule (this retains the Capsule HalfHeight offset) + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FVector GetVRLocation() const + { + return OffsetComponentToWorld.GetLocation(); + } + + inline FVector GetVRLocation_Inline() const + { + return OffsetComponentToWorld.GetLocation(); + } + + // Gets the rotation of the HMD offset capsule + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations") + FRotator GetVRRotation() const + { + return OffsetComponentToWorld.GetRotation().Rotator(); + } + // Gets the location of the HMD, if the camera is missing then it just returns waist location instead + UFUNCTION(BlueprintPure, Category = "BaseVRCharacter|VRLocations", meta = (DisplayName = "GetVRHeadLocation", ScriptName = "GetVRHeadLocation", Keywords = "position")) + FVector K2_GetVRHeadLocation() const + { + return GetVRHeadLocation(); + } + + inline FVector GetVRHeadLocation() const + { + return VRReplicatedCamera != nullptr ? VRReplicatedCamera->GetComponentLocation() : OffsetComponentToWorld.GetLocation(); + } + + + virtual FVector GetTargetLocation(AActor* RequestedBy) const override + { + return GetVRLocation_Inline(); + } + + // If true will use the experimental method of unseating that clears some movement replication options. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacter") + bool bUseExperimentalUnseatModeFix; + + UPROPERTY(BlueprintReadOnly, Replicated, EditAnywhere, Category = "Seating", ReplicatedUsing = OnRep_SeatedCharInfo) + FVRSeatedCharacterInfo SeatInformation; + + // Called when the seated mode is changed + UFUNCTION(BlueprintNativeEvent, Category = "Seating") + void OnSeatedModeChanged(bool bNewSeatedMode, bool bWasAlreadySeated); + virtual void OnSeatedModeChanged_Implementation(bool bNewSeatedMode, bool bWasAlreadySeated) {} + + // Called when the the player either transitions to/from the threshold boundry or the scaler value of being outside the boundry changes + // Can be used for warnings or screen darkening, ect + UFUNCTION(BlueprintNativeEvent, Category = "Seating") + void OnSeatThreshholdChanged(bool bIsWithinThreshold, float ToThresholdScaler); + virtual void OnSeatThreshholdChanged_Implementation(bool bIsWithinThreshold, float ToThresholdScaler) {} + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "Seating") + FVRSeatThresholdChangedSignature OnSeatThreshholdChanged_Bind; + + void ZeroToSeatInformation() + { + SetSeatRelativeLocationAndRotationVR(FVector::ZeroVector); + NotifyOfTeleport(); + //LeftMotionController->PostTeleportMoveGrippedObjects(); + //RightMotionController->PostTeleportMoveGrippedObjects(); + } + + // Called from the movement component + void TickSeatInformation(float DeltaTime); + + UFUNCTION() + virtual void OnRep_SeatedCharInfo(); + + void InitSeatedModeTransition(); + + // Re-zeros the seated settings + UFUNCTION(BlueprintCallable, Server, Reliable, WithValidation, Category = "BaseVRCharacter", meta = (DisplayName = "ReZeroSeating")) + void Server_ReZeroSeating(FTransform_NetQuantize NewTargetTransform, FTransform_NetQuantize NewInitialRelCameraTransform, bool bZeroToHead = true); + + + // Sets seated mode on the character and then fires off an event to handle any special setup + // Target Transform is for teleport location if standing up, or relative camera location when sitting down. + // InitialRelCameraTransform is generally the relative transform of the camera at the time of requesting to sit. + // ZeroToHead places central point on head, if false it will use foot location and ignore Z values instead. + // Post Seated movement mode is the movement mode to switch too after seating is canceled, defaults to Walking and only uses it when un-seating. + UFUNCTION(BlueprintCallable, Server, Reliable, WithValidation, Category = "BaseVRCharacter", meta = (DisplayName = "SetSeatedMode")) + void Server_SetSeatedMode(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform_NetQuantize TargetTransform, FTransform_NetQuantize InitialRelCameraTransform, float AllowedRadius = 40.0f, float AllowedRadiusThreshold = 20.0f, bool bZeroToHead = true, EVRConjoinedMovementModes PostSeatedMovementMode = EVRConjoinedMovementModes::C_MOVE_Walking); + + // Sets seated mode on the character and then fires off an event to handle any special setup + // Should only be called on the server / net authority + // If allowed radius is 0.0f then the seated mode does not check for radial distance anymore. + bool SetSeatedMode(USceneComponent * SeatParent, bool bSetSeatedMode, FTransform TargetTransform, FTransform InitialRelCameraTransform, float AllowedRadius = 40.0f, float AllowedRadiusThreshold = 20.0f, bool bZeroToHead = true, EVRConjoinedMovementModes PostSeatedMovementMode = EVRConjoinedMovementModes::C_MOVE_Walking); + + void SetSeatRelativeLocationAndRotationVR(FVector LocDelta); + + // Adds a rotation delta taking into account the HMD as a pivot point (also moves the actor), returns final location difference + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + FVector AddActorWorldRotationVR(FRotator DeltaRot, bool bUseYawOnly = true); + + // Sets the actors rotation taking into account the HMD as a pivot point (also moves the actor), returns the location difference + // bAccountForHMDRotation sets the rot to have the HMD face the given rot, if it is false it ignores the HMD rotation + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + FVector SetActorRotationVR(FRotator NewRot, bool bUseYawOnly = true, bool bAccountForHMDRotation = true); + + // Sets the actors rotation and location taking into account the HMD as a pivot point (also moves the actor), returns the location difference from the rotation + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + FVector SetActorLocationAndRotationVR(FVector NewLoc, FRotator NewRot, bool bUseYawOnly = true, bool bAccountForHMDRotation = true, bool bTeleport = false); + + // Regenerates the base offsetcomponenttoworld that VR uses + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter|VRLocations") + virtual void RegenerateOffsetComponentToWorld(bool bUpdateBounds, bool bCalculatePureYaw) + {} + + // This sets the capsules height, but also regenerates the offset transform instantly + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter") + virtual void SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps = true); + + // This sets the capsules half height, but also regenerates the offset transform instantly + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacter") + virtual void SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps = true); + + // This component is used with the normal character SkeletalMesh network smoothing system for simulated proxies + // It will lerp the characters components back to zero on simulated proxies after a move is complete. + // The simplest method of doing this was applying the exact same offset as the mesh gets to a base component that + // tracked objects are attached to. + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<USceneComponent> NetSmoother; + + // This is just a helper proxy component after the net smoother to make it easier to move tracking around for people + // but still maintain the netsmoothers functionality + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<USceneComponent> VRProxyComponent; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, Transient, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<UVRBaseCharacterMovementComponent> VRMovementReference; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<UReplicatedVRCameraComponent> VRReplicatedCamera; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<UParentRelativeAttachmentComponent> ParentRelativeAttachment; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<UGripMotionControllerComponent> LeftMotionController; + + UPROPERTY(Category = VRBaseCharacter, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<UGripMotionControllerComponent> RightMotionController; + + /** Name of the LeftMotionController component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName LeftMotionControllerComponentName; + + /** Name of the RightMotionController component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName RightMotionControllerComponentName; + + /** Name of the VRReplicatedCamera component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName ReplicatedCameraComponentName; + + /** Name of the ParentRelativeAttachment component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName ParentRelativeAttachmentComponentName; + + /** Name of the net smoother component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName SmoothingSceneParentComponentName; + + /** Name of the vr proxy component. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */ + static FName VRProxyComponentName; + + /* + A helper function that offsets a given vector by the roots collision location + pass in a teleport location and it provides the correct spot for it to be at your feet + */ + UFUNCTION(BlueprintPure, Category = "VRGrip") + virtual FVector GetTeleportLocation(FVector OriginalLocation); + + // Notifies that we should teleport the both hand grips on next tick + // When called server side will automatically apply to remote clients as well. + // Owning clients get it on server correction automatically already. + UFUNCTION(BlueprintCallable, Category = "VRGrip") + virtual void NotifyOfTeleport(bool bRegisterAsTeleport = true); + + + // Event triggered when a move action is performed, this is ran just prior to PerformMovement in the character tick + UFUNCTION(BlueprintNativeEvent, Category = "VRMovement") + void OnCustomMoveActionPerformed(EVRMoveAction MoveActionType, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags); + virtual void OnCustomMoveActionPerformed_Implementation(EVRMoveAction MoveActionType, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags); + + // Event triggered when beginning to be pushed back from a wall + // bHadLocomotionInput means that the character was moving itself + // HmdInput is how much the HMD moved in that tick so you can compare sizes to decide what to do + UFUNCTION(BlueprintNativeEvent, Category = "VRMovement") + void OnBeginWallPushback(FHitResult HitResultOfImpact, bool bHadLocomotionInput, FVector HmdInput); + virtual void OnBeginWallPushback_Implementation(FHitResult HitResultOfImpact, bool bHadLocomotionInput, FVector HmdInput); + + // Event triggered when beginning to be pushed back from a wall + UFUNCTION(BlueprintNativeEvent, Category = "VRMovement") + void OnEndWallPushback(); + virtual void OnEndWallPushback_Implementation(); + + // Event when a navigation pathing operation has completed, auto calls stop movement for VR characters + UFUNCTION(BlueprintImplementableEvent, Category = "VRBaseCharacter|Navigation") + void ReceiveNavigationMoveCompleted(EPathFollowingResult::Type PathingResult); + + virtual void NavigationMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result); + + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + EPathFollowingStatus::Type GetMoveStatus() const; + + /** Returns true if the current PathFollowingComponent's path is partial (does not reach desired destination). */ + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + bool HasPartialPath() const; + + // Instantly stops pathing + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + void StopNavigationMovement(); + + UPROPERTY(BlueprintReadWrite, Category = AI) + TSubclassOf<UNavigationQueryFilter> DefaultNavigationFilterClass; + + // An extended simple move to location with additional parameters + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation", Meta = (AdvancedDisplay = "bStopOnOverlap,bCanStrafe,bAllowPartialPath")) + virtual void ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius = -1, bool bStopOnOverlap = false, + bool bUsePathfinding = true, bool bProjectDestinationToNavigation = true, bool bCanStrafe = false, + TSubclassOf<UNavigationQueryFilter> FilterClass = NULL, bool bAllowPartialPath = true); + + // Returns the current path points on the active navigation path + // Will return false / an empty result if the path following component is not active yet or the path is empty + UFUNCTION(BlueprintCallable, Category = "VRBaseCharacter|Navigation") + bool GetCurrentNavigationPathPoints(TArray<FVector>& NavigationPointList); + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacterMovementComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacterMovementComponent.h new file mode 100644 index 0000000..43bda81 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRBaseCharacterMovementComponent.h @@ -0,0 +1,417 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "CharacterMovementCompTypes.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "Components/SkeletalMeshComponent.h" +#include "VRBaseCharacterMovementComponent.generated.h" + +class AVRBaseCharacter; +struct FAIRequestID; +struct FPathFollowingResult; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRBaseCharacterMovement, Log, All); + +/** Delegate for notification when to handle a climbing step up, will override default step up logic if is bound to. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FVROnPerformClimbingStepUp, FVector, FinalStepUpLocation); + +/* +* The base class for our VR characters, contains common logic across them, not to be used directly +*/ +UCLASS() +class VREXPANSIONPLUGIN_API UVRBaseCharacterMovementComponent : public UCharacterMovementComponent +{ + GENERATED_BODY() +public: + + UVRBaseCharacterMovementComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** Default client to server move RPC data container. Can be bypassed via SetNetworkMoveDataContainer(). */ + FVRCharacterNetworkMoveDataContainer VRNetworkMoveDataContainer; + FVRCharacterMoveResponseDataContainer VRMoveResponseDataContainer; + + bool bNotifyTeleported; + + /** BaseVR Character movement component belongs to */ + UPROPERTY(Transient, DuplicateTransient) + TObjectPtr<AVRBaseCharacter> BaseVRCharacterOwner; + + virtual void SetUpdatedComponent(USceneComponent* NewUpdatedComponent); + + virtual void MoveAutonomous(float ClientTimeStamp, float DeltaTime, uint8 CompressedFlags, const FVector& NewAccel) override; + virtual void PerformMovement(float DeltaSeconds) override; + //virtual void ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) override; + + // Overriding this to run the seated logic + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + // Skip force updating position if we are seated. + virtual bool ForcePositionUpdate(float DeltaTime) override; + + // When true will use the default engines behavior of setting rotation to match the clients instead of simulating rotations, this is really only here for FPS test pawns + // And non VRCharacter classes (simple character will use this) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacterMovementComponent") + bool bUseClientControlRotation; + + // When true remote proxies will no longer attempt to estimate player moves when motion smoothing is enabled. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRBaseCharacterMovementComponent|Smoothing") + bool bDisableSimulatedTickWhenSmoothingMovement; + + // When true the hmd movement injection speed is capped to the maximum movement speed + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bCapHMDMovementToMaxMovementSpeed; + + // Adding seated transition + void OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) override; + + // Called when a valid climbing step up movement is found, if bound to the default auto step up is not performed to let custom step up logic happen instead. + UPROPERTY(BlueprintAssignable, Category = "VRMovement") + FVROnPerformClimbingStepUp OnPerformClimbingStepUp; + + virtual void OnMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result); + + // Can't be inline anymore + FVector GetActorFeetLocationVR() const; + + FORCEINLINE bool HasRequestedVelocity() + { + return bHasRequestedVelocity; + } + + void SetHasRequestedVelocity(bool bNewHasRequestedVelocity); + bool IsClimbing() const; + + // Sets the crouching half height since it isn't exposed during runtime to blueprints + //UFUNCTION(BlueprintCallable, Category = "VRMovement") + // void SetCrouchedHalfHeight(float NewCrouchedHalfHeight); + + // Setting this higher will divide the wall slide effect by this value, to reduce collision sliding. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement", meta = (ClampMin = "0.0", UIMin = "0", ClampMax = "5.0", UIMax = "5")) + float VRWallSlideScaler; + + /** Custom version of SlideAlongSurface that handles different movement modes separately; namely during walking physics we might not want to slide up slopes. */ + virtual float SlideAlongSurface(const FVector& Delta, float Time, const FVector& Normal, FHitResult& Hit, bool bHandleImpact) override; + + // Add in the custom replicated movement that climbing mode uses, this is a cutom vector that is applied to character movements + // on the next tick as a movement input.. + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacterMovementComponent|VRLocations") + void AddCustomReplicatedMovement(FVector Movement); + + // Clears the custom replicated movement, can be used to cancel movements if the mode changes + UFUNCTION(BlueprintCallable, Category = "BaseVRCharacterMovementComponent|VRLocations") + void ClearCustomReplicatedMovement(); + + // Called to check if the server is performing a move action on a non controlled character + // If so then we just run the logic right away as it can't be inlined and won't be replicated + void CheckServerAuthedMoveAction(); + + // Set tracking paused for our root capsule and replicate the location to all connections + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_SetTrackingPaused(bool bNewTrackingPaused); + virtual void StoreSetTrackingPaused(bool bNewTrackingPaused); + + // Perform a snap turn in line with the move action system + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_SnapTurn(float SnapTurnDeltaYaw, EVRMoveActionVelocityRetention VelocityRetention = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None, bool bFlagGripTeleport = false, bool bFlagCharacterTeleport = false); + + // Perform a rotation set in line with the move actions system + // This node specifically sets the FACING direction to a value, where your HMD is pointed + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_SetRotation(float NewYaw, EVRMoveActionVelocityRetention VelocityRetention = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None, bool bFlagGripTeleport = false, bool bFlagCharacterTeleport = false); + + // Perform a teleport in line with the move action system + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_Teleport(FVector TeleportLocation, FRotator TeleportRotation, EVRMoveActionVelocityRetention VelocityRetention = EVRMoveActionVelocityRetention::VRMOVEACTION_Velocity_None, bool bSkipEncroachmentCheck = false); + + // Perform StopAllMovementImmediately in line with the move action system + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_StopAllMovement(); + + // Perform a custom moveaction that you define, will call the OnCustomMoveActionPerformed event in the character when processed so you can run your own logic + // Be sure to set the minimum data replication requirements for your move action in order to save on replication. + // Flags will always replicate if it is non zero + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void PerformMoveAction_Custom(EVRMoveAction MoveActionToPerform, EVRMoveActionDataReq DataRequirementsForMoveAction, FVector MoveActionVector, FRotator MoveActionRotator, uint8 MoveActionFlags = 0); + + FVRMoveActionArray MoveActionArray; + + bool CheckForMoveAction(); + virtual bool DoMASnapTurn(FVRMoveActionContainer& MoveAction); + virtual bool DoMASetRotation(FVRMoveActionContainer& MoveAction); + virtual bool DoMATeleport(FVRMoveActionContainer& MoveAction); + virtual bool DoMAStopAllMovement(FVRMoveActionContainer& MoveAction); + virtual bool DoMAPauseTracking(FVRMoveActionContainer& MoveAction); + + FVector CustomVRInputVector; + FVector AdditionalVRInputVector; + FVector LastPreAdditiveVRVelocity; + bool bHadExtremeInput; + bool bApplyAdditionalVRInputVectorAsNegative; + + // Rewind the relative movement that we had with the HMD + inline void RewindVRRelativeMovement() + { + if (bApplyAdditionalVRInputVectorAsNegative) + { + //FHitResult AHit; + MoveUpdatedComponent(-AdditionalVRInputVector, UpdatedComponent->GetComponentQuat(), false); + //SafeMoveUpdatedComponent(-AdditionalVRInputVector, UpdatedComponent->GetComponentQuat(), false, AHit); + } + } + + // Any movement above this value we will consider as have been a tracking jump and null out the movement in the character + // Raise this value higher if players are noticing freezing when moving quickly. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement", meta = (ClampMin = "0.0", UIMin = "0")) + float TrackingLossThreshold; + + // If we hit the tracking loss threshold then rewind position instead of running to the new location + // Will force the HMD to stay in its original spot prior to the tracking jump + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bHoldPositionOnTrackingLossThresholdHit; + + // Rewind the relative movement that we had with the HMD, this is exposed to Blueprint so that custom movement modes can use it to rewind prior to movement actions. + // Returns the Vector required to get back to the original position (for custom movement modes) + UFUNCTION(BlueprintCallable, Category = "VRMovement") + FVector RewindVRMovement(); + + // Gets the current CustomInputVector for use in custom movement modes + UFUNCTION(BlueprintCallable, Category = "VRMovement") + FVector GetCustomInputVector(); + + bool bWasInPushBack; + bool bIsInPushBack; + void StartPushBackNotification(FHitResult HitResult); + void EndPushBackNotification(); + + bool bJustUnseated; + + //virtual void SendClientAdjustment() override; + + virtual bool VerifyClientTimeStamp(float TimeStamp, FNetworkPredictionData_Server_Character & ServerData) override; + + inline void ApplyVRMotionToVelocity(float deltaTime) + { + bHadExtremeInput = false; + + if (AdditionalVRInputVector.IsNearlyZero() && CustomVRInputVector.IsNearlyZero()) + { + LastPreAdditiveVRVelocity = FVector::ZeroVector; + return; + } + + LastPreAdditiveVRVelocity = (AdditionalVRInputVector / deltaTime); // Save off pre-additive Velocity for restoration next tick + + if (LastPreAdditiveVRVelocity.SizeSquared() > FMath::Square(TrackingLossThreshold)) + { + bHadExtremeInput = true; + if (bHoldPositionOnTrackingLossThresholdHit) + { + LastPreAdditiveVRVelocity = FVector::ZeroVector; + } + } + + // Post the HMD velocity checks, add in our direct movement now + LastPreAdditiveVRVelocity += (CustomVRInputVector / deltaTime); + + Velocity += LastPreAdditiveVRVelocity; + + if (bCapHMDMovementToMaxMovementSpeed && IsExceedingMaxSpeed(GetMaxSpeed())) + { + // Force us to the max possible speed for the movement mode + Velocity = Velocity.GetSafeNormal() * GetMaxSpeed(); + } + } + + inline void RestorePreAdditiveVRMotionVelocity() + { + if (!LastPreAdditiveVRVelocity.IsNearlyZero()) + { + if (bHadExtremeInput) + { + // Just zero out the velocity here + Velocity = FVector::ZeroVector; + } + else + { + // This doesn't work with input in the opposing direction + /*FVector ProjectedVelocity = Velocity.ProjectOnToNormal(LastPreAdditiveVRVelocity.GetSafeNormal()); + float VelSq = ProjectedVelocity.SizeSquared(); + float AddSq = LastPreAdditiveVRVelocity.SizeSquared(); + + if (VelSq > AddSq || ProjectedVelocity.Equals(LastPreAdditiveVRVelocity, 0.1f)) + { + // Subtract velocity if we still relatively retain it in the normalized direction + Velocity -= LastPreAdditiveVRVelocity; + }*/ + + Velocity -= LastPreAdditiveVRVelocity; + } + } + + LastPreAdditiveVRVelocity = FVector::ZeroVector; + } + + virtual void PhysCustom(float deltaTime, int32 Iterations) override; + virtual void PhysCustom_Climbing(float deltaTime, int32 Iterations); + virtual void PhysCustom_LowGrav(float deltaTime, int32 Iterations); + + // Teleport grips on correction to fixup issues + virtual void OnClientCorrectionReceived(class FNetworkPredictionData_Client_Character& ClientData, float TimeStamp, FVector NewLocation, FVector NewVelocity, UPrimitiveComponent* NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode) override; + + // Fix network smoothing with our default mesh back in + virtual void SimulatedTick(float DeltaSeconds) override; + + // Skip updates with rotational differences + virtual void SmoothCorrection(const FVector& OldLocation, const FQuat& OldRotation, const FVector& NewLocation, const FQuat& NewRotation) override; + + /** + * Smooth mesh location for network interpolation, based on values set up by SmoothCorrection. + * Internally this simply calls SmoothClientPosition_Interpolate() then SmoothClientPosition_UpdateVisuals(). + * This function is not called when bNetworkSmoothingComplete is true. + * @param DeltaSeconds Time since last update. + */ + virtual void SmoothClientPosition(float DeltaSeconds) override; + + /** Update mesh location based on interpolated values. */ + void SmoothClientPosition_UpdateVRVisuals(); + + // Added in 4.16 + ///* Allow custom handling when character hits a wall while swimming. */ + //virtual void HandleSwimmingWallHit(const FHitResult& Hit, float DeltaTime); + + // If true will never count a physicsbody channel component as the floor, to prevent jitter / physics problems. + // Make sure that you set simulating objects to the physics body channel if you want this to work correctly + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bIgnoreSimulatingComponentsInFloorCheck; + + // If true will run the control rotation in the CMC instead of in the player controller + // This puts the player rotation into the scoped movement (perf savings) and also ensures it is properly rotated prior to movement + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + bool bRunControlRotationInMovementComponent; + + // Moved into compute floor dist + // Option to Skip simulating components when looking for floor + /*virtual bool FloorSweepTest( + const FVector& Start, + FHitResult& OutHit, + const FVector& End, + ECollisionChannel TraceChannel, + const struct FCollisionShape& CollisionShape, + const struct FCollisionQueryParams& Params, + const struct FCollisionResponseParams& ResponseParam + ) const override;*/ + + virtual void ComputeFloorDist(const FVector& CapsuleLocation, float LineDistance, float SweepDistance, FFindFloorResult& OutFloorResult, float SweepRadius, const FHitResult* DownwardSweepResult = NULL) const override; + + // Need to use actual capsule location for step up + virtual bool VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult = nullptr); + + // Height to auto step up + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingStepHeight; + + /* Custom distance that is required before accepting a climbing stepup + * This is to help with cases where head wobble causes falling backwards + * Do NOT set to larger than capsule radius! + * #TODO: Port to SimpleCharacter as well + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingEdgeRejectDistance; + + // Higher values make it easier to trigger a step up onto a platform and moves you farther in to the base *DEFUNCT* + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingStepUpMultiplier; + + // If true will clamp the maximum movement on climbing step up to: VRClimbingStepUpMaxSize + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + bool bClampClimbingStepUp; + + // Maximum X/Y vector size to use when climbing stepping up (prevents very deep step ups from large movements). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingStepUpMaxSize; + + // If true will automatically set falling when a stepup occurs during climbing + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + bool SetDefaultPostClimbMovementOnStepUp; + + // Max velocity on releasing a climbing grip + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + float VRClimbingMaxReleaseVelocitySize; + + /* Custom distance that is required before accepting a walking stepup + * This is to help promote stepping up, engine default is 0.15f, generally you want it lower than that + * Do NOT set to larger than capsule radius! + * #TODO: Port to SimpleCharacter as well + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement") + float VREdgeRejectDistance; + + UFUNCTION(BlueprintCallable, Category = "VRMovement|Climbing") + void SetClimbingMode(bool bIsClimbing); + + // Default movement mode to switch to post climb ended, only used if SetDefaultPostClimbMovementOnStepUp is true + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|Climbing") + EVRConjoinedMovementModes DefaultPostClimbMovement; + + // Overloading this to handle an edge case + virtual void ApplyNetworkMovementMode(const uint8 ReceivedMode) override; + + /* + * This is called client side to make a replicated movement mode change that hits the server in the saved move. + * + * Custom Movement Mode is currently limited to 0 - 8, the index's 0 and 1 are currently used up for the plugin movement modes. + * So setting it to 0 or 1 would be Climbing, and LowGrav respectivly, this leaves 2-8 as open index's for use. + * For a total of 6 Custom movement modes past the currently implemented plugin ones. + */ + UFUNCTION(BlueprintCallable, Category = "VRMovement") + void SetReplicatedMovementMode(EVRConjoinedMovementModes NewMovementMode); + + /* + * Call this to convert the current movement mode to a Conjoined one for reference + * + * Custom Movement Mode is currently limited to 0 - 8, the index's 0 and 1 are currently used up for the plugin movement modes. + * So setting it to 0 or 1 would be Climbing, and LowGrav respectivly, this leaves 2-8 as open index's for use. + * For a total of 6 Custom movement modes past the currently implemented plugin ones. + */ + UFUNCTION(BlueprintPure, Category = "VRMovement") + EVRConjoinedMovementModes GetReplicatedMovementMode(); + + // We use 4 bits for this so a maximum of 16 elements + EVRConjoinedMovementModes VRReplicatedMovementMode; + + FORCEINLINE void ApplyReplicatedMovementMode(EVRConjoinedMovementModes &NewMovementMode, bool bClearMovementMode = false) + { + if (NewMovementMode != EVRConjoinedMovementModes::C_MOVE_MAX)//None) + { + if (NewMovementMode <= EVRConjoinedMovementModes::C_MOVE_MAX) + { + // Is a default movement mode, just directly set it + SetMovementMode((EMovementMode)NewMovementMode); + } + else // Is Custom + { + // Auto calculates the difference for our VR movements, index is from 0 so using climbing should get me correct index's as it is the first custom mode + SetMovementMode(EMovementMode::MOVE_Custom, (((int8)NewMovementMode - (uint8)EVRConjoinedMovementModes::C_VRMOVE_Climbing))); + } + + // Clearing it here instead now, as this way the code can inject it during PerformMovement + // Specifically used by the Climbing Step up, so that server rollbacks are supported + if(bClearMovementMode) + NewMovementMode = EVRConjoinedMovementModes::C_MOVE_MAX;//None; + } + } + + void UpdateFromCompressedFlags(uint8 Flags) override; + + FVector RoundDirectMovement(FVector InMovement) const; + + // Setting this below 1.0 will change how fast you de-accelerate when touching a wall + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|LowGrav", meta = (ClampMin = "0.0", UIMin = "0", ClampMax = "5.0", UIMax = "5")) + float VRLowGravWallFrictionScaler; + + // If true then low grav will ignore the default physics volume fluid friction, useful if you have a mix of low grav and normal movement + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRMovement|LowGrav") + bool VRLowGravIgnoresDefaultFluidFriction; +}; + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacter.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacter.h new file mode 100644 index 0000000..8233bf6 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacter.h @@ -0,0 +1,46 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBaseCharacter.h" +#include "VRCharacter.generated.h" + +class UVRRootComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRCharacter, Log, All); + +UCLASS() +class VREXPANSIONPLUGIN_API AVRCharacter : public AVRBaseCharacter +{ + GENERATED_BODY() + +public: + AVRCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // Overriding teleport so that it auto calls my controllers re-positioning + virtual bool TeleportTo(const FVector& DestLocation, const FRotator& DestRotation, bool bIsATest = false, bool bNoCheck = false) override; + + UPROPERTY(Category = VRCharacter, VisibleAnywhere, Transient, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + TObjectPtr<UVRRootComponent> VRRootReference; + + // Regenerates the base offsetcomponenttoworld that VR uses + virtual void RegenerateOffsetComponentToWorld(bool bUpdateBounds, bool bCalculatePureYaw) override; + virtual void SetCharacterSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps = true) override; + virtual void SetCharacterHalfHeightVR(float HalfHeight, bool bUpdateOverlaps = true) override; + + /* + A helper function that offsets a given vector by the roots collision location + pass in a teleport location and it provides the correct spot for it to be at your feet + */ + virtual FVector GetTeleportLocation(FVector OriginalLocation) override; + + + // Overriding to correct some nav stuff + FVector GetNavAgentLocation() const override; + + // An extended simple move to location with additional parameters + virtual void ExtendedSimpleMoveToLocation(const FVector& GoalLocation, float AcceptanceRadius = -1, bool bStopOnOverlap = false, + bool bUsePathfinding = true, bool bProjectDestinationToNavigation = true, bool bCanStrafe = false, + TSubclassOf<UNavigationQueryFilter> FilterClass = NULL, bool bAllowPartialPath = true) override; + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacterMovementComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacterMovementComponent.h new file mode 100644 index 0000000..522b40e --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRCharacterMovementComponent.h @@ -0,0 +1,327 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +//#include "Engine/EngineBaseTypes.h" +//#include "Engine/EngineTypes.h" +#include "CharacterMovementCompTypes.h" +#include "VRBaseCharacterMovementComponent.h" +#include "VRCharacterMovementComponent.generated.h" + +class FDebugDisplayInfo; +class ACharacter; +class AVRCharacter; +class UVRRootComponent; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRCharacterMovement, Log, All); + +/** Shared pointer for easy memory management of FSavedMove_Character, for accumulating and replaying network moves. */ +//typedef TSharedPtr<class FSavedMove_Character> FSavedMovePtr; + + + +//FCharacterMoveResponseDataContainer VRMoveResponseDataContainer; + + +//============================================================================= +/** + * VRCharacterMovementComponent handles movement logic for the associated Character owner. + * It supports various movement modes including: walking, falling, swimming, flying, custom. + * + * Movement is affected primarily by current Velocity and Acceleration. Acceleration is updated each frame + * based on the input vector accumulated thus far (see UPawnMovementComponent::GetPendingInputVector()). + * + * Networking is fully implemented, with server-client correction and prediction included. + * + * @see ACharacter, UPawnMovementComponent + * @see https://docs.unrealengine.com/latest/INT/Gameplay/Framework/Pawn/Character/ + */ + +//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAIMoveCompletedSignature, FAIRequestID, RequestID, EPathFollowingResult::Type, Result); + +UCLASS() +class VREXPANSIONPLUGIN_API UVRCharacterMovementComponent : public UVRBaseCharacterMovementComponent +{ + GENERATED_BODY() +public: + + UPROPERTY(BlueprintReadOnly, Transient, Category = VRMovement) + TObjectPtr<UVRRootComponent> VRRootCapsule; + + /** Reject sweep impacts that are this close to the edge of the vertical portion of the capsule when performing vertical sweeps, and try again with a smaller capsule. */ + static const float CLIMB_SWEEP_EDGE_REJECT_DISTANCE; + virtual bool IsWithinClimbingEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const; + virtual bool VRClimbStepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult = nullptr) override; + + virtual bool IsWithinEdgeTolerance(const FVector& CapsuleLocation, const FVector& TestImpactPoint, const float CapsuleRadius) const override; + + // Allow merging movement replication (may cause issues when >10 players due to capsule location + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRCharacterMovementComponent") + bool bAllowMovementMerging; + + // Higher values will cause more slide but better step up + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRCharacterMovementComponent", meta = (ClampMin = "0.01", UIMin = "0", ClampMax = "1.0", UIMax = "1")) + //float WallRepulsionMultiplier; + + /** + * Checks if new capsule size fits (no encroachment), and call CharacterOwner->OnStartCrouch() if successful. + * In general you should set bWantsToCrouch instead to have the crouch persist during movement, or just use the crouch functions on the owning Character. + * @param bClientSimulation true when called when bIsCrouched is replicated to non owned clients, to update collision cylinder and offset. + */ + virtual void Crouch(bool bClientSimulation = false) override; + + /** + * Checks if default capsule size fits (no encroachment), and trigger OnEndCrouch() on the owner if successful. + * @param bClientSimulation true when called when bIsCrouched is replicated to non owned clients, to update collision cylinder and offset. + */ + virtual void UnCrouch(bool bClientSimulation = false) override; + + /** @return true if the character is allowed to crouch in the current state. By default it is allowed when walking or falling, if CanEverCrouch() is true. */ + //virtual bool CanCrouchInCurrentState() const; + + /////////////////////////// + // Navigation Functions + /////////////////////////// + + /** Blueprint notification that we've completed the current movement request */ + //UPROPERTY(BlueprintAssignable, meta = (DisplayName = "MoveCompleted")) + //FAIMoveCompletedSignature ReceiveMoveCompleted; + + virtual FBasedPosition GetActorFeetLocationBased() const override; + + /** + * Checks to see if the current location is not encroaching blocking geometry so the character can leave NavWalking. + * Restores collision settings and adjusts character location to avoid getting stuck in geometry. + * If it's not possible, MovementMode change will be delayed until character reach collision free spot. + * @return True if movement mode was successfully changed + */ + virtual bool TryToLeaveNavWalking() override; + + + virtual void PhysNavWalking(float deltaTime, int32 Iterations) override; + virtual void ProcessLanded(const FHitResult& Hit, float remainingTime, int32 Iterations) override; + + void PostPhysicsTickComponent(float DeltaTime, FCharacterMovementComponentPostPhysicsTickFunction& ThisTickFunction) override; + void SimulateMovement(float DeltaSeconds) override; + void MoveSmooth(const FVector& InVelocity, const float DeltaSeconds, FStepDownResult* OutStepDownResult) override; + //void PerformMovement(float DeltaSeconds) override; + + /////////////////////////// + // End Navigation Functions + /////////////////////////// + + + /////////////////////////// + // Client adjustment overrides to allow for rotation + /////////////////////////// + + // Engines version of this is private for some reason, making it impossible to override the function that uses it. + TWeakObjectPtr<UPrimitiveComponent> LastServerMovementBaseVR = nullptr; + + virtual void ClientHandleMoveResponse(const FCharacterMoveResponseDataContainer& MoveResponse) override; + + //virtual void SendClientAdjustment() override; + /** + * Have the server check if the client is outside an error tolerance, and queue a client adjustment if so. + * If either GetPredictionData_Server_Character()->bForceClientUpdate or ServerCheckClientError() are true, the client adjustment will be sent. + * RelativeClientLocation will be a relative location if MovementBaseUtility::UseRelativePosition(ClientMovementBase) is true, or a world location if false. + * @see ServerCheckClientError() + */ + virtual void ServerMoveHandleClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& RelativeClientLocation, float ClientYaw, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode); + + /** + * Check for Server-Client disagreement in position or other movement state important enough to trigger a client correction. + * @see ServerMoveHandleClientError() + */ + virtual bool ServerCheckClientErrorVR(float ClientTimeStamp, float DeltaTime, const FVector& Accel, const FVector& ClientWorldLocation, float ClientYaw, const FVector& RelativeClientLocation, UPrimitiveComponent* ClientMovementBase, FName ClientBaseBoneName, uint8 ClientMovementMode); + + /** Replicate position correction to client, associated with a timestamped servermove. Client will replay subsequent moves after applying adjustment. */ + virtual void ClientAdjustPositionVR_Implementation(float TimeStamp, FVector NewLoc, uint16 NewYaw, FVector NewVel, UPrimitiveComponent* NewBase, FName NewBaseBoneName, bool bHasBase, bool bBaseRelativePosition, uint8 ServerMovementMode); + + + /////////////////////////// + // Replication Functions + /////////////////////////// + + //////////////////////////////////// + // Network RPCs for movement + //////////////////////////////////// + /** + * The actual RPCs are passed to ACharacter, which wrap to the _Implementation and _Validate call here, to avoid Component RPC overhead. + * For example: + * Client: UCharacterMovementComponent::ServerMove(...) => Calls CharacterOwner->ServerMove(...) triggering RPC on server + * Server: ACharacter::ServerMove_Implementation(...) => Calls CharacterMovement->ServerMove_Implementation + * To override the client call to the server RPC (on CharacterOwner), override ServerMove(). + * To override the server implementation, override ServerMove_Implementation(). + */ + + + // Using my own as I don't want to cast the standard fsavedmove + //virtual void CallServerMove(const class FSavedMove_Character* NewMove, const class FSavedMove_Character* OldMove) override; + + virtual void ServerMove_PerformMovement(const FCharacterNetworkMoveData& MoveData) override; + + FNetworkPredictionData_Client* GetPredictionData_Client() const override; + FNetworkPredictionData_Server* GetPredictionData_Server() const override; + + /////////////////////////// + // End Replication Functions + /////////////////////////// + + /** + * Default UObject constructor. + */ + UVRCharacterMovementComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + float ImmersionDepth() const override; + bool CanCrouch(); + + // Don't really need to override this at all, it doesn't work that well even when fixed in VR + //void VisualizeMovement() const override; + + + /* + bool HasRootMotion() const + { + return RootMotionParams.bHasRootMotion; + }*/ + + // Had to modify this, since every frame can start in penetration (capsule component moves into wall before movement tick) + // It was throwing out the initial hit and not calling "step up", now I am only checking for penetration after adjustment but keeping the initial hit for step up. + // this makes for FAR more responsive step ups. + // I WILL need to override these for flying / swimming as well + bool SafeMoveUpdatedComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport = ETeleportType::None); + bool SafeMoveUpdatedComponent(const FVector& Delta, const FRotator& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport = ETeleportType::None); + + // This is here to force it to call the correct SafeMoveUpdatedComponent functions for floor movement + virtual void MoveAlongFloor(const FVector& InVelocity, float DeltaSeconds, FStepDownResult* OutStepDownResult) override; + + // Modify for correct location + virtual void ApplyRepulsionForce(float DeltaSeconds) override; + + // Update BaseOffset to be zero + virtual void UpdateBasedMovement(float DeltaSeconds) override; + + // Stop subtracting the capsules half height + virtual FVector GetImpartedMovementBaseVelocity() const override; + + // Cheating at the relative collision detection + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction); + + // Need to fill our capsule component variable here and override the default tick ordering + virtual void SetUpdatedComponent(USceneComponent* NewUpdatedComponent) override; + + // Correct an offset sweep test + virtual void ReplicateMoveToServer(float DeltaTime, const FVector& NewAcceleration) override; + + // Always called with the capsulecomponent location, no idea why it doesn't just get it inside it already + // Had to force it within the function to use VRLocation instead. + virtual void FindFloor(const FVector& CapsuleLocation, FFindFloorResult& OutFloorResult, bool bCanUseCachedLocation, const FHitResult* DownwardSweepResult = NULL) const; + + // Need to use actual capsule location for step up + bool StepUp(const FVector& GravDir, const FVector& Delta, const FHitResult &InHit, FStepDownResult* OutStepDownResult = NULL) override; + + virtual FVector GetPenetrationAdjustment(const FHitResult& Hit) const override; + + // MOVED THIS TO THE BASE VR CHARACTER MOVEMENT COMPONENT + // Also added a control variable for it there + // Skip physics channels when looking for floor + /*virtual bool FloorSweepTest( + FHitResult& OutHit, + const FVector& Start, + const FVector& End, + ECollisionChannel TraceChannel, + const struct FCollisionShape& CollisionShape, + const struct FCollisionQueryParams& Params, + const struct FCollisionResponseParams& ResponseParam + ) const override;*/ + + // Multiple changes to support relative motion and ledge sweeps + virtual void PhysWalking(float deltaTime, int32 Iterations) override; + + // Supporting the direct move injection + virtual void PhysFlying(float deltaTime, int32 Iterations) override; + + // Need to use VR location, was defaulting to actor + virtual bool ShouldCheckForValidLandingSpot(float DeltaTime, const FVector& Delta, const FHitResult& Hit) const override; + + // Overriding the physfalling because valid landing spots were computed incorrectly. + virtual void PhysFalling(float deltaTime, int32 Iterations) override; + + virtual void PhysSwimming(float deltaTime, int32 Iterations) override; + /** + * Handle start swimming functionality + * @param OldLocation - Location on last tick + * @param OldVelocity - velocity at last tick + * @param timeTick - time since at OldLocation + * @param remainingTime - DeltaTime to complete transition to swimming + * @param Iterations - physics iteration count + */ + void StartSwimmingVR(FVector OldLocation, FVector OldVelocity, float timeTick, float remainingTime, int32 Iterations); + + /* Swimming uses gravity - but scaled by (1.f - buoyancy) */ + float SwimVR(FVector Delta, FHitResult& Hit); + + /** Check if swimming pawn just ran into edge of the pool and should jump out. */ + virtual bool CheckWaterJump(FVector CheckPoint, FVector& WallNormal) override; + + // Shouldn't need to override this + /** Get as close to waterline as possible, staying on same side as currently. */ + //FVector FindWaterLine(FVector Start, FVector End) override; + + // Just calls find floor + /** Verify that the supplied hit result is a valid landing spot when falling. */ + //virtual bool IsValidLandingSpot(const FVector& CapsuleLocation, const FHitResult& Hit) const override; + + // Making sure that impulses are correct + virtual void CapsuleTouched(UPrimitiveComponent* OverlappedComp, AActor* Other, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) override; + + virtual void StoreSetTrackingPaused(bool bNewTrackingPaused) override; +}; + + +class VREXPANSIONPLUGIN_API FSavedMove_VRCharacter : public FSavedMove_VRBaseCharacter +{ + +public: + + virtual void SetInitialPosition(ACharacter* C); + virtual void PrepMoveFor(ACharacter* Character) override; + + FSavedMove_VRCharacter() : FSavedMove_VRBaseCharacter() + {} + +}; + +// Need this for capsule location replication +class VREXPANSIONPLUGIN_API FNetworkPredictionData_Client_VRCharacter : public FNetworkPredictionData_Client_Character +{ +public: + FNetworkPredictionData_Client_VRCharacter(const UCharacterMovementComponent& ClientMovement) + : FNetworkPredictionData_Client_Character(ClientMovement) + { + + } + + FSavedMovePtr AllocateNewMove() + { + return FSavedMovePtr(new FSavedMove_VRCharacter()); + } +}; + + +// Need this for capsule location replication????? +class VREXPANSIONPLUGIN_API FNetworkPredictionData_Server_VRCharacter : public FNetworkPredictionData_Server_Character +{ +public: + FNetworkPredictionData_Server_VRCharacter(const UCharacterMovementComponent& ClientMovement) + : FNetworkPredictionData_Server_Character(ClientMovement) + { + + } + + FSavedMovePtr AllocateNewMove() + { + return FSavedMovePtr(new FSavedMove_VRCharacter()); + } +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionFunctionLibrary.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionFunctionLibrary.h new file mode 100644 index 0000000..06e95d3 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionFunctionLibrary.h @@ -0,0 +1,380 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "VRBPDatatypes.h" +#include "XRMotionControllerBase.h" // for GetHandEnumForSourceName() +#include "VRExpansionFunctionLibrary.generated.h" + +class USplineComponent; +class UPrimitiveComponent; +class UGripMotioncontroller; +struct FGameplayTag; +struct FGameplayTagContainer; + + +//General Advanced Sessions Log +DECLARE_LOG_CATEGORY_EXTERN(VRExpansionFunctionLibraryLog, Log, All); + +/** +* Redefining this for blueprint as it wasn't declared as BlueprintType +* Stores if the user is wearing the HMD or not. For HMDs without a sensor to detect the user wearing it, the state defaults to Unknown. +*/ +UENUM(BlueprintType) +enum class EBPHMDWornState : uint8 +{ + Unknown UMETA(DisplayName = "Unknown"), + Worn UMETA(DisplayName = "Worn"), + NotWorn UMETA(DisplayName = "Not Worn"), +}; + +UCLASS()//, meta = (BlueprintSpawnableComponent)) +class VREXPANSIONPLUGIN_API UVRExpansionFunctionLibrary : public UBlueprintFunctionLibrary +{ + //GENERATED_BODY() + GENERATED_BODY() + //~UVRExpansionFunctionLibrary(); +public: + + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static UGameViewportClient * GetGameViewportClient(UObject* WorldContextObject); + + + // Sets two primitive components to ignore collision between two specific bodies + // If bAddChildBones is true then it will also add all child bones of the given bone (or the entire skeleton if no name is given) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void SetObjectsIgnoreCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1 = nullptr, FName OptionalBoneName1 = NAME_None, bool bAddChildBones1 = false, UPrimitiveComponent* Prim2 = nullptr, FName OptionalBoneName2 = NAME_None, bool bAddChildBones2 = false, bool bIgnoreCollision = true); + + // Sets two actors to entirely ignore collision between them + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void SetActorsIgnoreAllCollision(UObject* WorldContextObject, AActor* Actor1 = nullptr, AActor* Actor2 = nullptr, bool bIgnoreCollision = true); + + // Removes all collision ignore matches for the given primitive object + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void RemoveObjectCollisionIgnore(UObject* WorldContextObject, UPrimitiveComponent* Prim1); + + // Removes all collision ignore matches for the given actor + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void RemoveActorCollisionIgnore(UObject* WorldContextObject, AActor* Actor1); + + // Returns if the component is ignoring any collisions + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static bool IsComponentIgnoringCollision(UObject* WorldContextObject, UPrimitiveComponent* Prim1); + + // Returns if the component is ignoring collisions with the specific component + // This does not check per bone, but rather at scale if any part of them are ignoring each other + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions|Collision", meta = (bIgnoreSelf = "true", WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static bool AreComponentsIgnoringCollisions(UObject* WorldContextObject, UPrimitiveComponent* Prim1, UPrimitiveComponent* Prim2); + + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetHandFromMotionSourceName")) + static bool GetHandFromMotionSourceName(FName MotionSource, EControllerHand& Hand) + { + Hand = EControllerHand::Left; + if (FXRMotionControllerBase::GetHandEnumForSourceName(MotionSource, Hand)) + { + return true; + } + + return false; + } + + // Gets the unwound yaw of the HMD + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetHMDPureYaw")) + static FRotator GetHMDPureYaw(FRotator HMDRotation); + + inline static FRotator GetHMDPureYaw_I(FRotator HMDRotation) + { + // Took this from UnityVRToolkit, no shame, I liked it + FRotationMatrix HeadMat(HMDRotation); + FVector forward = HeadMat.GetScaledAxis(EAxis::X); + FVector forwardLeveled = forward; + forwardLeveled.Z = 0; + forwardLeveled.Normalize(); + FVector mixedInLocalForward = HeadMat.GetScaledAxis(EAxis::Z); + + if (forward.Z > 0) + { + mixedInLocalForward = -mixedInLocalForward; + } + + mixedInLocalForward.Z = 0; + mixedInLocalForward.Normalize(); + float dot = FMath::Clamp(FVector::DotProduct(forwardLeveled, forward), 0.0f, 1.0f); + FVector finalForward = FMath::Lerp(mixedInLocalForward, forwardLeveled, dot * dot); + + return FRotationMatrix::MakeFromXZ(finalForward, FVector::UpVector).Rotator(); + } + + // Applies a delta rotation around a pivot point, if bUseOriginalYawOnly is true then it only takes the original Yaw into account (characters) + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "RotateAroundPivot")) + static void RotateAroundPivot(FRotator RotationDelta, FVector OriginalLocation, FRotator OriginalRotation, FVector PivotPoint, FVector & NewLocation, FRotator & NewRotation,bool bUseOriginalYawOnly = true) + { + if (bUseOriginalYawOnly) + { + // Keep original pitch/roll + NewRotation.Pitch = OriginalRotation.Pitch; + NewRotation.Roll = OriginalRotation.Roll; + + // Throw out pitch/roll before calculating offset + OriginalRotation.Roll = 0; + OriginalRotation.Pitch = 0; + + // Offset to pivot point + NewLocation = OriginalLocation + OriginalRotation.RotateVector(PivotPoint); + + // Combine rotations + OriginalRotation.Yaw = (OriginalRotation.Quaternion() * RotationDelta.Quaternion()).Rotator().Yaw; + NewRotation.Yaw = OriginalRotation.Yaw; + + // Remove pivot point offset + NewLocation -= OriginalRotation.RotateVector(PivotPoint); + + } + else + { + NewLocation = OriginalLocation + OriginalRotation.RotateVector(PivotPoint); + NewRotation = (OriginalRotation.Quaternion() * RotationDelta.Quaternion()).Rotator(); + NewLocation -= NewRotation.RotateVector(PivotPoint); + } + } + + // Returns a delta rotation to have Vec1 point towards Vec2, assumes that the v + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "FindBetween")) + static FRotator BPQuat_FindBetween(FVector Vec1, FVector Vec2) + { + return FQuat::FindBetween(Vec1, Vec2).Rotator(); + } + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetIsHMDConnected")) + static bool GetIsHMDConnected(); + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetIsHMDWorn")) + static EBPHMDWornState GetIsHMDWorn(); + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetHMDType")) + static EBPHMDDeviceType GetHMDType(); + + // Gets whether the game is running in VRPreview or is a non editor build game (returns true for either). + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "IsInVREditorPreviewOrGame")) + static bool IsInVREditorPreviewOrGame(); + + // Gets whether the game is running in VRPreview + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "IsInVREditorPreview")) + static bool IsInVREditorPreview(); + + /** + * Finds the minimum area rectangle that encloses all of the points in InVerts + * Engine default version is server only for some reason + * Uses algorithm found in http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf + * + * @param InVerts - Points to enclose in the rectangle + * @outparam OutRectCenter - Center of the enclosing rectangle + * @outparam OutRectSideA - Vector oriented and sized to represent one edge of the enclosing rectangle, orthogonal to OutRectSideB + * @outparam OutRectSideB - Vector oriented and sized to represent one edge of the enclosing rectangle, orthogonal to OutRectSideA + */ + UFUNCTION(BlueprintCallable, Category = "VRExpansionFunctions", meta = (WorldContext = "WorldContextObject", CallableWithoutWorldContext)) + static void NonAuthorityMinimumAreaRectangle(UObject* WorldContextObject, const TArray<FVector>& InVerts, const FVector& SampleSurfaceNormal, FVector& OutRectCenter, FRotator& OutRectRotation, float& OutSideLengthX, float& OutSideLengthY, bool bDebugDraw = false); + + // A Rolling average low pass filter + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "LowPassFilter_RollingAverage")) + static void LowPassFilter_RollingAverage(FVector lastAverage, FVector newSample, FVector & newAverage, int32 numSamples = 10); + + // A exponential low pass filter + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "LowPassFilter_Exponential")) + static void LowPassFilter_Exponential(FVector lastAverage, FVector newSample, FVector & newAverage, float sampleFactor = 0.25f); + + // Gets whether an HMD device is connected + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions", meta = (bIgnoreSelf = "true", DisplayName = "GetIsActorMovable")) + static bool GetIsActorMovable(AActor * ActorToCheck); + + // Gets if an actors root component contains a grip slot within range + UFUNCTION(BlueprintPure, Category = "VRGrip", meta = (bIgnoreSelf = "true", DisplayName = "GetGripSlotInRangeByTypeName")) + static void GetGripSlotInRangeByTypeName(FName SlotType, AActor * Actor, FVector WorldLocation, float MaxRange, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent* QueryController = nullptr); + + // Gets if an actors root component contains a grip slot within range + UFUNCTION(BlueprintPure, Category = "VRGrip", meta = (bIgnoreSelf = "true", DisplayName = "GetGripSlotInRangeByTypeName_Component")) + static void GetGripSlotInRangeByTypeName_Component(FName SlotType, USceneComponent * Component, FVector WorldLocation, float MaxRange, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent* QueryController = nullptr); + + /* Returns true if the values are equal (A == B) */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Equal VR Grip", CompactNodeTitle = "==", Keywords = "== equal"), Category = "VRExpansionFunctions") + static bool EqualEqual_FBPActorGripInformation(const FBPActorGripInformation &A, const FBPActorGripInformation &B); + + /* Returns true if the grip is active (both valid and not paused) */ + UFUNCTION(BlueprintPure, Category = "VRExpansionFunctions") + static bool IsActiveGrip(const FBPActorGripInformation& Grip); + + /** Make a transform net quantize from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|TransformNetQuantize") + static FTransform_NetQuantize MakeTransform_NetQuantize(FVector Location, FRotator Rotation, FVector Scale); + + /** Breaks apart a transform net quantize into location, rotation and scale */ + UFUNCTION(BlueprintPure, Category = "VRExpansionLibrary|TransformNetQuantize", meta = (NativeBreakFunc)) + static void BreakTransform_NetQuantize(const FTransform_NetQuantize& InTransform, FVector& Location, FRotator& Rotation, FVector& Scale); + + /** Converts a FTransform into a FTransform_NetQuantize */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToTransform_NetQuantize (Transform)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|TransformNetQuantize") + static FTransform_NetQuantize Conv_TransformToTransformNetQuantize(const FTransform &InTransform); + + /** Converts a vector into a FVector_NetQuantize */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize (Vector)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize Conv_FVectorToFVectorNetQuantize(const FVector &InVector); + + /** Make a transform net quantize from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize MakeVector_NetQuantize(FVector InVector); + + /** Converts a vector into a FVector_NetQuantize10 */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize10 (Vector)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize10 Conv_FVectorToFVectorNetQuantize10(const FVector &InVector); + + /** Make a transform net quantize10 from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize10 MakeVector_NetQuantize10(FVector InVector); + + /** Converts a vector into a FVector_NetQuantize100 */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToVector_NetQuantize100 (Vector)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize100 Conv_FVectorToFVectorNetQuantize100(const FVector &InVector); + + /** Make a transform net quantize100 from location, rotation and scale */ + UFUNCTION(BlueprintPure, meta = (Scale = "1,1,1", Keywords = "construct build", NativeMakeFunc), Category = "VRExpansionLibrary|FVectorNetQuantize") + static FVector_NetQuantize100 MakeVector_NetQuantize100(FVector InVector); + + /** Converts a FBPGripPair into a MotionController */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToController (FBPGripPair)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary") + static UGripMotionControllerComponent * Conv_GripPairToMotionController(const FBPGripPair &GripPair); + + /** Converts a FBPGripPair into a GripID */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "ToGripID (FBPGripPair)", CompactNodeTitle = "->", BlueprintAutocast), Category = "VRExpansionLibrary") + static uint8 Conv_GripPairToGripID(const FBPGripPair &GripPair); + + // Adds a USceneComponent Subclass, that is based on the passed in Class, and added to the Outer(Actor) object + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Scene Component By Class"), Category = "VRExpansionLibrary") + static USceneComponent* AddSceneComponentByClass(UObject* Outer, TSubclassOf<USceneComponent> Class, const FTransform & ComponentRelativeTransform); + + + /** Resets a Filter so that the first time it is used again it is clean */ + UFUNCTION(BlueprintCallable, Category = "LowPassFilter_Peak") + static void ResetPeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter) + { + TargetPeakFilter.Reset(); + } + + /** Adds an entry to the Peak low pass filter */ + UFUNCTION(BlueprintCallable, Category = "LowPassFilter_Peak") + static void UpdatePeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter, FVector NewSample) + { + TargetPeakFilter.AddSample(NewSample); + } + + /** Gets the peak value of the Peak Low Pass Filter */ + UFUNCTION(BlueprintCallable, Category = "LowPassFilter_Peak") + static FVector GetPeak_PeakLowPassFilter(UPARAM(ref) FBPLowPassPeakFilter& TargetPeakFilter) + { + return TargetPeakFilter.GetPeak(); + } + + + /** Resets a Euro Low Pass Filter so that the first time it is used again it is clean */ + UFUNCTION(BlueprintCallable, Category = "EuroLowPassFilter") + static void ResetEuroSmoothingFilter(UPARAM(ref) FBPEuroLowPassFilter& TargetEuroFilter) + { + TargetEuroFilter.ResetSmoothingFilter(); + } + + /** Runs the smoothing function of a Euro Low Pass Filter */ + UFUNCTION(BlueprintCallable, Category = "EuroLowPassFilter") + static void RunEuroSmoothingFilter(UPARAM(ref) FBPEuroLowPassFilter& TargetEuroFilter, FVector InRawValue, const float DeltaTime, FVector & SmoothedValue) + { + SmoothedValue = TargetEuroFilter.RunFilterSmoothing(InRawValue, DeltaTime); + } + + // Applies the same laser smoothing that the vr editor uses to an array of points + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Smooth Update Laser Spline"), Category = "VRExpansionLibrary") + static void SmoothUpdateLaserSpline(USplineComponent * LaserSplineComponent, TArray<USplineMeshComponent *> LaserSplineMeshComponents, FVector InStartLocation, FVector InEndLocation, FVector InForward, float LaserRadius); + + /** + * Determine if any tag in the BaseContainer matches against any tag in OtherContainer with a required direct parent for both + * + * @param TagParent Required direct parent tag + * @param BaseContainer Container containing values to check + * @param OtherContainer Container to check against. + * + * @return True if any tag was found that matches any tags explicitly present in OtherContainer with the same DirectParent + */ + UFUNCTION(BlueprintPure, Category = "GameplayTags") + static bool MatchesAnyTagsWithDirectParentTag(FGameplayTag DirectParentTag,const FGameplayTagContainer& BaseContainer, const FGameplayTagContainer& OtherContainer); + + /** + * Determine if any tag in the BaseContainer has the exact same direct parent tag and returns the first one + * @param TagParent Required direct parent tag + * @param BaseContainer Container containing values to check + + * @return True if any tag was found and also returns the tag + */ + UFUNCTION(BlueprintPure, Category = "GameplayTags") + static bool GetFirstGameplayTagWithExactParent(FGameplayTag DirectParentTag, const FGameplayTagContainer& BaseContainer, FGameplayTag& FoundTag); + + // #TODO: probably need to implement this some day + // This doesn't work for the web browser widget but it does for the normal widgets like text boxes + // Just have to SetKeyboardFocus or SetUserFocus for the input widget first + // Main problem is it takes focus from the player then.... + /*UFUNCTION(BlueprintCallable, Category = "KeyboardSimulation") + static void SimulateCharacterEntry(UPARAM(ref) const FString& InChar) + { + + for (int32 CharIndex = 0; CharIndex < InChar.Len(); CharIndex++) + { + TCHAR CharKey = InChar[CharIndex]; + const bool bRepeat = false; + FCharacterEvent CharacterEvent(CharKey, FModifierKeysState(), 0, bRepeat); + FSlateApplication::Get().ProcessKeyCharEvent(CharacterEvent); + } + + }*/ + /* + void FVREditorActionCallbacks::SimulateBackspace() + { + // Slate editable text fields handle backspace as a character entry + FString BackspaceString = FString(TEXT("\b")); + bool bRepeat = false; + SimulateCharacterEntry(BackspaceString); + } + + void FVREditorActionCallbacks::SimulateKeyDown(const FKey Key, const bool bRepeat) + { + const uint32* KeyCodePtr; + const uint32* CharCodePtr; + FInputKeyManager::Get().GetCodesFromKey(Key, KeyCodePtr, CharCodePtr); + + uint32 KeyCode = KeyCodePtr ? *KeyCodePtr : 0; + uint32 CharCode = CharCodePtr ? *CharCodePtr : 0; + + FKeyEvent KeyEvent(Key, FModifierKeysState(), 0, bRepeat, KeyCode, CharCode); + bool DownResult = FSlateApplication::Get().ProcessKeyDownEvent(KeyEvent); + + if (CharCodePtr) + { + FCharacterEvent CharacterEvent(CharCode, FModifierKeysState(), 0, bRepeat); + FSlateApplication::Get().ProcessKeyCharEvent(CharacterEvent); + } + } + + void FVREditorActionCallbacks::SimulateKeyUp(const FKey Key) + { + const uint32* KeyCodePtr; + const uint32* CharCodePtr; + FInputKeyManager::Get().GetCodesFromKey(Key, KeyCodePtr, CharCodePtr); + + uint32 KeyCode = KeyCodePtr ? *KeyCodePtr : 0; + uint32 CharCode = CharCodePtr ? *CharCodePtr : 0; + + FKeyEvent KeyEvent(Key, FModifierKeysState(), 0, false, KeyCode, CharCode); + FSlateApplication::Get().ProcessKeyUpEvent(KeyEvent); + }*/ +}; + + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionPlugin.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionPlugin.h new file mode 100644 index 0000000..57fca23 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRExpansionPlugin.h @@ -0,0 +1,19 @@ +// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" + + +class FVRExpansionPluginModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + void RegisterSettings(); + + void UnregisterSettings(); +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGestureComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGestureComponent.h new file mode 100644 index 0000000..929a7aa --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGestureComponent.h @@ -0,0 +1,367 @@ + + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +#include "VRBPDatatypes.h" +#include "Engine/DataAsset.h" + +//#include "Engine/EngineTypes.h" +//#include "Engine/EngineBaseTypes.h" +#include "TimerManager.h" +#include "VRGestureComponent.generated.h" + +DECLARE_STATS_GROUP(TEXT("TICKGesture"), STATGROUP_TickGesture, STATCAT_Advanced); + +class USplineMeshComponent; +class USplineComponent; +class AVRBaseCharacter; + + +UENUM(Blueprintable) +enum class EVRGestureState : uint8 +{ + GES_None, + GES_Recording, + GES_Detecting +}; + + +UENUM(Blueprintable) +enum class EVRGestureMirrorMode : uint8 +{ + GES_NoMirror, + GES_MirrorLeft, + GES_MirrorRight, + GES_MirrorBoth +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct VREXPANSIONPLUGIN_API FVRGestureSettings +{ + GENERATED_BODY() +public: + + // Minimum length to start recognizing this gesture at + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + int Minimum_Gesture_Length; + + // Maximum distance between the last observations before throwing out this gesture, raise this to make it easier to start checking this gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + float firstThreshold; + + // Full threshold before detecting the gesture, raise this to lower accuracy but make it easier to detect this gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + float FullThreshold; + + // If set to left/right, will mirror the detected gesture if the gesture component is set to match that value + // If set to Both mode, the gesture will be checked both normal and mirrored and the best match will be chosen + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + EVRGestureMirrorMode MirrorMode; + + // If enabled this gesture will be checked when inside a DB + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + bool bEnabled; + + // If enabled this gesture will have sample data scaled to it when recognizing (if false you will want to record the gesture without scaling) + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture|Advanced") + bool bEnableScaling; + + FVRGestureSettings() + { + Minimum_Gesture_Length = 1; + firstThreshold = 20.0f; + FullThreshold = 20.0f; + MirrorMode = EVRGestureMirrorMode::GES_NoMirror; + bEnabled = true; + bEnableScaling = true; + } +}; + +USTRUCT(BlueprintType, Category = "VRGestures") +struct VREXPANSIONPLUGIN_API FVRGesture +{ + GENERATED_BODY() +public: + + // Name of the recorded gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FString Name; + + // Enum uint8 for end user use + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + uint8 GestureType; + + // Samples in the recorded gesture + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "VRGesture") + TArray<FVector> Samples; + + UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = "VRGesture") + FBox GestureSize; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGesture") + FVRGestureSettings GestureSettings; + + FVRGesture() + { + GestureType = 0; + GestureSize = FBox(); + } + + void CalculateSizeOfGesture(bool bAllowResizing = false, float TargetExtentSize = 1.f) + { + FVector NewSample; + for (int i = 0; i < Samples.Num(); ++i) + { + NewSample = Samples[i]; + GestureSize.Max.X = FMath::Max(NewSample.X, GestureSize.Max.X); + GestureSize.Max.Y = FMath::Max(NewSample.Y, GestureSize.Max.Y); + GestureSize.Max.Z = FMath::Max(NewSample.Z, GestureSize.Max.Z); + + GestureSize.Min.X = FMath::Min(NewSample.X, GestureSize.Min.X); + GestureSize.Min.Y = FMath::Min(NewSample.Y, GestureSize.Min.Y); + GestureSize.Min.Z = FMath::Min(NewSample.Z, GestureSize.Min.Z); + } + + if (bAllowResizing) + { + FVector BoxSize = GestureSize.GetSize(); + float Scaler = TargetExtentSize / BoxSize.GetMax(); + + for (int i = 0; i < Samples.Num(); ++i) + { + Samples[i] *= Scaler; + } + + GestureSize.Min *= Scaler; + GestureSize.Max *= Scaler; + } + } +}; + +/** +* Items Database DataAsset, here we can save all of our game items +*/ +UCLASS(BlueprintType, Category = "VRGestures") +class VREXPANSIONPLUGIN_API UGesturesDatabase : public UDataAsset +{ + GENERATED_BODY() +public: + + // Gestures in this database + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TArray <FVRGesture> Gestures; + + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + float TargetGestureScale; + + UGesturesDatabase() + { + TargetGestureScale = 100.0f; + } + + // Recalculate size of gestures and re-scale them to the TargetGestureScale (if bScaleToDatabase is true) + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void RecalculateGestures(bool bScaleToDatabase = true); + + // Fills a spline component with a gesture, optionally also generates spline mesh components for it (uses ones already attached if possible) + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void FillSplineWithGesture(UPARAM(ref)FVRGesture &Gesture, USplineComponent * SplineComponent, bool bCenterPointsOnSpline = true, bool bScaleToBounds = false, float OptionalBounds = 0.0f, bool bUseCurvedPoints = true, bool bFillInSplineMeshComponents = true, UStaticMesh * Mesh = nullptr, UMaterial * MeshMat = nullptr); + + // Imports a spline as a gesture, Segment len is the max segment length (will break lines up into lengths of this size) + UFUNCTION(BlueprintCallable, Category = "VRGestures") + bool ImportSplineAsGesture(USplineComponent * HostSplineComponent, FString GestureName, bool bKeepSplineCurves = true, float SegmentLen = 10.0f, bool bScaleToDatabase = true); + +}; + + +USTRUCT(BlueprintType, Category = "VRGestures") +struct VREXPANSIONPLUGIN_API FVRGestureSplineDraw +{ + GENERATED_BODY() +public: + + UPROPERTY() + TObjectPtr<USplineComponent> SplineComponent; + + UPROPERTY() + TArray<TObjectPtr<USplineMeshComponent>> SplineMeshes; + + int LastIndexSet; + int NextIndexCleared; + + // Marches through the array and clears the last point + void ClearLastPoint(); + + // Hides all spline meshes and re-inits the spline component + void Reset(); + + void Clear(); + + FVRGestureSplineDraw(); + + ~FVRGestureSplineDraw(); +}; + +/** Delegate for notification when the lever state changes. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FVRGestureDetectedSignature, uint8, GestureType, FString, DetectedGestureName, int, DetectedGestureIndex, UGesturesDatabase *, GestureDataBase, FVector, OriginalUnscaledGestureSize); + +/** +* A scene component that can sample its positions to record / track VR gestures +* Core code is from https://social.msdn.microsoft.com/Forums/en-US/4a428391-82df-445a-a867-557f284bd4b1/dynamic-time-warping-to-recognize-gestures?forum=kinectsdk +* I would also like to acknowledge RuneBerg as he appears to have used the same core codebase and I discovered that halfway through implementing this +* If this algorithm should not prove stable enough I will likely look into using a more complex and faster one in the future, I have several modifications +* to the base DTW algorithm noted from a few research papers. I only implemented this one first as it was a single header file and the quickest to implement. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRGestureComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + UVRGestureComponent(const FObjectInitializer& ObjectInitializer); + + + // Size of obeservations vectors. + //int dim; // Not needed, this is just dimensionality + // Can be used for arrays of samples (IE: multiple points), could add back in eventually + // if I decide to support three point tracked gestures or something at some point, but its a waste for single point. + + UFUNCTION(BlueprintImplementableEvent, Category = "BaseVRCharacter") + void OnGestureDetected(uint8 GestureType, FString &DetectedGestureName, int & DetectedGestureIndex, UGesturesDatabase * GestureDatabase, FVector OriginalUnscaledGestureSize); + + // Call to use an object + UPROPERTY(BlueprintAssignable, Category = "VRGestures") + FVRGestureDetectedSignature OnGestureDetected_Bind; + + // Known sequences + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr<UGesturesDatabase> GesturesDB; + + // Tolerance within we throw out duplicate samples + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + float SameSampleTolerance; + + // If a gesture is set to match this value then detection will mirror the gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + EVRGestureMirrorMode MirroringHand; + + // Tolerance within we throw out duplicate samples + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr<AVRBaseCharacter> TargetCharacter; + + FVRGestureSplineDraw RecordingGestureDraw; + + // Should we draw splines curved or straight + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + bool bDrawSplinesCurved; + + // If false will get the gesture in relative space instead + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + bool bGetGestureInWorldSpace; + + // Mesh to use when drawing splines + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr<UStaticMesh> SplineMesh; + + // Scaler to apply to the spline mesh components + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + FVector2D SplineMeshScaler; + + // Material to use when drawing splines + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + TObjectPtr<UMaterialInterface> SplineMaterial; + + // HTZ to run recording at for detection and saving - now being used as a frame time instead of a HTZ + float RecordingDelta; + + // Number of samples to keep in memory during detection + int RecordingBufferSize; + + float RecordingClampingTolerance; + bool bRecordingFlattenGesture; + bool bDrawRecordingGesture; + bool bDrawRecordingGestureAsSpline; + bool bGestureChanged; + + // Handle to our update timer + FTimerHandle TickGestureTimer_Handle; + + // Maximum vertical or horizontal steps in a row in the lookup table before throwing out a gesture + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "VRGestures") + int maxSlope; + + UPROPERTY(BlueprintReadOnly, Category = "VRGestures") + EVRGestureState CurrentState; + + // Currently recording gesture + UPROPERTY(BlueprintReadOnly, Category = "VRGestures") + FVRGesture GestureLog; + + inline float GetGestureDistance(FVector Seq1, FVector Seq2, bool bMirrorGesture = false) + { + if (bMirrorGesture) + { + return FVector::DistSquared(Seq1, FVector(Seq2.X, -Seq2.Y, Seq2.Z)); + } + + return FVector::DistSquared(Seq1, Seq2); + } + + void BeginDestroy() override; + + // Recalculates a gestures size and re-scales it to the given database + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void RecalculateGestureSize(UPARAM(ref) FVRGesture & InputGesture, UGesturesDatabase * GestureDB); + + // Draw a gesture with a debug line batch + UFUNCTION(BlueprintCallable, Category = "VRGestures", meta = (WorldContext = "WorldContextObject")) + void DrawDebugGesture(UObject* WorldContextObject, UPARAM(ref)FTransform& StartTransform, FVRGesture GestureToDraw, FColor const& Color, bool bPersistentLines = false, uint8 DepthPriority = 0, float LifeTime = -1.f, float Thickness = 0.f); + + FVector StartVector; + FTransform OriginatingTransform; + + /* Function to begin recording a gesture for detection or saving + * + * bRunDetection: Should we detect gestures or only record them + * bFlattenGestue: Should we flatten the gesture into 2 dimensions (more stable detection and recording, less pretty visually) + * bDrawGesture: Should we draw the gesture during recording of it + * bDrawAsSpline: If true we will use spline meshes, if false we will draw as debug lines + * SamplingHTZ: How many times a second we will record a gesture point, recording is done with a timer now, i would steer away + * from htz > possible frames as that could cause double timer updates with how timers are implemented. + * SampleBufferSize: How many points we will store in history at a time + * ClampingTolerance: If larger than 0.0, we will clamp points to a grid of this size + */ + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void BeginRecording(bool bRunDetection, bool bFlattenGesture = true, bool bDrawGesture = true, bool bDrawAsSpline = false, int SamplingHTZ = 30, int SampleBufferSize = 60, float ClampingTolerance = 0.01f); + + // Ends recording and returns the recorded gesture + UFUNCTION(BlueprintCallable, Category = "VRGestures") + FVRGesture EndRecording(); + + // Clears the current recording + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void ClearRecording(); + + // Saves a VRGesture to the database, if Scale To Database is true then it will scale the data + UFUNCTION(BlueprintCallable, Category = "VRGestures") + void SaveRecording(UPARAM(ref) FVRGesture &Recording, FString RecordingName, bool bScaleRecordingToDatabase = true); + + void CaptureGestureFrame(); + + // Ticks the logic from the gameplay timer. + void TickGesture(); + + + // Recognize gesture in the given sequence. + // It will always assume that the gesture ends on the last observation of that sequence. + // If the distance between the last observations of each sequence is too great, or if the overall DTW distance between the two sequences is too great, no gesture will be recognized. + void RecognizeGesture(FVRGesture inputGesture); + + + // Compute the min DTW distance between seq2 and all possible endings of seq1. + float dtw(FVRGesture seq1, FVRGesture seq2, bool bMirrorGesture = false, float Scaler = 1.f); + +}; + diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGlobalSettings.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGlobalSettings.h new file mode 100644 index 0000000..7eb57e2 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGlobalSettings.h @@ -0,0 +1,290 @@ +#pragma once +#include "CoreMinimal.h" +#include "GameFramework/PlayerInput.h" +#include "GameFramework/InputSettings.h" +#include "VRBPDatatypes.h" +#include "Curves/CurveFloat.h" +#include "GripScripts/GS_Melee.h" +#include "GripScripts/GS_GunTools.h" +#include "VRGlobalSettings.generated.h" + + +USTRUCT(BlueprintType, Category = "ControllerProfiles") +struct VREXPANSIONPLUGIN_API FBPVRControllerProfile +{ + GENERATED_BODY() +public: + + // Name of controller profile + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles") + FName ControllerName; + + // Offset to use with this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles") + FTransform_NetQuantize SocketOffsetTransform; + + // Offset to use with this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles") + bool bUseSeperateHandOffsetTransforms; + + // Offset to use with this controller + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ControllerProfiles", meta = (editcondition = "bUseSeperateHandOffsetTransforms")) + FTransform_NetQuantize SocketOffsetTransformRightHand; + + + FBPVRControllerProfile() : + ControllerName(NAME_None), + SocketOffsetTransform(FTransform::Identity), + bUseSeperateHandOffsetTransforms(false), + SocketOffsetTransformRightHand(FTransform::Identity) + {} + + FBPVRControllerProfile(FName ControllerName) : + ControllerName(ControllerName), + SocketOffsetTransform(FTransform::Identity), + bUseSeperateHandOffsetTransforms(false), + SocketOffsetTransformRightHand(FTransform::Identity) + {} + + FBPVRControllerProfile(FName ControllerNameIn, const FTransform & Offset) : + ControllerName(ControllerNameIn), + SocketOffsetTransform(Offset), + bUseSeperateHandOffsetTransforms(false), + SocketOffsetTransformRightHand(FTransform::Identity) + {} + + FBPVRControllerProfile(FName ControllerNameIn, const FTransform & Offset, const FTransform & OffsetRight) : + ControllerName(ControllerNameIn), + SocketOffsetTransform(Offset), + bUseSeperateHandOffsetTransforms(true), + SocketOffsetTransformRightHand(OffsetRight) + {} + + FORCEINLINE bool operator==(const FBPVRControllerProfile &Other) const + { + return this->ControllerName == Other.ControllerName; + } +}; + +UCLASS(config = Engine, defaultconfig) +class VREXPANSIONPLUGIN_API UVRGlobalSettings : public UObject +{ + GENERATED_BODY() + +public: + UVRGlobalSettings(const FObjectInitializer& ObjectInitializer); + + // Set scaler values + void SetScalers(); + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + // Whether we should use the physx to chaos translation scalers or not + // This should be off on native chaos projects that have been set with the correct stiffness and damping settings already + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + bool bUseChaosTranslationScalers; + + // If true we will also set the engines chaos scalers as well to equal our overrides + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + bool bSetEngineChaosScalers; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float LinearDriveStiffnessScale; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float LinearDriveDampingScale; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float AngularDriveStiffnessScale; + + // A scaler to apply to constraint drives when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics") + float AngularDriveDampingScale; + + // Hard joint stiffness + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float JointStiffness; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftLinearStiffnessScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftLinearDampingScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftAngularStiffnessScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float SoftAngularDampingScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float JointLinearBreakScale; + + // A scaler to apply to constraints when chaos is active + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "ChaosPhysics|Constraints") + float JointAngularBreakScale; + + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + bool bUseGlobalLerpToHand; + + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + bool bSkipLerpToHandIfHeld; + + // If the initial grip distance is closer than this value then the lerping will not be performed. + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float MinDistanceForLerp; + + // How many seconds the lerp should take + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float LerpDuration; + + // The minimum speed (in UU per second) that that the lerp should have across the initial grip distance + // Will speed the LerpSpeed up to try and maintain this initial speed if required + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float MinSpeedForLerp; + + // The maximum speed (in UU per second) that the lerp should have across the initial grip distance + // Will slow the LerpSpeed down to try and maintain this initial speed if required + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + float MaxSpeedForLerp; + + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand") + EVRLerpInterpolationMode LerpInterpolationMode; + + // Whether to use a curve map to map the lerp to + UPROPERTY(config, BlueprintReadWrite, EditAnywhere, Category = "GlobalLerpToHand|Curve") + bool bUseCurve; + + // The curve to follow when using a curve map, only uses from 0.0 - 1.0 of the curve timeline and maps it across the entire duration + UPROPERTY(config, Category = "GlobalLerpToHand|Curve", EditAnywhere, meta = (editcondition = "bUseCurve")) + FRuntimeFloatCurve OptionalCurveToFollow; + + // Alter the values of the virtual stock settings and save them out + UFUNCTION(BlueprintPure, Category = "GlobalLerpToHand") + static bool IsGlobalLerpEnabled(); + + // List of surfaces and their properties for the melee script + UPROPERTY(config, EditAnywhere, Category = "MeleeSettings") + TArray<FBPHitSurfaceProperties> MeleeSurfaceSettings; + + // Default global virtual stock settings for the gun script + UPROPERTY(config, EditAnywhere, Category = "GunSettings") + FBPVirtualStockSettings VirtualStockSettings; + + // Setting to use for the OneEuro smoothing low pass filter when double gripping something held with a hand + UPROPERTY(config, EditAnywhere, Category = "GunSettings|Secondary Grip 1Euro Settings") + float OneEuroMinCutoff; + + // Setting to use for the OneEuro smoothing low pass filter when double gripping something held with a hand + UPROPERTY(config, EditAnywhere, Category = "GunSettings|Secondary Grip 1Euro Settings") + float OneEuroCutoffSlope; + + // Setting to use for the OneEuro smoothing low pass filter when double gripping something held with a hand + UPROPERTY(config, EditAnywhere, Category = "GunSettings|Secondary Grip 1Euro Settings") + float OneEuroDeltaCutoff; + + // Get the values of the virtual stock settings + UFUNCTION(BlueprintCallable, Category = "MeleeSettings") + static void GetMeleeSurfaceGlobalSettings(TArray<FBPHitSurfaceProperties>& OutMeleeSurfaceSettings); + + // Get the values of the virtual stock settings + UFUNCTION(BlueprintCallable, Category = "GunSettings|VirtualStock") + static void GetVirtualStockGlobalSettings(FBPVirtualStockSettings& OutVirtualStockSettings); + + // Alter the values of the virtual stock settings and save them out + UFUNCTION(BlueprintCallable, Category = "GunSettings|VirtualStock") + static void SaveVirtualStockGlobalSettings(FBPVirtualStockSettings NewVirtualStockSettings); + + + DECLARE_MULTICAST_DELEGATE(FVRControllerProfileChangedEvent); + /** Delegate for notification when the controller profile changes. */ + FVRControllerProfileChangedEvent OnControllerProfileChangedEvent; + + // Controller profiles to store related information on a per profile basis + UPROPERTY(config, EditAnywhere, Category = "ControllerProfiles") + TArray<FBPVRControllerProfile> ControllerProfiles; + + // Store these to save some processing when getting the transform after a profile is loaded + FName CurrentControllerProfileInUse; + FTransform CurrentControllerProfileTransform; + bool bUseSeperateHandTransforms; + FTransform CurrentControllerProfileTransformRight; + + // Adjust the transform of a socket for a particular controller model, if a name is not sent in, it will use the currently loaded one + // If there is no currently loaded one, it will return the input transform as is. + // If bIsRightHand and the target profile uses seperate hand transforms it will use the right hand transform + UFUNCTION(BlueprintPure, Category = "VRControllerProfiles") + static FTransform AdjustTransformByControllerProfile(FName OptionalControllerProfileName, const FTransform& SocketTransform, bool bIsRightHand = false); + + // Adjust the transform of a socket for a particular controller model + // If there is no currently loaded one, it will return the input transform as is. + // If bIsRightHand and the target profile uses seperate hand transforms it will use the right hand transform + UFUNCTION(BlueprintPure, Category = "VRControllerProfiles") + static FTransform AdjustTransformByGivenControllerProfile(UPARAM(ref) FBPVRControllerProfile& ControllerProfile, const FTransform& SocketTransform, bool bIsRightHand = false); + + // Get array of controller profiles + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static TArray<FBPVRControllerProfile> GetControllerProfiles(); + + // Overwrite a controller profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void OverwriteControllerProfile(UPARAM(ref)FBPVRControllerProfile& OverwritingProfile, bool bSaveOutToConfig = true); + + // Add a controller profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void AddControllerProfile(UPARAM(ref)FBPVRControllerProfile& NewProfile, bool bSaveOutToConfig = true); + + // Overwrite a controller profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void DeleteControllerProfile(FName ControllerProfileName, bool bSaveOutToConfig = true); + + // Saved the VRGlobalSettings out to its config file, will include any alterations that you made to the profile + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles|Operations") + static void SaveControllerProfiles(); + + + // Get name of currently loaded profile (if one is loaded) + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static FName GetCurrentProfileName(bool& bHadLoadedProfile); + + // Get name of currently loaded profile (if one is loaded) + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static FBPVRControllerProfile GetCurrentProfile(bool& bHadLoadedProfile); + + // Get a controller profile by name + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static bool GetControllerProfile(FName ControllerProfileName, FBPVRControllerProfile& OutProfile); + + // Load a controller profile by name + // Action / Axis mappings overwrite ones with the same action/axis name in the input settings. + // If you have an action/axis override but don't assign buttons to it then it will just delete it. + // This can be useful for removing actions and using multiple actions (IE: Grip Touch, Grip Vive actions) + // For when just changing the buttons isn't good enough + // If bSetDefaults is true, will set this as the currently loaded profile + // Otherwise will just load it (useful for Left/Right handed profile additions and the like to have it false) + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static bool LoadControllerProfileByName(FName ControllerProfileName, bool bSetAsCurrentProfile = true); + + // Load a controller profile + // Action / Axis mappings overwrite ones with the same action/axis name in the input settings. + // If you have an action/axis override but don't assign buttons to it then it will just delete it. + // This can be useful for removing actions and using multiple actions (IE: Grip Touch, Grip Vive actions) + // For when just changing the buttons isn't good enough + // If bSetDefaults is true, will set this as the currently loaded profile + // Otherwise will just load it (useful for Left/Right handed profile additions and the like to have it false) + // AS OF 4.25 AXIS and ACTION mappings do nothing, will be deleting around 4.26 #TODO: Delete around 4.26 + UFUNCTION(BlueprintCallable, Category = "VRControllerProfiles") + static bool LoadControllerProfile(const FBPVRControllerProfile& ControllerProfile, bool bSetAsCurrentProfile = true); + + virtual void PostInitProperties() override; +}; diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGripInterface.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGripInterface.h new file mode 100644 index 0000000..a77a3d4 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRGripInterface.h @@ -0,0 +1,158 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +//#include "UObject/ObjectMacros.h" +//#include "UObject/ScriptMacros.h" +#include "VRBPDatatypes.h" +#include "UObject/Interface.h" + +#include "VRGripInterface.generated.h" + +// Forward declare +class UGripMotionControllerComponent; +class UVRGripScriptBase; + +UINTERFACE(Blueprintable) +class VREXPANSIONPLUGIN_API UVRGripInterface: public UInterface +{ + GENERATED_UINTERFACE_BODY() +}; + + +/** Delegate for notification when the controller grips a new object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FVROnGripSignature, UGripMotionControllerComponent *, GrippingController, const FBPActorGripInformation&, GripInformation); + +/** Delegate for notification when the controller drops a gripped object. */ +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FVROnDropSignature, UGripMotionControllerComponent*, GrippingController, const FBPActorGripInformation&, GripInformation, bool, bWasSocketed); + +class VREXPANSIONPLUGIN_API IVRGripInterface +{ + GENERATED_IINTERFACE_BODY() + +public: + + // Set up as deny instead of allow so that default allows for gripping + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface", meta = (DisplayName = "IsDenyingGrips")) + bool DenyGripping(UGripMotionControllerComponent * GripInitiator = nullptr); + + // How an interfaced object behaves when teleporting + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripInterfaceTeleportBehavior TeleportBehavior(); + + // Should this object simulate on drop + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool SimulateOnDrop(); + + // Grip type to use + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripCollisionType GetPrimaryGripType(bool bIsSlot); + + // Double Grip Type + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + ESecondaryGripType SecondaryGripType(); + + // Define the late update setting + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripLateUpdateSettings GripLateUpdateSetting(); + + // Define which movement repliation setting to use + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + EGripMovementReplicationSettings GripMovementReplicationType(); + + // What grip stiffness and damping to use if using a physics constraint + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void GetGripStiffnessAndDamping(float &GripStiffnessOut, float &GripDampingOut); + + // Get the advanced physics settings for this grip + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + FBPAdvGripSettings AdvancedGripSettings(); + + // What distance to break a grip at (only relevent with physics enabled grips + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + float GripBreakDistance(); + + /** + * Called to get the closest grip socket in range + * @param WorldLocation - World Location to check near + * @param bSecondarySlot - True if this is a check for a secondary slot or not + * @param CallingController - Controller checking for the slot (can be used in overrides for per hand checks) + * @param OverridePrefix - A different substring to check against in the socket names to find relevant ones + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void ClosestGripSlotInRange(FVector WorldLocation, bool bSecondarySlot, bool & bHadSlotInRange, FTransform & SlotWorldTransform, FName & SlotName, UGripMotionControllerComponent * CallingController = nullptr, FName OverridePrefix = NAME_None); + + // Events that can be called for interface inheriting actors + + // Event triggered each tick on the interfaced object when gripped, can be used for custom movement or grip based logic + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void TickGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation, float DeltaTime); + + // Event triggered on the interfaced object when gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnGripRelease(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + + virtual void Native_NotifyThrowGripDelegates(UGripMotionControllerComponent* Controller, bool bGripped, const FBPActorGripInformation& GripInformation, bool bWasSocketed = false); + + // Event triggered on the interfaced object when child component is gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnChildGrip(UGripMotionControllerComponent * GrippingController, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when child component is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnChildGripRelease(UGripMotionControllerComponent * ReleasingController, const FBPActorGripInformation & GripInformation, bool bWasSocketed = false); + + // Event triggered on the interfaced object when secondary gripped + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGrip(UGripMotionControllerComponent * GripOwningController, USceneComponent * SecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + // Event triggered on the interfaced object when secondary grip is released + UFUNCTION(BlueprintNativeEvent, Category = "VRGripInterface") + void OnSecondaryGripRelease(UGripMotionControllerComponent * GripOwningController, USceneComponent * ReleasingSecondaryGripComponent, const FBPActorGripInformation & GripInformation); + + // Interaction Functions + + // Call to use an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnUsed(); + + // Call to stop using an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnEndUsed(); + + // Call to use an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnSecondaryUsed(); + + // Call to stop using an object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnEndSecondaryUsed(); + + // Call to send an action event to the object + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void OnInput(FKey Key, EInputEvent KeyEvent); + + // Check if an object allows multiple grips at one time + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool AllowsMultipleGrips(); + + // Returns if the object is held and if so, which controllers are holding it + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + void IsHeld(TArray<FBPGripPair> & HoldingControllers, bool & bIsHeld); + + // Sets is held, used by the plugin + UFUNCTION(BlueprintNativeEvent, /*BlueprintCallable,*/ Category = "VRGripInterface") + void SetHeld(UGripMotionControllerComponent * HoldingController, uint8 GripID, bool bIsHeld); + + // Returns if the object requests to be socketed to something + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool RequestsSocketing(USceneComponent *& ParentToSocketTo, FName & OptionalSocketName, FTransform_NetQuantize & RelativeTransform); + + // Get grip scripts + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "VRGripInterface") + bool GetGripScripts(TArray<UVRGripScriptBase*>& ArrayReference); +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPathFollowingComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPathFollowingComponent.h new file mode 100644 index 0000000..eb8a7ab --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPathFollowingComponent.h @@ -0,0 +1,56 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "VRRootComponent.h" +#include "VRBaseCharacterMovementComponent.h" +#include "Navigation/PathFollowingComponent.h" +#include "AbstractNavData.h" +#include "Runtime/Launch/Resources/Version.h" +#include "VRPathFollowingComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogPathFollowingVR, Warning, All); + +UCLASS() +class VREXPANSIONPLUGIN_API UVRPathFollowingComponent : public UPathFollowingComponent +{ + GENERATED_BODY() + +public: + UPROPERTY(transient) + UVRBaseCharacterMovementComponent* VRMovementComp; + + // Add link to VRMovementComp + void SetMovementComponent(UNavMovementComponent* MoveComp) override; + + ///////UPDATE WITH 4.13//////// + // Have to override this to call the correct HasReachCurrentTarget + void UpdatePathSegment() override; + bool HasReachedCurrentTarget(const FVector& CurrentLocation) const; + + // Had to override this to get the correct DebugReachTest + virtual void GetDebugStringTokens(TArray<FString>& Tokens, TArray<EPathFollowingDebugTokens::Type>& Flags) const override; + void DebugReachTest(float& CurrentDot, float& CurrentDistance, float& CurrentHeight, uint8& bDotFailed, uint8& bDistanceFailed, uint8& bHeightFailed) const; + + void FollowPathSegment(float DeltaTime) override; + int32 DetermineStartingPathPoint(const FNavigationPath* ConsideredPath) const override; + + + // This has a foot location when using meta paths, i'm not overriding it yet but this means that meta paths might have slightly bugged implementation. + /** notify about finished movement */ + //virtual void OnPathFinished(const FPathFollowingResult& Result) override; + + /** pause path following + * @param RequestID - request to pause, FAIRequestID::CurrentRequest means pause current request, regardless of its ID */ + void PauseMove(FAIRequestID RequestID = FAIRequestID::CurrentRequest, EPathFollowingVelocityMode VelocityMode = EPathFollowingVelocityMode::Reset) override; + // Now has an actor feet call .......Just a debug reference + //virtual FAIRequestID RequestMove(FNavPathSharedPtr Path, FRequestCompletedSignature OnComplete, const AActor* DestinationActor = NULL, float AcceptanceRadius = UPathFollowingComponent::DefaultAcceptanceRadius, bool bStopOnOverlap = true, FCustomMoveSharedPtr GameData = NULL) override; + // + // Fine in 4.13 + bool ShouldCheckPathOnResume() const override; + + // Fine in 4.13, had to change feet based for both + bool UpdateBlockDetection() override; + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPlayerController.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPlayerController.h new file mode 100644 index 0000000..aa70f8a --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRPlayerController.h @@ -0,0 +1,86 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "Engine/LocalPlayer.h" +#include "VRPlayerController.generated.h" + +// A base player controller specifically for handling OnCameraManagerCreated. +// Used in case you don't want the VRPlayerCharacter changes in a PendingPlayerController +UCLASS() +class VREXPANSIONPLUGIN_API AVRBasePlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + + // Event called in BPs when the camera manager is created (only fired on locally controlled player controllers) + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnCameraManagerCreated"), Category = Actor) + void OnCameraManagerCreated(APlayerCameraManager* CameraManager); + + virtual void SpawnPlayerCameraManager() override + { + Super::SpawnPlayerCameraManager(); + + if (PlayerCameraManager != NULL && IsLocalController()) + { + OnCameraManagerCreated(PlayerCameraManager); + } + } + +}; + + +UCLASS() +class VREXPANSIONPLUGIN_API AVRPlayerController : public AVRBasePlayerController +{ + GENERATED_BODY() + +public: + AVRPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + // New path finding return, not actually sending anything currently unless the character created one for us + // or the user added one to us. The default implementation is fine for us. + //virtual IPathFollowingAgentInterface* GetPathFollowingAgent() const override; + + // Disable the ServerUpdateCamera function defaulted on in PlayerCameraManager + // We are manually replicating the camera position and rotation ourselves anyway + // Generally that function will just be additional replication overhead + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRPlayerController") + bool bDisableServerUpdateCamera; + + /** spawn cameras for servers and owning players */ + virtual void SpawnPlayerCameraManager() override; + + FRotator LastRotationInput; + + /** + * Processes player input (immediately after PlayerInput gets ticked) and calls UpdateRotation(). + * PlayerTick is only called if the PlayerController has a PlayerInput object. Therefore, it will only be called for locally controlled PlayerControllers. + * I am overriding this so that for VRCharacters it doesn't apply the view rotation and instead lets CMC handle it + */ + virtual void PlayerTick(float DeltaTime) override; +}; + +/** +* Utility class, when set as the default local player it will spawn the target PlayerController class instead as the pending player controller +*/ +UCLASS(Blueprintable, meta = (ShortTooltip = "Utility class, when set as the default local player it will spawn the target PlayerController class instead as the pending one")) +class VREXPANSIONPLUGIN_API UVRLocalPlayer : public ULocalPlayer +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "LocalPlayer") + TSubclassOf<class APlayerController> OverridePendingLevelPlayerControllerClass; + + virtual bool SpawnPlayActor(const FString& URL, FString& OutError, UWorld* InWorld) + { + if (OverridePendingLevelPlayerControllerClass) + { + PendingLevelPlayerControllerClass = OverridePendingLevelPlayerControllerClass; + } + + return Super::SpawnPlayActor(URL, OutError, InWorld); + } +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRRootComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRRootComponent.h new file mode 100644 index 0000000..3eeee75 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRRootComponent.h @@ -0,0 +1,296 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +//#include "Components/ShapeComponent.h" +#include "VRTrackedParentInterface.h" +#include "VRBaseCharacter.h" +#include "VRExpansionFunctionLibrary.h" +#include "GameFramework/PhysicsVolume.h" +#include "Components/CapsuleComponent.h" +#include "VRRootComponent.generated.h" + +//For UE4 Profiler ~ Stat Group +//DECLARE_STATS_GROUP(TEXT("VRPhysicsUpdate"), STATGROUP_VRPhysics, STATCAT_Advanced); + +//class AVRBaseCharacter; + +DECLARE_LOG_CATEGORY_EXTERN(LogVRRootComponent, Log, All); + +DECLARE_STATS_GROUP(TEXT("VRRootComponent"), STATGROUP_VRRootComponent, STATCAT_Advanced); +DECLARE_CYCLE_STAT(TEXT("VR Root Set Half Height"), STAT_VRRootSetHalfHeight, STATGROUP_VRRootComponent); +DECLARE_CYCLE_STAT(TEXT("VR Root Set Capsule Size"), STAT_VRRootSetCapsuleSize, STATGROUP_VRRootComponent); + +/** +* A capsule component that repositions its physics scene and rendering location to the camera/HMD's relative position. +* Generally not to be used by itself unless on a base Pawn and not a character, the VRCharacter has been highly customized to correctly support it. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = VRExpansionLibrary) +class VREXPANSIONPLUGIN_API UVRRootComponent : public UCapsuleComponent, public IVRTrackedParentInterface +{ + GENERATED_BODY() + +public: + UVRRootComponent(const FObjectInitializer& ObjectInitializer); + + friend class FDrawCylinderSceneProxy; + + bool bCalledUpdateTransform; + + // Overriding this and applying the offset to world position for the elements + virtual void GetNavigationData(FNavigationRelevantData& Data) const override; + + FORCEINLINE void GenerateOffsetToWorld(bool bUpdateBounds = true, bool bGetPureYaw = true); + + // If valid will use this as the tracked parent instead of the HMD / Parent + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRTrackedParentInterface") + FBPVRWaistTracking_Info OptionalWaistTrackingParent; + + virtual void SetTrackedParent(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode) override + { + IVRTrackedParentInterface::Default_SetTrackedParent_Impl(NewParentComponent, WaistRadius, WaistTrackingMode, OptionalWaistTrackingParent, this); + } + + /** + * This is overidden for the VR Character to re-set physics location + * Change the capsule size. This is the unscaled size, before component scale is applied. + * @param InRadius : radius of end-cap hemispheres and center cylinder. + * @param InHalfHeight : half-height, from capsule center to end of top or bottom hemisphere. + * @param bUpdateOverlaps: if true and this shape is registered and collides, updates touching array for owner actor. + */ + UFUNCTION(BlueprintCallable, Category = "Components|Capsule") + virtual void SetCapsuleSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps = true); + + // Used to update the capsule half height and calculate the new offset value for VR + UFUNCTION(BlueprintCallable, Category = "Components|Capsule") + void SetCapsuleHalfHeightVR(float HalfHeight, bool bUpdateOverlaps = true); + + inline void OnUpdateTransform_Public(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport = ETeleportType::None) + { + OnUpdateTransform(UpdateTransformFlags, Teleport); + if (bNavigationRelevant && bRegistered) + { + UpdateNavigationData(); + PostUpdateNavigationData(); + } + } + + virtual void SetSimulatePhysics(bool bSimulate) override; + +protected: + virtual bool MoveComponentImpl(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* OutHit = NULL, EMoveComponentFlags MoveFlags = MOVECOMP_NoFlags, ETeleportType Teleport = ETeleportType::None) override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport = ETeleportType::None) override; + + void SendPhysicsTransform(ETeleportType Teleport); + virtual bool UpdateOverlapsImpl(const TOverlapArrayView* NewPendingOverlaps = nullptr, bool bDoNotifies = true, const TOverlapArrayView* OverlapsAtEndLocation = nullptr) override; + + /** Convert a set of overlaps from a symmetric change in rotation to a subset that includes only those at the end location (filling in OverlapsAtEndLocation). */ + template<typename AllocatorType> + bool ConvertRotationOverlapsToCurrentOverlaps(TArray<FOverlapInfo, AllocatorType>& OutOverlapsAtEndLocation, const TOverlapArrayView& CurrentOverlaps); + + template<typename AllocatorType> + bool GetOverlapsWithActor_Template(const AActor* Actor, TArray<FOverlapInfo, AllocatorType>& OutOverlaps) const; + + /** Convert a set of overlaps from a sweep to a subset that includes only those at the end location (filling in OverlapsAtEndLocation). */ + template<typename AllocatorType> + bool ConvertSweptOverlapsToCurrentOverlaps(TArray<FOverlapInfo, AllocatorType>& OutOverlapsAtEndLocation, const TOverlapArrayView& SweptOverlaps, int32 SweptOverlapsIndex, const FVector& EndLocation, const FQuat& EndRotationQuat); + + +public: + void BeginPlay() override; + virtual void InitializeComponent() override; + + bool IsLocallyControlled() const; + + UPROPERTY(BlueprintReadWrite, Transient, Category = "VRExpansionLibrary") + TObjectPtr<USceneComponent> TargetPrimitiveComponent; + + //UPROPERTY(BlueprintReadWrite, Transient, Category = "VRExpansionLibrary") + TObjectPtr<AVRBaseCharacter> owningVRChar; + + //UPROPERTY(BlueprintReadWrite, Transient, Category = "VRExpansionLibrary") + //UCapsuleComponent * VRCameraCollider; + + FVector DifferenceFromLastFrame; + //UPROPERTY(BlueprintReadOnly, Transient, Category = "VRExpansionLibrary") + FTransform OffsetComponentToWorld; + + // Used to offset the collision (IE backwards from the player slightly. + // The default 2.15 Z offset is to account for floor hover from the character movement component. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + FVector VRCapsuleOffset; + + // If true we will stop tracking the camera / hmd until enabled again + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bPauseTracking; + + UFUNCTION(BlueprintCallable, Category = "VRExpansionLibrary") + void SetTrackingPaused(bool bPaused); + + // #TODO: Test with 100.f rounding to make sure it isn't noticable, currently that is what it is + // If true will subtract the HMD's location from the position, useful for if the actors base is set to the HMD location always (simple character). + /*UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReplicatedCamera") + bool bOffsetByHMD; + */ + + + + // #TODO: See if making this multiplayer compatible is viable + // Offsets capsule to be centered on HMD - currently NOT multiplayer compatible + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bCenterCapsuleOnHMD; + + // Allows the root component to be blocked by simulating objects (default off due to sickness inducing stuttering). + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bAllowSimulatingCollision; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + bool bUseWalkingCollisionOverride; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VRExpansionLibrary") + TEnumAsByte<ECollisionChannel> WalkingCollisionOverride; + + /*ECollisionChannel GetVRCollisionObjectType() + { + if (bUseWalkingCollisionOverride) + return WalkingCollisionOverride; + else + return GetCollisionObjectType(); + }*/ + + FVector curCameraLoc; + FRotator curCameraRot; + FRotator StoredCameraRotOffset; + + FVector lastCameraLoc; + FRotator lastCameraRot; + + // While misnamed, is true if we collided with a wall/obstacle due to the HMDs movement in this frame (not movement components) + UPROPERTY(BlueprintReadOnly, Category = "VRExpansionLibrary") + bool bHadRelativeMovement; + + FPrimitiveSceneProxy* CreateSceneProxy() override; + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + virtual void UpdatePhysicsVolume(bool bTriggerNotifiers) override; + + inline bool AreWeOverlappingVolume(APhysicsVolume* V) + { + bool bInsideVolume = true; + if (!V->bPhysicsOnContact) + { + FVector ClosestPoint(0.f); + // If there is no primitive component as root we consider you inside the volume. This is odd, but the behavior + // has existed for a long time, so keeping it this way + UPrimitiveComponent* RootPrimitive = Cast<UPrimitiveComponent>(V->GetRootComponent()); + if (RootPrimitive) + { + float DistToCollisionSqr = -1.f; + if (RootPrimitive->GetSquaredDistanceToCollision(OffsetComponentToWorld.GetTranslation(), DistToCollisionSqr, ClosestPoint)) + { + bInsideVolume = (DistToCollisionSqr == 0.f); + } + else + { + bInsideVolume = false; + } + } + } + + return bInsideVolume; + } + +public: + // Begin UObject interface +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + void PreEditChange(FProperty* PropertyThatWillChange); +#endif // WITH_EDITOR + // End UObject interface + + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; + + private: + friend class FVRCharacterScopedMovementUpdate; +}; + + +// Have to declare inlines here for blueprint +void inline UVRRootComponent::GenerateOffsetToWorld(bool bUpdateBounds, bool bGetPureYaw) +{ + FRotator CamRotOffset; + + if (bGetPureYaw) + CamRotOffset = StoredCameraRotOffset;//UVRExpansionFunctionLibrary::GetHMDPureYaw_I(curCameraRot); + else + CamRotOffset = curCameraRot; + + /*if(bOffsetByHMD) + { + OffsetComponentToWorld = FTransform(CamRotOffset.Quaternion(), FVector(0, 0, bCenterCapsuleOnHMD ? curCameraLoc.Z : CapsuleHalfHeight) + CamRotOffset.RotateVector(VRCapsuleOffset), FVector(1.0f)) * GetComponentTransform(); + } + else*/ + { + OffsetComponentToWorld = FTransform(CamRotOffset.Quaternion(), FVector(curCameraLoc.X, curCameraLoc.Y, bCenterCapsuleOnHMD ? curCameraLoc.Z : CapsuleHalfHeight) + CamRotOffset.RotateVector(VRCapsuleOffset), FVector(1.0f)) * GetComponentTransform(); + } + + if (owningVRChar) + { + owningVRChar->OffsetComponentToWorld = OffsetComponentToWorld; + } + + if (bUpdateBounds) + UpdateBounds(); +} + + +FORCEINLINE void UVRRootComponent::SetCapsuleHalfHeightVR(float HalfHeight, bool bUpdateOverlaps) +{ + SCOPE_CYCLE_COUNTER(STAT_VRRootSetHalfHeight); + + if (FMath::IsNearlyEqual(HalfHeight, CapsuleHalfHeight)) + { + return; + } + + SetCapsuleSizeVR(GetUnscaledCapsuleRadius(), HalfHeight, bUpdateOverlaps); +} + +FORCEINLINE void UVRRootComponent::SetCapsuleSizeVR(float NewRadius, float NewHalfHeight, bool bUpdateOverlaps) +{ + SCOPE_CYCLE_COUNTER(STAT_VRRootSetCapsuleSize); + + if (FMath::IsNearlyEqual(NewRadius, CapsuleRadius) && FMath::IsNearlyEqual(NewHalfHeight, CapsuleHalfHeight)) + { + return; + } + + CapsuleHalfHeight = FMath::Max3(0.f, NewHalfHeight, NewRadius); + + // Make sure that our character parent updates its replicated var as well + if (AVRBaseCharacter * BaseChar = Cast<AVRBaseCharacter>(GetOwner())) + { + if (GetNetMode() < ENetMode::NM_Client && BaseChar->VRReplicateCapsuleHeight) + BaseChar->ReplicatedCapsuleHeight.CapsuleHeight = CapsuleHalfHeight; + } + + CapsuleRadius = FMath::Max(0.f, NewRadius); + UpdateBounds(); + UpdateBodySetup(); + MarkRenderStateDirty(); + GenerateOffsetToWorld(); + + // do this if already created + // otherwise, it hasn't been really created yet + if (bPhysicsStateCreated) + { + // Update physics engine collision shapes + BodyInstance.UpdateBodyScale(GetComponentTransform().GetScale3D(), true); + + if (bUpdateOverlaps && IsCollisionEnabled() && GetOwner()) + { + UpdateOverlaps(); + } + } +} \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRStereoWidgetComponent.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRStereoWidgetComponent.h new file mode 100644 index 0000000..6cf4b44 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRStereoWidgetComponent.h @@ -0,0 +1,238 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +//#include "Engine/Engine.h" +//#include "VRBPDatatypes.h" +#include "Components/StereoLayerComponent.h" +#include "Components/WidgetComponent.h" +//#include "Animation/UMGSequencePlayer.h" + +#include "VRStereoWidgetComponent.generated.h" + +class FWidgetRenderer; +class UUserWidget; +class UTextureRenderTarget2D; +class UStereoLayerShape; + + +/** +* A stereo component that displays a widget instead of a texture. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin), HideCategories = ("Stereoscopic Properties", Collision)) +class VREXPANSIONPLUGIN_API UVRStereoWidgetRenderComponent : public UStereoLayerComponent +{ + GENERATED_BODY() + +public: + UVRStereoWidgetRenderComponent(const FObjectInitializer& ObjectInitializer); + + /** The class of User Widget to create and display an instance of */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + TSubclassOf<UUserWidget> WidgetClass; + + /** The User Widget object displayed and managed by this component */ + UPROPERTY(BlueprintReadWrite, Transient, DuplicateTransient, Category = "WidgetSettings") + TObjectPtr<UUserWidget> Widget; + + /** If true then we sample the requested size of the widget and reset the texture to be that size */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + bool bDrawAtDesiredSize; + + /** The desired render scale of the widget */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + float WidgetRenderScale; + + /** The desired render gamma of the widget */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + float WidgetRenderGamma; + + /** Automatically correct for gamma */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + bool bUseGammaCorrection; + + /** The desired clear color of the render target */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + FLinearColor RenderTargetClearColor; + + /** If true we will draw to the render target even without active stereo layers and skip the stereo tick*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + bool bDrawWithoutStereo; + + /** Rate (HTZ) we should draw the texture at */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WidgetSettings", meta = (ExposeOnSpawn = true)) + float DrawRate; + + // Counts how long until next draw + float DrawCounter; + + /** The Slate widget to be displayed by this component. Only one of either Widget or SlateWidget can be used */ + TSharedPtr<SWidget> SlateWidget; + + class FWidgetRenderer* WidgetRenderer; + + /** The render target being display */ + UPROPERTY(BlueprintReadOnly, Transient, DuplicateTransient, Category = "WidgetSettings") + TObjectPtr<UTextureRenderTarget2D> RenderTarget; + + /** The slate window that contains the user widget content */ + TSharedPtr<class SVirtualWindow> SlateWindow; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void DestroyComponent(bool bPromoteChildren/*= false*/) override; + + UFUNCTION(BlueprintCallable, Category = "WidgetSettings") + void SetWidgetAndInit(TSubclassOf<UUserWidget> NewWidgetClass); + + void OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld); + void InitWidget(); + void RenderWidget(float DeltaTime); + void ReleaseResources(); +}; + + +/** +* A widget component that displays the widget in a stereo layer instead of in worldspace. +* Currently this class uses a custom postion instead of the engines WorldLocked for stereo layers +* This is because world locked stereo layers don't account for player movement currently. +*/ +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = (VRExpansionPlugin)) +class VREXPANSIONPLUGIN_API UVRStereoWidgetComponent : public UWidgetComponent +{ + GENERATED_BODY() + +public: + UVRStereoWidgetComponent(const FObjectInitializer& ObjectInitializer); + + friend class FStereoLayerComponentVisualizer; + + ~UVRStereoWidgetComponent(); + + + /** Specifies which shape of layer it is. Note that some shapes will be supported only on certain platforms! **/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, NoClear, Instanced, Category = "StereoLayer", DisplayName = "Stereo Layer Shape") + TObjectPtr<UStereoLayerShape> Shape; + + void BeginDestroy() override; + void OnUnregister() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + virtual void DrawWidgetToRenderTarget(float DeltaTime) override; + virtual TStructOnScope<FActorComponentInstanceData> GetComponentInstanceData() const override; + void ApplyVRComponentInstanceData(class FVRStereoWidgetComponentInstanceData* WidgetInstanceData); + + virtual void UpdateRenderTarget(FIntPoint DesiredRenderTargetSize) override; + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + + + // If true forces the widget to render both stereo and world widgets + // Overriden by the console command vr.ForceNoStereoWithVRWidgets if it is set to 1 + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bRenderBothStereoAndWorld; + + /** Forces the widget to skip stereo regardless of all other settings (localized version of vr.ForceNoStereoWithVRWidgets)*/ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bDrawWithoutStereo; + + // If true, use Epics world locked stereo implementation instead of my own temp solution + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bUseEpicsWorldLockedStereo; + + // If true, will cache and delay the transform adjustment for one frame in order to sync with the game thread better + // Not for use with late updated parents. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bDelayForRenderThread; + + // If true will not render or update until false + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + bool bIsSleeping; + + /** + * Change the layer's render priority, higher priorities render on top of lower priorities + * @param InPriority: Priority value + */ + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void SetPriority(int32 InPriority); + + // @return the render priority + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + int32 GetPriority() const { return Priority; } + + /** True if the stereo layer needs to support depth intersections with the scene geometry, if available on the platform */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + uint32 bSupportsDepth : 1; + + /** True if the texture should not use its own alpha channel (1.0 will be substituted) */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + uint32 bNoAlphaChannel : 1; + + /** True if the quad should internally set it's Y value based on the set texture's dimensions */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StereoLayer") + uint32 bQuadPreserveTextureRatio : 1; + +protected: + /** Texture displayed on the stereo layer (is stereocopic textures are supported on the platfrom and more than one texture is provided, this will be the right eye) **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "StereoLayer") + // class UTexture* Texture; + + // Forget left texture implementation + /** Texture displayed on the stereo layer for left eye, if stereoscopic textures are supported on the platform **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "StereoLayer | Cubemap Overlay Properties") + // class UTexture* LeftTexture; + +public: + /** Size of the rendered stereo layer quad **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Quad Overlay Properties") + // FVector2D StereoLayerQuadSize; + + /** UV coordinates mapped to the quad face **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Quad Overlay Properties") + FBox2D UVRect; + + /** Radial size of the rendered stereo layer cylinder **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Cylinder Overlay Properties") + // float CylinderRadius; + + /** Arc angle for the stereo layer cylinder **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Cylinder Overlay Properties") + //float CylinderOverlayArc; + + /** Height of the stereo layer cylinder **/ + ///UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer | Cylinder Overlay Properties") + // int CylinderHeight; + + /** Specifies how and where the quad is rendered to the screen **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer") + //TEnumAsByte<enum EStereoLayerType> StereoLayerType; + + // Forcing quad layer so that it works with the widget better + /** Specifies which type of layer it is. Note that some shapes will be supported only on certain platforms! **/ + //UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer") + // TEnumAsByte<enum EStereoLayerShape> StereoLayerShape; + + /** Render priority among all stereo layers, higher priority render on top of lower priority **/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, export, Category = "StereoLayer") + int32 Priority; + + bool bShouldCreateProxy; + +private: + /** Dirty state determines whether the stereo layer needs updating **/ + bool bIsDirty; + bool bDirtyRenderTarget; + + /** Texture needs to be marked for update **/ + bool bTextureNeedsUpdate; + + /** IStereoLayer id, 0 is unassigned **/ + uint32 LayerId; + + /** Last transform is cached to determine if the new frames transform has changed **/ + FTransform LastTransform; + + /** Last frames visiblity state **/ + bool bLastVisible; + +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRTrackedParentInterface.h b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRTrackedParentInterface.h new file mode 100644 index 0000000..771ef63 --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/Public/VRTrackedParentInterface.h @@ -0,0 +1,101 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once +#include "CoreMinimal.h" +#include "VRBPDatatypes.h" +#include "UObject/ObjectMacros.h" +#include "UObject/ScriptMacros.h" +#include "UObject/Interface.h" + +#include "VRTrackedParentInterface.generated.h" + + +UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) +class UVRTrackedParentInterface: public UInterface +{ + GENERATED_UINTERFACE_BODY() +}; + + +class VREXPANSIONPLUGIN_API IVRTrackedParentInterface +{ + GENERATED_IINTERFACE_BODY() + +public: + + // Set a tracked parent + UFUNCTION(BlueprintCallable, Category = "VRTrackedParentInterface") + virtual void SetTrackedParent(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode) + {} + + static void Default_SetTrackedParent_Impl(UPrimitiveComponent * NewParentComponent, float WaistRadius, EBPVRWaistTrackingMode WaistTrackingMode, FBPVRWaistTracking_Info & OptionalWaistTrackingParent, USceneComponent * Self) + { + // If had a different original tracked parent + // Moved this to first thing so the pre-res is removed prior to erroring out and clearing this + if (OptionalWaistTrackingParent.IsValid()) + { + // Remove the tick Prerequisite + Self->RemoveTickPrerequisiteComponent(OptionalWaistTrackingParent.TrackedDevice); + } + + if (!NewParentComponent || !Self) + { + OptionalWaistTrackingParent.Clear(); + return; + } + + // Make other component tick first if possible, waste of time if in wrong tick group + if (NewParentComponent->PrimaryComponentTick.TickGroup == Self->PrimaryComponentTick.TickGroup) + { + // Make sure the other component isn't depending on this one + NewParentComponent->RemoveTickPrerequisiteComponent(Self); + + // Add a tick pre-res for ourselves so that we tick after our tracked parent. + Self->AddTickPrerequisiteComponent(NewParentComponent); + } + + OptionalWaistTrackingParent.TrackedDevice = NewParentComponent; + OptionalWaistTrackingParent.RestingRotation = NewParentComponent->GetRelativeRotation(); + OptionalWaistTrackingParent.RestingRotation.Yaw = 0.0f; + + OptionalWaistTrackingParent.TrackingMode = WaistTrackingMode; + OptionalWaistTrackingParent.WaistRadius = WaistRadius; + } + + // Returns local transform of the parent relative attachment + static FTransform Default_GetWaistOrientationAndPosition(FBPVRWaistTracking_Info & WaistTrackingInfo) + { + if (!WaistTrackingInfo.IsValid()) + return FTransform::Identity; + + FTransform DeviceTransform = WaistTrackingInfo.TrackedDevice->GetRelativeTransform(); + + // Rewind by the initial rotation when the new parent was set, this should be where the tracker rests on the person + DeviceTransform.ConcatenateRotation(WaistTrackingInfo.RestingRotation.Quaternion().Inverse()); + DeviceTransform.SetScale3D(FVector(1, 1, 1)); + + // Don't bother if not set + if (WaistTrackingInfo.WaistRadius > 0.0f) + { + DeviceTransform.AddToTranslation(DeviceTransform.GetRotation().RotateVector(FVector(-WaistTrackingInfo.WaistRadius, 0, 0))); + } + + + // This changes the forward vector to be correct + // I could pre do it by changed the yaw in resting mode to these values, but that had its own problems + // If given an initial forward vector that it should align to I wouldn't have to do this and could auto calculate it. + // But without that I am limited to this. + + // #TODO: add optional ForwardVector to initial setup function that auto calculates offset so that the user can pass in HMD forward or something for calibration X+ + // Also would be better overall because slightly offset from right angles in yaw wouldn't matter anymore, it would adjust for it. + switch (WaistTrackingInfo.TrackingMode) + { + case EBPVRWaistTrackingMode::VRWaist_Tracked_Front: DeviceTransform.ConcatenateRotation(FRotator(0, 0, 0).Quaternion()); break; + case EBPVRWaistTrackingMode::VRWaist_Tracked_Rear: DeviceTransform.ConcatenateRotation(FRotator(0, -180, 0).Quaternion()); break; + case EBPVRWaistTrackingMode::VRWaist_Tracked_Left: DeviceTransform.ConcatenateRotation(FRotator(0, 90, 0).Quaternion()); break; + case EBPVRWaistTrackingMode::VRWaist_Tracked_Right: DeviceTransform.ConcatenateRotation(FRotator(0, -90, 0).Quaternion()); break; + } + + return DeviceTransform; + } +}; \ No newline at end of file diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/VRExpansionPlugin.Build.cs b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/VRExpansionPlugin.Build.cs new file mode 100644 index 0000000..ed70ceb --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/Source/VRExpansionPlugin/VRExpansionPlugin.Build.cs @@ -0,0 +1,144 @@ +// Some copyright should be here... +using System.IO; +using UnrealBuildTool; + +public class VRExpansionPlugin : ModuleRules +{ + private string PluginsPath + { + get { return Path.GetFullPath(Target.RelativeEnginePath) + "Plugins/Runtime/"; } + } + + public VRExpansionPlugin(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + //bEnforceIWYU = true; + + PublicDefinitions.Add("WITH_VR_EXPANSION=1"); + + // To detect VR Preview, not built out in packaged builds + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.AddRange( + new string[] { + "UnrealEd" + } + ); + } + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "Settings" + } + ); + + PublicIncludePaths.AddRange( + new string[] { + //"VRExpansionPlugin/Public", + //"VRExpansionPlugin/Public/SimpleChar", + //"HeadMountedDisplay/Public", + //"Runtime/Engine/Private/PhysicsEngine" + + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + //"VRExpansionPlugin/Private", + //"VRExpansionPlugin/Private/SimpleChar", + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "NetCore", + "CoreUObject", + "Engine", + // "InputCore", + "PhysicsCore", + //"FLEX", remove comment if building in the NVIDIA flex branch - NOTE when put in place FLEX only listed win32 and win64 at compatible platforms + "HeadMountedDisplay", + // "RHI", + //"RenderCore", + //"ShaderCore", + //"NetworkReplayStreaming", + //"AIModule", + "UMG", + "NavigationSystem", + "AIModule", + "AnimGraphRuntime" + + //"Renderer", + //"UtilityShaders" + }); + + //if(Target.bUseChaos) + // { + PublicDependencyModuleNames.Add("Chaos"); + PublicDependencyModuleNames.Add("ChaosVehicles"); + //} + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + // "Core", + // "CoreUObject", + //"Engine", + "InputCore", + //"FLEX", remove comment if building in the NVIDIA flex branch - NOTE when put in place FLEX only listed win32 and win64 at compatible platforms + //"HeadMountedDisplay", + "RHI", + "ApplicationCore", + "RenderCore", + // "ShaderCore", + "NetworkReplayStreaming", + "AIModule", + "UMG", + "GameplayTags" + //"Renderer", + // "UtilityShaders" + }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + //"CoreUObject", + //"Engine", + "Slate", + "SlateCore" + + // ... add private dependencies that you statically link with here ... + } + ); + + // Don't load APEX on incompatible platforms + /* if ( + Target.Platform != UnrealTargetPlatform.IOS && + Target.Platform != UnrealTargetPlatform.TVOS && + Target.Platform != UnrealTargetPlatform.Android && + Target.Platform != UnrealTargetPlatform.HTML5) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "APEX" + }); + }*/ + + // Allow gameplay debugger on editor builds + if (Target.bBuildDeveloperTools || (Target.Configuration != UnrealTargetConfiguration.Shipping && Target.Configuration != UnrealTargetConfiguration.Test)) + { + PrivateDependencyModuleNames.Add("GameplayDebugger"); + PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=1"); // Already in AI Module, but gameplay abilities and other modules also duplicate the definition + } + else + { + PublicDefinitions.Add("WITH_GAMEPLAY_DEBUGGER=0"); + } + } +} diff --git a/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/VRExpansionPlugin.uplugin b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/VRExpansionPlugin.uplugin new file mode 100644 index 0000000..d4cf16d --- /dev/null +++ b/ExcavatorSimulator/Plugins/VRExpansionPlugin/VRExpansionPlugin/VRExpansionPlugin.uplugin @@ -0,0 +1,41 @@ +{ + "FileVersion": 3, + "Version": 5.0, + "VersionName": "5.0", + "FriendlyName": "VRExpansionPlugin", + "Description": "Adds several new VR features & components to UE4", + "Category": "VRExpansion", + "CreatedBy": "Joshua (MordenTral) Statzer", + "CreatedByURL": "http://www.vreue4.com", + "DocsURL": "http://www.vreue4.com", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": false, + "IsBetaVersion": false, + "Installed": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android", + "HoloLens" + ], + "Modules": [ + { + "Name": "VRExpansionPlugin", + "Type": "RunTime", + "LoadingPhase": "Default" + }, + { + "Name": "VRExpansionEditor", + "Type": "UnCookedOnly", + "LoadingPhase": "PostEngineInit" + } + ], + "Plugins": [ + { + "Name": "ChaosVehiclesPlugin", + "Enabled": true + } + ] +} \ No newline at end of file -- GitLab From 9bdbb36ed52a87b18d93f903e90ddd3eead97f4c Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Fri, 18 Nov 2022 15:40:33 +0200 Subject: [PATCH 032/121] modified FirstLevel --- ExcavatorSimulator/Content/Blueprints/BP_rock.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/FirstLevel.umap | 2 +- .../Content/Sound/Assets/excavator_dynamic_engine-cue.uasset | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index 1348771..4fa13b4 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c301795eb4747bfefa0f63c4df9ef5894d9fc0b0c561d763e910624c4b79b54f -size 119340 +oid sha256:0f1275ef7cc0ee74638acbe3520f87222390318ace4affe6336c34f5a30f5de1 +size 122473 diff --git a/ExcavatorSimulator/Content/Levels/FirstLevel.umap b/ExcavatorSimulator/Content/Levels/FirstLevel.umap index ae97114..4ca68bb 100644 --- a/ExcavatorSimulator/Content/Levels/FirstLevel.umap +++ b/ExcavatorSimulator/Content/Levels/FirstLevel.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5375d84990ec8aee4a3b11a6147144a8471b6ccf2a32b2bff4c0923cf3902c0 +oid sha256:59834d45e7755e01ddc037e5a2591d5b04c9cb3efc29bf9bca23886e34e50b1d size 2672536 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset new file mode 100644 index 0000000..c1bcc63 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1aa157b5a796f00ec3956f5020d4feb3b7ac592bb76d5e4cfe2367471aed5627 +size 12997 -- GitLab From c1a4544f40fdc86b446e58417004ed5ef3db1167 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 18 Nov 2022 15:50:36 +0200 Subject: [PATCH 033/121] =?UTF-8?q?siin=C3=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 33fd6bd..686420f 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0daddead4a99bec0ce0607db86f329ee9a99334bd78c29c2a14a748247c90418 -size 1143619 +oid sha256:77dcd1f7f727a5800ce1454c5c1786fa3b2a7d93468f852adfd78dd69044715a +size 1139661 -- GitLab From c1650c9b40054d1f350fa0936516a862916a5ae5 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 18 Nov 2022 15:50:53 +0200 Subject: [PATCH 034/121] Modified CheckCollisionComponent - added better checking --- .../CheckCollisionComponent.cpp | 37 ++++++++++++++++--- .../CheckCollisionComponent.h | 8 +++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index db678b2..e5530a1 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -1,6 +1,7 @@ // Fill out your copyright notice in the Description page of Project Settings. + #include "CheckCollisionComponent.h" #include "ExcavatorCharacter.h" #include "Engine/World.h" @@ -11,8 +12,24 @@ UCheckCollisionComponent::UCheckCollisionComponent() // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; + + CollisionMeshBody = CreateDefaultSubobject<UBoxComponent>(FName("Body Collision")); + FVector boxSize(380.0f, 200.0f, 100.0f); + CollisionMeshBody->SetBoxExtent(boxSize); + CollisionMeshBody->bHiddenInGame = false; + CollisionMeshBody->CanCharacterStepUpOn = ECB_No; + CollisionMeshBody->BodyInstance.SetObjectType(ECC_Pawn); + CollisionMeshBody->BodyInstance.SetCollisionProfileName("Pawn"); + + CollisionMeshTrack = CreateDefaultSubobject<UBoxComponent>(FName("Track Collision")); + boxSize = FVector(320.0f, 250.0f, 90.0f); + CollisionMeshTrack->SetBoxExtent(boxSize); + CollisionMeshTrack->bHiddenInGame = false; + CollisionMeshTrack->CanCharacterStepUpOn = ECB_No; + CollisionMeshTrack->BodyInstance.SetObjectType(ECC_Pawn); + CollisionMeshTrack->BodyInstance.SetCollisionProfileName("Pawn"); - BucketCollidingFromDown = false; + BucketCollidingFromDown = false; BucketCollidingFromFront = false; BucketCollidingFromLeft = false; BucketCollidingFromRight = false; @@ -33,6 +50,8 @@ void UCheckCollisionComponent::BeginPlay() void UCheckCollisionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + CollisionCheck(); + CheckCollisionsBody(); } // oikealle rot = -1 @@ -40,15 +59,15 @@ void UCheckCollisionComponent::TickComponent(float DeltaTime, ELevelTick TickTyp void UCheckCollisionComponent::IsCollidingLR(EOutputs& OutputPins, float rotationDirection) { // if we are colliding from left and trying to rotate left we cant - if (rotationDirection == 1.0f && BucketCollidingFromLeft) + if (rotationDirection == -1.0f && BucketCollidingFromLeft) { - OutputPins = EOutputs::CanRotateRight; + OutputPins = EOutputs::Colliding; return; } // if we are colliding from right and trying to rotate right we cant - else if (rotationDirection == -1.0f && BucketCollidingFromRight) + else if (rotationDirection == 1.0f && BucketCollidingFromRight) { - OutputPins = EOutputs::CanRotateLeft; + OutputPins = EOutputs::Colliding; return; } // if we are not colliding form eather direction rotate freely @@ -58,6 +77,13 @@ void UCheckCollisionComponent::IsCollidingLR(EOutputs& OutputPins, float rotatio return; } +void UCheckCollisionComponent::CheckCollisionsBody() +{ + CollisionMeshBody->SetWorldLocationAndRotation(FVector(mesh->GetSocketLocation("bone_bodySocketMiddle").X, mesh->GetSocketLocation("bone_bodySocketMiddle").Y, mesh->GetSocketLocation("bone_bodySocketMiddle").Z + 120.0), FQuat({ 0.0, mesh->GetSocketRotation("bone_bodySocketMiddle").Yaw, 0.0 })); + CollisionMeshTrack->SetWorldLocationAndRotation(mesh->GetSocketLocation("root_bone_tracksSocket"), FQuat({ 0.0, mesh->GetSocketRotation("root_bone_tracksSocket").Yaw, 0.0 })); + //UE_LOG(LogTemp, Warning, TEXT("Body loc is: x(%f), y(%f), z(%f) "), mesh->GetSocketLocation("bone_bodySocketMiddle").X, mesh->GetSocketLocation("bone_bodySocketMiddle").Y, mesh->GetSocketLocation("bone_bodySocketMiddle").Z); +} + void UCheckCollisionComponent::IsCollidingFB(EOutputs& OutputPins, bool movingForward = false) { if (movingForward) @@ -79,6 +105,7 @@ void UCheckCollisionComponent::CollisionCheck() FCollisionQueryParams collisionParams; collisionParams.AddIgnoredActor(character); FVector socketLocation = mesh->GetSocketLocation("socket_bucketMiddle"); + //UE_LOG(LogTemp, Warning, TEXT("Socket: right is: x(%f), y(%f), z(%f) vs x(%f), y(%f), z(%f) mesh right:"), testLoc.RightVector.X, testLoc.RightVector.Y, testLoc.RightVector.Z, mesh->GetRightVector().X, mesh->GetRightVector().Y, mesh->GetRightVector().Z); // Line trace down float lengthDown = -160.0f; diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index b86de55..7b3e91f 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "Components/ActorComponent.h" +#include <Components/BoxComponent.h> #include "CheckCollisionComponent.generated.h" UENUM(BlueprintType) @@ -11,8 +12,6 @@ enum class EOutputs : uint8 { Colliding, NotColliding, - CanRotateRight, - CanRotateLeft }; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) @@ -36,6 +35,8 @@ public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + void CheckCollisionsBody(); + protected: // Called when the game starts virtual void BeginPlay() override; @@ -55,4 +56,7 @@ private: USkeletalMeshComponent* mesh; ACharacter* character; + + UBoxComponent* CollisionMeshBody; + UBoxComponent* CollisionMeshTrack; }; -- GitLab From c97d26a17192f3e78a6737ae87729af69b6670bc Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 18 Nov 2022 15:51:24 +0200 Subject: [PATCH 035/121] Modified ExcavatorCharacter --- .../Source/ExcavatorSimulator/ExcavatorCharacter.cpp | 2 -- .../Source/ExcavatorSimulator/ExcavatorCharacter.h | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 5f0b246..3d1c0df 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -8,9 +8,7 @@ AExcavatorCharacter::AExcavatorCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; - //collisionComponent = CreateDefaultSubobject<UCollisionCheckComponent>(TEXT("Collision Component")); //collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); - //collisionComponent->RegisterComponent(); } // Called every frame diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 3af27ca..cfd64f2 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -6,7 +6,6 @@ #include "GameFramework/Character.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/InputSettings.h" -//#include "CollisionCheckComponent.h" #include "CheckCollisionComponent.h" #include "ExcavatorCharacter.generated.h" @@ -19,9 +18,7 @@ public: /** Sets default values for this character's properties. */ AExcavatorCharacter(); - /*UPROPERTY(VisibleAnywhere, BlueprintReadOnly) - class UCollisionCheckComponent* collisionComponent;*/ - + //UPROPERTY(VisibleAnywhere, BlueprintReadOnly) //class UCheckCollisionComponent* collisionComponent; /** Called every frame. */ -- GitLab From 9df3441e327310e8db63ed56a554ef5cf60434ab Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Fri, 18 Nov 2022 15:54:15 +0200 Subject: [PATCH 036/121] Prototype digging --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 686420f..4d59e6a 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77dcd1f7f727a5800ce1454c5c1786fa3b2a7d93468f852adfd78dd69044715a -size 1139661 +oid sha256:ae8d965ccb58c47bdbd5fc0ae5c8eb76acc76e01764db08d26cd001e9fc05813 +size 1135921 diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap index e53ff32..5f6c09c 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ae3b6f1b7728efd1683d309b6f3086b0857d1ac5a09eec038aa1c74d58f58aa -size 32376 +oid sha256:1fe4052b0eafcacb75a2f63de006d83c6c1c5fa082f328cea26534cd8d41e195 +size 33247 -- GitLab From 64fdc34cb8982daafdc68ae2d315239b96767293 Mon Sep 17 00:00:00 2001 From: make <make.rangepure@gmail.com> Date: Sun, 20 Nov 2022 11:25:18 +0200 Subject: [PATCH 037/121] Minor changes, added particle beam --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- .../Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 4d59e6a..b08fc37 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae8d965ccb58c47bdbd5fc0ae5c8eb76acc76e01764db08d26cd001e9fc05813 -size 1135921 +oid sha256:40cb3e04c4dcbef9fdcbcdc6c66d1060683fc66369f0fac02685d3c2e0087a3c +size 1135071 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index b69323c..a7af7ec 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99cb344c28fd0b9670a9719c2eee8f0d9c18e15c4a681f372f14d63371df3eaa -size 167946 +oid sha256:eb8e80b6fd50631139916181bb033378f9314bc449e83c847f1335ff9b0a811f +size 165665 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 63465c6..bc69c35 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5f1d1baf3dfdf3ab91b1ab796919be3abcefb0238e14166e92c74c6b5913196 -size 848576 +oid sha256:e96c82a0c6a7a355df82740cde1d40aea33cd2b35002a769e626e8b217709861 +size 911207 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset index 45ff7a0..c8cc8e5 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09cffddc1b29231cd6951656eaea99c24ef955c12be39fd520b394347f22f4a8 -size 11539 +oid sha256:198fa6ffbab4f69852de67b7637be66d762958d29a16c4ec06f838ff2a46ca81 +size 11543 -- GitLab From 0101bcde5f307368677bf9e8ea53394a8d879846 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Sun, 20 Nov 2022 13:51:24 +0200 Subject: [PATCH 038/121] Add Materials --- ExcavatorSimulator/Content/Materials/M_Forward.uasset | 3 +++ ExcavatorSimulator/Content/Materials/MovingMat.uasset | 3 +++ .../Content/Materials/TrackTestMats/M_Backward.uasset | 3 +++ .../Content/Materials/TrackTestMats/M_Forward.uasset | 3 +++ .../Content/Materials/TrackTestMats/M_Static.uasset | 3 +++ .../Content/Materials/TrackTestMats/snow_04_diff_4k.uasset | 3 +++ .../Materials/TrackTestMats/tank_track_2s3_BaseColor.uasset | 3 +++ .../Content/Materials/TrackTestMats/tank_track_2s3_Mat.uasset | 3 +++ .../Materials/TrackTestMats/tank_track_2s3_Normal.uasset | 3 +++ .../tank_track_2s3_OcclusionRoughnessMetallic.uasset | 3 +++ 10 files changed, 30 insertions(+) create mode 100644 ExcavatorSimulator/Content/Materials/M_Forward.uasset create mode 100644 ExcavatorSimulator/Content/Materials/MovingMat.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/M_Backward.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/M_Forward.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/M_Static.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/snow_04_diff_4k.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_BaseColor.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Mat.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Normal.uasset create mode 100644 ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_OcclusionRoughnessMetallic.uasset diff --git a/ExcavatorSimulator/Content/Materials/M_Forward.uasset b/ExcavatorSimulator/Content/Materials/M_Forward.uasset new file mode 100644 index 0000000..960b78c --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/M_Forward.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fae9e4d2ad6a44bd86b842d47755ac939d350f75926e79f770761dd24df93f00 +size 1353 diff --git a/ExcavatorSimulator/Content/Materials/MovingMat.uasset b/ExcavatorSimulator/Content/Materials/MovingMat.uasset new file mode 100644 index 0000000..b3d7da2 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/MovingMat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e198a8fe052d2bfc25a038a097c189a973189440434fdee67fad2cfc5116816 +size 1343 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Backward.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Backward.uasset new file mode 100644 index 0000000..d4c7f9e --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Backward.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d748ccf15987061cd5146a302b741b294e452cdd4327e18de3a6d9c2bf93c18a +size 14220 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Forward.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Forward.uasset new file mode 100644 index 0000000..1e4078d --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Forward.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfbee77d6d0c253dc5d5588909231a2076591811bf1369dffb56e2943913bf84 +size 15046 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Static.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Static.uasset new file mode 100644 index 0000000..ffd0a8a --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/M_Static.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd5a57a87d598c94be1bc4d11418fcfe814322bca9735daecf1f1e2254bd6c63 +size 12819 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/snow_04_diff_4k.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/snow_04_diff_4k.uasset new file mode 100644 index 0000000..5436f22 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/snow_04_diff_4k.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10da272a8000b8b4c6e6e3740f250dfe0c80e44dd2517d519d1431c78a9a5e45 +size 7197887 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_BaseColor.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_BaseColor.uasset new file mode 100644 index 0000000..525edc2 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_BaseColor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:524a35c78edc5c3aeeeafd2a341ee281dbc9dbbc437f80b9850d1c9e6da184af +size 539330 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Mat.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Mat.uasset new file mode 100644 index 0000000..5a1cf46 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Mat.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d7de1e99a52591720eb5566efab6e41e110e1f16aec7fec8a6dd31b55358e53 +size 111062 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Normal.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Normal.uasset new file mode 100644 index 0000000..c5ddbf5 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_Normal.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6096deeafe5746cd1e4928cb117169584b7ac311484b37803d8a162187c65768 +size 204288 diff --git a/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_OcclusionRoughnessMetallic.uasset b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_OcclusionRoughnessMetallic.uasset new file mode 100644 index 0000000..d5c074b --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/TrackTestMats/tank_track_2s3_OcclusionRoughnessMetallic.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff3ea2efaf4e818fc3f5a78386695fd22ce19dc1ab535d2d28e4e6a51890c2c0 +size 634483 -- GitLab From 8ae84027c32e929a25c275110dde8f42b11d63e2 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Sun, 20 Nov 2022 13:52:08 +0200 Subject: [PATCH 039/121] Added Skeletos --- .../ExcavatorParts/Skeletons/Skeleton_Excavator_finale.uasset | 3 +++ .../Excavator/ExcavatorParts/Skeletons/Skeleton_Track.uasset | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator_finale.uasset create mode 100644 ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Track.uasset diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator_finale.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator_finale.uasset new file mode 100644 index 0000000..f884d7f --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator_finale.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07ed42af400cd24064a88c8c386772260e2de916a48327e708801e98e309f37c +size 11142 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Track.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Track.uasset new file mode 100644 index 0000000..3083c44 --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Track.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fd8a1aed9f8760c5c6cecdd523bfb43c072c4a62bbaeddcbd0ab333070a1318 +size 15229 -- GitLab From e3c40cefcc2efbc2febb0764ddaccc1df99e9fd5 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Sun, 20 Nov 2022 13:52:50 +0200 Subject: [PATCH 040/121] Add animations --- ExcavatorSimulator/Content/Cinematics/AM_LeftTrack.uasset | 3 +++ ExcavatorSimulator/Content/Cinematics/AM_RightTrack.uasset | 3 +++ ExcavatorSimulator/Content/Cinematics/Anim_Track.uasset | 3 +++ ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 ExcavatorSimulator/Content/Cinematics/AM_LeftTrack.uasset create mode 100644 ExcavatorSimulator/Content/Cinematics/AM_RightTrack.uasset create mode 100644 ExcavatorSimulator/Content/Cinematics/Anim_Track.uasset create mode 100644 ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset diff --git a/ExcavatorSimulator/Content/Cinematics/AM_LeftTrack.uasset b/ExcavatorSimulator/Content/Cinematics/AM_LeftTrack.uasset new file mode 100644 index 0000000..62b08ab --- /dev/null +++ b/ExcavatorSimulator/Content/Cinematics/AM_LeftTrack.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e770dcef4639576c4199612d8afae489516f470e3b1cf680b237a4271ed7945a +size 7505 diff --git a/ExcavatorSimulator/Content/Cinematics/AM_RightTrack.uasset b/ExcavatorSimulator/Content/Cinematics/AM_RightTrack.uasset new file mode 100644 index 0000000..2c4f76d --- /dev/null +++ b/ExcavatorSimulator/Content/Cinematics/AM_RightTrack.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5646c7e362bdd5332ec11a275999fc7b8fac7ea4270f5f5dc3bc5d620c15989d +size 7489 diff --git a/ExcavatorSimulator/Content/Cinematics/Anim_Track.uasset b/ExcavatorSimulator/Content/Cinematics/Anim_Track.uasset new file mode 100644 index 0000000..a738f72 --- /dev/null +++ b/ExcavatorSimulator/Content/Cinematics/Anim_Track.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03ea9b3e3a80c015d7bf017fd6be5d1054c408637fce1395184f82bbdaae9c46 +size 234309 diff --git a/ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset b/ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset new file mode 100644 index 0000000..cf01875 --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbba73a471cbee8f02ba28ac9154439fecf82cf66cc8685eaf466c61188bc633 +size 52448 -- GitLab From 4889bf6e914d251b386d573da2c7b2c79ac244e2 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Sun, 20 Nov 2022 13:53:20 +0200 Subject: [PATCH 041/121] Add SkelMesh_Track --- .../Excavator/ExcavatorParts/SkelMesh_Excavaror_finale.uasset | 3 +++ .../Excavator/ExcavatorParts/SkelMesh_Excavator.uasset | 4 ++-- .../Content/Excavator/ExcavatorParts/SkelMesh_Track.uasset | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavaror_finale.uasset create mode 100644 ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Track.uasset diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavaror_finale.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavaror_finale.uasset new file mode 100644 index 0000000..149285e --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavaror_finale.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e30ab228d172dca09a9ba4350d2048f27d80f434a76b8f28bba0a1d25f979678 +size 32151008 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset index 74f1bbb..46460e8 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0947484efdc0199797660958bd9dc6ee7b18bbe73d2f9d4b99f8721cb542678 -size 28924803 +oid sha256:9ead8d005e5e01f2fd943699ad06bce74bc7efe424773a9cfe16a40d0b5c23a1 +size 28925505 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Track.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Track.uasset new file mode 100644 index 0000000..7c2e9e5 --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Track.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:853a21cb9a5898adbf1584dcc5012a4ba09ddd2f262cd432fc2c4c3bd12a2b8c +size 169021 -- GitLab From a7e70cea3f40b79d391f910b9031a6e330193419 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Sun, 20 Nov 2022 13:54:12 +0200 Subject: [PATCH 042/121] Add track mesh --- ExcavatorSimulator/Content/Excavator/SM_Track.uasset | 3 +++ ExcavatorSimulator/Content/Objects/SM_Track.uasset | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 ExcavatorSimulator/Content/Excavator/SM_Track.uasset create mode 100644 ExcavatorSimulator/Content/Objects/SM_Track.uasset diff --git a/ExcavatorSimulator/Content/Excavator/SM_Track.uasset b/ExcavatorSimulator/Content/Excavator/SM_Track.uasset new file mode 100644 index 0000000..6c45db1 --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/SM_Track.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1791c737373f3ed08f062175a9b0d74a6274d4a969fd71afcdb0218539ddd969 +size 1318 diff --git a/ExcavatorSimulator/Content/Objects/SM_Track.uasset b/ExcavatorSimulator/Content/Objects/SM_Track.uasset new file mode 100644 index 0000000..68a8bb0 --- /dev/null +++ b/ExcavatorSimulator/Content/Objects/SM_Track.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23975282984537bdf6fa67043e18ac37a872ec7098edbc2a1af081dddf2e4df1 +size 28723 -- GitLab From 7276634411d44c8e43b786c10fccfca6a181b5c7 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Sun, 20 Nov 2022 13:54:41 +0200 Subject: [PATCH 043/121] Add new Excavator character --- ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset | 3 +++ .../ExcavatorParts/PhysicsAssets/PA_Excavator_finale.uasset | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset create mode 100644 ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator_finale.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset new file mode 100644 index 0000000..1fb7704 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:221d16cf1fe05d59d2f9d5e1de06d563f3c37a11a87dc08bbf4be2b00c155345 +size 53111 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator_finale.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator_finale.uasset new file mode 100644 index 0000000..76a1e84 --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/PhysicsAssets/PA_Excavator_finale.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6eb5681d4866ae48b3e74c5ec26f2e27c39fbd79e3ea5915f06f138f4c44dbe0 +size 14558 -- GitLab From 025719637387d104c8a76c7f03abbe1444df3991 Mon Sep 17 00:00:00 2001 From: Tero Koponen <terokoponen@kamk.fi> Date: Sun, 20 Nov 2022 13:55:12 +0200 Subject: [PATCH 044/121] modified TestLevel_Tero --- ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap index cbe335d..39ec19b 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d90649002af974dfc247a08a7ea395bce2ce40b7e599fbeb7c2538c22e58c40a -size 55030 +oid sha256:ce104e6b04c302001ad47e7f5c5ac3c95f83ae757c77c4736b9aaf2a0236d9bb +size 88486 -- GitLab From 00cdcd8fa82d12c208790529d2b76867f9cda62b Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Mon, 21 Nov 2022 10:38:39 +0200 Subject: [PATCH 045/121] Create ExcavatorAnim.h/cpp reparent AnimBP_Excavator --- .../Content/Excavator/AnimBP_Excavator.uasset | 4 ++-- .../ExcavatorSimulator/ExcavatorAnim.cpp | 5 +++++ .../Source/ExcavatorSimulator/ExcavatorAnim.h | 20 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h diff --git a/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset index 1d5e00e..e348086 100644 --- a/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:151fcb275f89eb43d263ea12b656afc688b1ee5bb679c8b1246bca636bb01d00 -size 142378 +oid sha256:43fec0f78a0f9aabd573b615b87668c1a7592a46f1d41ea0cdd4f4b56f3b483e +size 143254 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp new file mode 100644 index 0000000..b82e4f4 --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp @@ -0,0 +1,5 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "ExcavatorAnim.h" + diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h new file mode 100644 index 0000000..77bf61a --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h @@ -0,0 +1,20 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimInstance.h" +#include "ExcavatorAnim.generated.h" + +/** + * + */ +UCLASS() +class EXCAVATORSIMULATOR_API UExcavatorAnim : public UAnimInstance +{ + GENERATED_BODY() +public: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + FRotator Bodyrotation; +private: +}; -- GitLab From c0eb3a3e583d272546d11e4a727ddccf067c52e7 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Mon, 21 Nov 2022 11:38:58 +0200 Subject: [PATCH 046/121] WOO grab working now adjusting angles --- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset | 4 ++-- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- .../Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index 9858cf5..f1845d6 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2f48c4c78a244448a0f0bbac026eb2391b4081aeca09798bd9da775d8ab399c -size 26775 +oid sha256:f1cccd6013ce715bd4aaa5b19f76e29e35af6692e80f409ff292d5476d32fcb5 +size 107236 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset index 1fb7704..01c8921 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:221d16cf1fe05d59d2f9d5e1de06d563f3c37a11a87dc08bbf4be2b00c155345 -size 53111 +oid sha256:b6c38c128e2ae08a408244273d26289add9cb037963251e9726a2d6809e17b3f +size 54054 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index b08fc37..ba7d83a 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40cb3e04c4dcbef9fdcbcdc6c66d1060683fc66369f0fac02685d3c2e0087a3c -size 1135071 +oid sha256:65c17ae9d3972da52a236f0a05591a0d2dc2a38c7fc6653866fe7b77e2695330 +size 1098591 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index a7af7ec..ea8ad8a 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb8e80b6fd50631139916181bb033378f9314bc449e83c847f1335ff9b0a811f -size 165665 +oid sha256:4943b8b401e648807e067dbd460fe7d3390cb23d6669af335603443691770b7d +size 179197 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index bc69c35..8f2f70e 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e96c82a0c6a7a355df82740cde1d40aea33cd2b35002a769e626e8b217709861 -size 911207 +oid sha256:a92678c2a6121345e6f71ff915614a046618552095a08a7fc108039afb8e4e71 +size 1012397 diff --git a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset index c8cc8e5..fec9d02 100644 --- a/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset +++ b/ExcavatorSimulator/Content/Blueprints/VR/HandModel/QK_CustomHand_Skeleton.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:198fa6ffbab4f69852de67b7637be66d762958d29a16c4ec06f838ff2a46ca81 -size 11543 +oid sha256:3884b0c8f04b70e4a2c036d822c21d6cdfe6f9ea03c27d9294997f3f9c229008 +size 11539 -- GitLab From a35ea7ac64bd868f342368542d1e093e3427ab6a Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Mon, 21 Nov 2022 11:46:54 +0200 Subject: [PATCH 047/121] Add Key object and material --- .../Content/Materials/Instances/m_Key_Inst.uasset | 3 +++ ExcavatorSimulator/Content/Materials/Texture_Key.uasset | 3 +++ ExcavatorSimulator/Content/Materials/m_Key.uasset | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 ExcavatorSimulator/Content/Materials/Instances/m_Key_Inst.uasset create mode 100644 ExcavatorSimulator/Content/Materials/Texture_Key.uasset create mode 100644 ExcavatorSimulator/Content/Materials/m_Key.uasset diff --git a/ExcavatorSimulator/Content/Materials/Instances/m_Key_Inst.uasset b/ExcavatorSimulator/Content/Materials/Instances/m_Key_Inst.uasset new file mode 100644 index 0000000..cf1a2f3 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/Instances/m_Key_Inst.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab6fcd8246f1673f68c11bab2e3dba693487049c7c61cc20ba2c8c1f8e4f4e46 +size 10217 diff --git a/ExcavatorSimulator/Content/Materials/Texture_Key.uasset b/ExcavatorSimulator/Content/Materials/Texture_Key.uasset new file mode 100644 index 0000000..2e01324 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/Texture_Key.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7371c54c8466d5aab2992356f3fa8a763e61c4f46828c62155d7280bd239e59 +size 30123895 diff --git a/ExcavatorSimulator/Content/Materials/m_Key.uasset b/ExcavatorSimulator/Content/Materials/m_Key.uasset new file mode 100644 index 0000000..42be0eb --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/m_Key.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcffe56f887c481adf1e6af086d8bb3f62725dc88bff01ee5014a61cc4f93477 +size 14201 -- GitLab From ba7114542beee4c7008662a580b65b2c9af25b3c Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Mon, 21 Nov 2022 11:49:14 +0200 Subject: [PATCH 048/121] Changes to ExcavatorCharacter c++ --- .../Content/Levels/TestLevel_Tero.umap | 4 +-- .../Content/Objects/SM_ExcavatorKey.uasset | 3 ++ .../ExcavatorSimulator/ExcavatorCharacter.cpp | 33 +++++++++++++++++++ .../ExcavatorSimulator/ExcavatorCharacter.h | 31 ++++++++++++++--- 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 ExcavatorSimulator/Content/Objects/SM_ExcavatorKey.uasset diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap index 39ec19b..78af2ab 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce104e6b04c302001ad47e7f5c5ac3c95f83ae757c77c4736b9aaf2a0236d9bb -size 88486 +oid sha256:1078dde4aaeb6238e0d9a857d2440b77852f097b64fa23af3be479705073f19c +size 62462 diff --git a/ExcavatorSimulator/Content/Objects/SM_ExcavatorKey.uasset b/ExcavatorSimulator/Content/Objects/SM_ExcavatorKey.uasset new file mode 100644 index 0000000..ca5a7ff --- /dev/null +++ b/ExcavatorSimulator/Content/Objects/SM_ExcavatorKey.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6380d5b2715ccb677ca6fa43eb3a4cec5fbd91699c39b6f7a85bfc110db59dd5 +size 277847 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 3d1c0df..b650fba 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -2,6 +2,7 @@ #include "ExcavatorCharacter.h" +#include <Kismet/KismetMathLibrary.h> // Sets default values AExcavatorCharacter::AExcavatorCharacter() @@ -9,6 +10,10 @@ AExcavatorCharacter::AExcavatorCharacter() // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; //collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); + mesh = AExcavatorCharacter::GetMesh(); + AccelerationMultiplier = 1.1f; + BucketRotationMultiplier = 1.0f; + animInstance = mesh->GetAnimInstance(); } // Called every frame @@ -23,8 +28,36 @@ void AExcavatorCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInput Super::SetupPlayerInputComponent(PlayerInputComponent); } +void AExcavatorCharacter::MovementC(float Direction) +{ + //UE_LOG(LogTemp, Warning, TEXT("Direction: (%f)"), Direction); + AddMovementInput(mesh->GetForwardVector(), Direction * AccelerationMultiplier); +} + + +void AExcavatorCharacter::BucketRotationC(float RotationDirection) +{ + //UE_LOG(LogTemp, Warning, TEXT("BucketRotation Dir: (%f)"), RotationDirection); + //AddMovementInput(mesh->GetForwardVector(), Direction * AccelerationMultiplier); + + //AnimExcavatorREF->BucketRotation.Yaw + + //float temprRot = RotationDirection* BucketRotationMultiplier + AnimExcavatorREF->BucketRotation.Yaw; + float clampedVal = UKismetMathLibrary::FClamp(RotationDirection * BucketRotationMultiplier + AnimExcavatorREF->BucketRotation.Yaw, 0.1, 180); + AnimExcavatorREF.BucketRotation.Yaw = clampedVal; + if (clampedVal >= 110.0f) + { + DetachFromBucket(); + } +} + + // Called when the game starts or when spawned void AExcavatorCharacter::BeginPlay() { Super::BeginPlay(); } + +void AExcavatorCharacter::DetachFromBucket() +{ + +} diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index cfd64f2..c8b85ee 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -6,7 +6,7 @@ #include "GameFramework/Character.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/InputSettings.h" -#include "CheckCollisionComponent.h" +//#include "CheckCollisionComponent.h" #include "ExcavatorCharacter.generated.h" UCLASS() @@ -18,14 +18,17 @@ public: /** Sets default values for this character's properties. */ AExcavatorCharacter(); - //UPROPERTY(VisibleAnywhere, BlueprintReadOnly) - //class UCheckCollisionComponent* collisionComponent; - /** Called every frame. */ virtual void Tick(float DeltaTime) override; /** Called to bind functionality to input. */ virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + + UFUNCTION(BlueprintCallable) + void MovementC(float Direction); + + UFUNCTION(BlueprintCallable) + void BucketRotationC(float RotationDirection); protected: @@ -34,4 +37,24 @@ protected: private: UInputSettings* Inputsettings = UInputSettings::GetInputSettings(); + + USkeletalMeshComponent* mesh; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) + float AccelerationMultiplier; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) + float BucketRotationMultiplier; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) + float BucketRotationMultiplier; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) + UAnimInstance* animInstance; + + // this is null + UAnimInstance* AnimExcavatorREF; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Excavator REF's", meta = (AllowPrivateAccess = "true")) + AActor* RockREF; + + void DetachFromBucket(); }; -- GitLab From 6540f08dd097284007d990e5521a3d78956d3234 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 22 Nov 2022 08:52:14 +0200 Subject: [PATCH 049/121] Create ABP_Excavator Modify bp_excavator rotations --- .../Content/Blueprints/ABP_Excavator.uasset | 3 +++ .../Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- .../Source/ExcavatorSimulator/ExcavatorAnim.cpp | 10 ++++++++++ .../Source/ExcavatorSimulator/ExcavatorAnim.h | 15 +++++++++++++-- 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset new file mode 100644 index 0000000..b7b4253 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6532f35b7bc12467840df0284fe8446dccf2c2525fc03128c9ece8e3de42f598 +size 137195 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index ba7d83a..f677467 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65c17ae9d3972da52a236f0a05591a0d2dc2a38c7fc6653866fe7b77e2695330 -size 1098591 +oid sha256:67bee3b297ea9d22d10c359f43d50df15c9311f1783b19c5bd9c3bb0b7169134 +size 1085416 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp index b82e4f4..b1193aa 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp @@ -3,3 +3,13 @@ #include "ExcavatorAnim.h" +UExcavatorAnim::UExcavatorAnim(const FObjectInitializer& ObjecInitializer) + : Super(ObjecInitializer) +{ + +} + +FRotator UExcavatorAnim::GetBodyRotation() +{ + return BodyRotation; +} diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h index 77bf61a..6c776ba 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h @@ -14,7 +14,18 @@ class EXCAVATORSIMULATOR_API UExcavatorAnim : public UAnimInstance { GENERATED_BODY() public: + UExcavatorAnim(const FObjectInitializer& ObjecInitializer); + + FRotator GetBodyRotation(); + +private: + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + FRotator BodyRotation; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + FRotator BeamTopRotation; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + FRotator BeamBottomRotation; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) - FRotator Bodyrotation; -private: + FRotator BucketRotation; }; -- GitLab From b782a345b99f57014f6f6150e6fce28da8588bbd Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 22 Nov 2022 09:12:10 +0200 Subject: [PATCH 050/121] Add getters and setters --- .../ExcavatorSimulator/ExcavatorAnim.cpp | 35 +++++++++++++++++ .../Source/ExcavatorSimulator/ExcavatorAnim.h | 38 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp index b1193aa..cf4ef2a 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp @@ -13,3 +13,38 @@ FRotator UExcavatorAnim::GetBodyRotation() { return BodyRotation; } + +void UExcavatorAnim::SetBodyRotation(FRotator rotator) +{ + BodyRotation = rotator; +} + +FRotator UExcavatorAnim::GetBeamTopRotation() +{ + return BeamTopRotation; +} + +void UExcavatorAnim::SetBeamTopRotation(FRotator rotator) +{ + BeamTopRotation = rotator; +} + +FRotator UExcavatorAnim::GetBeamBottomRotation() +{ + return BeamBottomRotation; +} + +void UExcavatorAnim::SetBeamBottomRotation(FRotator rotator) +{ + BeamBottomRotation = rotator; +} + +FRotator UExcavatorAnim::GetBucketRotation() +{ + return BucketRotation; +} + +void UExcavatorAnim::SetBucketRotation(FRotator rotator) +{ + BucketRotation = rotator; +} diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h index 6c776ba..ebeb78f 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h @@ -14,18 +14,56 @@ class EXCAVATORSIMULATOR_API UExcavatorAnim : public UAnimInstance { GENERATED_BODY() public: + + /** Constructor. */ UExcavatorAnim(const FObjectInitializer& ObjecInitializer); + /** Getter for BodyRotation. */ FRotator GetBodyRotation(); + + /** Setter for BodyRotation + * @Param rotator + */ + void SetBodyRotation(FRotator rotator); + + /** Getter for BeamTopRotation. */ + FRotator GetBeamTopRotation(); + + /** Setter for BeamTopRotation + * @Param rotator + */ + void SetBeamTopRotation(FRotator rotator); + + /** Getter for BeamBottomRotation. */ + FRotator GetBeamBottomRotation(); + /** Setter for BeamBottomRotation + * @Param rotator + */ + void SetBeamBottomRotation(FRotator rotator); + + /** Getter for BucketRotation. */ + FRotator GetBucketRotation(); + + /** Setter for BucketRotation + * @Param rotator + */ + void SetBucketRotation(FRotator rotator); private: + /***/ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BodyRotation; + + /***/ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BeamTopRotation; + + /***/ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BeamBottomRotation; + + /***/ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BucketRotation; }; -- GitLab From 1440b8a34358315126f8b494e8c4bd4893a4b855 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 22 Nov 2022 09:29:24 +0200 Subject: [PATCH 051/121] Add more comments. --- .../Source/ExcavatorSimulator/ExcavatorAnim.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h index ebeb78f..b29861e 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h @@ -22,7 +22,7 @@ public: FRotator GetBodyRotation(); /** Setter for BodyRotation - * @Param rotator + * @Param rotator sets new rotation to BodyRotation */ void SetBodyRotation(FRotator rotator); @@ -30,14 +30,15 @@ public: FRotator GetBeamTopRotation(); /** Setter for BeamTopRotation - * @Param rotator + * @Param rotator sets new rotation to BeamTopRotation */ void SetBeamTopRotation(FRotator rotator); /** Getter for BeamBottomRotation. */ FRotator GetBeamBottomRotation(); + /** Setter for BeamBottomRotation - * @Param rotator + * @Param rotator sets new rotation to BeamBottomRotator */ void SetBeamBottomRotation(FRotator rotator); @@ -45,25 +46,25 @@ public: FRotator GetBucketRotation(); /** Setter for BucketRotation - * @Param rotator + * @Param rotator sets new rotation to BucketRotation */ void SetBucketRotation(FRotator rotator); private: - /***/ + /** Rotator to Excavators body. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BodyRotation; - /***/ + /** Rotator to BeamTop. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BeamTopRotation; - /***/ + /** Rotator to BeamBottom. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BeamBottomRotation; - /***/ + /** Rotator to Bucket. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) FRotator BucketRotation; }; -- GitLab From 0d7e83264c88eb4f88dd481366dc39f0374f58b9 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 09:48:29 +0200 Subject: [PATCH 052/121] Modified Bucket rot to c++ --- .../ExcavatorSimulator/ExcavatorAnim.cpp | 5 ++++- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 20 ++++++++++++------- .../ExcavatorSimulator/ExcavatorCharacter.h | 18 ++++++++++------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp index cf4ef2a..557037a 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp @@ -6,7 +6,10 @@ UExcavatorAnim::UExcavatorAnim(const FObjectInitializer& ObjecInitializer) : Super(ObjecInitializer) { - + BodyRotation = FRotator(0.0f, 0.0f, 0.0f); + BeamBottomRotation = FRotator(0.0f, 0.0f, 0.0f); + BeamTopRotation = FRotator(0.0f, 0.0f, 0.0f); + BucketRotation = FRotator(0.0f,0.0f,0.0f); } FRotator UExcavatorAnim::GetBodyRotation() diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index b650fba..8e81812 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -11,9 +11,14 @@ AExcavatorCharacter::AExcavatorCharacter() PrimaryActorTick.bCanEverTick = true; //collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); mesh = AExcavatorCharacter::GetMesh(); + AccelerationMultiplier = 1.1f; + BeamTopRotationMultiplier = 1.0f; + BucketRotationMultiplier = 1.0f; - animInstance = mesh->GetAnimInstance(); + BucketClampMAX = 180.0f; + BucketClampMIN = 0.1f; + } // Called every frame @@ -38,12 +43,12 @@ void AExcavatorCharacter::MovementC(float Direction) void AExcavatorCharacter::BucketRotationC(float RotationDirection) { //UE_LOG(LogTemp, Warning, TEXT("BucketRotation Dir: (%f)"), RotationDirection); - //AddMovementInput(mesh->GetForwardVector(), Direction * AccelerationMultiplier); - - //AnimExcavatorREF->BucketRotation.Yaw + - //float temprRot = RotationDirection* BucketRotationMultiplier + AnimExcavatorREF->BucketRotation.Yaw; - float clampedVal = UKismetMathLibrary::FClamp(RotationDirection * BucketRotationMultiplier + AnimExcavatorREF->BucketRotation.Yaw, 0.1, 180); - AnimExcavatorREF.BucketRotation.Yaw = clampedVal; + if (!ExcavatorAnimREF) + { + UE_LOG(LogTemp, Warning, TEXT("Ei vörki")); + } + float clampedVal = UKismetMathLibrary::FClamp(RotationDirection * BucketRotationMultiplier + ExcavatorAnimREF->GetBucketRotation().Yaw, BucketClampMIN, BucketClampMAX); + ExcavatorAnimREF->SetBucketRotation(FRotator(0.0f, clampedVal, 0.0f)); if (clampedVal >= 110.0f) { DetachFromBucket(); @@ -55,6 +60,7 @@ void AExcavatorCharacter::BucketRotationC(float RotationDirection) void AExcavatorCharacter::BeginPlay() { Super::BeginPlay(); + ExcavatorAnimREF = Cast<UExcavatorAnim>(mesh->GetAnimInstance()); } void AExcavatorCharacter::DetachFromBucket() diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index c8b85ee..7e88929 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -7,6 +7,7 @@ #include "Kismet/GameplayStatics.h" #include "GameFramework/InputSettings.h" //#include "CheckCollisionComponent.h" +#include "ExcavatorAnim.h" #include "ExcavatorCharacter.generated.h" UCLASS() @@ -37,24 +38,27 @@ protected: private: UInputSettings* Inputsettings = UInputSettings::GetInputSettings(); - - USkeletalMeshComponent* mesh; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float AccelerationMultiplier; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float BucketRotationMultiplier; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) - float BucketRotationMultiplier; + float BeamTopRotationMultiplier; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) - UAnimInstance* animInstance; - - // this is null - UAnimInstance* AnimExcavatorREF; + float BucketClampMAX; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) + float BucketClampMIN; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Excavator REF's", meta = (AllowPrivateAccess = "true")) AActor* RockREF; + class UExcavatorAnim* ExcavatorAnimREF; + + USkeletalMeshComponent* mesh; + void DetachFromBucket(); }; -- GitLab From e445a75904ce442fde114874b66336098054b1a9 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 22 Nov 2022 11:37:14 +0200 Subject: [PATCH 053/121] Reworking rotation with joystick --- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset | 4 ++-- .../Content/Excavator/ExcavatorParts/BP_KeyActor.uasset | 3 +++ .../Excavator/ExcavatorParts/SkelMesh_Excavator.uasset | 4 ++-- .../ExcavatorParts/Skeletons/Skeleton_ExcavatorChair.uasset | 4 ++-- 8 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index f1845d6..6cfb442 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1cccd6013ce715bd4aaa5b19f76e29e35af6692e80f409ff292d5476d32fcb5 -size 107236 +oid sha256:178a61abbc7f6377e084557cfbd9242de476223b0dff90a2f3461b1bc20659cb +size 142602 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index f677467..15dedb6 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67bee3b297ea9d22d10c359f43d50df15c9311f1783b19c5bd9c3bb0b7169134 -size 1085416 +oid sha256:5aecc4c017156883f9a9c2bd6613b9d67473d663d7fd6b2a666357664c8b5851 +size 1094578 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index ea8ad8a..1e2538d 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4943b8b401e648807e067dbd460fe7d3390cb23d6669af335603443691770b7d -size 179197 +oid sha256:16d8c4f0792b13d4fcadaa8f51fefc1be84c6f5e9f413a2c5c136d66fe77780a +size 166697 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 8f2f70e..6d5016f 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a92678c2a6121345e6f71ff915614a046618552095a08a7fc108039afb8e4e71 -size 1012397 +oid sha256:97a234ecd25884a472d225dbb158c207010dacd3603a0a503674665fa7040d2b +size 1024360 diff --git a/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset index e348086..0ac7897 100644 --- a/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/AnimBP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43fec0f78a0f9aabd573b615b87668c1a7592a46f1d41ea0cdd4f4b56f3b483e -size 143254 +oid sha256:4000e673151d0a57463c0c81903e99eb7ec270f3bda13b3c328556d2cba74c16 +size 142988 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset new file mode 100644 index 0000000..1fb3526 --- /dev/null +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b391938d70fb61397429ccce8a7fc5d5a4af4ce668555ca4721f47ad23fdc55 +size 45607 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset index 46460e8..05aca02 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/SkelMesh_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ead8d005e5e01f2fd943699ad06bce74bc7efe424773a9cfe16a40d0b5c23a1 -size 28925505 +oid sha256:aa8d557508079e5841292b3451f6f132471ba36629ba0a2e8936751a4157ce96 +size 28925181 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_ExcavatorChair.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_ExcavatorChair.uasset index bb04576..3bb8f76 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_ExcavatorChair.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_ExcavatorChair.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3046acbc73dfa2957da708355484c82278f667ec01b53bb61acc5f1bae67bf2 -size 6418 +oid sha256:a835ae77554bba82b09b9806c05ed1bdbc2ff46496713f7943c6e16c5a340458 +size 6586 -- GitLab From 93304d86f1cafdc104d1596af14b75aa48247ad5 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 12:01:41 +0200 Subject: [PATCH 054/121] Modified Character and Controller - To use Cpp functions - removed blueprint functions --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 15dedb6..84b6dbf 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5aecc4c017156883f9a9c2bd6613b9d67473d663d7fd6b2a666357664c8b5851 -size 1094578 +oid sha256:1e4bd62d87c186a284bc4fe5c3edeaca8ddb9eea5c2512de029e658890d2a500 +size 969834 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 6d5016f..c9faebf 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97a234ecd25884a472d225dbb158c207010dacd3603a0a503674665fa7040d2b -size 1024360 +oid sha256:fdcdd54ceb4583a1723296ce5b1eae6bf5c8081910168b6cc4b2af40baa39b5c +size 1003639 -- GitLab From 407888a10babda638542957d946e19539dc36de9 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 12:02:35 +0200 Subject: [PATCH 055/121] Modified ExcavatorCharacter - Added functions --- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 58 ++++++++++++++----- .../ExcavatorSimulator/ExcavatorCharacter.h | 34 +++++++---- 2 files changed, 69 insertions(+), 23 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 8e81812..ebec0d0 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -13,12 +13,13 @@ AExcavatorCharacter::AExcavatorCharacter() mesh = AExcavatorCharacter::GetMesh(); AccelerationMultiplier = 1.1f; - BeamTopRotationMultiplier = 1.0f; BucketRotationMultiplier = 1.0f; - BucketClampMAX = 180.0f; - BucketClampMIN = 0.1f; - + BeamTopRotationMultiplier = 1.0f; + BeamBottomRotationMultiplier = 1.0f; + BodyRotationMultiplier = 1.0f; + + DetachRocksAngle = 110.0f; } // Called every frame @@ -33,28 +34,59 @@ void AExcavatorCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInput Super::SetupPlayerInputComponent(PlayerInputComponent); } -void AExcavatorCharacter::MovementC(float Direction) +void AExcavatorCharacter::Movement(float Direction) { //UE_LOG(LogTemp, Warning, TEXT("Direction: (%f)"), Direction); AddMovementInput(mesh->GetForwardVector(), Direction * AccelerationMultiplier); } +void AExcavatorCharacter::BucketRotation(float RotationDirection) +{ + float tempBucketRot = RotationDirection * BucketRotationMultiplier + ExcavatorAnimREF->GetBucketRotation().Yaw; + ExcavatorAnimREF->SetBucketRotation(FRotator(0.0f, tempBucketRot, 0.0f)); + if (tempBucketRot >= DetachRocksAngle) + { + DetachFromBucket(); + } +} + +void AExcavatorCharacter::BeamTopRotation(float RotationDirection) +{ + float tempTopRot = RotationDirection * BeamTopRotationMultiplier + ExcavatorAnimREF->GetBeamTopRotation().Yaw; + ExcavatorAnimREF->SetBeamTopRotation(FRotator(0.0f, tempTopRot, 0.0f)); +} -void AExcavatorCharacter::BucketRotationC(float RotationDirection) +void AExcavatorCharacter::BeamBottomRotation(float RotationDirection) { - //UE_LOG(LogTemp, Warning, TEXT("BucketRotation Dir: (%f)"), RotationDirection); - if (!ExcavatorAnimREF) + float tempBottomRot = RotationDirection * BeamBottomRotationMultiplier + ExcavatorAnimREF->GetBeamBottomRotation().Yaw; + ExcavatorAnimREF->SetBeamBottomRotation(FRotator(0.0f, tempBottomRot, 0.0f)); +} + +void AExcavatorCharacter::BodyRotation(float RotationDirection) +{ + float tempBodyRot = RotationDirection * BodyRotationMultiplier + ExcavatorAnimREF->GetBodyRotation().Pitch; + if (-360.0f >= tempBodyRot || 360.0f <= tempBodyRot) { - UE_LOG(LogTemp, Warning, TEXT("Ei vörki")); + ExcavatorAnimREF->SetBodyRotation(FRotator(0.0f, 0.0f, 0.0f)); } - float clampedVal = UKismetMathLibrary::FClamp(RotationDirection * BucketRotationMultiplier + ExcavatorAnimREF->GetBucketRotation().Yaw, BucketClampMIN, BucketClampMAX); - ExcavatorAnimREF->SetBucketRotation(FRotator(0.0f, clampedVal, 0.0f)); - if (clampedVal >= 110.0f) + else { - DetachFromBucket(); + ExcavatorAnimREF->SetBodyRotation(FRotator(tempBodyRot, 0.0f, 0.0f)); } } +void AExcavatorCharacter::VehicleRotation(float RotationDirection) +{ + float tempVehicleRot = RotationDirection * BeamBottomRotationMultiplier + mesh->GetComponentRotation().Yaw; + if (-360.0f >= tempVehicleRot || 360.0f <= tempVehicleRot) + { + mesh->SetWorldRotation(FRotator(0.0f, 0.0f, 0.0f)); + } + else + { + mesh->SetWorldRotation(FRotator(0.0f, tempVehicleRot, 0.0f)); + } +} // Called when the game starts or when spawned void AExcavatorCharacter::BeginPlay() diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 7e88929..ef6fa12 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -26,10 +26,22 @@ public: virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; UFUNCTION(BlueprintCallable) - void MovementC(float Direction); + void Movement(float Direction); UFUNCTION(BlueprintCallable) - void BucketRotationC(float RotationDirection); + void BucketRotation(float RotationDirection); + + UFUNCTION(BlueprintCallable) + void BeamTopRotation(float RotationDirection); + + UFUNCTION(BlueprintCallable) + void BeamBottomRotation(float RotationDirection); + + UFUNCTION(BlueprintCallable) + void BodyRotation(float RotationDirection); + + UFUNCTION(BlueprintCallable) + void VehicleRotation(float RotationDirection); protected: @@ -40,21 +52,23 @@ private: UInputSettings* Inputsettings = UInputSettings::GetInputSettings(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) - float AccelerationMultiplier; + float AccelerationMultiplier; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) - float BucketRotationMultiplier; + float BucketRotationMultiplier; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) - float BeamTopRotationMultiplier; - + float BeamTopRotationMultiplier; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) - float BucketClampMAX; - + float BeamBottomRotationMultiplier; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) + float BodyRotationMultiplier; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) - float BucketClampMIN; + float VehicleRotationMultiplier; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) + float DetachRocksAngle; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Excavator REF's", meta = (AllowPrivateAccess = "true")) - AActor* RockREF; + AActor* RockREF; class UExcavatorAnim* ExcavatorAnimREF; -- GitLab From 454fbe4dca67bedee0dc4be8a342ea64e2d6ee9d Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 12:03:37 +0200 Subject: [PATCH 056/121] Modified ABP_Excavator - Added construction initialisation --- ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset index b7b4253..f54f6b9 100644 --- a/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6532f35b7bc12467840df0284fe8446dccf2c2525fc03128c9ece8e3de42f598 -size 137195 +oid sha256:42f87f03a97593aca2dd250c0586d322e3adfa5bc6df28d1156fd9e9e2e5b3e3 +size 157228 -- GitLab From aa346ad1fe11e910f1254b5a1af39b36e056073f Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 22 Nov 2022 12:05:27 +0200 Subject: [PATCH 057/121] Add VoxelWorld --- ExcavatorSimulator/Content/Levels/FirstLevel.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/FirstLevel.umap b/ExcavatorSimulator/Content/Levels/FirstLevel.umap index 4ca68bb..c7f3a5e 100644 --- a/ExcavatorSimulator/Content/Levels/FirstLevel.umap +++ b/ExcavatorSimulator/Content/Levels/FirstLevel.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59834d45e7755e01ddc037e5a2591d5b04c9cb3efc29bf9bca23886e34e50b1d -size 2672536 +oid sha256:6293d7871770fe372cd86ca5afcf5db486b7b07f8b2aed4c28561ee959fd110a +size 2695396 -- GitLab From d3e451309d1c17445fa52968c01270dc7a5a3f65 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 22 Nov 2022 12:06:27 +0200 Subject: [PATCH 058/121] Add Prototype digging --- .../CheckCollisionComponent.cpp | 8 +++++-- .../CheckCollisionComponent.h | 5 ++++ .../GroundDeformerComponent.cpp | 24 ++++++++++++------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index e5530a1..29f8de7 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -44,6 +44,7 @@ void UCheckCollisionComponent::BeginPlay() character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); mesh = character->GetMesh(); + VoxelWorldREF = Cast<AVoxelWorld>(GetWorld()); } // Called every frame @@ -103,7 +104,10 @@ void UCheckCollisionComponent::CollisionCheck() { FHitResult OutHit = FHitResult(ForceInit); FCollisionQueryParams collisionParams; + FCollisionQueryParams collisionParams2; collisionParams.AddIgnoredActor(character); + collisionParams2.AddIgnoredActor(character); + collisionParams2.AddIgnoredActor(VoxelWorldREF); FVector socketLocation = mesh->GetSocketLocation("socket_bucketMiddle"); //UE_LOG(LogTemp, Warning, TEXT("Socket: right is: x(%f), y(%f), z(%f) vs x(%f), y(%f), z(%f) mesh right:"), testLoc.RightVector.X, testLoc.RightVector.Y, testLoc.RightVector.Z, mesh->GetRightVector().X, mesh->GetRightVector().Y, mesh->GetRightVector().Z); @@ -111,7 +115,7 @@ void UCheckCollisionComponent::CollisionCheck() float lengthDown = -160.0f; FVector lineDown = mesh->GetUpVector() * lengthDown; FVector endDown = socketLocation + lineDown; - BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endDown, ECC_Visibility, collisionParams); + BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endDown, ECC_Visibility, collisionParams2); DrawDebugLine(GetWorld(), socketLocation, endDown, FColor::Red); // Line trace forward @@ -119,7 +123,7 @@ void UCheckCollisionComponent::CollisionCheck() float lengthForward = -220.0f; FVector lineFront = MeshForwardVector * lengthForward; FVector endForward = socketLocation + lineFront; - BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endForward, ECC_Visibility, collisionParams); + BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endForward, ECC_Visibility, collisionParams2); DrawDebugLine(GetWorld(), socketLocation, endForward, FColor::Red); // Line trace backward diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index 7b3e91f..f0991ac 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -5,8 +5,11 @@ #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include <Components/BoxComponent.h> +#include "VoxelWorld.h" +#include "ExcavatorAnim.h" #include "CheckCollisionComponent.generated.h" + UENUM(BlueprintType) enum class EOutputs : uint8 { @@ -59,4 +62,6 @@ private: UBoxComponent* CollisionMeshBody; UBoxComponent* CollisionMeshTrack; + + AVoxelWorld* VoxelWorldREF; }; diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp index d3a6dad..2219f2d 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp @@ -6,6 +6,7 @@ #include <Voxel/Public/VoxelDebug/VoxelDebugUtilities.h> #include <Voxel/Public/VoxelTools/Gen/VoxelSurfaceEditTools.h> #include <Voxel/Public/VoxelTools/VoxelProjectionTools.h> +#include <Voxel/Public/VoxelTools/VoxelDataTools.h> #include "WorldGenerator.h" #include "GroundDeformerComponent.h" @@ -16,7 +17,7 @@ UGroundDeformerComponent::UGroundDeformerComponent() PrimaryComponentTick.bCanEverTick = false; RemoveSphereRadius = 100.0f; - ForwardVectorMultiplier = 250.0f; + ForwardVectorMultiplier = 100.0f; } void UGroundDeformerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) @@ -28,10 +29,22 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) { FHitResult hit = FHitResult(ForceInit); TArray<FVoxelProjectionHit> voxelHit; + FVoxelLineTraceParameters parameters; //GetComponentLocation() is WorldLocation, GetForwardVctor() is ForwardVector. FVector EndVector = Mesh->GetComponentLocation() + (Mesh->GetForwardVector() * ForwardVectorMultiplier); - + if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) + { + DrawDebugLine(GetWorld(), Mesh->GetComponentLocation(), EndVector, FColor::Red, false, 5.0f, ECC_WorldStatic, 1.0f); + UVoxelProjectionTools::FindProjectionVoxels(voxelHit, VoxelWorldREF, parameters, hit.Location, Mesh->GetForwardVector(), 100.0f); + for (FVoxelProjectionHit hits : voxelHit) + { + FVector Convert2DToVector = FVector(hits.PlanePosition.X, hits.PlanePosition.Y, 0.0f); + UVoxelDataTools::SetValue(VoxelWorldREF, hits.VoxelPosition, 1.0f); + UVoxelSurfaceTools::ApplyFlatten(VoxelWorldREF, Convert2DToVector); + + } + } //FVoxelIntBox IntBox = UVoxelBlueprintLibrary::MakeIntBoxFromGlobalPositionAndRadius(VoxelWorldREF, Mesh->GetComponentLocation(), 100.0f); //UVoxelDebugUtilities::DrawDebugIntBox(VoxelWorldREF, IntBox, IntBox.); @@ -48,13 +61,6 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) //UVoxelSurfaceEditTools::EditVoxelValues(VoxelWorldREF, PVoxels); //UVoxelSphereTools::SmoothSphere(VoxelWorldREF, Mesh->GetComponentLocation(), 140.0f, 0.5f); - - - //if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) - //{ - ///* DrawDebugLine(GetWorld(), Mesh->GetComponentLocation(), EndVector, FColor::Red, false, 5.0f, ECC_WorldStatic, 1.0f); - // UVoxelProjectionTools::FindProjectionVoxels(voxelHit, VoxelWorldREF,Parameters,hit.ImpactPoint,Mesh->GetForwardVector(), 10.0f);*/ - //} } void UGroundDeformerComponent::BeginPlay() -- GitLab From db84a1385d39d856fc603f066a6ca1db8c77b981 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 22 Nov 2022 12:28:35 +0200 Subject: [PATCH 059/121] POG --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 84b6dbf..b7e433f 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e4bd62d87c186a284bc4fe5c3edeaca8ddb9eea5c2512de029e658890d2a500 -size 969834 +oid sha256:f3d4a3f5036d2bc41c8d5b532d455f463b433d44697f323caf371a50364ccf3c +size 983762 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index c9faebf..4e57c19 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdcdd54ceb4583a1723296ce5b1eae6bf5c8081910168b6cc4b2af40baa39b5c -size 1003639 +oid sha256:e3ca8a81deee58ee8184632365af79c1ca598b65e8704f8642477a3a050c8b4a +size 1009805 -- GitLab From 3486f3bc2a870a50e4cc98318621c745daed9477 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 22 Nov 2022 12:49:32 +0200 Subject: [PATCH 060/121] Keyactor added and modified values --- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- .../Content/Excavator/ExcavatorParts/BP_KeyActor.uasset | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index 6cfb442..807f154 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:178a61abbc7f6377e084557cfbd9242de476223b0dff90a2f3461b1bc20659cb -size 142602 +oid sha256:8612630693ff5718654ef2f952e6e7b54640d7a52a43aa12c56aca198cc4714f +size 108897 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index b7e433f..f760482 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3d4a3f5036d2bc41c8d5b532d455f463b433d44697f323caf371a50364ccf3c -size 983762 +oid sha256:b946dbd816be59b950e29b0073f4285abfaa4ed9092b55d5b0f476d0a593d8f1 +size 1022980 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index 1e2538d..a162db0 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16d8c4f0792b13d4fcadaa8f51fefc1be84c6f5e9f413a2c5c136d66fe77780a -size 166697 +oid sha256:870041684e5e808ff8ee23f4476375f4b8bfacd2582d031469bfa7c6b0574019 +size 166748 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset index 1fb3526..d24f16a 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b391938d70fb61397429ccce8a7fc5d5a4af4ce668555ca4721f47ad23fdc55 -size 45607 +oid sha256:cecab3d7b59d6f89721ccaa0c9183a671b49f02e3716dec7782a218b3256073d +size 45152 -- GitLab From 3d0e3f2395645b33b5911d5c115743ca895873e7 Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Tue, 22 Nov 2022 14:01:09 +0200 Subject: [PATCH 061/121] excavator engine start up sound --- .../Content/Sound/Assets/excavator_dynamic_engine-cue.uasset | 4 ++-- .../Content/Sound/Assets/excavator_engine_high.uasset | 3 +++ .../Content/Sound/Assets/excavator_engine_low.uasset | 3 +++ .../Content/Sound/Assets/excavator_engine_med.uasset | 3 +++ .../Content/Sound/Assets/excavator_idle_engine.uasset | 3 --- .../Content/Sound/Assets/excavator_idle_engine_Cue.uasset | 4 ++-- .../Content/Sound/Assets/excavator_startup.uasset | 3 +++ .../Content/Sound/Assets/idle_engine_high.uasset | 3 --- .../Content/Sound/Assets/idle_engine_med.uasset | 3 --- .../Content/Sound/Assets/small_engine_Cue.uasset | 3 --- .../Content/Sound/Assets/wheels_rolling_Cue.uasset | 3 --- 11 files changed, 16 insertions(+), 19 deletions(-) create mode 100644 ExcavatorSimulator/Content/Sound/Assets/excavator_engine_high.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/excavator_engine_low.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/excavator_engine_med.uasset delete mode 100644 ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/excavator_startup.uasset delete mode 100644 ExcavatorSimulator/Content/Sound/Assets/idle_engine_high.uasset delete mode 100644 ExcavatorSimulator/Content/Sound/Assets/idle_engine_med.uasset delete mode 100644 ExcavatorSimulator/Content/Sound/Assets/small_engine_Cue.uasset delete mode 100644 ExcavatorSimulator/Content/Sound/Assets/wheels_rolling_Cue.uasset diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset index c1bcc63..f275f59 100644 --- a/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1aa157b5a796f00ec3956f5020d4feb3b7ac592bb76d5e4cfe2367471aed5627 -size 12997 +oid sha256:cacd16adb43567f2e7b4e6ca5b055552ee671bd06a7afa5e4cb9a8a8eb770136 +size 13033 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_high.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_high.uasset new file mode 100644 index 0000000..34fac58 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_high.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:817e71a5a1fe11b10b10c77d5574b99b47229c7dc7eb62f845ce82ede9f98740 +size 5856170 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_low.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_low.uasset new file mode 100644 index 0000000..489793a --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_low.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5274ecc9a7972e4e2c6c0f004dc8fee69a7a84310c4f1a4461dcbfe673ee23ac +size 6469414 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_med.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_med.uasset new file mode 100644 index 0000000..0365977 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_engine_med.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b0a659be723ddfbbb618e00255085b3f7ad24bd323d228918b8f048ff303c02 +size 6405449 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine.uasset deleted file mode 100644 index 456e3e5..0000000 --- a/ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:28f4d7c793a1d754bb61daa58bec6ccbc545de23f2ad89e4924f782ea715426c -size 6469418 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine_Cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine_Cue.uasset index 70b5832..df4a961 100644 --- a/ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine_Cue.uasset +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_idle_engine_Cue.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6850956f1bea64c486644d1b0fd8393df0cdb5a7914f028987e272e8d7313600 -size 4544 +oid sha256:cea2f15c25c5504688c182b6f74d777d3e37df9e00d1941edfd63d33c28b4b17 +size 4540 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_startup.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_startup.uasset new file mode 100644 index 0000000..61d38d7 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_startup.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de90032661f792f86daf4a19d6d0a080c12b79042d4bdd67eb059e26d1a65499 +size 470287 diff --git a/ExcavatorSimulator/Content/Sound/Assets/idle_engine_high.uasset b/ExcavatorSimulator/Content/Sound/Assets/idle_engine_high.uasset deleted file mode 100644 index 560b9b0..0000000 --- a/ExcavatorSimulator/Content/Sound/Assets/idle_engine_high.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be70fedf54a580e9bc317e4afa425dc93c8bd80422e7f13607f961cd925b688b -size 5856150 diff --git a/ExcavatorSimulator/Content/Sound/Assets/idle_engine_med.uasset b/ExcavatorSimulator/Content/Sound/Assets/idle_engine_med.uasset deleted file mode 100644 index 9327447..0000000 --- a/ExcavatorSimulator/Content/Sound/Assets/idle_engine_med.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7cca1a6d50ca5a8d353fcd08e452ed6df680afe94dbec86c577c416d8c49370 -size 6405429 diff --git a/ExcavatorSimulator/Content/Sound/Assets/small_engine_Cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/small_engine_Cue.uasset deleted file mode 100644 index be417da..0000000 --- a/ExcavatorSimulator/Content/Sound/Assets/small_engine_Cue.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea77cf26ce4264b817854c399700f9374fa28fbc55e8df1fb754aefb07f06b88 -size 4426 diff --git a/ExcavatorSimulator/Content/Sound/Assets/wheels_rolling_Cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/wheels_rolling_Cue.uasset deleted file mode 100644 index 4ff8b95..0000000 --- a/ExcavatorSimulator/Content/Sound/Assets/wheels_rolling_Cue.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6eacfd65c9408509793e428309d93c7fad1155134c28805488b99eaa06935820 -size 4517 -- GitLab From 260ddfec54ed2effdc1401898666149a1d5df665 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 14:37:36 +0200 Subject: [PATCH 062/121] Modified BP_ExcavatorCharacter --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index f760482..e32255a 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b946dbd816be59b950e29b0073f4285abfaa4ed9092b55d5b0f476d0a593d8f1 -size 1022980 +oid sha256:ac8e6de630ae638971383145d09d6d09f180bca295c5602f777dcfba48508793 +size 924361 -- GitLab From 0580542308b80744c652d859cf7c691faa41dddf Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 14:37:56 +0200 Subject: [PATCH 063/121] Modified CheckCollisionComponent --- .../CheckCollisionComponent.cpp | 52 ++++++++++++------- .../CheckCollisionComponent.h | 18 ++----- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index 29f8de7..419e484 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -35,6 +35,7 @@ UCheckCollisionComponent::UCheckCollisionComponent() BucketCollidingFromRight = false; character = nullptr; mesh = nullptr; + VoxelWorldREF = nullptr; } // Called when the game starts @@ -44,7 +45,8 @@ void UCheckCollisionComponent::BeginPlay() character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); mesh = character->GetMesh(); - VoxelWorldREF = Cast<AVoxelWorld>(GetWorld()); + + VoxelWorldREF = Cast<AVoxelWorld>(UGameplayStatics::GetActorOfClass(GetWorld(), AVoxelWorld::StaticClass())); } // Called every frame @@ -57,25 +59,24 @@ void UCheckCollisionComponent::TickComponent(float DeltaTime, ELevelTick TickTyp // oikealle rot = -1 // vasemmalle rot = 1 -void UCheckCollisionComponent::IsCollidingLR(EOutputs& OutputPins, float rotationDirection) +bool UCheckCollisionComponent::IsCollidingLR(float rotationDirection) { // if we are colliding from left and trying to rotate left we cant - if (rotationDirection == -1.0f && BucketCollidingFromLeft) + if (rotationDirection <= -0.1f && BucketCollidingFromLeft) { - OutputPins = EOutputs::Colliding; - return; + UE_LOG(LogTemp, Warning, TEXT("CollisionFromLeft")); + return false; } // if we are colliding from right and trying to rotate right we cant - else if (rotationDirection == 1.0f && BucketCollidingFromRight) + else if (rotationDirection >= 0.1f && BucketCollidingFromRight) { - OutputPins = EOutputs::Colliding; - return; + UE_LOG(LogTemp, Warning, TEXT("CollisionFromRight")); + return false; } // if we are not colliding form eather direction rotate freely // or we are colliding only from one direction we can still spin to the other // like collidingfromLeft but trying to rotate to the right that is possible - OutputPins = EOutputs::NotColliding; - return; + return true; } void UCheckCollisionComponent::CheckCollisionsBody() @@ -85,21 +86,23 @@ void UCheckCollisionComponent::CheckCollisionsBody() //UE_LOG(LogTemp, Warning, TEXT("Body loc is: x(%f), y(%f), z(%f) "), mesh->GetSocketLocation("bone_bodySocketMiddle").X, mesh->GetSocketLocation("bone_bodySocketMiddle").Y, mesh->GetSocketLocation("bone_bodySocketMiddle").Z); } -void UCheckCollisionComponent::IsCollidingFB(EOutputs& OutputPins, bool movingForward = false) +bool UCheckCollisionComponent::IsCollidingFB(float movingDirection) { - if (movingForward) + if (movingDirection == 1.0f && BucketCollidingFromFront) + { + return false; + } + + if (movingDirection == 1.0f && BucketCollidingFromDown) { - if (BucketCollidingFromDown || BucketCollidingFromFront) - { - OutputPins = EOutputs::Colliding; - return; - } + return false; } - OutputPins = EOutputs::NotColliding; - return; + + return true; } -//@TODO: ota anim instance skeletal meshistä +// @TODO: Lisää kivet ingoreeen +// @TODO: lisää kaikki physiikka actorit ingnoreen void UCheckCollisionComponent::CollisionCheck() { FHitResult OutHit = FHitResult(ForceInit); @@ -144,4 +147,13 @@ void UCheckCollisionComponent::CollisionCheck() FVector endRight = socketLocation + lineRight; BucketCollidingFromRight = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endRight, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Red); + + if (BucketCollidingFromDown) + UE_LOG(LogTemp, Warning, TEXT("Colliding from DOWN!")); + if (BucketCollidingFromFront) + UE_LOG(LogTemp, Warning, TEXT("Colliding from FRONT!")); + if (BucketCollidingFromLeft) + UE_LOG(LogTemp, Warning, TEXT("Colliding from LEFT!")); + if (BucketCollidingFromRight) + UE_LOG(LogTemp, Warning, TEXT("Colliding from RIGHT!")); } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index f0991ac..d64f28e 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -5,18 +5,10 @@ #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include <Components/BoxComponent.h> -#include "VoxelWorld.h" #include "ExcavatorAnim.h" +#include "VoxelWorld.h" #include "CheckCollisionComponent.generated.h" - -UENUM(BlueprintType) -enum class EOutputs : uint8 -{ - Colliding, - NotColliding, -}; - UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class EXCAVATORSIMULATOR_API UCheckCollisionComponent : public UActorComponent { @@ -28,12 +20,9 @@ public: void CollisionCheck(); - //void Collisons(TArray<FString> Sockets, float lineLenghtUp, float lineLenghtDown, float lineLenghtFront); - UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) - void IsCollidingLR(EOutputs& OutputPins, float rotationDirection); + bool IsCollidingLR(float rotationDirection); - UFUNCTION(BlueprintCallable, Category = "Collision Functions", Meta = (ExpandEnumAsExecs = "OutputPins")) - void IsCollidingFB(EOutputs& OutputPins, bool moving); + bool IsCollidingFB(float movingDirection); // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; @@ -63,5 +52,6 @@ private: UBoxComponent* CollisionMeshBody; UBoxComponent* CollisionMeshTrack; + // optional class in front AVoxelWorld* VoxelWorldREF; }; -- GitLab From 56bb1ee32c054a1c0fcd8fb2a52e63d16427b662 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 14:38:16 +0200 Subject: [PATCH 064/121] Modified ExcavatorCharacter --- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 63 ++++++++++++------- .../ExcavatorSimulator/ExcavatorCharacter.h | 4 +- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index ebec0d0..620ab85 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -9,7 +9,7 @@ AExcavatorCharacter::AExcavatorCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; - //collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); + collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); mesh = AExcavatorCharacter::GetMesh(); AccelerationMultiplier = 1.1f; @@ -18,8 +18,10 @@ AExcavatorCharacter::AExcavatorCharacter() BeamTopRotationMultiplier = 1.0f; BeamBottomRotationMultiplier = 1.0f; BodyRotationMultiplier = 1.0f; + VehicleRotationMultiplier = 1.0f; DetachRocksAngle = 110.0f; + ExcavatorAnimREF = nullptr; } // Called every frame @@ -36,8 +38,12 @@ void AExcavatorCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInput void AExcavatorCharacter::Movement(float Direction) { - //UE_LOG(LogTemp, Warning, TEXT("Direction: (%f)"), Direction); - AddMovementInput(mesh->GetForwardVector(), Direction * AccelerationMultiplier); + float tempDir = Direction * -1; + if (collisionComponent->IsCollidingFB(tempDir)) + { + //UE_LOG(LogTemp, Warning, TEXT("Direction: (%f)"), Direction); + AddMovementInput(mesh->GetForwardVector(), Direction * AccelerationMultiplier); + } } void AExcavatorCharacter::BucketRotation(float RotationDirection) @@ -52,39 +58,54 @@ void AExcavatorCharacter::BucketRotation(float RotationDirection) void AExcavatorCharacter::BeamTopRotation(float RotationDirection) { - float tempTopRot = RotationDirection * BeamTopRotationMultiplier + ExcavatorAnimREF->GetBeamTopRotation().Yaw; - ExcavatorAnimREF->SetBeamTopRotation(FRotator(0.0f, tempTopRot, 0.0f)); + bool movingForward = RotationDirection >= 0.0f ? true : false; + if (collisionComponent->IsCollidingFB(movingForward)) + { + float tempTopRot = RotationDirection * BeamTopRotationMultiplier + ExcavatorAnimREF->GetBeamTopRotation().Yaw; + ExcavatorAnimREF->SetBeamTopRotation(FRotator(0.0f, tempTopRot, 0.0f)); + } } void AExcavatorCharacter::BeamBottomRotation(float RotationDirection) { - float tempBottomRot = RotationDirection * BeamBottomRotationMultiplier + ExcavatorAnimREF->GetBeamBottomRotation().Yaw; - ExcavatorAnimREF->SetBeamBottomRotation(FRotator(0.0f, tempBottomRot, 0.0f)); + bool movingForward = RotationDirection <= 0.0f ? true : false; + if (collisionComponent->IsCollidingFB(movingForward)) + { + float tempBottomRot = RotationDirection * BeamBottomRotationMultiplier + ExcavatorAnimREF->GetBeamBottomRotation().Yaw; + ExcavatorAnimREF->SetBeamBottomRotation(FRotator(0.0f, tempBottomRot, 0.0f)); + } } void AExcavatorCharacter::BodyRotation(float RotationDirection) { - float tempBodyRot = RotationDirection * BodyRotationMultiplier + ExcavatorAnimREF->GetBodyRotation().Pitch; - if (-360.0f >= tempBodyRot || 360.0f <= tempBodyRot) - { - ExcavatorAnimREF->SetBodyRotation(FRotator(0.0f, 0.0f, 0.0f)); - } - else + if (collisionComponent->IsCollidingLR(RotationDirection)) { - ExcavatorAnimREF->SetBodyRotation(FRotator(tempBodyRot, 0.0f, 0.0f)); + float tempBodyRot = RotationDirection * BodyRotationMultiplier + ExcavatorAnimREF->GetBodyRotation().Pitch; + + if (-360.0f >= tempBodyRot || 360.0f <= tempBodyRot) + { + ExcavatorAnimREF->SetBodyRotation(FRotator(0.0f, 0.0f, 0.0f)); + } + else + { + ExcavatorAnimREF->SetBodyRotation(FRotator(tempBodyRot, 0.0f, 0.0f)); + } } } void AExcavatorCharacter::VehicleRotation(float RotationDirection) { - float tempVehicleRot = RotationDirection * BeamBottomRotationMultiplier + mesh->GetComponentRotation().Yaw; - if (-360.0f >= tempVehicleRot || 360.0f <= tempVehicleRot) - { - mesh->SetWorldRotation(FRotator(0.0f, 0.0f, 0.0f)); - } - else + if (collisionComponent->IsCollidingLR(RotationDirection)) { - mesh->SetWorldRotation(FRotator(0.0f, tempVehicleRot, 0.0f)); + float tempVehicleRot = RotationDirection * BeamBottomRotationMultiplier + mesh->GetComponentRotation().Yaw; + if (-360.0f >= tempVehicleRot || 360.0f <= tempVehicleRot) + { + mesh->SetWorldRotation(FRotator(0.0f, 0.0f, 0.0f)); + } + else + { + mesh->SetWorldRotation(FRotator(0.0f, tempVehicleRot, 0.0f)); + } } } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index ef6fa12..740920a 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -6,7 +6,7 @@ #include "GameFramework/Character.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/InputSettings.h" -//#include "CheckCollisionComponent.h" +#include "CheckCollisionComponent.h" #include "ExcavatorAnim.h" #include "ExcavatorCharacter.generated.h" @@ -72,6 +72,8 @@ private: class UExcavatorAnim* ExcavatorAnimREF; + class UCheckCollisionComponent* collisionComponent; + USkeletalMeshComponent* mesh; void DetachFromBucket(); -- GitLab From d1f72b6362bfd1e93316a55577a6551ac822a4aa Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 14:38:38 +0200 Subject: [PATCH 065/121] Modified GroundDeformerComponent --- .../Source/ExcavatorSimulator/GroundDeformerComponent.cpp | 1 + .../Source/ExcavatorSimulator/GroundDeformerComponent.h | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp index 2219f2d..915250b 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp @@ -66,6 +66,7 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) void UGroundDeformerComponent::BeginPlay() { Super::BeginPlay(); + VoxelWorldREF = Cast<AVoxelWorld>(UGameplayStatics::GetActorOfClass(GetWorld(), AVoxelWorld::StaticClass())); } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.h index b97a0a6..a2f5336 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.h @@ -8,6 +8,7 @@ #include "Kismet/KismetMathLibrary.h" #include "CollisionQueryParams.h" #include "Kismet/GameplayStatics.h" +#include "VoxelWorld.h" #include <Voxel/Public/VoxelTools/Gen/VoxelSphereTools.h> #include <Voxel/Public/VoxelTools/Gen/VoxelBoxTools.h> #include <Voxel/Public/VoxelIntBoxLibrary.h> @@ -39,12 +40,7 @@ protected: virtual void BeginPlay() override; private: - /** VoxelWorld. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true")) - TSubclassOf<AActor> ClassToFind; - /** Refrence to VoxelWorld. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = "true")) AVoxelWorld* VoxelWorldREF; /** Variable for RemoveSphere radius. */ -- GitLab From c52d85302feccb39e37953120e60edf6b777bf14 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 15:02:24 +0200 Subject: [PATCH 066/121] Add Key and Accessory --- ExcavatorSimulator/Content/Objects/Key.uasset | 3 +++ ExcavatorSimulator/Content/Objects/KeyAccessory.uasset | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 ExcavatorSimulator/Content/Objects/Key.uasset create mode 100644 ExcavatorSimulator/Content/Objects/KeyAccessory.uasset diff --git a/ExcavatorSimulator/Content/Objects/Key.uasset b/ExcavatorSimulator/Content/Objects/Key.uasset new file mode 100644 index 0000000..7ad13f6 --- /dev/null +++ b/ExcavatorSimulator/Content/Objects/Key.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b75dadf23a099b51647ab084871180bd25d7b462475ee75ce2d9ed4a4586f7cf +size 178454 diff --git a/ExcavatorSimulator/Content/Objects/KeyAccessory.uasset b/ExcavatorSimulator/Content/Objects/KeyAccessory.uasset new file mode 100644 index 0000000..0aa664c --- /dev/null +++ b/ExcavatorSimulator/Content/Objects/KeyAccessory.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0dee7b9eb5a6b4d2bd17d17fcf7f63511e3e9e81a686be9bc25e2edbab34e3c +size 148082 -- GitLab From 7aa50344287f735032714359fe0ff165d6a98a9b Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 22 Nov 2022 15:45:26 +0200 Subject: [PATCH 067/121] Added physics to accessory --- ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset | 4 ++-- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- .../Content/Excavator/ExcavatorParts/BP_KeyActor.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/FirstLevel.umap | 4 ++-- ExcavatorSimulator/Content/Objects/Key.uasset | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset index 807f154..22af7c3 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Cabin.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8612630693ff5718654ef2f952e6e7b54640d7a52a43aa12c56aca198cc4714f -size 108897 +oid sha256:1fc2e77c5e32533aa9aa216e1b141af3b15f1d81e86383c72a14ec7b3d9b3359 +size 121700 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index e32255a..3ad323f 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac8e6de630ae638971383145d09d6d09f180bca295c5602f777dcfba48508793 -size 924361 +oid sha256:ccef16b2c662e41a7c0de356fa4cf3b8bb35535b6082e31545cdf4778139d0cb +size 933832 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset index a162db0..fc6e99b 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_JoyStick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:870041684e5e808ff8ee23f4476375f4b8bfacd2582d031469bfa7c6b0574019 -size 166748 +oid sha256:078b8134eec0b560bbaeb92a0c9ef5b47dee1651f699ec62dd8adfbc176fbe8e +size 171160 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 4e57c19..0320d6d 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3ca8a81deee58ee8184632365af79c1ca598b65e8704f8642477a3a050c8b4a -size 1009805 +oid sha256:743fb8674588842a168f1a041186d95f584a74031513660c6b712fa5a16a7310 +size 1074868 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset index d24f16a..23bddd3 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cecab3d7b59d6f89721ccaa0c9183a671b49f02e3716dec7782a218b3256073d -size 45152 +oid sha256:79e3176e79beb6f3da3e5a707833d3b43ebe76afbecce1507bb3009fcb2983f9 +size 64487 diff --git a/ExcavatorSimulator/Content/Levels/FirstLevel.umap b/ExcavatorSimulator/Content/Levels/FirstLevel.umap index c7f3a5e..110625a 100644 --- a/ExcavatorSimulator/Content/Levels/FirstLevel.umap +++ b/ExcavatorSimulator/Content/Levels/FirstLevel.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6293d7871770fe372cd86ca5afcf5db486b7b07f8b2aed4c28561ee959fd110a -size 2695396 +oid sha256:692f9a656497da9d515c16994e4999e946f0eab4e4166d90d99735b3424e1292 +size 2688991 diff --git a/ExcavatorSimulator/Content/Objects/Key.uasset b/ExcavatorSimulator/Content/Objects/Key.uasset index 7ad13f6..b759c93 100644 --- a/ExcavatorSimulator/Content/Objects/Key.uasset +++ b/ExcavatorSimulator/Content/Objects/Key.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b75dadf23a099b51647ab084871180bd25d7b462475ee75ce2d9ed4a4586f7cf -size 178454 +oid sha256:7c56994be2b32c70efb85110a34a1a77bd20b2de0b57bd41d1ce70aa992e3cf9 +size 179344 -- GitLab From 6604bfce2a9bd8c763b72b4eda7136cbd259cfc6 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 15:58:49 +0200 Subject: [PATCH 068/121] Modified CheckCollisionComponent changed ingore params --- .../Source/ExcavatorSimulator/CheckCollisionComponent.cpp | 4 ++-- .../Source/ExcavatorSimulator/CheckCollisionComponent.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index 419e484..d3dce5a 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -106,8 +106,8 @@ bool UCheckCollisionComponent::IsCollidingFB(float movingDirection) void UCheckCollisionComponent::CollisionCheck() { FHitResult OutHit = FHitResult(ForceInit); - FCollisionQueryParams collisionParams; FCollisionQueryParams collisionParams2; + FCollisionQueryParams collisionParams; collisionParams.AddIgnoredActor(character); collisionParams2.AddIgnoredActor(character); collisionParams2.AddIgnoredActor(VoxelWorldREF); @@ -130,7 +130,7 @@ void UCheckCollisionComponent::CollisionCheck() DrawDebugLine(GetWorld(), socketLocation, endForward, FColor::Red); // Line trace backward - /*FVector lineBack = MeshForwardVector * lengthForward * (-1); + /* FVector lineBack = MeshForwardVector * lengthForward * (-1); FVector endBack = socketLocation + lineBack; BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endBack, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endBack, FColor::Red);*/ diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index d64f28e..a058848 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -52,6 +52,5 @@ private: UBoxComponent* CollisionMeshBody; UBoxComponent* CollisionMeshTrack; - // optional class in front AVoxelWorld* VoxelWorldREF; }; -- GitLab From f2108400b22107c47ebb473c0ea46ed50914ea8a Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 15:59:32 +0200 Subject: [PATCH 069/121] Modified ExcavatorCharacter --- .../Content/Blueprints/BP_Excavator.uasset | 4 +-- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 25 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset index 01c8921..7cf0a6d 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6c38c128e2ae08a408244273d26289add9cb037963251e9726a2d6809e17b3f -size 54054 +oid sha256:1c67b1b04a516b85feaa25ca01ae8e5d4eb5040beedb150b5217e9410b956e52 +size 53423 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 620ab85..56c6b53 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -2,6 +2,7 @@ #include "ExcavatorCharacter.h" +#include "PickableRock.h" #include <Kismet/KismetMathLibrary.h> // Sets default values @@ -10,7 +11,7 @@ AExcavatorCharacter::AExcavatorCharacter() // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); - mesh = AExcavatorCharacter::GetMesh(); + mesh = GetMesh(); AccelerationMultiplier = 1.1f; @@ -118,5 +119,25 @@ void AExcavatorCharacter::BeginPlay() void AExcavatorCharacter::DetachFromBucket() { - + APickableRock* RockREF; + RockREF = Cast<APickableRock>(UGameplayStatics::GetActorOfClass(GetWorld(), APickableRock::StaticClass())); + TArray<FString> RockNameArray; + if (RockREF) + { + TArray<AActor*> test; + UGameplayStatics::GetAllActorsOfClass(GetWorld(), RockREF->StaticClass(), test); + + for (AActor* rock : test) + { + rock = Cast<APickableRock>(UGameplayStatics::GetActorOfClass(GetWorld(), APickableRock::StaticClass())); + FString currentName = rock->GetName(); + for (FString rockName : RockNameArray) + { + if (rockName == currentName) + { + rock->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + } + } + } } -- GitLab From 80c7e9a0102a6390c167a61c8c70e7893a244e77 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 22 Nov 2022 15:59:51 +0200 Subject: [PATCH 070/121] Added rock C++ class --- .../Content/Blueprints/BP_rock.uasset | 4 +- .../ExcavatorSimulator/PickableRock.cpp | 27 +++++++++++ .../Source/ExcavatorSimulator/PickableRock.h | 48 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp create mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index 4fa13b4..67360e7 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f1275ef7cc0ee74638acbe3520f87222390318ace4affe6336c34f5a30f5de1 -size 122473 +oid sha256:e63d3cf46499d5fa8b39a844c50873835e19e42d8d97507bb5ad989a6d399793 +size 123073 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp new file mode 100644 index 0000000..8adbc7b --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp @@ -0,0 +1,27 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "PickableRock.h" + +// Sets default values +APickableRock::APickableRock() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + +} + +// Called when the game starts or when spawned +void APickableRock::BeginPlay() +{ + Super::BeginPlay(); + +} + +// Called every frame +void APickableRock::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h b/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h new file mode 100644 index 0000000..9e5afdf --- /dev/null +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h @@ -0,0 +1,48 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "PickableRock.generated.h" + +UCLASS() +class EXCAVATORSIMULATOR_API APickableRock : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + APickableRock(); + + // Called every frame + virtual void Tick(float DeltaTime) override; + + void SetIsAtStart(bool isAtStart) + { + IsAtStartPoint = isAtStart; + } + + void SetIsAtEnd(bool isAtEnd) + { + IsAtEndPoint = isAtEnd; + } + + bool GetIsAtEnd() + { + return IsAtEndPoint; + } + + bool GetIsAtStart() + { + return IsAtStartPoint; + } + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +private: + bool IsAtStartPoint; + bool IsAtEndPoint; +}; -- GitLab From 36c82c578ea1d88fc697600112e87e8bb7740ea8 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Tue, 22 Nov 2022 16:00:09 +0200 Subject: [PATCH 071/121] Add FirstLevelVoxelSave --- ExcavatorSimulator/Content/FirstLevelVoxelSave.uasset | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ExcavatorSimulator/Content/FirstLevelVoxelSave.uasset diff --git a/ExcavatorSimulator/Content/FirstLevelVoxelSave.uasset b/ExcavatorSimulator/Content/FirstLevelVoxelSave.uasset new file mode 100644 index 0000000..34cfca3 --- /dev/null +++ b/ExcavatorSimulator/Content/FirstLevelVoxelSave.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8116ef18ed6f386fa92830217b67d30ccf7402a0acc4ddf4bcddd1b3a342cfc5 +size 667913 -- GitLab From 9dc992fbea4d13869f0bf2f8a90ba95a5365787a Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 08:33:11 +0200 Subject: [PATCH 072/121] Modified rock back to Blueprints --- ExcavatorSimulator/Content/Blueprints/BP_rock.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index 67360e7..a17dff6 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e63d3cf46499d5fa8b39a844c50873835e19e42d8d97507bb5ad989a6d399793 -size 123073 +oid sha256:69a2007bbcb87b741283b0b3cdf2ed10bccfffc599c2b7581541d5137797a910 +size 122525 -- GitLab From 0ac290ac9c8baf749341c88ae716c1ba5f252698 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 08:33:52 +0200 Subject: [PATCH 073/121] Modified ExcavatorCharacter - Detach form bucket remains as blueprint function - BP_VRController now calls the Bucket rot func in BP_ExcavatorCharacter - No longer need the rock reference in header file --- .../Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- .../Content/Blueprints/BP_VRController.uasset | 4 ++-- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 15 ++++++++------- .../ExcavatorSimulator/ExcavatorCharacter.h | 6 +++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 3ad323f..9f1575b 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccef16b2c662e41a7c0de356fa4cf3b8bb35535b6082e31545cdf4778139d0cb -size 933832 +oid sha256:31c1e6a8defbea54878b130fd736c3ae588f3fdd1fc3b3820c429f9b83fd8051 +size 947462 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 0320d6d..d13d929 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:743fb8674588842a168f1a041186d95f584a74031513660c6b712fa5a16a7310 -size 1074868 +oid sha256:82e7d9b18bb87a6fc246b4ffaf9e0756c7bdb0aae2f320615212be72c978b614 +size 1070851 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 56c6b53..d570ea1 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -47,14 +47,15 @@ void AExcavatorCharacter::Movement(float Direction) } } -void AExcavatorCharacter::BucketRotation(float RotationDirection) +bool AExcavatorCharacter::BucketRotationReleaceRocks(float RotationDirection) { float tempBucketRot = RotationDirection * BucketRotationMultiplier + ExcavatorAnimREF->GetBucketRotation().Yaw; ExcavatorAnimREF->SetBucketRotation(FRotator(0.0f, tempBucketRot, 0.0f)); - if (tempBucketRot >= DetachRocksAngle) - { - DetachFromBucket(); - } + //if (tempBucketRot >= DetachRocksAngle) + //{ + // DetachFromBucket(); + //} + return (tempBucketRot >= DetachRocksAngle) ? true : false; } void AExcavatorCharacter::BeamTopRotation(float RotationDirection) @@ -119,7 +120,7 @@ void AExcavatorCharacter::BeginPlay() void AExcavatorCharacter::DetachFromBucket() { - APickableRock* RockREF; + /*APickableRock* RockREF; RockREF = Cast<APickableRock>(UGameplayStatics::GetActorOfClass(GetWorld(), APickableRock::StaticClass())); TArray<FString> RockNameArray; if (RockREF) @@ -139,5 +140,5 @@ void AExcavatorCharacter::DetachFromBucket() } } } - } + }*/ } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 740920a..0f528f6 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -29,7 +29,7 @@ public: void Movement(float Direction); UFUNCTION(BlueprintCallable) - void BucketRotation(float RotationDirection); + bool BucketRotationReleaceRocks(float RotationDirection); UFUNCTION(BlueprintCallable) void BeamTopRotation(float RotationDirection); @@ -67,8 +67,8 @@ private: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float DetachRocksAngle; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Excavator REF's", meta = (AllowPrivateAccess = "true")) - AActor* RockREF; + //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Excavator REF's", meta = (AllowPrivateAccess = "true")) + // AActor* RockREF; class UExcavatorAnim* ExcavatorAnimREF; -- GitLab From 75f4bfec7741bf93e31a224934c3f9ce65bcbb6e Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Fri, 25 Nov 2022 10:31:06 +0200 Subject: [PATCH 074/121] Startup sounds, removed debug menu --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- .../Content/Excavator/ExcavatorParts/BP_KeyActor.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/FirstLevel.umap | 4 ++-- ExcavatorSimulator/Content/Objects/Key.uasset | 4 ++-- ExcavatorSimulator/Content/Objects/KeyAccessory.uasset | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 9f1575b..239e978 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31c1e6a8defbea54878b130fd736c3ae588f3fdd1fc3b3820c429f9b83fd8051 -size 947462 +oid sha256:6ef4290bd3f69227757f5d1148208933fc6d03d78c0f2a5e36e769f290102ca7 +size 889710 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index d13d929..798e838 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82e7d9b18bb87a6fc246b4ffaf9e0756c7bdb0aae2f320615212be72c978b614 -size 1070851 +oid sha256:cc2d9d8f49d4ec4388f495ebefd398399deca360e775862eab4cf50732cf4ada +size 1143649 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset index 23bddd3..6e062c4 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79e3176e79beb6f3da3e5a707833d3b43ebe76afbecce1507bb3009fcb2983f9 -size 64487 +oid sha256:6dab4c4da5e58f478c857202b650de8a5e563ea3253c32194b79db5d5a142d29 +size 100317 diff --git a/ExcavatorSimulator/Content/Levels/FirstLevel.umap b/ExcavatorSimulator/Content/Levels/FirstLevel.umap index 110625a..a5ac92d 100644 --- a/ExcavatorSimulator/Content/Levels/FirstLevel.umap +++ b/ExcavatorSimulator/Content/Levels/FirstLevel.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:692f9a656497da9d515c16994e4999e946f0eab4e4166d90d99735b3424e1292 -size 2688991 +oid sha256:afdb435dd1ee31cf19b525bf430185d1bb8217f29313d7c7f4589c33382b4724 +size 1979161 diff --git a/ExcavatorSimulator/Content/Objects/Key.uasset b/ExcavatorSimulator/Content/Objects/Key.uasset index b759c93..dfc04dc 100644 --- a/ExcavatorSimulator/Content/Objects/Key.uasset +++ b/ExcavatorSimulator/Content/Objects/Key.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c56994be2b32c70efb85110a34a1a77bd20b2de0b57bd41d1ce70aa992e3cf9 -size 179344 +oid sha256:e4f93e68646c598698a8be7d235a22dd663259d1a440fd309efb428c287ab30c +size 179348 diff --git a/ExcavatorSimulator/Content/Objects/KeyAccessory.uasset b/ExcavatorSimulator/Content/Objects/KeyAccessory.uasset index 0aa664c..008d71a 100644 --- a/ExcavatorSimulator/Content/Objects/KeyAccessory.uasset +++ b/ExcavatorSimulator/Content/Objects/KeyAccessory.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0dee7b9eb5a6b4d2bd17d17fcf7f63511e3e9e81a686be9bc25e2edbab34e3c -size 148082 +oid sha256:e2ad75adadc16bce8428c5a5da7911df645c74798255f329a17fb73137380e29 +size 148496 -- GitLab From 1745a794db71cc05868d7752d807ecd8d2011280 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Fri, 25 Nov 2022 12:04:27 +0200 Subject: [PATCH 075/121] Modified keyactor --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- .../Content/Excavator/ExcavatorParts/BP_KeyActor.uasset | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 239e978..a4f4ed8 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ef4290bd3f69227757f5d1148208933fc6d03d78c0f2a5e36e769f290102ca7 -size 889710 +oid sha256:a0328ca1d91b9d86b9ae5a29baf868a7761845c4689441d95082208816116e3c +size 846009 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 798e838..4ae441d 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc2d9d8f49d4ec4388f495ebefd398399deca360e775862eab4cf50732cf4ada -size 1143649 +oid sha256:811790b3e7c811dfd42ff34e01f84622bf53a4682be1f3dab2c34859f117c169 +size 1165141 diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset index 6e062c4..b31787f 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/BP_KeyActor.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dab4c4da5e58f478c857202b650de8a5e563ea3253c32194b79db5d5a142d29 -size 100317 +oid sha256:33a0f622c193b769ecc56de77043d5f6c2a3cf88bf65582353da6874d47e98c9 +size 98812 -- GitLab From 9de30f31da129fb2bcc932731d872a6759ab3bce Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 14:23:16 +0200 Subject: [PATCH 076/121] Modified Excavator - Added c++ functions to be callable from Blueprints - Added Functions for getting and setting Clamp values - Clamp values are now in propertyes --- .../Content/Blueprints/ABP_Excavator.uasset | 4 +- .../Blueprints/BP_ExcavatorCharacter.uasset | 4 +- .../ExcavatorSimulator/ExcavatorAnim.cpp | 9 +++ .../Source/ExcavatorSimulator/ExcavatorAnim.h | 72 +++++++++++++++---- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 38 +++------- .../ExcavatorSimulator/ExcavatorCharacter.h | 10 +-- 6 files changed, 84 insertions(+), 53 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset index f54f6b9..26a75ba 100644 --- a/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Blueprints/ABP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42f87f03a97593aca2dd250c0586d322e3adfa5bc6df28d1156fd9e9e2e5b3e3 -size 157228 +oid sha256:18f2e6b0727c7df6818abdb058d9433b1bba5a5b832e104533cc60ea847d5c53 +size 131113 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index a4f4ed8..ee8c9da 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0328ca1d91b9d86b9ae5a29baf868a7761845c4689441d95082208816116e3c -size 846009 +oid sha256:3caaa85ad180069c9528f5dc4725a6c062cf33c11dec54016fe07a17c1021670 +size 889057 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp index 557037a..849d2a0 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.cpp @@ -10,6 +10,15 @@ UExcavatorAnim::UExcavatorAnim(const FObjectInitializer& ObjecInitializer) BeamBottomRotation = FRotator(0.0f, 0.0f, 0.0f); BeamTopRotation = FRotator(0.0f, 0.0f, 0.0f); BucketRotation = FRotator(0.0f,0.0f,0.0f); + + BucketRotationMAX = 180.0f; + BucketRotationMIN = 0.1f; + + TopRotationMAX = 100.0f; + TopRotationMIN = -10.0f; + + BottomRotationMAX = 20.0f; + BottomRotationMIN = -60.0f; } FRotator UExcavatorAnim::GetBodyRotation() diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h index b29861e..688bd7f 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h @@ -19,52 +19,100 @@ public: UExcavatorAnim(const FObjectInitializer& ObjecInitializer); /** Getter for BodyRotation. */ - FRotator GetBodyRotation(); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + FRotator GetBodyRotation(); /** Setter for BodyRotation * @Param rotator sets new rotation to BodyRotation */ - void SetBodyRotation(FRotator rotator); + UFUNCTION(BlueprintCallable, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + void SetBodyRotation(FRotator rotator); /** Getter for BeamTopRotation. */ - FRotator GetBeamTopRotation(); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + FRotator GetBeamTopRotation(); /** Setter for BeamTopRotation * @Param rotator sets new rotation to BeamTopRotation */ - void SetBeamTopRotation(FRotator rotator); + UFUNCTION(BlueprintCallable, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + void SetBeamTopRotation(FRotator rotator); /** Getter for BeamBottomRotation. */ - FRotator GetBeamBottomRotation(); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + FRotator GetBeamBottomRotation(); /** Setter for BeamBottomRotation * @Param rotator sets new rotation to BeamBottomRotator */ - void SetBeamBottomRotation(FRotator rotator); + UFUNCTION(BlueprintCallable, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + void SetBeamBottomRotation(FRotator rotator); /** Getter for BucketRotation. */ - FRotator GetBucketRotation(); + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + FRotator GetBucketRotation(); /** Setter for BucketRotation * @Param rotator sets new rotation to BucketRotation */ - void SetBucketRotation(FRotator rotator); + UFUNCTION(BlueprintCallable, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) + void SetBucketRotation(FRotator rotator); + + float GetBucketMaxRotation() + { + return BucketRotationMAX; + } + float GetBucketMinRotation() + { + return BucketRotationMIN; + } + + float GetTopMaxRotation() + { + return TopRotationMAX; + } + float GetTopMinRotation() + { + return TopRotationMIN; + } + + float GetBottomMaxRotation() + { + return BottomRotationMAX; + } + float GetBottomMinRotation() + { + return BottomRotationMIN; + } private: /** Rotator to Excavators body. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true")) FRotator BodyRotation; /** Rotator to BeamTop. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true")) FRotator BeamTopRotation; /** Rotator to BeamBottom. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true")) FRotator BeamBottomRotation; /** Rotator to Bucket. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "rotations", meta = (AllowPrivateAccess = "true")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true")) FRotator BucketRotation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) + float BucketRotationMIN; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) + float BucketRotationMAX; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) + float TopRotationMIN; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) + float TopRotationMAX; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) + float BottomRotationMIN; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) + float BottomRotationMAX; }; diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index d570ea1..72b1e7c 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -21,7 +21,7 @@ AExcavatorCharacter::AExcavatorCharacter() BodyRotationMultiplier = 1.0f; VehicleRotationMultiplier = 1.0f; - DetachRocksAngle = 110.0f; + DetachRocksAngle = 138.0f; ExcavatorAnimREF = nullptr; } @@ -47,14 +47,13 @@ void AExcavatorCharacter::Movement(float Direction) } } +// @BUG BucketRotation can go up/down for ever!! bool AExcavatorCharacter::BucketRotationReleaceRocks(float RotationDirection) { float tempBucketRot = RotationDirection * BucketRotationMultiplier + ExcavatorAnimREF->GetBucketRotation().Yaw; + tempBucketRot = UKismetMathLibrary::FClamp(tempBucketRot, ExcavatorAnimREF->GetBucketMinRotation(), ExcavatorAnimREF->GetBucketMaxRotation()); ExcavatorAnimREF->SetBucketRotation(FRotator(0.0f, tempBucketRot, 0.0f)); - //if (tempBucketRot >= DetachRocksAngle) - //{ - // DetachFromBucket(); - //} + //UE_LOG(LogTemp, Warning, TEXT("BucketRotation angle: (%f)"), tempBucketRot); return (tempBucketRot >= DetachRocksAngle) ? true : false; } @@ -64,8 +63,11 @@ void AExcavatorCharacter::BeamTopRotation(float RotationDirection) if (collisionComponent->IsCollidingFB(movingForward)) { float tempTopRot = RotationDirection * BeamTopRotationMultiplier + ExcavatorAnimREF->GetBeamTopRotation().Yaw; + tempTopRot = UKismetMathLibrary::FClamp(tempTopRot, ExcavatorAnimREF->GetTopMinRotation(), ExcavatorAnimREF->GetTopMaxRotation()); ExcavatorAnimREF->SetBeamTopRotation(FRotator(0.0f, tempTopRot, 0.0f)); + UE_LOG(LogTemp, Warning, TEXT("TopRotation angle: (%f)"), tempTopRot); } + } void AExcavatorCharacter::BeamBottomRotation(float RotationDirection) @@ -74,6 +76,7 @@ void AExcavatorCharacter::BeamBottomRotation(float RotationDirection) if (collisionComponent->IsCollidingFB(movingForward)) { float tempBottomRot = RotationDirection * BeamBottomRotationMultiplier + ExcavatorAnimREF->GetBeamBottomRotation().Yaw; + tempBottomRot = UKismetMathLibrary::FClamp(tempBottomRot, ExcavatorAnimREF->GetBottomMinRotation(), ExcavatorAnimREF->GetBottomMaxRotation()); ExcavatorAnimREF->SetBeamBottomRotation(FRotator(0.0f, tempBottomRot, 0.0f)); } } @@ -117,28 +120,3 @@ void AExcavatorCharacter::BeginPlay() Super::BeginPlay(); ExcavatorAnimREF = Cast<UExcavatorAnim>(mesh->GetAnimInstance()); } - -void AExcavatorCharacter::DetachFromBucket() -{ - /*APickableRock* RockREF; - RockREF = Cast<APickableRock>(UGameplayStatics::GetActorOfClass(GetWorld(), APickableRock::StaticClass())); - TArray<FString> RockNameArray; - if (RockREF) - { - TArray<AActor*> test; - UGameplayStatics::GetAllActorsOfClass(GetWorld(), RockREF->StaticClass(), test); - - for (AActor* rock : test) - { - rock = Cast<APickableRock>(UGameplayStatics::GetActorOfClass(GetWorld(), APickableRock::StaticClass())); - FString currentName = rock->GetName(); - for (FString rockName : RockNameArray) - { - if (rockName == currentName) - { - rock->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - } - } - }*/ -} diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 0f528f6..116bfde 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -67,14 +67,10 @@ private: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float DetachRocksAngle; - //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Excavator REF's", meta = (AllowPrivateAccess = "true")) - // AActor* RockREF; + UExcavatorAnim* ExcavatorAnimREF; - class UExcavatorAnim* ExcavatorAnimREF; - - class UCheckCollisionComponent* collisionComponent; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision variables", meta = (AllowPrivateAccess = "true")) + UCheckCollisionComponent* collisionComponent; USkeletalMeshComponent* mesh; - - void DetachFromBucket(); }; -- GitLab From ab272b61cafc4df0887f05f410c97b035ae84c30 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 14:26:08 +0200 Subject: [PATCH 077/121] Added BP_PhysicksObject - objec that will be ignored by collisions --- ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset b/ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset new file mode 100644 index 0000000..75a7d31 --- /dev/null +++ b/ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e048e7264fec95a4c1f91cfaac4427f35d1f2b29f99237f1aa1db86c43f42a38 +size 26811 -- GitLab From 4c33b55c63a8a2390b80eaad285b654629136717 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 14:26:42 +0200 Subject: [PATCH 078/121] Added Tags to objects --- ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset | 4 ++-- .../Content/Blueprints/BP_RockStartPoint.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_rock.uasset | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset b/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset index c7d0750..1b9f556 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8c0869b6181b365c7cb0cd4de423538392c756a89582b730d599c9a70868bfb7 -size 190715 +oid sha256:9eea45e6ee3f8e52a34690b874f33af278341c85782a66ae296c34bb8fa127b0 +size 187263 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset b/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset index 03aea72..121db8f 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_RockStartPoint.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01f9b34a88f9cd0ed3ef6cd4989bf6c8d2ddb8431d6fc9884f83f4e03167759d -size 218124 +oid sha256:27c20cee70dc418badb3696d6a505c8b2bd68f0a3c185d0832f501a336c849c7 +size 216623 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index a17dff6..745d803 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69a2007bbcb87b741283b0b3cdf2ed10bccfffc599c2b7581541d5137797a910 -size 122525 +oid sha256:bf623d231b5b98de6a17aa532c217de1ee547cd97bb9a639969795e3e2656514 +size 124102 -- GitLab From 8c0da611c94e970b85b56899483feeceec43e762 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 14:27:00 +0200 Subject: [PATCH 079/121] modified Skeleton_Excavator and TestLevel_Tero - Added sockets to Skeleton - Added test stuff to level --- .../ExcavatorParts/Skeletons/Skeleton_Excavator.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset index 7075c66..2a87fa3 100644 --- a/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset +++ b/ExcavatorSimulator/Content/Excavator/ExcavatorParts/Skeletons/Skeleton_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7740d685808e5b284ca37a4e221a8091958af9fd681b7f3a22c2a9040fafcd0b -size 13650 +oid sha256:fe5eff12f009c81e5d7f598a045cb048f7a39bbff083bd6ef2ad918b60fa4907 +size 14319 diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap index 78af2ab..5ab9751 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1078dde4aaeb6238e0d9a857d2440b77852f097b64fa23af3be479705073f19c -size 62462 +oid sha256:27018d41711aa157a101f65471b3af55ee095d1ed74b4de4c18de966b15d9ad1 +size 63081 -- GitLab From 7485842502a3e97b7c6da6adce33e16088635474 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 14:27:44 +0200 Subject: [PATCH 080/121] Removed Body and track collision for now --- .../CheckCollisionComponent.cpp | 91 ++++++++----------- .../CheckCollisionComponent.h | 18 ++-- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index d3dce5a..e0bc6a6 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -12,30 +12,19 @@ UCheckCollisionComponent::UCheckCollisionComponent() // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features // off to improve performance if you don't need them. PrimaryComponentTick.bCanEverTick = true; - - CollisionMeshBody = CreateDefaultSubobject<UBoxComponent>(FName("Body Collision")); - FVector boxSize(380.0f, 200.0f, 100.0f); - CollisionMeshBody->SetBoxExtent(boxSize); - CollisionMeshBody->bHiddenInGame = false; - CollisionMeshBody->CanCharacterStepUpOn = ECB_No; - CollisionMeshBody->BodyInstance.SetObjectType(ECC_Pawn); - CollisionMeshBody->BodyInstance.SetCollisionProfileName("Pawn"); - - CollisionMeshTrack = CreateDefaultSubobject<UBoxComponent>(FName("Track Collision")); - boxSize = FVector(320.0f, 250.0f, 90.0f); - CollisionMeshTrack->SetBoxExtent(boxSize); - CollisionMeshTrack->bHiddenInGame = false; - CollisionMeshTrack->CanCharacterStepUpOn = ECB_No; - CollisionMeshTrack->BodyInstance.SetObjectType(ECC_Pawn); - CollisionMeshTrack->BodyInstance.SetCollisionProfileName("Pawn"); BucketCollidingFromDown = false; BucketCollidingFromFront = false; BucketCollidingFromLeft = false; BucketCollidingFromRight = false; - character = nullptr; + CharacterREF = nullptr; mesh = nullptr; VoxelWorldREF = nullptr; + + //these values are multiplied with -1 so you can but them positive + CollisionLengthDown = 160.0f; + CollisionLengthLeftAndRight = 160.0f; + CollisionLengthFront = 220.0f; } // Called when the game starts @@ -43,10 +32,25 @@ void UCheckCollisionComponent::BeginPlay() { Super::BeginPlay(); - character = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); - mesh = character->GetMesh(); + CharacterREF = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); + mesh = CharacterREF->GetMesh(); VoxelWorldREF = Cast<AVoxelWorld>(UGameplayStatics::GetActorOfClass(GetWorld(), AVoxelWorld::StaticClass())); + + FName tag = "CanCollide"; + UGameplayStatics::GetAllActorsWithTag(GetWorld(), tag, PhysicksObjects); + if (PhysicksObjects.IsEmpty()) + { + UE_LOG(LogTemp, Warning, TEXT("No objects found with tag: CanCollide")); + } + + for (AActor* a : PhysicksObjects) + { + FString test = a->GetName(); + std::string test2 = std::string(TCHAR_TO_UTF8(*test)); + UE_LOG(LogTemp, Warning, TEXT("Name: %s"), *FString(test2.c_str())); + } + UE_LOG(LogTemp, Warning, TEXT("Objects %d"), PhysicksObjects.Num()); } // Called every frame @@ -54,7 +58,6 @@ void UCheckCollisionComponent::TickComponent(float DeltaTime, ELevelTick TickTyp { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); CollisionCheck(); - CheckCollisionsBody(); } // oikealle rot = -1 @@ -79,13 +82,6 @@ bool UCheckCollisionComponent::IsCollidingLR(float rotationDirection) return true; } -void UCheckCollisionComponent::CheckCollisionsBody() -{ - CollisionMeshBody->SetWorldLocationAndRotation(FVector(mesh->GetSocketLocation("bone_bodySocketMiddle").X, mesh->GetSocketLocation("bone_bodySocketMiddle").Y, mesh->GetSocketLocation("bone_bodySocketMiddle").Z + 120.0), FQuat({ 0.0, mesh->GetSocketRotation("bone_bodySocketMiddle").Yaw, 0.0 })); - CollisionMeshTrack->SetWorldLocationAndRotation(mesh->GetSocketLocation("root_bone_tracksSocket"), FQuat({ 0.0, mesh->GetSocketRotation("root_bone_tracksSocket").Yaw, 0.0 })); - //UE_LOG(LogTemp, Warning, TEXT("Body loc is: x(%f), y(%f), z(%f) "), mesh->GetSocketLocation("bone_bodySocketMiddle").X, mesh->GetSocketLocation("bone_bodySocketMiddle").Y, mesh->GetSocketLocation("bone_bodySocketMiddle").Z); -} - bool UCheckCollisionComponent::IsCollidingFB(float movingDirection) { if (movingDirection == 1.0f && BucketCollidingFromFront) @@ -106,54 +102,43 @@ bool UCheckCollisionComponent::IsCollidingFB(float movingDirection) void UCheckCollisionComponent::CollisionCheck() { FHitResult OutHit = FHitResult(ForceInit); - FCollisionQueryParams collisionParams2; + // Forward/Down IgnoredActors FCollisionQueryParams collisionParams; - collisionParams.AddIgnoredActor(character); - collisionParams2.AddIgnoredActor(character); + collisionParams.AddIgnoredActor(CharacterREF); + collisionParams.AddIgnoredActors(PhysicksObjects); + + // Left/Right IgnoredActors + FCollisionQueryParams collisionParams2; + collisionParams2.AddIgnoredActor(CharacterREF); + collisionParams2.AddIgnoredActors(PhysicksObjects); collisionParams2.AddIgnoredActor(VoxelWorldREF); + FVector socketLocation = mesh->GetSocketLocation("socket_bucketMiddle"); //UE_LOG(LogTemp, Warning, TEXT("Socket: right is: x(%f), y(%f), z(%f) vs x(%f), y(%f), z(%f) mesh right:"), testLoc.RightVector.X, testLoc.RightVector.Y, testLoc.RightVector.Z, mesh->GetRightVector().X, mesh->GetRightVector().Y, mesh->GetRightVector().Z); // Line trace down - float lengthDown = -160.0f; - FVector lineDown = mesh->GetUpVector() * lengthDown; + FVector lineDown = mesh->GetUpVector() * (CollisionLengthDown * -1); FVector endDown = socketLocation + lineDown; BucketCollidingFromDown = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endDown, ECC_Visibility, collisionParams2); - DrawDebugLine(GetWorld(), socketLocation, endDown, FColor::Red); + DrawDebugLine(GetWorld(), socketLocation, endDown, FColor::Purple); // Line trace forward FVector MeshForwardVector = mesh->GetForwardVector(); - float lengthForward = -220.0f; - FVector lineFront = MeshForwardVector * lengthForward; + FVector lineFront = MeshForwardVector * (CollisionLengthFront * -1); FVector endForward = socketLocation + lineFront; BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endForward, ECC_Visibility, collisionParams2); DrawDebugLine(GetWorld(), socketLocation, endForward, FColor::Red); - // Line trace backward - /* FVector lineBack = MeshForwardVector * lengthForward * (-1); - FVector endBack = socketLocation + lineBack; - BucketCollidingFromFront = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endBack, ECC_Visibility, collisionParams); - DrawDebugLine(GetWorld(), socketLocation, endBack, FColor::Red);*/ - // Line trace Left FVector MeshRightVector = mesh->GetRightVector(); - FVector lineLeft = MeshRightVector * lengthDown * (-1); + FVector lineLeft = MeshRightVector * CollisionLengthLeftAndRight; FVector endLeft = socketLocation + lineLeft; BucketCollidingFromLeft = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endLeft, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endLeft, FColor::Green); // Line trace Right - FVector lineRight = MeshRightVector * lengthDown; + FVector lineRight = MeshRightVector * CollisionLengthLeftAndRight * -1; FVector endRight = socketLocation + lineRight; BucketCollidingFromRight = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endRight, ECC_Visibility, collisionParams); - DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Red); - - if (BucketCollidingFromDown) - UE_LOG(LogTemp, Warning, TEXT("Colliding from DOWN!")); - if (BucketCollidingFromFront) - UE_LOG(LogTemp, Warning, TEXT("Colliding from FRONT!")); - if (BucketCollidingFromLeft) - UE_LOG(LogTemp, Warning, TEXT("Colliding from LEFT!")); - if (BucketCollidingFromRight) - UE_LOG(LogTemp, Warning, TEXT("Colliding from RIGHT!")); + DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Blue); } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index a058848..b0b6e60 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -7,6 +7,7 @@ #include <Components/BoxComponent.h> #include "ExcavatorAnim.h" #include "VoxelWorld.h" +#include "PickableRock.h" #include "CheckCollisionComponent.generated.h" UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) @@ -27,8 +28,6 @@ public: // Called every frame virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; - void CheckCollisionsBody(); - protected: // Called when the game starts virtual void BeginPlay() override; @@ -46,11 +45,16 @@ private: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) bool BucketCollidingFromRight; - USkeletalMeshComponent* mesh; - ACharacter* character; - - UBoxComponent* CollisionMeshBody; - UBoxComponent* CollisionMeshTrack; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true")) + float CollisionLengthDown; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true")) + float CollisionLengthLeftAndRight; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true")) + float CollisionLengthFront; + USkeletalMeshComponent* mesh; + ACharacter* CharacterREF; AVoxelWorld* VoxelWorldREF; + + TArray<AActor*, FDefaultAllocator> PhysicksObjects; }; -- GitLab From ae4bae09a54ad322b54587162dd2303c6dd92856 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Fri, 25 Nov 2022 15:03:14 +0200 Subject: [PATCH 081/121] Add level_a2 --- ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap | 2 +- ExcavatorSimulator/Content/Levels/level_a2.umap | 3 +++ ExcavatorSimulator/Content/level_a2_Voxel_save.uasset | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 ExcavatorSimulator/Content/Levels/level_a2.umap create mode 100644 ExcavatorSimulator/Content/level_a2_Voxel_save.uasset diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap index 5f6c09c..04b53af 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Niko.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fe4052b0eafcacb75a2f63de006d83c6c1c5fa082f328cea26534cd8d41e195 +oid sha256:2769f33edcc2b2494b0311e0ac2eeb0c179be52911dcda07133303658f2f74db size 33247 diff --git a/ExcavatorSimulator/Content/Levels/level_a2.umap b/ExcavatorSimulator/Content/Levels/level_a2.umap new file mode 100644 index 0000000..687f4f6 --- /dev/null +++ b/ExcavatorSimulator/Content/Levels/level_a2.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b346d51756dc1dc1c47a18ab573c0b9764b58cf61f76331231fd086ab077823d +size 56284 diff --git a/ExcavatorSimulator/Content/level_a2_Voxel_save.uasset b/ExcavatorSimulator/Content/level_a2_Voxel_save.uasset new file mode 100644 index 0000000..2144d3c --- /dev/null +++ b/ExcavatorSimulator/Content/level_a2_Voxel_save.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67a2d55991deb7d63f841b63fdd7025b9ad94073734e461fbf72886a2e377536 +size 9011773 -- GitLab From ef191ea95fa3dcf97b79c2a496d30d5075544847 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 15:47:46 +0200 Subject: [PATCH 082/121] Modified New Excavator --- ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset index 7cf0a6d..da136fe 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c67b1b04a516b85feaa25ca01ae8e5d4eb5040beedb150b5217e9410b956e52 -size 53423 +oid sha256:3c71ce0b503468546d5ab95ab7badd2c276948f6d960260ce523da2388e2c55d +size 32128 -- GitLab From c7bb79d12b93056dca06577c1b5eaa50b662d246 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 15:48:08 +0200 Subject: [PATCH 083/121] Modified BP_PhysicksObject - Added rock to simulate physics --- .../Content/Blueprints/BP_PhysicksObject.uasset | 2 +- ExcavatorSimulator/Content/Blueprints/BP_rock.uasset | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset b/ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset index 75a7d31..154667b 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_PhysicksObject.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e048e7264fec95a4c1f91cfaac4427f35d1f2b29f99237f1aa1db86c43f42a38 +oid sha256:e5110c8bba8f1bfac79e7b835d984453c444372694780cc1f816937563721b63 size 26811 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index 745d803..c4a7fc6 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf623d231b5b98de6a17aa532c217de1ee547cd97bb9a639969795e3e2656514 -size 124102 +oid sha256:00b45c3335e0d013addacf36d74b299756edcf64722e9adf29a57fa780a438ab +size 133128 -- GitLab From 28c4a35d2db1464df3977f4526581c26a0063eb2 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 15:48:48 +0200 Subject: [PATCH 084/121] Modified CheckCollisionComponent - added more line traces --- .../CheckCollisionComponent.cpp | 28 +++++++++++++++++-- .../CheckCollisionComponent.h | 1 + .../ExcavatorSimulator/ExcavatorCharacter.h | 1 - 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index e0bc6a6..2e164a0 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -44,13 +44,15 @@ void UCheckCollisionComponent::BeginPlay() UE_LOG(LogTemp, Warning, TEXT("No objects found with tag: CanCollide")); } - for (AActor* a : PhysicksObjects) + /* for (AActor* a : PhysicksObjects) { FString test = a->GetName(); std::string test2 = std::string(TCHAR_TO_UTF8(*test)); UE_LOG(LogTemp, Warning, TEXT("Name: %s"), *FString(test2.c_str())); } - UE_LOG(LogTemp, Warning, TEXT("Objects %d"), PhysicksObjects.Num()); + UE_LOG(LogTemp, Warning, TEXT("Objects %d"), PhysicksObjects.Num());*/ + + ExcavatorAnimREF = Cast<UExcavatorAnim>(mesh->GetAnimInstance()); } // Called every frame @@ -141,4 +143,26 @@ void UCheckCollisionComponent::CollisionCheck() FVector endRight = socketLocation + lineRight; BucketCollidingFromRight = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endRight, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Blue); + + // @TODO: Figure out how to calculate line from the cabbin front + + FRotator ts = ExcavatorAnimREF->GetBodyRotation(); + FVector a = FVector(ts.Roll, ts.Pitch, ts.Yaw); + FVector b = mesh->GetForwardVector(); + UE_LOG(LogTemp, Warning, TEXT("Body rot: x(%f), y(%f), z(%f)"), a.X, a.Y, a.Z); + UE_LOG(LogTemp, Warning, TEXT("Mesh Forward: x(%f), y(%f), z(%f)"), b.X, b.Y, b.Z); + + // Position half-way between a and b: + FVector cL = (a + b) / 2.0f; + UE_LOG(LogTemp, Warning, TEXT("cL: x(%f), y(%f), z(%f)"), cL.X, cL.Y, cL.Z); + + // Rotated half-way between a and b: + FVector aPlusb = a + b; + FVector3d cR = aPlusb.GetSafeNormal(); + UE_LOG(LogTemp, Warning, TEXT("cR: x(%f), y(%f), z(%f)"), cR.X, cR.Y, cR.Z); + + FVector lineSize = cR * (150 * -1); + FVector endTest = socketLocation + lineSize; + GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endTest, ECC_Visibility, collisionParams); + DrawDebugLine(GetWorld(), socketLocation, endTest, FColor::Yellow); } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index b0b6e60..5ba246d 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -55,6 +55,7 @@ private: USkeletalMeshComponent* mesh; ACharacter* CharacterREF; AVoxelWorld* VoxelWorldREF; + UExcavatorAnim* ExcavatorAnimREF; TArray<AActor*, FDefaultAllocator> PhysicksObjects; }; diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 116bfde..14877a3 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -42,7 +42,6 @@ public: UFUNCTION(BlueprintCallable) void VehicleRotation(float RotationDirection); - protected: /** Called when the game starts or when spawned. */ -- GitLab From 73044a0535e603c32eac6a390cab924e8fd12d1f Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 15:49:25 +0200 Subject: [PATCH 085/121] Removed AnimBP_Tracks --- ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset diff --git a/ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset b/ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset deleted file mode 100644 index cf01875..0000000 --- a/ExcavatorSimulator/Content/Excavator/AnimBP_Tracks.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dbba73a471cbee8f02ba28ac9154439fecf82cf66cc8685eaf466c61188bc633 -size 52448 -- GitLab From d00defeeb08e3cc4079280409aeb91e3e12e61cc Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Fri, 25 Nov 2022 15:49:43 +0200 Subject: [PATCH 086/121] Modified TestLevel_Tero --- ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap index 5ab9751..8970a30 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27018d41711aa157a101f65471b3af55ee095d1ed74b4de4c18de966b15d9ad1 -size 63081 +oid sha256:3b5e5d491d9c5dc4b5af19b3d91e5b5f594d15a8c1ebb62a22a1e4127c9788b6 +size 63143 -- GitLab From f96a8b36e321f8c09adb3c3e90996c99dc154df8 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Fri, 25 Nov 2022 15:54:57 +0200 Subject: [PATCH 087/121] Added first tutorial screen --- .../Content/Blueprints/BP_VRController.uasset | 4 ++-- .../Content/Levels/Level_Mainmenu.umap | 4 ++-- .../Content/Materials/M_Translucent_Text.uasset | 3 +++ .../Materials/M_Translucent_Text_Inst.uasset | 3 +++ .../Materials/Pictures/Oculus Left Joystick.jpg | Bin 0 -> 79461 bytes .../Pictures/Oculus Right Joystick.jpg | Bin 0 -> 81452 bytes .../Pictures/Oculus_Left_Joystick.uasset | 3 +++ .../Pictures/Oculus_Right_Joystick.uasset | 3 +++ .../Content/UI/BPT_Tutorial1st.uasset | 3 +++ .../Content/UI/BPT_Tutorial2nd_Child.uasset | 3 +++ 10 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 ExcavatorSimulator/Content/Materials/M_Translucent_Text.uasset create mode 100644 ExcavatorSimulator/Content/Materials/M_Translucent_Text_Inst.uasset create mode 100644 ExcavatorSimulator/Content/Materials/Pictures/Oculus Left Joystick.jpg create mode 100644 ExcavatorSimulator/Content/Materials/Pictures/Oculus Right Joystick.jpg create mode 100644 ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset create mode 100644 ExcavatorSimulator/Content/Materials/Pictures/Oculus_Right_Joystick.uasset create mode 100644 ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset create mode 100644 ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 4ae441d..ed956ef 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:811790b3e7c811dfd42ff34e01f84622bf53a4682be1f3dab2c34859f117c169 -size 1165141 +oid sha256:903d889bf1c6113f15776c9369dcdca5adb17c8bba9ad75c41cba6cd82bb726d +size 1177767 diff --git a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap index 70f7f0b..4610a61 100644 --- a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap +++ b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d6bcd17d4368fb15fbcb9073cf362337bc34e3920402df19ded1b2212aeac91 -size 74263 +oid sha256:0f99c6dd0c94314f9beaedc53b4f58de3e25b1a50f584e5db5607cb9db50cd85 +size 89100 diff --git a/ExcavatorSimulator/Content/Materials/M_Translucent_Text.uasset b/ExcavatorSimulator/Content/Materials/M_Translucent_Text.uasset new file mode 100644 index 0000000..c3e0e18 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/M_Translucent_Text.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c488b2fd0313491c8a26d605ce4520ae04016295527aa00b94ea3e1069c0e18 +size 24187 diff --git a/ExcavatorSimulator/Content/Materials/M_Translucent_Text_Inst.uasset b/ExcavatorSimulator/Content/Materials/M_Translucent_Text_Inst.uasset new file mode 100644 index 0000000..a070d7a --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/M_Translucent_Text_Inst.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14d10b79941e7e4138a59cd4df9fdb7a54a4b5609fe1445e33dba219d4aa37ac +size 21969 diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus Left Joystick.jpg b/ExcavatorSimulator/Content/Materials/Pictures/Oculus Left Joystick.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b3a6c7dd4794b9f565f06d34614d8245672528e6 GIT binary patch literal 79461 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	X`ly{$o3L^Qvh*<1f@Et5Ci5oeL<B9POob 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 KYCO9C-vj`6dI5F- literal 0 HcmV?d00001 diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus Right Joystick.jpg b/ExcavatorSimulator/Content/Materials/Pictures/Oculus Right Joystick.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d945a64eb5672108d46430d0966d3f7f049b44e GIT binary patch literal 81452 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 literal 0 HcmV?d00001 diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset new file mode 100644 index 0000000..607e3ce --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d3057e81fe6f8d4332c858118b27e2f7c50bd8e0bc7036b319d25ab85bbe2f5 +size 90700 diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Right_Joystick.uasset b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Right_Joystick.uasset new file mode 100644 index 0000000..82b1437 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Right_Joystick.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec37e960823357694783e6eec74806c6506a0e4b46e4d1dd0c586a50dc2089ac +size 92863 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset new file mode 100644 index 0000000..32b99e2 --- /dev/null +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67acce14f9d11e7e0a891f0607f18c2bb1b76c788e357abbb7c11376f23c0f10 +size 55847 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset new file mode 100644 index 0000000..a52e58e --- /dev/null +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76398f02e0733c7619245847b8c62e2dfde94290a83a950ca824658af78ec088 +size 68910 -- GitLab From 6620698596c8136ca0e11c91381e1e6b073553b0 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Mon, 28 Nov 2022 09:37:08 +0200 Subject: [PATCH 088/121] Modified gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6961da7..119e80e 100644 --- a/.gitignore +++ b/.gitignore @@ -74,4 +74,5 @@ ExcavatorSimulator/Intermediate/* Plugins/*/Intermediate/* # Cache files for the editor to use -DerivedDataCache/* \ No newline at end of file +DerivedDataCache/* +DerivedDataCache/VT/* -- GitLab From 29265df7ea3d9ef1fb261c1530334cb0165db99c Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Mon, 28 Nov 2022 09:38:58 +0200 Subject: [PATCH 089/121] Removed DerivedDataCache --- ...NK02D4C3BCAD7DFA48AB32B8281FB06B8B83DB6275 | Bin 1183780 -> 0 bytes ...NK2A5F9F60441DA258D18B46D59C64D5BAD50E8EE3 | Bin 74020 -> 0 bytes ...NK44B429B40BB45A070EB17C1259C28734996A2F49 | Bin 74020 -> 0 bytes ...NK967C747FFC357A828288FA05D93CD5DCECDA891B | Bin 148004 -> 0 bytes ...NKADD97F8ADF814AE1541D0CBC3FB50485CE10BA12 | Bin 591908 -> 0 bytes 5 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK02D4C3BCAD7DFA48AB32B8281FB06B8B83DB6275 delete mode 100644 ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK2A5F9F60441DA258D18B46D59C64D5BAD50E8EE3 delete mode 100644 ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK44B429B40BB45A070EB17C1259C28734996A2F49 delete mode 100644 ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK967C747FFC357A828288FA05D93CD5DCECDA891B delete mode 100644 ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNKADD97F8ADF814AE1541D0CBC3FB50485CE10BA12 diff --git a/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK02D4C3BCAD7DFA48AB32B8281FB06B8B83DB6275 b/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK02D4C3BCAD7DFA48AB32B8281FB06B8B83DB6275 deleted file mode 100644 index d84ca768711fb0da6fc43b18ba7b82a64705c698..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1183780 zcmeIys||!u5CzZ|LJK$&@EaPy<B`-sf~SJDI1Gw}N{|@jwzB*jEu1kU#@*aGzon_7 za7-UUJ0)EmXZ3eqKgE{MV_FX9>6zHR5|G1P1UbmzOh6CxKo9%@2ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4 zIKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G z-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$ zfCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h0 z0S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 z4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14!j)r184CQ AK>z>% diff --git a/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK2A5F9F60441DA258D18B46D59C64D5BAD50E8EE3 b/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK2A5F9F60441DA258D18B46D59C64D5BAD50E8EE3 deleted file mode 100644 index befafbb6f33f7b9cb5b6b851bda43a9b76f5fb01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74020 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 T00+PUZ~z<t2f%@&#ex3;uffJ? diff --git a/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK44B429B40BB45A070EB17C1259C28734996A2F49 b/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK44B429B40BB45A070EB17C1259C28734996A2F49 deleted file mode 100644 index 5d6b8c303ea48d001a435ca55e117ca2bfb657d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74020 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 I*nJ%M4<rqxO#lD@ diff --git a/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK967C747FFC357A828288FA05D93CD5DCECDA891B b/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNK967C747FFC357A828288FA05D93CD5DCECDA891B deleted file mode 100644 index 1628eecefd9bfbd57ddc2115a43a960a07be10cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148004 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 diff --git a/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNKADD97F8ADF814AE1541D0CBC3FB50485CE10BA12 b/ExcavatorSimulator/DerivedDataCache/VT/TEXTURE_F36E4B807BC24Y818FE085C655C45176_VTCHUNKADD97F8ADF814AE1541D0CBC3FB50485CE10BA12 deleted file mode 100644 index ed8fff6f984d66de89f4607ab01540be4232ddbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591908 zcmeIws||!e00htt3PTYn+Q6JelTZhK(N_TO5)=kU7bp}Kho=HQ_G);Oj2bt2`&56D z@Fa%NWoy~AeX)GU`AqX)wH?DdT`^wj0|ppifB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ z1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;= zV1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~ z0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz z7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|Xg zfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfB^;=V1NMz7+`<_ k1{h#~0R|XgfB^;=V1NMz7+`<_1{h#~0R|XgfPsGlH&3+gH~;_u -- GitLab From 3f86a9f3ab3c570fa7bd7c7716f33daa743ba380 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Mon, 28 Nov 2022 09:40:00 +0200 Subject: [PATCH 090/121] Commented CheckCollisionComponent --- .../CheckCollisionComponent.cpp | 34 ++++--------------- .../CheckCollisionComponent.h | 26 ++++++++++++++ 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index 2e164a0..77a16bf 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -17,9 +17,11 @@ UCheckCollisionComponent::UCheckCollisionComponent() BucketCollidingFromFront = false; BucketCollidingFromLeft = false; BucketCollidingFromRight = false; - CharacterREF = nullptr; - mesh = nullptr; + + mesh = nullptr; + CharacterREF = nullptr; VoxelWorldREF = nullptr; + ExcavatorAnimREF = nullptr; //these values are multiplied with -1 so you can but them positive CollisionLengthDown = 160.0f; @@ -62,8 +64,8 @@ void UCheckCollisionComponent::TickComponent(float DeltaTime, ELevelTick TickTyp CollisionCheck(); } -// oikealle rot = -1 -// vasemmalle rot = 1 +// Right rot = -1 +// Left rot = 1 bool UCheckCollisionComponent::IsCollidingLR(float rotationDirection) { // if we are colliding from left and trying to rotate left we cant @@ -99,8 +101,6 @@ bool UCheckCollisionComponent::IsCollidingFB(float movingDirection) return true; } -// @TODO: Lisää kivet ingoreeen -// @TODO: lisää kaikki physiikka actorit ingnoreen void UCheckCollisionComponent::CollisionCheck() { FHitResult OutHit = FHitResult(ForceInit); @@ -143,26 +143,4 @@ void UCheckCollisionComponent::CollisionCheck() FVector endRight = socketLocation + lineRight; BucketCollidingFromRight = GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endRight, ECC_Visibility, collisionParams); DrawDebugLine(GetWorld(), socketLocation, endRight, FColor::Blue); - - // @TODO: Figure out how to calculate line from the cabbin front - - FRotator ts = ExcavatorAnimREF->GetBodyRotation(); - FVector a = FVector(ts.Roll, ts.Pitch, ts.Yaw); - FVector b = mesh->GetForwardVector(); - UE_LOG(LogTemp, Warning, TEXT("Body rot: x(%f), y(%f), z(%f)"), a.X, a.Y, a.Z); - UE_LOG(LogTemp, Warning, TEXT("Mesh Forward: x(%f), y(%f), z(%f)"), b.X, b.Y, b.Z); - - // Position half-way between a and b: - FVector cL = (a + b) / 2.0f; - UE_LOG(LogTemp, Warning, TEXT("cL: x(%f), y(%f), z(%f)"), cL.X, cL.Y, cL.Z); - - // Rotated half-way between a and b: - FVector aPlusb = a + b; - FVector3d cR = aPlusb.GetSafeNormal(); - UE_LOG(LogTemp, Warning, TEXT("cR: x(%f), y(%f), z(%f)"), cR.X, cR.Y, cR.Z); - - FVector lineSize = cR * (150 * -1); - FVector endTest = socketLocation + lineSize; - GetWorld()->LineTraceSingleByChannel(OutHit, socketLocation, endTest, ECC_Visibility, collisionParams); - DrawDebugLine(GetWorld(), socketLocation, endTest, FColor::Yellow); } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index 5ba246d..5c54582 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -19,10 +19,19 @@ public: // Sets default values for this component's properties UCheckCollisionComponent(); + /** Called every frame Checks collisions left, right, down and front */ void CollisionCheck(); + /** + * Checks collisions left and right + * @Param RotationDirection direction to where body is rotating to + */ bool IsCollidingLR(float rotationDirection); + /** + * Checks collisions forward and down + * @Param movingDirection direction player is moving at + */ bool IsCollidingFB(float movingDirection); // Called every frame @@ -33,29 +42,46 @@ protected: virtual void BeginPlay() override; private: + /** BucketCollidingFromDown collision check down. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) bool BucketCollidingFromDown; + /** BucketCollidingFromFront collision check front. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) bool BucketCollidingFromFront; + /** BucketCollidingFromLeft collision check left. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) bool BucketCollidingFromLeft; + /** BucketCollidingFromRight collision check right. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true")) bool BucketCollidingFromRight; + /** CollisionLengthDown lenght of collision line down. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true")) float CollisionLengthDown; + + /** CollisionLengthLeftAndRight lenght of collision line left and right. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true")) float CollisionLengthLeftAndRight; + + /** CollisionLengthFront lenght of collision line forward. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true")) float CollisionLengthFront; + /** Excavator mesh. */ USkeletalMeshComponent* mesh; + + /** Refrence to Excavator character class. */ ACharacter* CharacterREF; + + /** Refrence to voxelWorld class. */ AVoxelWorld* VoxelWorldREF; + + /** Refrence to excavator anim class. */ UExcavatorAnim* ExcavatorAnimREF; + /** Array of Physicks objects. */ TArray<AActor*, FDefaultAllocator> PhysicksObjects; }; -- GitLab From d2a45c08a9294876658d31ec720251dedf215a8b Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Mon, 28 Nov 2022 09:40:25 +0200 Subject: [PATCH 091/121] Commented ExcavatorCharacter --- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 2 - .../ExcavatorSimulator/ExcavatorCharacter.h | 45 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 72b1e7c..94f75a2 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -47,7 +47,6 @@ void AExcavatorCharacter::Movement(float Direction) } } -// @BUG BucketRotation can go up/down for ever!! bool AExcavatorCharacter::BucketRotationReleaceRocks(float RotationDirection) { float tempBucketRot = RotationDirection * BucketRotationMultiplier + ExcavatorAnimREF->GetBucketRotation().Yaw; @@ -86,7 +85,6 @@ void AExcavatorCharacter::BodyRotation(float RotationDirection) if (collisionComponent->IsCollidingLR(RotationDirection)) { float tempBodyRot = RotationDirection * BodyRotationMultiplier + ExcavatorAnimREF->GetBodyRotation().Pitch; - if (-360.0f >= tempBodyRot || 360.0f <= tempBodyRot) { ExcavatorAnimREF->SetBodyRotation(FRotator(0.0f, 0.0f, 0.0f)); diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index 14877a3..eb47c45 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -25,21 +25,45 @@ public: /** Called to bind functionality to input. */ virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + /** + * Movement, move the player + * @Param direction player is going + */ UFUNCTION(BlueprintCallable) void Movement(float Direction); + /** + * Rotates bucket and will release rocks at angle + * @Param RotationDirection buckets rotation direction + */ UFUNCTION(BlueprintCallable) bool BucketRotationReleaceRocks(float RotationDirection); + /** + * Rotates beam top + * @Param RotationDirection beam top rotation direction + */ UFUNCTION(BlueprintCallable) void BeamTopRotation(float RotationDirection); + /** + * Rotates beam bottom + * @Param RotationDirection beam bottom rotation direction + */ UFUNCTION(BlueprintCallable) void BeamBottomRotation(float RotationDirection); + /** + * Rotates excavator body + * @Param RotationDirection body rotation direction + */ UFUNCTION(BlueprintCallable) void BodyRotation(float RotationDirection); - + + /** + * Rotates whole vehicle + * @Param RotationDirection vehicle rotation direction + */ UFUNCTION(BlueprintCallable) void VehicleRotation(float RotationDirection); protected: @@ -50,26 +74,41 @@ protected: private: UInputSettings* Inputsettings = UInputSettings::GetInputSettings(); + /** AccelerationMultiplier multiplier for movement speed. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float AccelerationMultiplier; + + /** BucketRotationMultiplier multiplier for bucket rotation speed. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float BucketRotationMultiplier; + + /** BeamTopRotationMultiplier multiplier for beam top rotation speed. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float BeamTopRotationMultiplier; + + /** BeamBottomRotationMultiplier multiplier for beam bottom rotation speed. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float BeamBottomRotationMultiplier; + + /** BodyRotationMultiplier multiplier for body rotation speed. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float BodyRotationMultiplier; + + /** VehicleRotationMultiplier multiplier for Vehicle rotation speed. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float VehicleRotationMultiplier; + /** DetachRocksAngle angle to detachRocks at. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float DetachRocksAngle; - UExcavatorAnim* ExcavatorAnimREF; - + /** Refrence to Collision component class. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision variables", meta = (AllowPrivateAccess = "true")) UCheckCollisionComponent* collisionComponent; + /** Refrence to excavator anim class. */ + UExcavatorAnim* ExcavatorAnimREF; + + /** Excavator mesh. */ USkeletalMeshComponent* mesh; }; -- GitLab From 4438899815cf29460130eb3b948585e4f923a1fe Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Mon, 28 Nov 2022 09:40:37 +0200 Subject: [PATCH 092/121] Commented ExcavatorAnim --- .../Source/ExcavatorSimulator/ExcavatorAnim.h | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h index 688bd7f..409a96c 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorAnim.h @@ -58,28 +58,37 @@ public: UFUNCTION(BlueprintCallable, Category = "Rotations", meta = (BlueprintThreadSafe = "true")) void SetBucketRotation(FRotator rotator); + /** Getter for BucketMaxRotation. */ float GetBucketMaxRotation() { return BucketRotationMAX; } + + /** Getter for BucketMinRotation. */ float GetBucketMinRotation() { return BucketRotationMIN; } + /** Getter for TopMaxRotation. */ float GetTopMaxRotation() { return TopRotationMAX; } + + /** Getter for TopMinRotation. */ float GetTopMinRotation() { return TopRotationMIN; } + /** Getter for BottomMaxRotation. */ float GetBottomMaxRotation() { return BottomRotationMAX; } + + /** Getter for BottomMinRotation. */ float GetBottomMinRotation() { return BottomRotationMIN; @@ -103,16 +112,27 @@ private: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true")) FRotator BucketRotation; + /** BucketRotationMIN bucket clamp min value. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) float BucketRotationMIN; + + /** BucketRotationMAX bucket clamp max value. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) float BucketRotationMAX; + + /** TopRotationMIN top clamp min value. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) float TopRotationMIN; + + /** TopRotationMAX top clamp min value. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) float TopRotationMAX; + + /** BottomRotationMIN bottom clamp min value. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) float BottomRotationMIN; + + /** BottomRotationMAX bottom clamp min value. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true")) float BottomRotationMAX; }; -- GitLab From 7ee013f15e21f70a96aa0914378e6f2ce20b8d2a Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Mon, 28 Nov 2022 10:16:03 +0200 Subject: [PATCH 093/121] First tutorial for seating added --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- ExcavatorSimulator/Content/Cinematics/CarMovement.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/FirstLevel.umap | 3 --- ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap | 4 ++-- ExcavatorSimulator/Content/Levels/level_a1.umap | 3 +++ ExcavatorSimulator/Content/Levels/level_tutorial.umap | 3 +++ ExcavatorSimulator/Content/UI/BPI_UI.uasset | 3 +++ ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset | 4 ++-- ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset | 4 ++-- ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset | 4 ++-- 11 files changed, 23 insertions(+), 17 deletions(-) delete mode 100644 ExcavatorSimulator/Content/Levels/FirstLevel.umap create mode 100644 ExcavatorSimulator/Content/Levels/level_a1.umap create mode 100644 ExcavatorSimulator/Content/Levels/level_tutorial.umap create mode 100644 ExcavatorSimulator/Content/UI/BPI_UI.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index ee8c9da..dd62a5e 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3caaa85ad180069c9528f5dc4725a6c062cf33c11dec54016fe07a17c1021670 -size 889057 +oid sha256:12feaf7606e6ea576e55f25b74c2d817d26dbdcdd9c959ba82c4191f49c19907 +size 881799 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index ed956ef..6bb2240 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:903d889bf1c6113f15776c9369dcdca5adb17c8bba9ad75c41cba6cd82bb726d -size 1177767 +oid sha256:55082f167d8c86255f630ad4416ddc2bb0193e5ba4306feb3f01476e568d048b +size 1203412 diff --git a/ExcavatorSimulator/Content/Cinematics/CarMovement.uasset b/ExcavatorSimulator/Content/Cinematics/CarMovement.uasset index fcef652..2100c87 100644 --- a/ExcavatorSimulator/Content/Cinematics/CarMovement.uasset +++ b/ExcavatorSimulator/Content/Cinematics/CarMovement.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8169c36c8a36a94271a7fbbea59194272ab3131c4ea2697d80ec4ad9b9235fe7 -size 38542 +oid sha256:a03ab25994a4ceb3b9ea67070edf1a62765d4ecb2c7096cc9718a2fab9e0aee4 +size 38536 diff --git a/ExcavatorSimulator/Content/Levels/FirstLevel.umap b/ExcavatorSimulator/Content/Levels/FirstLevel.umap deleted file mode 100644 index a5ac92d..0000000 --- a/ExcavatorSimulator/Content/Levels/FirstLevel.umap +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:afdb435dd1ee31cf19b525bf430185d1bb8217f29313d7c7f4589c33382b4724 -size 1979161 diff --git a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap index 4610a61..028786f 100644 --- a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap +++ b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f99c6dd0c94314f9beaedc53b4f58de3e25b1a50f584e5db5607cb9db50cd85 -size 89100 +oid sha256:1063e7f0959d8b1abfe0e875bf23a593f9589852dd70e14acbd99480c49777d9 +size 92615 diff --git a/ExcavatorSimulator/Content/Levels/level_a1.umap b/ExcavatorSimulator/Content/Levels/level_a1.umap new file mode 100644 index 0000000..2060a3d --- /dev/null +++ b/ExcavatorSimulator/Content/Levels/level_a1.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3723cb1facd8c6a6791f94301c5b470fa844fc3b7cb1600778c4b067518d951e +size 1979028 diff --git a/ExcavatorSimulator/Content/Levels/level_tutorial.umap b/ExcavatorSimulator/Content/Levels/level_tutorial.umap new file mode 100644 index 0000000..1a19b18 --- /dev/null +++ b/ExcavatorSimulator/Content/Levels/level_tutorial.umap @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c67cc1c8783b649f0e3b4e422f5e39e7a292a122094677cbf272eefeed58374e +size 43547 diff --git a/ExcavatorSimulator/Content/UI/BPI_UI.uasset b/ExcavatorSimulator/Content/UI/BPI_UI.uasset new file mode 100644 index 0000000..9641e6c --- /dev/null +++ b/ExcavatorSimulator/Content/UI/BPI_UI.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d94da6cdb65a0625665dfcc829fcfcb49d0c631a29bff79105fe9065b7f36c73 +size 11394 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset index 32b99e2..f3f9b21 100644 --- a/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67acce14f9d11e7e0a891f0607f18c2bb1b76c788e357abbb7c11376f23c0f10 -size 55847 +oid sha256:e4ad84b69f1a32fb8d428abaeb9807c6472841292621dd2e4bea8a3a0ed537de +size 55383 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset index a52e58e..8816ef2 100644 --- a/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76398f02e0733c7619245847b8c62e2dfde94290a83a950ca824658af78ec088 -size 68910 +oid sha256:805e1e83b19e3d3a0ea59e3f81ea611b22176d8b46f9e326aed7d11f8b01536a +size 143414 diff --git a/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset b/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset index 25e6051..44a36c3 100644 --- a/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset +++ b/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a86d9433da45c65205c05f8aa3b4584bfc2cf6f89643c1236d4ee5a18511ca0b -size 282378 +oid sha256:9f011fa3aac781ebb64e37f77fbf4b3da6e4d3c18fb6ce353ec114a307c076c8 +size 282005 -- GitLab From 60b302f7663d25bac62a726ff655e14f847ac019 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Mon, 28 Nov 2022 11:46:40 +0200 Subject: [PATCH 094/121] Add slopes to level --- ExcavatorSimulator/Content/Levels/level_a2.umap | 4 ++-- ExcavatorSimulator/Content/level_a2_Voxel_save.uasset | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/level_a2.umap b/ExcavatorSimulator/Content/Levels/level_a2.umap index 687f4f6..a8e19b3 100644 --- a/ExcavatorSimulator/Content/Levels/level_a2.umap +++ b/ExcavatorSimulator/Content/Levels/level_a2.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b346d51756dc1dc1c47a18ab573c0b9764b58cf61f76331231fd086ab077823d -size 56284 +oid sha256:674063a550873f0eab3fb00fdfc168c8905b0c4a1dca0273a988cac11117eb54 +size 61912 diff --git a/ExcavatorSimulator/Content/level_a2_Voxel_save.uasset b/ExcavatorSimulator/Content/level_a2_Voxel_save.uasset index 2144d3c..ed6781e 100644 --- a/ExcavatorSimulator/Content/level_a2_Voxel_save.uasset +++ b/ExcavatorSimulator/Content/level_a2_Voxel_save.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67a2d55991deb7d63f841b63fdd7025b9ad94073734e461fbf72886a2e377536 -size 9011773 +oid sha256:a384cc5f6bda77caed28f82bfd9a4ed896cc74688a7e3816200fc80cfc5c2725 +size 9396588 -- GitLab From 22b1a15512336d02d6d00b852efeaf12707efc96 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Mon, 28 Nov 2022 11:48:18 +0200 Subject: [PATCH 095/121] Modify digging values --- .../GroundDeformerComponent.cpp | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp index 915250b..d87d3e4 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp @@ -17,7 +17,7 @@ UGroundDeformerComponent::UGroundDeformerComponent() PrimaryComponentTick.bCanEverTick = false; RemoveSphereRadius = 100.0f; - ForwardVectorMultiplier = 100.0f; + ForwardVectorMultiplier = 10.0f; } void UGroundDeformerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) @@ -30,37 +30,27 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) FHitResult hit = FHitResult(ForceInit); TArray<FVoxelProjectionHit> voxelHit; FVoxelLineTraceParameters parameters; + FVoxelPaintMaterial PaintMaterial; + //GetComponentLocation() is WorldLocation, GetForwardVctor() is ForwardVector. - FVector EndVector = Mesh->GetComponentLocation() + (Mesh->GetForwardVector() * ForwardVectorMultiplier); + FVector EndVector = Mesh->GetComponentLocation() + (-Mesh->GetRightVector() * ForwardVectorMultiplier); if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) { DrawDebugLine(GetWorld(), Mesh->GetComponentLocation(), EndVector, FColor::Red, false, 5.0f, ECC_WorldStatic, 1.0f); - UVoxelProjectionTools::FindProjectionVoxels(voxelHit, VoxelWorldREF, parameters, hit.Location, Mesh->GetForwardVector(), 100.0f); + UVoxelProjectionTools::FindProjectionVoxels(voxelHit, VoxelWorldREF, parameters, hit.Location, Mesh->GetForwardVector(), 100.0f, EVoxelProjectionShape::Square, 50.0f, 100.0f); + FVector PositionVector = FVector(hit.ImpactPoint.X, hit.ImpactPoint.Y, hit.ImpactPoint.Z); for (FVoxelProjectionHit hits : voxelHit) { FVector Convert2DToVector = FVector(hits.PlanePosition.X, hits.PlanePosition.Y, 0.0f); - UVoxelDataTools::SetValue(VoxelWorldREF, hits.VoxelPosition, 1.0f); + UVoxelDataTools::SetValue(VoxelWorldREF, hits.VoxelPosition, 0.0f); UVoxelSurfaceTools::ApplyFlatten(VoxelWorldREF, Convert2DToVector); + + } + //UVoxelSphereTools::SetMaterialSphere(VoxelWorldREF, PositionVector, 200.0f, PaintMaterial); } - //FVoxelIntBox IntBox = UVoxelBlueprintLibrary::MakeIntBoxFromGlobalPositionAndRadius(VoxelWorldREF, Mesh->GetComponentLocation(), 100.0f); - //UVoxelDebugUtilities::DrawDebugIntBox(VoxelWorldREF, IntBox, IntBox.); - - //UVoxelBoxTools::RemoveBox(VoxelWorldREF, IntBox); - - //FVoxelSurfaceEditsVoxels Voxels; - //UVoxelSurfaceTools::FindSurfaceVoxelsFromDistanceField(Voxels, VoxelWorldREF, IntBox); - - //FVoxelSurfaceEditsStack Stack; - //Stack.Add(UVoxelSurfaceTools::ApplyFalloff(VoxelWorldREF, EVoxelFalloff::Linear, Mesh->GetComponentLocation(), 100.0f, 0.5f)); - //Stack.Add(UVoxelSurfaceTools::ApplyConstantStrength(-0.8f)); - - //FVoxelSurfaceEditsProcessedVoxels PVoxels = Stack.Execute(Voxels); - //UVoxelSurfaceEditTools::EditVoxelValues(VoxelWorldREF, PVoxels); - - //UVoxelSphereTools::SmoothSphere(VoxelWorldREF, Mesh->GetComponentLocation(), 140.0f, 0.5f); } void UGroundDeformerComponent::BeginPlay() -- GitLab From fce3b938b26beaa20c49bd12f104a7c819d399d0 Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Mon, 28 Nov 2022 11:49:18 +0200 Subject: [PATCH 096/121] modified level_a1 --- ExcavatorSimulator/Content/Levels/level_a1.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/level_a1.umap b/ExcavatorSimulator/Content/Levels/level_a1.umap index 2060a3d..93c6552 100644 --- a/ExcavatorSimulator/Content/Levels/level_a1.umap +++ b/ExcavatorSimulator/Content/Levels/level_a1.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3723cb1facd8c6a6791f94301c5b470fa844fc3b7cb1600778c4b067518d951e -size 1979028 +oid sha256:7b043485e6847c3e835b26fb060d1b4e1d446e0be1592489449d4224963f22ff +size 1973623 -- GitLab From ca58656df8b30a5505c665d3b896ff8b62a03b5a Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Mon, 28 Nov 2022 11:50:45 +0200 Subject: [PATCH 097/121] new sounds --- .../Content/Sound/Assets/excavator_dynamic_engine-cue.uasset | 4 ++-- .../Content/Sound/Assets/excavator_moving_cue.uasset | 3 +++ .../Content/Sound/Assets/tracks_moving_Cue.uasset | 3 --- .../Content/Sound/Assets/tracks_moving_slow.uasset | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 ExcavatorSimulator/Content/Sound/Assets/excavator_moving_cue.uasset delete mode 100644 ExcavatorSimulator/Content/Sound/Assets/tracks_moving_Cue.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/tracks_moving_slow.uasset diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset index f275f59..8a9ae6f 100644 --- a/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_dynamic_engine-cue.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cacd16adb43567f2e7b4e6ca5b055552ee671bd06a7afa5e4cb9a8a8eb770136 -size 13033 +oid sha256:04b956d3c2420218271d97e4d8bcc4ce151331ab1fb0eec4c4d6a1fe7958c829 +size 13807 diff --git a/ExcavatorSimulator/Content/Sound/Assets/excavator_moving_cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/excavator_moving_cue.uasset new file mode 100644 index 0000000..f48a0bb --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/excavator_moving_cue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e9a4a09fa3ac2ba8992d0714ea230cd85a1dae5f24866ac788e1655dce5c00c +size 6786 diff --git a/ExcavatorSimulator/Content/Sound/Assets/tracks_moving_Cue.uasset b/ExcavatorSimulator/Content/Sound/Assets/tracks_moving_Cue.uasset deleted file mode 100644 index b344242..0000000 --- a/ExcavatorSimulator/Content/Sound/Assets/tracks_moving_Cue.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d3430b08c97ec0799bae7868fb20c33bdd1eb144657f40429026b54d1cedfcf4 -size 4480 diff --git a/ExcavatorSimulator/Content/Sound/Assets/tracks_moving_slow.uasset b/ExcavatorSimulator/Content/Sound/Assets/tracks_moving_slow.uasset new file mode 100644 index 0000000..a044b14 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/tracks_moving_slow.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fda8a4f021afac2fdf47acbd706c56bedab9c61e082e3f06d9f66502b436e32 +size 7026618 -- GitLab From ccd26d017ffb9b010b35e76cdc3c0e89b41f86c5 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 10:43:51 +0200 Subject: [PATCH 098/121] Commented useless Log print --- .../Source/ExcavatorSimulator/ExcavatorCharacter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 94f75a2..763c735 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -64,7 +64,7 @@ void AExcavatorCharacter::BeamTopRotation(float RotationDirection) float tempTopRot = RotationDirection * BeamTopRotationMultiplier + ExcavatorAnimREF->GetBeamTopRotation().Yaw; tempTopRot = UKismetMathLibrary::FClamp(tempTopRot, ExcavatorAnimREF->GetTopMinRotation(), ExcavatorAnimREF->GetTopMaxRotation()); ExcavatorAnimREF->SetBeamTopRotation(FRotator(0.0f, tempTopRot, 0.0f)); - UE_LOG(LogTemp, Warning, TEXT("TopRotation angle: (%f)"), tempTopRot); + //UE_LOG(LogTemp, Warning, TEXT("TopRotation angle: (%f)"), tempTopRot); } } -- GitLab From f4f740c3cadc72c729cefa91ebe4ac0c3fc2559b Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 10:44:17 +0200 Subject: [PATCH 099/121] Fixed GroundDeformerComponent not digging --- .../Source/ExcavatorSimulator/GroundDeformerComponent.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp index d87d3e4..79728d6 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp @@ -17,7 +17,7 @@ UGroundDeformerComponent::UGroundDeformerComponent() PrimaryComponentTick.bCanEverTick = false; RemoveSphereRadius = 100.0f; - ForwardVectorMultiplier = 10.0f; + ForwardVectorMultiplier = 30.0f; } void UGroundDeformerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) @@ -32,7 +32,6 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) FVoxelLineTraceParameters parameters; FVoxelPaintMaterial PaintMaterial; - //GetComponentLocation() is WorldLocation, GetForwardVctor() is ForwardVector. FVector EndVector = Mesh->GetComponentLocation() + (-Mesh->GetRightVector() * ForwardVectorMultiplier); if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) @@ -43,10 +42,9 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) for (FVoxelProjectionHit hits : voxelHit) { FVector Convert2DToVector = FVector(hits.PlanePosition.X, hits.PlanePosition.Y, 0.0f); - UVoxelDataTools::SetValue(VoxelWorldREF, hits.VoxelPosition, 0.0f); + UVoxelDataTools::SetValue(VoxelWorldREF, hits.VoxelPosition, 1.0f); UVoxelSurfaceTools::ApplyFlatten(VoxelWorldREF, Convert2DToVector); - } //UVoxelSphereTools::SetMaterialSphere(VoxelWorldREF, PositionVector, 200.0f, PaintMaterial); -- GitLab From 2ec2abce1c1698b753eb3ffed752cd033ad610b2 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 29 Nov 2022 11:44:26 +0200 Subject: [PATCH 100/121] Tutorial 1st part done --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap | 4 ++-- ExcavatorSimulator/Content/Levels/level_tutorial.umap | 4 ++-- .../Content/Materials/Pictures/Oculus instructions.png | 3 +++ .../Content/Materials/Pictures/Oculus_instructions.uasset | 3 +++ ExcavatorSimulator/Content/Sound/Assets/Adjust seating.wav | 3 +++ ExcavatorSimulator/Content/Sound/Assets/Adjust_seating.uasset | 3 +++ .../Content/Sound/Assets/Now Lets try out movement.wav | 3 +++ .../Content/Sound/Assets/Now_Lets_try_out_movement.uasset | 3 +++ .../On your right side is the key to turn on excavator.wav | 3 +++ .../On_your_right_side_is_the_key_to_turn_on_excavator.uasset | 3 +++ .../Content/Sound/Assets/Use Left Controller .wav | 3 +++ .../Content/Sound/Assets/Use Right Controller.wav | 3 +++ .../Content/Sound/Assets/Use start the engine.wav | 3 +++ .../Content/Sound/Assets/Use_Left_Controller_.uasset | 3 +++ .../Content/Sound/Assets/Use_Right_Controller.uasset | 3 +++ .../Content/Sound/Assets/Use_start_the_engine.uasset | 3 +++ .../Content/Sound/Assets/Welcome to Excavator.wav | 3 +++ .../Content/Sound/Assets/Welcome_to_Excavator.uasset | 3 +++ ExcavatorSimulator/Content/UI/BPI_UI.uasset | 3 --- ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset | 4 ++-- ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset | 4 ++-- .../Content/UI/BPT_Tutorial3rd_Child_Child.uasset | 3 +++ ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset | 4 ++-- 25 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 ExcavatorSimulator/Content/Materials/Pictures/Oculus instructions.png create mode 100644 ExcavatorSimulator/Content/Materials/Pictures/Oculus_instructions.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Adjust seating.wav create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Adjust_seating.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Now Lets try out movement.wav create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Now_Lets_try_out_movement.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/On your right side is the key to turn on excavator.wav create mode 100644 ExcavatorSimulator/Content/Sound/Assets/On_your_right_side_is_the_key_to_turn_on_excavator.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Use Left Controller .wav create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Use Right Controller.wav create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Use start the engine.wav create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Use_Left_Controller_.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Use_Right_Controller.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Use_start_the_engine.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Welcome to Excavator.wav create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Welcome_to_Excavator.uasset delete mode 100644 ExcavatorSimulator/Content/UI/BPI_UI.uasset create mode 100644 ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index dd62a5e..06ff98b 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12feaf7606e6ea576e55f25b74c2d817d26dbdcdd9c959ba82c4191f49c19907 -size 881799 +oid sha256:d66e19eb38ebc9f906a93447788ebd49ba43c9e4df2359741131a4f6a86a88d2 +size 874386 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 6bb2240..eb021a1 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:55082f167d8c86255f630ad4416ddc2bb0193e5ba4306feb3f01476e568d048b -size 1203412 +oid sha256:bebe52a1d7bbaeabe6e09883f18d1e5566e0540c34abba465a5c2153647b170b +size 1228629 diff --git a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap index 028786f..17e7e3c 100644 --- a/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap +++ b/ExcavatorSimulator/Content/Levels/Level_Mainmenu.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1063e7f0959d8b1abfe0e875bf23a593f9589852dd70e14acbd99480c49777d9 -size 92615 +oid sha256:83f6d5eef33c99ede40ee9eb8693771fabbd353b65d2df576b63a5ee4b04d728 +size 76887 diff --git a/ExcavatorSimulator/Content/Levels/level_tutorial.umap b/ExcavatorSimulator/Content/Levels/level_tutorial.umap index 1a19b18..19f94f3 100644 --- a/ExcavatorSimulator/Content/Levels/level_tutorial.umap +++ b/ExcavatorSimulator/Content/Levels/level_tutorial.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c67cc1c8783b649f0e3b4e422f5e39e7a292a122094677cbf272eefeed58374e -size 43547 +oid sha256:d4aa7a2d03d6cee3db7627d915f59c105c2c7662141a11bb638d5573892de907 +size 61651 diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus instructions.png b/ExcavatorSimulator/Content/Materials/Pictures/Oculus instructions.png new file mode 100644 index 0000000..3e701b2 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/Pictures/Oculus instructions.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b616bb3d00a40b0b63bf27a4b894383a1724be2be21907dcd0008569722dab82 +size 90170 diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus_instructions.uasset b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_instructions.uasset new file mode 100644 index 0000000..1bbd4cb --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_instructions.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0a32b63158e0762cdcf9de29652abee95fcf88d02929bcaf0a9aa6fce867672 +size 94907 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Adjust seating.wav b/ExcavatorSimulator/Content/Sound/Assets/Adjust seating.wav new file mode 100644 index 0000000..afde203 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Adjust seating.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e10f5f940777d7501a29bfbe66e355535f21c0ef2fdea89070e6b0794081c436 +size 280146 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Adjust_seating.uasset b/ExcavatorSimulator/Content/Sound/Assets/Adjust_seating.uasset new file mode 100644 index 0000000..0b35e18 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Adjust_seating.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53d06d76c388f70422afcf2559c80c66caac4897398f125eb57b2ddd61856fc5 +size 288145 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Now Lets try out movement.wav b/ExcavatorSimulator/Content/Sound/Assets/Now Lets try out movement.wav new file mode 100644 index 0000000..d2585c8 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Now Lets try out movement.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfe84515b38b3ce6e198fb10d287659cdc6dcdcd94db12a36aa44cdc828cac57 +size 265244 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Now_Lets_try_out_movement.uasset b/ExcavatorSimulator/Content/Sound/Assets/Now_Lets_try_out_movement.uasset new file mode 100644 index 0000000..f3a2645 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Now_Lets_try_out_movement.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1eaedfa15970af1d32587340154e2ed6a1f5f40638f2b8dfbf1a03accd12f886 +size 271463 diff --git a/ExcavatorSimulator/Content/Sound/Assets/On your right side is the key to turn on excavator.wav b/ExcavatorSimulator/Content/Sound/Assets/On your right side is the key to turn on excavator.wav new file mode 100644 index 0000000..3f0da06 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/On your right side is the key to turn on excavator.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84eefcddbf30821a16fd0842a55073a9dbe3d568c92423b359c7a843a9edc535 +size 681644 diff --git a/ExcavatorSimulator/Content/Sound/Assets/On_your_right_side_is_the_key_to_turn_on_excavator.uasset b/ExcavatorSimulator/Content/Sound/Assets/On_your_right_side_is_the_key_to_turn_on_excavator.uasset new file mode 100644 index 0000000..164a4d4 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/On_your_right_side_is_the_key_to_turn_on_excavator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b4de34034a5c744f2882a9c37fecb5736b775d8a3ba9141234aff238aee18ed +size 689656 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Use Left Controller .wav b/ExcavatorSimulator/Content/Sound/Assets/Use Left Controller .wav new file mode 100644 index 0000000..91b420e --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Use Left Controller .wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d7373a70ad35d5c2409fe48ecc110f89f386ce738d209f4f1e4340e8cf7096d +size 164670 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Use Right Controller.wav b/ExcavatorSimulator/Content/Sound/Assets/Use Right Controller.wav new file mode 100644 index 0000000..48e4803 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Use Right Controller.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:738cb0ebff78c88285d149aa15431344da456f22548375249ef0b2691f9931f0 +size 189328 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Use start the engine.wav b/ExcavatorSimulator/Content/Sound/Assets/Use start the engine.wav new file mode 100644 index 0000000..842a4c3 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Use start the engine.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df318f1caa5707222da451157c3ca4a369d3cca114646867ffe1944d0ed1f57a +size 422444 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Use_Left_Controller_.uasset b/ExcavatorSimulator/Content/Sound/Assets/Use_Left_Controller_.uasset new file mode 100644 index 0000000..13638c3 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Use_Left_Controller_.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9f5e7f3166d5145fcb04dbf2a3baa363dcc4eaabc24de61ba6684e599a33f08 +size 172140 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Use_Right_Controller.uasset b/ExcavatorSimulator/Content/Sound/Assets/Use_Right_Controller.uasset new file mode 100644 index 0000000..9b2cf1f --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Use_Right_Controller.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e5c2ea2791965ead1be7d4c9049d87c24f8f5221950040cff3dc8fabbac327 +size 197219 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Use_start_the_engine.uasset b/ExcavatorSimulator/Content/Sound/Assets/Use_start_the_engine.uasset new file mode 100644 index 0000000..3359042 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Use_start_the_engine.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f667c922309635695961d24d322c4f3df8959e5e808d1367fbad86cfb34a00f +size 429717 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Welcome to Excavator.wav b/ExcavatorSimulator/Content/Sound/Assets/Welcome to Excavator.wav new file mode 100644 index 0000000..0904ebf --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Welcome to Excavator.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc7171ad2ee9be98e4ad3c7a63d1d41bdee91220f44a15d3d68990f655a9aa88 +size 116958 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Welcome_to_Excavator.uasset b/ExcavatorSimulator/Content/Sound/Assets/Welcome_to_Excavator.uasset new file mode 100644 index 0000000..bc7ea94 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Welcome_to_Excavator.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83c892e6b2c7b2581d54fe10af0eb7805149c92bbf400035c9603f897cc254c0 +size 124483 diff --git a/ExcavatorSimulator/Content/UI/BPI_UI.uasset b/ExcavatorSimulator/Content/UI/BPI_UI.uasset deleted file mode 100644 index 9641e6c..0000000 --- a/ExcavatorSimulator/Content/UI/BPI_UI.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d94da6cdb65a0625665dfcc829fcfcb49d0c631a29bff79105fe9065b7f36c73 -size 11394 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset index f3f9b21..930c686 100644 --- a/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial1st.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4ad84b69f1a32fb8d428abaeb9807c6472841292621dd2e4bea8a3a0ed537de -size 55383 +oid sha256:6450d652919c08cf77c63395e1bec4ff2fa873341470c43406dc5c888b348837 +size 52676 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset index 8816ef2..a1fa921 100644 --- a/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial2nd_Child.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:805e1e83b19e3d3a0ea59e3f81ea611b22176d8b46f9e326aed7d11f8b01536a -size 143414 +oid sha256:5f14d08c94e1c5547de0e3b1fd50b3017252d7e81ec2d9e9dbe745a6db29f743 +size 145094 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset new file mode 100644 index 0000000..a00089e --- /dev/null +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56fd198c6f02af3502c9dbeefde232996d82250f159fc580cab95ec2610e01bd +size 112424 diff --git a/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset b/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset index 44a36c3..844fb72 100644 --- a/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset +++ b/ExcavatorSimulator/Content/UI/BPW_MainMenu.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f011fa3aac781ebb64e37f77fbf4b3da6e4d3c18fb6ce353ec114a307c076c8 -size 282005 +oid sha256:fd94f647723892d3972978fe7e3f62bc48de6c2e4dfc62b319dce877d406f085 +size 285727 -- GitLab From f8e352a387176684e18114ada12811e64a4ce28c Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 13:04:22 +0200 Subject: [PATCH 101/121] Modified ExcavatorCharacter - Changed consturctor values to be smaller - Removed old animation ref from Blueprints - Added Ground Deform component to c++ --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 13 +++++++------ .../Source/ExcavatorSimulator/ExcavatorCharacter.h | 6 ++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index 06ff98b..a7d7e61 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d66e19eb38ebc9f906a93447788ebd49ba43c9e4df2359741131a4f6a86a88d2 -size 874386 +oid sha256:258a1571b5170d54c260aaa0b255dc017cfb4ff077a4737f82c5212ffcaf4572 +size 846183 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 763c735..1907b19 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -11,15 +11,16 @@ AExcavatorCharacter::AExcavatorCharacter() // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; collisionComponent = CreateDefaultSubobject<UCheckCollisionComponent>(TEXT("Collision Component")); + groundDeformComponent = CreateDefaultSubobject<UGroundDeformerComponent>(TEXT("Ground Deform Component")); mesh = GetMesh(); - AccelerationMultiplier = 1.1f; + AccelerationMultiplier = 0.5f; - BucketRotationMultiplier = 1.0f; - BeamTopRotationMultiplier = 1.0f; - BeamBottomRotationMultiplier = 1.0f; - BodyRotationMultiplier = 1.0f; - VehicleRotationMultiplier = 1.0f; + BucketRotationMultiplier = 0.25f; + BeamTopRotationMultiplier = 0.5f; + BeamBottomRotationMultiplier = 0.5f; + BodyRotationMultiplier = 0.75f; + VehicleRotationMultiplier = 0.5f; DetachRocksAngle = 138.0f; ExcavatorAnimREF = nullptr; diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index eb47c45..a761d36 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -7,6 +7,7 @@ #include "Kismet/GameplayStatics.h" #include "GameFramework/InputSettings.h" #include "CheckCollisionComponent.h" +#include "GroundDeformerComponent.h" #include "ExcavatorAnim.h" #include "ExcavatorCharacter.generated.h" @@ -66,6 +67,7 @@ public: */ UFUNCTION(BlueprintCallable) void VehicleRotation(float RotationDirection); + protected: /** Called when the game starts or when spawned. */ @@ -106,7 +108,11 @@ private: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision variables", meta = (AllowPrivateAccess = "true")) UCheckCollisionComponent* collisionComponent; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ground variables", meta = (AllowPrivateAccess = "true")) + UGroundDeformerComponent* groundDeformComponent; + /** Refrence to excavator anim class. */ + UPROPERTY(BlueprintReadWrite, Category = "Ground variables", meta = (AllowPrivateAccess = "true")) UExcavatorAnim* ExcavatorAnimREF; /** Excavator mesh. */ -- GitLab From 3c933ace29462dd0ca29800f7fe36d4125cc340a Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 13:05:40 +0200 Subject: [PATCH 102/121] Modified BP_RockEndPoint - Added function that spawns player to victory screen --- ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset | 4 ++-- ExcavatorSimulator/Content/Blueprints/BP_rock.uasset | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset b/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset index 1b9f556..be8e1ea 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_RockEndPoint.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9eea45e6ee3f8e52a34690b874f33af278341c85782a66ae296c34bb8fa127b0 -size 187263 +oid sha256:7794879f47579bd936f5c946f795cb35290f32157949a7f7aa93f7ad5d6a8c42 +size 244810 diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index c4a7fc6..33b0171 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00b45c3335e0d013addacf36d74b299756edcf64722e9adf29a57fa780a438ab -size 133128 +oid sha256:cf8cc6223b51ca4c6a3daac641bbed25b99f3486f473cbf553d37f6d456c9f05 +size 131450 -- GitLab From 1a18cdfe497c6c32e0f196ba3b7953b445995022 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 13:06:26 +0200 Subject: [PATCH 103/121] modified BP_VRController - Added test function for victory --- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index eb021a1..e264b19 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bebe52a1d7bbaeabe6e09883f18d1e5566e0540c34abba465a5c2153647b170b -size 1228629 +oid sha256:092a4db5dec677ecab77883924493958ae21b205f2b9fdcd3ed5651eadce2356 +size 1279206 -- GitLab From 18b2cefa1316fe150f9865d07894ae9b3ab960ff Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 13:06:52 +0200 Subject: [PATCH 104/121] Modified Game Mode - Added timer --- ExcavatorSimulator/Content/Blueprints/BPGM_Excavator.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BPGM_Excavator.uasset b/ExcavatorSimulator/Content/Blueprints/BPGM_Excavator.uasset index c923376..d015edd 100644 --- a/ExcavatorSimulator/Content/Blueprints/BPGM_Excavator.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BPGM_Excavator.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d39e32d9bc5951b507fcc164283f60b368fead78d0970965fbfc588b8dd279e1 -size 20465 +oid sha256:efb7d192de92fb422be96bb1b2d6ae456d883c4b724db4c060c2dfbc3f320afc +size 57457 -- GitLab From 8261d3eaaee3e974cb5271af70ba06dc082bc90b Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 13:07:26 +0200 Subject: [PATCH 105/121] Modified Levels - Added end screen to level_a1 - Added Voxel world to TestLevel_Tero --- ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap | 4 ++-- ExcavatorSimulator/Content/Levels/level_a1.umap | 4 ++-- ExcavatorSimulator/Content/TeroLevelSaveObject.uasset | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 ExcavatorSimulator/Content/TeroLevelSaveObject.uasset diff --git a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap index 8970a30..20e3c89 100644 --- a/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap +++ b/ExcavatorSimulator/Content/Levels/TestLevel_Tero.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b5e5d491d9c5dc4b5af19b3d91e5b5f594d15a8c1ebb62a22a1e4127c9788b6 -size 63143 +oid sha256:a1325d18b3ecfd62491b84d578e4702feabb8c6225a660ca84f6b77f78a16926 +size 70441 diff --git a/ExcavatorSimulator/Content/Levels/level_a1.umap b/ExcavatorSimulator/Content/Levels/level_a1.umap index 93c6552..882ce20 100644 --- a/ExcavatorSimulator/Content/Levels/level_a1.umap +++ b/ExcavatorSimulator/Content/Levels/level_a1.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b043485e6847c3e835b26fb060d1b4e1d446e0be1592489449d4224963f22ff -size 1973623 +oid sha256:e7fa4e1e14c9f99d743cc12a3eed58ffaa5815d2c13fcd879f7b6adce2afa011 +size 1978782 diff --git a/ExcavatorSimulator/Content/TeroLevelSaveObject.uasset b/ExcavatorSimulator/Content/TeroLevelSaveObject.uasset new file mode 100644 index 0000000..dfcbc20 --- /dev/null +++ b/ExcavatorSimulator/Content/TeroLevelSaveObject.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcac650c89d9a9609ab4488fa240366b46f629a54122c2c89201a3c9d115a47c +size 17644 -- GitLab From 6801bb3240d231c2dfacf092968f190df6c0260b Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 13:08:17 +0200 Subject: [PATCH 106/121] Added FallingSandEffect --- .../Content/Materials/FallingSand/FallingSand.uasset | 3 +++ .../Content/Materials/FallingSand/FallingSandEffect.uasset | 3 +++ .../Content/Materials/FallingSand/FallingSandMesh.uasset | 3 +++ .../Content/Materials/FallingSand/FallingSandMeshTHIN.uasset | 3 +++ .../Materials/FallingSand/FallingSandMeshTHINNER.uasset | 3 +++ .../Content/Materials/FallingSand/PackedClouds.uasset | 3 +++ .../Content/Materials/FallingSand/Sparkles.uasset | 3 +++ .../Content/Materials/FallingSand/TesselatedSquare.uasset | 3 +++ .../Materials/FallingSand/Tesselated_SquareCircle.uasset | 3 +++ .../FallingSand/Tesselated_SquareCircleHighRes.uasset | 3 +++ 10 files changed, 30 insertions(+) create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/FallingSand.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/FallingSandEffect.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMesh.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHIN.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHINNER.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/PackedClouds.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/Sparkles.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/TesselatedSquare.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircle.uasset create mode 100644 ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircleHighRes.uasset diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/FallingSand.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSand.uasset new file mode 100644 index 0000000..7764b06 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSand.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f7f992f333e9a5481a51afa348ad23b470f736a8e94073ba57a0a7d7510470b +size 45684 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandEffect.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandEffect.uasset new file mode 100644 index 0000000..f009235 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandEffect.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7534cdfa771e9f442e131c518e0d13d9c4253d3167241dd3a2041c7a95fbb3cc +size 57376 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMesh.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMesh.uasset new file mode 100644 index 0000000..be5f2d6 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMesh.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c4e1237ae1b212a77354162324b41a6f607c05474c1e7adefe5daf03ad0c563 +size 21197 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHIN.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHIN.uasset new file mode 100644 index 0000000..ef601d6 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHIN.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cff9692e9a5600d6f6d2daa5ba7c927bcbf282dfbe5c4f4940eb9fc9131e94e7 +size 21187 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHINNER.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHINNER.uasset new file mode 100644 index 0000000..d6604dd --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/FallingSandMeshTHINNER.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ceea055ce79371f460a3a1303b3b686487fab8f9659bcaee52e777b8be94f99 +size 21624 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/PackedClouds.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/PackedClouds.uasset new file mode 100644 index 0000000..e295960 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/PackedClouds.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1c6f84cf46f2f58f0e648731180bb4d90302d4dae88606908f7fee1291ea558 +size 5649837 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/Sparkles.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/Sparkles.uasset new file mode 100644 index 0000000..ddae6a3 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/Sparkles.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ac6f1f300dd20d86e9d6415aaa7c42bd255f8bc915570de0164250adbc164f4 +size 297994 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/TesselatedSquare.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/TesselatedSquare.uasset new file mode 100644 index 0000000..007f2ae --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/TesselatedSquare.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0896bfa6813cbfb60eae16179dc71526bca19f0879f31b451a5f6ff66a37f032 +size 25467 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircle.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircle.uasset new file mode 100644 index 0000000..0d953c5 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircle.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0bc6998ed79a77b9c0d5e6fc069dd1b28a721313b37455a09bc192a8fb73466 +size 169902 diff --git a/ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircleHighRes.uasset b/ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircleHighRes.uasset new file mode 100644 index 0000000..c22fff9 --- /dev/null +++ b/ExcavatorSimulator/Content/Materials/FallingSand/Tesselated_SquareCircleHighRes.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f17e6922d2272e171e617ee1edc85c0b41c0871f681ae4675843b6bbb3fddf49 +size 568999 -- GitLab From 1acb989e2be65c1ec38da567127c723731bea77a Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 13:08:33 +0200 Subject: [PATCH 107/121] Added BP_EndLevelScreen --- ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset diff --git a/ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset b/ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset new file mode 100644 index 0000000..9c795d6 --- /dev/null +++ b/ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f15d35ac980c2cc4e97dbb4eb8057d012d1fb16acdfac768b363cafcb4a067e +size 63557 -- GitLab From ff52a1f48bf14aeed236ee06a2464bddbd20f69a Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 14:26:11 +0200 Subject: [PATCH 108/121] Modified BP_rock - Removed unUsed c++ class - Fixed rocks not picking up - Fixed rocks flying crazy when picked up --- .../Content/Blueprints/BP_rock.uasset | 4 +- .../ExcavatorSimulator/PickableRock.cpp | 27 ----------- .../Source/ExcavatorSimulator/PickableRock.h | 48 ------------------- 3 files changed, 2 insertions(+), 77 deletions(-) delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp delete mode 100644 ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h diff --git a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset index 33b0171..4f7f118 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_rock.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf8cc6223b51ca4c6a3daac641bbed25b99f3486f473cbf553d37f6d456c9f05 -size 131450 +oid sha256:f4247b55e8f716f0746a5a3b87ca5c8907dea19988f50bbc341bdc061e74caf0 +size 133749 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp deleted file mode 100644 index 8adbc7b..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - - -#include "PickableRock.h" - -// Sets default values -APickableRock::APickableRock() -{ - // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. - PrimaryActorTick.bCanEverTick = true; - -} - -// Called when the game starts or when spawned -void APickableRock::BeginPlay() -{ - Super::BeginPlay(); - -} - -// Called every frame -void APickableRock::Tick(float DeltaTime) -{ - Super::Tick(DeltaTime); - -} - diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h b/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h deleted file mode 100644 index 9e5afdf..0000000 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/PickableRock.h +++ /dev/null @@ -1,48 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/Actor.h" -#include "PickableRock.generated.h" - -UCLASS() -class EXCAVATORSIMULATOR_API APickableRock : public AActor -{ - GENERATED_BODY() - -public: - // Sets default values for this actor's properties - APickableRock(); - - // Called every frame - virtual void Tick(float DeltaTime) override; - - void SetIsAtStart(bool isAtStart) - { - IsAtStartPoint = isAtStart; - } - - void SetIsAtEnd(bool isAtEnd) - { - IsAtEndPoint = isAtEnd; - } - - bool GetIsAtEnd() - { - return IsAtEndPoint; - } - - bool GetIsAtStart() - { - return IsAtStartPoint; - } - -protected: - // Called when the game starts or when spawned - virtual void BeginPlay() override; - -private: - bool IsAtStartPoint; - bool IsAtEndPoint; -}; -- GitLab From eaae5356d793054a2be22525f5a1b05d74cd9c9a Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 14:27:06 +0200 Subject: [PATCH 109/121] modified ExcavatorCharacter - Deleted old functions - Removed reference to rocks class from cpp --- .../Content/Blueprints/BP_ExcavatorCharacter.uasset | 4 ++-- .../Source/ExcavatorSimulator/ExcavatorCharacter.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index a7d7e61..cbf2f0b 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:258a1571b5170d54c260aaa0b255dc017cfb4ff077a4737f82c5212ffcaf4572 -size 846183 +oid sha256:974d0a6840691876e120778f72e554f50f58bcd8bbac8a032d5c53007fe43e12 +size 638060 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index 1907b19..a991a56 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -2,7 +2,6 @@ #include "ExcavatorCharacter.h" -#include "PickableRock.h" #include <Kismet/KismetMathLibrary.h> // Sets default values @@ -22,7 +21,7 @@ AExcavatorCharacter::AExcavatorCharacter() BodyRotationMultiplier = 0.75f; VehicleRotationMultiplier = 0.5f; - DetachRocksAngle = 138.0f; + DetachRocksAngle = 90.0f; ExcavatorAnimREF = nullptr; } -- GitLab From 2189d2159182e8c7288ac02aff28b21b70de3d9b Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 14:27:54 +0200 Subject: [PATCH 110/121] Added Rock Object collision --- ExcavatorSimulator/Config/DefaultEngine.ini | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/ExcavatorSimulator/Config/DefaultEngine.ini b/ExcavatorSimulator/Config/DefaultEngine.ini index 65b5b1c..3cf435b 100644 --- a/ExcavatorSimulator/Config/DefaultEngine.ini +++ b/ExcavatorSimulator/Config/DefaultEngine.ini @@ -211,3 +211,60 @@ HandTrackingFrequency=LOW XrApi=NativeOpenXR ColorSpace=Rift_CV1 +[/Script/Engine.CollisionProfile] +-Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision",bCanModify=False) +-Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) +-Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ",bCanModify=False) +-Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ",bCanModify=False) +-Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic",Response=ECR_Block),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.",bCanModify=False) +-Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors",bCanModify=False) +-Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors",bCanModify=False) +-Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.",bCanModify=False) +-Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.",bCanModify=False) +-Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.",bCanModify=False) +-Profiles=(Name="UI",CollisionEnabled=QueryOnly,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Block),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ",bCanModify=False) ++Profiles=(Name="NoCollision",CollisionEnabled=NoCollision,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="No collision") ++Profiles=(Name="BlockAll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=,HelpMessage="WorldStatic object that blocks all actors by default. All new custom channels will use its own default response. ") ++Profiles=(Name="OverlapAll",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ") ++Profiles=(Name="BlockAllDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=,HelpMessage="WorldDynamic object that blocks all actors by default. All new custom channels will use its own default response. ") ++Profiles=(Name="OverlapAllDynamic",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Overlap),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that overlaps all actors by default. All new custom channels will use its own default response. ") ++Profiles=(Name="IgnoreOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that ignores Pawn and Vehicle. All other channels will be set to default.") ++Profiles=(Name="OverlapOnlyPawn",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Pawn",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that overlaps Pawn, Camera, and Vehicle. All other channels will be set to default. ") ++Profiles=(Name="Pawn",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object. Can be used for capsule of any playerable character or AI. ") ++Profiles=(Name="Spectator",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="WorldStatic"),(Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Ignore),(Channel="Camera",Response=ECR_Ignore),(Channel="PhysicsBody",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Destructible",Response=ECR_Ignore)),HelpMessage="Pawn object that ignores all other actors except WorldStatic.") ++Profiles=(Name="CharacterMesh",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="Pawn",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Vehicle",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Pawn object that is used for Character Mesh. All other channels will be set to default.") ++Profiles=(Name="PhysicsActor",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=,HelpMessage="Simulating actors") ++Profiles=(Name="Destructible",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Destructible",CustomResponses=,HelpMessage="Destructible actors") ++Profiles=(Name="InvisibleWall",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldStatic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldStatic object that is invisible.") ++Profiles=(Name="InvisibleWallDynamic",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="Visibility",Response=ECR_Ignore)),HelpMessage="WorldDynamic object that is invisible.") ++Profiles=(Name="Trigger",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility",Response=ECR_Ignore),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldDynamic object that is used for trigger. All other channels will be set to default.") ++Profiles=(Name="Ragdoll",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="PhysicsBody",CustomResponses=((Channel="Pawn",Response=ECR_Ignore),(Channel="Visibility",Response=ECR_Ignore)),HelpMessage="Simulating Skeletal Mesh Component. All other channels will be set to default.") ++Profiles=(Name="Vehicle",CollisionEnabled=QueryAndPhysics,bCanModify=False,ObjectTypeName="Vehicle",CustomResponses=,HelpMessage="Vehicle object that blocks Vehicle, WorldStatic, and WorldDynamic. All other channels will be set to default.") ++Profiles=(Name="UI",CollisionEnabled=QueryOnly,bCanModify=False,ObjectTypeName="WorldDynamic",CustomResponses=((Channel="WorldStatic",Response=ECR_Overlap),(Channel="Pawn",Response=ECR_Overlap),(Channel="Visibility"),(Channel="WorldDynamic",Response=ECR_Overlap),(Channel="Camera",Response=ECR_Overlap),(Channel="PhysicsBody",Response=ECR_Overlap),(Channel="Vehicle",Response=ECR_Overlap),(Channel="Destructible",Response=ECR_Overlap)),HelpMessage="WorldStatic object that overlaps all actors by default. All new custom channels will use its own default response. ") ++DefaultChannelResponses=(Channel=ECC_GameTraceChannel1,DefaultResponse=ECR_Block,bTraceType=False,bStaticObject=False,Name="Rocks") +-ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") +-ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") +-ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") +-ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") +-ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") ++ProfileRedirects=(OldName="BlockingVolume",NewName="InvisibleWall") ++ProfileRedirects=(OldName="InterpActor",NewName="IgnoreOnlyPawn") ++ProfileRedirects=(OldName="StaticMeshComponent",NewName="BlockAllDynamic") ++ProfileRedirects=(OldName="SkeletalMeshActor",NewName="PhysicsActor") ++ProfileRedirects=(OldName="InvisibleActor",NewName="InvisibleWallDynamic") +-CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") +-CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") +-CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") +-CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") ++CollisionChannelRedirects=(OldName="Static",NewName="WorldStatic") ++CollisionChannelRedirects=(OldName="Dynamic",NewName="WorldDynamic") ++CollisionChannelRedirects=(OldName="VehicleMovement",NewName="Vehicle") ++CollisionChannelRedirects=(OldName="PawnMovement",NewName="Pawn") + -- GitLab From 04a13efeadd4efe51a95b79abd77972690e7eece Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 14:28:50 +0200 Subject: [PATCH 111/121] modified BP_EndLevelScreen --- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- ExcavatorSimulator/Content/Levels/level_a1.umap | 4 ++-- ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index e264b19..eb75e64 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:092a4db5dec677ecab77883924493958ae21b205f2b9fdcd3ed5651eadce2356 -size 1279206 +oid sha256:ff78158c1ffaa8902341e203030d0706ea177626aff2a512717d48c14d66083f +size 1294896 diff --git a/ExcavatorSimulator/Content/Levels/level_a1.umap b/ExcavatorSimulator/Content/Levels/level_a1.umap index 882ce20..8208da3 100644 --- a/ExcavatorSimulator/Content/Levels/level_a1.umap +++ b/ExcavatorSimulator/Content/Levels/level_a1.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7fa4e1e14c9f99d743cc12a3eed58ffaa5815d2c13fcd879f7b6adce2afa011 -size 1978782 +oid sha256:fa637cee8ab4a381a0c0e40060fc1269a5e8e8f8a353c65f01f1a692dc4579e5 +size 1979167 diff --git a/ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset b/ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset index 9c795d6..f1a6424 100644 --- a/ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset +++ b/ExcavatorSimulator/Content/UI/BP_EndLevelScreen.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f15d35ac980c2cc4e97dbb4eb8057d012d1fb16acdfac768b363cafcb4a067e -size 63557 +oid sha256:05ff9eef3e823343defa1792367ed072a2f7d8d85d1520f171b8af8868b3ede9 +size 63924 -- GitLab From 17c95c8268e220a2b83a7237e91fd28537bd9c44 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 14:29:12 +0200 Subject: [PATCH 112/121] Modified CheckCollisionComponent --- .../Source/ExcavatorSimulator/CheckCollisionComponent.cpp | 2 +- .../Source/ExcavatorSimulator/CheckCollisionComponent.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp index 77a16bf..60c313a 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.cpp @@ -24,7 +24,7 @@ UCheckCollisionComponent::UCheckCollisionComponent() ExcavatorAnimREF = nullptr; //these values are multiplied with -1 so you can but them positive - CollisionLengthDown = 160.0f; + CollisionLengthDown = 80.0f; CollisionLengthLeftAndRight = 160.0f; CollisionLengthFront = 220.0f; } diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h index 5c54582..74718b2 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/CheckCollisionComponent.h @@ -7,7 +7,6 @@ #include <Components/BoxComponent.h> #include "ExcavatorAnim.h" #include "VoxelWorld.h" -#include "PickableRock.h" #include "CheckCollisionComponent.generated.h" UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) -- GitLab From 538faf4fd5ed40ca69762e41e6bd8df40d085eb2 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 15:53:58 +0200 Subject: [PATCH 113/121] Modified ExcavatorCharacter - Added Vr rotation functions to c++ --- .../Blueprints/BP_ExcavatorCharacter.uasset | 4 +- .../ExcavatorSimulator/ExcavatorCharacter.cpp | 79 ++++++++++++++++++- .../ExcavatorSimulator/ExcavatorCharacter.h | 35 ++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset index cbf2f0b..321163b 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_ExcavatorCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:974d0a6840691876e120778f72e554f50f58bcd8bbac8a032d5c53007fe43e12 -size 638060 +oid sha256:4414ea3ab3c17a555e5e9687adfd5fbb1db1db0bc12a8404693c9ad28ea651e4 +size 606578 diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp index a991a56..aa07d6b 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.cpp @@ -13,6 +13,8 @@ AExcavatorCharacter::AExcavatorCharacter() groundDeformComponent = CreateDefaultSubobject<UGroundDeformerComponent>(TEXT("Ground Deform Component")); mesh = GetMesh(); + deltaTime = 0.0f; + AccelerationMultiplier = 0.5f; BucketRotationMultiplier = 0.25f; @@ -23,12 +25,25 @@ AExcavatorCharacter::AExcavatorCharacter() DetachRocksAngle = 90.0f; ExcavatorAnimREF = nullptr; + + RightJoyStickRotXMin = -6.0f; + RightJoyStickRotXMax = 6.0f; + + RightJoyStickRotYMin = -6.0f; + RightJoyStickRotYMax = 6.0f; + + LeftJoyStickRotXMin = -7.0f; + LeftJoyStickRotXMax = 7.0f; + + LeftJoyStickRotYMin = -6.0f; + LeftJoyStickRotYMax = 6.0f; } // Called every frame void AExcavatorCharacter::Tick(float DeltaTime) { Super::Tick(DeltaTime); + deltaTime = DeltaTime; } // Called to bind functionality to input @@ -56,6 +71,17 @@ bool AExcavatorCharacter::BucketRotationReleaceRocks(float RotationDirection) return (tempBucketRot >= DetachRocksAngle) ? true : false; } +void AExcavatorCharacter::BucketRotationVR(float RightJoyStickRotationX) +{ + //if (RightJoyStickRotationX < -6.0f || RightJoyStickRotationX > 6.0f) + if (RightJoyStickRotationX < RightJoyStickRotXMin || RightJoyStickRotationX > RightJoyStickRotXMax) + { + float tempBucketRot = (deltaTime * RightJoyStickRotationX) / 2 + ExcavatorAnimREF->GetBucketRotation().Yaw; + tempBucketRot = UKismetMathLibrary::FClamp(tempBucketRot, ExcavatorAnimREF->GetBucketMinRotation(), ExcavatorAnimREF->GetBucketMaxRotation()); + ExcavatorAnimREF->SetBucketRotation(FRotator(0.0f, tempBucketRot, 0.0f)); + } +} + void AExcavatorCharacter::BeamTopRotation(float RotationDirection) { bool movingForward = RotationDirection >= 0.0f ? true : false; @@ -66,7 +92,17 @@ void AExcavatorCharacter::BeamTopRotation(float RotationDirection) ExcavatorAnimREF->SetBeamTopRotation(FRotator(0.0f, tempTopRot, 0.0f)); //UE_LOG(LogTemp, Warning, TEXT("TopRotation angle: (%f)"), tempTopRot); } - +} + +void AExcavatorCharacter::BeamTopRotationVR(float RightJoyStickRotationY) +{ + //if (RightJoyStickRotationY < -6.0f || RightJoyStickRotationY > 6.0f) + if (RightJoyStickRotationY < RightJoyStickRotYMin || RightJoyStickRotationY > RightJoyStickRotYMax) + { + float tempTopRot = (deltaTime * RightJoyStickRotationY) / 2 + ExcavatorAnimREF->GetBucketRotation().Yaw; + tempTopRot = UKismetMathLibrary::FClamp(tempTopRot, ExcavatorAnimREF->GetTopMinRotation(), ExcavatorAnimREF->GetTopMaxRotation()); + ExcavatorAnimREF->SetBeamTopRotation(FRotator(0.0f, tempTopRot, 0.0f)); + } } void AExcavatorCharacter::BeamBottomRotation(float RotationDirection) @@ -80,6 +116,17 @@ void AExcavatorCharacter::BeamBottomRotation(float RotationDirection) } } +void AExcavatorCharacter::BeamBottomRotationVR(float LeftJoyStickRotationY) +{ + //if (LeftJoyStickRotationY < -6.0f || LeftJoyStickRotationY > 6.0f) + if (LeftJoyStickRotationY < LeftJoyStickRotYMin || LeftJoyStickRotationY > LeftJoyStickRotYMax) + { + float tempBottomRot = (deltaTime * LeftJoyStickRotationY) / 2 + ExcavatorAnimREF->GetBucketRotation().Yaw; + tempBottomRot = UKismetMathLibrary::FClamp(tempBottomRot, ExcavatorAnimREF->GetBottomMinRotation(), ExcavatorAnimREF->GetBottomMaxRotation()); + ExcavatorAnimREF->SetBeamBottomRotation(FRotator(0.0f, tempBottomRot, 0.0f)); + } +} + void AExcavatorCharacter::BodyRotation(float RotationDirection) { if (collisionComponent->IsCollidingLR(RotationDirection)) @@ -96,6 +143,23 @@ void AExcavatorCharacter::BodyRotation(float RotationDirection) } } +void AExcavatorCharacter::BodyRotationVR(float LeftJoyStickRotationX) +{ + //if (LeftJoyStickRotationX < -7.0f || LeftJoyStickRotationX > 7.0f) + if (LeftJoyStickRotationX < LeftJoyStickRotXMin || LeftJoyStickRotationX > LeftJoyStickRotXMax) + { + float tempBodyRot = (deltaTime * LeftJoyStickRotationX * -1) / 2 + ExcavatorAnimREF->GetBucketRotation().Pitch; + if (-360.0f >= tempBodyRot || 360.0f <= tempBodyRot) + { + ExcavatorAnimREF->SetBodyRotation(FRotator(0.0f, 0.0f, 0.0f)); + } + else + { + ExcavatorAnimREF->SetBodyRotation(FRotator(tempBodyRot, 0.0f, 0.0f)); + } + } +} + void AExcavatorCharacter::VehicleRotation(float RotationDirection) { if (collisionComponent->IsCollidingLR(RotationDirection)) @@ -112,6 +176,19 @@ void AExcavatorCharacter::VehicleRotation(float RotationDirection) } } +void AExcavatorCharacter::VehicleRotationVR(float AxisValue) +{ + float tempVehicleRot = AxisValue * 0.5f + mesh->GetComponentRotation().Yaw; + if (-360.0f >= tempVehicleRot || 360.0f <= tempVehicleRot) + { + mesh->SetWorldRotation(FRotator(0.0f, 0.0f, 0.0f)); + } + else + { + mesh->SetWorldRotation(FRotator(0.0f, tempVehicleRot, 0.0f)); + } +} + // Called when the game starts or when spawned void AExcavatorCharacter::BeginPlay() { diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h index a761d36..5825fc1 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/ExcavatorCharacter.h @@ -40,6 +40,9 @@ public: UFUNCTION(BlueprintCallable) bool BucketRotationReleaceRocks(float RotationDirection); + UFUNCTION(BlueprintCallable) + void BucketRotationVR(float RightJoyStickRotationX); + /** * Rotates beam top * @Param RotationDirection beam top rotation direction @@ -47,6 +50,9 @@ public: UFUNCTION(BlueprintCallable) void BeamTopRotation(float RotationDirection); + UFUNCTION(BlueprintCallable) + void BeamTopRotationVR(float RightJoyStickRotationY); + /** * Rotates beam bottom * @Param RotationDirection beam bottom rotation direction @@ -54,12 +60,18 @@ public: UFUNCTION(BlueprintCallable) void BeamBottomRotation(float RotationDirection); + UFUNCTION(BlueprintCallable) + void BeamBottomRotationVR(float LeftJoyStickRotationY); + /** * Rotates excavator body * @Param RotationDirection body rotation direction */ UFUNCTION(BlueprintCallable) void BodyRotation(float RotationDirection); + + UFUNCTION(BlueprintCallable) + void BodyRotationVR(float LeftJoyStickRotationX); /** * Rotates whole vehicle @@ -68,6 +80,9 @@ public: UFUNCTION(BlueprintCallable) void VehicleRotation(float RotationDirection); + UFUNCTION(BlueprintCallable) + void VehicleRotationVR(float AxisValue); + protected: /** Called when the game starts or when spawned. */ @@ -76,6 +91,24 @@ protected: private: UInputSettings* Inputsettings = UInputSettings::GetInputSettings(); + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Right Joy Stick values", meta = (AllowPrivateAccess = "true")) + float RightJoyStickRotXMin; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Right Joy Stick values", meta = (AllowPrivateAccess = "true")) + float RightJoyStickRotXMax; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Right Joy Stick values", meta = (AllowPrivateAccess = "true")) + float RightJoyStickRotYMin; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Right Joy Stick values", meta = (AllowPrivateAccess = "true")) + float RightJoyStickRotYMax; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Left Joy Stick values", meta = (AllowPrivateAccess = "true")) + float LeftJoyStickRotXMin; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Left Joy Stick values", meta = (AllowPrivateAccess = "true")) + float LeftJoyStickRotXMax; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Left Joy Stick values", meta = (AllowPrivateAccess = "true")) + float LeftJoyStickRotYMin; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Left Joy Stick values", meta = (AllowPrivateAccess = "true")) + float LeftJoyStickRotYMax; + /** AccelerationMultiplier multiplier for movement speed. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement variables", meta = (AllowPrivateAccess = "true")) float AccelerationMultiplier; @@ -117,4 +150,6 @@ private: /** Excavator mesh. */ USkeletalMeshComponent* mesh; + + float deltaTime; }; -- GitLab From 56130295c974a7e8c56bf3ca90abff96334e649d Mon Sep 17 00:00:00 2001 From: Markka <markuskoistinen2@kamk.fi> Date: Tue, 29 Nov 2022 15:54:06 +0200 Subject: [PATCH 114/121] modified level_a2 --- ExcavatorSimulator/Content/Levels/level_a2.umap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Levels/level_a2.umap b/ExcavatorSimulator/Content/Levels/level_a2.umap index a8e19b3..b01eb73 100644 --- a/ExcavatorSimulator/Content/Levels/level_a2.umap +++ b/ExcavatorSimulator/Content/Levels/level_a2.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:674063a550873f0eab3fb00fdfc168c8905b0c4a1dca0273a988cac11117eb54 -size 61912 +oid sha256:ed1ea4d9380c8a1a419d45d92144a27c6949a38544507bc268a3e0cad3ac50e3 +size 100928 -- GitLab From 02979fda8e2838198703b5d90231d1571a7e91f6 Mon Sep 17 00:00:00 2001 From: Koponen Tero TTV20SP <terokoponen@kamk.fi> Date: Tue, 29 Nov 2022 15:54:34 +0200 Subject: [PATCH 115/121] Modified BP_VRController - Changed Blueprint to use c++ functions --- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index eb75e64..d2085b9 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff78158c1ffaa8902341e203030d0706ea177626aff2a512717d48c14d66083f -size 1294896 +oid sha256:44022c8ae913665729d6c6e38ab1cab0b45e1030d7a5c73b23e222ef3247f25c +size 1320882 -- GitLab From 1a3d2a603997711545d30b532ae26e4fdcb981c0 Mon Sep 17 00:00:00 2001 From: Laurila Markus TTV20SP <markuslaurila@kamk.fi> Date: Tue, 29 Nov 2022 15:55:57 +0200 Subject: [PATCH 116/121] TUTORIAL ALMOST DONE --- .../Content/Levels/level_a1.umap | 4 +- .../Content/Levels/level_tutorial.umap | 4 +- .../Pictures/Oculus Left Joystick.jpg | Bin 79461 -> 142931 bytes .../Pictures/Oculus_Left_Joystick.uasset | 4 +- .../Assets/Left_side_controls_movement.uasset | 3 + .../Sound/Assets/Right_side_controls.uasset | 3 + .../Use_right_controller_to_continue.uasset | 3 + .../UI/BPT_Tutorial3rd_Child_Child.uasset | 4 +- SAVED TEXT VR CONTROLLEr.txt | 80 ++++++++++++++++++ 9 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Left_side_controls_movement.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Right_side_controls.uasset create mode 100644 ExcavatorSimulator/Content/Sound/Assets/Use_right_controller_to_continue.uasset create mode 100644 SAVED TEXT VR CONTROLLEr.txt diff --git a/ExcavatorSimulator/Content/Levels/level_a1.umap b/ExcavatorSimulator/Content/Levels/level_a1.umap index 8208da3..4a3313e 100644 --- a/ExcavatorSimulator/Content/Levels/level_a1.umap +++ b/ExcavatorSimulator/Content/Levels/level_a1.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa637cee8ab4a381a0c0e40060fc1269a5e8e8f8a353c65f01f1a692dc4579e5 -size 1979167 +oid sha256:cd8b56f376ec8c955d83a14b265db51a1abb1851dff1f2606bf533bd32a3dc90 +size 1975109 diff --git a/ExcavatorSimulator/Content/Levels/level_tutorial.umap b/ExcavatorSimulator/Content/Levels/level_tutorial.umap index 19f94f3..d25a03f 100644 --- a/ExcavatorSimulator/Content/Levels/level_tutorial.umap +++ b/ExcavatorSimulator/Content/Levels/level_tutorial.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4aa7a2d03d6cee3db7627d915f59c105c2c7662141a11bb638d5573892de907 -size 61651 +oid sha256:235e6845f865f88c07fc8eab05c4883f0773cbf395a4c7025812cb1c6135ddb4 +size 71953 diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus Left Joystick.jpg b/ExcavatorSimulator/Content/Materials/Pictures/Oculus Left Joystick.jpg index b3a6c7dd4794b9f565f06d34614d8245672528e6..35ced17b1ad31e2a6c1538d21e253ac65a9e6513 100644 GIT binary patch literal 142931 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%0A@QF7u4CTL-i*)D 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%0D{R3Cbg)AJa-Hf&-Wn9GAjHs@9V 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 M|L4~S`l<In0Twp5Qvd(} literal 79461 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	X`ly{$o3L^Qvh*<1f@Et5Ci5oeL<B9POob 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 KYCO9C-vj`6dI5F- diff --git a/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset index 607e3ce..afe6390 100644 --- a/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset +++ b/ExcavatorSimulator/Content/Materials/Pictures/Oculus_Left_Joystick.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d3057e81fe6f8d4332c858118b27e2f7c50bd8e0bc7036b319d25ab85bbe2f5 -size 90700 +oid sha256:beee1534e761fd208f1d7380ca5656eaa886383240cc3d6e32af4a11dab04e48 +size 154769 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Left_side_controls_movement.uasset b/ExcavatorSimulator/Content/Sound/Assets/Left_side_controls_movement.uasset new file mode 100644 index 0000000..178ed9f --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Left_side_controls_movement.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8af49696fe8f03a02f320cec3b9c4cdc0082c09d47b871a9a8a8f81b3b9a2a5a +size 480563 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Right_side_controls.uasset b/ExcavatorSimulator/Content/Sound/Assets/Right_side_controls.uasset new file mode 100644 index 0000000..89562bb --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Right_side_controls.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1a490f5d2cf9dcc5d55b28747fed7b3cad07d932df60166fc5f636a81aa65cc +size 581155 diff --git a/ExcavatorSimulator/Content/Sound/Assets/Use_right_controller_to_continue.uasset b/ExcavatorSimulator/Content/Sound/Assets/Use_right_controller_to_continue.uasset new file mode 100644 index 0000000..762ba58 --- /dev/null +++ b/ExcavatorSimulator/Content/Sound/Assets/Use_right_controller_to_continue.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:201d7cbf6dc91bd1f2be5885fdb447fd9a394b15ded8dc84751f2e6ceacbf8e0 +size 449394 diff --git a/ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset b/ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset index a00089e..cbd48f4 100644 --- a/ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset +++ b/ExcavatorSimulator/Content/UI/BPT_Tutorial3rd_Child_Child.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56fd198c6f02af3502c9dbeefde232996d82250f159fc580cab95ec2610e01bd -size 112424 +oid sha256:f2dd15898c8d249e9a6c4b9b38867ab754ab48ff6b6c76c8a67f2b0dd961d854 +size 212027 diff --git a/SAVED TEXT VR CONTROLLEr.txt b/SAVED TEXT VR CONTROLLEr.txt new file mode 100644 index 0000000..1cac5a6 --- /dev/null +++ b/SAVED TEXT VR CONTROLLEr.txt @@ -0,0 +1,80 @@ +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_19" + FunctionReference=(MemberParent=Class'"/Script/Engine.GameplayStatics"',MemberName="OpenLevel") + NodePosX=-3872 + NodePosY=1696 + AdvancedPinDisplay=Hidden + NodeGuid=F2F16DDE4C4927084C9955910254C1ED + CustomProperties Pin (PinId=9EFC40D741009CE2A973B6AC9E078ADF,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_20 AE78AAFB4ED4B6E203D213B272AA149F,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=3DB7885B40214736D7EB03A742BCC7FA,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=62B6CB284673C2A53C8FD0BF8BA0453F,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nGameplay Statics Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.GameplayStatics"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__GameplayStatics",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=FED3E46342E163CEB0EE4D8347FC82AB,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=62CE12D545AE014B1F2452A60D6A6E80,PinName="LevelName",PinToolTip="Level Name\nName\n\nthe level to open",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="level_a1",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=588DE20C4CAA2A19F561798AB74FFE01,PinName="bAbsolute",PinToolTip="Absolute\nBoolean\n\nif true options are reset, if false options are carried over from current level",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) + CustomProperties Pin (PinId=6289A1674F2A3AC455E6249B29206339,PinName="Options",PinToolTip="Options\nString\n\na string of options to use for the travel URL",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_16" + FunctionReference=(MemberParent=Class'"/Script/Engine.PlayerCameraManager"',MemberName="StartCameraFade") + NodePosX=-4352 + NodePosY=1600 + NodeGuid=EF7231E8451902D774952BB4E0DADFE0 + CustomProperties Pin (PinId=F9715EE140ABE890CFCF1987C64EA903,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_IfThenElse_9 27DC9998431C570B93280CA8CABF11B3,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=959E8B23440CF9280613898377BE1AF9,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_20 6FA02FBB4EBD176C8B9B4FBE460B0347,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=6139EC2941D7C3FC7EC04796F19C8668,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nPlayer Camera Manager Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.PlayerCameraManager"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_15 DFECAE814FAEECEBA7EA5F98F1D39C3D,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=9EC07CA943CE77BA95EEC78EAE0EA977,PinName="FromAlpha",PinToolTip="From Alpha\nFloat (single-precision)\n\nAlpha at which to begin the fade. Range [0..1], where 0 is fully transparent and 1 is fully opaque solid color.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0.0",AutogeneratedDefaultValue="0.0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=A24C4FC145BEBA2A842650917975EE93,PinName="ToAlpha",PinToolTip="To Alpha\nFloat (single-precision)\n\nAlpha at which to finish the fade.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="1.000000",AutogeneratedDefaultValue="0.0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=4AAC7D3E44D02D4BD8957AB0ECE42FB6,PinName="Duration",PinToolTip="Duration\nFloat (single-precision)\n\nHow long the fade should take, in seconds.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0.200000",AutogeneratedDefaultValue="0.0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=F08C82EE41363A753F3747A8BAA4F98F,PinName="Color",PinToolTip="Color\nLinear Color Structure\n\nColor to fade to/from.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B83E14D54CEF631C18BD0F9AB001A4DD,PinName="bShouldFadeAudio",PinToolTip="Should Fade Audio\nBoolean\n\nTrue to fade audio volume along with the alpha of the solid color.",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=FB09599C4F9440C4C836308BF1AA9EED,PinName="bHoldWhenFinished",PinToolTip="Hold when Finished\nBoolean\n\nTrue for fade to hold at the ToAlpha until explicitly stopped (e.g. with StopCameraFade)",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_20" + FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="Delay") + NodePosX=-4064 + NodePosY=1616 + NodeGuid=819C4EED49132EE25742318A5AA5447C + CustomProperties Pin (PinId=6FA02FBB4EBD176C8B9B4FBE460B0347,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_16 959E8B23440CF9280613898377BE1AF9,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=AE78AAFB4ED4B6E203D213B272AA149F,PinName="then",PinFriendlyName="Completed",PinToolTip="Completed\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_19 9EFC40D741009CE2A973B6AC9E078ADF,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=EBE0E4074772882903C42EB49CBF0EE4,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=7F59F09947C334135171BEA020E69956,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=05C43FB647A6DEF2EF340FAE9D73BA6D,PinName="Duration",PinToolTip="Duration\nFloat (single-precision)\n\nlength of delay (in seconds).",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="5.000000",AutogeneratedDefaultValue="0.2",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=28D9D5AD4EA89EED51F323959C7DA962,PinName="LatentInfo",PinToolTip="Latent Info\nLatent Action Info Structure\n\nThe latent action.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/Engine.LatentActionInfo"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(Linkage=-1,UUID=-1,ExecutionFunction=\"\",CallbackTarget=None)",AutogeneratedDefaultValue="LatentInfo",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_15" + bIsPureFunc=True + FunctionReference=(MemberParent=Class'"/Script/Engine.GameplayStatics"',MemberName="GetPlayerCameraManager") + NodePosX=-4688 + NodePosY=1664 + NodeGuid=0CE63AD3401F08CB9139C6863FCF6E61 + CustomProperties Pin (PinId=0C69824C45CE70E16D99BB992A31AF02,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nGameplay Statics Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.GameplayStatics"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__GameplayStatics",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B5EB425446C638D0206ED5AB9FFADA39,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=F1DD7533451BD6DB29B733B94B5DB62F,PinName="PlayerIndex",PinToolTip="Player Index\nInteger\n\nIndex in the player controller list, starting first with local players and then available remote ones",PinType.PinCategory="int",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0",AutogeneratedDefaultValue="0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=DFECAE814FAEECEBA7EA5F98F1D39C3D,PinName="ReturnValue",PinToolTip="Return Value\nPlayer Camera Manager Object Reference",Direction="EGPD_Output",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.PlayerCameraManager"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_16 6139EC2941D7C3FC7EC04796F19C8668,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet Name="K2Node_VariableGet_0" + VariableReference=(MemberParent=BlueprintGeneratedClass'"/Game/UI/BPT_Tutorial3rd_Child_Child.BPT_Tutorial3rd_Child_Child_C"',MemberName="ALLOW LEVEL TRANSITION",MemberGuid=3FACD3704B9B268B141284A1384D3B32) + SelfContextInfo=NotSelfContext + NodePosX=-4816 + NodePosY=1328 + NodeGuid=FFC315A946C5B1A71139F7A0F668A822 + CustomProperties Pin (PinId=72D44A7F4A6ADC7AE1AE72BC268E31F0,PinName="ALLOW LEVEL TRANSITION",Direction="EGPD_Output",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_IfThenElse_9 30EBFD5F4BC82BE77B28FDA4F50CC358,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=ED6DE8A34918195AD188DDB316FA1ACC,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=BlueprintGeneratedClass'"/Game/UI/BPT_Tutorial3rd_Child_Child.BPT_Tutorial3rd_Child_Child_C"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_Knot_9 AAC9F899467E7D5BC9C9948EB307CE94,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_IfThenElse Name="K2Node_IfThenElse_9" + NodePosX=-4640 + NodePosY=1408 + NodeGuid=63298C1D4A4AB94F4E2A538055AF5D64 + CustomProperties Pin (PinId=518067A741A32F42C897CCB64B36EAC9,PinName="execute",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_IfThenElse_26 0D90A3EC45C4604229DB36A57E31A66D,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=30EBFD5F4BC82BE77B28FDA4F50CC358,PinName="Condition",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",LinkedTo=(K2Node_VariableGet_0 72D44A7F4A6ADC7AE1AE72BC268E31F0,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=27DC9998431C570B93280CA8CABF11B3,PinName="then",PinFriendlyName=NSLOCTEXT("K2Node", "true", "true"),Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_16 F9715EE140ABE890CFCF1987C64EA903,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=F3BA16DC430D974F5611AC92AF73C065,PinName="else",PinFriendlyName=NSLOCTEXT("K2Node", "false", "false"),Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_ExecutionSequence_1 E0659C07472B54B5A53AD0A19DE074B3,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object +Begin Object Class=/Script/BlueprintGraph.K2Node_MacroInstance Name="K2Node_MacroInstance_2" + MacroGraphReference=(MacroGraph=EdGraph'"/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:DoOnce"',GraphBlueprint=Blueprint'"/Engine/EditorBlueprintResources/StandardMacros.StandardMacros"',GraphGuid=1281F54248A2ECB5B8B2C5B24AE6FDF4) + NodePosX=-4192 + NodePosY=1344 + NodeGuid=992E832A4C4EFBC412AB4EB1B5063FBE + CustomProperties Pin (PinId=9AB8C2EE4BBDF3AD40DABEB89EDFC361,PinName="execute",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_ExecutionSequence_1 9A21E67C4CE497D6220877A881751E75,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=98B385204BC7A4F47CF090844BA24E3C,PinName="Reset",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=DE96853E496057C2A153C381800293BE,PinName="Start Closed",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) + CustomProperties Pin (PinId=B7646A5849182CB2BF7FDF8D8928DB99,PinName="Completed",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_71 F9715EE140ABE890CFCF1987C64EA903,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) +End Object -- GitLab From 6a5c76d806e7d2c95af9be292416dc0f148dfccc Mon Sep 17 00:00:00 2001 From: make <make.rangepure@gmail.com> Date: Thu, 1 Dec 2022 14:36:40 +0200 Subject: [PATCH 117/121] Readded the lua code in text file --- .../Content/Blueprints/BP_VRController.uasset | 4 +- SAVED TEXT VR CONTROLLEr.txt | 80 ------------------- 2 files changed, 2 insertions(+), 82 deletions(-) delete mode 100644 SAVED TEXT VR CONTROLLEr.txt diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index d2085b9..5a8ef1f 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44022c8ae913665729d6c6e38ab1cab0b45e1030d7a5c73b23e222ef3247f25c -size 1320882 +oid sha256:995fe5976b2f908bce55bdbe72f4f68f3e10a82becca83640cba3f3340241151 +size 1348319 diff --git a/SAVED TEXT VR CONTROLLEr.txt b/SAVED TEXT VR CONTROLLEr.txt deleted file mode 100644 index 1cac5a6..0000000 --- a/SAVED TEXT VR CONTROLLEr.txt +++ /dev/null @@ -1,80 +0,0 @@ -Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_19" - FunctionReference=(MemberParent=Class'"/Script/Engine.GameplayStatics"',MemberName="OpenLevel") - NodePosX=-3872 - NodePosY=1696 - AdvancedPinDisplay=Hidden - NodeGuid=F2F16DDE4C4927084C9955910254C1ED - CustomProperties Pin (PinId=9EFC40D741009CE2A973B6AC9E078ADF,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_20 AE78AAFB4ED4B6E203D213B272AA149F,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=3DB7885B40214736D7EB03A742BCC7FA,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=62B6CB284673C2A53C8FD0BF8BA0453F,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nGameplay Statics Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.GameplayStatics"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__GameplayStatics",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=FED3E46342E163CEB0EE4D8347FC82AB,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=62CE12D545AE014B1F2452A60D6A6E80,PinName="LevelName",PinToolTip="Level Name\nName\n\nthe level to open",PinType.PinCategory="name",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="level_a1",AutogeneratedDefaultValue="None",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=588DE20C4CAA2A19F561798AB74FFE01,PinName="bAbsolute",PinToolTip="Absolute\nBoolean\n\nif true options are reset, if false options are carried over from current level",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) - CustomProperties Pin (PinId=6289A1674F2A3AC455E6249B29206339,PinName="Options",PinToolTip="Options\nString\n\na string of options to use for the travel URL",PinType.PinCategory="string",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=True,bOrphanedPin=False,) -End Object -Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_16" - FunctionReference=(MemberParent=Class'"/Script/Engine.PlayerCameraManager"',MemberName="StartCameraFade") - NodePosX=-4352 - NodePosY=1600 - NodeGuid=EF7231E8451902D774952BB4E0DADFE0 - CustomProperties Pin (PinId=F9715EE140ABE890CFCF1987C64EA903,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_IfThenElse_9 27DC9998431C570B93280CA8CABF11B3,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=959E8B23440CF9280613898377BE1AF9,PinName="then",PinToolTip="\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_20 6FA02FBB4EBD176C8B9B4FBE460B0347,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=6139EC2941D7C3FC7EC04796F19C8668,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nPlayer Camera Manager Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.PlayerCameraManager"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_15 DFECAE814FAEECEBA7EA5F98F1D39C3D,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=9EC07CA943CE77BA95EEC78EAE0EA977,PinName="FromAlpha",PinToolTip="From Alpha\nFloat (single-precision)\n\nAlpha at which to begin the fade. Range [0..1], where 0 is fully transparent and 1 is fully opaque solid color.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0.0",AutogeneratedDefaultValue="0.0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=A24C4FC145BEBA2A842650917975EE93,PinName="ToAlpha",PinToolTip="To Alpha\nFloat (single-precision)\n\nAlpha at which to finish the fade.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="1.000000",AutogeneratedDefaultValue="0.0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=4AAC7D3E44D02D4BD8957AB0ECE42FB6,PinName="Duration",PinToolTip="Duration\nFloat (single-precision)\n\nHow long the fade should take, in seconds.",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0.200000",AutogeneratedDefaultValue="0.0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=F08C82EE41363A753F3747A8BAA4F98F,PinName="Color",PinToolTip="Color\nLinear Color Structure\n\nColor to fade to/from.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/CoreUObject.LinearColor"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=B83E14D54CEF631C18BD0F9AB001A4DD,PinName="bShouldFadeAudio",PinToolTip="Should Fade Audio\nBoolean\n\nTrue to fade audio volume along with the alpha of the solid color.",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=FB09599C4F9440C4C836308BF1AA9EED,PinName="bHoldWhenFinished",PinToolTip="Hold when Finished\nBoolean\n\nTrue for fade to hold at the ToAlpha until explicitly stopped (e.g. with StopCameraFade)",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="false",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) -End Object -Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_20" - FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="Delay") - NodePosX=-4064 - NodePosY=1616 - NodeGuid=819C4EED49132EE25742318A5AA5447C - CustomProperties Pin (PinId=6FA02FBB4EBD176C8B9B4FBE460B0347,PinName="execute",PinToolTip="\nExec",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_16 959E8B23440CF9280613898377BE1AF9,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=AE78AAFB4ED4B6E203D213B272AA149F,PinName="then",PinFriendlyName="Completed",PinToolTip="Completed\nExec",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_19 9EFC40D741009CE2A973B6AC9E078ADF,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=EBE0E4074772882903C42EB49CBF0EE4,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nKismet System Library Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.KismetSystemLibrary"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__KismetSystemLibrary",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=7F59F09947C334135171BEA020E69956,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=05C43FB647A6DEF2EF340FAE9D73BA6D,PinName="Duration",PinToolTip="Duration\nFloat (single-precision)\n\nlength of delay (in seconds).",PinType.PinCategory="real",PinType.PinSubCategory="float",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="5.000000",AutogeneratedDefaultValue="0.2",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=28D9D5AD4EA89EED51F323959C7DA962,PinName="LatentInfo",PinToolTip="Latent Info\nLatent Action Info Structure\n\nThe latent action.",PinType.PinCategory="struct",PinType.PinSubCategory="",PinType.PinSubCategoryObject=ScriptStruct'"/Script/Engine.LatentActionInfo"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="(Linkage=-1,UUID=-1,ExecutionFunction=\"\",CallbackTarget=None)",AutogeneratedDefaultValue="LatentInfo",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) -End Object -Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_15" - bIsPureFunc=True - FunctionReference=(MemberParent=Class'"/Script/Engine.GameplayStatics"',MemberName="GetPlayerCameraManager") - NodePosX=-4688 - NodePosY=1664 - NodeGuid=0CE63AD3401F08CB9139C6863FCF6E61 - CustomProperties Pin (PinId=0C69824C45CE70E16D99BB992A31AF02,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="Target\nGameplay Statics Object Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.GameplayStatics"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultObject="/Script/Engine.Default__GameplayStatics",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=B5EB425446C638D0206ED5AB9FFADA39,PinName="WorldContextObject",PinToolTip="World Context Object\nObject Reference",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=F1DD7533451BD6DB29B733B94B5DB62F,PinName="PlayerIndex",PinToolTip="Player Index\nInteger\n\nIndex in the player controller list, starting first with local players and then available remote ones",PinType.PinCategory="int",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="0",AutogeneratedDefaultValue="0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=DFECAE814FAEECEBA7EA5F98F1D39C3D,PinName="ReturnValue",PinToolTip="Return Value\nPlayer Camera Manager Object Reference",Direction="EGPD_Output",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.PlayerCameraManager"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_16 6139EC2941D7C3FC7EC04796F19C8668,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) -End Object -Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet Name="K2Node_VariableGet_0" - VariableReference=(MemberParent=BlueprintGeneratedClass'"/Game/UI/BPT_Tutorial3rd_Child_Child.BPT_Tutorial3rd_Child_Child_C"',MemberName="ALLOW LEVEL TRANSITION",MemberGuid=3FACD3704B9B268B141284A1384D3B32) - SelfContextInfo=NotSelfContext - NodePosX=-4816 - NodePosY=1328 - NodeGuid=FFC315A946C5B1A71139F7A0F668A822 - CustomProperties Pin (PinId=72D44A7F4A6ADC7AE1AE72BC268E31F0,PinName="ALLOW LEVEL TRANSITION",Direction="EGPD_Output",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="false",AutogeneratedDefaultValue="false",LinkedTo=(K2Node_IfThenElse_9 30EBFD5F4BC82BE77B28FDA4F50CC358,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=ED6DE8A34918195AD188DDB316FA1ACC,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=BlueprintGeneratedClass'"/Game/UI/BPT_Tutorial3rd_Child_Child.BPT_Tutorial3rd_Child_Child_C"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_Knot_9 AAC9F899467E7D5BC9C9948EB307CE94,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) -End Object -Begin Object Class=/Script/BlueprintGraph.K2Node_IfThenElse Name="K2Node_IfThenElse_9" - NodePosX=-4640 - NodePosY=1408 - NodeGuid=63298C1D4A4AB94F4E2A538055AF5D64 - CustomProperties Pin (PinId=518067A741A32F42C897CCB64B36EAC9,PinName="execute",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_IfThenElse_26 0D90A3EC45C4604229DB36A57E31A66D,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=30EBFD5F4BC82BE77B28FDA4F50CC358,PinName="Condition",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,DefaultValue="true",AutogeneratedDefaultValue="true",LinkedTo=(K2Node_VariableGet_0 72D44A7F4A6ADC7AE1AE72BC268E31F0,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=27DC9998431C570B93280CA8CABF11B3,PinName="then",PinFriendlyName=NSLOCTEXT("K2Node", "true", "true"),Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_16 F9715EE140ABE890CFCF1987C64EA903,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=F3BA16DC430D974F5611AC92AF73C065,PinName="else",PinFriendlyName=NSLOCTEXT("K2Node", "false", "false"),Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_ExecutionSequence_1 E0659C07472B54B5A53AD0A19DE074B3,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) -End Object -Begin Object Class=/Script/BlueprintGraph.K2Node_MacroInstance Name="K2Node_MacroInstance_2" - MacroGraphReference=(MacroGraph=EdGraph'"/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:DoOnce"',GraphBlueprint=Blueprint'"/Engine/EditorBlueprintResources/StandardMacros.StandardMacros"',GraphGuid=1281F54248A2ECB5B8B2C5B24AE6FDF4) - NodePosX=-4192 - NodePosY=1344 - NodeGuid=992E832A4C4EFBC412AB4EB1B5063FBE - CustomProperties Pin (PinId=9AB8C2EE4BBDF3AD40DABEB89EDFC361,PinName="execute",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_ExecutionSequence_1 9A21E67C4CE497D6220877A881751E75,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=98B385204BC7A4F47CF090844BA24E3C,PinName="Reset",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=DE96853E496057C2A153C381800293BE,PinName="Start Closed",PinType.PinCategory="bool",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) - CustomProperties Pin (PinId=B7646A5849182CB2BF7FDF8D8928DB99,PinName="Completed",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PinType.bSerializeAsSinglePrecisionFloat=False,LinkedTo=(K2Node_CallFunction_71 F9715EE140ABE890CFCF1987C64EA903,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,) -End Object -- GitLab From 91c117e59e2e5f508f7280d00db3fc9f834ba992 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Fri, 2 Dec 2022 12:26:22 +0200 Subject: [PATCH 118/121] Modify rawinput and input values. -RawInput: genUSBaxis 5 and 4 offset = 0.24 -RawInput genUSBAxis 3 offset = 0.23 -Input GenUSBAxis 3 scale value to -5.0 -Input genUSBAxis 5 scale valueto 5.0 --- ExcavatorSimulator/Config/DefaultInput.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ExcavatorSimulator/Config/DefaultInput.ini b/ExcavatorSimulator/Config/DefaultInput.ini index 2f9cb36..2d43663 100644 --- a/ExcavatorSimulator/Config/DefaultInput.ini +++ b/ExcavatorSimulator/Config/DefaultInput.ini @@ -1,7 +1,7 @@ [/Script/RawInput.RawInputSettings] -+DeviceConfigurations=(VendorID="0x046D",ProductID="0xC294",AxisProperties=((bEnabled=False,Key=GenericUSBController_Axis1,Offset=-0.500000),(Key=GenericUSBController_Axis2,bInverted=True,Offset=0.498039),(Key=GenericUSBController_Axis3,Offset=-1.142858),(bEnabled=False,Key=GenericUSBController_Axis4,Offset=1.000000),(Key=GenericUSBController_Axis5),(Key=GenericUSBController_Axis6),(Key=GenericUSBController_Axis7),(Key=GenericUSBController_Axis8)),ButtonProperties=((Key=GenericUSBController_Button1),(Key=GenericUSBController_Button2),(Key=GenericUSBController_Button3),(Key=GenericUSBController_Button4),(Key=GenericUSBController_Button5),(Key=GenericUSBController_Button6),(Key=GenericUSBController_Button7),(Key=GenericUSBController_Button8),(Key=GenericUSBController_Button9),(Key=GenericUSBController_Button10),(Key=GenericUSBController_Button11),(Key=GenericUSBController_Button12),(Key=GenericUSBController_Button13),(Key=GenericUSBController_Button14),(Key=GenericUSBController_Button15),(Key=GenericUSBController_Button16),(Key=GenericUSBController_Button17),(Key=GenericUSBController_Button18),(Key=GenericUSBController_Button19),(Key=GenericUSBController_Button20))) ++DeviceConfigurations=(VendorID="0x2341",ProductID="0x8036",AxisProperties=((bEnabled=False,Key=GenericUSBController_Axis1,bInverted=True),(bEnabled=False,Key=GenericUSBController_Axis2,bInverted=True),(Key=GenericUSBController_Axis3,bInverted=True,Offset=0.230000),(Key=GenericUSBController_Axis4,bInverted=True,Offset=0.240000),(Key=GenericUSBController_Axis5,bInverted=True,Offset=0.240000),(bEnabled=False,Key=GenericUSBController_Axis6,bInverted=True),(bEnabled=False,Key=GenericUSBController_Axis7,bInverted=True),(bEnabled=False,Key=GenericUSBController_Axis8,bInverted=True)),ButtonProperties=((Key=GenericUSBController_Button1),(Key=GenericUSBController_Button2),(Key=GenericUSBController_Button3),(Key=GenericUSBController_Button4),(Key=GenericUSBController_Button5),(Key=GenericUSBController_Button6),(Key=GenericUSBController_Button7),(Key=GenericUSBController_Button8),(Key=GenericUSBController_Button9),(Key=GenericUSBController_Button10),(Key=GenericUSBController_Button11),(Key=GenericUSBController_Button12),(Key=GenericUSBController_Button13),(Key=GenericUSBController_Button14),(Key=GenericUSBController_Button15),(Key=GenericUSBController_Button16),(Key=GenericUSBController_Button17),(Key=GenericUSBController_Button18),(Key=GenericUSBController_Button19),(Key=GenericUSBController_Button20))) bRegisterDefaultDevice=True [/Script/Engine.InputSettings] @@ -151,8 +151,8 @@ DoubleClickTime=0.200000 +AxisMappings=(AxisName="Turn tracks",Scale=-1.000000,Key=D) +AxisMappings=(AxisName="Mouse X",Scale=1.000000,Key=MouseX) +AxisMappings=(AxisName="Mouse Y",Scale=1.000000,Key=MouseY) -+AxisMappings=(AxisName="Move excavator pedals",Scale=-2.000000,Key=GenericUSBController_Axis2) -+AxisMappings=(AxisName="Move excavator pedals",Scale=2.000000,Key=GenericUSBController_Axis3) ++AxisMappings=(AxisName="Move excavator pedals",Scale=-5.000000,Key=GenericUSBController_Axis3) ++AxisMappings=(AxisName="Move excavator pedals",Scale=5.000000,Key=GenericUSBController_Axis5) DefaultPlayerInputClass=/Script/Engine.PlayerInput DefaultInputComponentClass=/Script/Engine.InputComponent DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks -- GitLab From f7c1eaa43e78716e4842c33604d9c2e7016c14e5 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Fri, 2 Dec 2022 15:07:46 +0200 Subject: [PATCH 119/121] Add O key -Pressin O now shows iputdebugs -Unpinned anykey print since rawinput updates input 24/7 --- ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset index 5a8ef1f..b78c93f 100644 --- a/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset +++ b/ExcavatorSimulator/Content/Blueprints/BP_VRController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:995fe5976b2f908bce55bdbe72f4f68f3e10a82becca83640cba3f3340241151 -size 1348319 +oid sha256:405abe34f27a5a9c75625aa1edc18dbcbc13a374db9cc7fe149442cfe077270d +size 1345980 -- GitLab From c8a90d77b2ce5664f88a381e471a04a2da24a6b0 Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Fri, 2 Dec 2022 15:16:34 +0200 Subject: [PATCH 120/121] Change hits to be a reference to hits --- .../ExcavatorSimulator/GroundDeformerComponent.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp index 79728d6..ef4dec2 100644 --- a/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp +++ b/ExcavatorSimulator/Source/ExcavatorSimulator/GroundDeformerComponent.cpp @@ -18,6 +18,7 @@ UGroundDeformerComponent::UGroundDeformerComponent() RemoveSphereRadius = 100.0f; ForwardVectorMultiplier = 30.0f; + VoxelWorldREF = nullptr; } void UGroundDeformerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) @@ -31,15 +32,14 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) TArray<FVoxelProjectionHit> voxelHit; FVoxelLineTraceParameters parameters; FVoxelPaintMaterial PaintMaterial; - - //GetComponentLocation() is WorldLocation, GetForwardVctor() is ForwardVector. - FVector EndVector = Mesh->GetComponentLocation() + (-Mesh->GetRightVector() * ForwardVectorMultiplier); + FVector EndVector = Mesh->GetComponentLocation() + (Mesh->GetUpVector() * ForwardVectorMultiplier); + DrawDebugLine(GetWorld(), Mesh->GetComponentLocation(), EndVector, FColor::Red, false, 5.0f, ECC_WorldStatic, 1.0f); if (GetWorld()->LineTraceSingleByChannel(hit, Mesh->GetComponentLocation(), EndVector, ECC_Visibility)) { DrawDebugLine(GetWorld(), Mesh->GetComponentLocation(), EndVector, FColor::Red, false, 5.0f, ECC_WorldStatic, 1.0f); UVoxelProjectionTools::FindProjectionVoxels(voxelHit, VoxelWorldREF, parameters, hit.Location, Mesh->GetForwardVector(), 100.0f, EVoxelProjectionShape::Square, 50.0f, 100.0f); FVector PositionVector = FVector(hit.ImpactPoint.X, hit.ImpactPoint.Y, hit.ImpactPoint.Z); - for (FVoxelProjectionHit hits : voxelHit) + for (FVoxelProjectionHit &hits : voxelHit) { FVector Convert2DToVector = FVector(hits.PlanePosition.X, hits.PlanePosition.Y, 0.0f); UVoxelDataTools::SetValue(VoxelWorldREF, hits.VoxelPosition, 1.0f); @@ -47,7 +47,7 @@ void UGroundDeformerComponent::DeformGround(UStaticMeshComponent *Mesh) } - //UVoxelSphereTools::SetMaterialSphere(VoxelWorldREF, PositionVector, 200.0f, PaintMaterial); + UVoxelSphereTools::SetMaterialSphere(VoxelWorldREF, PositionVector, 200.0f, PaintMaterial); } } -- GitLab From a52c02fbaba19afd5d85c29ca4b1a5cea1ac440e Mon Sep 17 00:00:00 2001 From: Niko Korkala <nikokorkala5@kamk.fi> Date: Sun, 4 Dec 2022 02:12:06 +0200 Subject: [PATCH 121/121] Add Doxygen folder and doxygen documentation --- .../html/_check_collision_component_8cpp.html | 84 + .../html/_check_collision_component_8h.html | 89 + .../_check_collision_component_8h_source.html | 148 + ...custom_procedural_mesh_component_8cpp.html | 82 + .../_custom_procedural_mesh_component_8h.html | 95 + ...m_procedural_mesh_component_8h_source.html | 156 + Doxygen/html/_excavator_anim_8cpp.html | 82 + Doxygen/html/_excavator_anim_8h.html | 94 + Doxygen/html/_excavator_anim_8h_source.html | 190 ++ Doxygen/html/_excavator_character_8cpp.html | 83 + Doxygen/html/_excavator_character_8h.html | 99 + .../html/_excavator_character_8h_source.html | 203 ++ .../_excavator_simulator_8_build_8cs.html | 89 + Doxygen/html/_excavator_simulator_8cpp.html | 126 + Doxygen/html/_excavator_simulator_8h.html | 84 + .../html/_excavator_simulator_8h_source.html | 87 + ...cavator_simulator_game_mode_base_8cpp.html | 82 + ...excavator_simulator_game_mode_base_8h.html | 95 + ...or_simulator_game_mode_base_8h_source.html | 101 + .../html/_ground_deformer_component_8cpp.html | 90 + .../html/_ground_deformer_component_8h.html | 95 + .../_ground_deformer_component_8h_source.html | 122 + Doxygen/html/_world_generator_8cpp.html | 84 + Doxygen/html/_world_generator_8h.html | 97 + Doxygen/html/_world_generator_8h_source.html | 131 + Doxygen/html/annotated.html | 92 + Doxygen/html/bc_s.png | 3 + Doxygen/html/bc_sd.png | 3 + Doxygen/html/bdwn.png | 3 + ...tom_procedural_mesh_component-members.html | 90 + ...ss_a_custom_procedural_mesh_component.html | 238 ++ ...ass_a_custom_procedural_mesh_component.png | 3 + .../class_a_excavator_character-members.html | 99 + Doxygen/html/class_a_excavator_character.html | 438 +++ Doxygen/html/class_a_excavator_character.png | 3 + ...ator_simulator_game_mode_base-members.html | 83 + ..._a_excavator_simulator_game_mode_base.html | 94 + ...s_a_excavator_simulator_game_mode_base.png | 3 + .../class_excavator_simulator-members.html | 85 + Doxygen/html/class_excavator_simulator.html | 126 + Doxygen/html/class_excavator_simulator.png | 3 + ...ss_f_world_generator_instance-members.html | 91 + .../class_f_world_generator_instance.html | 355 +++ .../html/class_f_world_generator_instance.png | 3 + .../html/class_u_excavator_anim-members.html | 99 + Doxygen/html/class_u_excavator_anim.html | 455 +++ Doxygen/html/class_u_excavator_anim.png | 3 + .../html/class_u_world_generator-members.html | 89 + Doxygen/html/class_u_world_generator.html | 204 ++ Doxygen/html/class_u_world_generator.png | 3 + Doxygen/html/classes.html | 96 + Doxygen/html/closed.png | 3 + Doxygen/html/doc.png | 3 + Doxygen/html/docd.png | 3 + Doxygen/html/doxygen.css | 1973 +++++++++++++ Doxygen/html/doxygen.svg | 26 + Doxygen/html/dynsections.js | 123 + Doxygen/html/files.html | 102 + Doxygen/html/folderclosed.png | 3 + Doxygen/html/folderopen.png | 3 + Doxygen/html/functions.html | 181 ++ Doxygen/html/functions_func.html | 174 ++ Doxygen/html/functions_type.html | 81 + Doxygen/html/functions_vars.html | 82 + Doxygen/html/globals.html | 81 + Doxygen/html/globals_func.html | 81 + Doxygen/html/hierarchy.html | 99 + Doxygen/html/index.html | 81 + Doxygen/html/jquery.js | 34 + Doxygen/html/menu.js | 136 + Doxygen/html/menudata.js | 67 + Doxygen/html/nav_f.png | 3 + Doxygen/html/nav_fd.png | 3 + Doxygen/html/nav_g.png | 3 + Doxygen/html/nav_h.png | 3 + Doxygen/html/nav_hd.png | 3 + Doxygen/html/open.png | 3 + Doxygen/html/search/all_0.js | 6 + Doxygen/html/search/all_1.js | 12 + Doxygen/html/search/all_2.js | 10 + Doxygen/html/search/all_3.js | 13 + Doxygen/html/search/all_4.js | 4 + Doxygen/html/search/all_5.js | 20 + Doxygen/html/search/all_6.js | 5 + Doxygen/html/search/all_7.js | 4 + Doxygen/html/search/all_8.js | 4 + Doxygen/html/search/all_9.js | 10 + Doxygen/html/search/all_a.js | 4 + Doxygen/html/search/all_b.js | 5 + Doxygen/html/search/all_c.js | 5 + Doxygen/html/search/all_d.js | 5 + Doxygen/html/search/all_e.js | 4 + Doxygen/html/search/classes_0.js | 6 + Doxygen/html/search/classes_1.js | 4 + Doxygen/html/search/classes_2.js | 4 + Doxygen/html/search/classes_3.js | 5 + Doxygen/html/search/close.svg | 31 + Doxygen/html/search/files_0.js | 7 + Doxygen/html/search/files_1.js | 12 + Doxygen/html/search/files_2.js | 5 + Doxygen/html/search/files_3.js | 5 + Doxygen/html/search/functions_0.js | 5 + Doxygen/html/search/functions_1.js | 12 + Doxygen/html/search/functions_2.js | 6 + Doxygen/html/search/functions_3.js | 4 + Doxygen/html/search/functions_4.js | 4 + Doxygen/html/search/functions_5.js | 18 + Doxygen/html/search/functions_6.js | 5 + Doxygen/html/search/functions_7.js | 4 + Doxygen/html/search/functions_8.js | 8 + Doxygen/html/search/functions_9.js | 4 + Doxygen/html/search/functions_a.js | 5 + Doxygen/html/search/functions_b.js | 5 + Doxygen/html/search/functions_c.js | 4 + Doxygen/html/search/mag.svg | 37 + Doxygen/html/search/mag_d.svg | 37 + Doxygen/html/search/mag_sel.svg | 74 + Doxygen/html/search/mag_seld.svg | 74 + Doxygen/html/search/search.css | 291 ++ Doxygen/html/search/search.js | 816 ++++++ Doxygen/html/search/searchdata.js | 30 + Doxygen/html/search/typedefs_0.js | 4 + Doxygen/html/search/variables_0.js | 4 + Doxygen/html/search/variables_1.js | 4 + Doxygen/html/splitbar.png | 3 + Doxygen/html/splitbard.png | 3 + Doxygen/html/sync_off.png | 3 + Doxygen/html/sync_on.png | 3 + Doxygen/html/tab_a.png | 3 + Doxygen/html/tab_ad.png | 3 + Doxygen/html/tab_b.png | 3 + Doxygen/html/tab_bd.png | 3 + Doxygen/html/tab_h.png | 3 + Doxygen/html/tab_hd.png | 3 + Doxygen/html/tab_s.png | 3 + Doxygen/html/tab_sd.png | 3 + Doxygen/html/tabs.css | 1 + Doxygen/latex/Makefile | 27 + .../latex/_check_collision_component_8cpp.tex | 5 + .../latex/_check_collision_component_8h.tex | 8 + .../_check_collision_component_8h_source.tex | 70 + ..._custom_procedural_mesh_component_8cpp.tex | 3 + .../_custom_procedural_mesh_component_8h.tex | 11 + ...om_procedural_mesh_component_8h_source.tex | 79 + Doxygen/latex/_excavator_anim_8cpp.tex | 3 + Doxygen/latex/_excavator_anim_8h.tex | 10 + Doxygen/latex/_excavator_anim_8h_source.tex | 107 + Doxygen/latex/_excavator_character_8cpp.tex | 4 + Doxygen/latex/_excavator_character_8h.tex | 15 + .../latex/_excavator_character_8h_source.tex | 122 + .../_excavator_simulator_8_build_8cs.tex | 7 + Doxygen/latex/_excavator_simulator_8cpp.tex | 18 + Doxygen/latex/_excavator_simulator_8h.tex | 3 + .../latex/_excavator_simulator_8h_source.tex | 11 + ...xcavator_simulator_game_mode_base_8cpp.tex | 3 + ..._excavator_simulator_game_mode_base_8h.tex | 11 + ...tor_simulator_game_mode_base_8h_source.tex | 22 + .../latex/_ground_deformer_component_8cpp.tex | 11 + .../latex/_ground_deformer_component_8h.tex | 14 + .../_ground_deformer_component_8h_source.tex | 46 + Doxygen/latex/_world_generator_8cpp.tex | 5 + Doxygen/latex/_world_generator_8h.tex | 13 + Doxygen/latex/_world_generator_8h_source.tex | 52 + Doxygen/latex/annotated.tex | 10 + ...ass_a_custom_procedural_mesh_component.eps | 197 ++ ...ass_a_custom_procedural_mesh_component.tex | 78 + Doxygen/latex/class_a_excavator_character.eps | 197 ++ Doxygen/latex/class_a_excavator_character.tex | 150 + ...s_a_excavator_simulator_game_mode_base.eps | 197 ++ ...s_a_excavator_simulator_game_mode_base.tex | 17 + Doxygen/latex/class_excavator_simulator.eps | 197 ++ Doxygen/latex/class_excavator_simulator.tex | 27 + .../class_f_world_generator_instance.eps | 197 ++ .../class_f_world_generator_instance.tex | 89 + Doxygen/latex/class_u_excavator_anim.eps | 197 ++ Doxygen/latex/class_u_excavator_anim.tex | 147 + Doxygen/latex/class_u_world_generator.eps | 197 ++ Doxygen/latex/class_u_world_generator.tex | 73 + Doxygen/latex/doxygen.sty | 596 ++++ Doxygen/latex/files.tex | 20 + Doxygen/latex/hierarchy.tex | 24 + Doxygen/latex/longtable_doxygen.sty | 448 +++ Doxygen/latex/make.bat | 56 + Doxygen/latex/refman.tex | 228 ++ Doxygen/latex/tabu_doxygen.sty | 2557 +++++++++++++++++ 185 files changed, 17067 insertions(+) create mode 100644 Doxygen/html/_check_collision_component_8cpp.html create mode 100644 Doxygen/html/_check_collision_component_8h.html create mode 100644 Doxygen/html/_check_collision_component_8h_source.html create mode 100644 Doxygen/html/_custom_procedural_mesh_component_8cpp.html create mode 100644 Doxygen/html/_custom_procedural_mesh_component_8h.html create mode 100644 Doxygen/html/_custom_procedural_mesh_component_8h_source.html create mode 100644 Doxygen/html/_excavator_anim_8cpp.html create mode 100644 Doxygen/html/_excavator_anim_8h.html create mode 100644 Doxygen/html/_excavator_anim_8h_source.html create mode 100644 Doxygen/html/_excavator_character_8cpp.html create mode 100644 Doxygen/html/_excavator_character_8h.html create mode 100644 Doxygen/html/_excavator_character_8h_source.html create mode 100644 Doxygen/html/_excavator_simulator_8_build_8cs.html create mode 100644 Doxygen/html/_excavator_simulator_8cpp.html create mode 100644 Doxygen/html/_excavator_simulator_8h.html create mode 100644 Doxygen/html/_excavator_simulator_8h_source.html create mode 100644 Doxygen/html/_excavator_simulator_game_mode_base_8cpp.html create mode 100644 Doxygen/html/_excavator_simulator_game_mode_base_8h.html create mode 100644 Doxygen/html/_excavator_simulator_game_mode_base_8h_source.html create mode 100644 Doxygen/html/_ground_deformer_component_8cpp.html create mode 100644 Doxygen/html/_ground_deformer_component_8h.html create mode 100644 Doxygen/html/_ground_deformer_component_8h_source.html create mode 100644 Doxygen/html/_world_generator_8cpp.html create mode 100644 Doxygen/html/_world_generator_8h.html create mode 100644 Doxygen/html/_world_generator_8h_source.html create mode 100644 Doxygen/html/annotated.html create mode 100644 Doxygen/html/bc_s.png create mode 100644 Doxygen/html/bc_sd.png create mode 100644 Doxygen/html/bdwn.png create mode 100644 Doxygen/html/class_a_custom_procedural_mesh_component-members.html create mode 100644 Doxygen/html/class_a_custom_procedural_mesh_component.html create mode 100644 Doxygen/html/class_a_custom_procedural_mesh_component.png create mode 100644 Doxygen/html/class_a_excavator_character-members.html create mode 100644 Doxygen/html/class_a_excavator_character.html create mode 100644 Doxygen/html/class_a_excavator_character.png create mode 100644 Doxygen/html/class_a_excavator_simulator_game_mode_base-members.html create mode 100644 Doxygen/html/class_a_excavator_simulator_game_mode_base.html create mode 100644 Doxygen/html/class_a_excavator_simulator_game_mode_base.png create mode 100644 Doxygen/html/class_excavator_simulator-members.html create mode 100644 Doxygen/html/class_excavator_simulator.html create mode 100644 Doxygen/html/class_excavator_simulator.png create mode 100644 Doxygen/html/class_f_world_generator_instance-members.html create mode 100644 Doxygen/html/class_f_world_generator_instance.html create mode 100644 Doxygen/html/class_f_world_generator_instance.png create mode 100644 Doxygen/html/class_u_excavator_anim-members.html create mode 100644 Doxygen/html/class_u_excavator_anim.html create mode 100644 Doxygen/html/class_u_excavator_anim.png create mode 100644 Doxygen/html/class_u_world_generator-members.html create mode 100644 Doxygen/html/class_u_world_generator.html create mode 100644 Doxygen/html/class_u_world_generator.png create mode 100644 Doxygen/html/classes.html create mode 100644 Doxygen/html/closed.png create mode 100644 Doxygen/html/doc.png create mode 100644 Doxygen/html/docd.png create mode 100644 Doxygen/html/doxygen.css create mode 100644 Doxygen/html/doxygen.svg create mode 100644 Doxygen/html/dynsections.js create mode 100644 Doxygen/html/files.html create mode 100644 Doxygen/html/folderclosed.png create mode 100644 Doxygen/html/folderopen.png create mode 100644 Doxygen/html/functions.html create mode 100644 Doxygen/html/functions_func.html create mode 100644 Doxygen/html/functions_type.html create mode 100644 Doxygen/html/functions_vars.html create mode 100644 Doxygen/html/globals.html create mode 100644 Doxygen/html/globals_func.html create mode 100644 Doxygen/html/hierarchy.html create mode 100644 Doxygen/html/index.html create mode 100644 Doxygen/html/jquery.js create mode 100644 Doxygen/html/menu.js create mode 100644 Doxygen/html/menudata.js create mode 100644 Doxygen/html/nav_f.png create mode 100644 Doxygen/html/nav_fd.png create mode 100644 Doxygen/html/nav_g.png create mode 100644 Doxygen/html/nav_h.png create mode 100644 Doxygen/html/nav_hd.png create mode 100644 Doxygen/html/open.png create mode 100644 Doxygen/html/search/all_0.js create mode 100644 Doxygen/html/search/all_1.js create mode 100644 Doxygen/html/search/all_2.js create mode 100644 Doxygen/html/search/all_3.js create mode 100644 Doxygen/html/search/all_4.js create mode 100644 Doxygen/html/search/all_5.js create mode 100644 Doxygen/html/search/all_6.js create mode 100644 Doxygen/html/search/all_7.js create mode 100644 Doxygen/html/search/all_8.js create mode 100644 Doxygen/html/search/all_9.js create mode 100644 Doxygen/html/search/all_a.js create mode 100644 Doxygen/html/search/all_b.js create mode 100644 Doxygen/html/search/all_c.js create mode 100644 Doxygen/html/search/all_d.js create mode 100644 Doxygen/html/search/all_e.js create mode 100644 Doxygen/html/search/classes_0.js create mode 100644 Doxygen/html/search/classes_1.js create mode 100644 Doxygen/html/search/classes_2.js create mode 100644 Doxygen/html/search/classes_3.js create mode 100644 Doxygen/html/search/close.svg create mode 100644 Doxygen/html/search/files_0.js create mode 100644 Doxygen/html/search/files_1.js create mode 100644 Doxygen/html/search/files_2.js create mode 100644 Doxygen/html/search/files_3.js create mode 100644 Doxygen/html/search/functions_0.js create mode 100644 Doxygen/html/search/functions_1.js create mode 100644 Doxygen/html/search/functions_2.js create mode 100644 Doxygen/html/search/functions_3.js create mode 100644 Doxygen/html/search/functions_4.js create mode 100644 Doxygen/html/search/functions_5.js create mode 100644 Doxygen/html/search/functions_6.js create mode 100644 Doxygen/html/search/functions_7.js create mode 100644 Doxygen/html/search/functions_8.js create mode 100644 Doxygen/html/search/functions_9.js create mode 100644 Doxygen/html/search/functions_a.js create mode 100644 Doxygen/html/search/functions_b.js create mode 100644 Doxygen/html/search/functions_c.js create mode 100644 Doxygen/html/search/mag.svg create mode 100644 Doxygen/html/search/mag_d.svg create mode 100644 Doxygen/html/search/mag_sel.svg create mode 100644 Doxygen/html/search/mag_seld.svg create mode 100644 Doxygen/html/search/search.css create mode 100644 Doxygen/html/search/search.js create mode 100644 Doxygen/html/search/searchdata.js create mode 100644 Doxygen/html/search/typedefs_0.js create mode 100644 Doxygen/html/search/variables_0.js create mode 100644 Doxygen/html/search/variables_1.js create mode 100644 Doxygen/html/splitbar.png create mode 100644 Doxygen/html/splitbard.png create mode 100644 Doxygen/html/sync_off.png create mode 100644 Doxygen/html/sync_on.png create mode 100644 Doxygen/html/tab_a.png create mode 100644 Doxygen/html/tab_ad.png create mode 100644 Doxygen/html/tab_b.png create mode 100644 Doxygen/html/tab_bd.png create mode 100644 Doxygen/html/tab_h.png create mode 100644 Doxygen/html/tab_hd.png create mode 100644 Doxygen/html/tab_s.png create mode 100644 Doxygen/html/tab_sd.png create mode 100644 Doxygen/html/tabs.css create mode 100644 Doxygen/latex/Makefile create mode 100644 Doxygen/latex/_check_collision_component_8cpp.tex create mode 100644 Doxygen/latex/_check_collision_component_8h.tex create mode 100644 Doxygen/latex/_check_collision_component_8h_source.tex create mode 100644 Doxygen/latex/_custom_procedural_mesh_component_8cpp.tex create mode 100644 Doxygen/latex/_custom_procedural_mesh_component_8h.tex create mode 100644 Doxygen/latex/_custom_procedural_mesh_component_8h_source.tex create mode 100644 Doxygen/latex/_excavator_anim_8cpp.tex create mode 100644 Doxygen/latex/_excavator_anim_8h.tex create mode 100644 Doxygen/latex/_excavator_anim_8h_source.tex create mode 100644 Doxygen/latex/_excavator_character_8cpp.tex create mode 100644 Doxygen/latex/_excavator_character_8h.tex create mode 100644 Doxygen/latex/_excavator_character_8h_source.tex create mode 100644 Doxygen/latex/_excavator_simulator_8_build_8cs.tex create mode 100644 Doxygen/latex/_excavator_simulator_8cpp.tex create mode 100644 Doxygen/latex/_excavator_simulator_8h.tex create mode 100644 Doxygen/latex/_excavator_simulator_8h_source.tex create mode 100644 Doxygen/latex/_excavator_simulator_game_mode_base_8cpp.tex create mode 100644 Doxygen/latex/_excavator_simulator_game_mode_base_8h.tex create mode 100644 Doxygen/latex/_excavator_simulator_game_mode_base_8h_source.tex create mode 100644 Doxygen/latex/_ground_deformer_component_8cpp.tex create mode 100644 Doxygen/latex/_ground_deformer_component_8h.tex create mode 100644 Doxygen/latex/_ground_deformer_component_8h_source.tex create mode 100644 Doxygen/latex/_world_generator_8cpp.tex create mode 100644 Doxygen/latex/_world_generator_8h.tex create mode 100644 Doxygen/latex/_world_generator_8h_source.tex create mode 100644 Doxygen/latex/annotated.tex create mode 100644 Doxygen/latex/class_a_custom_procedural_mesh_component.eps create mode 100644 Doxygen/latex/class_a_custom_procedural_mesh_component.tex create mode 100644 Doxygen/latex/class_a_excavator_character.eps create mode 100644 Doxygen/latex/class_a_excavator_character.tex create mode 100644 Doxygen/latex/class_a_excavator_simulator_game_mode_base.eps create mode 100644 Doxygen/latex/class_a_excavator_simulator_game_mode_base.tex create mode 100644 Doxygen/latex/class_excavator_simulator.eps create mode 100644 Doxygen/latex/class_excavator_simulator.tex create mode 100644 Doxygen/latex/class_f_world_generator_instance.eps create mode 100644 Doxygen/latex/class_f_world_generator_instance.tex create mode 100644 Doxygen/latex/class_u_excavator_anim.eps create mode 100644 Doxygen/latex/class_u_excavator_anim.tex create mode 100644 Doxygen/latex/class_u_world_generator.eps create mode 100644 Doxygen/latex/class_u_world_generator.tex create mode 100644 Doxygen/latex/doxygen.sty create mode 100644 Doxygen/latex/files.tex create mode 100644 Doxygen/latex/hierarchy.tex create mode 100644 Doxygen/latex/longtable_doxygen.sty create mode 100644 Doxygen/latex/make.bat create mode 100644 Doxygen/latex/refman.tex create mode 100644 Doxygen/latex/tabu_doxygen.sty diff --git a/Doxygen/html/_check_collision_component_8cpp.html b/Doxygen/html/_check_collision_component_8cpp.html new file mode 100644 index 0000000..cbf1af8 --- /dev/null +++ b/Doxygen/html/_check_collision_component_8cpp.html @@ -0,0 +1,84 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: CheckCollisionComponent.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">CheckCollisionComponent.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "<a class="el" href="_check_collision_component_8h_source.html">CheckCollisionComponent.h</a>"</code><br /> +<code>#include "<a class="el" href="_excavator_character_8h_source.html">ExcavatorCharacter.h</a>"</code><br /> +<code>#include "Engine/World.h"</code><br /> +</div></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_check_collision_component_8h.html b/Doxygen/html/_check_collision_component_8h.html new file mode 100644 index 0000000..1d3b937 --- /dev/null +++ b/Doxygen/html/_check_collision_component_8h.html @@ -0,0 +1,89 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: CheckCollisionComponent.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">CheckCollisionComponent.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +<code>#include "Components/ActorComponent.h"</code><br /> +<code>#include <Components/BoxComponent.h></code><br /> +<code>#include "<a class="el" href="_excavator_anim_8h_source.html">ExcavatorAnim.h</a>"</code><br /> +<code>#include "VoxelWorld.h"</code><br /> +<code>#include "CheckCollisionComponent.generated.h"</code><br /> +</div> +<p><a href="_check_collision_component_8h_source.html">Go to the source code of this file.</a></p> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_check_collision_component_8h_source.html b/Doxygen/html/_check_collision_component_8h_source.html new file mode 100644 index 0000000..9db5af0 --- /dev/null +++ b/Doxygen/html/_check_collision_component_8h_source.html @@ -0,0 +1,148 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: CheckCollisionComponent.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">CheckCollisionComponent.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_check_collision_component_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Fill out your copyright notice in the Description page of Project Settings.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span><span class="preprocessor">#include "Components/ActorComponent.h"</span></div> +<div class="line"><a id="l00007" name="l00007"></a><span class="lineno"> 7</span><span class="preprocessor">#include <Components/BoxComponent.h></span></div> +<div class="line"><a id="l00008" name="l00008"></a><span class="lineno"> 8</span><span class="preprocessor">#include "<a class="code" href="_excavator_anim_8h.html">ExcavatorAnim.h</a>"</span></div> +<div class="line"><a id="l00009" name="l00009"></a><span class="lineno"> 9</span><span class="preprocessor">#include "VoxelWorld.h"</span></div> +<div class="line"><a id="l00010" name="l00010"></a><span class="lineno"> 10</span><span class="preprocessor">#include "CheckCollisionComponent.generated.h"</span></div> +<div class="line"><a id="l00011" name="l00011"></a><span class="lineno"> 11</span> </div> +<div class="line"><a id="l00012" name="l00012"></a><span class="lineno"> 12</span>UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )</div> +<div class="line"><a id="l00013" name="l00013"></a><span class="lineno"> 13</span><span class="keyword">class </span>EXCAVATORSIMULATOR_API UCheckCollisionComponent : <span class="keyword">public</span> UActorComponent</div> +<div class="line"><a id="l00014" name="l00014"></a><span class="lineno"> 14</span>{</div> +<div class="line"><a id="l00015" name="l00015"></a><span class="lineno"> 15</span> GENERATED_BODY()</div> +<div class="line"><a id="l00016" name="l00016"></a><span class="lineno"> 16</span> </div> +<div class="line"><a id="l00017" name="l00017"></a><span class="lineno"> 17</span>public: </div> +<div class="line"><a id="l00018" name="l00018"></a><span class="lineno"> 18</span> <span class="comment">// Sets default values for this component's properties</span></div> +<div class="line"><a id="l00019" name="l00019"></a><span class="lineno"> 19</span> UCheckCollisionComponent();</div> +<div class="line"><a id="l00020" name="l00020"></a><span class="lineno"> 20</span> </div> +<div class="line"><a id="l00022" name="l00022"></a><span class="lineno"> 22</span> <span class="keywordtype">void</span> CollisionCheck();</div> +<div class="line"><a id="l00023" name="l00023"></a><span class="lineno"> 23</span> </div> +<div class="line"><a id="l00028" name="l00028"></a><span class="lineno"> 28</span> <span class="keywordtype">bool</span> IsCollidingLR(<span class="keywordtype">float</span> rotationDirection);</div> +<div class="line"><a id="l00029" name="l00029"></a><span class="lineno"> 29</span> </div> +<div class="line"><a id="l00034" name="l00034"></a><span class="lineno"> 34</span> <span class="keywordtype">bool</span> IsCollidingFB(<span class="keywordtype">float</span> movingDirection);</div> +<div class="line"><a id="l00035" name="l00035"></a><span class="lineno"> 35</span> </div> +<div class="line"><a id="l00036" name="l00036"></a><span class="lineno"> 36</span> <span class="comment">// Called every frame</span></div> +<div class="line"><a id="l00037" name="l00037"></a><span class="lineno"> 37</span> virtual <span class="keywordtype">void</span> TickComponent(<span class="keywordtype">float</span> DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;</div> +<div class="line"><a id="l00038" name="l00038"></a><span class="lineno"> 38</span> </div> +<div class="line"><a id="l00039" name="l00039"></a><span class="lineno"> 39</span>protected:</div> +<div class="line"><a id="l00040" name="l00040"></a><span class="lineno"> 40</span> <span class="comment">// Called when the game starts</span></div> +<div class="line"><a id="l00041" name="l00041"></a><span class="lineno"> 41</span> virtual <span class="keywordtype">void</span> BeginPlay() override; </div> +<div class="line"><a id="l00042" name="l00042"></a><span class="lineno"> 42</span> </div> +<div class="line"><a id="l00043" name="l00043"></a><span class="lineno"> 43</span>private:</div> +<div class="line"><a id="l00045" name="l00045"></a><span class="lineno"> 45</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00046" name="l00046"></a><span class="lineno"> 46</span> <span class="keywordtype">bool</span> BucketCollidingFromDown;</div> +<div class="line"><a id="l00047" name="l00047"></a><span class="lineno"> 47</span> </div> +<div class="line"><a id="l00049" name="l00049"></a><span class="lineno"> 49</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00050" name="l00050"></a><span class="lineno"> 50</span> <span class="keywordtype">bool</span> BucketCollidingFromFront;</div> +<div class="line"><a id="l00051" name="l00051"></a><span class="lineno"> 51</span> </div> +<div class="line"><a id="l00053" name="l00053"></a><span class="lineno"> 53</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00054" name="l00054"></a><span class="lineno"> 54</span> <span class="keywordtype">bool</span> BucketCollidingFromLeft;</div> +<div class="line"><a id="l00055" name="l00055"></a><span class="lineno"> 55</span> </div> +<div class="line"><a id="l00057" name="l00057"></a><span class="lineno"> 57</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision Bools", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00058" name="l00058"></a><span class="lineno"> 58</span> <span class="keywordtype">bool</span> BucketCollidingFromRight;</div> +<div class="line"><a id="l00059" name="l00059"></a><span class="lineno"> 59</span> </div> +<div class="line"><a id="l00061" name="l00061"></a><span class="lineno"> 61</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00062" name="l00062"></a><span class="lineno"> 62</span> <span class="keywordtype">float</span> CollisionLengthDown;</div> +<div class="line"><a id="l00063" name="l00063"></a><span class="lineno"> 63</span> </div> +<div class="line"><a id="l00065" name="l00065"></a><span class="lineno"> 65</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00066" name="l00066"></a><span class="lineno"> 66</span> <span class="keywordtype">float</span> CollisionLengthLeftAndRight;</div> +<div class="line"><a id="l00067" name="l00067"></a><span class="lineno"> 67</span> </div> +<div class="line"><a id="l00069" name="l00069"></a><span class="lineno"> 69</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collision varbles", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00070" name="l00070"></a><span class="lineno"> 70</span> <span class="keywordtype">float</span> CollisionLengthFront;</div> +<div class="line"><a id="l00071" name="l00071"></a><span class="lineno"> 71</span> </div> +<div class="line"><a id="l00073" name="l00073"></a><span class="lineno"> 73</span> USkeletalMeshComponent* mesh;</div> +<div class="line"><a id="l00074" name="l00074"></a><span class="lineno"> 74</span> </div> +<div class="line"><a id="l00076" name="l00076"></a><span class="lineno"> 76</span> ACharacter* CharacterREF;</div> +<div class="line"><a id="l00077" name="l00077"></a><span class="lineno"> 77</span> </div> +<div class="line"><a id="l00079" name="l00079"></a><span class="lineno"> 79</span> AVoxelWorld* VoxelWorldREF;</div> +<div class="line"><a id="l00080" name="l00080"></a><span class="lineno"> 80</span> </div> +<div class="line"><a id="l00082" name="l00082"></a><span class="lineno"> 82</span> <a class="code hl_class" href="class_u_excavator_anim.html">UExcavatorAnim</a>* ExcavatorAnimREF;</div> +<div class="line"><a id="l00083" name="l00083"></a><span class="lineno"> 83</span> </div> +<div class="line"><a id="l00085" name="l00085"></a><span class="lineno"> 85</span> TArray<AActor*, FDefaultAllocator> PhysicksObjects;</div> +<div class="line"><a id="l00086" name="l00086"></a><span class="lineno"> 86</span>};</div> +<div class="ttc" id="a_excavator_anim_8h_html"><div class="ttname"><a href="_excavator_anim_8h.html">ExcavatorAnim.h</a></div></div> +<div class="ttc" id="aclass_u_excavator_anim_html"><div class="ttname"><a href="class_u_excavator_anim.html">UExcavatorAnim</a></div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:14</div></div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_custom_procedural_mesh_component_8cpp.html b/Doxygen/html/_custom_procedural_mesh_component_8cpp.html new file mode 100644 index 0000000..9712f79 --- /dev/null +++ b/Doxygen/html/_custom_procedural_mesh_component_8cpp.html @@ -0,0 +1,82 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: CustomProceduralMeshComponent.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">CustomProceduralMeshComponent.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "<a class="el" href="_custom_procedural_mesh_component_8h_source.html">CustomProceduralMeshComponent.h</a>"</code><br /> +</div></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_custom_procedural_mesh_component_8h.html b/Doxygen/html/_custom_procedural_mesh_component_8h.html new file mode 100644 index 0000000..c2d1169 --- /dev/null +++ b/Doxygen/html/_custom_procedural_mesh_component_8h.html @@ -0,0 +1,95 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: CustomProceduralMeshComponent.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#nested-classes">Classes</a> </div> + <div class="headertitle"><div class="title">CustomProceduralMeshComponent.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +<code>#include "GameFramework/Actor.h"</code><br /> +<code>#include "ProceduralMeshComponent.h"</code><br /> +<code>#include "CustomProceduralMeshComponent.generated.h"</code><br /> +</div> +<p><a href="_custom_procedural_mesh_component_8h_source.html">Go to the source code of this file.</a></p> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="nested-classes" name="nested-classes"></a> +Classes</h2></td></tr> +<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></td></tr> +<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> +</table> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_custom_procedural_mesh_component_8h_source.html b/Doxygen/html/_custom_procedural_mesh_component_8h_source.html new file mode 100644 index 0000000..5106fe8 --- /dev/null +++ b/Doxygen/html/_custom_procedural_mesh_component_8h_source.html @@ -0,0 +1,156 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: CustomProceduralMeshComponent.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">CustomProceduralMeshComponent.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_custom_procedural_mesh_component_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Fill out your copyright notice in the Description page of Project Settings.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span><span class="preprocessor">#include "GameFramework/Actor.h"</span></div> +<div class="line"><a id="l00007" name="l00007"></a><span class="lineno"> 7</span><span class="preprocessor">#include "ProceduralMeshComponent.h"</span></div> +<div class="line"><a id="l00008" name="l00008"></a><span class="lineno"> 8</span><span class="preprocessor">#include "CustomProceduralMeshComponent.generated.h"</span></div> +<div class="line"><a id="l00009" name="l00009"></a><span class="lineno"> 9</span> </div> +<div class="line"><a id="l00010" name="l00010"></a><span class="lineno"> 10</span>UCLASS()</div> +<div class="line"><a id="l00011" name="l00011"></a><span class="lineno"><a class="line" href="class_a_custom_procedural_mesh_component.html"> 11</a></span>class EXCAVATORSIMULATOR_API <a class="code hl_class" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a> : public AActor</div> +<div class="line"><a id="l00012" name="l00012"></a><span class="lineno"> 12</span>{</div> +<div class="line"><a id="l00013" name="l00013"></a><span class="lineno"> 13</span> GENERATED_BODY()</div> +<div class="line"><a id="l00014" name="l00014"></a><span class="lineno"> 14</span> </div> +<div class="line"><a id="l00015" name="l00015"></a><span class="lineno"> 15</span><span class="keyword">public</span>: </div> +<div class="line"><a id="l00016" name="l00016"></a><span class="lineno"> 16</span> <span class="comment">// Sets default values for this actor's properties</span></div> +<div class="line"><a id="l00017" name="l00017"></a><span class="lineno"> 17</span> <a class="code hl_class" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a>();</div> +<div class="line"><a id="l00018" name="l00018"></a><span class="lineno"> 18</span> </div> +<div class="line"><a id="l00019" name="l00019"></a><span class="lineno"> 19</span> <span class="comment">// Called every frame</span></div> +<div class="line"><a id="l00020" name="l00020"></a><span class="lineno"> 20</span> <span class="keyword">virtual</span> <span class="keywordtype">void</span> Tick(<span class="keywordtype">float</span> DeltaTime) <span class="keyword">override</span>;</div> +<div class="line"><a id="l00021" name="l00021"></a><span class="lineno"> 21</span> </div> +<div class="line"><a id="l00022" name="l00022"></a><span class="lineno"> 22</span> <span class="keywordtype">void</span> CreateTriangle();</div> +<div class="line"><a id="l00023" name="l00023"></a><span class="lineno"> 23</span> <span class="keywordtype">void</span> CreateWall();</div> +<div class="line"><a id="l00024" name="l00024"></a><span class="lineno"> 24</span> </div> +<div class="line"><a id="l00025" name="l00025"></a><span class="lineno"> 25</span> <span class="keywordtype">void</span> CreateVertices(<span class="keywordtype">int</span> layer);</div> +<div class="line"><a id="l00026" name="l00026"></a><span class="lineno"> 26</span> </div> +<div class="line"><a id="l00027" name="l00027"></a><span class="lineno"> 27</span><span class="keyword">protected</span>:</div> +<div class="line"><a id="l00028" name="l00028"></a><span class="lineno"> 28</span> <span class="comment">// Called when the game starts or when spawned</span></div> +<div class="line"><a id="l00029" name="l00029"></a><span class="lineno"> 29</span> <span class="keyword">virtual</span> <span class="keywordtype">void</span> BeginPlay() <span class="keyword">override</span>;</div> +<div class="line"><a id="l00030" name="l00030"></a><span class="lineno"> 30</span> </div> +<div class="line"><a id="l00031" name="l00031"></a><span class="lineno"> 31</span><span class="keyword">private</span>:</div> +<div class="line"><a id="l00033" name="l00033"></a><span class="lineno"> 33</span> UPROPERTY(VisibleAnywhere)</div> +<div class="line"><a id="l00034" name="l00034"></a><span class="lineno"> 34</span> UProceduralMeshComponent* Mesh;</div> +<div class="line"><a id="l00035" name="l00035"></a><span class="lineno"> 35</span> </div> +<div class="line"><a id="l00036" name="l00036"></a><span class="lineno"> 36</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00037" name="l00037"></a><span class="lineno"> 37</span> TArray<FVector> Vertices;</div> +<div class="line"><a id="l00038" name="l00038"></a><span class="lineno"> 38</span> </div> +<div class="line"><a id="l00039" name="l00039"></a><span class="lineno"> 39</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00040" name="l00040"></a><span class="lineno"> 40</span> TArray<int32> Triangles;</div> +<div class="line"><a id="l00041" name="l00041"></a><span class="lineno"> 41</span> </div> +<div class="line"><a id="l00042" name="l00042"></a><span class="lineno"> 42</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00043" name="l00043"></a><span class="lineno"> 43</span> TArray<FVector> normals;</div> +<div class="line"><a id="l00044" name="l00044"></a><span class="lineno"> 44</span> </div> +<div class="line"><a id="l00045" name="l00045"></a><span class="lineno"> 45</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00046" name="l00046"></a><span class="lineno"> 46</span> TArray<FVector2D> UV0;</div> +<div class="line"><a id="l00047" name="l00047"></a><span class="lineno"> 47</span> </div> +<div class="line"><a id="l00048" name="l00048"></a><span class="lineno"> 48</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00049" name="l00049"></a><span class="lineno"> 49</span> TArray<FProcMeshTangent> tangents;</div> +<div class="line"><a id="l00050" name="l00050"></a><span class="lineno"> 50</span> </div> +<div class="line"><a id="l00051" name="l00051"></a><span class="lineno"> 51</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00052" name="l00052"></a><span class="lineno"> 52</span> TArray<FLinearColor> vertexColors;</div> +<div class="line"><a id="l00053" name="l00053"></a><span class="lineno"> 53</span> </div> +<div class="line"><a id="l00054" name="l00054"></a><span class="lineno"> 54</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00055" name="l00055"></a><span class="lineno"> 55</span> UMaterialInterface* Material;</div> +<div class="line"><a id="l00056" name="l00056"></a><span class="lineno"> 56</span> </div> +<div class="line"><a id="l00057" name="l00057"></a><span class="lineno"> 57</span> UPROPERTY(EditAnywhere, Meta = (ClampMin = 0))</div> +<div class="line"><a id="l00058" name="l00058"></a><span class="lineno"> 58</span> <span class="keywordtype">int</span> Xsize = 0;</div> +<div class="line"><a id="l00059" name="l00059"></a><span class="lineno"> 59</span> </div> +<div class="line"><a id="l00060" name="l00060"></a><span class="lineno"> 60</span> UPROPERTY(EditAnywhere, Meta = (ClampMin = 0))</div> +<div class="line"><a id="l00061" name="l00061"></a><span class="lineno"> 61</span> <span class="keywordtype">int</span> Ysize = 0;</div> +<div class="line"><a id="l00062" name="l00062"></a><span class="lineno"> 62</span> </div> +<div class="line"><a id="l00063" name="l00063"></a><span class="lineno"> 63</span> UPROPERTY(EditAnywhere, Meta = (ClampMin = 0))</div> +<div class="line"><a id="l00064" name="l00064"></a><span class="lineno"> 64</span> <span class="keywordtype">int</span> Zsize = 0;</div> +<div class="line"><a id="l00065" name="l00065"></a><span class="lineno"> 65</span> </div> +<div class="line"><a id="l00066" name="l00066"></a><span class="lineno"> 66</span> UPROPERTY(EditAnywhere, Meta = (ClampMin = 0.000001))</div> +<div class="line"><a id="l00067" name="l00067"></a><span class="lineno"> 67</span> <span class="keywordtype">float</span> scale = 0;</div> +<div class="line"><a id="l00068" name="l00068"></a><span class="lineno"> 68</span> </div> +<div class="line"><a id="l00069" name="l00069"></a><span class="lineno"> 69</span> UPROPERTY(EditAnywhere, Meta = (ClampMin = 0.000001))</div> +<div class="line"><a id="l00070" name="l00070"></a><span class="lineno"> 70</span> <span class="keywordtype">float</span> UVscale = 0;</div> +<div class="line"><a id="l00071" name="l00071"></a><span class="lineno"> 71</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00072" name="l00072"></a><span class="lineno"> 72</span> <span class="keywordtype">float</span> Zmin = 0;</div> +<div class="line"><a id="l00073" name="l00073"></a><span class="lineno"> 73</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, category = <span class="stringliteral">"Arrays"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00074" name="l00074"></a><span class="lineno"> 74</span> <span class="keywordtype">float</span> Zmax = 0;</div> +<div class="line"><a id="l00075" name="l00075"></a><span class="lineno"> 75</span>};</div> +<div class="ttc" id="aclass_a_custom_procedural_mesh_component_html"><div class="ttname"><a href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></div><div class="ttdef"><b>Definition:</b> CustomProceduralMeshComponent.h:12</div></div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_anim_8cpp.html b/Doxygen/html/_excavator_anim_8cpp.html new file mode 100644 index 0000000..2a3cab4 --- /dev/null +++ b/Doxygen/html/_excavator_anim_8cpp.html @@ -0,0 +1,82 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorAnim.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorAnim.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "<a class="el" href="_excavator_anim_8h_source.html">ExcavatorAnim.h</a>"</code><br /> +</div></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_anim_8h.html b/Doxygen/html/_excavator_anim_8h.html new file mode 100644 index 0000000..8a3d064 --- /dev/null +++ b/Doxygen/html/_excavator_anim_8h.html @@ -0,0 +1,94 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorAnim.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#nested-classes">Classes</a> </div> + <div class="headertitle"><div class="title">ExcavatorAnim.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +<code>#include "Animation/AnimInstance.h"</code><br /> +<code>#include "ExcavatorAnim.generated.h"</code><br /> +</div> +<p><a href="_excavator_anim_8h_source.html">Go to the source code of this file.</a></p> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="nested-classes" name="nested-classes"></a> +Classes</h2></td></tr> +<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td></tr> +<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> +</table> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_anim_8h_source.html b/Doxygen/html/_excavator_anim_8h_source.html new file mode 100644 index 0000000..9269c0e --- /dev/null +++ b/Doxygen/html/_excavator_anim_8h_source.html @@ -0,0 +1,190 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorAnim.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorAnim.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_excavator_anim_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Fill out your copyright notice in the Description page of Project Settings.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span><span class="preprocessor">#include "Animation/AnimInstance.h"</span></div> +<div class="line"><a id="l00007" name="l00007"></a><span class="lineno"> 7</span><span class="preprocessor">#include "ExcavatorAnim.generated.h"</span></div> +<div class="line"><a id="l00008" name="l00008"></a><span class="lineno"> 8</span> </div> +<div class="line"><a id="l00012" name="l00012"></a><span class="lineno"> 12</span>UCLASS()</div> +<div class="line"><a id="l00013" name="l00013"></a><span class="lineno"><a class="line" href="class_u_excavator_anim.html"> 13</a></span>class EXCAVATORSIMULATOR_API <a class="code hl_class" href="class_u_excavator_anim.html">UExcavatorAnim</a> : public UAnimInstance</div> +<div class="line"><a id="l00014" name="l00014"></a><span class="lineno"> 14</span>{</div> +<div class="line"><a id="l00015" name="l00015"></a><span class="lineno"> 15</span> GENERATED_BODY()</div> +<div class="line"><a id="l00016" name="l00016"></a><span class="lineno"> 16</span><span class="keyword">public</span>:</div> +<div class="line"><a id="l00017" name="l00017"></a><span class="lineno"> 17</span> </div> +<div class="line"><a id="l00019" name="l00019"></a><span class="lineno"> 19</span> <a class="code hl_class" href="class_u_excavator_anim.html">UExcavatorAnim</a>(<span class="keyword">const</span> FObjectInitializer& ObjecInitializer);</div> +<div class="line"><a id="l00020" name="l00020"></a><span class="lineno"> 20</span> </div> +<div class="line"><a id="l00022" name="l00022"></a><span class="lineno"> 22</span> UFUNCTION(BlueprintCallable, BlueprintPure, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00023" name="l00023"></a><span class="lineno"> 23</span> FRotator GetBodyRotation();</div> +<div class="line"><a id="l00024" name="l00024"></a><span class="lineno"> 24</span> </div> +<div class="line"><a id="l00028" name="l00028"></a><span class="lineno"> 28</span> UFUNCTION(BlueprintCallable, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00029" name="l00029"></a><span class="lineno"> 29</span> <span class="keywordtype">void</span> SetBodyRotation(FRotator rotator);</div> +<div class="line"><a id="l00030" name="l00030"></a><span class="lineno"> 30</span> </div> +<div class="line"><a id="l00032" name="l00032"></a><span class="lineno"> 32</span> UFUNCTION(BlueprintCallable, BlueprintPure, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00033" name="l00033"></a><span class="lineno"> 33</span> FRotator GetBeamTopRotation();</div> +<div class="line"><a id="l00034" name="l00034"></a><span class="lineno"> 34</span> </div> +<div class="line"><a id="l00038" name="l00038"></a><span class="lineno"> 38</span> UFUNCTION(BlueprintCallable, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00039" name="l00039"></a><span class="lineno"> 39</span> <span class="keywordtype">void</span> SetBeamTopRotation(FRotator rotator);</div> +<div class="line"><a id="l00040" name="l00040"></a><span class="lineno"> 40</span> </div> +<div class="line"><a id="l00042" name="l00042"></a><span class="lineno"> 42</span> UFUNCTION(BlueprintCallable, BlueprintPure, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00043" name="l00043"></a><span class="lineno"> 43</span> FRotator GetBeamBottomRotation();</div> +<div class="line"><a id="l00044" name="l00044"></a><span class="lineno"> 44</span> </div> +<div class="line"><a id="l00048" name="l00048"></a><span class="lineno"> 48</span> UFUNCTION(BlueprintCallable, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00049" name="l00049"></a><span class="lineno"> 49</span> <span class="keywordtype">void</span> SetBeamBottomRotation(FRotator rotator);</div> +<div class="line"><a id="l00050" name="l00050"></a><span class="lineno"> 50</span> </div> +<div class="line"><a id="l00052" name="l00052"></a><span class="lineno"> 52</span> UFUNCTION(BlueprintCallable, BlueprintPure, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00053" name="l00053"></a><span class="lineno"> 53</span> FRotator GetBucketRotation();</div> +<div class="line"><a id="l00054" name="l00054"></a><span class="lineno"> 54</span> </div> +<div class="line"><a id="l00058" name="l00058"></a><span class="lineno"> 58</span> UFUNCTION(BlueprintCallable, Category = <span class="stringliteral">"Rotations"</span>, meta = (BlueprintThreadSafe = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00059" name="l00059"></a><span class="lineno"> 59</span> <span class="keywordtype">void</span> SetBucketRotation(FRotator rotator);</div> +<div class="line"><a id="l00060" name="l00060"></a><span class="lineno"> 60</span> </div> +<div class="line"><a id="l00062" name="l00062"></a><span class="lineno"><a class="line" href="class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f"> 62</a></span> <span class="keywordtype">float</span> <a class="code hl_function" href="class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f">GetBucketMaxRotation</a>()</div> +<div class="line"><a id="l00063" name="l00063"></a><span class="lineno"> 63</span> {</div> +<div class="line"><a id="l00064" name="l00064"></a><span class="lineno"> 64</span> <span class="keywordflow">return</span> BucketRotationMAX;</div> +<div class="line"><a id="l00065" name="l00065"></a><span class="lineno"> 65</span> }</div> +<div class="line"><a id="l00066" name="l00066"></a><span class="lineno"> 66</span> </div> +<div class="line"><a id="l00068" name="l00068"></a><span class="lineno"><a class="line" href="class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc"> 68</a></span> <span class="keywordtype">float</span> <a class="code hl_function" href="class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc">GetBucketMinRotation</a>()</div> +<div class="line"><a id="l00069" name="l00069"></a><span class="lineno"> 69</span> {</div> +<div class="line"><a id="l00070" name="l00070"></a><span class="lineno"> 70</span> <span class="keywordflow">return</span> BucketRotationMIN;</div> +<div class="line"><a id="l00071" name="l00071"></a><span class="lineno"> 71</span> }</div> +<div class="line"><a id="l00072" name="l00072"></a><span class="lineno"> 72</span> </div> +<div class="line"><a id="l00074" name="l00074"></a><span class="lineno"><a class="line" href="class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1"> 74</a></span> <span class="keywordtype">float</span> <a class="code hl_function" href="class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1">GetTopMaxRotation</a>()</div> +<div class="line"><a id="l00075" name="l00075"></a><span class="lineno"> 75</span> {</div> +<div class="line"><a id="l00076" name="l00076"></a><span class="lineno"> 76</span> <span class="keywordflow">return</span> TopRotationMAX;</div> +<div class="line"><a id="l00077" name="l00077"></a><span class="lineno"> 77</span> }</div> +<div class="line"><a id="l00078" name="l00078"></a><span class="lineno"> 78</span> </div> +<div class="line"><a id="l00080" name="l00080"></a><span class="lineno"><a class="line" href="class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793"> 80</a></span> <span class="keywordtype">float</span> <a class="code hl_function" href="class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793">GetTopMinRotation</a>()</div> +<div class="line"><a id="l00081" name="l00081"></a><span class="lineno"> 81</span> {</div> +<div class="line"><a id="l00082" name="l00082"></a><span class="lineno"> 82</span> <span class="keywordflow">return</span> TopRotationMIN;</div> +<div class="line"><a id="l00083" name="l00083"></a><span class="lineno"> 83</span> }</div> +<div class="line"><a id="l00084" name="l00084"></a><span class="lineno"> 84</span> </div> +<div class="line"><a id="l00086" name="l00086"></a><span class="lineno"><a class="line" href="class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79"> 86</a></span> <span class="keywordtype">float</span> <a class="code hl_function" href="class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79">GetBottomMaxRotation</a>()</div> +<div class="line"><a id="l00087" name="l00087"></a><span class="lineno"> 87</span> {</div> +<div class="line"><a id="l00088" name="l00088"></a><span class="lineno"> 88</span> <span class="keywordflow">return</span> BottomRotationMAX;</div> +<div class="line"><a id="l00089" name="l00089"></a><span class="lineno"> 89</span> }</div> +<div class="line"><a id="l00090" name="l00090"></a><span class="lineno"> 90</span> </div> +<div class="line"><a id="l00092" name="l00092"></a><span class="lineno"><a class="line" href="class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b"> 92</a></span> <span class="keywordtype">float</span> <a class="code hl_function" href="class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b">GetBottomMinRotation</a>()</div> +<div class="line"><a id="l00093" name="l00093"></a><span class="lineno"> 93</span> {</div> +<div class="line"><a id="l00094" name="l00094"></a><span class="lineno"> 94</span> <span class="keywordflow">return</span> BottomRotationMIN;</div> +<div class="line"><a id="l00095" name="l00095"></a><span class="lineno"> 95</span> }</div> +<div class="line"><a id="l00096" name="l00096"></a><span class="lineno"> 96</span> </div> +<div class="line"><a id="l00097" name="l00097"></a><span class="lineno"> 97</span><span class="keyword">private</span>:</div> +<div class="line"><a id="l00098" name="l00098"></a><span class="lineno"> 98</span> </div> +<div class="line"><a id="l00100" name="l00100"></a><span class="lineno"> 100</span> UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = <span class="stringliteral">"Rotations"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00101" name="l00101"></a><span class="lineno"> 101</span> FRotator BodyRotation;</div> +<div class="line"><a id="l00102" name="l00102"></a><span class="lineno"> 102</span> </div> +<div class="line"><a id="l00104" name="l00104"></a><span class="lineno"> 104</span> UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00105" name="l00105"></a><span class="lineno"> 105</span> FRotator BeamTopRotation;</div> +<div class="line"><a id="l00106" name="l00106"></a><span class="lineno"> 106</span> </div> +<div class="line"><a id="l00108" name="l00108"></a><span class="lineno"> 108</span> UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00109" name="l00109"></a><span class="lineno"> 109</span> FRotator BeamBottomRotation;</div> +<div class="line"><a id="l00110" name="l00110"></a><span class="lineno"> 110</span> </div> +<div class="line"><a id="l00112" name="l00112"></a><span class="lineno"> 112</span> UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Rotations", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00113" name="l00113"></a><span class="lineno"> 113</span> FRotator BucketRotation;</div> +<div class="line"><a id="l00114" name="l00114"></a><span class="lineno"> 114</span> </div> +<div class="line"><a id="l00116" name="l00116"></a><span class="lineno"> 116</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00117" name="l00117"></a><span class="lineno"> 117</span> <span class="keywordtype">float</span> BucketRotationMIN;</div> +<div class="line"><a id="l00118" name="l00118"></a><span class="lineno"> 118</span> </div> +<div class="line"><a id="l00120" name="l00120"></a><span class="lineno"> 120</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00121" name="l00121"></a><span class="lineno"> 121</span> <span class="keywordtype">float</span> BucketRotationMAX;</div> +<div class="line"><a id="l00122" name="l00122"></a><span class="lineno"> 122</span> </div> +<div class="line"><a id="l00124" name="l00124"></a><span class="lineno"> 124</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00125" name="l00125"></a><span class="lineno"> 125</span> <span class="keywordtype">float</span> TopRotationMIN;</div> +<div class="line"><a id="l00126" name="l00126"></a><span class="lineno"> 126</span> </div> +<div class="line"><a id="l00128" name="l00128"></a><span class="lineno"> 128</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00129" name="l00129"></a><span class="lineno"> 129</span> <span class="keywordtype">float</span> TopRotationMAX;</div> +<div class="line"><a id="l00130" name="l00130"></a><span class="lineno"> 130</span> </div> +<div class="line"><a id="l00132" name="l00132"></a><span class="lineno"> 132</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00133" name="l00133"></a><span class="lineno"> 133</span> <span class="keywordtype">float</span> BottomRotationMIN;</div> +<div class="line"><a id="l00134" name="l00134"></a><span class="lineno"> 134</span> </div> +<div class="line"><a id="l00136" name="l00136"></a><span class="lineno"> 136</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Clap values", meta = (AllowPrivateAccess = "true"))</div> +<div class="line"><a id="l00137" name="l00137"></a><span class="lineno"> 137</span> <span class="keywordtype">float</span> BottomRotationMAX;</div> +<div class="line"><a id="l00138" name="l00138"></a><span class="lineno"> 138</span>};</div> +<div class="ttc" id="aclass_u_excavator_anim_html"><div class="ttname"><a href="class_u_excavator_anim.html">UExcavatorAnim</a></div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:14</div></div> +<div class="ttc" id="aclass_u_excavator_anim_html_a19af34e8fb205cccbe3796330e5fff79"><div class="ttname"><a href="class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79">UExcavatorAnim::GetBottomMaxRotation</a></div><div class="ttdeci">float GetBottomMaxRotation()</div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:86</div></div> +<div class="ttc" id="aclass_u_excavator_anim_html_a290c2158c3bd36b4cf9f6cf53395c44b"><div class="ttname"><a href="class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b">UExcavatorAnim::GetBottomMinRotation</a></div><div class="ttdeci">float GetBottomMinRotation()</div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:92</div></div> +<div class="ttc" id="aclass_u_excavator_anim_html_a56e5697f1cb92abaf41d0af039e641b1"><div class="ttname"><a href="class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1">UExcavatorAnim::GetTopMaxRotation</a></div><div class="ttdeci">float GetTopMaxRotation()</div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:74</div></div> +<div class="ttc" id="aclass_u_excavator_anim_html_a6542745f31287654904430a08208f4dc"><div class="ttname"><a href="class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc">UExcavatorAnim::GetBucketMinRotation</a></div><div class="ttdeci">float GetBucketMinRotation()</div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:68</div></div> +<div class="ttc" id="aclass_u_excavator_anim_html_a7e4af257d3369d934d999c5889bfe60f"><div class="ttname"><a href="class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f">UExcavatorAnim::GetBucketMaxRotation</a></div><div class="ttdeci">float GetBucketMaxRotation()</div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:62</div></div> +<div class="ttc" id="aclass_u_excavator_anim_html_ad7c23fdcc7208d1e925a0ea8ac09b793"><div class="ttname"><a href="class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793">UExcavatorAnim::GetTopMinRotation</a></div><div class="ttdeci">float GetTopMinRotation()</div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:80</div></div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_character_8cpp.html b/Doxygen/html/_excavator_character_8cpp.html new file mode 100644 index 0000000..1b56fee --- /dev/null +++ b/Doxygen/html/_excavator_character_8cpp.html @@ -0,0 +1,83 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorCharacter.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorCharacter.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "<a class="el" href="_excavator_character_8h_source.html">ExcavatorCharacter.h</a>"</code><br /> +<code>#include <Kismet/KismetMathLibrary.h></code><br /> +</div></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_character_8h.html b/Doxygen/html/_excavator_character_8h.html new file mode 100644 index 0000000..218e28f --- /dev/null +++ b/Doxygen/html/_excavator_character_8h.html @@ -0,0 +1,99 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorCharacter.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#nested-classes">Classes</a> </div> + <div class="headertitle"><div class="title">ExcavatorCharacter.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +<code>#include "GameFramework/Character.h"</code><br /> +<code>#include "Kismet/GameplayStatics.h"</code><br /> +<code>#include "GameFramework/InputSettings.h"</code><br /> +<code>#include "<a class="el" href="_check_collision_component_8h_source.html">CheckCollisionComponent.h</a>"</code><br /> +<code>#include "<a class="el" href="_ground_deformer_component_8h_source.html">GroundDeformerComponent.h</a>"</code><br /> +<code>#include "<a class="el" href="_excavator_anim_8h_source.html">ExcavatorAnim.h</a>"</code><br /> +<code>#include "ExcavatorCharacter.generated.h"</code><br /> +</div> +<p><a href="_excavator_character_8h_source.html">Go to the source code of this file.</a></p> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="nested-classes" name="nested-classes"></a> +Classes</h2></td></tr> +<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td></tr> +<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> +</table> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_character_8h_source.html b/Doxygen/html/_excavator_character_8h_source.html new file mode 100644 index 0000000..b6542d7 --- /dev/null +++ b/Doxygen/html/_excavator_character_8h_source.html @@ -0,0 +1,203 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorCharacter.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorCharacter.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_excavator_character_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Fill out your copyright notice in the Description page of Project Settings.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span><span class="preprocessor">#include "GameFramework/Character.h"</span></div> +<div class="line"><a id="l00007" name="l00007"></a><span class="lineno"> 7</span><span class="preprocessor">#include "Kismet/GameplayStatics.h"</span></div> +<div class="line"><a id="l00008" name="l00008"></a><span class="lineno"> 8</span><span class="preprocessor">#include "GameFramework/InputSettings.h"</span></div> +<div class="line"><a id="l00009" name="l00009"></a><span class="lineno"> 9</span><span class="preprocessor">#include "<a class="code" href="_check_collision_component_8h.html">CheckCollisionComponent.h</a>"</span></div> +<div class="line"><a id="l00010" name="l00010"></a><span class="lineno"> 10</span><span class="preprocessor">#include "<a class="code" href="_ground_deformer_component_8h.html">GroundDeformerComponent.h</a>"</span></div> +<div class="line"><a id="l00011" name="l00011"></a><span class="lineno"> 11</span><span class="preprocessor">#include "<a class="code" href="_excavator_anim_8h.html">ExcavatorAnim.h</a>"</span></div> +<div class="line"><a id="l00012" name="l00012"></a><span class="lineno"> 12</span><span class="preprocessor">#include "ExcavatorCharacter.generated.h"</span></div> +<div class="line"><a id="l00013" name="l00013"></a><span class="lineno"> 13</span> </div> +<div class="line"><a id="l00014" name="l00014"></a><span class="lineno"> 14</span>UCLASS()</div> +<div class="line"><a id="l00015" name="l00015"></a><span class="lineno"><a class="line" href="class_a_excavator_character.html"> 15</a></span>class EXCAVATORSIMULATOR_API <a class="code hl_class" href="class_a_excavator_character.html">AExcavatorCharacter</a> : public ACharacter</div> +<div class="line"><a id="l00016" name="l00016"></a><span class="lineno"> 16</span>{</div> +<div class="line"><a id="l00017" name="l00017"></a><span class="lineno"> 17</span> GENERATED_BODY()</div> +<div class="line"><a id="l00018" name="l00018"></a><span class="lineno"> 18</span> </div> +<div class="line"><a id="l00019" name="l00019"></a><span class="lineno"> 19</span><span class="keyword">public</span>:</div> +<div class="line"><a id="l00021" name="l00021"></a><span class="lineno"> 21</span> <a class="code hl_class" href="class_a_excavator_character.html">AExcavatorCharacter</a>();</div> +<div class="line"><a id="l00022" name="l00022"></a><span class="lineno"> 22</span> </div> +<div class="line"><a id="l00024" name="l00024"></a><span class="lineno"> 24</span> <span class="keyword">virtual</span> <span class="keywordtype">void</span> Tick(<span class="keywordtype">float</span> DeltaTime) <span class="keyword">override</span>;</div> +<div class="line"><a id="l00025" name="l00025"></a><span class="lineno"> 25</span> </div> +<div class="line"><a id="l00027" name="l00027"></a><span class="lineno"> 27</span> <span class="keyword">virtual</span> <span class="keywordtype">void</span> SetupPlayerInputComponent(<span class="keyword">class</span> UInputComponent* PlayerInputComponent) <span class="keyword">override</span>;</div> +<div class="line"><a id="l00028" name="l00028"></a><span class="lineno"> 28</span> </div> +<div class="line"><a id="l00033" name="l00033"></a><span class="lineno"> 33</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00034" name="l00034"></a><span class="lineno"> 34</span> <span class="keywordtype">void</span> Movement(<span class="keywordtype">float</span> Direction);</div> +<div class="line"><a id="l00035" name="l00035"></a><span class="lineno"> 35</span> </div> +<div class="line"><a id="l00040" name="l00040"></a><span class="lineno"> 40</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00041" name="l00041"></a><span class="lineno"> 41</span> <span class="keywordtype">bool</span> BucketRotationReleaceRocks(<span class="keywordtype">float</span> RotationDirection);</div> +<div class="line"><a id="l00042" name="l00042"></a><span class="lineno"> 42</span> </div> +<div class="line"><a id="l00043" name="l00043"></a><span class="lineno"> 43</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00044" name="l00044"></a><span class="lineno"> 44</span> <span class="keywordtype">void</span> BucketRotationVR(<span class="keywordtype">float</span> RightJoyStickRotationX);</div> +<div class="line"><a id="l00045" name="l00045"></a><span class="lineno"> 45</span> </div> +<div class="line"><a id="l00050" name="l00050"></a><span class="lineno"> 50</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00051" name="l00051"></a><span class="lineno"> 51</span> <span class="keywordtype">void</span> BeamTopRotation(<span class="keywordtype">float</span> RotationDirection);</div> +<div class="line"><a id="l00052" name="l00052"></a><span class="lineno"> 52</span> </div> +<div class="line"><a id="l00053" name="l00053"></a><span class="lineno"> 53</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00054" name="l00054"></a><span class="lineno"> 54</span> <span class="keywordtype">void</span> BeamTopRotationVR(<span class="keywordtype">float</span> RightJoyStickRotationY);</div> +<div class="line"><a id="l00055" name="l00055"></a><span class="lineno"> 55</span> </div> +<div class="line"><a id="l00060" name="l00060"></a><span class="lineno"> 60</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00061" name="l00061"></a><span class="lineno"> 61</span> <span class="keywordtype">void</span> BeamBottomRotation(<span class="keywordtype">float</span> RotationDirection);</div> +<div class="line"><a id="l00062" name="l00062"></a><span class="lineno"> 62</span> </div> +<div class="line"><a id="l00063" name="l00063"></a><span class="lineno"> 63</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00064" name="l00064"></a><span class="lineno"> 64</span> <span class="keywordtype">void</span> BeamBottomRotationVR(<span class="keywordtype">float</span> LeftJoyStickRotationY);</div> +<div class="line"><a id="l00065" name="l00065"></a><span class="lineno"> 65</span> </div> +<div class="line"><a id="l00070" name="l00070"></a><span class="lineno"> 70</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00071" name="l00071"></a><span class="lineno"> 71</span> <span class="keywordtype">void</span> BodyRotation(<span class="keywordtype">float</span> RotationDirection);</div> +<div class="line"><a id="l00072" name="l00072"></a><span class="lineno"> 72</span> </div> +<div class="line"><a id="l00073" name="l00073"></a><span class="lineno"> 73</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00074" name="l00074"></a><span class="lineno"> 74</span> <span class="keywordtype">void</span> BodyRotationVR(<span class="keywordtype">float</span> LeftJoyStickRotationX);</div> +<div class="line"><a id="l00075" name="l00075"></a><span class="lineno"> 75</span> </div> +<div class="line"><a id="l00080" name="l00080"></a><span class="lineno"> 80</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00081" name="l00081"></a><span class="lineno"> 81</span> <span class="keywordtype">void</span> VehicleRotation(<span class="keywordtype">float</span> RotationDirection);</div> +<div class="line"><a id="l00082" name="l00082"></a><span class="lineno"> 82</span> </div> +<div class="line"><a id="l00083" name="l00083"></a><span class="lineno"> 83</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00084" name="l00084"></a><span class="lineno"> 84</span> <span class="keywordtype">void</span> VehicleRotationVR(<span class="keywordtype">float</span> AxisValue);</div> +<div class="line"><a id="l00085" name="l00085"></a><span class="lineno"> 85</span> </div> +<div class="line"><a id="l00086" name="l00086"></a><span class="lineno"> 86</span><span class="keyword">protected</span>:</div> +<div class="line"><a id="l00087" name="l00087"></a><span class="lineno"> 87</span> </div> +<div class="line"><a id="l00089" name="l00089"></a><span class="lineno"> 89</span> <span class="keyword">virtual</span> <span class="keywordtype">void</span> BeginPlay() <span class="keyword">override</span>;</div> +<div class="line"><a id="l00090" name="l00090"></a><span class="lineno"> 90</span> </div> +<div class="line"><a id="l00091" name="l00091"></a><span class="lineno"> 91</span><span class="keyword">private</span>:</div> +<div class="line"><a id="l00092" name="l00092"></a><span class="lineno"> 92</span> UInputSettings* Inputsettings = UInputSettings::GetInputSettings();</div> +<div class="line"><a id="l00093" name="l00093"></a><span class="lineno"> 93</span> </div> +<div class="line"><a id="l00094" name="l00094"></a><span class="lineno"> 94</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Right Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00095" name="l00095"></a><span class="lineno"> 95</span> <span class="keywordtype">float</span> RightJoyStickRotXMin;</div> +<div class="line"><a id="l00096" name="l00096"></a><span class="lineno"> 96</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Right Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00097" name="l00097"></a><span class="lineno"> 97</span> <span class="keywordtype">float</span> RightJoyStickRotXMax;</div> +<div class="line"><a id="l00098" name="l00098"></a><span class="lineno"> 98</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Right Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00099" name="l00099"></a><span class="lineno"> 99</span> <span class="keywordtype">float</span> RightJoyStickRotYMin;</div> +<div class="line"><a id="l00100" name="l00100"></a><span class="lineno"> 100</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Right Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00101" name="l00101"></a><span class="lineno"> 101</span> <span class="keywordtype">float</span> RightJoyStickRotYMax;</div> +<div class="line"><a id="l00102" name="l00102"></a><span class="lineno"> 102</span> </div> +<div class="line"><a id="l00103" name="l00103"></a><span class="lineno"> 103</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Left Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00104" name="l00104"></a><span class="lineno"> 104</span> <span class="keywordtype">float</span> LeftJoyStickRotXMin;</div> +<div class="line"><a id="l00105" name="l00105"></a><span class="lineno"> 105</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Left Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00106" name="l00106"></a><span class="lineno"> 106</span> <span class="keywordtype">float</span> LeftJoyStickRotXMax;</div> +<div class="line"><a id="l00107" name="l00107"></a><span class="lineno"> 107</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Left Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00108" name="l00108"></a><span class="lineno"> 108</span> <span class="keywordtype">float</span> LeftJoyStickRotYMin;</div> +<div class="line"><a id="l00109" name="l00109"></a><span class="lineno"> 109</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Left Joy Stick values"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00110" name="l00110"></a><span class="lineno"> 110</span> <span class="keywordtype">float</span> LeftJoyStickRotYMax;</div> +<div class="line"><a id="l00111" name="l00111"></a><span class="lineno"> 111</span> </div> +<div class="line"><a id="l00113" name="l00113"></a><span class="lineno"> 113</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Movement variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00114" name="l00114"></a><span class="lineno"> 114</span> <span class="keywordtype">float</span> AccelerationMultiplier;</div> +<div class="line"><a id="l00115" name="l00115"></a><span class="lineno"> 115</span> </div> +<div class="line"><a id="l00117" name="l00117"></a><span class="lineno"> 117</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Movement variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00118" name="l00118"></a><span class="lineno"> 118</span> <span class="keywordtype">float</span> BucketRotationMultiplier;</div> +<div class="line"><a id="l00119" name="l00119"></a><span class="lineno"> 119</span> </div> +<div class="line"><a id="l00121" name="l00121"></a><span class="lineno"> 121</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Movement variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00122" name="l00122"></a><span class="lineno"> 122</span> <span class="keywordtype">float</span> BeamTopRotationMultiplier;</div> +<div class="line"><a id="l00123" name="l00123"></a><span class="lineno"> 123</span> </div> +<div class="line"><a id="l00125" name="l00125"></a><span class="lineno"> 125</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Movement variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00126" name="l00126"></a><span class="lineno"> 126</span> <span class="keywordtype">float</span> BeamBottomRotationMultiplier;</div> +<div class="line"><a id="l00127" name="l00127"></a><span class="lineno"> 127</span> </div> +<div class="line"><a id="l00129" name="l00129"></a><span class="lineno"> 129</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Movement variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00130" name="l00130"></a><span class="lineno"> 130</span> <span class="keywordtype">float</span> BodyRotationMultiplier;</div> +<div class="line"><a id="l00131" name="l00131"></a><span class="lineno"> 131</span> </div> +<div class="line"><a id="l00133" name="l00133"></a><span class="lineno"> 133</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Movement variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00134" name="l00134"></a><span class="lineno"> 134</span> <span class="keywordtype">float</span> VehicleRotationMultiplier;</div> +<div class="line"><a id="l00135" name="l00135"></a><span class="lineno"> 135</span> </div> +<div class="line"><a id="l00137" name="l00137"></a><span class="lineno"> 137</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Movement variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00138" name="l00138"></a><span class="lineno"> 138</span> <span class="keywordtype">float</span> DetachRocksAngle;</div> +<div class="line"><a id="l00139" name="l00139"></a><span class="lineno"> 139</span> </div> +<div class="line"><a id="l00141" name="l00141"></a><span class="lineno"> 141</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Collision variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00142" name="l00142"></a><span class="lineno"> 142</span> UCheckCollisionComponent* collisionComponent;</div> +<div class="line"><a id="l00143" name="l00143"></a><span class="lineno"> 143</span> </div> +<div class="line"><a id="l00144" name="l00144"></a><span class="lineno"> 144</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Ground variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00145" name="l00145"></a><span class="lineno"> 145</span> UGroundDeformerComponent* groundDeformComponent;</div> +<div class="line"><a id="l00146" name="l00146"></a><span class="lineno"> 146</span> </div> +<div class="line"><a id="l00148" name="l00148"></a><span class="lineno"> 148</span> UPROPERTY(BlueprintReadWrite, Category = <span class="stringliteral">"Ground variables"</span>, meta = (AllowPrivateAccess = <span class="stringliteral">"true"</span>))</div> +<div class="line"><a id="l00149" name="l00149"></a><span class="lineno"> 149</span> <a class="code hl_class" href="class_u_excavator_anim.html">UExcavatorAnim</a>* ExcavatorAnimREF;</div> +<div class="line"><a id="l00150" name="l00150"></a><span class="lineno"> 150</span> </div> +<div class="line"><a id="l00152" name="l00152"></a><span class="lineno"> 152</span> USkeletalMeshComponent* mesh;</div> +<div class="line"><a id="l00153" name="l00153"></a><span class="lineno"> 153</span> </div> +<div class="line"><a id="l00154" name="l00154"></a><span class="lineno"> 154</span> <span class="keywordtype">float</span> deltaTime;</div> +<div class="line"><a id="l00155" name="l00155"></a><span class="lineno"> 155</span>};</div> +<div class="ttc" id="a_check_collision_component_8h_html"><div class="ttname"><a href="_check_collision_component_8h.html">CheckCollisionComponent.h</a></div></div> +<div class="ttc" id="a_excavator_anim_8h_html"><div class="ttname"><a href="_excavator_anim_8h.html">ExcavatorAnim.h</a></div></div> +<div class="ttc" id="a_ground_deformer_component_8h_html"><div class="ttname"><a href="_ground_deformer_component_8h.html">GroundDeformerComponent.h</a></div></div> +<div class="ttc" id="aclass_a_excavator_character_html"><div class="ttname"><a href="class_a_excavator_character.html">AExcavatorCharacter</a></div><div class="ttdef"><b>Definition:</b> ExcavatorCharacter.h:16</div></div> +<div class="ttc" id="aclass_u_excavator_anim_html"><div class="ttname"><a href="class_u_excavator_anim.html">UExcavatorAnim</a></div><div class="ttdef"><b>Definition:</b> ExcavatorAnim.h:14</div></div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_simulator_8_build_8cs.html b/Doxygen/html/_excavator_simulator_8_build_8cs.html new file mode 100644 index 0000000..4419268 --- /dev/null +++ b/Doxygen/html/_excavator_simulator_8_build_8cs.html @@ -0,0 +1,89 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulator.Build.cs File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#nested-classes">Classes</a> </div> + <div class="headertitle"><div class="title">ExcavatorSimulator.Build.cs File Reference</div></div> +</div><!--header--> +<div class="contents"> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="nested-classes" name="nested-classes"></a> +Classes</h2></td></tr> +<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_excavator_simulator.html">ExcavatorSimulator</a></td></tr> +<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> +</table> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_simulator_8cpp.html b/Doxygen/html/_excavator_simulator_8cpp.html new file mode 100644 index 0000000..a206728 --- /dev/null +++ b/Doxygen/html/_excavator_simulator_8cpp.html @@ -0,0 +1,126 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulator.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#func-members">Functions</a> </div> + <div class="headertitle"><div class="title">ExcavatorSimulator.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "<a class="el" href="_excavator_simulator_8h_source.html">ExcavatorSimulator.h</a>"</code><br /> +<code>#include "Modules/ModuleManager.h"</code><br /> +</div><table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="func-members" name="func-members"></a> +Functions</h2></td></tr> +<tr class="memitem:abdfae4c413c9ef6bcaa5cf0c657807d7"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="_excavator_simulator_8cpp.html#abdfae4c413c9ef6bcaa5cf0c657807d7">IMPLEMENT_PRIMARY_GAME_MODULE</a> (FDefaultGameModuleImpl, <a class="el" href="class_excavator_simulator.html">ExcavatorSimulator</a>, "ExcavatorSimulator")</td></tr> +<tr class="separator:abdfae4c413c9ef6bcaa5cf0c657807d7"><td class="memSeparator" colspan="2"> </td></tr> +</table> +<h2 class="groupheader">Function Documentation</h2> +<a id="abdfae4c413c9ef6bcaa5cf0c657807d7" name="abdfae4c413c9ef6bcaa5cf0c657807d7"></a> +<h2 class="memtitle"><span class="permalink"><a href="#abdfae4c413c9ef6bcaa5cf0c657807d7">◆ </a></span>IMPLEMENT_PRIMARY_GAME_MODULE()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">IMPLEMENT_PRIMARY_GAME_MODULE </td> + <td>(</td> + <td class="paramtype">FDefaultGameModuleImpl </td> + <td class="paramname">, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype"><a class="el" href="class_excavator_simulator.html">ExcavatorSimulator</a> </td> + <td class="paramname">, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">"ExcavatorSimulator" </td> + <td class="paramname"> </td> + </tr> + <tr> + <td></td> + <td>)</td> + <td></td><td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_simulator_8h.html b/Doxygen/html/_excavator_simulator_8h.html new file mode 100644 index 0000000..53367a7 --- /dev/null +++ b/Doxygen/html/_excavator_simulator_8h.html @@ -0,0 +1,84 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulator.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorSimulator.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +</div> +<p><a href="_excavator_simulator_8h_source.html">Go to the source code of this file.</a></p> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_simulator_8h_source.html b/Doxygen/html/_excavator_simulator_8h_source.html new file mode 100644 index 0000000..3a39070 --- /dev/null +++ b/Doxygen/html/_excavator_simulator_8h_source.html @@ -0,0 +1,87 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulator.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorSimulator.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_excavator_simulator_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Copyright Epic Games, Inc. All Rights Reserved.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span> </div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_simulator_game_mode_base_8cpp.html b/Doxygen/html/_excavator_simulator_game_mode_base_8cpp.html new file mode 100644 index 0000000..f109d69 --- /dev/null +++ b/Doxygen/html/_excavator_simulator_game_mode_base_8cpp.html @@ -0,0 +1,82 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulatorGameModeBase.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorSimulatorGameModeBase.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "<a class="el" href="_excavator_simulator_game_mode_base_8h_source.html">ExcavatorSimulatorGameModeBase.h</a>"</code><br /> +</div></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_simulator_game_mode_base_8h.html b/Doxygen/html/_excavator_simulator_game_mode_base_8h.html new file mode 100644 index 0000000..70b9ea9 --- /dev/null +++ b/Doxygen/html/_excavator_simulator_game_mode_base_8h.html @@ -0,0 +1,95 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulatorGameModeBase.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#nested-classes">Classes</a> </div> + <div class="headertitle"><div class="title">ExcavatorSimulatorGameModeBase.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +<code>#include "GameFramework/GameModeBase.h"</code><br /> +<code>#include "<a class="el" href="_custom_procedural_mesh_component_8h_source.html">CustomProceduralMeshComponent.h</a>"</code><br /> +<code>#include "ExcavatorSimulatorGameModeBase.generated.h"</code><br /> +</div> +<p><a href="_excavator_simulator_game_mode_base_8h_source.html">Go to the source code of this file.</a></p> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="nested-classes" name="nested-classes"></a> +Classes</h2></td></tr> +<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_simulator_game_mode_base.html">AExcavatorSimulatorGameModeBase</a></td></tr> +<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> +</table> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_excavator_simulator_game_mode_base_8h_source.html b/Doxygen/html/_excavator_simulator_game_mode_base_8h_source.html new file mode 100644 index 0000000..04c1b67 --- /dev/null +++ b/Doxygen/html/_excavator_simulator_game_mode_base_8h_source.html @@ -0,0 +1,101 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulatorGameModeBase.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorSimulatorGameModeBase.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_excavator_simulator_game_mode_base_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Copyright Epic Games, Inc. All Rights Reserved.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span><span class="preprocessor">#include "GameFramework/GameModeBase.h"</span></div> +<div class="line"><a id="l00007" name="l00007"></a><span class="lineno"> 7</span><span class="preprocessor">#include "<a class="code" href="_custom_procedural_mesh_component_8h.html">CustomProceduralMeshComponent.h</a>"</span></div> +<div class="line"><a id="l00008" name="l00008"></a><span class="lineno"> 8</span><span class="preprocessor">#include "ExcavatorSimulatorGameModeBase.generated.h"</span></div> +<div class="line"><a id="l00009" name="l00009"></a><span class="lineno"> 9</span> </div> +<div class="line"><a id="l00013" name="l00013"></a><span class="lineno"> 13</span>UCLASS()</div> +<div class="line"><a id="l00014" name="l00014"></a><span class="lineno"><a class="line" href="class_a_excavator_simulator_game_mode_base.html"> 14</a></span>class EXCAVATORSIMULATOR_API <a class="code hl_class" href="class_a_excavator_simulator_game_mode_base.html">AExcavatorSimulatorGameModeBase</a> : public AGameModeBase</div> +<div class="line"><a id="l00015" name="l00015"></a><span class="lineno"> 15</span>{</div> +<div class="line"><a id="l00016" name="l00016"></a><span class="lineno"> 16</span> GENERATED_BODY()</div> +<div class="line"><a id="l00017" name="l00017"></a><span class="lineno"> 17</span> </div> +<div class="line"><a id="l00018" name="l00018"></a><span class="lineno"> 18</span><span class="keyword">private</span>:</div> +<div class="line"><a id="l00019" name="l00019"></a><span class="lineno"> 19</span> <a class="code hl_class" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a>* ProceduralmeshComponent;</div> +<div class="line"><a id="l00020" name="l00020"></a><span class="lineno"> 20</span>};</div> +<div class="ttc" id="a_custom_procedural_mesh_component_8h_html"><div class="ttname"><a href="_custom_procedural_mesh_component_8h.html">CustomProceduralMeshComponent.h</a></div></div> +<div class="ttc" id="aclass_a_custom_procedural_mesh_component_html"><div class="ttname"><a href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></div><div class="ttdef"><b>Definition:</b> CustomProceduralMeshComponent.h:12</div></div> +<div class="ttc" id="aclass_a_excavator_simulator_game_mode_base_html"><div class="ttname"><a href="class_a_excavator_simulator_game_mode_base.html">AExcavatorSimulatorGameModeBase</a></div><div class="ttdef"><b>Definition:</b> ExcavatorSimulatorGameModeBase.h:15</div></div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_ground_deformer_component_8cpp.html b/Doxygen/html/_ground_deformer_component_8cpp.html new file mode 100644 index 0000000..08b97fd --- /dev/null +++ b/Doxygen/html/_ground_deformer_component_8cpp.html @@ -0,0 +1,90 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: GroundDeformerComponent.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">GroundDeformerComponent.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include <Voxel/Public/VoxelTools/VoxelSurfaceEdits.h></code><br /> +<code>#include <Voxel/Public/VoxelTools/Impl/VoxelSurfaceEditToolsImpl.h></code><br /> +<code>#include <Voxel/Public/VoxelTools/VoxelSurfaceTools.h></code><br /> +<code>#include <Voxel/Public/VoxelDebug/VoxelDebugUtilities.h></code><br /> +<code>#include <Voxel/Public/VoxelTools/Gen/VoxelSurfaceEditTools.h></code><br /> +<code>#include <Voxel/Public/VoxelTools/VoxelProjectionTools.h></code><br /> +<code>#include <Voxel/Public/VoxelTools/VoxelDataTools.h></code><br /> +<code>#include "<a class="el" href="_world_generator_8h_source.html">WorldGenerator.h</a>"</code><br /> +<code>#include "<a class="el" href="_ground_deformer_component_8h_source.html">GroundDeformerComponent.h</a>"</code><br /> +</div></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_ground_deformer_component_8h.html b/Doxygen/html/_ground_deformer_component_8h.html new file mode 100644 index 0000000..9587ac1 --- /dev/null +++ b/Doxygen/html/_ground_deformer_component_8h.html @@ -0,0 +1,95 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: GroundDeformerComponent.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">GroundDeformerComponent.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +<code>#include "Components/ActorComponent.h"</code><br /> +<code>#include "Components/StaticMeshComponent.h"</code><br /> +<code>#include "Kismet/KismetMathLibrary.h"</code><br /> +<code>#include "CollisionQueryParams.h"</code><br /> +<code>#include "Kismet/GameplayStatics.h"</code><br /> +<code>#include "VoxelWorld.h"</code><br /> +<code>#include <Voxel/Public/VoxelTools/Gen/VoxelSphereTools.h></code><br /> +<code>#include <Voxel/Public/VoxelTools/Gen/VoxelBoxTools.h></code><br /> +<code>#include <Voxel/Public/VoxelIntBoxLibrary.h></code><br /> +<code>#include <Voxel/Public/VoxelTools/VoxelBlueprintLibrary.h></code><br /> +<code>#include "GroundDeformerComponent.generated.h"</code><br /> +</div> +<p><a href="_ground_deformer_component_8h_source.html">Go to the source code of this file.</a></p> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_ground_deformer_component_8h_source.html b/Doxygen/html/_ground_deformer_component_8h_source.html new file mode 100644 index 0000000..594d576 --- /dev/null +++ b/Doxygen/html/_ground_deformer_component_8h_source.html @@ -0,0 +1,122 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: GroundDeformerComponent.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">GroundDeformerComponent.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_ground_deformer_component_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Fill out your copyright notice in the Description page of Project Settings.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span><span class="preprocessor">#include "Components/ActorComponent.h"</span></div> +<div class="line"><a id="l00007" name="l00007"></a><span class="lineno"> 7</span><span class="preprocessor">#include "Components/StaticMeshComponent.h"</span></div> +<div class="line"><a id="l00008" name="l00008"></a><span class="lineno"> 8</span><span class="preprocessor">#include "Kismet/KismetMathLibrary.h"</span></div> +<div class="line"><a id="l00009" name="l00009"></a><span class="lineno"> 9</span><span class="preprocessor">#include "CollisionQueryParams.h"</span></div> +<div class="line"><a id="l00010" name="l00010"></a><span class="lineno"> 10</span><span class="preprocessor">#include "Kismet/GameplayStatics.h"</span></div> +<div class="line"><a id="l00011" name="l00011"></a><span class="lineno"> 11</span><span class="preprocessor">#include "VoxelWorld.h"</span></div> +<div class="line"><a id="l00012" name="l00012"></a><span class="lineno"> 12</span><span class="preprocessor">#include <Voxel/Public/VoxelTools/Gen/VoxelSphereTools.h></span></div> +<div class="line"><a id="l00013" name="l00013"></a><span class="lineno"> 13</span><span class="preprocessor">#include <Voxel/Public/VoxelTools/Gen/VoxelBoxTools.h></span></div> +<div class="line"><a id="l00014" name="l00014"></a><span class="lineno"> 14</span><span class="preprocessor">#include <Voxel/Public/VoxelIntBoxLibrary.h></span></div> +<div class="line"><a id="l00015" name="l00015"></a><span class="lineno"> 15</span><span class="preprocessor">#include <Voxel/Public/VoxelTools/VoxelBlueprintLibrary.h></span></div> +<div class="line"><a id="l00016" name="l00016"></a><span class="lineno"> 16</span><span class="preprocessor">#include "GroundDeformerComponent.generated.h"</span></div> +<div class="line"><a id="l00017" name="l00017"></a><span class="lineno"> 17</span> </div> +<div class="line"><a id="l00018" name="l00018"></a><span class="lineno"> 18</span> </div> +<div class="line"><a id="l00019" name="l00019"></a><span class="lineno"> 19</span>UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )</div> +<div class="line"><a id="l00020" name="l00020"></a><span class="lineno"> 20</span><span class="keyword">class </span>EXCAVATORSIMULATOR_API UGroundDeformerComponent : <span class="keyword">public</span> UActorComponent</div> +<div class="line"><a id="l00021" name="l00021"></a><span class="lineno"> 21</span>{</div> +<div class="line"><a id="l00022" name="l00022"></a><span class="lineno"> 22</span> GENERATED_BODY()</div> +<div class="line"><a id="l00023" name="l00023"></a><span class="lineno"> 23</span> </div> +<div class="line"><a id="l00024" name="l00024"></a><span class="lineno"> 24</span>public: </div> +<div class="line"><a id="l00026" name="l00026"></a><span class="lineno"> 26</span> UGroundDeformerComponent();</div> +<div class="line"><a id="l00027" name="l00027"></a><span class="lineno"> 27</span> </div> +<div class="line"><a id="l00029" name="l00029"></a><span class="lineno"> 29</span> virtual <span class="keywordtype">void</span> TickComponent(<span class="keywordtype">float</span> DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;</div> +<div class="line"><a id="l00030" name="l00030"></a><span class="lineno"> 30</span> </div> +<div class="line"><a id="l00035" name="l00035"></a><span class="lineno"> 35</span> UFUNCTION(BlueprintCallable)</div> +<div class="line"><a id="l00036" name="l00036"></a><span class="lineno"> 36</span> <span class="keywordtype">void</span> DeformGround(UStaticMeshComponent *Mesh);</div> +<div class="line"><a id="l00037" name="l00037"></a><span class="lineno"> 37</span> </div> +<div class="line"><a id="l00038" name="l00038"></a><span class="lineno"> 38</span>protected:</div> +<div class="line"><a id="l00040" name="l00040"></a><span class="lineno"> 40</span> virtual <span class="keywordtype">void</span> BeginPlay() override;</div> +<div class="line"><a id="l00041" name="l00041"></a><span class="lineno"> 41</span> </div> +<div class="line"><a id="l00042" name="l00042"></a><span class="lineno"> 42</span>private:</div> +<div class="line"><a id="l00044" name="l00044"></a><span class="lineno"> 44</span> AVoxelWorld* VoxelWorldREF;</div> +<div class="line"><a id="l00045" name="l00045"></a><span class="lineno"> 45</span> </div> +<div class="line"><a id="l00047" name="l00047"></a><span class="lineno"> 47</span> <span class="keywordtype">float</span> RemoveSphereRadius;</div> +<div class="line"><a id="l00048" name="l00048"></a><span class="lineno"> 48</span> </div> +<div class="line"><a id="l00050" name="l00050"></a><span class="lineno"> 50</span> <span class="keywordtype">float</span> ForwardVectorMultiplier;</div> +<div class="line"><a id="l00051" name="l00051"></a><span class="lineno"> 51</span>};</div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_world_generator_8cpp.html b/Doxygen/html/_world_generator_8cpp.html new file mode 100644 index 0000000..95a871c --- /dev/null +++ b/Doxygen/html/_world_generator_8cpp.html @@ -0,0 +1,84 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: WorldGenerator.cpp File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">WorldGenerator.cpp File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include <Voxel/Public/VoxelMaterialBuilder.h></code><br /> +<code>#include <Voxel/Public/FastNoise/VoxelFastNoise.inl></code><br /> +<code>#include "<a class="el" href="_world_generator_8h_source.html">WorldGenerator.h</a>"</code><br /> +</div></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_world_generator_8h.html b/Doxygen/html/_world_generator_8h.html new file mode 100644 index 0000000..81afd51 --- /dev/null +++ b/Doxygen/html/_world_generator_8h.html @@ -0,0 +1,97 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: WorldGenerator.h File Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#nested-classes">Classes</a> </div> + <div class="headertitle"><div class="title">WorldGenerator.h File Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock"><code>#include "CoreMinimal.h"</code><br /> +<code>#include <Voxel/Public/FastNoise/VoxelFastNoise.h></code><br /> +<code>#include <Voxel/Public/VoxelGenerators/VoxelGeneratorHelpers.h></code><br /> +<code>#include "WorldGenerator.generated.h"</code><br /> +</div> +<p><a href="_world_generator_8h_source.html">Go to the source code of this file.</a></p> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="nested-classes" name="nested-classes"></a> +Classes</h2></td></tr> +<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_world_generator.html">UWorldGenerator</a></td></tr> +<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:"><td class="memItemLeft" align="right" valign="top">class  </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td></tr> +<tr class="separator:"><td class="memSeparator" colspan="2"> </td></tr> +</table> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/_world_generator_8h_source.html b/Doxygen/html/_world_generator_8h_source.html new file mode 100644 index 0000000..5d0f62b --- /dev/null +++ b/Doxygen/html/_world_generator_8h_source.html @@ -0,0 +1,131 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: WorldGenerator.h Source File</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">WorldGenerator.h</div></div> +</div><!--header--> +<div class="contents"> +<a href="_world_generator_8h.html">Go to the documentation of this file.</a><div class="fragment"><div class="line"><a id="l00001" name="l00001"></a><span class="lineno"> 1</span><span class="comment">// Fill out your copyright notice in the Description page of Project Settings.</span></div> +<div class="line"><a id="l00002" name="l00002"></a><span class="lineno"> 2</span> </div> +<div class="line"><a id="l00003" name="l00003"></a><span class="lineno"> 3</span><span class="preprocessor">#pragma once</span></div> +<div class="line"><a id="l00004" name="l00004"></a><span class="lineno"> 4</span> </div> +<div class="line"><a id="l00005" name="l00005"></a><span class="lineno"> 5</span><span class="preprocessor">#include "CoreMinimal.h"</span></div> +<div class="line"><a id="l00006" name="l00006"></a><span class="lineno"> 6</span><span class="preprocessor">#include <Voxel/Public/FastNoise/VoxelFastNoise.h></span></div> +<div class="line"><a id="l00007" name="l00007"></a><span class="lineno"> 7</span><span class="preprocessor">#include <Voxel/Public/VoxelGenerators/VoxelGeneratorHelpers.h></span></div> +<div class="line"><a id="l00008" name="l00008"></a><span class="lineno"> 8</span><span class="preprocessor">#include "WorldGenerator.generated.h"</span></div> +<div class="line"><a id="l00009" name="l00009"></a><span class="lineno"> 9</span> </div> +<div class="line"><a id="l00013" name="l00013"></a><span class="lineno"> 13</span>UCLASS(Blueprintable)</div> +<div class="line"><a id="l00014" name="l00014"></a><span class="lineno"><a class="line" href="class_u_world_generator.html"> 14</a></span>class EXCAVATORSIMULATOR_API <a class="code hl_class" href="class_u_world_generator.html">UWorldGenerator</a> : public UVoxelGenerator</div> +<div class="line"><a id="l00015" name="l00015"></a><span class="lineno"> 15</span>{</div> +<div class="line"><a id="l00016" name="l00016"></a><span class="lineno"> 16</span> GENERATED_BODY()</div> +<div class="line"><a id="l00017" name="l00017"></a><span class="lineno"> 17</span><span class="keyword">public</span>:</div> +<div class="line"><a id="l00018" name="l00018"></a><span class="lineno"> 18</span> <a class="code hl_class" href="class_u_world_generator.html">UWorldGenerator</a>();</div> +<div class="line"><a id="l00019" name="l00019"></a><span class="lineno"> 19</span> <a class="code hl_class" href="class_u_world_generator.html">~UWorldGenerator</a>();</div> +<div class="line"><a id="l00020" name="l00020"></a><span class="lineno"> 20</span> <span class="keyword">virtual</span> TVoxelSharedRef<FVoxelGeneratorInstance> GetInstance() <span class="keyword">override</span>;</div> +<div class="line"><a id="l00021" name="l00021"></a><span class="lineno"> 21</span> </div> +<div class="line"><a id="l00022" name="l00022"></a><span class="lineno"> 22</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = <span class="stringliteral">"Generator"</span>)</div> +<div class="line"><a id="l00023" name="l00023"></a><span class="lineno"><a class="line" href="class_u_world_generator.html#a97672fed88363bef5359e4a9e1bc22d2"> 23</a></span> float NoiseHeight = 10.0f;</div> +<div class="line"><a id="l00024" name="l00024"></a><span class="lineno"> 24</span> </div> +<div class="line"><a id="l00025" name="l00025"></a><span class="lineno"> 25</span> UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Generator")</div> +<div class="line"><a id="l00026" name="l00026"></a><span class="lineno"><a class="line" href="class_u_world_generator.html#ae3e0313a980ab00d26cb7691a9d178e7"> 26</a></span> int32 Seed = 1337;</div> +<div class="line"><a id="l00027" name="l00027"></a><span class="lineno"> 27</span>};</div> +<div class="line"><a id="l00028" name="l00028"></a><span class="lineno"> 28</span> </div> +<div class="line"><a id="l00029" name="l00029"></a><span class="lineno"><a class="line" href="class_f_world_generator_instance.html"> 29</a></span>class <a class="code hl_class" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a> : public TVoxelGeneratorInstanceHelper<<a class="code hl_class" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a>, <a class="code hl_class" href="class_u_world_generator.html">UWorldGenerator</a> ></div> +<div class="line"><a id="l00030" name="l00030"></a><span class="lineno"> 30</span>{</div> +<div class="line"><a id="l00031" name="l00031"></a><span class="lineno"> 31</span><span class="keyword">public</span>:</div> +<div class="line"><a id="l00032" name="l00032"></a><span class="lineno"> 32</span> </div> +<div class="line"><a id="l00033" name="l00033"></a><span class="lineno"><a class="line" href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01"> 33</a></span> <span class="keyword">using </span><a class="code hl_typedef" href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01">Super</a> = TVoxelGeneratorInstanceHelper<FWorldGeneratorInstance, UWorldGenerator >;</div> +<div class="line"><a id="l00034" name="l00034"></a><span class="lineno"> 34</span> </div> +<div class="line"><a id="l00035" name="l00035"></a><span class="lineno"> 35</span> <span class="keyword">explicit</span> <a class="code hl_class" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a>(<span class="keyword">const</span> <a class="code hl_class" href="class_u_world_generator.html">UWorldGenerator</a>& MyGenerator);</div> +<div class="line"><a id="l00036" name="l00036"></a><span class="lineno"> 36</span> </div> +<div class="line"><a id="l00037" name="l00037"></a><span class="lineno"> 37</span> <span class="keyword">virtual</span> <span class="keywordtype">void</span> Init(<span class="keyword">const</span> FVoxelGeneratorInit& InitStruct) <span class="keyword">override</span>;</div> +<div class="line"><a id="l00038" name="l00038"></a><span class="lineno"> 38</span> </div> +<div class="line"><a id="l00039" name="l00039"></a><span class="lineno"> 39</span> v_flt GetValueImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, <span class="keyword">const</span> FVoxelItemStack& Items) <span class="keyword">const</span>;</div> +<div class="line"><a id="l00040" name="l00040"></a><span class="lineno"> 40</span> </div> +<div class="line"><a id="l00041" name="l00041"></a><span class="lineno"> 41</span> FVoxelMaterial GetMaterialImpl(v_flt X, v_flt Y, v_flt Z, int32 LOD, <span class="keyword">const</span> FVoxelItemStack& Items) <span class="keyword">const</span>;</div> +<div class="line"><a id="l00042" name="l00042"></a><span class="lineno"> 42</span> </div> +<div class="line"><a id="l00043" name="l00043"></a><span class="lineno"> 43</span> TVoxelRange<v_flt> GetValueRangeImpl(<span class="keyword">const</span> FVoxelIntBox& Bounds, int32 LOD, <span class="keyword">const</span> FVoxelItemStack& Items) <span class="keyword">const</span>;</div> +<div class="line"><a id="l00044" name="l00044"></a><span class="lineno"> 44</span> </div> +<div class="line"><a id="l00045" name="l00045"></a><span class="lineno"> 45</span> <span class="keyword">virtual</span> FVector GetUpVector(v_flt X, v_flt Y, v_flt Z) <span class="keyword">const</span> <span class="keyword">override</span> <span class="keyword">final</span>;</div> +<div class="line"><a id="l00046" name="l00046"></a><span class="lineno"> 46</span><span class="keyword">private</span>:</div> +<div class="line"><a id="l00047" name="l00047"></a><span class="lineno"> 47</span> <span class="keyword">const</span> <span class="keywordtype">float</span> NoiseHeight;</div> +<div class="line"><a id="l00048" name="l00048"></a><span class="lineno"> 48</span> <span class="keyword">const</span> int32 Seed;</div> +<div class="line"><a id="l00049" name="l00049"></a><span class="lineno"> 49</span> FVoxelFastNoise Noise;</div> +<div class="line"><a id="l00050" name="l00050"></a><span class="lineno"> 50</span>};</div> +<div class="ttc" id="aclass_f_world_generator_instance_html"><div class="ttname"><a href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></div><div class="ttdef"><b>Definition:</b> WorldGenerator.h:30</div></div> +<div class="ttc" id="aclass_f_world_generator_instance_html_a6835c93d686a4f8871078253233f7a01"><div class="ttname"><a href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01">FWorldGeneratorInstance::Super</a></div><div class="ttdeci">TVoxelGeneratorInstanceHelper< FWorldGeneratorInstance, UWorldGenerator > Super</div><div class="ttdef"><b>Definition:</b> WorldGenerator.h:33</div></div> +<div class="ttc" id="aclass_u_world_generator_html"><div class="ttname"><a href="class_u_world_generator.html">UWorldGenerator</a></div><div class="ttdef"><b>Definition:</b> WorldGenerator.h:15</div></div> +</div><!-- fragment --></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/annotated.html b/Doxygen/html/annotated.html new file mode 100644 index 0000000..01ad751 --- /dev/null +++ b/Doxygen/html/annotated.html @@ -0,0 +1,92 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Class List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">Class List</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock">Here are the classes, structs, unions and interfaces with brief descriptions:</div><div class="directory"> +<table class="directory"> +<tr id="row_0_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_a_custom_procedural_mesh_component.html" target="_self">ACustomProceduralMeshComponent</a></td><td class="desc"></td></tr> +<tr id="row_1_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_a_excavator_character.html" target="_self">AExcavatorCharacter</a></td><td class="desc"></td></tr> +<tr id="row_2_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_a_excavator_simulator_game_mode_base.html" target="_self">AExcavatorSimulatorGameModeBase</a></td><td class="desc"></td></tr> +<tr id="row_3_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_excavator_simulator.html" target="_self">ExcavatorSimulator</a></td><td class="desc"></td></tr> +<tr id="row_4_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_f_world_generator_instance.html" target="_self">FWorldGeneratorInstance</a></td><td class="desc"></td></tr> +<tr id="row_5_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_u_excavator_anim.html" target="_self">UExcavatorAnim</a></td><td class="desc"></td></tr> +<tr id="row_6_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_u_world_generator.html" target="_self">UWorldGenerator</a></td><td class="desc"></td></tr> +</table> +</div><!-- directory --> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/bc_s.png b/Doxygen/html/bc_s.png new file mode 100644 index 0000000..2eba412 --- /dev/null +++ b/Doxygen/html/bc_s.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e7ed0ef70f99bb7f763a48ddd95d5990e103bb145eedfd0a76d19c122374be2 +size 676 diff --git a/Doxygen/html/bc_sd.png b/Doxygen/html/bc_sd.png new file mode 100644 index 0000000..b259ca9 --- /dev/null +++ b/Doxygen/html/bc_sd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be713b2cca9757f7589575afd788e336fc967cbb3218d4973122f2f19572eae6 +size 635 diff --git a/Doxygen/html/bdwn.png b/Doxygen/html/bdwn.png new file mode 100644 index 0000000..4263830 --- /dev/null +++ b/Doxygen/html/bdwn.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:782b30d237bdbeddfde4aed01f007264cc116b2d4be2f398a7cb74ec7a5bc58b +size 147 diff --git a/Doxygen/html/class_a_custom_procedural_mesh_component-members.html b/Doxygen/html/class_a_custom_procedural_mesh_component-members.html new file mode 100644 index 0000000..a153ee5 --- /dev/null +++ b/Doxygen/html/class_a_custom_procedural_mesh_component-members.html @@ -0,0 +1,90 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Member List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">ACustomProceduralMeshComponent Member List</div></div> +</div><!--header--> +<div class="contents"> + +<p>This is the complete list of members for <a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a>, including all inherited members.</p> +<table class="directory"> + <tr class="even"><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html#ad2a083e99c4303ff692c6de7fd7c89d7">ACustomProceduralMeshComponent</a>()</td><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html#a91a659f1e60e2aadddc5ffd88372f66d">BeginPlay</a>() override</td><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></td><td class="entry"><span class="mlabel">protected</span><span class="mlabel">virtual</span></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html#a9d663c4e5f670976a2e9a9de0b9e9268">CreateTriangle</a>()</td><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html#a722837f83f1b0db0cb97f47b50d310b3">CreateVertices</a>(int layer)</td><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html#ac1c8d09545e81f4cee3a33d8e1ff0810">CreateWall</a>()</td><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html#a9b6c8c619a8d0396e2d3d19f61007427">Tick</a>(float DeltaTime) override</td><td class="entry"><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></td><td class="entry"><span class="mlabel">virtual</span></td></tr> +</table></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_a_custom_procedural_mesh_component.html b/Doxygen/html/class_a_custom_procedural_mesh_component.html new file mode 100644 index 0000000..cab4694 --- /dev/null +++ b/Doxygen/html/class_a_custom_procedural_mesh_component.html @@ -0,0 +1,238 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ACustomProceduralMeshComponent Class Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#pub-methods">Public Member Functions</a> | +<a href="#pro-methods">Protected Member Functions</a> | +<a href="class_a_custom_procedural_mesh_component-members.html">List of all members</a> </div> + <div class="headertitle"><div class="title">ACustomProceduralMeshComponent Class Reference</div></div> +</div><!--header--> +<div class="contents"> + +<p><code>#include <<a class="el" href="_custom_procedural_mesh_component_8h_source.html">CustomProceduralMeshComponent.h</a>></code></p> +<div class="dynheader"> +Inheritance diagram for ACustomProceduralMeshComponent:</div> +<div class="dyncontent"> + <div class="center"> + <img src="class_a_custom_procedural_mesh_component.png" alt=""/> + </div></div> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-methods" name="pub-methods"></a> +Public Member Functions</h2></td></tr> +<tr class="memitem:ad2a083e99c4303ff692c6de7fd7c89d7"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_custom_procedural_mesh_component.html#ad2a083e99c4303ff692c6de7fd7c89d7">ACustomProceduralMeshComponent</a> ()</td></tr> +<tr class="separator:ad2a083e99c4303ff692c6de7fd7c89d7"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a9b6c8c619a8d0396e2d3d19f61007427"><td class="memItemLeft" align="right" valign="top">virtual void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_custom_procedural_mesh_component.html#a9b6c8c619a8d0396e2d3d19f61007427">Tick</a> (float DeltaTime) override</td></tr> +<tr class="separator:a9b6c8c619a8d0396e2d3d19f61007427"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a9d663c4e5f670976a2e9a9de0b9e9268"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_custom_procedural_mesh_component.html#a9d663c4e5f670976a2e9a9de0b9e9268">CreateTriangle</a> ()</td></tr> +<tr class="separator:a9d663c4e5f670976a2e9a9de0b9e9268"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:ac1c8d09545e81f4cee3a33d8e1ff0810"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_custom_procedural_mesh_component.html#ac1c8d09545e81f4cee3a33d8e1ff0810">CreateWall</a> ()</td></tr> +<tr class="separator:ac1c8d09545e81f4cee3a33d8e1ff0810"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a722837f83f1b0db0cb97f47b50d310b3"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_custom_procedural_mesh_component.html#a722837f83f1b0db0cb97f47b50d310b3">CreateVertices</a> (int layer)</td></tr> +<tr class="separator:a722837f83f1b0db0cb97f47b50d310b3"><td class="memSeparator" colspan="2"> </td></tr> +</table><table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pro-methods" name="pro-methods"></a> +Protected Member Functions</h2></td></tr> +<tr class="memitem:a91a659f1e60e2aadddc5ffd88372f66d"><td class="memItemLeft" align="right" valign="top">virtual void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_custom_procedural_mesh_component.html#a91a659f1e60e2aadddc5ffd88372f66d">BeginPlay</a> () override</td></tr> +<tr class="separator:a91a659f1e60e2aadddc5ffd88372f66d"><td class="memSeparator" colspan="2"> </td></tr> +</table> +<h2 class="groupheader">Constructor & Destructor Documentation</h2> +<a id="ad2a083e99c4303ff692c6de7fd7c89d7" name="ad2a083e99c4303ff692c6de7fd7c89d7"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ad2a083e99c4303ff692c6de7fd7c89d7">◆ </a></span>ACustomProceduralMeshComponent()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">ACustomProceduralMeshComponent::ACustomProceduralMeshComponent </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<h2 class="groupheader">Member Function Documentation</h2> +<a id="a91a659f1e60e2aadddc5ffd88372f66d" name="a91a659f1e60e2aadddc5ffd88372f66d"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a91a659f1e60e2aadddc5ffd88372f66d">◆ </a></span>BeginPlay()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">void ACustomProceduralMeshComponent::BeginPlay </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">override</span><span class="mlabel">protected</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> + +</div> +</div> +<a id="a9d663c4e5f670976a2e9a9de0b9e9268" name="a9d663c4e5f670976a2e9a9de0b9e9268"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a9d663c4e5f670976a2e9a9de0b9e9268">◆ </a></span>CreateTriangle()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void ACustomProceduralMeshComponent::CreateTriangle </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="a722837f83f1b0db0cb97f47b50d310b3" name="a722837f83f1b0db0cb97f47b50d310b3"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a722837f83f1b0db0cb97f47b50d310b3">◆ </a></span>CreateVertices()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void ACustomProceduralMeshComponent::CreateVertices </td> + <td>(</td> + <td class="paramtype">int </td> + <td class="paramname"><em>layer</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="ac1c8d09545e81f4cee3a33d8e1ff0810" name="ac1c8d09545e81f4cee3a33d8e1ff0810"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ac1c8d09545e81f4cee3a33d8e1ff0810">◆ </a></span>CreateWall()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void ACustomProceduralMeshComponent::CreateWall </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="a9b6c8c619a8d0396e2d3d19f61007427" name="a9b6c8c619a8d0396e2d3d19f61007427"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a9b6c8c619a8d0396e2d3d19f61007427">◆ </a></span>Tick()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">void ACustomProceduralMeshComponent::Tick </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>DeltaTime</em></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">override</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> + +</div> +</div> +<hr/>The documentation for this class was generated from the following files:<ul> +<li><a class="el" href="_custom_procedural_mesh_component_8h_source.html">CustomProceduralMeshComponent.h</a></li> +<li><a class="el" href="_custom_procedural_mesh_component_8cpp.html">CustomProceduralMeshComponent.cpp</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_a_custom_procedural_mesh_component.png b/Doxygen/html/class_a_custom_procedural_mesh_component.png new file mode 100644 index 0000000..7fe5b3f --- /dev/null +++ b/Doxygen/html/class_a_custom_procedural_mesh_component.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ae5234859aeebbdc9c66905324780c0d92f0697605b4694734c1f2a073c4d31 +size 668 diff --git a/Doxygen/html/class_a_excavator_character-members.html b/Doxygen/html/class_a_excavator_character-members.html new file mode 100644 index 0000000..ecbcc86 --- /dev/null +++ b/Doxygen/html/class_a_excavator_character-members.html @@ -0,0 +1,99 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Member List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">AExcavatorCharacter Member List</div></div> +</div><!--header--> +<div class="contents"> + +<p>This is the complete list of members for <a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a>, including all inherited members.</p> +<table class="directory"> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#a897f85cbfeb62c8cf9357124030678f0">AExcavatorCharacter</a>()</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_excavator_character.html#a4c0322adf74c147b25e0a56eb82e8b63">BeamBottomRotation</a>(float RotationDirection)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#a2fd6354fdc9f48c9ec37aa8a0881bb39">BeamBottomRotationVR</a>(float LeftJoyStickRotationY)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_excavator_character.html#a700a5e4f8e43868c15d6b60460006eb9">BeamTopRotation</a>(float RotationDirection)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#a0fa69b8a1aa0f05dad74a41da45f5551">BeamTopRotationVR</a>(float RightJoyStickRotationY)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_excavator_character.html#aff0f3c6a77a15a8476981c7845dff158">BeginPlay</a>() override</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"><span class="mlabel">protected</span><span class="mlabel">virtual</span></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#a63014640642f4964440cd3923cc8396f">BodyRotation</a>(float RotationDirection)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_excavator_character.html#a52c7b1ac54ccb49c0cd5e9baf25dc338">BodyRotationVR</a>(float LeftJoyStickRotationX)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#a18933e6161a7489652e64e06d6dc9d72">BucketRotationReleaceRocks</a>(float RotationDirection)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_excavator_character.html#a5eaf154b0d816e682669609f6a1145b4">BucketRotationVR</a>(float RightJoyStickRotationX)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#abfc64bb0a310badadb9e85e7336b0735">Movement</a>(float Direction)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_excavator_character.html#ae0c6e52ac2b153baa789be4e0b2b75fc">SetupPlayerInputComponent</a>(class UInputComponent *PlayerInputComponent) override</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"><span class="mlabel">virtual</span></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#a080c503815b1f096cf7704e5bfa47df1">Tick</a>(float DeltaTime) override</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"><span class="mlabel">virtual</span></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_a_excavator_character.html#a3f262813a65448739540f41c73498f56">VehicleRotation</a>(float RotationDirection)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_a_excavator_character.html#a173be62746e6adc66879eea680f21d56">VehicleRotationVR</a>(float AxisValue)</td><td class="entry"><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></td><td class="entry"></td></tr> +</table></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_a_excavator_character.html b/Doxygen/html/class_a_excavator_character.html new file mode 100644 index 0000000..d54c6d7 --- /dev/null +++ b/Doxygen/html/class_a_excavator_character.html @@ -0,0 +1,438 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: AExcavatorCharacter Class Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#pub-methods">Public Member Functions</a> | +<a href="#pro-methods">Protected Member Functions</a> | +<a href="class_a_excavator_character-members.html">List of all members</a> </div> + <div class="headertitle"><div class="title">AExcavatorCharacter Class Reference</div></div> +</div><!--header--> +<div class="contents"> + +<p><code>#include <<a class="el" href="_excavator_character_8h_source.html">ExcavatorCharacter.h</a>></code></p> +<div class="dynheader"> +Inheritance diagram for AExcavatorCharacter:</div> +<div class="dyncontent"> + <div class="center"> + <img src="class_a_excavator_character.png" alt=""/> + </div></div> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-methods" name="pub-methods"></a> +Public Member Functions</h2></td></tr> +<tr class="memitem:a897f85cbfeb62c8cf9357124030678f0"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a897f85cbfeb62c8cf9357124030678f0">AExcavatorCharacter</a> ()</td></tr> +<tr class="separator:a897f85cbfeb62c8cf9357124030678f0"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a080c503815b1f096cf7704e5bfa47df1"><td class="memItemLeft" align="right" valign="top">virtual void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a080c503815b1f096cf7704e5bfa47df1">Tick</a> (float DeltaTime) override</td></tr> +<tr class="separator:a080c503815b1f096cf7704e5bfa47df1"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:ae0c6e52ac2b153baa789be4e0b2b75fc"><td class="memItemLeft" align="right" valign="top">virtual void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#ae0c6e52ac2b153baa789be4e0b2b75fc">SetupPlayerInputComponent</a> (class UInputComponent *PlayerInputComponent) override</td></tr> +<tr class="separator:ae0c6e52ac2b153baa789be4e0b2b75fc"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:abfc64bb0a310badadb9e85e7336b0735"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#abfc64bb0a310badadb9e85e7336b0735">Movement</a> (float Direction)</td></tr> +<tr class="separator:abfc64bb0a310badadb9e85e7336b0735"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a18933e6161a7489652e64e06d6dc9d72"><td class="memItemLeft" align="right" valign="top">bool </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a18933e6161a7489652e64e06d6dc9d72">BucketRotationReleaceRocks</a> (float RotationDirection)</td></tr> +<tr class="separator:a18933e6161a7489652e64e06d6dc9d72"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a5eaf154b0d816e682669609f6a1145b4"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a5eaf154b0d816e682669609f6a1145b4">BucketRotationVR</a> (float RightJoyStickRotationX)</td></tr> +<tr class="separator:a5eaf154b0d816e682669609f6a1145b4"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a700a5e4f8e43868c15d6b60460006eb9"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a700a5e4f8e43868c15d6b60460006eb9">BeamTopRotation</a> (float RotationDirection)</td></tr> +<tr class="separator:a700a5e4f8e43868c15d6b60460006eb9"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a0fa69b8a1aa0f05dad74a41da45f5551"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a0fa69b8a1aa0f05dad74a41da45f5551">BeamTopRotationVR</a> (float RightJoyStickRotationY)</td></tr> +<tr class="separator:a0fa69b8a1aa0f05dad74a41da45f5551"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a4c0322adf74c147b25e0a56eb82e8b63"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a4c0322adf74c147b25e0a56eb82e8b63">BeamBottomRotation</a> (float RotationDirection)</td></tr> +<tr class="separator:a4c0322adf74c147b25e0a56eb82e8b63"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a2fd6354fdc9f48c9ec37aa8a0881bb39"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a2fd6354fdc9f48c9ec37aa8a0881bb39">BeamBottomRotationVR</a> (float LeftJoyStickRotationY)</td></tr> +<tr class="separator:a2fd6354fdc9f48c9ec37aa8a0881bb39"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a63014640642f4964440cd3923cc8396f"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a63014640642f4964440cd3923cc8396f">BodyRotation</a> (float RotationDirection)</td></tr> +<tr class="separator:a63014640642f4964440cd3923cc8396f"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a52c7b1ac54ccb49c0cd5e9baf25dc338"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a52c7b1ac54ccb49c0cd5e9baf25dc338">BodyRotationVR</a> (float LeftJoyStickRotationX)</td></tr> +<tr class="separator:a52c7b1ac54ccb49c0cd5e9baf25dc338"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a3f262813a65448739540f41c73498f56"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a3f262813a65448739540f41c73498f56">VehicleRotation</a> (float RotationDirection)</td></tr> +<tr class="separator:a3f262813a65448739540f41c73498f56"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a173be62746e6adc66879eea680f21d56"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#a173be62746e6adc66879eea680f21d56">VehicleRotationVR</a> (float AxisValue)</td></tr> +<tr class="separator:a173be62746e6adc66879eea680f21d56"><td class="memSeparator" colspan="2"> </td></tr> +</table><table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pro-methods" name="pro-methods"></a> +Protected Member Functions</h2></td></tr> +<tr class="memitem:aff0f3c6a77a15a8476981c7845dff158"><td class="memItemLeft" align="right" valign="top">virtual void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_a_excavator_character.html#aff0f3c6a77a15a8476981c7845dff158">BeginPlay</a> () override</td></tr> +<tr class="separator:aff0f3c6a77a15a8476981c7845dff158"><td class="memSeparator" colspan="2"> </td></tr> +</table> +<h2 class="groupheader">Constructor & Destructor Documentation</h2> +<a id="a897f85cbfeb62c8cf9357124030678f0" name="a897f85cbfeb62c8cf9357124030678f0"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a897f85cbfeb62c8cf9357124030678f0">◆ </a></span>AExcavatorCharacter()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">AExcavatorCharacter::AExcavatorCharacter </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Sets default values for this character's properties. </p> + +</div> +</div> +<h2 class="groupheader">Member Function Documentation</h2> +<a id="a4c0322adf74c147b25e0a56eb82e8b63" name="a4c0322adf74c147b25e0a56eb82e8b63"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a4c0322adf74c147b25e0a56eb82e8b63">◆ </a></span>BeamBottomRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BeamBottomRotation </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>RotationDirection</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Rotates beam bottom @Param RotationDirection beam bottom rotation direction </p> + +</div> +</div> +<a id="a2fd6354fdc9f48c9ec37aa8a0881bb39" name="a2fd6354fdc9f48c9ec37aa8a0881bb39"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a2fd6354fdc9f48c9ec37aa8a0881bb39">◆ </a></span>BeamBottomRotationVR()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BeamBottomRotationVR </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>LeftJoyStickRotationY</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="a700a5e4f8e43868c15d6b60460006eb9" name="a700a5e4f8e43868c15d6b60460006eb9"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a700a5e4f8e43868c15d6b60460006eb9">◆ </a></span>BeamTopRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BeamTopRotation </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>RotationDirection</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Rotates beam top @Param RotationDirection beam top rotation direction </p> + +</div> +</div> +<a id="a0fa69b8a1aa0f05dad74a41da45f5551" name="a0fa69b8a1aa0f05dad74a41da45f5551"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a0fa69b8a1aa0f05dad74a41da45f5551">◆ </a></span>BeamTopRotationVR()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BeamTopRotationVR </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>RightJoyStickRotationY</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="aff0f3c6a77a15a8476981c7845dff158" name="aff0f3c6a77a15a8476981c7845dff158"></a> +<h2 class="memtitle"><span class="permalink"><a href="#aff0f3c6a77a15a8476981c7845dff158">◆ </a></span>BeginPlay()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BeginPlay </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">override</span><span class="mlabel">protected</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Called when the game starts or when spawned. </p> + +</div> +</div> +<a id="a63014640642f4964440cd3923cc8396f" name="a63014640642f4964440cd3923cc8396f"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a63014640642f4964440cd3923cc8396f">◆ </a></span>BodyRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BodyRotation </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>RotationDirection</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Rotates excavator body @Param RotationDirection body rotation direction </p> + +</div> +</div> +<a id="a52c7b1ac54ccb49c0cd5e9baf25dc338" name="a52c7b1ac54ccb49c0cd5e9baf25dc338"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a52c7b1ac54ccb49c0cd5e9baf25dc338">◆ </a></span>BodyRotationVR()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BodyRotationVR </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>LeftJoyStickRotationX</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="a18933e6161a7489652e64e06d6dc9d72" name="a18933e6161a7489652e64e06d6dc9d72"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a18933e6161a7489652e64e06d6dc9d72">◆ </a></span>BucketRotationReleaceRocks()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">bool AExcavatorCharacter::BucketRotationReleaceRocks </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>RotationDirection</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Rotates bucket and will release rocks at angle @Param RotationDirection buckets rotation direction </p> + +</div> +</div> +<a id="a5eaf154b0d816e682669609f6a1145b4" name="a5eaf154b0d816e682669609f6a1145b4"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a5eaf154b0d816e682669609f6a1145b4">◆ </a></span>BucketRotationVR()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::BucketRotationVR </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>RightJoyStickRotationX</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="abfc64bb0a310badadb9e85e7336b0735" name="abfc64bb0a310badadb9e85e7336b0735"></a> +<h2 class="memtitle"><span class="permalink"><a href="#abfc64bb0a310badadb9e85e7336b0735">◆ </a></span>Movement()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::Movement </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>Direction</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Movement, move the player @Param direction player is going </p> + +</div> +</div> +<a id="ae0c6e52ac2b153baa789be4e0b2b75fc" name="ae0c6e52ac2b153baa789be4e0b2b75fc"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ae0c6e52ac2b153baa789be4e0b2b75fc">◆ </a></span>SetupPlayerInputComponent()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::SetupPlayerInputComponent </td> + <td>(</td> + <td class="paramtype">class UInputComponent * </td> + <td class="paramname"><em>PlayerInputComponent</em></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">override</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Called to bind functionality to input. </p> + +</div> +</div> +<a id="a080c503815b1f096cf7704e5bfa47df1" name="a080c503815b1f096cf7704e5bfa47df1"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a080c503815b1f096cf7704e5bfa47df1">◆ </a></span>Tick()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::Tick </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>DeltaTime</em></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">override</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Called every frame. </p> + +</div> +</div> +<a id="a3f262813a65448739540f41c73498f56" name="a3f262813a65448739540f41c73498f56"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a3f262813a65448739540f41c73498f56">◆ </a></span>VehicleRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::VehicleRotation </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>RotationDirection</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Rotates whole vehicle @Param RotationDirection vehicle rotation direction </p> + +</div> +</div> +<a id="a173be62746e6adc66879eea680f21d56" name="a173be62746e6adc66879eea680f21d56"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a173be62746e6adc66879eea680f21d56">◆ </a></span>VehicleRotationVR()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void AExcavatorCharacter::VehicleRotationVR </td> + <td>(</td> + <td class="paramtype">float </td> + <td class="paramname"><em>AxisValue</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<hr/>The documentation for this class was generated from the following files:<ul> +<li><a class="el" href="_excavator_character_8h_source.html">ExcavatorCharacter.h</a></li> +<li><a class="el" href="_excavator_character_8cpp.html">ExcavatorCharacter.cpp</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_a_excavator_character.png b/Doxygen/html/class_a_excavator_character.png new file mode 100644 index 0000000..3df3c49 --- /dev/null +++ b/Doxygen/html/class_a_excavator_character.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:539793d4a2b1aed8afce99bf35cb3f65ecc428d9c22026b3fb64408005366d21 +size 586 diff --git a/Doxygen/html/class_a_excavator_simulator_game_mode_base-members.html b/Doxygen/html/class_a_excavator_simulator_game_mode_base-members.html new file mode 100644 index 0000000..22ecd00 --- /dev/null +++ b/Doxygen/html/class_a_excavator_simulator_game_mode_base-members.html @@ -0,0 +1,83 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Member List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">AExcavatorSimulatorGameModeBase Member List</div></div> +</div><!--header--> +<div class="contents"> + +<p>This is the complete list of members for <a class="el" href="class_a_excavator_simulator_game_mode_base.html">AExcavatorSimulatorGameModeBase</a>, including all inherited members.</p> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_a_excavator_simulator_game_mode_base.html b/Doxygen/html/class_a_excavator_simulator_game_mode_base.html new file mode 100644 index 0000000..00fc909 --- /dev/null +++ b/Doxygen/html/class_a_excavator_simulator_game_mode_base.html @@ -0,0 +1,94 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: AExcavatorSimulatorGameModeBase Class Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="class_a_excavator_simulator_game_mode_base-members.html">List of all members</a> </div> + <div class="headertitle"><div class="title">AExcavatorSimulatorGameModeBase Class Reference</div></div> +</div><!--header--> +<div class="contents"> + +<p><code>#include <<a class="el" href="_excavator_simulator_game_mode_base_8h_source.html">ExcavatorSimulatorGameModeBase.h</a>></code></p> +<div class="dynheader"> +Inheritance diagram for AExcavatorSimulatorGameModeBase:</div> +<div class="dyncontent"> + <div class="center"> + <img src="class_a_excavator_simulator_game_mode_base.png" alt=""/> + </div></div> +<hr/>The documentation for this class was generated from the following file:<ul> +<li><a class="el" href="_excavator_simulator_game_mode_base_8h_source.html">ExcavatorSimulatorGameModeBase.h</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_a_excavator_simulator_game_mode_base.png b/Doxygen/html/class_a_excavator_simulator_game_mode_base.png new file mode 100644 index 0000000..8347986 --- /dev/null +++ b/Doxygen/html/class_a_excavator_simulator_game_mode_base.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2088ec0030996034f3c7281d781fde59f4a3ae72649ae6f61ee241352d092713 +size 779 diff --git a/Doxygen/html/class_excavator_simulator-members.html b/Doxygen/html/class_excavator_simulator-members.html new file mode 100644 index 0000000..370aa58 --- /dev/null +++ b/Doxygen/html/class_excavator_simulator-members.html @@ -0,0 +1,85 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Member List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorSimulator Member List</div></div> +</div><!--header--> +<div class="contents"> + +<p>This is the complete list of members for <a class="el" href="class_excavator_simulator.html">ExcavatorSimulator</a>, including all inherited members.</p> +<table class="directory"> + <tr class="even"><td class="entry"><a class="el" href="class_excavator_simulator.html#ac3cbb6777236f4b4900e850d58050951">ExcavatorSimulator</a>(ReadOnlyTargetRules Target)</td><td class="entry"><a class="el" href="class_excavator_simulator.html">ExcavatorSimulator</a></td><td class="entry"><span class="mlabel">inline</span></td></tr> +</table></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_excavator_simulator.html b/Doxygen/html/class_excavator_simulator.html new file mode 100644 index 0000000..fac94ea --- /dev/null +++ b/Doxygen/html/class_excavator_simulator.html @@ -0,0 +1,126 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: ExcavatorSimulator Class Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#pub-methods">Public Member Functions</a> | +<a href="class_excavator_simulator-members.html">List of all members</a> </div> + <div class="headertitle"><div class="title">ExcavatorSimulator Class Reference</div></div> +</div><!--header--> +<div class="contents"> +<div class="dynheader"> +Inheritance diagram for ExcavatorSimulator:</div> +<div class="dyncontent"> + <div class="center"> + <img src="class_excavator_simulator.png" alt=""/> + </div></div> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-methods" name="pub-methods"></a> +Public Member Functions</h2></td></tr> +<tr class="memitem:ac3cbb6777236f4b4900e850d58050951"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="class_excavator_simulator.html#ac3cbb6777236f4b4900e850d58050951">ExcavatorSimulator</a> (ReadOnlyTargetRules Target)</td></tr> +<tr class="separator:ac3cbb6777236f4b4900e850d58050951"><td class="memSeparator" colspan="2"> </td></tr> +</table> +<h2 class="groupheader">Constructor & Destructor Documentation</h2> +<a id="ac3cbb6777236f4b4900e850d58050951" name="ac3cbb6777236f4b4900e850d58050951"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ac3cbb6777236f4b4900e850d58050951">◆ </a></span>ExcavatorSimulator()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">ExcavatorSimulator.ExcavatorSimulator </td> + <td>(</td> + <td class="paramtype">ReadOnlyTargetRules </td> + <td class="paramname"><em>Target</em></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">inline</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> + +</div> +</div> +<hr/>The documentation for this class was generated from the following file:<ul> +<li><a class="el" href="_excavator_simulator_8_build_8cs.html">ExcavatorSimulator.Build.cs</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_excavator_simulator.png b/Doxygen/html/class_excavator_simulator.png new file mode 100644 index 0000000..700c5b6 --- /dev/null +++ b/Doxygen/html/class_excavator_simulator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12ca54fe46e2f20d253fb4e0ef6a13e6b715f1b0a13fa8425096d1b6d0c1a7c0 +size 530 diff --git a/Doxygen/html/class_f_world_generator_instance-members.html b/Doxygen/html/class_f_world_generator_instance-members.html new file mode 100644 index 0000000..b835d8f --- /dev/null +++ b/Doxygen/html/class_f_world_generator_instance-members.html @@ -0,0 +1,91 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Member List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">FWorldGeneratorInstance Member List</div></div> +</div><!--header--> +<div class="contents"> + +<p>This is the complete list of members for <a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a>, including all inherited members.</p> +<table class="directory"> + <tr class="even"><td class="entry"><a class="el" href="class_f_world_generator_instance.html#a9340a460da1c5a05a25e496f5bbe3e47">FWorldGeneratorInstance</a>(const UWorldGenerator &MyGenerator)</td><td class="entry"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td><td class="entry"><span class="mlabel">explicit</span></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_f_world_generator_instance.html#a5d982bbb90e6a7368d4eab2bbf4107a0">GetMaterialImpl</a>(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack &Items) const</td><td class="entry"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_f_world_generator_instance.html#a41a32aee12e9228c3ec2bdfa4be7a92a">GetUpVector</a>(v_flt X, v_flt Y, v_flt Z) const override final</td><td class="entry"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td><td class="entry"><span class="mlabel">virtual</span></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_f_world_generator_instance.html#a9a18ba21423f11dc01d3a2e54fbd214f">GetValueImpl</a>(v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack &Items) const</td><td class="entry"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_f_world_generator_instance.html#aa18eccf536acdf8d2115f32ac0dab1ef">GetValueRangeImpl</a>(const FVoxelIntBox &Bounds, int32 LOD, const FVoxelItemStack &Items) const</td><td class="entry"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_f_world_generator_instance.html#acb9442be2e711fb3ba4d146bf584bd5d">Init</a>(const FVoxelGeneratorInit &InitStruct) override</td><td class="entry"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td><td class="entry"><span class="mlabel">virtual</span></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01">Super</a> typedef</td><td class="entry"><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></td><td class="entry"></td></tr> +</table></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_f_world_generator_instance.html b/Doxygen/html/class_f_world_generator_instance.html new file mode 100644 index 0000000..f1a03f7 --- /dev/null +++ b/Doxygen/html/class_f_world_generator_instance.html @@ -0,0 +1,355 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: FWorldGeneratorInstance Class Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#pub-types">Public Types</a> | +<a href="#pub-methods">Public Member Functions</a> | +<a href="class_f_world_generator_instance-members.html">List of all members</a> </div> + <div class="headertitle"><div class="title">FWorldGeneratorInstance Class Reference</div></div> +</div><!--header--> +<div class="contents"> + +<p><code>#include <<a class="el" href="_world_generator_8h_source.html">WorldGenerator.h</a>></code></p> +<div class="dynheader"> +Inheritance diagram for FWorldGeneratorInstance:</div> +<div class="dyncontent"> + <div class="center"> + <img src="class_f_world_generator_instance.png" alt=""/> + </div></div> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-types" name="pub-types"></a> +Public Types</h2></td></tr> +<tr class="memitem:a6835c93d686a4f8871078253233f7a01"><td class="memItemLeft" align="right" valign="top">using </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01">Super</a> = TVoxelGeneratorInstanceHelper< <a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a>, <a class="el" href="class_u_world_generator.html">UWorldGenerator</a> ></td></tr> +<tr class="separator:a6835c93d686a4f8871078253233f7a01"><td class="memSeparator" colspan="2"> </td></tr> +</table><table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-methods" name="pub-methods"></a> +Public Member Functions</h2></td></tr> +<tr class="memitem:a9340a460da1c5a05a25e496f5bbe3e47"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html#a9340a460da1c5a05a25e496f5bbe3e47">FWorldGeneratorInstance</a> (const <a class="el" href="class_u_world_generator.html">UWorldGenerator</a> &MyGenerator)</td></tr> +<tr class="separator:a9340a460da1c5a05a25e496f5bbe3e47"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:acb9442be2e711fb3ba4d146bf584bd5d"><td class="memItemLeft" align="right" valign="top">virtual void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html#acb9442be2e711fb3ba4d146bf584bd5d">Init</a> (const FVoxelGeneratorInit &InitStruct) override</td></tr> +<tr class="separator:acb9442be2e711fb3ba4d146bf584bd5d"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a9a18ba21423f11dc01d3a2e54fbd214f"><td class="memItemLeft" align="right" valign="top">v_flt </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html#a9a18ba21423f11dc01d3a2e54fbd214f">GetValueImpl</a> (v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack &Items) const</td></tr> +<tr class="separator:a9a18ba21423f11dc01d3a2e54fbd214f"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a5d982bbb90e6a7368d4eab2bbf4107a0"><td class="memItemLeft" align="right" valign="top">FVoxelMaterial </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html#a5d982bbb90e6a7368d4eab2bbf4107a0">GetMaterialImpl</a> (v_flt X, v_flt Y, v_flt Z, int32 LOD, const FVoxelItemStack &Items) const</td></tr> +<tr class="separator:a5d982bbb90e6a7368d4eab2bbf4107a0"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:aa18eccf536acdf8d2115f32ac0dab1ef"><td class="memItemLeft" align="right" valign="top">TVoxelRange< v_flt > </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html#aa18eccf536acdf8d2115f32ac0dab1ef">GetValueRangeImpl</a> (const FVoxelIntBox &Bounds, int32 LOD, const FVoxelItemStack &Items) const</td></tr> +<tr class="separator:aa18eccf536acdf8d2115f32ac0dab1ef"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a41a32aee12e9228c3ec2bdfa4be7a92a"><td class="memItemLeft" align="right" valign="top">virtual FVector </td><td class="memItemRight" valign="bottom"><a class="el" href="class_f_world_generator_instance.html#a41a32aee12e9228c3ec2bdfa4be7a92a">GetUpVector</a> (v_flt X, v_flt Y, v_flt Z) const override final</td></tr> +<tr class="separator:a41a32aee12e9228c3ec2bdfa4be7a92a"><td class="memSeparator" colspan="2"> </td></tr> +</table> +<h2 class="groupheader">Member Typedef Documentation</h2> +<a id="a6835c93d686a4f8871078253233f7a01" name="a6835c93d686a4f8871078253233f7a01"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a6835c93d686a4f8871078253233f7a01">◆ </a></span>Super</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">using <a class="el" href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01">FWorldGeneratorInstance::Super</a> = TVoxelGeneratorInstanceHelper<<a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a>, <a class="el" href="class_u_world_generator.html">UWorldGenerator</a> ></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<h2 class="groupheader">Constructor & Destructor Documentation</h2> +<a id="a9340a460da1c5a05a25e496f5bbe3e47" name="a9340a460da1c5a05a25e496f5bbe3e47"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a9340a460da1c5a05a25e496f5bbe3e47">◆ </a></span>FWorldGeneratorInstance()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">FWorldGeneratorInstance::FWorldGeneratorInstance </td> + <td>(</td> + <td class="paramtype">const <a class="el" href="class_u_world_generator.html">UWorldGenerator</a> & </td> + <td class="paramname"><em>MyGenerator</em></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">explicit</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> + +</div> +</div> +<h2 class="groupheader">Member Function Documentation</h2> +<a id="a5d982bbb90e6a7368d4eab2bbf4107a0" name="a5d982bbb90e6a7368d4eab2bbf4107a0"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a5d982bbb90e6a7368d4eab2bbf4107a0">◆ </a></span>GetMaterialImpl()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">FVoxelMaterial FWorldGeneratorInstance::GetMaterialImpl </td> + <td>(</td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>X</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>Y</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>Z</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">int32 </td> + <td class="paramname"><em>LOD</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">const FVoxelItemStack & </td> + <td class="paramname"><em>Items</em> </td> + </tr> + <tr> + <td></td> + <td>)</td> + <td></td><td> const</td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="a41a32aee12e9228c3ec2bdfa4be7a92a" name="a41a32aee12e9228c3ec2bdfa4be7a92a"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a41a32aee12e9228c3ec2bdfa4be7a92a">◆ </a></span>GetUpVector()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">FVector FWorldGeneratorInstance::GetUpVector </td> + <td>(</td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>X</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>Y</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>Z</em> </td> + </tr> + <tr> + <td></td> + <td>)</td> + <td></td><td> const</td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">final</span><span class="mlabel">override</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> + +</div> +</div> +<a id="a9a18ba21423f11dc01d3a2e54fbd214f" name="a9a18ba21423f11dc01d3a2e54fbd214f"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a9a18ba21423f11dc01d3a2e54fbd214f">◆ </a></span>GetValueImpl()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">v_flt FWorldGeneratorInstance::GetValueImpl </td> + <td>(</td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>X</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>Y</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">v_flt </td> + <td class="paramname"><em>Z</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">int32 </td> + <td class="paramname"><em>LOD</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">const FVoxelItemStack & </td> + <td class="paramname"><em>Items</em> </td> + </tr> + <tr> + <td></td> + <td>)</td> + <td></td><td> const</td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="aa18eccf536acdf8d2115f32ac0dab1ef" name="aa18eccf536acdf8d2115f32ac0dab1ef"></a> +<h2 class="memtitle"><span class="permalink"><a href="#aa18eccf536acdf8d2115f32ac0dab1ef">◆ </a></span>GetValueRangeImpl()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">TVoxelRange< v_flt > FWorldGeneratorInstance::GetValueRangeImpl </td> + <td>(</td> + <td class="paramtype">const FVoxelIntBox & </td> + <td class="paramname"><em>Bounds</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">int32 </td> + <td class="paramname"><em>LOD</em>, </td> + </tr> + <tr> + <td class="paramkey"></td> + <td></td> + <td class="paramtype">const FVoxelItemStack & </td> + <td class="paramname"><em>Items</em> </td> + </tr> + <tr> + <td></td> + <td>)</td> + <td></td><td> const</td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="acb9442be2e711fb3ba4d146bf584bd5d" name="acb9442be2e711fb3ba4d146bf584bd5d"></a> +<h2 class="memtitle"><span class="permalink"><a href="#acb9442be2e711fb3ba4d146bf584bd5d">◆ </a></span>Init()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">void FWorldGeneratorInstance::Init </td> + <td>(</td> + <td class="paramtype">const FVoxelGeneratorInit & </td> + <td class="paramname"><em>InitStruct</em></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">override</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> + +</div> +</div> +<hr/>The documentation for this class was generated from the following files:<ul> +<li><a class="el" href="_world_generator_8h_source.html">WorldGenerator.h</a></li> +<li><a class="el" href="_world_generator_8cpp.html">WorldGenerator.cpp</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_f_world_generator_instance.png b/Doxygen/html/class_f_world_generator_instance.png new file mode 100644 index 0000000..85c1d7c --- /dev/null +++ b/Doxygen/html/class_f_world_generator_instance.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6778395baba85ad52c2cd35b19ab3c80113ecbbad59776248df04f52bd6c8cae +size 1104 diff --git a/Doxygen/html/class_u_excavator_anim-members.html b/Doxygen/html/class_u_excavator_anim-members.html new file mode 100644 index 0000000..9811a47 --- /dev/null +++ b/Doxygen/html/class_u_excavator_anim-members.html @@ -0,0 +1,99 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Member List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">UExcavatorAnim Member List</div></div> +</div><!--header--> +<div class="contents"> + +<p>This is the complete list of members for <a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a>, including all inherited members.</p> +<table class="directory"> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#affb325f375a952a31fd6c7edbda878b2">GetBeamBottomRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a1cf8d06387c0c8bf61315209a2277b89">GetBeamTopRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#adf9be335c9520d1b730d0c983ad42734">GetBodyRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79">GetBottomMaxRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"><span class="mlabel">inline</span></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b">GetBottomMinRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"><span class="mlabel">inline</span></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f">GetBucketMaxRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"><span class="mlabel">inline</span></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc">GetBucketMinRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"><span class="mlabel">inline</span></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_excavator_anim.html#ad206f925afc96462b27ebd5c33273da5">GetBucketRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1">GetTopMaxRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"><span class="mlabel">inline</span></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793">GetTopMinRotation</a>()</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"><span class="mlabel">inline</span></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a09b8e1814d38428a70423d3d81922448">SetBeamBottomRotation</a>(FRotator rotator)</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a59c863bb954f22c019bc20b6d4b947df">SetBeamTopRotation</a>(FRotator rotator)</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a215798edeb92f27e3e5ad9ad5c25626a">SetBodyRotation</a>(FRotator rotator)</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_excavator_anim.html#aba254277652747c4c1dabd60308b4c17">SetBucketRotation</a>(FRotator rotator)</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_excavator_anim.html#a17a5fcd9bd609ed5ad1538906286184c">UExcavatorAnim</a>(const FObjectInitializer &ObjecInitializer)</td><td class="entry"><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></td><td class="entry"></td></tr> +</table></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_u_excavator_anim.html b/Doxygen/html/class_u_excavator_anim.html new file mode 100644 index 0000000..3095e50 --- /dev/null +++ b/Doxygen/html/class_u_excavator_anim.html @@ -0,0 +1,455 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: UExcavatorAnim Class Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#pub-methods">Public Member Functions</a> | +<a href="class_u_excavator_anim-members.html">List of all members</a> </div> + <div class="headertitle"><div class="title">UExcavatorAnim Class Reference</div></div> +</div><!--header--> +<div class="contents"> + +<p><code>#include <<a class="el" href="_excavator_anim_8h_source.html">ExcavatorAnim.h</a>></code></p> +<div class="dynheader"> +Inheritance diagram for UExcavatorAnim:</div> +<div class="dyncontent"> + <div class="center"> + <img src="class_u_excavator_anim.png" alt=""/> + </div></div> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-methods" name="pub-methods"></a> +Public Member Functions</h2></td></tr> +<tr class="memitem:a17a5fcd9bd609ed5ad1538906286184c"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a17a5fcd9bd609ed5ad1538906286184c">UExcavatorAnim</a> (const FObjectInitializer &ObjecInitializer)</td></tr> +<tr class="separator:a17a5fcd9bd609ed5ad1538906286184c"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:adf9be335c9520d1b730d0c983ad42734"><td class="memItemLeft" align="right" valign="top">FRotator </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#adf9be335c9520d1b730d0c983ad42734">GetBodyRotation</a> ()</td></tr> +<tr class="separator:adf9be335c9520d1b730d0c983ad42734"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a215798edeb92f27e3e5ad9ad5c25626a"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a215798edeb92f27e3e5ad9ad5c25626a">SetBodyRotation</a> (FRotator rotator)</td></tr> +<tr class="separator:a215798edeb92f27e3e5ad9ad5c25626a"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a1cf8d06387c0c8bf61315209a2277b89"><td class="memItemLeft" align="right" valign="top">FRotator </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a1cf8d06387c0c8bf61315209a2277b89">GetBeamTopRotation</a> ()</td></tr> +<tr class="separator:a1cf8d06387c0c8bf61315209a2277b89"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a59c863bb954f22c019bc20b6d4b947df"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a59c863bb954f22c019bc20b6d4b947df">SetBeamTopRotation</a> (FRotator rotator)</td></tr> +<tr class="separator:a59c863bb954f22c019bc20b6d4b947df"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:affb325f375a952a31fd6c7edbda878b2"><td class="memItemLeft" align="right" valign="top">FRotator </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#affb325f375a952a31fd6c7edbda878b2">GetBeamBottomRotation</a> ()</td></tr> +<tr class="separator:affb325f375a952a31fd6c7edbda878b2"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a09b8e1814d38428a70423d3d81922448"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a09b8e1814d38428a70423d3d81922448">SetBeamBottomRotation</a> (FRotator rotator)</td></tr> +<tr class="separator:a09b8e1814d38428a70423d3d81922448"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:ad206f925afc96462b27ebd5c33273da5"><td class="memItemLeft" align="right" valign="top">FRotator </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#ad206f925afc96462b27ebd5c33273da5">GetBucketRotation</a> ()</td></tr> +<tr class="separator:ad206f925afc96462b27ebd5c33273da5"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:aba254277652747c4c1dabd60308b4c17"><td class="memItemLeft" align="right" valign="top">void </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#aba254277652747c4c1dabd60308b4c17">SetBucketRotation</a> (FRotator rotator)</td></tr> +<tr class="separator:aba254277652747c4c1dabd60308b4c17"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a7e4af257d3369d934d999c5889bfe60f"><td class="memItemLeft" align="right" valign="top">float </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f">GetBucketMaxRotation</a> ()</td></tr> +<tr class="separator:a7e4af257d3369d934d999c5889bfe60f"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a6542745f31287654904430a08208f4dc"><td class="memItemLeft" align="right" valign="top">float </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc">GetBucketMinRotation</a> ()</td></tr> +<tr class="separator:a6542745f31287654904430a08208f4dc"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a56e5697f1cb92abaf41d0af039e641b1"><td class="memItemLeft" align="right" valign="top">float </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1">GetTopMaxRotation</a> ()</td></tr> +<tr class="separator:a56e5697f1cb92abaf41d0af039e641b1"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:ad7c23fdcc7208d1e925a0ea8ac09b793"><td class="memItemLeft" align="right" valign="top">float </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793">GetTopMinRotation</a> ()</td></tr> +<tr class="separator:ad7c23fdcc7208d1e925a0ea8ac09b793"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a19af34e8fb205cccbe3796330e5fff79"><td class="memItemLeft" align="right" valign="top">float </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79">GetBottomMaxRotation</a> ()</td></tr> +<tr class="separator:a19af34e8fb205cccbe3796330e5fff79"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a290c2158c3bd36b4cf9f6cf53395c44b"><td class="memItemLeft" align="right" valign="top">float </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b">GetBottomMinRotation</a> ()</td></tr> +<tr class="separator:a290c2158c3bd36b4cf9f6cf53395c44b"><td class="memSeparator" colspan="2"> </td></tr> +</table> +<h2 class="groupheader">Constructor & Destructor Documentation</h2> +<a id="a17a5fcd9bd609ed5ad1538906286184c" name="a17a5fcd9bd609ed5ad1538906286184c"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a17a5fcd9bd609ed5ad1538906286184c">◆ </a></span>UExcavatorAnim()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">UExcavatorAnim::UExcavatorAnim </td> + <td>(</td> + <td class="paramtype">const FObjectInitializer & </td> + <td class="paramname"><em>ObjecInitializer</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Constructor. </p> + +</div> +</div> +<h2 class="groupheader">Member Function Documentation</h2> +<a id="affb325f375a952a31fd6c7edbda878b2" name="affb325f375a952a31fd6c7edbda878b2"></a> +<h2 class="memtitle"><span class="permalink"><a href="#affb325f375a952a31fd6c7edbda878b2">◆ </a></span>GetBeamBottomRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">FRotator UExcavatorAnim::GetBeamBottomRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Getter for BeamBottomRotation. </p> + +</div> +</div> +<a id="a1cf8d06387c0c8bf61315209a2277b89" name="a1cf8d06387c0c8bf61315209a2277b89"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a1cf8d06387c0c8bf61315209a2277b89">◆ </a></span>GetBeamTopRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">FRotator UExcavatorAnim::GetBeamTopRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Getter for BeamTopRotation. </p> + +</div> +</div> +<a id="adf9be335c9520d1b730d0c983ad42734" name="adf9be335c9520d1b730d0c983ad42734"></a> +<h2 class="memtitle"><span class="permalink"><a href="#adf9be335c9520d1b730d0c983ad42734">◆ </a></span>GetBodyRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">FRotator UExcavatorAnim::GetBodyRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Getter for BodyRotation. </p> + +</div> +</div> +<a id="a19af34e8fb205cccbe3796330e5fff79" name="a19af34e8fb205cccbe3796330e5fff79"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a19af34e8fb205cccbe3796330e5fff79">◆ </a></span>GetBottomMaxRotation()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">float UExcavatorAnim::GetBottomMaxRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">inline</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Getter for BottomMaxRotation. </p> + +</div> +</div> +<a id="a290c2158c3bd36b4cf9f6cf53395c44b" name="a290c2158c3bd36b4cf9f6cf53395c44b"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a290c2158c3bd36b4cf9f6cf53395c44b">◆ </a></span>GetBottomMinRotation()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">float UExcavatorAnim::GetBottomMinRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">inline</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Getter for BottomMinRotation. </p> + +</div> +</div> +<a id="a7e4af257d3369d934d999c5889bfe60f" name="a7e4af257d3369d934d999c5889bfe60f"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a7e4af257d3369d934d999c5889bfe60f">◆ </a></span>GetBucketMaxRotation()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">float UExcavatorAnim::GetBucketMaxRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">inline</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Getter for BucketMaxRotation. </p> + +</div> +</div> +<a id="a6542745f31287654904430a08208f4dc" name="a6542745f31287654904430a08208f4dc"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a6542745f31287654904430a08208f4dc">◆ </a></span>GetBucketMinRotation()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">float UExcavatorAnim::GetBucketMinRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">inline</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Getter for BucketMinRotation. </p> + +</div> +</div> +<a id="ad206f925afc96462b27ebd5c33273da5" name="ad206f925afc96462b27ebd5c33273da5"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ad206f925afc96462b27ebd5c33273da5">◆ </a></span>GetBucketRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">FRotator UExcavatorAnim::GetBucketRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Getter for BucketRotation. </p> + +</div> +</div> +<a id="a56e5697f1cb92abaf41d0af039e641b1" name="a56e5697f1cb92abaf41d0af039e641b1"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a56e5697f1cb92abaf41d0af039e641b1">◆ </a></span>GetTopMaxRotation()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">float UExcavatorAnim::GetTopMaxRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">inline</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Getter for TopMaxRotation. </p> + +</div> +</div> +<a id="ad7c23fdcc7208d1e925a0ea8ac09b793" name="ad7c23fdcc7208d1e925a0ea8ac09b793"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ad7c23fdcc7208d1e925a0ea8ac09b793">◆ </a></span>GetTopMinRotation()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">float UExcavatorAnim::GetTopMinRotation </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">inline</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> +<p >Getter for TopMinRotation. </p> + +</div> +</div> +<a id="a09b8e1814d38428a70423d3d81922448" name="a09b8e1814d38428a70423d3d81922448"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a09b8e1814d38428a70423d3d81922448">◆ </a></span>SetBeamBottomRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void UExcavatorAnim::SetBeamBottomRotation </td> + <td>(</td> + <td class="paramtype">FRotator </td> + <td class="paramname"><em>rotator</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Setter for BeamBottomRotation @Param rotator sets new rotation to BeamBottomRotator </p> + +</div> +</div> +<a id="a59c863bb954f22c019bc20b6d4b947df" name="a59c863bb954f22c019bc20b6d4b947df"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a59c863bb954f22c019bc20b6d4b947df">◆ </a></span>SetBeamTopRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void UExcavatorAnim::SetBeamTopRotation </td> + <td>(</td> + <td class="paramtype">FRotator </td> + <td class="paramname"><em>rotator</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Setter for BeamTopRotation @Param rotator sets new rotation to BeamTopRotation </p> + +</div> +</div> +<a id="a215798edeb92f27e3e5ad9ad5c25626a" name="a215798edeb92f27e3e5ad9ad5c25626a"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a215798edeb92f27e3e5ad9ad5c25626a">◆ </a></span>SetBodyRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void UExcavatorAnim::SetBodyRotation </td> + <td>(</td> + <td class="paramtype">FRotator </td> + <td class="paramname"><em>rotator</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Setter for BodyRotation @Param rotator sets new rotation to BodyRotation </p> + +</div> +</div> +<a id="aba254277652747c4c1dabd60308b4c17" name="aba254277652747c4c1dabd60308b4c17"></a> +<h2 class="memtitle"><span class="permalink"><a href="#aba254277652747c4c1dabd60308b4c17">◆ </a></span>SetBucketRotation()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">void UExcavatorAnim::SetBucketRotation </td> + <td>(</td> + <td class="paramtype">FRotator </td> + <td class="paramname"><em>rotator</em></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> +<p >Setter for BucketRotation @Param rotator sets new rotation to BucketRotation </p> + +</div> +</div> +<hr/>The documentation for this class was generated from the following files:<ul> +<li><a class="el" href="_excavator_anim_8h_source.html">ExcavatorAnim.h</a></li> +<li><a class="el" href="_excavator_anim_8cpp.html">ExcavatorAnim.cpp</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_u_excavator_anim.png b/Doxygen/html/class_u_excavator_anim.png new file mode 100644 index 0000000..6b72ebe --- /dev/null +++ b/Doxygen/html/class_u_excavator_anim.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:002e0e791b9bcfc584b60ada0b3246877ac45a8a8c8dfc2e73455877e84e8e8f +size 502 diff --git a/Doxygen/html/class_u_world_generator-members.html b/Doxygen/html/class_u_world_generator-members.html new file mode 100644 index 0000000..85e0c5b --- /dev/null +++ b/Doxygen/html/class_u_world_generator-members.html @@ -0,0 +1,89 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Member List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="headertitle"><div class="title">UWorldGenerator Member List</div></div> +</div><!--header--> +<div class="contents"> + +<p>This is the complete list of members for <a class="el" href="class_u_world_generator.html">UWorldGenerator</a>, including all inherited members.</p> +<table class="directory"> + <tr class="even"><td class="entry"><a class="el" href="class_u_world_generator.html#a92908af10f0cdfee492c5e21ba1000d1">GetInstance</a>() override</td><td class="entry"><a class="el" href="class_u_world_generator.html">UWorldGenerator</a></td><td class="entry"><span class="mlabel">virtual</span></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_world_generator.html#a97672fed88363bef5359e4a9e1bc22d2">NoiseHeight</a></td><td class="entry"><a class="el" href="class_u_world_generator.html">UWorldGenerator</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_world_generator.html#ae3e0313a980ab00d26cb7691a9d178e7">Seed</a></td><td class="entry"><a class="el" href="class_u_world_generator.html">UWorldGenerator</a></td><td class="entry"></td></tr> + <tr class="odd"><td class="entry"><a class="el" href="class_u_world_generator.html#a4ea59a679fc8eff20b3b143461020977">UWorldGenerator</a>()</td><td class="entry"><a class="el" href="class_u_world_generator.html">UWorldGenerator</a></td><td class="entry"></td></tr> + <tr class="even"><td class="entry"><a class="el" href="class_u_world_generator.html#ad2e31d185549690f85ddec11f48a709e">~UWorldGenerator</a>()</td><td class="entry"><a class="el" href="class_u_world_generator.html">UWorldGenerator</a></td><td class="entry"></td></tr> +</table></div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_u_world_generator.html b/Doxygen/html/class_u_world_generator.html new file mode 100644 index 0000000..b4e8df2 --- /dev/null +++ b/Doxygen/html/class_u_world_generator.html @@ -0,0 +1,204 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: UWorldGenerator Class Reference</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +</div><!-- top --> +<div class="header"> + <div class="summary"> +<a href="#pub-methods">Public Member Functions</a> | +<a href="#pub-attribs">Public Attributes</a> | +<a href="class_u_world_generator-members.html">List of all members</a> </div> + <div class="headertitle"><div class="title">UWorldGenerator Class Reference</div></div> +</div><!--header--> +<div class="contents"> + +<p><code>#include <<a class="el" href="_world_generator_8h_source.html">WorldGenerator.h</a>></code></p> +<div class="dynheader"> +Inheritance diagram for UWorldGenerator:</div> +<div class="dyncontent"> + <div class="center"> + <img src="class_u_world_generator.png" alt=""/> + </div></div> +<table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-methods" name="pub-methods"></a> +Public Member Functions</h2></td></tr> +<tr class="memitem:a4ea59a679fc8eff20b3b143461020977"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_world_generator.html#a4ea59a679fc8eff20b3b143461020977">UWorldGenerator</a> ()</td></tr> +<tr class="separator:a4ea59a679fc8eff20b3b143461020977"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:ad2e31d185549690f85ddec11f48a709e"><td class="memItemLeft" align="right" valign="top"> </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_world_generator.html#ad2e31d185549690f85ddec11f48a709e">~UWorldGenerator</a> ()</td></tr> +<tr class="separator:ad2e31d185549690f85ddec11f48a709e"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:a92908af10f0cdfee492c5e21ba1000d1"><td class="memItemLeft" align="right" valign="top">virtual TVoxelSharedRef< FVoxelGeneratorInstance > </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_world_generator.html#a92908af10f0cdfee492c5e21ba1000d1">GetInstance</a> () override</td></tr> +<tr class="separator:a92908af10f0cdfee492c5e21ba1000d1"><td class="memSeparator" colspan="2"> </td></tr> +</table><table class="memberdecls"> +<tr class="heading"><td colspan="2"><h2 class="groupheader"><a id="pub-attribs" name="pub-attribs"></a> +Public Attributes</h2></td></tr> +<tr class="memitem:a97672fed88363bef5359e4a9e1bc22d2"><td class="memItemLeft" align="right" valign="top">float </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_world_generator.html#a97672fed88363bef5359e4a9e1bc22d2">NoiseHeight</a> = 10.0f</td></tr> +<tr class="separator:a97672fed88363bef5359e4a9e1bc22d2"><td class="memSeparator" colspan="2"> </td></tr> +<tr class="memitem:ae3e0313a980ab00d26cb7691a9d178e7"><td class="memItemLeft" align="right" valign="top">int32 </td><td class="memItemRight" valign="bottom"><a class="el" href="class_u_world_generator.html#ae3e0313a980ab00d26cb7691a9d178e7">Seed</a> = 1337</td></tr> +<tr class="separator:ae3e0313a980ab00d26cb7691a9d178e7"><td class="memSeparator" colspan="2"> </td></tr> +</table> +<h2 class="groupheader">Constructor & Destructor Documentation</h2> +<a id="a4ea59a679fc8eff20b3b143461020977" name="a4ea59a679fc8eff20b3b143461020977"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a4ea59a679fc8eff20b3b143461020977">◆ </a></span>UWorldGenerator()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">UWorldGenerator::UWorldGenerator </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="ad2e31d185549690f85ddec11f48a709e" name="ad2e31d185549690f85ddec11f48a709e"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ad2e31d185549690f85ddec11f48a709e">◆ </a></span>~UWorldGenerator()</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">UWorldGenerator::~UWorldGenerator </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<h2 class="groupheader">Member Function Documentation</h2> +<a id="a92908af10f0cdfee492c5e21ba1000d1" name="a92908af10f0cdfee492c5e21ba1000d1"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a92908af10f0cdfee492c5e21ba1000d1">◆ </a></span>GetInstance()</h2> + +<div class="memitem"> +<div class="memproto"> +<table class="mlabels"> + <tr> + <td class="mlabels-left"> + <table class="memname"> + <tr> + <td class="memname">TVoxelSharedRef< FVoxelGeneratorInstance > UWorldGenerator::GetInstance </td> + <td>(</td> + <td class="paramname"></td><td>)</td> + <td></td> + </tr> + </table> + </td> + <td class="mlabels-right"> +<span class="mlabels"><span class="mlabel">override</span><span class="mlabel">virtual</span></span> </td> + </tr> +</table> +</div><div class="memdoc"> + +</div> +</div> +<h2 class="groupheader">Member Data Documentation</h2> +<a id="a97672fed88363bef5359e4a9e1bc22d2" name="a97672fed88363bef5359e4a9e1bc22d2"></a> +<h2 class="memtitle"><span class="permalink"><a href="#a97672fed88363bef5359e4a9e1bc22d2">◆ </a></span>NoiseHeight</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">float UWorldGenerator::NoiseHeight = 10.0f</td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<a id="ae3e0313a980ab00d26cb7691a9d178e7" name="ae3e0313a980ab00d26cb7691a9d178e7"></a> +<h2 class="memtitle"><span class="permalink"><a href="#ae3e0313a980ab00d26cb7691a9d178e7">◆ </a></span>Seed</h2> + +<div class="memitem"> +<div class="memproto"> + <table class="memname"> + <tr> + <td class="memname">int32 UWorldGenerator::Seed = 1337</td> + </tr> + </table> +</div><div class="memdoc"> + +</div> +</div> +<hr/>The documentation for this class was generated from the following files:<ul> +<li><a class="el" href="_world_generator_8h_source.html">WorldGenerator.h</a></li> +<li><a class="el" href="_world_generator_8cpp.html">WorldGenerator.cpp</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/class_u_world_generator.png b/Doxygen/html/class_u_world_generator.png new file mode 100644 index 0000000..17534eb --- /dev/null +++ b/Doxygen/html/class_u_world_generator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0c2b40fc3fddfd2a2430bc9e5883b1a76d565fb2904bafa1b8425aa22e607d2 +size 526 diff --git a/Doxygen/html/classes.html b/Doxygen/html/classes.html new file mode 100644 index 0000000..c507b6e --- /dev/null +++ b/Doxygen/html/classes.html @@ -0,0 +1,96 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Class Index</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">Class Index</div></div> +</div><!--header--> +<div class="contents"> +<div class="qindex"><a class="qindex" href="#letter_A">A</a> | <a class="qindex" href="#letter_E">E</a> | <a class="qindex" href="#letter_F">F</a> | <a class="qindex" href="#letter_U">U</a></div> +<div class="classindex"> +<dl class="classindex even"> +<dt class="alphachar"><a id="letter_A" name="letter_A">A</a></dt> +<dd><a class="el" href="class_a_custom_procedural_mesh_component.html">ACustomProceduralMeshComponent</a></dd><dd><a class="el" href="class_a_excavator_character.html">AExcavatorCharacter</a></dd><dd><a class="el" href="class_a_excavator_simulator_game_mode_base.html">AExcavatorSimulatorGameModeBase</a></dd></dl> +<dl class="classindex odd"> +<dt class="alphachar"><a id="letter_E" name="letter_E">E</a></dt> +<dd><a class="el" href="class_excavator_simulator.html">ExcavatorSimulator</a></dd></dl> +<dl class="classindex even"> +<dt class="alphachar"><a id="letter_F" name="letter_F">F</a></dt> +<dd><a class="el" href="class_f_world_generator_instance.html">FWorldGeneratorInstance</a></dd></dl> +<dl class="classindex odd"> +<dt class="alphachar"><a id="letter_U" name="letter_U">U</a></dt> +<dd><a class="el" href="class_u_excavator_anim.html">UExcavatorAnim</a></dd><dd><a class="el" href="class_u_world_generator.html">UWorldGenerator</a></dd></dl> +</div> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/closed.png b/Doxygen/html/closed.png new file mode 100644 index 0000000..7b3198f --- /dev/null +++ b/Doxygen/html/closed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c98c02adc57337f58c40aae15bbac05a3ccb364e5adb1d610a16452e92f17830 +size 132 diff --git a/Doxygen/html/doc.png b/Doxygen/html/doc.png new file mode 100644 index 0000000..3b60d92 --- /dev/null +++ b/Doxygen/html/doc.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cd4cb41607a30d7820cc20ea76b4a3b8f57d3d2b7d102b58c8e13ad95e83aa1 +size 746 diff --git a/Doxygen/html/docd.png b/Doxygen/html/docd.png new file mode 100644 index 0000000..de2b286 --- /dev/null +++ b/Doxygen/html/docd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a7711a8821f56dec6c2ffee4245275b7c43d657447955da2ed5b8dcd49217da +size 756 diff --git a/Doxygen/html/doxygen.css b/Doxygen/html/doxygen.css new file mode 100644 index 0000000..89dee6c --- /dev/null +++ b/Doxygen/html/doxygen.css @@ -0,0 +1,1973 @@ +/* The standard CSS for doxygen 1.9.5*/ + +html { +/* page base colors */ +--page-background-color: white; +--page-foreground-color: black; +--page-link-color: #3D578C; +--page-visited-link-color: #4665A2; + +/* index */ +--index-odd-item-bg-color: #F8F9FC; +--index-even-item-bg-color: white; +--index-header-color: black; +--index-separator-color: #A0A0A0; + +/* header */ +--header-background-color: #F9FAFC; +--header-separator-color: #C4CFE5; +--header-gradient-image: url('nav_h.png'); +--group-header-separator-color: #879ECB; +--group-header-color: #354C7B; +--inherit-header-color: gray; + +--footer-foreground-color: #2A3D61; +--footer-logo-width: 104px; +--citation-label-color: #334975; +--glow-color: cyan; + +--title-background-color: white; +--title-separator-color: #5373B4; +--directory-separator-color: #9CAFD4; +--separator-color: #4A6AAA; + +--blockquote-background-color: #F7F8FB; +--blockquote-border-color: #9CAFD4; + +--scrollbar-thumb-color: #9CAFD4; +--scrollbar-background-color: #F9FAFC; + +--icon-background-color: #728DC1; +--icon-foreground-color: white; +--icon-doc-image: url('doc.png'); + +/* brief member declaration list */ +--memdecl-background-color: #F9FAFC; +--memdecl-separator-color: #DEE4F0; +--memdecl-foreground-color: #555; +--memdecl-template-color: #4665A2; + +/* detailed member list */ +--memdef-border-color: #A8B8D9; +--memdef-title-background-color: #E2E8F2; +--memdef-title-gradient-image: url('nav_f.png'); +--memdef-proto-background-color: #DFE5F1; +--memdef-proto-text-color: #253555; +--memdef-proto-text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--memdef-doc-background-color: white; +--memdef-param-name-color: #602020; +--memdef-template-color: #4665A2; + +/* tables */ +--table-cell-border-color: #2D4068; +--table-header-background-color: #374F7F; +--table-header-foreground-color: #FFFFFF; + +/* labels */ +--label-background-color: #728DC1; +--label-left-top-border-color: #5373B4; +--label-right-bottom-border-color: #C4CFE5; +--label-foreground-color: white; + +/** navigation bar/tree/menu */ +--nav-background-color: #F9FAFC; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_b.png'); +--nav-gradient-hover-image: url('tab_h.png'); +--nav-gradient-active-image: url('tab_a.png'); +--nav-gradient-active-image-parent: url("../tab_a.png"); +--nav-separator-image: url('tab_s.png'); +--nav-breadcrumb-image: url('bc_s.png'); +--nav-breadcrumb-border-color: #C2CDE4; +--nav-splitbar-image: url('splitbar.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #283A5D; +--nav-text-hover-color: white; +--nav-text-active-color: white; +--nav-text-normal-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #364D7C; +--nav-menu-background-color: white; +--nav-menu-foreground-color: #555555; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.5); +--nav-arrow-color: #9CAFD4; +--nav-arrow-selected-color: #9CAFD4; + +/* table of contents */ +--toc-background-color: #F4F6FA; +--toc-border-color: #D8DFEE; +--toc-header-color: #4665A2; + +/** search field */ +--search-background-color: white; +--search-foreground-color: #909090; +--search-magnification-image: url('mag.svg'); +--search-magnification-select-image: url('mag_sel.svg'); +--search-active-color: black; +--search-filter-background-color: #F9FAFC; +--search-filter-foreground-color: black; +--search-filter-border-color: #90A5CE; +--search-filter-highlight-text-color: white; +--search-filter-highlight-bg-color: #3D578C; +--search-results-foreground-color: #425E97; +--search-results-background-color: #EEF1F7; +--search-results-border-color: black; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #555; + +/** code fragments */ +--code-keyword-color: #008000; +--code-type-keyword-color: #604020; +--code-flow-keyword-color: #E08000; +--code-comment-color: #800000; +--code-preprocessor-color: #806020; +--code-string-literal-color: #002080; +--code-char-literal-color: #008080; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #000000; +--code-vhdl-keyword-color: #700070; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #4665A2; +--code-external-link-color: #4665A2; +--fragment-foreground-color: black; +--fragment-background-color: #FBFCFD; +--fragment-border-color: #C4CFE5; +--fragment-lineno-border-color: #00FF00; +--fragment-lineno-background-color: #E8E8E8; +--fragment-lineno-foreground-color: black; +--fragment-lineno-link-fg-color: #4665A2; +--fragment-lineno-link-bg-color: #D8D8D8; +--fragment-lineno-link-hover-fg-color: #4665A2; +--fragment-lineno-link-hover-bg-color: #C8C8C8; +--tooltip-foreground-color: black; +--tooltip-background-color: white; +--tooltip-border-color: gray; +--tooltip-doc-color: grey; +--tooltip-declaration-color: #006318; +--tooltip-link-color: #4665A2; +--tooltip-shadow: 1px 1px 7px gray; + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) { + color-scheme: dark; + +/* page base colors */ +--page-background-color: black; +--page-foreground-color: #C9D1D9; +--page-link-color: #90A5CE; +--page-visited-link-color: #A3B4D7; + +/* index */ +--index-odd-item-bg-color: #0B101A; +--index-even-item-bg-color: black; +--index-header-color: #C4CFE5; +--index-separator-color: #334975; + +/* header */ +--header-background-color: #070B11; +--header-separator-color: #141C2E; +--header-gradient-image: url('nav_hd.png'); +--group-header-separator-color: #283A5D; +--group-header-color: #90A5CE; +--inherit-header-color: #A0A0A0; + +--footer-foreground-color: #5B7AB7; +--footer-logo-width: 60px; +--citation-label-color: #90A5CE; +--glow-color: cyan; + +--title-background-color: #090D16; +--title-separator-color: #354C79; +--directory-separator-color: #283A5D; +--separator-color: #283A5D; + +--blockquote-background-color: #101826; +--blockquote-border-color: #283A5D; + +--scrollbar-thumb-color: #283A5D; +--scrollbar-background-color: #070B11; + +--icon-background-color: #334975; +--icon-foreground-color: #C4CFE5; +--icon-doc-image: url('docd.png'); + +/* brief member declaration list */ +--memdecl-background-color: #0B101A; +--memdecl-separator-color: #2C3F65; +--memdecl-foreground-color: #BBB; +--memdecl-template-color: #7C95C6; + +/* detailed member list */ +--memdef-border-color: #233250; +--memdef-title-background-color: #1B2840; +--memdef-title-gradient-image: url('nav_fd.png'); +--memdef-proto-background-color: #19243A; +--memdef-proto-text-color: #9DB0D4; +--memdef-proto-text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.9); +--memdef-doc-background-color: black; +--memdef-param-name-color: #D28757; +--memdef-template-color: #7C95C6; + +/* tables */ +--table-cell-border-color: #283A5D; +--table-header-background-color: #283A5D; +--table-header-foreground-color: #C4CFE5; + +/* labels */ +--label-background-color: #354C7B; +--label-left-top-border-color: #4665A2; +--label-right-bottom-border-color: #283A5D; +--label-foreground-color: #CCCCCC; + +/** navigation bar/tree/menu */ +--nav-background-color: #101826; +--nav-foreground-color: #364D7C; +--nav-gradient-image: url('tab_bd.png'); +--nav-gradient-hover-image: url('tab_hd.png'); +--nav-gradient-active-image: url('tab_ad.png'); +--nav-gradient-active-image-parent: url("../tab_ad.png"); +--nav-separator-image: url('tab_sd.png'); +--nav-breadcrumb-image: url('bc_sd.png'); +--nav-breadcrumb-border-color: #2A3D61; +--nav-splitbar-image: url('splitbard.png'); +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #B6C4DF; +--nav-text-hover-color: #DCE2EF; +--nav-text-active-color: #DCE2EF; +--nav-text-normal-shadow: 0px 1px 1px black; +--nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); +--nav-menu-button-color: #B6C4DF; +--nav-menu-background-color: #05070C; +--nav-menu-foreground-color: #BBBBBB; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.2); +--nav-arrow-color: #334975; +--nav-arrow-selected-color: #90A5CE; + +/* table of contents */ +--toc-background-color: #151E30; +--toc-border-color: #202E4A; +--toc-header-color: #A3B4D7; + +/** search field */ +--search-background-color: black; +--search-foreground-color: #C5C5C5; +--search-magnification-image: url('mag_d.svg'); +--search-magnification-select-image: url('mag_seld.svg'); +--search-active-color: #C5C5C5; +--search-filter-background-color: #101826; +--search-filter-foreground-color: #90A5CE; +--search-filter-border-color: #7C95C6; +--search-filter-highlight-text-color: #BCC9E2; +--search-filter-highlight-bg-color: #283A5D; +--search-results-background-color: #101826; +--search-results-foreground-color: #90A5CE; +--search-results-border-color: #7C95C6; +--search-box-shadow: inset 0.5px 0.5px 3px 0px #2F436C; + +/** code fragments */ +--code-keyword-color: #CC99CD; +--code-type-keyword-color: #AB99CD; +--code-flow-keyword-color: #E08000; +--code-comment-color: #717790; +--code-preprocessor-color: #65CABE; +--code-string-literal-color: #7EC699; +--code-char-literal-color: #00E0F0; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #000000; +--code-vhdl-keyword-color: #700070; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #79C0FF; +--code-external-link-color: #79C0FF; +--fragment-foreground-color: #C9D1D9; +--fragment-background-color: black; +--fragment-border-color: #30363D; +--fragment-lineno-border-color: #30363D; +--fragment-lineno-background-color: black; +--fragment-lineno-foreground-color: #6E7681; +--fragment-lineno-link-fg-color: #6E7681; +--fragment-lineno-link-bg-color: #303030; +--fragment-lineno-link-hover-fg-color: #8E96A1; +--fragment-lineno-link-hover-bg-color: #505050; +--tooltip-foreground-color: #C9D1D9; +--tooltip-background-color: #202020; +--tooltip-border-color: #C9D1D9; +--tooltip-doc-color: #D9E1E9; +--tooltip-declaration-color: #20C348; +--tooltip-link-color: #79C0FF; +--tooltip-shadow: none; + +/** font-family */ +--font-family-normal: Roboto,sans-serif; +--font-family-monospace: monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: Tahoma,Arial,sans-serif; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +}} +body { + background-color: var(--page-background-color); + color: var(--page-foreground-color); + scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-background-color); +} + +::-webkit-scrollbar { + background-color: var(--scrollbar-background-color); + height: 10px; + width: 10px; +} +::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-thumb-color); + border-radius: 8px; +} +::-webkit-scrollbar-corner { + background-color: var(--scrollbar-background-color); +} + +body, table, div, p, dl { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 22px; +} + +/* @group Heading Levels */ + +.title { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 28px; + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +h1.groupheader { + font-size: 150%; +} + +h2.groupheader { + border-bottom: 1px solid var(--group-header-separator-color); + color: var(--group-header-color); + font-size: 150%; + font-weight: normal; + margin-top: 1.75em; + padding-top: 8px; + padding-bottom: 4px; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px var(--glow-color); +} + +dt { + font-weight: bold; +} + +p.startli, p.startdd { + margin-top: 2px; +} + +th p.starttd, th p.intertd, th p.endtd { + font-size: 100%; + font-weight: 700; +} + +p.starttd { + margin-top: 0px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +p.interli { +} + +p.interdd { +} + +p.intertd { +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.navtab { + padding-right: 15px; + text-align: right; + line-height: 110%; +} + +div.navtab table { + border-spacing: 0; +} + +td.navtab { + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL { + background-image: var(--nav-gradient-active-image); + background-repeat:repeat-x; + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL a, td.navtabHL a:visited { + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +a.navtab { + font-weight: bold; +} + +div.qindex{ + text-align: center; + width: 100%; + line-height: 140%; + font-size: 130%; + color: var(--index-separator-color); +} + +dt.alphachar{ + font-size: 180%; + font-weight: bold; +} + +.alphachar a{ + color: var(--index-header-color); +} + +.alphachar a:hover, .alphachar a:visited{ + text-decoration: none; +} + +.classindex dl { + padding: 25px; + column-count:1 +} + +.classindex dd { + display:inline-block; + margin-left: 50px; + width: 90%; + line-height: 1.15em; +} + +.classindex dl.even { + background-color: var(--index-even-item-bg-color); +} + +.classindex dl.odd { + background-color: var(--index-odd-item-bg-color); +} + +@media(min-width: 1120px) { + .classindex dl { + column-count:2 + } +} + +@media(min-width: 1320px) { + .classindex dl { + column-count:3 + } +} + + +/* @group Link Styling */ + +a { + color: var(--page-link-color); + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: var(--page-visited-link-color); +} + +a:hover { + text-decoration: underline; +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.code, a.code:visited, a.line, a.line:visited { + color: var(--code-link-color); +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: var(--code-external-link-color); +} + +a.code.hl_class { /* style for links to class names in code snippets */ } +a.code.hl_struct { /* style for links to struct names in code snippets */ } +a.code.hl_union { /* style for links to union names in code snippets */ } +a.code.hl_interface { /* style for links to interface names in code snippets */ } +a.code.hl_protocol { /* style for links to protocol names in code snippets */ } +a.code.hl_category { /* style for links to category names in code snippets */ } +a.code.hl_exception { /* style for links to exception names in code snippets */ } +a.code.hl_service { /* style for links to service names in code snippets */ } +a.code.hl_singleton { /* style for links to singleton names in code snippets */ } +a.code.hl_concept { /* style for links to concept names in code snippets */ } +a.code.hl_namespace { /* style for links to namespace names in code snippets */ } +a.code.hl_package { /* style for links to package names in code snippets */ } +a.code.hl_define { /* style for links to macro names in code snippets */ } +a.code.hl_function { /* style for links to function names in code snippets */ } +a.code.hl_variable { /* style for links to variable names in code snippets */ } +a.code.hl_typedef { /* style for links to typedef names in code snippets */ } +a.code.hl_enumvalue { /* style for links to enum value names in code snippets */ } +a.code.hl_enumeration { /* style for links to enumeration names in code snippets */ } +a.code.hl_signal { /* style for links to Qt signal names in code snippets */ } +a.code.hl_slot { /* style for links to Qt slot names in code snippets */ } +a.code.hl_friend { /* style for links to friend names in code snippets */ } +a.code.hl_dcop { /* style for links to KDE3 DCOP names in code snippets */ } +a.code.hl_property { /* style for links to property names in code snippets */ } +a.code.hl_event { /* style for links to event names in code snippets */ } +a.code.hl_sequence { /* style for links to sequence names in code snippets */ } +a.code.hl_dictionary { /* style for links to dictionary names in code snippets */ } + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +ul { + overflow: visible; +} + +#side-nav ul { + overflow: visible; /* reset ul rule for scroll bar in GENERATE_TREEVIEW window */ +} + +#main-nav ul { + overflow: visible; /* reset ul rule for the navigation bar drop down lists */ +} + +.fragment { + text-align: left; + direction: ltr; + overflow-x: auto; /*Fixed: fragment lines overlap floating elements*/ + overflow-y: hidden; +} + +pre.fragment { + border: 1px solid var(--fragment-border-color); + background-color: var(--fragment-background-color); + color: var(--fragment-foreground-color); + padding: 4px 6px; + margin: 4px 8px 4px 2px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; + font-family: var(--font-family-monospace); + font-size: 105%; +} + +div.fragment { + padding: 0 0 1px 0; /*Fixed: last line underline overlap border*/ + margin: 4px 8px 4px 2px; + color: var(--fragment-foreground-color); + background-color: var(--fragment-background-color); + border: 1px solid var(--fragment-border-color); +} + +div.line { + font-family: var(--font-family-monospace); + font-size: 13px; + min-height: 13px; + line-height: 1.0; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line:after { + content:"\000A"; + white-space: pre; +} + +div.line.glow { + background-color: var(--glow-color); + box-shadow: 0 0 10px var(--glow-color); +} + + +span.lineno { + padding-right: 4px; + margin-right: 9px; + text-align: right; + border-right: 2px solid var(--fragment-lineno-border-color); + color: var(--fragment-lineno-foreground-color); + background-color: var(--fragment-lineno-background-color); + white-space: pre; +} +span.lineno a, span.lineno a:visited { + color: var(--fragment-lineno-link-fg-color); + background-color: var(--fragment-lineno-link-bg-color); +} + +span.lineno a:hover { + color: var(--fragment-lineno-link-hover-fg-color); + background-color: var(--fragment-lineno-link-hover-bg-color); +} + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.classindex ul { + list-style: none; + padding-left: 0; +} + +div.classindex span.ai { + display: inline-block; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + color: var(--page-foreground-color); + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 12px; + margin-right: 8px; +} + +p.formulaDsp { + text-align: center; +} + +img.dark-mode-visible { + display: none; +} +img.light-mode-visible { + display: none; +} + +img.formulaDsp { + +} + +img.formulaInl, img.inline { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; + width: var(--footer-logo-width); +} + +.compoundTemplParams { + color: var(--memdecl-template-color); + font-size: 80%; + line-height: 120%; +} + +/* @group Code Colorization */ + +span.keyword { + color: var(--code-keyword-color); +} + +span.keywordtype { + color: var(--code-type-keyword-color); +} + +span.keywordflow { + color: var(--code-flow-keyword-color); +} + +span.comment { + color: var(--code-comment-color); +} + +span.preprocessor { + color: var(--code-preprocessor-color); +} + +span.stringliteral { + color: var(--code-string-literal-color); +} + +span.charliteral { + color: var(--code-char-literal-color); +} + +span.vhdldigit { + color: var(--code-vhdl-digit-color); +} + +span.vhdlchar { + color: var(--code-vhdl-char-color); +} + +span.vhdlkeyword { + color: var(--code-vhdl-keyword-color); +} + +span.vhdllogic { + color: var(--code-vhdl-logic-color); +} + +blockquote { + background-color: var(--blockquote-background-color); + border-left: 2px solid var(--blockquote-border-color); + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid var(--table-cell-border-color); +} + +th.dirtab { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid var(--separator-color); +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, .fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--glow-color); + box-shadow: 0 0 15px var(--glow-color); +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: var(--memdecl-background-color); + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: var(--memdecl-foreground-color); +} + +.memSeparator { + border-bottom: 1px solid var(--memdecl-separator-color); + line-height: 1px; + margin: 0px; + padding: 0px; +} + +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight, .memTemplItemRight { + width: 100%; +} + +.memTemplParams { + color: var(--memdecl-template-color); + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 8px; + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + border-top-right-radius: 4px; + border-top-left-radius: 4px; + margin-bottom: -1px; + background-image: var(--memdef-title-gradient-image); + background-repeat: repeat-x; + background-color: var(--memdef-title-background-color); + line-height: 1.25; + font-weight: 300; + float:left; +} + +.permalink +{ + font-size: 65%; + display: inline-block; + vertical-align: middle; +} + +.memtemplate { + font-size: 80%; + color: var(--memdef-template-color); + font-weight: normal; + margin-left: 9px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; +} + +.memitem.glow { + box-shadow: 0 0 15px var(--glow-color); +} + +.memname { + font-weight: 400; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 0px 6px 0px; + color: var(--memdef-proto-text-color); + font-weight: bold; + text-shadow: var(--memdef-proto-text-shadow); + background-color: var(--memdef-proto-background-color); + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 4px; +} + +.overload { + font-family: var(--font-family-monospace); + font-size: 65%; +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 10px 2px 10px; + border-top-width: 0; + background-image:url('nav_g.png'); + background-repeat:repeat-x; + background-color: var(--memdef-doc-background-color); + /* opera specific markup */ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: var(--memdef-param-name-color); + white-space: nowrap; +} +.paramname em { + font-style: normal; +} +.paramname code { + line-height: 14px; +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname, .tparams .paramname, .exception .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype, .tparams .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir, .tparams .paramdir { + font-family: var(--font-family-monospace); + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: var(--label-background-color); + border-top:1px solid var(--label-left-top-border-color); + border-left:1px solid var(--label-left-top-border-color); + border-right:1px solid var(--label-right-bottom-border-color); + border-bottom:1px solid var(--label-right-bottom-border-color); + text-shadow: none; + color: var(--label-foreground-color); + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view inside a (index) page */ + +div.directory { + margin: 10px 0px; + border-top: 1px solid var(--directory-separator-color); + border-bottom: 1px solid var(--directory-separator-color); + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline:none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.odd { + padding-left: 6px; + background-color: var(--index-odd-item-bg-color); +} + +.directory tr.even { + padding-left: 6px; + background-color: var(--index-even-item-bg-color); +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: var(--page-link-color); +} + +.arrow { + color: var(--nav-arrow-color); + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 22px; +} + +.icon { + font-family: var(--font-family-icon); + line-height: normal; + font-weight: bold; + font-size: 12px; + height: 14px; + width: 16px; + display: inline-block; + background-color: var(--icon-background-color); + color: var(--icon-foreground-color); + text-align: center; + border-radius: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.icona { + width: 24px; + height: 22px; + display: inline-block; +} + +.iconfopen { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:url('folderopen.png'); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.iconfclosed { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:url('folderclosed.png'); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +.icondoc { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image:var(--icon-doc-image); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align:top; + display: inline-block; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: var(--footer-foreground-color); +} + +table.doxtable caption { + caption-side: top; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid var(--memdef-border-color); + border-spacing: 0px; + border-radius: 4px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid var(--memdef-border-color); + border-bottom: 1px solid var(--memdef-border-color); + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--memdef-border-color); +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image: var(--memdef-title-gradient-image); + background-repeat:repeat-x; + background-color: var(--memdef-title-background-color); + font-size: 90%; + color: var(--memdef-proto-text-color); + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + font-weight: 400; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid var(--memdef-border-color); +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: var(--nav-gradient-image); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + background-image: var(--nav-gradient-image); + background-repeat:repeat-x; + background-position: 0 -5px; + height:30px; + line-height:30px; + color:var(--nav-text-normal-color); + border:solid 1px var(--nav-breadcrumb-border-color); + overflow:hidden; + margin:0px; + padding:0px; +} + +.navpath li +{ + list-style-type:none; + float:left; + padding-left:10px; + padding-right:15px; + background-image:var(--nav-breadcrumb-image); + background-repeat:no-repeat; + background-position:right; + color: var(--nav-foreground-color); +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; + color: var(--nav-text-normal-color); + font-family: var(--font-family-nav); + text-shadow: var(--nav-text-normal-shadow); + text-decoration: none; +} + +.navpath li.navelem a:hover +{ + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + color: var(--footer-foreground-color); + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +table.classindex +{ + margin: 10px; + white-space: nowrap; + margin-left: 3%; + margin-right: 3%; + width: 94%; + border: 0; + border-spacing: 0; + padding: 0; +} + +div.ingroups +{ + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + background-image: var(--header-gradient-image); + background-repeat:repeat-x; + background-color: var(--header-background-color); + margin: 0px; + border-bottom: 1px solid var(--header-separator-color); +} + +div.headertitle +{ + padding: 5px 5px 5px 10px; +} + +.PageDocRTL-title div.headertitle { + text-align: right; + direction: rtl; +} + +dl { + padding: 0 0 0 0; +} + +/* dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug, dl.examples */ +dl.section { + margin-left: 0px; + padding-left: 0px; +} + +dl.note { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #D0C000; +} + +dl.warning, dl.attention { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00D000; +} + +dl.deprecated { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #505050; +} + +dl.todo { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #00C0E0; +} + +dl.test { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #3030E0; +} + +dl.bug { + margin-left: -7px; + padding-left: 3px; + border-left: 4px solid; + border-color: #C08050; +} + +dl.section dd { + margin-bottom: 6px; +} + + +#projectrow +{ + height: 56px; +} + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectalign +{ + vertical-align: middle; + padding-left: 0.5em; +} + +#projectname +{ + font-size: 200%; + font-family: var(--font-family-title); + margin: 0px; + padding: 2px 0px; +} + +#projectbrief +{ + font-size: 90%; + font-family: var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#projectnumber +{ + font-size: 50%; + font-family: 50% var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 1px solid var(--title-separator-color); + background-color: var(--title-background-color); +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.plantumlgraph +{ + text-align: center; +} + +.diagraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:var(--citation-label-color); + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; + text-align:right; + width:52px; +} + +dl.citelist dd { + margin:2px 0 2px 72px; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: var(--toc-background-color); + border: 1px solid var(--toc-border-color); + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 8px 10px 10px; + width: 200px; +} + +div.toc li { + background: url("bdwn.png") no-repeat scroll 0 5px transparent; + font: 10px/1.2 var(--font-family-toc); + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 var(--font-family-toc); + color: var(--toc-header-color); + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.level2 { + margin-left: 15px; +} + +div.toc li.level3 { + margin-left: 30px; +} + +div.toc li.level4 { + margin-left: 45px; +} + +span.emoji { + /* font family used at the site: https://unicode.org/emoji/charts/full-emoji-list.html + * font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort; + */ +} + +span.obfuscator { + display: none; +} + +.inherit_header { + font-weight: bold; + color: var(--inherit-header-color); + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 4px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + /*white-space: nowrap;*/ + color: var(--tooltip-foreground-color); + background-color: var(--tooltip-background-color); + border: 1px solid var(--tooltip-border-color); + border-radius: 4px 4px 4px 4px; + box-shadow: var(--tooltip-shadow); + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: var(--tooltip-doc-color); + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip a { + color: var(--tooltip-link-color); +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: var(--tooltip-declaration-color); +} + +#powerTip div { + margin: 0px; + padding: 0px; + font-size: 12px; + font-family: var(--font-family-tooltip); + line-height: 16px; +} + +#powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.s:after, #powerTip.s:before, +#powerTip.w:after, #powerTip.w:before, +#powerTip.e:after, #powerTip.e:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.nw:after, #powerTip.nw:before, +#powerTip.sw:after, #powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, #powerTip.s:after, +#powerTip.w:after, #powerTip.e:after, +#powerTip.nw:after, #powerTip.ne:after, +#powerTip.sw:after, #powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, #powerTip.s:before, +#powerTip.w:before, #powerTip.e:before, +#powerTip.nw:before, #powerTip.ne:before, +#powerTip.sw:before, #powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.nw:after, #powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { + border-top-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} +#powerTip.n:before, #powerTip.ne:before, #powerTip.nw:before { + border-top-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} +#powerTip.n:after, #powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, #powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, #powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, #powerTip.s:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.sw:after, #powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { + border-bottom-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { + border-bottom-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, #powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, #powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, #powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, #powerTip.e:before { + left: 100%; +} +#powerTip.e:after { + border-left-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.e:before { + border-left-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, #powerTip.w:before { + right: 100%; +} +#powerTip.w:after { + border-right-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.w:before { + border-right-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + +/* @group Markdown */ + +table.markdownTable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, table.markdownTable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.markdownTable tr { +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft, td.markdownTableBodyLeft { + text-align: left +} + +th.markdownTableHeadRight, td.markdownTableBodyRight { + text-align: right +} + +th.markdownTableHeadCenter, td.markdownTableBodyCenter { + text-align: center +} + +tt, code, kbd, samp +{ + display: inline-block; +} +/* @end */ + +u { + text-decoration: underline; +} + diff --git a/Doxygen/html/doxygen.svg b/Doxygen/html/doxygen.svg new file mode 100644 index 0000000..d42dad5 --- /dev/null +++ b/Doxygen/html/doxygen.svg @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg version="1.1" viewBox="0 0 104 31" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <defs> + <linearGradient id="a"> + <stop stop-color="#5373B4" offset="0"/> + <stop stop-color="#7C95C6" offset="1"/> + </linearGradient> + <linearGradient id="d" x1="31.474" x2="31.474" y1="24.821" y2="26.773" gradientUnits="userSpaceOnUse" xlink:href="#a"/> + <linearGradient id="c" x1="31.474" x2="31.474" y1="24.821" y2="26.773" gradientTransform="matrix(.6816 0 0 1.0248 72.391 -.91809)" gradientUnits="userSpaceOnUse" xlink:href="#a"/> + <linearGradient id="b" x1="56.295" x2="56.295" y1="24.622" y2="26.574" gradientUnits="userSpaceOnUse" xlink:href="#a"/> + <linearGradient id="e" x1="49.067" x2="48.956" y1="19.719" y2="9.5227" gradientTransform="matrix(.97968 0 0 1.0207 -.25579 -.25579)" gradientUnits="userSpaceOnUse"> + <stop stop-color="#C0CCE3" offset="0"/> + <stop stop-color="#EEF1F7" offset="1"/> + </linearGradient> + <filter id="f" x="-.010676" y="-.045304" width="1.0214" height="1.0906" color-interpolation-filters="sRGB"> + <feGaussianBlur stdDeviation="0.45293203"/> + </filter> + </defs> + <g> + <path transform="translate(-2.5759 -27.848)" d="m13.609 32.203v6.8633h-0.05078c-0.40533-0.66867-0.96254-1.1715-1.6719-1.5059-0.69244-0.35193-1.4282-0.52734-2.2051-0.52734-0.96267 0-1.807 0.2027-2.5332 0.60742-0.72622 0.38713-1.3344 0.90556-1.8242 1.5566-0.47289 0.65108-0.83456 1.4092-1.0879 2.2715-0.23644 0.84464-0.35547 1.7236-0.35547 2.6387 0 0.95022 0.11902 1.8643 0.35547 2.7441 0.25333 0.87983 0.615 1.6633 1.0879 2.3496 0.48978 0.66867 1.1065 1.2066 1.8496 1.6113 0.74311 0.38713 1.6044 0.58008 2.584 0.58008 0.86133 0 1.6311-0.15787 2.3066-0.47461 0.69244-0.33434 1.2497-0.87227 1.6719-1.6113h0.05078v1.7422h3.4199v-18.846zm12.875 4.8301c-1.0302 0-1.9596 0.17541-2.7871 0.52734-0.82756 0.33434-1.5358 0.81965-2.127 1.4531-0.59111 0.61588-1.0483 1.3721-1.3691 2.2695-0.32089 0.87983-0.48047 1.866-0.48047 2.957s0.15958 2.0752 0.48047 2.9551c0.32089 0.87983 0.77803 1.6361 1.3691 2.2695 0.59111 0.61588 1.2994 1.0914 2.127 1.4258 0.82756 0.33434 1.7569 0.50195 2.7871 0.50195 1.0302 0 1.9596-0.16762 2.7871-0.50195 0.84444-0.33434 1.5612-0.8099 2.1523-1.4258 0.59111-0.63348 1.0483-1.3897 1.3691-2.2695 0.32089-0.87983 0.48047-1.8641 0.48047-2.9551s-0.15958-2.0772-0.48047-2.957c-0.32089-0.89743-0.77803-1.6536-1.3691-2.2695-0.59111-0.63348-1.3079-1.1188-2.1523-1.4531-0.82756-0.35193-1.7569-0.52734-2.7871-0.52734zm41.715 0c-0.912 0-1.7223 0.18516-2.4316 0.55469-0.69244 0.36953-1.2752 0.87043-1.748 1.5039-0.47289 0.61588-0.83651 1.337-1.0898 2.1641-0.23645 0.80944-0.35352 1.6553-0.35352 2.5352 0 0.93262 0.10007 1.8214 0.30273 2.666 0.21956 0.82704 0.55767 1.556 1.0137 2.1895 0.456 0.61588 1.0387 1.109 1.748 1.4785 0.70933 0.35193 1.5536 0.5293 2.5332 0.5293 0.79378 0 1.5446-0.16762 2.2539-0.50195 0.72622-0.35193 1.2834-0.88986 1.6719-1.6113h0.05078v1.7949c0.01689 0.96782-0.21071 1.7689-0.68359 2.4023-0.456 0.63348-1.1898 0.95117-2.2031 0.95117-0.64178 0-1.2075-0.14228-1.6973-0.42383-0.48978-0.26395-0.81939-0.74731-0.98828-1.4512h-3.5723c0.05067 0.77425 0.25276 1.435 0.60742 1.9805 0.37156 0.56309 0.8287 1.0192 1.3691 1.3711 0.55733 0.35193 1.1656 0.60726 1.8242 0.76562 0.67556 0.17597 1.3328 0.26562 1.9746 0.26562 1.5031 0 2.7025-0.21245 3.5977-0.63477 0.89511-0.42232 1.5798-0.94076 2.0527-1.5566 0.47289-0.59829 0.777-1.2493 0.91211-1.9531 0.152-0.70386 0.22656-1.3295 0.22656-1.875v-12.775h-3.4199v1.8223h-0.05078c-0.43911-0.79185-0.98782-1.3551-1.6465-1.6895-0.64178-0.33434-1.3926-0.50195-2.2539-0.50195zm16.523 0c-0.99644 0-1.9088 0.18516-2.7363 0.55469-0.81067 0.36953-1.5124 0.88018-2.1035 1.5312-0.59111 0.63348-1.0463 1.3897-1.3672 2.2695s-0.48047 1.831-0.48047 2.8516c0 1.0558 0.15108 2.0225 0.45508 2.9023 0.32089 0.87983 0.76758 1.6361 1.3418 2.2695 0.57422 0.63348 1.276 1.1266 2.1035 1.4785 0.82756 0.33434 1.7569 0.50195 2.7871 0.50195 1.4862 0 2.7517-0.35277 3.7988-1.0566 1.0471-0.70387 1.8254-1.8733 2.332-3.5098h-3.168c-0.11822 0.42232-0.43934 0.82772-0.96289 1.2148-0.52355 0.36953-1.1468 0.55274-1.873 0.55273-1.0133 0-1.7916-0.27286-2.332-0.81836-0.54044-0.5455-0.83605-1.4245-0.88672-2.6387h9.4492c0.06756-1.0558-0.01551-2.0673-0.25195-3.0352-0.23644-0.96782-0.62557-1.8293-1.166-2.5859-0.52356-0.75666-1.1998-1.355-2.0273-1.7949-0.82756-0.45751-1.7974-0.6875-2.9121-0.6875zm16.189 0c-0.76 0-1.5023 0.18516-2.2285 0.55469-0.72622 0.35193-1.3174 0.92299-1.7734 1.7148h-0.07617v-1.9004h-3.4199v13.646h3.5977v-7.1523c0-1.3901 0.21909-2.3841 0.6582-2.9824 0.43911-0.61588 1.1494-0.92383 2.1289-0.92383 0.86133 0 1.4611 0.28066 1.7988 0.84375 0.33777 0.5455 0.50586 1.3816 0.50586 2.5078v7.707h3.5976v-8.3926c0-0.84464-0.0765-1.6106-0.22851-2.2969-0.13511-0.70387-0.37971-1.2925-0.73438-1.7676-0.35466-0.49271-0.84386-0.87277-1.4688-1.1367-0.608-0.28155-1.3948-0.42188-2.3574-0.42188zm-66.063 0.36914 4.3066 6.4668-4.7129 7.1797h4.0293l2.7363-4.3027 2.7344 4.3027h4.1055l-4.8398-7.2578 4.3066-6.3887h-3.9766l-2.2793 3.5645-2.3066-3.5645zm13.275 0 4.584 12.803c0.10133 0.26395 0.15234 0.54461 0.15234 0.84375 0 0.40472-0.11707 0.77504-0.35352 1.1094-0.21956 0.33434-0.56617 0.52729-1.0391 0.58008-0.35467 0.0176-0.70979 0.0098-1.0645-0.02539-0.35467-0.03519-0.70128-0.07028-1.0391-0.10547v3.0879c0.37156 0.03519 0.73518 0.06051 1.0898 0.07813 0.37156 0.03519 0.74368 0.05273 1.1152 0.05273 1.2329 0 2.1943-0.23778 2.8867-0.71289 0.69244-0.47511 1.2326-1.2664 1.6211-2.375l5.4727-15.336h-3.7246l-2.8613 9.3438h-0.05078l-2.9648-9.3438zm-37.48 2.4551c0.59111 0 1.0823 0.12279 1.4707 0.36914 0.38844 0.24635 0.6991 0.57184 0.93555 0.97656 0.25333 0.38713 0.43187 0.84515 0.5332 1.373 0.10133 0.5103 0.15234 1.0482 0.15234 1.6113 0 0.56309-0.05101 1.1069-0.15234 1.6348-0.10133 0.5279-0.27137 1.0035-0.50781 1.4258-0.23644 0.40472-0.5556 0.73021-0.96094 0.97656-0.38844 0.24635-0.87959 0.36914-1.4707 0.36914-0.55733 0-1.038-0.12279-1.4434-0.36914-0.38844-0.26395-0.71806-0.59723-0.98828-1.002-0.25333-0.42232-0.43842-0.89788-0.55664-1.4258s-0.17773-1.0561-0.17773-1.584c-1e-7 -0.56309 0.05101-1.0991 0.15234-1.6094 0.11822-0.5279 0.29481-0.99567 0.53125-1.4004 0.25333-0.40472 0.58295-0.73021 0.98828-0.97656 0.40533-0.24635 0.90303-0.36914 1.4941-0.36914zm15.84 0c0.608 0 1.1142 0.13253 1.5195 0.39648 0.42222 0.24635 0.75184 0.57184 0.98828 0.97656 0.25333 0.40472 0.42992 0.87054 0.53125 1.3984 0.10133 0.5279 0.15234 1.0658 0.15234 1.6113 0 0.5455-0.05101 1.0815-0.15234 1.6094-0.10134 0.5103-0.27792 0.97612-0.53125 1.3984-0.23644 0.40472-0.56606 0.73021-0.98828 0.97656-0.40533 0.24635-0.91153 0.36914-1.5195 0.36914-0.608 0-1.1142-0.12279-1.5195-0.36914s-0.73495-0.57184-0.98828-0.97656c-0.23644-0.42232-0.40648-0.88814-0.50781-1.3984-0.10133-0.5279-0.15234-1.0639-0.15234-1.6094 0-0.5455 0.05101-1.0834 0.15234-1.6113 0.10133-0.5279 0.27137-0.99371 0.50781-1.3984 0.25333-0.40472 0.58295-0.73021 0.98828-0.97656 0.40533-0.26395 0.91153-0.39648 1.5195-0.39648zm42.602 0c0.59111 0 1.0803 0.11499 1.4688 0.34375 0.38844 0.22876 0.70105 0.5367 0.9375 0.92383 0.23644 0.38713 0.40648 0.8354 0.50781 1.3457 0.10133 0.49271 0.15039 1.0209 0.15039 1.584 0 0.4927-0.06606 0.96827-0.20117 1.4258-0.11822 0.43992-0.30526 0.83557-0.55859 1.1875-0.25333 0.35193-0.57445 0.63259-0.96289 0.84375-0.38844 0.21116-0.83513 0.31836-1.3418 0.31836-0.55733 0-1.021-0.12474-1.3926-0.37109-0.37156-0.24635-0.67566-0.56209-0.91211-0.94922-0.21956-0.38713-0.38109-0.81786-0.48242-1.293-0.08444-0.49271-0.12695-0.98581-0.12695-1.4785 0-0.5103 0.05101-0.99366 0.15234-1.4512 0.11822-0.47511 0.29676-0.89025 0.5332-1.2422 0.25333-0.36953 0.55744-0.65993 0.91211-0.87109 0.37156-0.21116 0.80974-0.31641 1.3164-0.31641zm15.535 0c0.87822 0 1.529 0.24753 1.9512 0.74023 0.43911 0.49271 0.74322 1.2138 0.91211 2.1641h-5.8535c0.01689-0.26395 0.0679-0.5641 0.15234-0.89844 0.10133-0.33434 0.26287-0.65008 0.48242-0.94922 0.23644-0.29914 0.54055-0.54667 0.91211-0.74023 0.38845-0.21116 0.86914-0.31641 1.4434-0.31641z" filter="url(#f)" opacity=".3" stroke="#969696" xlink:href="#path141"/> + <path d="m0.97202 24.161 43.605-0.0019 0.0508 3.3061-43.6 0.04174z" fill="url(#d)" stroke="#000" stroke-width=".5"/> + <path d="m10.283 3.5547v6.8633h-0.05078c-0.40533-0.66867-0.96254-1.1715-1.6719-1.5059-0.69244-0.35193-1.4282-0.52734-2.2051-0.52734-0.96267 0-1.807 0.2027-2.5332 0.60742-0.72622 0.38713-1.3344 0.90556-1.8242 1.5566-0.47289 0.65108-0.83456 1.4092-1.0879 2.2715-0.23644 0.84464-0.35547 1.7236-0.35547 2.6387 0 0.95022 0.11902 1.8643 0.35547 2.7441 0.25333 0.87983 0.615 1.6633 1.0879 2.3496 0.48978 0.66867 1.1065 1.2066 1.8496 1.6113 0.74311 0.38713 1.6044 0.58008 2.584 0.58008 0.86133 0 1.6311-0.15787 2.3066-0.47461 0.69244-0.33434 1.2497-0.87227 1.6719-1.6113h0.05078v1.7422h3.4199v-18.846zm12.875 4.8301c-1.0302 0-1.9596 0.17541-2.7871 0.52734-0.82756 0.33434-1.5358 0.81965-2.127 1.4531-0.59111 0.61588-1.0483 1.3721-1.3691 2.2695-0.32089 0.87983-0.48047 1.866-0.48047 2.957s0.15958 2.0752 0.48047 2.9551c0.32089 0.87983 0.77803 1.6361 1.3691 2.2695 0.59111 0.61588 1.2994 1.0914 2.127 1.4258 0.82756 0.33434 1.7569 0.50195 2.7871 0.50195 1.0302 0 1.9596-0.16762 2.7871-0.50195 0.84444-0.33434 1.5612-0.8099 2.1523-1.4258 0.59111-0.63348 1.0483-1.3897 1.3691-2.2695 0.32089-0.87983 0.48047-1.8641 0.48047-2.9551s-0.15958-2.0772-0.48047-2.957c-0.32089-0.89743-0.77803-1.6536-1.3691-2.2695-0.59111-0.63348-1.3079-1.1188-2.1523-1.4531-0.82756-0.35193-1.7569-0.52734-2.7871-0.52734zm41.715 0c-0.912 0-1.7223 0.18516-2.4316 0.55469-0.69244 0.36953-1.2752 0.87043-1.748 1.5039-0.47289 0.61588-0.83651 1.337-1.0898 2.1641-0.23644 0.80944-0.35352 1.6553-0.35352 2.5352 0 0.93262 0.10007 1.8214 0.30273 2.666 0.21956 0.82704 0.55767 1.556 1.0137 2.1895 0.456 0.61588 1.0387 1.109 1.748 1.4785 0.70933 0.35193 1.5536 0.5293 2.5332 0.5293 0.79378 0 1.5446-0.16762 2.2539-0.50195 0.72622-0.35193 1.2834-0.88986 1.6719-1.6113h0.05078v1.7949c0.01689 0.96782-0.21071 1.7689-0.68359 2.4023-0.456 0.63348-1.1898 0.95117-2.2031 0.95117-0.64178 0-1.2075-0.14228-1.6973-0.42383-0.48978-0.26395-0.81939-0.74731-0.98828-1.4512h-3.5723c0.05067 0.77425 0.25276 1.435 0.60742 1.9805 0.37156 0.56309 0.8287 1.0192 1.3691 1.3711 0.55733 0.35193 1.1656 0.60726 1.8242 0.76562 0.67556 0.17597 1.3328 0.26562 1.9746 0.26562 1.5031 0 2.7025-0.21245 3.5977-0.63477 0.89511-0.42232 1.5798-0.94076 2.0527-1.5566 0.47289-0.59829 0.777-1.2493 0.91211-1.9531 0.152-0.70386 0.22656-1.3295 0.22656-1.875v-12.775h-3.4199v1.8223h-0.05078c-0.43911-0.79185-0.98782-1.3551-1.6465-1.6895-0.64178-0.33434-1.3926-0.50195-2.2539-0.50195zm16.523 0c-0.99644 0-1.9088 0.18516-2.7363 0.55469-0.81067 0.36953-1.5124 0.88017-2.1035 1.5312-0.59111 0.63348-1.0463 1.3897-1.3672 2.2695s-0.48047 1.831-0.48047 2.8516c0 1.0558 0.15108 2.0225 0.45508 2.9023 0.32089 0.87983 0.76758 1.6361 1.3418 2.2695 0.57422 0.63348 1.276 1.1266 2.1035 1.4785 0.82756 0.33434 1.7569 0.50195 2.7871 0.50195 1.4862 0 2.7517-0.35278 3.7988-1.0566 1.0471-0.70386 1.8254-1.8733 2.332-3.5098h-3.168c-0.11822 0.42232-0.43934 0.82772-0.96289 1.2148-0.52355 0.36953-1.1468 0.55274-1.873 0.55273-1.0133 0-1.7916-0.27286-2.332-0.81836-0.54044-0.5455-0.83605-1.4245-0.88672-2.6387h9.4492c0.06756-1.0558-0.01551-2.0673-0.25195-3.0352-0.23644-0.96782-0.62557-1.8293-1.166-2.5859-0.52356-0.75666-1.1998-1.355-2.0273-1.7949-0.82756-0.45751-1.7974-0.6875-2.9121-0.6875zm16.189 0c-0.76 0-1.5023 0.18516-2.2285 0.55469-0.72622 0.35193-1.3174 0.923-1.7734 1.7148h-0.07617v-1.9004h-3.4199v13.646h3.5977v-7.1523c0-1.3901 0.21909-2.3841 0.6582-2.9824 0.43911-0.61588 1.1494-0.92383 2.1289-0.92383 0.86133 0 1.461 0.28066 1.7988 0.84375 0.33778 0.5455 0.50586 1.3816 0.50586 2.5078v7.707h3.5977v-8.3926c0-0.84464-0.0765-1.6106-0.22852-2.2969-0.13511-0.70387-0.3797-1.2925-0.73437-1.7676-0.35466-0.49271-0.84386-0.87277-1.4688-1.1367-0.608-0.28155-1.3948-0.42188-2.3574-0.42188zm-66.062 0.36914 4.3066 6.4668-4.7129 7.1797h4.0293l2.7363-4.3027 2.7344 4.3027h4.1055l-4.8398-7.2578 4.3066-6.3887h-3.9766l-2.2793 3.5645-2.3066-3.5645zm13.275 0 4.584 12.803c0.10133 0.26395 0.15234 0.54461 0.15234 0.84375 0 0.40472-0.11707 0.77504-0.35352 1.1094-0.21956 0.33434-0.56617 0.52729-1.0391 0.58008-0.35467 0.0176-0.70979 0.0098-1.0645-0.02539-0.35467-0.03519-0.70128-0.07027-1.0391-0.10547v3.0879c0.37156 0.03519 0.73518 0.06052 1.0898 0.07813 0.37156 0.03519 0.74368 0.05273 1.1152 0.05273 1.2329 0 2.1943-0.23778 2.8867-0.71289 0.69244-0.47511 1.2326-1.2664 1.6211-2.375l5.4727-15.336h-3.7246l-2.8613 9.3437h-0.05078l-2.9648-9.3437zm-37.48 2.4551c0.59111 0 1.0823 0.12279 1.4707 0.36914s0.6991 0.57184 0.93555 0.97656c0.25333 0.38713 0.43187 0.84515 0.5332 1.373 0.10133 0.5103 0.15234 1.0482 0.15234 1.6113 0 0.56309-0.05101 1.1069-0.15234 1.6348-0.10133 0.5279-0.27137 1.0035-0.50781 1.4258-0.23644 0.40472-0.5556 0.73021-0.96094 0.97656-0.38844 0.24635-0.87959 0.36914-1.4707 0.36914-0.55733 0-1.038-0.12279-1.4434-0.36914-0.38844-0.26395-0.71806-0.59723-0.98828-1.002-0.25333-0.42232-0.43842-0.89788-0.55664-1.4258s-0.17773-1.0561-0.17773-1.584c-1e-7 -0.56309 0.05101-1.0991 0.15234-1.6094 0.11822-0.5279 0.29481-0.99567 0.53125-1.4004 0.25333-0.40472 0.58295-0.73021 0.98828-0.97656 0.40533-0.24635 0.90303-0.36914 1.4941-0.36914zm15.84 0c0.608 0 1.1142 0.13254 1.5195 0.39648 0.42222 0.24635 0.75184 0.57184 0.98828 0.97656 0.25333 0.40472 0.42992 0.87054 0.53125 1.3984 0.10133 0.5279 0.15234 1.0658 0.15234 1.6113 0 0.5455-0.05101 1.0815-0.15234 1.6094-0.10133 0.5103-0.27792 0.97612-0.53125 1.3984-0.23644 0.40472-0.56606 0.73021-0.98828 0.97656-0.40533 0.24635-0.91153 0.36914-1.5195 0.36914-0.608 0-1.1142-0.12279-1.5195-0.36914s-0.73495-0.57184-0.98828-0.97656c-0.23644-0.42232-0.40648-0.88813-0.50781-1.3984-0.10133-0.5279-0.15234-1.0639-0.15234-1.6094 0-0.5455 0.05101-1.0834 0.15234-1.6113 0.10133-0.5279 0.27137-0.99371 0.50781-1.3984 0.25333-0.40472 0.58295-0.73021 0.98828-0.97656 0.40533-0.26395 0.91153-0.39648 1.5195-0.39648zm42.602 0c0.59111 0 1.0803 0.11499 1.4688 0.34375 0.38844 0.22876 0.70106 0.5367 0.9375 0.92383 0.23644 0.38713 0.40648 0.8354 0.50781 1.3457 0.10133 0.49271 0.15039 1.0209 0.15039 1.584 0 0.49271-0.06606 0.96827-0.20117 1.4258-0.11822 0.43992-0.30526 0.83557-0.55859 1.1875-0.25333 0.35193-0.57445 0.63259-0.96289 0.84375-0.38844 0.21116-0.83513 0.31836-1.3418 0.31836-0.55733 0-1.021-0.12474-1.3926-0.37109-0.37156-0.24635-0.67566-0.56209-0.91211-0.94922-0.21956-0.38713-0.38109-0.81786-0.48242-1.293-0.08444-0.49271-0.12695-0.98581-0.12695-1.4785 0-0.5103 0.05101-0.99366 0.15234-1.4512 0.11822-0.47511 0.29676-0.89026 0.5332-1.2422 0.25333-0.36953 0.55744-0.65993 0.91211-0.87109 0.37156-0.21116 0.80974-0.31641 1.3164-0.31641zm15.535 0c0.87822 0 1.529 0.24753 1.9512 0.74024 0.43911 0.49271 0.74322 1.2138 0.91211 2.1641h-5.8535c0.01689-0.26395 0.0679-0.5641 0.15234-0.89844 0.10133-0.33434 0.26287-0.65008 0.48242-0.94922 0.23644-0.29914 0.54055-0.54667 0.91211-0.74023 0.38845-0.21116 0.86914-0.31641 1.4434-0.31641z" fill="url(#e)" stroke="#4665A2" stroke-width=".7"/> + <path d="m52.988 27.291c0.99602-1.0359 1.3944-1.8725 1.7928-3.1076l3.8247-0.03984c0.3113 1.6096 0.82413 2.5137 1.6335 3.1474z" fill="url(#b)" stroke="#000" stroke-width=".5"/> + <path d="m73.89 24.04 28.885-0.2011-0.12476 3.3879-31.033 0.16229c1.2621-1.0234 1.9665-2.2859 2.2724-3.3491z" fill="url(#c)" stroke="#000" stroke-width=".41788"/> + </g> +</svg> diff --git a/Doxygen/html/dynsections.js b/Doxygen/html/dynsections.js new file mode 100644 index 0000000..f579fbf --- /dev/null +++ b/Doxygen/html/dynsections.js @@ -0,0 +1,123 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + 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. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function toggleVisibility(linkObj) +{ + var base = $(linkObj).attr('id'); + var summary = $('#'+base+'-summary'); + var content = $('#'+base+'-content'); + var trigger = $('#'+base+'-trigger'); + var src=$(trigger).attr('src'); + if (content.is(':visible')===true) { + content.hide(); + summary.show(); + $(linkObj).addClass('closed').removeClass('opened'); + $(trigger).attr('src',src.substring(0,src.length-8)+'closed.png'); + } else { + content.show(); + summary.hide(); + $(linkObj).removeClass('closed').addClass('opened'); + $(trigger).attr('src',src.substring(0,src.length-10)+'open.png'); + } + return false; +} + +function updateStripes() +{ + $('table.directory tr'). + removeClass('even').filter(':visible:even').addClass('even'); + $('table.directory tr'). + removeClass('odd').filter(':visible:odd').addClass('odd'); +} + +function toggleLevel(level) +{ + $('table.directory tr').each(function() { + var l = this.id.split('_').length-1; + var i = $('#img'+this.id.substring(3)); + var a = $('#arr'+this.id.substring(3)); + if (l<level+1) { + i.removeClass('iconfopen iconfclosed').addClass('iconfopen'); + a.html('▼'); + $(this).show(); + } else if (l==level+1) { + i.removeClass('iconfclosed iconfopen').addClass('iconfclosed'); + a.html('►'); + $(this).show(); + } else { + $(this).hide(); + } + }); + updateStripes(); +} + +function toggleFolder(id) +{ + // the clicked row + var currentRow = $('#row_'+id); + + // all rows after the clicked row + var rows = currentRow.nextAll("tr"); + + var re = new RegExp('^row_'+id+'\\d+_$', "i"); //only one sub + + // only match elements AFTER this one (can't hide elements before) + var childRows = rows.filter(function() { return this.id.match(re); }); + + // first row is visible we are HIDING + if (childRows.filter(':first').is(':visible')===true) { + // replace down arrow by right arrow for current row + var currentRowSpans = currentRow.find("span"); + currentRowSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed"); + currentRowSpans.filter(".arrow").html('►'); + rows.filter("[id^=row_"+id+"]").hide(); // hide all children + } else { // we are SHOWING + // replace right arrow by down arrow for current row + var currentRowSpans = currentRow.find("span"); + currentRowSpans.filter(".iconfclosed").removeClass("iconfclosed").addClass("iconfopen"); + currentRowSpans.filter(".arrow").html('▼'); + // replace down arrows by right arrows for child rows + var childRowsSpans = childRows.find("span"); + childRowsSpans.filter(".iconfopen").removeClass("iconfopen").addClass("iconfclosed"); + childRowsSpans.filter(".arrow").html('►'); + childRows.show(); //show all children + } + updateStripes(); +} + + +function toggleInherit(id) +{ + var rows = $('tr.inherit.'+id); + var img = $('tr.inherit_header.'+id+' img'); + var src = $(img).attr('src'); + if (rows.filter(':first').is(':visible')===true) { + rows.css('display','none'); + $(img).attr('src',src.substring(0,src.length-8)+'closed.png'); + } else { + rows.css('display','table-row'); // using show() causes jump in firefox + $(img).attr('src',src.substring(0,src.length-10)+'open.png'); + } +} +/* @license-end */ diff --git a/Doxygen/html/files.html b/Doxygen/html/files.html new file mode 100644 index 0000000..1b0ec96 --- /dev/null +++ b/Doxygen/html/files.html @@ -0,0 +1,102 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: File List</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">File List</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock">Here is a list of all files with brief descriptions:</div><div class="directory"> +<table class="directory"> +<tr id="row_0_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_check_collision_component_8cpp.html" target="_self">CheckCollisionComponent.cpp</a></td><td class="desc"></td></tr> +<tr id="row_1_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_check_collision_component_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_check_collision_component_8h.html" target="_self">CheckCollisionComponent.h</a></td><td class="desc"></td></tr> +<tr id="row_2_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_custom_procedural_mesh_component_8cpp.html" target="_self">CustomProceduralMeshComponent.cpp</a></td><td class="desc"></td></tr> +<tr id="row_3_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_custom_procedural_mesh_component_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_custom_procedural_mesh_component_8h.html" target="_self">CustomProceduralMeshComponent.h</a></td><td class="desc"></td></tr> +<tr id="row_4_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_excavator_anim_8cpp.html" target="_self">ExcavatorAnim.cpp</a></td><td class="desc"></td></tr> +<tr id="row_5_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_excavator_anim_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_excavator_anim_8h.html" target="_self">ExcavatorAnim.h</a></td><td class="desc"></td></tr> +<tr id="row_6_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_excavator_character_8cpp.html" target="_self">ExcavatorCharacter.cpp</a></td><td class="desc"></td></tr> +<tr id="row_7_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_excavator_character_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_excavator_character_8h.html" target="_self">ExcavatorCharacter.h</a></td><td class="desc"></td></tr> +<tr id="row_8_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_excavator_simulator_8_build_8cs.html" target="_self">ExcavatorSimulator.Build.cs</a></td><td class="desc"></td></tr> +<tr id="row_9_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_excavator_simulator_8cpp.html" target="_self">ExcavatorSimulator.cpp</a></td><td class="desc"></td></tr> +<tr id="row_10_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_excavator_simulator_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_excavator_simulator_8h.html" target="_self">ExcavatorSimulator.h</a></td><td class="desc"></td></tr> +<tr id="row_11_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_excavator_simulator_game_mode_base_8cpp.html" target="_self">ExcavatorSimulatorGameModeBase.cpp</a></td><td class="desc"></td></tr> +<tr id="row_12_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_excavator_simulator_game_mode_base_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_excavator_simulator_game_mode_base_8h.html" target="_self">ExcavatorSimulatorGameModeBase.h</a></td><td class="desc"></td></tr> +<tr id="row_13_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_ground_deformer_component_8cpp.html" target="_self">GroundDeformerComponent.cpp</a></td><td class="desc"></td></tr> +<tr id="row_14_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_ground_deformer_component_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_ground_deformer_component_8h.html" target="_self">GroundDeformerComponent.h</a></td><td class="desc"></td></tr> +<tr id="row_15_" class="odd"><td class="entry"><span style="width:16px;display:inline-block;"> </span><span class="icondoc"></span><a class="el" href="_world_generator_8cpp.html" target="_self">WorldGenerator.cpp</a></td><td class="desc"></td></tr> +<tr id="row_16_" class="even"><td class="entry"><span style="width:16px;display:inline-block;"> </span><a href="_world_generator_8h_source.html"><span class="icondoc"></span></a><a class="el" href="_world_generator_8h.html" target="_self">WorldGenerator.h</a></td><td class="desc"></td></tr> +</table> +</div><!-- directory --> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/folderclosed.png b/Doxygen/html/folderclosed.png new file mode 100644 index 0000000..0e8458a --- /dev/null +++ b/Doxygen/html/folderclosed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6ca13a1c87edcfbfc91317896452c31a9d49c4768f1b4b46ac32e0907e00a73 +size 616 diff --git a/Doxygen/html/folderopen.png b/Doxygen/html/folderopen.png new file mode 100644 index 0000000..cc10730 --- /dev/null +++ b/Doxygen/html/folderopen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:680166339ff62595dd2d2eed3a79fb9fa0c2e8250e89539f6d678aa2e5e51e26 +size 597 diff --git a/Doxygen/html/functions.html b/Doxygen/html/functions.html new file mode 100644 index 0000000..545134e --- /dev/null +++ b/Doxygen/html/functions.html @@ -0,0 +1,181 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Class Members</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="contents"> +<div class="textblock">Here is a list of all class members with links to the classes they belong to:</div> + +<h3><a id="index_a" name="index_a"></a>- a -</h3><ul> +<li>ACustomProceduralMeshComponent() : <a class="el" href="class_a_custom_procedural_mesh_component.html#ad2a083e99c4303ff692c6de7fd7c89d7">ACustomProceduralMeshComponent</a></li> +<li>AExcavatorCharacter() : <a class="el" href="class_a_excavator_character.html#a897f85cbfeb62c8cf9357124030678f0">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_b" name="index_b"></a>- b -</h3><ul> +<li>BeamBottomRotation() : <a class="el" href="class_a_excavator_character.html#a4c0322adf74c147b25e0a56eb82e8b63">AExcavatorCharacter</a></li> +<li>BeamBottomRotationVR() : <a class="el" href="class_a_excavator_character.html#a2fd6354fdc9f48c9ec37aa8a0881bb39">AExcavatorCharacter</a></li> +<li>BeamTopRotation() : <a class="el" href="class_a_excavator_character.html#a700a5e4f8e43868c15d6b60460006eb9">AExcavatorCharacter</a></li> +<li>BeamTopRotationVR() : <a class="el" href="class_a_excavator_character.html#a0fa69b8a1aa0f05dad74a41da45f5551">AExcavatorCharacter</a></li> +<li>BeginPlay() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a91a659f1e60e2aadddc5ffd88372f66d">ACustomProceduralMeshComponent</a>, <a class="el" href="class_a_excavator_character.html#aff0f3c6a77a15a8476981c7845dff158">AExcavatorCharacter</a></li> +<li>BodyRotation() : <a class="el" href="class_a_excavator_character.html#a63014640642f4964440cd3923cc8396f">AExcavatorCharacter</a></li> +<li>BodyRotationVR() : <a class="el" href="class_a_excavator_character.html#a52c7b1ac54ccb49c0cd5e9baf25dc338">AExcavatorCharacter</a></li> +<li>BucketRotationReleaceRocks() : <a class="el" href="class_a_excavator_character.html#a18933e6161a7489652e64e06d6dc9d72">AExcavatorCharacter</a></li> +<li>BucketRotationVR() : <a class="el" href="class_a_excavator_character.html#a5eaf154b0d816e682669609f6a1145b4">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_c" name="index_c"></a>- c -</h3><ul> +<li>CreateTriangle() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a9d663c4e5f670976a2e9a9de0b9e9268">ACustomProceduralMeshComponent</a></li> +<li>CreateVertices() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a722837f83f1b0db0cb97f47b50d310b3">ACustomProceduralMeshComponent</a></li> +<li>CreateWall() : <a class="el" href="class_a_custom_procedural_mesh_component.html#ac1c8d09545e81f4cee3a33d8e1ff0810">ACustomProceduralMeshComponent</a></li> +</ul> + + +<h3><a id="index_e" name="index_e"></a>- e -</h3><ul> +<li>ExcavatorSimulator() : <a class="el" href="class_excavator_simulator.html#ac3cbb6777236f4b4900e850d58050951">ExcavatorSimulator</a></li> +</ul> + + +<h3><a id="index_f" name="index_f"></a>- f -</h3><ul> +<li>FWorldGeneratorInstance() : <a class="el" href="class_f_world_generator_instance.html#a9340a460da1c5a05a25e496f5bbe3e47">FWorldGeneratorInstance</a></li> +</ul> + + +<h3><a id="index_g" name="index_g"></a>- g -</h3><ul> +<li>GetBeamBottomRotation() : <a class="el" href="class_u_excavator_anim.html#affb325f375a952a31fd6c7edbda878b2">UExcavatorAnim</a></li> +<li>GetBeamTopRotation() : <a class="el" href="class_u_excavator_anim.html#a1cf8d06387c0c8bf61315209a2277b89">UExcavatorAnim</a></li> +<li>GetBodyRotation() : <a class="el" href="class_u_excavator_anim.html#adf9be335c9520d1b730d0c983ad42734">UExcavatorAnim</a></li> +<li>GetBottomMaxRotation() : <a class="el" href="class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79">UExcavatorAnim</a></li> +<li>GetBottomMinRotation() : <a class="el" href="class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b">UExcavatorAnim</a></li> +<li>GetBucketMaxRotation() : <a class="el" href="class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f">UExcavatorAnim</a></li> +<li>GetBucketMinRotation() : <a class="el" href="class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc">UExcavatorAnim</a></li> +<li>GetBucketRotation() : <a class="el" href="class_u_excavator_anim.html#ad206f925afc96462b27ebd5c33273da5">UExcavatorAnim</a></li> +<li>GetInstance() : <a class="el" href="class_u_world_generator.html#a92908af10f0cdfee492c5e21ba1000d1">UWorldGenerator</a></li> +<li>GetMaterialImpl() : <a class="el" href="class_f_world_generator_instance.html#a5d982bbb90e6a7368d4eab2bbf4107a0">FWorldGeneratorInstance</a></li> +<li>GetTopMaxRotation() : <a class="el" href="class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1">UExcavatorAnim</a></li> +<li>GetTopMinRotation() : <a class="el" href="class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793">UExcavatorAnim</a></li> +<li>GetUpVector() : <a class="el" href="class_f_world_generator_instance.html#a41a32aee12e9228c3ec2bdfa4be7a92a">FWorldGeneratorInstance</a></li> +<li>GetValueImpl() : <a class="el" href="class_f_world_generator_instance.html#a9a18ba21423f11dc01d3a2e54fbd214f">FWorldGeneratorInstance</a></li> +<li>GetValueRangeImpl() : <a class="el" href="class_f_world_generator_instance.html#aa18eccf536acdf8d2115f32ac0dab1ef">FWorldGeneratorInstance</a></li> +</ul> + + +<h3><a id="index_i" name="index_i"></a>- i -</h3><ul> +<li>Init() : <a class="el" href="class_f_world_generator_instance.html#acb9442be2e711fb3ba4d146bf584bd5d">FWorldGeneratorInstance</a></li> +</ul> + + +<h3><a id="index_m" name="index_m"></a>- m -</h3><ul> +<li>Movement() : <a class="el" href="class_a_excavator_character.html#abfc64bb0a310badadb9e85e7336b0735">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_n" name="index_n"></a>- n -</h3><ul> +<li>NoiseHeight : <a class="el" href="class_u_world_generator.html#a97672fed88363bef5359e4a9e1bc22d2">UWorldGenerator</a></li> +</ul> + + +<h3><a id="index_s" name="index_s"></a>- s -</h3><ul> +<li>Seed : <a class="el" href="class_u_world_generator.html#ae3e0313a980ab00d26cb7691a9d178e7">UWorldGenerator</a></li> +<li>SetBeamBottomRotation() : <a class="el" href="class_u_excavator_anim.html#a09b8e1814d38428a70423d3d81922448">UExcavatorAnim</a></li> +<li>SetBeamTopRotation() : <a class="el" href="class_u_excavator_anim.html#a59c863bb954f22c019bc20b6d4b947df">UExcavatorAnim</a></li> +<li>SetBodyRotation() : <a class="el" href="class_u_excavator_anim.html#a215798edeb92f27e3e5ad9ad5c25626a">UExcavatorAnim</a></li> +<li>SetBucketRotation() : <a class="el" href="class_u_excavator_anim.html#aba254277652747c4c1dabd60308b4c17">UExcavatorAnim</a></li> +<li>SetupPlayerInputComponent() : <a class="el" href="class_a_excavator_character.html#ae0c6e52ac2b153baa789be4e0b2b75fc">AExcavatorCharacter</a></li> +<li>Super : <a class="el" href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01">FWorldGeneratorInstance</a></li> +</ul> + + +<h3><a id="index_t" name="index_t"></a>- t -</h3><ul> +<li>Tick() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a9b6c8c619a8d0396e2d3d19f61007427">ACustomProceduralMeshComponent</a>, <a class="el" href="class_a_excavator_character.html#a080c503815b1f096cf7704e5bfa47df1">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_u" name="index_u"></a>- u -</h3><ul> +<li>UExcavatorAnim() : <a class="el" href="class_u_excavator_anim.html#a17a5fcd9bd609ed5ad1538906286184c">UExcavatorAnim</a></li> +<li>UWorldGenerator() : <a class="el" href="class_u_world_generator.html#a4ea59a679fc8eff20b3b143461020977">UWorldGenerator</a></li> +</ul> + + +<h3><a id="index_v" name="index_v"></a>- v -</h3><ul> +<li>VehicleRotation() : <a class="el" href="class_a_excavator_character.html#a3f262813a65448739540f41c73498f56">AExcavatorCharacter</a></li> +<li>VehicleRotationVR() : <a class="el" href="class_a_excavator_character.html#a173be62746e6adc66879eea680f21d56">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index__7E" name="index__7E"></a>- ~ -</h3><ul> +<li>~UWorldGenerator() : <a class="el" href="class_u_world_generator.html#ad2e31d185549690f85ddec11f48a709e">UWorldGenerator</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/functions_func.html b/Doxygen/html/functions_func.html new file mode 100644 index 0000000..c8502f7 --- /dev/null +++ b/Doxygen/html/functions_func.html @@ -0,0 +1,174 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Class Members - Functions</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="contents"> +  + +<h3><a id="index_a" name="index_a"></a>- a -</h3><ul> +<li>ACustomProceduralMeshComponent() : <a class="el" href="class_a_custom_procedural_mesh_component.html#ad2a083e99c4303ff692c6de7fd7c89d7">ACustomProceduralMeshComponent</a></li> +<li>AExcavatorCharacter() : <a class="el" href="class_a_excavator_character.html#a897f85cbfeb62c8cf9357124030678f0">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_b" name="index_b"></a>- b -</h3><ul> +<li>BeamBottomRotation() : <a class="el" href="class_a_excavator_character.html#a4c0322adf74c147b25e0a56eb82e8b63">AExcavatorCharacter</a></li> +<li>BeamBottomRotationVR() : <a class="el" href="class_a_excavator_character.html#a2fd6354fdc9f48c9ec37aa8a0881bb39">AExcavatorCharacter</a></li> +<li>BeamTopRotation() : <a class="el" href="class_a_excavator_character.html#a700a5e4f8e43868c15d6b60460006eb9">AExcavatorCharacter</a></li> +<li>BeamTopRotationVR() : <a class="el" href="class_a_excavator_character.html#a0fa69b8a1aa0f05dad74a41da45f5551">AExcavatorCharacter</a></li> +<li>BeginPlay() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a91a659f1e60e2aadddc5ffd88372f66d">ACustomProceduralMeshComponent</a>, <a class="el" href="class_a_excavator_character.html#aff0f3c6a77a15a8476981c7845dff158">AExcavatorCharacter</a></li> +<li>BodyRotation() : <a class="el" href="class_a_excavator_character.html#a63014640642f4964440cd3923cc8396f">AExcavatorCharacter</a></li> +<li>BodyRotationVR() : <a class="el" href="class_a_excavator_character.html#a52c7b1ac54ccb49c0cd5e9baf25dc338">AExcavatorCharacter</a></li> +<li>BucketRotationReleaceRocks() : <a class="el" href="class_a_excavator_character.html#a18933e6161a7489652e64e06d6dc9d72">AExcavatorCharacter</a></li> +<li>BucketRotationVR() : <a class="el" href="class_a_excavator_character.html#a5eaf154b0d816e682669609f6a1145b4">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_c" name="index_c"></a>- c -</h3><ul> +<li>CreateTriangle() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a9d663c4e5f670976a2e9a9de0b9e9268">ACustomProceduralMeshComponent</a></li> +<li>CreateVertices() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a722837f83f1b0db0cb97f47b50d310b3">ACustomProceduralMeshComponent</a></li> +<li>CreateWall() : <a class="el" href="class_a_custom_procedural_mesh_component.html#ac1c8d09545e81f4cee3a33d8e1ff0810">ACustomProceduralMeshComponent</a></li> +</ul> + + +<h3><a id="index_e" name="index_e"></a>- e -</h3><ul> +<li>ExcavatorSimulator() : <a class="el" href="class_excavator_simulator.html#ac3cbb6777236f4b4900e850d58050951">ExcavatorSimulator</a></li> +</ul> + + +<h3><a id="index_f" name="index_f"></a>- f -</h3><ul> +<li>FWorldGeneratorInstance() : <a class="el" href="class_f_world_generator_instance.html#a9340a460da1c5a05a25e496f5bbe3e47">FWorldGeneratorInstance</a></li> +</ul> + + +<h3><a id="index_g" name="index_g"></a>- g -</h3><ul> +<li>GetBeamBottomRotation() : <a class="el" href="class_u_excavator_anim.html#affb325f375a952a31fd6c7edbda878b2">UExcavatorAnim</a></li> +<li>GetBeamTopRotation() : <a class="el" href="class_u_excavator_anim.html#a1cf8d06387c0c8bf61315209a2277b89">UExcavatorAnim</a></li> +<li>GetBodyRotation() : <a class="el" href="class_u_excavator_anim.html#adf9be335c9520d1b730d0c983ad42734">UExcavatorAnim</a></li> +<li>GetBottomMaxRotation() : <a class="el" href="class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79">UExcavatorAnim</a></li> +<li>GetBottomMinRotation() : <a class="el" href="class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b">UExcavatorAnim</a></li> +<li>GetBucketMaxRotation() : <a class="el" href="class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f">UExcavatorAnim</a></li> +<li>GetBucketMinRotation() : <a class="el" href="class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc">UExcavatorAnim</a></li> +<li>GetBucketRotation() : <a class="el" href="class_u_excavator_anim.html#ad206f925afc96462b27ebd5c33273da5">UExcavatorAnim</a></li> +<li>GetInstance() : <a class="el" href="class_u_world_generator.html#a92908af10f0cdfee492c5e21ba1000d1">UWorldGenerator</a></li> +<li>GetMaterialImpl() : <a class="el" href="class_f_world_generator_instance.html#a5d982bbb90e6a7368d4eab2bbf4107a0">FWorldGeneratorInstance</a></li> +<li>GetTopMaxRotation() : <a class="el" href="class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1">UExcavatorAnim</a></li> +<li>GetTopMinRotation() : <a class="el" href="class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793">UExcavatorAnim</a></li> +<li>GetUpVector() : <a class="el" href="class_f_world_generator_instance.html#a41a32aee12e9228c3ec2bdfa4be7a92a">FWorldGeneratorInstance</a></li> +<li>GetValueImpl() : <a class="el" href="class_f_world_generator_instance.html#a9a18ba21423f11dc01d3a2e54fbd214f">FWorldGeneratorInstance</a></li> +<li>GetValueRangeImpl() : <a class="el" href="class_f_world_generator_instance.html#aa18eccf536acdf8d2115f32ac0dab1ef">FWorldGeneratorInstance</a></li> +</ul> + + +<h3><a id="index_i" name="index_i"></a>- i -</h3><ul> +<li>Init() : <a class="el" href="class_f_world_generator_instance.html#acb9442be2e711fb3ba4d146bf584bd5d">FWorldGeneratorInstance</a></li> +</ul> + + +<h3><a id="index_m" name="index_m"></a>- m -</h3><ul> +<li>Movement() : <a class="el" href="class_a_excavator_character.html#abfc64bb0a310badadb9e85e7336b0735">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_s" name="index_s"></a>- s -</h3><ul> +<li>SetBeamBottomRotation() : <a class="el" href="class_u_excavator_anim.html#a09b8e1814d38428a70423d3d81922448">UExcavatorAnim</a></li> +<li>SetBeamTopRotation() : <a class="el" href="class_u_excavator_anim.html#a59c863bb954f22c019bc20b6d4b947df">UExcavatorAnim</a></li> +<li>SetBodyRotation() : <a class="el" href="class_u_excavator_anim.html#a215798edeb92f27e3e5ad9ad5c25626a">UExcavatorAnim</a></li> +<li>SetBucketRotation() : <a class="el" href="class_u_excavator_anim.html#aba254277652747c4c1dabd60308b4c17">UExcavatorAnim</a></li> +<li>SetupPlayerInputComponent() : <a class="el" href="class_a_excavator_character.html#ae0c6e52ac2b153baa789be4e0b2b75fc">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_t" name="index_t"></a>- t -</h3><ul> +<li>Tick() : <a class="el" href="class_a_custom_procedural_mesh_component.html#a9b6c8c619a8d0396e2d3d19f61007427">ACustomProceduralMeshComponent</a>, <a class="el" href="class_a_excavator_character.html#a080c503815b1f096cf7704e5bfa47df1">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index_u" name="index_u"></a>- u -</h3><ul> +<li>UExcavatorAnim() : <a class="el" href="class_u_excavator_anim.html#a17a5fcd9bd609ed5ad1538906286184c">UExcavatorAnim</a></li> +<li>UWorldGenerator() : <a class="el" href="class_u_world_generator.html#a4ea59a679fc8eff20b3b143461020977">UWorldGenerator</a></li> +</ul> + + +<h3><a id="index_v" name="index_v"></a>- v -</h3><ul> +<li>VehicleRotation() : <a class="el" href="class_a_excavator_character.html#a3f262813a65448739540f41c73498f56">AExcavatorCharacter</a></li> +<li>VehicleRotationVR() : <a class="el" href="class_a_excavator_character.html#a173be62746e6adc66879eea680f21d56">AExcavatorCharacter</a></li> +</ul> + + +<h3><a id="index__7E" name="index__7E"></a>- ~ -</h3><ul> +<li>~UWorldGenerator() : <a class="el" href="class_u_world_generator.html#ad2e31d185549690f85ddec11f48a709e">UWorldGenerator</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/functions_type.html b/Doxygen/html/functions_type.html new file mode 100644 index 0000000..de7974b --- /dev/null +++ b/Doxygen/html/functions_type.html @@ -0,0 +1,81 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Class Members - Typedefs</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="contents"> + <ul> +<li>Super : <a class="el" href="class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01">FWorldGeneratorInstance</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/functions_vars.html b/Doxygen/html/functions_vars.html new file mode 100644 index 0000000..78fa000 --- /dev/null +++ b/Doxygen/html/functions_vars.html @@ -0,0 +1,82 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Class Members - Variables</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="contents"> + <ul> +<li>NoiseHeight : <a class="el" href="class_u_world_generator.html#a97672fed88363bef5359e4a9e1bc22d2">UWorldGenerator</a></li> +<li>Seed : <a class="el" href="class_u_world_generator.html#ae3e0313a980ab00d26cb7691a9d178e7">UWorldGenerator</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/globals.html b/Doxygen/html/globals.html new file mode 100644 index 0000000..0c828bd --- /dev/null +++ b/Doxygen/html/globals.html @@ -0,0 +1,81 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: File Members</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="contents"> +<div class="textblock">Here is a list of all file members with links to the files they belong to:</div><ul> +<li>IMPLEMENT_PRIMARY_GAME_MODULE() : <a class="el" href="_excavator_simulator_8cpp.html#abdfae4c413c9ef6bcaa5cf0c657807d7">ExcavatorSimulator.cpp</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/globals_func.html b/Doxygen/html/globals_func.html new file mode 100644 index 0000000..01d9824 --- /dev/null +++ b/Doxygen/html/globals_func.html @@ -0,0 +1,81 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: File Members</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="contents"> + <ul> +<li>IMPLEMENT_PRIMARY_GAME_MODULE() : <a class="el" href="_excavator_simulator_8cpp.html#abdfae4c413c9ef6bcaa5cf0c657807d7">ExcavatorSimulator.cpp</a></li> +</ul> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/hierarchy.html b/Doxygen/html/hierarchy.html new file mode 100644 index 0000000..afecdfb --- /dev/null +++ b/Doxygen/html/hierarchy.html @@ -0,0 +1,99 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Class Hierarchy</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">Class Hierarchy</div></div> +</div><!--header--> +<div class="contents"> +<div class="textblock">This inheritance list is sorted roughly, but not completely, alphabetically:</div><div class="directory"> +<div class="levels">[detail level <span onclick="javascript:toggleLevel(1);">1</span><span onclick="javascript:toggleLevel(2);">2</span>]</div><table class="directory"> +<tr id="row_0_" class="even"><td class="entry"><span style="width:0px;display:inline-block;"> </span><span id="arr_0_" class="arrow" onclick="toggleFolder('0_')">▼</span><span class="icona"><span class="icon">C</span></span><b>AActor</b></td><td class="desc"></td></tr> +<tr id="row_0_0_" class="odd"><td class="entry"><span style="width:32px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_a_custom_procedural_mesh_component.html" target="_self">ACustomProceduralMeshComponent</a></td><td class="desc"></td></tr> +<tr id="row_1_" class="even"><td class="entry"><span style="width:0px;display:inline-block;"> </span><span id="arr_1_" class="arrow" onclick="toggleFolder('1_')">▼</span><span class="icona"><span class="icon">C</span></span><b>ACharacter</b></td><td class="desc"></td></tr> +<tr id="row_1_0_" class="odd"><td class="entry"><span style="width:32px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_a_excavator_character.html" target="_self">AExcavatorCharacter</a></td><td class="desc"></td></tr> +<tr id="row_2_" class="even"><td class="entry"><span style="width:0px;display:inline-block;"> </span><span id="arr_2_" class="arrow" onclick="toggleFolder('2_')">▼</span><span class="icona"><span class="icon">C</span></span><b>AGameModeBase</b></td><td class="desc"></td></tr> +<tr id="row_2_0_" class="odd"><td class="entry"><span style="width:32px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_a_excavator_simulator_game_mode_base.html" target="_self">AExcavatorSimulatorGameModeBase</a></td><td class="desc"></td></tr> +<tr id="row_3_" class="even"><td class="entry"><span style="width:0px;display:inline-block;"> </span><span id="arr_3_" class="arrow" onclick="toggleFolder('3_')">▼</span><span class="icona"><span class="icon">C</span></span><b>ModuleRules</b></td><td class="desc"></td></tr> +<tr id="row_3_0_" class="odd"><td class="entry"><span style="width:32px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_excavator_simulator.html" target="_self">ExcavatorSimulator</a></td><td class="desc"></td></tr> +<tr id="row_4_" class="even"><td class="entry"><span style="width:0px;display:inline-block;"> </span><span id="arr_4_" class="arrow" onclick="toggleFolder('4_')">▼</span><span class="icona"><span class="icon">C</span></span><b>TVoxelGeneratorInstanceHelper</b></td><td class="desc"></td></tr> +<tr id="row_4_0_" class="odd"><td class="entry"><span style="width:32px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_f_world_generator_instance.html" target="_self">FWorldGeneratorInstance</a></td><td class="desc"></td></tr> +<tr id="row_5_" class="even"><td class="entry"><span style="width:0px;display:inline-block;"> </span><span id="arr_5_" class="arrow" onclick="toggleFolder('5_')">▼</span><span class="icona"><span class="icon">C</span></span><b>UAnimInstance</b></td><td class="desc"></td></tr> +<tr id="row_5_0_" class="odd"><td class="entry"><span style="width:32px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_u_excavator_anim.html" target="_self">UExcavatorAnim</a></td><td class="desc"></td></tr> +<tr id="row_6_" class="even"><td class="entry"><span style="width:0px;display:inline-block;"> </span><span id="arr_6_" class="arrow" onclick="toggleFolder('6_')">▼</span><span class="icona"><span class="icon">C</span></span><b>UVoxelGenerator</b></td><td class="desc"></td></tr> +<tr id="row_6_0_" class="odd"><td class="entry"><span style="width:32px;display:inline-block;"> </span><span class="icona"><span class="icon">C</span></span><a class="el" href="class_u_world_generator.html" target="_self">UWorldGenerator</a></td><td class="desc"></td></tr> +</table> +</div><!-- directory --> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/index.html b/Doxygen/html/index.html new file mode 100644 index 0000000..b7ccea6 --- /dev/null +++ b/Doxygen/html/index.html @@ -0,0 +1,81 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.9.5"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>ExcavatorSimulator: Main Page</title> +<link href="tabs.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="jquery.js"></script> +<script type="text/javascript" src="dynsections.js"></script> +<link href="search/search.css" rel="stylesheet" type="text/css"/> +<script type="text/javascript" src="search/searchdata.js"></script> +<script type="text/javascript" src="search/search.js"></script> +<link href="doxygen.css" rel="stylesheet" type="text/css" /> +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr id="projectrow"> + <td id="projectalign"> + <div id="projectname">ExcavatorSimulator<span id="projectnumber"> 0.5.0</span> + </div> + </td> + </tr> + </tbody> +</table> +</div> +<!-- end header part --> +<!-- Generated by Doxygen 1.9.5 --> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +var searchBox = new SearchBox("searchBox", "search/",'.html'); +/* @license-end */ +</script> +<script type="text/javascript" src="menudata.js"></script> +<script type="text/javascript" src="menu.js"></script> +<script type="text/javascript"> +/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */ +$(function() { + initMenu('',true,false,'search.php','Search'); + $(document).ready(function() { init_search(); }); +}); +/* @license-end */ +</script> +<div id="main-nav"></div> +</div><!-- top --> +<!-- window showing the filter options --> +<div id="MSearchSelectWindow" + onmouseover="return searchBox.OnSearchSelectShow()" + onmouseout="return searchBox.OnSearchSelectHide()" + onkeydown="return searchBox.OnSearchSelectKey(event)"> +</div> + +<!-- iframe showing the search results (closed by default) --> +<div id="MSearchResultsWindow"> +<div id="MSearchResults"> +<div class="SRPage"> +<div id="SRIndex"> +<div id="SRResults"></div> +<div class="SRStatus" id="Loading">Loading...</div> +<div class="SRStatus" id="Searching">Searching...</div> +<div class="SRStatus" id="NoMatches">No Matches</div> +</div> +</div> +</div> +</div> + +<div class="header"> + <div class="headertitle"><div class="title">ExcavatorSimulator Documentation</div></div> +</div><!--header--> +<div class="contents"> +</div><!-- contents --> +<!-- start footer part --> +<hr class="footer"/><address class="footer"><small> +Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.9.5 +</small></address> +</body> +</html> diff --git a/Doxygen/html/jquery.js b/Doxygen/html/jquery.js new file mode 100644 index 0000000..1dffb65 --- /dev/null +++ b/Doxygen/html/jquery.js @@ -0,0 +1,34 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=v.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),"function"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S="sizzle"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),j=function(e,t){return e===t&&(l=!0),0},D={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",I="(?:\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",W="\\["+M+"*("+I+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+I+"))|)"+M+"*\\]",F=":("+I+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+W+")*)|.*)\\)|)",B=new RegExp(M+"+","g"),$=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=new RegExp("^"+M+"*,"+M+"*"),z=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+S+"'></a><select id='"+S+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&D.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(j),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(B," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),x="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split("").sort(j).join("")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[":"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var D,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return O(e,"nextSibling")},prev:function(e){return O(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,"template")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\x20\t\r\n\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[["notify","progress",S.Callbacks("memory"),S.Callbacks("memory"),2],["resolve","done",S.Callbacks("once memory"),S.Callbacks("once memory"),0,"resolved"],["reject","fail",S.Callbacks("once memory"),S.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener("DOMContentLoaded",B),C.removeEventListener("load",B),S.ready()}S.fn.ready=function(e){return F.then(e)["catch"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,"complete"===E.readyState||"loading"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener("DOMContentLoaded",B),C.addEventListener("load",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,"ms-").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(K,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===S.css(e,"display")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,"")},u=s(),l=n&&n[3]||(S.cssNumber[t]?"":"px"),c=e.nodeType&&(S.cssNumber[t]||"px"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ue[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="<textarea>x</textarea>",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="<option></option>",y.option=!!ce.lastChild;var ge={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,"<select multiple='multiple'>","</select>"]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}var be=/^([^.]*)(?:\.(.+)|)/;function we(){return!0}function Te(){return!1}function Ce(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ee(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ee(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Te;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Se(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n&&n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,we)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(P)||[""]).length;while(l--)d=g=(s=be.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(P)||[""]).length;while(l--)if(d=g=(s=be.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Se(t,"click",we),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,"input")&&Se(t,"click"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,"input")&&Y.get(t,"click")||A(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?we:Te,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Te,isPropagationStopped:Te,isImmediatePropagationStopped:Te,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=we,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=we,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=we,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:!0},S.event.addProp),S.each({focus:"focusin",blur:"focusout"},function(e,t){S.event.special[e]={setup:function(){return Se(this,e,Ce),!1},trigger:function(){return Se(this,e),!0},_default:function(){return!0},delegateType:t}}),S.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return Ee(this,e,t,n,r)},one:function(e,t,n,r){return Ee(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Te),this.each(function(){S.event.remove(this,e,n,t)})}});var ke=/<script|<style|<link/i,Ae=/checked\s*(?:[^=]|=\s*.checked.)/i,Ne=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function He(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&"string"==typeof d&&!y.checkClone&&Ae.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),He(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,"script"),De)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,qe),c=0;c<s;c++)u=a[c],he.test(u.type||"")&&!Y.access(u,"globalEval")&&S.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):b(u.textContent.replace(Ne,""),u,l))}return n}function Oe(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,"script")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Le(o[r],a[r]);else Le(e,c);return 0<(a=ve(c,"script")).length&&ye(a,!f&&ve(e,"script")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Oe(this,e,!0)},remove:function(e){return Oe(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return He(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||je(this,e).appendChild(e)})},prepend:function(){return He(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=je(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return He(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return He(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!ke.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return He(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Pe=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Re=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},Me=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Ie=new RegExp(ne.join("|"),"i");function We(e,t,n){var r,i,o,a,s=e.style;return(n=n||Re(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Pe.test(a)&&Ie.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function Fe(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement("div"),l=E.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",y.clearCloneStyle="content-box"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement("table"),t=E.createElement("tr"),n=E.createElement("div"),e.style.cssText="position:absolute;left:-11111px;border-collapse:separate",t.style.cssText="border:1px solid",t.style.height="1px",n.style.height="9px",n.style.display="block",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=parseInt(r.height,10)+parseInt(r.borderTopWidth,10)+parseInt(r.borderBottomWidth,10)===t.offsetHeight,re.removeChild(e)),a}}))}();var Be=["Webkit","Moz","ms"],$e=E.createElement("div").style,_e={};function ze(e){var t=S.cssProps[e]||_e[e];return t||(e in $e?e:_e[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=Be.length;while(n--)if((e=Be[n]+t)in $e)return e}(e)||e)}var Ue=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ve={position:"absolute",visibility:"hidden",display:"block"},Ge={letterSpacing:"0",fontWeight:"400"};function Ye(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Qe(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=S.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=S.css(e,"border"+ne[a]+"Width",!0,i))):(u+=S.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=S.css(e,"border"+ne[a]+"Width",!0,i):s+=S.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Je(e,t,n){var r=Re(e),i=(!y.boxSizingReliable()||n)&&"border-box"===S.css(e,"boxSizing",!1,r),o=i,a=We(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Pe.test(a)){if(!n)return a;a="auto"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===S.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===S.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Qe(e,t,n||(i?"border":"content"),o,r,a)+"px"}function Ke(e,t,n,r,i){return new Ke.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=We(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Xe.test(t),l=e.style;if(u||(t=ze(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?"":"px")),y.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Xe.test(t)||(t=ze(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=We(e,t,r)),"normal"===i&&t in Ge&&(i=Ge[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each(["height","width"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ue.test(S.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?Je(e,u,n):Me(e,Ve,function(){return Je(e,u,n)})},set:function(e,t,n){var r,i=Re(e),o=!y.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===S.css(e,"boxSizing",!1,i),s=n?Qe(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Qe(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=S.css(e,u)),Ye(0,t,s)}}}),S.cssHooks.marginLeft=Fe(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(We(e,"marginLeft"))||e.getBoundingClientRect().left-Me(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),S.each({margin:"",padding:"",border:"Width"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(S.cssHooks[i+o].set=Ye)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Re(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=Ke).prototype={constructor:Ke,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?"":"px")},cur:function(){var e=Ke.propHooks[this.prop];return e&&e.get?e.get(this):Ke.propHooks._default.get(this)},run:function(e){var t,n=Ke.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Ke.propHooks._default.set(this),this}}).init.prototype=Ke.prototype,(Ke.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[ze(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=Ke.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},S.fx=Ke.prototype.init,S.fx.step={};var Ze,et,tt,nt,rt=/^(?:toggle|show|hide)$/,it=/queueHooks$/;function ot(){et&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(ot):C.setTimeout(ot,S.fx.interval),S.fx.tick())}function at(){return C.setTimeout(function(){Ze=void 0}),Ze=Date.now()}function st(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=ne[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function ut(e,t,n){for(var r,i=(lt.tweeners[t]||[]).concat(lt.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function lt(o,e,t){var n,a,r=0,i=lt.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=Ze||at(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:Ze||at(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=lt.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ut,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(lt,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=["*"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],lt.tweeners[n]=lt.tweeners[n]||[],lt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,"fxshow");for(r in n.queue||(null==(a=S._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],rt.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,"display")),"none"===(c=S.css(e,"display"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,"display"),le([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===S.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=Y.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,"fxshow"),d)S.style(e,r,d[r])})),u=ut(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?lt.prefilters.unshift(e):lt.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&"object"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=lt(this,S.extend({},t),o);(i||Y.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&it.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=Y.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each(["toggle","show","hide"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(st(r,!0),e,t,n)}}),S.each({slideDown:st("show"),slideUp:st("hide"),slideToggle:st("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(Ze=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),Ze=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){et||(et=!0,ot())},S.fx.stop=function(){et=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},tt=E.createElement("input"),nt=E.createElement("select").appendChild(E.createElement("option")),tt.type="checkbox",y.checkOn=""!==tt.value,y.optSelected=nt.selected,(tt=E.createElement("input")).value="t",tt.type="radio",y.radioValue="t"===tt.value;var ct,ft=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?ct:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&"radio"===t&&A(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),ct={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\w+/g),function(e,t){var a=ft[t]||S.find.attr;ft[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=ft[o],ft[o]=r,r=null!=a(e,t,n)?o:null,ft[o]=i),r}});var pt=/^(?:input|select|textarea|button)$/i,dt=/^(?:a|area)$/i;function ht(e){return(e.match(P)||[]).join(" ")}function gt(e){return e.getAttribute&&e.getAttribute("class")||""}function vt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,"tabindex");return t?parseInt(t,10):pt.test(e.nodeName)||dt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,gt(this)))});if((e=vt(t)).length)while(n=this[u++])if(i=gt(n),r=1===n.nodeType&&" "+ht(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=ht(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,gt(this)))});if(!arguments.length)return this.attr("class","");if((e=vt(t)).length)while(n=this[u++])if(i=gt(n),r=1===n.nodeType&&" "+ht(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=ht(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,gt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=vt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=gt(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+ht(gt(n))+" ").indexOf(t))return!0;return!1}});var yt=/\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?"":e+""})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(yt,""):null==e?"":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,"value");return null!=t?t:ht(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,"optgroup"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each(["radio","checkbox"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),y.focusin="onfocusin"in C;var mt=/^(?:focusinfocus|focusoutblur)$/,xt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,"type")?e.type:e,h=v.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!mt.test(d+S.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[S.expando]?e:new S.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,mt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,xt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,xt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var bt=C.location,wt={guid:Date.now()},Tt=/\?/;S.parseXML=function(e){var t,n;if(!e||"string"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,"text/xml")}catch(e){}return n=t&&t.getElementsByTagName("parsererror")[0],t&&!n||S.error("Invalid XML: "+(n?S.map(n.childNodes,function(e){return e.textContent}).join("\n"):e)),t};var Ct=/\[\]$/,Et=/\r?\n/g,St=/^(?:submit|button|image|reset|file)$/i,kt=/^(?:input|select|textarea|keygen)/i;function At(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||Ct.test(n)?i(n,t):At(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==w(e))i(n,e);else for(t in e)At(n+"["+t+"]",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)At(n,e[n],t,i);return r.join("&")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,"elements");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(":disabled")&&kt.test(this.nodeName)&&!St.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(Et,"\r\n")}}):{name:t.name,value:n.replace(Et,"\r\n")}}).get()}});var Nt=/%20/g,jt=/#.*$/,Dt=/([?&])_=[^&]*/,qt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Lt=/^(?:GET|HEAD)$/,Ht=/^\/\//,Ot={},Pt={},Rt="*/".concat("*"),Mt=E.createElement("a");function It(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Wt(t,i,o,a){var s={},u=t===Pt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function Ft(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Mt.href=bt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:bt.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(bt.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Rt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Ft(Ft(e,S.ajaxSettings),t):Ft(S.ajaxSettings,e)},ajaxPrefilter:It(Ot),ajaxTransport:It(Pt),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=qt.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||bt.href)+"").replace(Ht,bt.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(P)||[""],null==v.crossDomain){r=E.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Mt.protocol+"//"+Mt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Wt(Ot,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Lt.test(v.type),f=v.url.replace(jt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(Nt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(Tt.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Dt,"$1"),o=(Tt.test(f)?"&":"?")+"_="+wt.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader("If-Modified-Since",S.lastModified[f]),S.etag[f]&&T.setRequestHeader("If-None-Match",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+Rt+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Wt(Pt,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray("script",v.dataTypes)&&S.inArray("json",v.dataTypes)<0&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(S.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(S.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--S.active||S.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,"json")},getScript:function(e,t){return S.get(e,void 0,t,"script")}}),S.each(["get","post"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var Bt={0:200,1223:204},$t=S.ajaxSettings.xhr();y.cors=!!$t&&"withCredentials"in $t,y.ajax=$t=!!$t,S.ajaxTransport(function(i){var o,a;if(y.cors||$t&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(Bt[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),S.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=ht(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&S.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?S("<div>").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Xt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?"":(e+"").replace(Xt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return S});var Vt=C.jQuery,Gt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Gt),e&&C.jQuery===S&&(C.jQuery=Vt),S},"undefined"==typeof e&&(C.jQuery=C.$=S),S}); +/*! jQuery UI - v1.13.2 - 2022-08-01 +* http://jqueryui.com +* Includes: widget.js, position.js, data.js, disable-selection.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/resizable.js, widgets/mouse.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +!function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(y){"use strict";y.ui=y.ui||{};y.ui.version="1.13.2";var n,i=0,h=Array.prototype.hasOwnProperty,a=Array.prototype.slice;y.cleanData=(n=y.cleanData,function(t){for(var e,i,s=0;null!=(i=t[s]);s++)(e=y._data(i,"events"))&&e.remove&&y(i).triggerHandler("remove");n(t)}),y.widget=function(t,i,e){var s,n,o,h={},a=t.split(".")[0],r=a+"-"+(t=t.split(".")[1]);return e||(e=i,i=y.Widget),Array.isArray(e)&&(e=y.extend.apply(null,[{}].concat(e))),y.expr.pseudos[r.toLowerCase()]=function(t){return!!y.data(t,r)},y[a]=y[a]||{},s=y[a][t],n=y[a][t]=function(t,e){if(!this||!this._createWidget)return new n(t,e);arguments.length&&this._createWidget(t,e)},y.extend(n,s,{version:e.version,_proto:y.extend({},e),_childConstructors:[]}),(o=new i).options=y.widget.extend({},o.options),y.each(e,function(e,s){function n(){return i.prototype[e].apply(this,arguments)}function o(t){return i.prototype[e].apply(this,t)}h[e]="function"==typeof s?function(){var t,e=this._super,i=this._superApply;return this._super=n,this._superApply=o,t=s.apply(this,arguments),this._super=e,this._superApply=i,t}:s}),n.prototype=y.widget.extend(o,{widgetEventPrefix:s&&o.widgetEventPrefix||t},h,{constructor:n,namespace:a,widgetName:t,widgetFullName:r}),s?(y.each(s._childConstructors,function(t,e){var i=e.prototype;y.widget(i.namespace+"."+i.widgetName,n,e._proto)}),delete s._childConstructors):i._childConstructors.push(n),y.widget.bridge(t,n),n},y.widget.extend=function(t){for(var e,i,s=a.call(arguments,1),n=0,o=s.length;n<o;n++)for(e in s[n])i=s[n][e],h.call(s[n],e)&&void 0!==i&&(y.isPlainObject(i)?t[e]=y.isPlainObject(t[e])?y.widget.extend({},t[e],i):y.widget.extend({},i):t[e]=i);return t},y.widget.bridge=function(o,e){var h=e.prototype.widgetFullName||o;y.fn[o]=function(i){var t="string"==typeof i,s=a.call(arguments,1),n=this;return t?this.length||"instance"!==i?this.each(function(){var t,e=y.data(this,h);return"instance"===i?(n=e,!1):e?"function"!=typeof e[i]||"_"===i.charAt(0)?y.error("no such method '"+i+"' for "+o+" widget instance"):(t=e[i].apply(e,s))!==e&&void 0!==t?(n=t&&t.jquery?n.pushStack(t.get()):t,!1):void 0:y.error("cannot call methods on "+o+" prior to initialization; attempted to call method '"+i+"'")}):n=void 0:(s.length&&(i=y.widget.extend.apply(null,[i].concat(s))),this.each(function(){var t=y.data(this,h);t?(t.option(i||{}),t._init&&t._init()):y.data(this,h,new e(i,this))})),n}},y.Widget=function(){},y.Widget._childConstructors=[],y.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=y(e||this.defaultElement||this)[0],this.element=y(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=y(),this.hoverable=y(),this.focusable=y(),this.classesElementLookup={},e!==this&&(y.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=y(e.style?e.ownerDocument:e.document||e),this.window=y(this.document[0].defaultView||this.document[0].parentWindow)),this.options=y.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:y.noop,_create:y.noop,_init:y.noop,destroy:function(){var i=this;this._destroy(),y.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:y.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return y.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=y.widget.extend({},this.options[t]),n=0;n<i.length-1;n++)s[i[n]]=s[i[n]]||{},s=s[i[n]];if(t=i.pop(),1===arguments.length)return void 0===s[t]?null:s[t];s[t]=e}else{if(1===arguments.length)return void 0===this.options[t]?null:this.options[t];o[t]=e}return this._setOptions(o),this},_setOptions:function(t){for(var e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(t){var e,i,s;for(e in t)s=this.classesElementLookup[e],t[e]!==this.options.classes[e]&&s&&s.length&&(i=y(s.get()),this._removeClass(s,e),i.addClass(this._classes({element:i,keys:e,classes:t,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(n){var o=[],h=this;function t(t,e){for(var i,s=0;s<t.length;s++)i=h.classesElementLookup[t[s]]||y(),i=n.add?(function(){var i=[];n.element.each(function(t,e){y.map(h.classesElementLookup,function(t){return t}).some(function(t){return t.is(e)})||i.push(e)}),h._on(y(i),{remove:"_untrackClassesElement"})}(),y(y.uniqueSort(i.get().concat(n.element.get())))):y(i.not(n.element).get()),h.classesElementLookup[t[s]]=i,o.push(t[s]),e&&n.classes[t[s]]&&o.push(n.classes[t[s]])}return(n=y.extend({element:this.element,classes:this.options.classes||{}},n)).keys&&t(n.keys.match(/\S+/g)||[],!0),n.extra&&t(n.extra.match(/\S+/g)||[]),o.join(" ")},_untrackClassesElement:function(i){var s=this;y.each(s.classesElementLookup,function(t,e){-1!==y.inArray(i.target,e)&&(s.classesElementLookup[t]=y(e.not(i.target).get()))}),this._off(y(i.target))},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){var n="string"==typeof t||null===t,i={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s="boolean"==typeof s?s:i};return i.element.toggleClass(this._classes(i),s),this},_on:function(n,o,t){var h,a=this;"boolean"!=typeof n&&(t=o,o=n,n=!1),t?(o=h=y(o),this.bindings=this.bindings.add(o)):(t=o,o=this.element,h=this.widget()),y.each(t,function(t,e){function i(){if(n||!0!==a.options.disabled&&!y(this).hasClass("ui-state-disabled"))return("string"==typeof e?a[e]:e).apply(a,arguments)}"string"!=typeof e&&(i.guid=e.guid=e.guid||i.guid||y.guid++);var s=t.match(/^([\w:-]*)\s*(.*)$/),t=s[1]+a.eventNamespace,s=s[2];s?h.on(t,s,i):o.on(t,i)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.off(e),this.bindings=y(this.bindings.not(t).get()),this.focusable=y(this.focusable.not(t).get()),this.hoverable=y(this.hoverable.not(t).get())},_delay:function(t,e){var i=this;return setTimeout(function(){return("string"==typeof t?i[t]:t).apply(i,arguments)},e||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){this._addClass(y(t.currentTarget),null,"ui-state-hover")},mouseleave:function(t){this._removeClass(y(t.currentTarget),null,"ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){this._addClass(y(t.currentTarget),null,"ui-state-focus")},focusout:function(t){this._removeClass(y(t.currentTarget),null,"ui-state-focus")}})},_trigger:function(t,e,i){var s,n,o=this.options[t];if(i=i||{},(e=y.Event(e)).type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),e.target=this.element[0],n=e.originalEvent)for(s in n)s in e||(e[s]=n[s]);return this.element.trigger(e,i),!("function"==typeof o&&!1===o.apply(this.element[0],[e].concat(i))||e.isDefaultPrevented())}},y.each({show:"fadeIn",hide:"fadeOut"},function(o,h){y.Widget.prototype["_"+o]=function(e,t,i){var s,n=(t="string"==typeof t?{effect:t}:t)?!0!==t&&"number"!=typeof t&&t.effect||h:o;"number"==typeof(t=t||{})?t={duration:t}:!0===t&&(t={}),s=!y.isEmptyObject(t),t.complete=i,t.delay&&e.delay(t.delay),s&&y.effects&&y.effects.effect[n]?e[o](t):n!==o&&e[n]?e[n](t.duration,t.easing,i):e.queue(function(t){y(this)[o](),i&&i.call(e[0]),t()})}});var s,x,D,o,r,l,u,p,W;y.widget;function E(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function H(t,e){return parseInt(y.css(t,e),10)||0}function S(t){return null!=t&&t===t.window}x=Math.max,D=Math.abs,o=/left|center|right/,r=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,u=/^\w+/,p=/%$/,W=y.fn.position,y.position={scrollbarWidth:function(){if(void 0!==s)return s;var t,e=y("<div style='display:block;position:absolute;width:200px;height:200px;overflow:hidden;'><div style='height:300px;width:auto;'></div></div>"),i=e.children()[0];return y("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i},getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.width<t.element[0].scrollWidth;return{width:"scroll"===i||"auto"===i&&t.height<t.element[0].scrollHeight?y.position.scrollbarWidth():0,height:e?y.position.scrollbarWidth():0}},getWithinInfo:function(t){var e=y(t||window),i=S(e[0]),s=!!e[0]&&9===e[0].nodeType;return{element:e,isWindow:i,isDocument:s,offset:!i&&!s?y(t).offset():{left:0,top:0},scrollLeft:e.scrollLeft(),scrollTop:e.scrollTop(),width:e.outerWidth(),height:e.outerHeight()}}},y.fn.position=function(p){if(!p||!p.of)return W.apply(this,arguments);var d,c,f,g,m,t,_="string"==typeof(p=y.extend({},p)).of?y(document).find(p.of):y(p.of),w=y.position.getWithinInfo(p.within),v=y.position.getScrollInfo(w),b=(p.collision||"flip").split(" "),z={},e=9===(t=(e=_)[0]).nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:S(t)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:t.preventDefault?{width:0,height:0,offset:{top:t.pageY,left:t.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()};return _[0].preventDefault&&(p.at="left top"),c=e.width,f=e.height,m=y.extend({},g=e.offset),y.each(["my","at"],function(){var t,e,i=(p[this]||"").split(" ");(i=1===i.length?o.test(i[0])?i.concat(["center"]):r.test(i[0])?["center"].concat(i):["center","center"]:i)[0]=o.test(i[0])?i[0]:"center",i[1]=r.test(i[1])?i[1]:"center",t=l.exec(i[0]),e=l.exec(i[1]),z[this]=[t?t[0]:0,e?e[0]:0],p[this]=[u.exec(i[0])[0],u.exec(i[1])[0]]}),1===b.length&&(b[1]=b[0]),"right"===p.at[0]?m.left+=c:"center"===p.at[0]&&(m.left+=c/2),"bottom"===p.at[1]?m.top+=f:"center"===p.at[1]&&(m.top+=f/2),d=E(z.at,c,f),m.left+=d[0],m.top+=d[1],this.each(function(){var i,t,h=y(this),a=h.outerWidth(),r=h.outerHeight(),e=H(this,"marginLeft"),s=H(this,"marginTop"),n=a+e+H(this,"marginRight")+v.width,o=r+s+H(this,"marginBottom")+v.height,l=y.extend({},m),u=E(z.my,h.outerWidth(),h.outerHeight());"right"===p.my[0]?l.left-=a:"center"===p.my[0]&&(l.left-=a/2),"bottom"===p.my[1]?l.top-=r:"center"===p.my[1]&&(l.top-=r/2),l.left+=u[0],l.top+=u[1],i={marginLeft:e,marginTop:s},y.each(["left","top"],function(t,e){y.ui.position[b[t]]&&y.ui.position[b[t]][e](l,{targetWidth:c,targetHeight:f,elemWidth:a,elemHeight:r,collisionPosition:i,collisionWidth:n,collisionHeight:o,offset:[d[0]+u[0],d[1]+u[1]],my:p.my,at:p.at,within:w,elem:h})}),p.using&&(t=function(t){var e=g.left-l.left,i=e+c-a,s=g.top-l.top,n=s+f-r,o={target:{element:_,left:g.left,top:g.top,width:c,height:f},element:{element:h,left:l.left,top:l.top,width:a,height:r},horizontal:i<0?"left":0<e?"right":"center",vertical:n<0?"top":0<s?"bottom":"middle"};c<a&&D(e+i)<c&&(o.horizontal="center"),f<r&&D(s+n)<f&&(o.vertical="middle"),x(D(e),D(i))>x(D(s),D(n))?o.important="horizontal":o.important="vertical",p.using.call(this,t,o)}),h.offset(y.extend(l,{using:t}))})},y.ui.position={fit:{left:function(t,e){var i=e.within,s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,h=s-o,a=o+e.collisionWidth-n-s;e.collisionWidth>n?0<h&&a<=0?(i=t.left+h+e.collisionWidth-n-s,t.left+=h-i):t.left=!(0<a&&h<=0)&&a<h?s+n-e.collisionWidth:s:0<h?t.left+=h:0<a?t.left-=a:t.left=x(t.left-o,t.left)},top:function(t,e){var i=e.within,s=i.isWindow?i.scrollTop:i.offset.top,n=e.within.height,o=t.top-e.collisionPosition.marginTop,h=s-o,a=o+e.collisionHeight-n-s;e.collisionHeight>n?0<h&&a<=0?(i=t.top+h+e.collisionHeight-n-s,t.top+=h-i):t.top=!(0<a&&h<=0)&&a<h?s+n-e.collisionHeight:s:0<h?t.top+=h:0<a?t.top-=a:t.top=x(t.top-o,t.top)}},flip:{left:function(t,e){var i=e.within,s=i.offset.left+i.scrollLeft,n=i.width,o=i.isWindow?i.scrollLeft:i.offset.left,h=t.left-e.collisionPosition.marginLeft,a=h-o,r=h+e.collisionWidth-n-o,l="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,i="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,h=-2*e.offset[0];a<0?((s=t.left+l+i+h+e.collisionWidth-n-s)<0||s<D(a))&&(t.left+=l+i+h):0<r&&(0<(o=t.left-e.collisionPosition.marginLeft+l+i+h-o)||D(o)<r)&&(t.left+=l+i+h)},top:function(t,e){var i=e.within,s=i.offset.top+i.scrollTop,n=i.height,o=i.isWindow?i.scrollTop:i.offset.top,h=t.top-e.collisionPosition.marginTop,a=h-o,r=h+e.collisionHeight-n-o,l="top"===e.my[1]?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,i="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,h=-2*e.offset[1];a<0?((s=t.top+l+i+h+e.collisionHeight-n-s)<0||s<D(a))&&(t.top+=l+i+h):0<r&&(0<(o=t.top-e.collisionPosition.marginTop+l+i+h-o)||D(o)<r)&&(t.top+=l+i+h)}},flipfit:{left:function(){y.ui.position.flip.left.apply(this,arguments),y.ui.position.fit.left.apply(this,arguments)},top:function(){y.ui.position.flip.top.apply(this,arguments),y.ui.position.fit.top.apply(this,arguments)}}};var t;y.ui.position,y.extend(y.expr.pseudos,{data:y.expr.createPseudo?y.expr.createPseudo(function(e){return function(t){return!!y.data(t,e)}}):function(t,e,i){return!!y.data(t,i[3])}}),y.fn.extend({disableSelection:(t="onselectstart"in document.createElement("div")?"selectstart":"mousedown",function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}),enableSelection:function(){return this.off(".ui-disableSelection")}});y.ui.focusable=function(t,e){var i,s,n,o,h=t.nodeName.toLowerCase();return"area"===h?(s=(i=t.parentNode).name,!(!t.href||!s||"map"!==i.nodeName.toLowerCase())&&(0<(s=y("img[usemap='#"+s+"']")).length&&s.is(":visible"))):(/^(input|select|textarea|button|object)$/.test(h)?(n=!t.disabled)&&(o=y(t).closest("fieldset")[0])&&(n=!o.disabled):n="a"===h&&t.href||e,n&&y(t).is(":visible")&&function(t){var e=t.css("visibility");for(;"inherit"===e;)t=t.parent(),e=t.css("visibility");return"visible"===e}(y(t)))},y.extend(y.expr.pseudos,{focusable:function(t){return y.ui.focusable(t,null!=y.attr(t,"tabindex"))}});var e,d;y.ui.focusable,y.fn._form=function(){return"string"==typeof this[0].form?this.closest("form"):y(this[0].form)},y.ui.formResetMixin={_formResetHandler:function(){var e=y(this);setTimeout(function(){var t=e.data("ui-form-reset-instances");y.each(t,function(){this.refresh()})})},_bindFormResetHandler:function(){var t;this.form=this.element._form(),this.form.length&&((t=this.form.data("ui-form-reset-instances")||[]).length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t))},_unbindFormResetHandler:function(){var t;this.form.length&&((t=this.form.data("ui-form-reset-instances")).splice(y.inArray(this,t),1),t.length?this.form.data("ui-form-reset-instances",t):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset"))}};y.expr.pseudos||(y.expr.pseudos=y.expr[":"]),y.uniqueSort||(y.uniqueSort=y.unique),y.escapeSelector||(e=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,d=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},y.escapeSelector=function(t){return(t+"").replace(e,d)}),y.fn.even&&y.fn.odd||y.fn.extend({even:function(){return this.filter(function(t){return t%2==0})},odd:function(){return this.filter(function(t){return t%2==1})}});y.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},y.fn.labels=function(){var t,e,i;return this.length?this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(e=this.eq(0).parents("label"),(t=this.attr("id"))&&(i=(i=this.eq(0).parents().last()).add((i.length?i:this).siblings()),t="label[for='"+y.escapeSelector(t)+"']",e=e.add(i.find(t).addBack(t))),this.pushStack(e)):this.pushStack([])},y.fn.scrollParent=function(t){var e=this.css("position"),i="absolute"===e,s=t?/(auto|scroll|hidden)/:/(auto|scroll)/,t=this.parents().filter(function(){var t=y(this);return(!i||"static"!==t.css("position"))&&s.test(t.css("overflow")+t.css("overflow-y")+t.css("overflow-x"))}).eq(0);return"fixed"!==e&&t.length?t:y(this[0].ownerDocument||document)},y.extend(y.expr.pseudos,{tabbable:function(t){var e=y.attr(t,"tabindex"),i=null!=e;return(!i||0<=e)&&y.ui.focusable(t,i)}}),y.fn.extend({uniqueId:(c=0,function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++c)})}),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&y(this).removeAttr("id")})}}),y.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var c,f=!1;y(document).on("mouseup",function(){f=!1});y.widget("ui.mouse",{version:"1.13.2",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(t){if(!0===y.data(t.target,e.widgetName+".preventClickEvent"))return y.removeData(t.target,e.widgetName+".preventClickEvent"),t.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(!f){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var e=this,i=1===t.which,s=!("string"!=typeof this.options.cancel||!t.target.nodeName)&&y(t.target).closest(this.options.cancel).length;return i&&!s&&this._mouseCapture(t)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){e.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=!1!==this._mouseStart(t),!this._mouseStarted)?(t.preventDefault(),!0):(!0===y.data(t.target,this.widgetName+".preventClickEvent")&&y.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return e._mouseMove(t)},this._mouseUpDelegate=function(t){return e._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),f=!0)):!0}},_mouseMove:function(t){if(this._mouseMoved){if(y.ui.ie&&(!document.documentMode||document.documentMode<9)&&!t.button)return this._mouseUp(t);if(!t.which)if(t.originalEvent.altKey||t.originalEvent.ctrlKey||t.originalEvent.metaKey||t.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(t)}return(t.which||t.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=!1!==this._mouseStart(this._mouseDownEvent,t),this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&y.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,f=!1,t.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),y.ui.plugin={add:function(t,e,i){var s,n=y.ui[t].prototype;for(s in i)n.plugins[s]=n.plugins[s]||[],n.plugins[s].push([e,i[s]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;n<o.length;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}};y.widget("ui.resizable",y.ui.mouse,{version:"1.13.2",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(t,e){if("hidden"===y(t).css("overflow"))return!1;var i=e&&"left"===e?"scrollLeft":"scrollTop",e=!1;if(0<t[i])return!0;try{t[i]=1,e=0<t[i],t[i]=0}catch(t){}return e},_create:function(){var t,e=this.options,i=this;this._addClass("ui-resizable"),y.extend(this,{_aspectRatio:!!e.aspectRatio,aspectRatio:e.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:e.helper||e.ghost||e.animate?e.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(y("<div class='ui-wrapper'></div>").css({overflow:"hidden",position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,t={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(t),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(t),this._proportionallyResize()),this._setupHandles(),e.autoHide&&y(this.element).on("mouseenter",function(){e.disabled||(i._removeClass("ui-resizable-autohide"),i._handles.show())}).on("mouseleave",function(){e.disabled||i.resizing||(i._addClass("ui-resizable-autohide"),i._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy(),this._addedHandles.remove();function t(t){y(t).removeData("resizable").removeData("ui-resizable").off(".resizable")}var e;return this.elementIsWrapper&&(t(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;case"aspectRatio":this._aspectRatio=!!e}},_setupHandles:function(){var t,e,i,s,n,o=this.options,h=this;if(this.handles=o.handles||(y(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=y(),this._addedHandles=y(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),i=this.handles.split(","),this.handles={},e=0;e<i.length;e++)s="ui-resizable-"+(t=String.prototype.trim.call(i[e])),n=y("<div>"),this._addClass(n,"ui-resizable-handle "+s),n.css({zIndex:o.zIndex}),this.handles[t]=".ui-resizable-"+t,this.element.children(this.handles[t]).length||(this.element.append(n),this._addedHandles=this._addedHandles.add(n));this._renderAxis=function(t){var e,i,s;for(e in t=t||this.element,this.handles)this.handles[e].constructor===String?this.handles[e]=this.element.children(this.handles[e]).first().show():(this.handles[e].jquery||this.handles[e].nodeType)&&(this.handles[e]=y(this.handles[e]),this._on(this.handles[e],{mousedown:h._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(i=y(this.handles[e],this.element),s=/sw|ne|nw|se|n|s/.test(e)?i.outerHeight():i.outerWidth(),i=["padding",/ne|nw|n/.test(e)?"Top":/se|sw|s/.test(e)?"Bottom":/^e$/.test(e)?"Right":"Left"].join(""),t.css(i,s),this._proportionallyResize()),this._handles=this._handles.add(this.handles[e])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){h.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),h.axis=n&&n[1]?n[1]:"se")}),o.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._addedHandles.remove()},_mouseCapture:function(t){var e,i,s=!1;for(e in this.handles)(i=y(this.handles[e])[0])!==t.target&&!y.contains(i,t.target)||(s=!0);return!this.options.disabled&&s},_mouseStart:function(t){var e,i,s=this.options,n=this.element;return this.resizing=!0,this._renderProxy(),e=this._num(this.helper.css("left")),i=this._num(this.helper.css("top")),s.containment&&(e+=y(s.containment).scrollLeft()||0,i+=y(s.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:e,top:i},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:n.width(),height:n.height()},this.originalSize=this._helper?{width:n.outerWidth(),height:n.outerHeight()}:{width:n.width(),height:n.height()},this.sizeDiff={width:n.outerWidth()-n.width(),height:n.outerHeight()-n.height()},this.originalPosition={left:e,top:i},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof s.aspectRatio?s.aspectRatio:this.originalSize.width/this.originalSize.height||1,s=y(".ui-resizable-"+this.axis).css("cursor"),y("body").css("cursor","auto"===s?this.axis+"-resize":s),this._addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var e=this.originalMousePosition,i=this.axis,s=t.pageX-e.left||0,e=t.pageY-e.top||0,i=this._change[i];return this._updatePrevProperties(),i&&(e=i.apply(this,[t,s,e]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(e=this._updateRatio(e,t)),e=this._respectSize(e,t),this._updateCache(e),this._propagate("resize",t),e=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),y.isEmptyObject(e)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges())),!1},_mouseStop:function(t){this.resizing=!1;var e,i,s,n=this.options,o=this;return this._helper&&(s=(e=(i=this._proportionallyResizeElements).length&&/textarea/i.test(i[0].nodeName))&&this._hasScroll(i[0],"left")?0:o.sizeDiff.height,i=e?0:o.sizeDiff.width,e={width:o.helper.width()-i,height:o.helper.height()-s},i=parseFloat(o.element.css("left"))+(o.position.left-o.originalPosition.left)||null,s=parseFloat(o.element.css("top"))+(o.position.top-o.originalPosition.top)||null,n.animate||this.element.css(y.extend(e,{top:s,left:i})),o.helper.height(o.size.height),o.helper.width(o.size.width),this._helper&&!n.animate&&this._proportionallyResize()),y("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s=this.options,n={minWidth:this._isNumber(s.minWidth)?s.minWidth:0,maxWidth:this._isNumber(s.maxWidth)?s.maxWidth:1/0,minHeight:this._isNumber(s.minHeight)?s.minHeight:0,maxHeight:this._isNumber(s.maxHeight)?s.maxHeight:1/0};(this._aspectRatio||t)&&(e=n.minHeight*this.aspectRatio,i=n.minWidth/this.aspectRatio,s=n.maxHeight*this.aspectRatio,t=n.maxWidth/this.aspectRatio,e>n.minWidth&&(n.minWidth=e),i>n.minHeight&&(n.minHeight=i),s<n.maxWidth&&(n.maxWidth=s),t<n.maxHeight&&(n.maxHeight=t)),this._vBoundaries=n},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidth<t.width,n=this._isNumber(t.height)&&e.maxHeight&&e.maxHeight<t.height,o=this._isNumber(t.width)&&e.minWidth&&e.minWidth>t.width,h=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,a=this.originalPosition.left+this.originalSize.width,r=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),i=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),h&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=a-e.minWidth),s&&l&&(t.left=a-e.maxWidth),h&&i&&(t.top=r-e.minHeight),n&&i&&(t.top=r-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];e<4;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;e<this._proportionallyResizeElements.length;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var t=this.element,e=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||y("<div></div>").css({overflow:"hidden"}),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++e.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize;return{left:this.originalPosition.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize;return{top:this.originalPosition.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(t,e,i){return y.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},sw:function(t,e,i){return y.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,e,i]))},ne:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},nw:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,e,i]))}},_propagate:function(t,e){y.ui.plugin.call(this,t,[e,this.ui()]),"resize"!==t&&this._trigger(t,e,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),y.ui.plugin.add("resizable","animate",{stop:function(e){var i=y(this).resizable("instance"),t=i.options,s=i._proportionallyResizeElements,n=s.length&&/textarea/i.test(s[0].nodeName),o=n&&i._hasScroll(s[0],"left")?0:i.sizeDiff.height,h=n?0:i.sizeDiff.width,n={width:i.size.width-h,height:i.size.height-o},h=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,o=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(y.extend(n,o&&h?{top:o,left:h}:{}),{duration:t.animateDuration,easing:t.animateEasing,step:function(){var t={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};s&&s.length&&y(s[0]).css({width:t.width,height:t.height}),i._updateCache(t),i._propagate("resize",e)}})}}),y.ui.plugin.add("resizable","containment",{start:function(){var i,s,n=y(this).resizable("instance"),t=n.options,e=n.element,o=t.containment,h=o instanceof y?o.get(0):/parent/.test(o)?e.parent().get(0):o;h&&(n.containerElement=y(h),/document/.test(o)||o===document?(n.containerOffset={left:0,top:0},n.containerPosition={left:0,top:0},n.parentData={element:y(document),left:0,top:0,width:y(document).width(),height:y(document).height()||document.body.parentNode.scrollHeight}):(i=y(h),s=[],y(["Top","Right","Left","Bottom"]).each(function(t,e){s[t]=n._num(i.css("padding"+e))}),n.containerOffset=i.offset(),n.containerPosition=i.position(),n.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},t=n.containerOffset,e=n.containerSize.height,o=n.containerSize.width,o=n._hasScroll(h,"left")?h.scrollWidth:o,e=n._hasScroll(h)?h.scrollHeight:e,n.parentData={element:h,left:t.left,top:t.top,width:o,height:e}))},resize:function(t){var e=y(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.position,o=e._aspectRatio||t.shiftKey,h={top:0,left:0},a=e.containerElement,t=!0;a[0]!==document&&/static/.test(a.css("position"))&&(h=s),n.left<(e._helper?s.left:0)&&(e.size.width=e.size.width+(e._helper?e.position.left-s.left:e.position.left-h.left),o&&(e.size.height=e.size.width/e.aspectRatio,t=!1),e.position.left=i.helper?s.left:0),n.top<(e._helper?s.top:0)&&(e.size.height=e.size.height+(e._helper?e.position.top-s.top:e.position.top),o&&(e.size.width=e.size.height*e.aspectRatio,t=!1),e.position.top=e._helper?s.top:0),i=e.containerElement.get(0)===e.element.parent().get(0),n=/relative|absolute/.test(e.containerElement.css("position")),i&&n?(e.offset.left=e.parentData.left+e.position.left,e.offset.top=e.parentData.top+e.position.top):(e.offset.left=e.element.offset().left,e.offset.top=e.element.offset().top),n=Math.abs(e.sizeDiff.width+(e._helper?e.offset.left-h.left:e.offset.left-s.left)),s=Math.abs(e.sizeDiff.height+(e._helper?e.offset.top-h.top:e.offset.top-s.top)),n+e.size.width>=e.parentData.width&&(e.size.width=e.parentData.width-n,o&&(e.size.height=e.size.width/e.aspectRatio,t=!1)),s+e.size.height>=e.parentData.height&&(e.size.height=e.parentData.height-s,o&&(e.size.width=e.size.height*e.aspectRatio,t=!1)),t||(e.position.left=e.prevPosition.left,e.position.top=e.prevPosition.top,e.size.width=e.prevSize.width,e.size.height=e.prevSize.height)},stop:function(){var t=y(this).resizable("instance"),e=t.options,i=t.containerOffset,s=t.containerPosition,n=t.containerElement,o=y(t.helper),h=o.offset(),a=o.outerWidth()-t.sizeDiff.width,o=o.outerHeight()-t.sizeDiff.height;t._helper&&!e.animate&&/relative/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o}),t._helper&&!e.animate&&/static/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o})}}),y.ui.plugin.add("resizable","alsoResize",{start:function(){var t=y(this).resizable("instance").options;y(t.alsoResize).each(function(){var t=y(this);t.data("ui-resizable-alsoresize",{width:parseFloat(t.width()),height:parseFloat(t.height()),left:parseFloat(t.css("left")),top:parseFloat(t.css("top"))})})},resize:function(t,i){var e=y(this).resizable("instance"),s=e.options,n=e.originalSize,o=e.originalPosition,h={height:e.size.height-n.height||0,width:e.size.width-n.width||0,top:e.position.top-o.top||0,left:e.position.left-o.left||0};y(s.alsoResize).each(function(){var t=y(this),s=y(this).data("ui-resizable-alsoresize"),n={},e=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];y.each(e,function(t,e){var i=(s[e]||0)+(h[e]||0);i&&0<=i&&(n[e]=i||null)}),t.css(n)})},stop:function(){y(this).removeData("ui-resizable-alsoresize")}}),y.ui.plugin.add("resizable","ghost",{start:function(){var t=y(this).resizable("instance"),e=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}),t._addClass(t.ghost,"ui-resizable-ghost"),!1!==y.uiBackCompat&&"string"==typeof t.options.ghost&&t.ghost.addClass(this.options.ghost),t.ghost.appendTo(t.helper)},resize:function(){var t=y(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=y(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),y.ui.plugin.add("resizable","grid",{resize:function(){var t,e=y(this).resizable("instance"),i=e.options,s=e.size,n=e.originalSize,o=e.originalPosition,h=e.axis,a="number"==typeof i.grid?[i.grid,i.grid]:i.grid,r=a[0]||1,l=a[1]||1,u=Math.round((s.width-n.width)/r)*r,p=Math.round((s.height-n.height)/l)*l,d=n.width+u,c=n.height+p,f=i.maxWidth&&i.maxWidth<d,g=i.maxHeight&&i.maxHeight<c,m=i.minWidth&&i.minWidth>d,s=i.minHeight&&i.minHeight>c;i.grid=a,m&&(d+=r),s&&(c+=l),f&&(d-=r),g&&(c-=l),/^(se|s|e)$/.test(h)?(e.size.width=d,e.size.height=c):/^(ne)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.top=o.top-p):/^(sw)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.left=o.left-u):((c-l<=0||d-r<=0)&&(t=e._getPaddingPlusBorderDimensions(this)),0<c-l?(e.size.height=c,e.position.top=o.top-p):(c=l-t.height,e.size.height=c,e.position.top=o.top+n.height-c),0<d-r?(e.size.width=d,e.position.left=o.left-u):(d=r-t.width,e.size.width=d,e.position.left=o.left+n.width-d))}});y.ui.resizable});/** + * Copyright (c) 2007 Ariel Flesler - aflesler â—‹ gmail • com | https://github.com/flesler + * Licensed under MIT + * @author Ariel Flesler + * @version 2.1.2 + */ +;(function(f){"use strict";"function"===typeof define&&define.amd?define(["jquery"],f):"undefined"!==typeof module&&module.exports?module.exports=f(require("jquery")):f(jQuery)})(function($){"use strict";function n(a){return!a.nodeName||-1!==$.inArray(a.nodeName.toLowerCase(),["iframe","#document","html","body"])}function h(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}var p=$.scrollTo=function(a,d,b){return $(window).scrollTo(a,d,b)};p.defaults={axis:"xy",duration:0,limit:!0};$.fn.scrollTo=function(a,d,b){"object"=== typeof d&&(b=d,d=0);"function"===typeof b&&(b={onAfter:b});"max"===a&&(a=9E9);b=$.extend({},p.defaults,b);d=d||b.duration;var u=b.queue&&1<b.axis.length;u&&(d/=2);b.offset=h(b.offset);b.over=h(b.over);return this.each(function(){function k(a){var k=$.extend({},b,{queue:!0,duration:d,complete:a&&function(){a.call(q,e,b)}});r.animate(f,k)}if(null!==a){var l=n(this),q=l?this.contentWindow||window:this,r=$(q),e=a,f={},t;switch(typeof e){case "number":case "string":if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(e)){e= h(e);break}e=l?$(e):$(e,q);case "object":if(e.length===0)return;if(e.is||e.style)t=(e=$(e)).offset()}var v=$.isFunction(b.offset)&&b.offset(q,e)||b.offset;$.each(b.axis.split(""),function(a,c){var d="x"===c?"Left":"Top",m=d.toLowerCase(),g="scroll"+d,h=r[g](),n=p.max(q,c);t?(f[g]=t[m]+(l?0:h-r.offset()[m]),b.margin&&(f[g]-=parseInt(e.css("margin"+d),10)||0,f[g]-=parseInt(e.css("border"+d+"Width"),10)||0),f[g]+=v[m]||0,b.over[m]&&(f[g]+=e["x"===c?"width":"height"]()*b.over[m])):(d=e[m],f[g]=d.slice&& "%"===d.slice(-1)?parseFloat(d)/100*n:d);b.limit&&/^\d+$/.test(f[g])&&(f[g]=0>=f[g]?0:Math.min(f[g],n));!a&&1<b.axis.length&&(h===f[g]?f={}:u&&(k(b.onAfterFirst),f={}))});k(b.onAfter)}})};p.max=function(a,d){var b="x"===d?"Width":"Height",h="scroll"+b;if(!n(a))return a[h]-$(a)[b.toLowerCase()]();var b="client"+b,k=a.ownerDocument||a.document,l=k.documentElement,k=k.body;return Math.max(l[h],k[h])-Math.min(l[b],k[b])};$.Tween.propHooks.scrollLeft=$.Tween.propHooks.scrollTop={get:function(a){return $(a.elem)[a.prop]()}, set:function(a){var d=this.get(a);if(a.options.interrupt&&a._last&&a._last!==d)return $(a.elem).stop();var b=Math.round(a.now);d!==b&&($(a.elem)[a.prop](b),a._last=this.get(a))}};return p}); +/*! + PowerTip v1.3.1 (2018-04-15) + https://stevenbenner.github.io/jquery-powertip/ + Copyright (c) 2018 Steven Benner (http://stevenbenner.com/). + Released under MIT license. + https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt +*/ +(function(root,factory){if(typeof define==="function"&&define.amd){define(["jquery"],factory)}else if(typeof module==="object"&&module.exports){module.exports=factory(require("jquery"))}else{factory(root.jQuery)}})(this,function($){var $document=$(document),$window=$(window),$body=$("body");var DATA_DISPLAYCONTROLLER="displayController",DATA_HASACTIVEHOVER="hasActiveHover",DATA_FORCEDOPEN="forcedOpen",DATA_HASMOUSEMOVE="hasMouseMove",DATA_MOUSEONTOTIP="mouseOnToPopup",DATA_ORIGINALTITLE="originalTitle",DATA_POWERTIP="powertip",DATA_POWERTIPJQ="powertipjq",DATA_POWERTIPTARGET="powertiptarget",EVENT_NAMESPACE=".powertip",RAD2DEG=180/Math.PI,MOUSE_EVENTS=["click","dblclick","mousedown","mouseup","mousemove","mouseover","mouseout","mouseenter","mouseleave","contextmenu"];var session={tooltips:null,isTipOpen:false,isFixedTipOpen:false,isClosing:false,tipOpenImminent:false,activeHover:null,currentX:0,currentY:0,previousX:0,previousY:0,desyncTimeout:null,closeDelayTimeout:null,mouseTrackingActive:false,delayInProgress:false,windowWidth:0,windowHeight:0,scrollTop:0,scrollLeft:0};var Collision={none:0,top:1,bottom:2,left:4,right:8};$.fn.powerTip=function(opts,arg){var targetElements=this,options,tipController;if(!targetElements.length){return targetElements}if($.type(opts)==="string"&&$.powerTip[opts]){return $.powerTip[opts].call(targetElements,targetElements,arg)}options=$.extend({},$.fn.powerTip.defaults,opts);tipController=new TooltipController(options);initTracking();targetElements.each(function elementSetup(){var $this=$(this),dataPowertip=$this.data(DATA_POWERTIP),dataElem=$this.data(DATA_POWERTIPJQ),dataTarget=$this.data(DATA_POWERTIPTARGET),title=$this.attr("title");if(!dataPowertip&&!dataTarget&&!dataElem&&title){$this.data(DATA_POWERTIP,title);$this.data(DATA_ORIGINALTITLE,title);$this.removeAttr("title")}$this.data(DATA_DISPLAYCONTROLLER,new DisplayController($this,options,tipController))});if(!options.manual){$.each(options.openEvents,function(idx,evt){if($.inArray(evt,options.closeEvents)>-1){targetElements.on(evt+EVENT_NAMESPACE,function elementToggle(event){$.powerTip.toggle(this,event)})}else{targetElements.on(evt+EVENT_NAMESPACE,function elementOpen(event){$.powerTip.show(this,event)})}});$.each(options.closeEvents,function(idx,evt){if($.inArray(evt,options.openEvents)<0){targetElements.on(evt+EVENT_NAMESPACE,function elementClose(event){$.powerTip.hide(this,!isMouseEvent(event))})}});targetElements.on("keydown"+EVENT_NAMESPACE,function elementKeyDown(event){if(event.keyCode===27){$.powerTip.hide(this,true)}})}return targetElements};$.fn.powerTip.defaults={fadeInTime:200,fadeOutTime:100,followMouse:false,popupId:"powerTip",popupClass:null,intentSensitivity:7,intentPollInterval:100,closeDelay:100,placement:"n",smartPlacement:false,offset:10,mouseOnToPopup:false,manual:false,openEvents:["mouseenter","focus"],closeEvents:["mouseleave","blur"]};$.fn.powerTip.smartPlacementLists={n:["n","ne","nw","s"],e:["e","ne","se","w","nw","sw","n","s","e"],s:["s","se","sw","n"],w:["w","nw","sw","e","ne","se","n","s","w"],nw:["nw","w","sw","n","s","se","nw"],ne:["ne","e","se","n","s","sw","ne"],sw:["sw","w","nw","s","n","ne","sw"],se:["se","e","ne","s","n","nw","se"],"nw-alt":["nw-alt","n","ne-alt","sw-alt","s","se-alt","w","e"],"ne-alt":["ne-alt","n","nw-alt","se-alt","s","sw-alt","e","w"],"sw-alt":["sw-alt","s","se-alt","nw-alt","n","ne-alt","w","e"],"se-alt":["se-alt","s","sw-alt","ne-alt","n","nw-alt","e","w"]};$.powerTip={show:function apiShowTip(element,event){if(isMouseEvent(event)){trackMouse(event);session.previousX=event.pageX;session.previousY=event.pageY;$(element).data(DATA_DISPLAYCONTROLLER).show()}else{$(element).first().data(DATA_DISPLAYCONTROLLER).show(true,true)}return element},reposition:function apiResetPosition(element){$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();return element},hide:function apiCloseTip(element,immediate){var displayController;immediate=element?immediate:true;if(element){displayController=$(element).first().data(DATA_DISPLAYCONTROLLER)}else if(session.activeHover){displayController=session.activeHover.data(DATA_DISPLAYCONTROLLER)}if(displayController){displayController.hide(immediate)}return element},toggle:function apiToggle(element,event){if(session.activeHover&&session.activeHover.is(element)){$.powerTip.hide(element,!isMouseEvent(event))}else{$.powerTip.show(element,event)}return element}};$.powerTip.showTip=$.powerTip.show;$.powerTip.closeTip=$.powerTip.hide;function CSSCoordinates(){var me=this;me.top="auto";me.left="auto";me.right="auto";me.bottom="auto";me.set=function(property,value){if($.isNumeric(value)){me[property]=Math.round(value)}}}function DisplayController(element,options,tipController){var hoverTimer=null,myCloseDelay=null;function openTooltip(immediate,forceOpen){cancelTimer();if(!element.data(DATA_HASACTIVEHOVER)){if(!immediate){session.tipOpenImminent=true;hoverTimer=setTimeout(function intentDelay(){hoverTimer=null;checkForIntent()},options.intentPollInterval)}else{if(forceOpen){element.data(DATA_FORCEDOPEN,true)}closeAnyDelayed();tipController.showTip(element)}}else{cancelClose()}}function closeTooltip(disableDelay){if(myCloseDelay){myCloseDelay=session.closeDelayTimeout=clearTimeout(myCloseDelay);session.delayInProgress=false}cancelTimer();session.tipOpenImminent=false;if(element.data(DATA_HASACTIVEHOVER)){element.data(DATA_FORCEDOPEN,false);if(!disableDelay){session.delayInProgress=true;session.closeDelayTimeout=setTimeout(function closeDelay(){session.closeDelayTimeout=null;tipController.hideTip(element);session.delayInProgress=false;myCloseDelay=null},options.closeDelay);myCloseDelay=session.closeDelayTimeout}else{tipController.hideTip(element)}}}function checkForIntent(){var xDifference=Math.abs(session.previousX-session.currentX),yDifference=Math.abs(session.previousY-session.currentY),totalDifference=xDifference+yDifference;if(totalDifference<options.intentSensitivity){cancelClose();closeAnyDelayed();tipController.showTip(element)}else{session.previousX=session.currentX;session.previousY=session.currentY;openTooltip()}}function cancelTimer(stopClose){hoverTimer=clearTimeout(hoverTimer);if(session.closeDelayTimeout&&myCloseDelay===session.closeDelayTimeout||stopClose){cancelClose()}}function cancelClose(){session.closeDelayTimeout=clearTimeout(session.closeDelayTimeout);session.delayInProgress=false}function closeAnyDelayed(){if(session.delayInProgress&&session.activeHover&&!session.activeHover.is(element)){session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true)}}function repositionTooltip(){tipController.resetPosition(element)}this.show=openTooltip;this.hide=closeTooltip;this.cancel=cancelTimer;this.resetPosition=repositionTooltip}function PlacementCalculator(){function computePlacementCoords(element,placement,tipWidth,tipHeight,offset){var placementBase=placement.split("-")[0],coords=new CSSCoordinates,position;if(isSvgElement(element)){position=getSvgPlacement(element,placementBase)}else{position=getHtmlPlacement(element,placementBase)}switch(placement){case"n":coords.set("left",position.left-tipWidth/2);coords.set("bottom",session.windowHeight-position.top+offset);break;case"e":coords.set("left",position.left+offset);coords.set("top",position.top-tipHeight/2);break;case"s":coords.set("left",position.left-tipWidth/2);coords.set("top",position.top+offset);break;case"w":coords.set("top",position.top-tipHeight/2);coords.set("right",session.windowWidth-position.left+offset);break;case"nw":coords.set("bottom",session.windowHeight-position.top+offset);coords.set("right",session.windowWidth-position.left-20);break;case"nw-alt":coords.set("left",position.left);coords.set("bottom",session.windowHeight-position.top+offset);break;case"ne":coords.set("left",position.left-20);coords.set("bottom",session.windowHeight-position.top+offset);break;case"ne-alt":coords.set("bottom",session.windowHeight-position.top+offset);coords.set("right",session.windowWidth-position.left);break;case"sw":coords.set("top",position.top+offset);coords.set("right",session.windowWidth-position.left-20);break;case"sw-alt":coords.set("left",position.left);coords.set("top",position.top+offset);break;case"se":coords.set("left",position.left-20);coords.set("top",position.top+offset);break;case"se-alt":coords.set("top",position.top+offset);coords.set("right",session.windowWidth-position.left);break}return coords}function getHtmlPlacement(element,placement){var objectOffset=element.offset(),objectWidth=element.outerWidth(),objectHeight=element.outerHeight(),left,top;switch(placement){case"n":left=objectOffset.left+objectWidth/2;top=objectOffset.top;break;case"e":left=objectOffset.left+objectWidth;top=objectOffset.top+objectHeight/2;break;case"s":left=objectOffset.left+objectWidth/2;top=objectOffset.top+objectHeight;break;case"w":left=objectOffset.left;top=objectOffset.top+objectHeight/2;break;case"nw":left=objectOffset.left;top=objectOffset.top;break;case"ne":left=objectOffset.left+objectWidth;top=objectOffset.top;break;case"sw":left=objectOffset.left;top=objectOffset.top+objectHeight;break;case"se":left=objectOffset.left+objectWidth;top=objectOffset.top+objectHeight;break}return{top:top,left:left}}function getSvgPlacement(element,placement){var svgElement=element.closest("svg")[0],domElement=element[0],point=svgElement.createSVGPoint(),boundingBox=domElement.getBBox(),matrix=domElement.getScreenCTM(),halfWidth=boundingBox.width/2,halfHeight=boundingBox.height/2,placements=[],placementKeys=["nw","n","ne","e","se","s","sw","w"],coords,rotation,steps,x;function pushPlacement(){placements.push(point.matrixTransform(matrix))}point.x=boundingBox.x;point.y=boundingBox.y;pushPlacement();point.x+=halfWidth;pushPlacement();point.x+=halfWidth;pushPlacement();point.y+=halfHeight;pushPlacement();point.y+=halfHeight;pushPlacement();point.x-=halfWidth;pushPlacement();point.x-=halfWidth;pushPlacement();point.y-=halfHeight;pushPlacement();if(placements[0].y!==placements[1].y||placements[0].x!==placements[7].x){rotation=Math.atan2(matrix.b,matrix.a)*RAD2DEG;steps=Math.ceil((rotation%360-22.5)/45);if(steps<1){steps+=8}while(steps--){placementKeys.push(placementKeys.shift())}}for(x=0;x<placements.length;x++){if(placementKeys[x]===placement){coords=placements[x];break}}return{top:coords.y+session.scrollTop,left:coords.x+session.scrollLeft}}this.compute=computePlacementCoords}function TooltipController(options){var placementCalculator=new PlacementCalculator,tipElement=$("#"+options.popupId);if(tipElement.length===0){tipElement=$("<div/>",{id:options.popupId});if($body.length===0){$body=$("body")}$body.append(tipElement);session.tooltips=session.tooltips?session.tooltips.add(tipElement):tipElement}if(options.followMouse){if(!tipElement.data(DATA_HASMOUSEMOVE)){$document.on("mousemove"+EVENT_NAMESPACE,positionTipOnCursor);$window.on("scroll"+EVENT_NAMESPACE,positionTipOnCursor);tipElement.data(DATA_HASMOUSEMOVE,true)}}function beginShowTip(element){element.data(DATA_HASACTIVEHOVER,true);tipElement.queue(function queueTipInit(next){showTip(element);next()})}function showTip(element){var tipContent;if(!element.data(DATA_HASACTIVEHOVER)){return}if(session.isTipOpen){if(!session.isClosing){hideTip(session.activeHover)}tipElement.delay(100).queue(function queueTipAgain(next){showTip(element);next()});return}element.trigger("powerTipPreRender");tipContent=getTooltipContent(element);if(tipContent){tipElement.empty().append(tipContent)}else{return}element.trigger("powerTipRender");session.activeHover=element;session.isTipOpen=true;tipElement.data(DATA_MOUSEONTOTIP,options.mouseOnToPopup);tipElement.addClass(options.popupClass);if(!options.followMouse||element.data(DATA_FORCEDOPEN)){positionTipOnElement(element);session.isFixedTipOpen=true}else{positionTipOnCursor()}if(!element.data(DATA_FORCEDOPEN)&&!options.followMouse){$document.on("click"+EVENT_NAMESPACE,function documentClick(event){var target=event.target;if(target!==element[0]){if(options.mouseOnToPopup){if(target!==tipElement[0]&&!$.contains(tipElement[0],target)){$.powerTip.hide()}}else{$.powerTip.hide()}}})}if(options.mouseOnToPopup&&!options.manual){tipElement.on("mouseenter"+EVENT_NAMESPACE,function tipMouseEnter(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel()}});tipElement.on("mouseleave"+EVENT_NAMESPACE,function tipMouseLeave(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).hide()}})}tipElement.fadeIn(options.fadeInTime,function fadeInCallback(){if(!session.desyncTimeout){session.desyncTimeout=setInterval(closeDesyncedTip,500)}element.trigger("powerTipOpen")})}function hideTip(element){session.isClosing=true;session.isTipOpen=false;session.desyncTimeout=clearInterval(session.desyncTimeout);element.data(DATA_HASACTIVEHOVER,false);element.data(DATA_FORCEDOPEN,false);$document.off("click"+EVENT_NAMESPACE);tipElement.off(EVENT_NAMESPACE);tipElement.fadeOut(options.fadeOutTime,function fadeOutCallback(){var coords=new CSSCoordinates;session.activeHover=null;session.isClosing=false;session.isFixedTipOpen=false;tipElement.removeClass();coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset);tipElement.css(coords);element.trigger("powerTipClose")})}function positionTipOnCursor(){var tipWidth,tipHeight,coords,collisions,collisionCount;if(!session.isFixedTipOpen&&(session.isTipOpen||session.tipOpenImminent&&tipElement.data(DATA_HASMOUSEMOVE))){tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=new CSSCoordinates;coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset);collisions=getViewportCollisions(coords,tipWidth,tipHeight);if(collisions!==Collision.none){collisionCount=countFlags(collisions);if(collisionCount===1){if(collisions===Collision.right){coords.set("left",session.scrollLeft+session.windowWidth-tipWidth)}else if(collisions===Collision.bottom){coords.set("top",session.scrollTop+session.windowHeight-tipHeight)}}else{coords.set("left",session.currentX-tipWidth-options.offset);coords.set("top",session.currentY-tipHeight-options.offset)}}tipElement.css(coords)}}function positionTipOnElement(element){var priorityList,finalPlacement;if(options.smartPlacement||options.followMouse&&element.data(DATA_FORCEDOPEN)){priorityList=$.fn.powerTip.smartPlacementLists[options.placement];$.each(priorityList,function(idx,pos){var collisions=getViewportCollisions(placeTooltip(element,pos),tipElement.outerWidth(),tipElement.outerHeight());finalPlacement=pos;return collisions!==Collision.none})}else{placeTooltip(element,options.placement);finalPlacement=options.placement}tipElement.removeClass("w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt");tipElement.addClass(finalPlacement)}function placeTooltip(element,placement){var iterationCount=0,tipWidth,tipHeight,coords=new CSSCoordinates;coords.set("top",0);coords.set("left",0);tipElement.css(coords);do{tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=placementCalculator.compute(element,placement,tipWidth,tipHeight,options.offset);tipElement.css(coords)}while(++iterationCount<=5&&(tipWidth!==tipElement.outerWidth()||tipHeight!==tipElement.outerHeight()));return coords}function closeDesyncedTip(){var isDesynced=false,hasDesyncableCloseEvent=$.grep(["mouseleave","mouseout","blur","focusout"],function(eventType){return $.inArray(eventType,options.closeEvents)!==-1}).length>0;if(session.isTipOpen&&!session.isClosing&&!session.delayInProgress&&hasDesyncableCloseEvent){if(session.activeHover.data(DATA_HASACTIVEHOVER)===false||session.activeHover.is(":disabled")){isDesynced=true}else if(!isMouseOver(session.activeHover)&&!session.activeHover.is(":focus")&&!session.activeHover.data(DATA_FORCEDOPEN)){if(tipElement.data(DATA_MOUSEONTOTIP)){if(!isMouseOver(tipElement)){isDesynced=true}}else{isDesynced=true}}if(isDesynced){hideTip(session.activeHover)}}}this.showTip=beginShowTip;this.hideTip=hideTip;this.resetPosition=positionTipOnElement}function isSvgElement(element){return Boolean(window.SVGElement&&element[0]instanceof SVGElement)}function isMouseEvent(event){return Boolean(event&&$.inArray(event.type,MOUSE_EVENTS)>-1&&typeof event.pageX==="number")}function initTracking(){if(!session.mouseTrackingActive){session.mouseTrackingActive=true;getViewportDimensions();$(getViewportDimensions);$document.on("mousemove"+EVENT_NAMESPACE,trackMouse);$window.on("resize"+EVENT_NAMESPACE,trackResize);$window.on("scroll"+EVENT_NAMESPACE,trackScroll)}}function getViewportDimensions(){session.scrollLeft=$window.scrollLeft();session.scrollTop=$window.scrollTop();session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackResize(){session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackScroll(){var x=$window.scrollLeft(),y=$window.scrollTop();if(x!==session.scrollLeft){session.currentX+=x-session.scrollLeft;session.scrollLeft=x}if(y!==session.scrollTop){session.currentY+=y-session.scrollTop;session.scrollTop=y}}function trackMouse(event){session.currentX=event.pageX;session.currentY=event.pageY}function isMouseOver(element){var elementPosition=element.offset(),elementBox=element[0].getBoundingClientRect(),elementWidth=elementBox.right-elementBox.left,elementHeight=elementBox.bottom-elementBox.top;return session.currentX>=elementPosition.left&&session.currentX<=elementPosition.left+elementWidth&&session.currentY>=elementPosition.top&&session.currentY<=elementPosition.top+elementHeight}function getTooltipContent(element){var tipText=element.data(DATA_POWERTIP),tipObject=element.data(DATA_POWERTIPJQ),tipTarget=element.data(DATA_POWERTIPTARGET),targetElement,content;if(tipText){if($.isFunction(tipText)){tipText=tipText.call(element[0])}content=tipText}else if(tipObject){if($.isFunction(tipObject)){tipObject=tipObject.call(element[0])}if(tipObject.length>0){content=tipObject.clone(true,true)}}else if(tipTarget){targetElement=$("#"+tipTarget);if(targetElement.length>0){content=targetElement.html()}}return content}function getViewportCollisions(coords,elementWidth,elementHeight){var viewportTop=session.scrollTop,viewportLeft=session.scrollLeft,viewportBottom=viewportTop+session.windowHeight,viewportRight=viewportLeft+session.windowWidth,collisions=Collision.none;if(coords.top<viewportTop||Math.abs(coords.bottom-session.windowHeight)-elementHeight<viewportTop){collisions|=Collision.top}if(coords.top+elementHeight>viewportBottom||Math.abs(coords.bottom-session.windowHeight)>viewportBottom){collisions|=Collision.bottom}if(coords.left<viewportLeft||coords.right+elementWidth>viewportRight){collisions|=Collision.left}if(coords.left+elementWidth>viewportRight||coords.right<viewportLeft){collisions|=Collision.right}return collisions}function countFlags(value){var count=0;while(value){value&=value-1;count++}return count}return $.powerTip});/*! + * jQuery UI Touch Punch 0.2.3 + * + * Copyright 2011–2014, Dave Furfero + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Depends: + * jquery.ui.widget.js + * jquery.ui.mouse.js + */ +!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);/*! SmartMenus jQuery Plugin - v1.1.0 - September 17, 2017 + * http://www.smartmenus.org/ + * Copyright Vasil Dinkov, Vadikom Web Ltd. http://vadikom.com; Licensed MIT */(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof module&&"object"==typeof module.exports?module.exports=t(require("jquery")):t(jQuery)})(function($){function initMouseDetection(t){var e=".smartmenus_mouse";if(mouseDetectionEnabled||t)mouseDetectionEnabled&&t&&($(document).off(e),mouseDetectionEnabled=!1);else{var i=!0,s=null,o={mousemove:function(t){var e={x:t.pageX,y:t.pageY,timeStamp:(new Date).getTime()};if(s){var o=Math.abs(s.x-e.x),a=Math.abs(s.y-e.y);if((o>0||a>0)&&2>=o&&2>=a&&300>=e.timeStamp-s.timeStamp&&(mouse=!0,i)){var n=$(t.target).closest("a");n.is("a")&&$.each(menuTrees,function(){return $.contains(this.$root[0],n[0])?(this.itemEnter({currentTarget:n[0]}),!1):void 0}),i=!1}}s=e}};o[touchEvents?"touchstart":"pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut"]=function(t){isTouchEvent(t.originalEvent)&&(mouse=!1)},$(document).on(getEventsNS(o,e)),mouseDetectionEnabled=!0}}function isTouchEvent(t){return!/^(4|mouse)$/.test(t.pointerType)}function getEventsNS(t,e){e||(e="");var i={};for(var s in t)i[s.split(" ").join(e+" ")+e]=t[s];return i}var menuTrees=[],mouse=!1,touchEvents="ontouchstart"in window,mouseDetectionEnabled=!1,requestAnimationFrame=window.requestAnimationFrame||function(t){return setTimeout(t,1e3/60)},cancelAnimationFrame=window.cancelAnimationFrame||function(t){clearTimeout(t)},canAnimate=!!$.fn.animate;return $.SmartMenus=function(t,e){this.$root=$(t),this.opts=e,this.rootId="",this.accessIdPrefix="",this.$subArrow=null,this.activatedItems=[],this.visibleSubMenus=[],this.showTimeout=0,this.hideTimeout=0,this.scrollTimeout=0,this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.idInc=0,this.$firstLink=null,this.$firstSub=null,this.disabled=!1,this.$disableOverlay=null,this.$touchScrollingSub=null,this.cssTransforms3d="perspective"in t.style||"webkitPerspective"in t.style,this.wasCollapsible=!1,this.init()},$.extend($.SmartMenus,{hideAll:function(){$.each(menuTrees,function(){this.menuHideAll()})},destroy:function(){for(;menuTrees.length;)menuTrees[0].destroy();initMouseDetection(!0)},prototype:{init:function(t){var e=this;if(!t){menuTrees.push(this),this.rootId=((new Date).getTime()+Math.random()+"").replace(/\D/g,""),this.accessIdPrefix="sm-"+this.rootId+"-",this.$root.hasClass("sm-rtl")&&(this.opts.rightToLeftSubMenus=!0);var i=".smartmenus";this.$root.data("smartmenus",this).attr("data-smartmenus-id",this.rootId).dataSM("level",1).on(getEventsNS({"mouseover focusin":$.proxy(this.rootOver,this),"mouseout focusout":$.proxy(this.rootOut,this),keydown:$.proxy(this.rootKeyDown,this)},i)).on(getEventsNS({mouseenter:$.proxy(this.itemEnter,this),mouseleave:$.proxy(this.itemLeave,this),mousedown:$.proxy(this.itemDown,this),focus:$.proxy(this.itemFocus,this),blur:$.proxy(this.itemBlur,this),click:$.proxy(this.itemClick,this)},i),"a"),i+=this.rootId,this.opts.hideOnClick&&$(document).on(getEventsNS({touchstart:$.proxy(this.docTouchStart,this),touchmove:$.proxy(this.docTouchMove,this),touchend:$.proxy(this.docTouchEnd,this),click:$.proxy(this.docClick,this)},i)),$(window).on(getEventsNS({"resize orientationchange":$.proxy(this.winResize,this)},i)),this.opts.subIndicators&&(this.$subArrow=$("<span/>").addClass("sub-arrow"),this.opts.subIndicatorsText&&this.$subArrow.html(this.opts.subIndicatorsText)),initMouseDetection()}if(this.$firstSub=this.$root.find("ul").each(function(){e.menuInit($(this))}).eq(0),this.$firstLink=this.$root.find("a").eq(0),this.opts.markCurrentItem){var s=/(index|default)\.[^#\?\/]*/i,o=/#.*/,a=window.location.href.replace(s,""),n=a.replace(o,"");this.$root.find("a").each(function(){var t=this.href.replace(s,""),i=$(this);(t==a||t==n)&&(i.addClass("current"),e.opts.markCurrentTree&&i.parentsUntil("[data-smartmenus-id]","ul").each(function(){$(this).dataSM("parent-a").addClass("current")}))})}this.wasCollapsible=this.isCollapsible()},destroy:function(t){if(!t){var e=".smartmenus";this.$root.removeData("smartmenus").removeAttr("data-smartmenus-id").removeDataSM("level").off(e),e+=this.rootId,$(document).off(e),$(window).off(e),this.opts.subIndicators&&(this.$subArrow=null)}this.menuHideAll();var i=this;this.$root.find("ul").each(function(){var t=$(this);t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.dataSM("shown-before")&&((i.opts.subMenusMinWidth||i.opts.subMenusMaxWidth)&&t.css({width:"",minWidth:"",maxWidth:""}).removeClass("sm-nowrap"),t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.css({zIndex:"",top:"",left:"",marginLeft:"",marginTop:"",display:""})),0==(t.attr("id")||"").indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeDataSM("in-mega").removeDataSM("shown-before").removeDataSM("scroll-arrows").removeDataSM("parent-a").removeDataSM("level").removeDataSM("beforefirstshowfired").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeAttr("aria-expanded"),this.$root.find("a.has-submenu").each(function(){var t=$(this);0==t.attr("id").indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeClass("has-submenu").removeDataSM("sub").removeAttr("aria-haspopup").removeAttr("aria-controls").removeAttr("aria-expanded").closest("li").removeDataSM("sub"),this.opts.subIndicators&&this.$root.find("span.sub-arrow").remove(),this.opts.markCurrentItem&&this.$root.find("a.current").removeClass("current"),t||(this.$root=null,this.$firstLink=null,this.$firstSub=null,this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),menuTrees.splice($.inArray(this,menuTrees),1))},disable:function(t){if(!this.disabled){if(this.menuHideAll(),!t&&!this.opts.isPopup&&this.$root.is(":visible")){var e=this.$root.offset();this.$disableOverlay=$('<div class="sm-jquery-disable-overlay"/>').css({position:"absolute",top:e.top,left:e.left,width:this.$root.outerWidth(),height:this.$root.outerHeight(),zIndex:this.getStartZIndex(!0),opacity:0}).appendTo(document.body)}this.disabled=!0}},docClick:function(t){return this.$touchScrollingSub?(this.$touchScrollingSub=null,void 0):((this.visibleSubMenus.length&&!$.contains(this.$root[0],t.target)||$(t.target).closest("a").length)&&this.menuHideAll(),void 0)},docTouchEnd:function(){if(this.lastTouch){if(!(!this.visibleSubMenus.length||void 0!==this.lastTouch.x2&&this.lastTouch.x1!=this.lastTouch.x2||void 0!==this.lastTouch.y2&&this.lastTouch.y1!=this.lastTouch.y2||this.lastTouch.target&&$.contains(this.$root[0],this.lastTouch.target))){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var t=this;this.hideTimeout=setTimeout(function(){t.menuHideAll()},350)}this.lastTouch=null}},docTouchMove:function(t){if(this.lastTouch){var e=t.originalEvent.touches[0];this.lastTouch.x2=e.pageX,this.lastTouch.y2=e.pageY}},docTouchStart:function(t){var e=t.originalEvent.touches[0];this.lastTouch={x1:e.pageX,y1:e.pageY,target:e.target}},enable:function(){this.disabled&&(this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),this.disabled=!1)},getClosestMenu:function(t){for(var e=$(t).closest("ul");e.dataSM("in-mega");)e=e.parent().closest("ul");return e[0]||null},getHeight:function(t){return this.getOffset(t,!0)},getOffset:function(t,e){var i;"none"==t.css("display")&&(i={position:t[0].style.position,visibility:t[0].style.visibility},t.css({position:"absolute",visibility:"hidden"}).show());var s=t[0].getBoundingClientRect&&t[0].getBoundingClientRect(),o=s&&(e?s.height||s.bottom-s.top:s.width||s.right-s.left);return o||0===o||(o=e?t[0].offsetHeight:t[0].offsetWidth),i&&t.hide().css(i),o},getStartZIndex:function(t){var e=parseInt(this[t?"$root":"$firstSub"].css("z-index"));return!t&&isNaN(e)&&(e=parseInt(this.$root.css("z-index"))),isNaN(e)?1:e},getTouchPoint:function(t){return t.touches&&t.touches[0]||t.changedTouches&&t.changedTouches[0]||t},getViewport:function(t){var e=t?"Height":"Width",i=document.documentElement["client"+e],s=window["inner"+e];return s&&(i=Math.min(i,s)),i},getViewportHeight:function(){return this.getViewport(!0)},getViewportWidth:function(){return this.getViewport()},getWidth:function(t){return this.getOffset(t)},handleEvents:function(){return!this.disabled&&this.isCSSOn()},handleItemEvents:function(t){return this.handleEvents()&&!this.isLinkInMegaMenu(t)},isCollapsible:function(){return"static"==this.$firstSub.css("position")},isCSSOn:function(){return"inline"!=this.$firstLink.css("display")},isFixed:function(){var t="fixed"==this.$root.css("position");return t||this.$root.parentsUntil("body").each(function(){return"fixed"==$(this).css("position")?(t=!0,!1):void 0}),t},isLinkInMegaMenu:function(t){return $(this.getClosestMenu(t[0])).hasClass("mega-menu")},isTouchMode:function(){return!mouse||this.opts.noMouseOver||this.isCollapsible()},itemActivate:function(t,e){var i=t.closest("ul"),s=i.dataSM("level");if(s>1&&(!this.activatedItems[s-2]||this.activatedItems[s-2][0]!=i.dataSM("parent-a")[0])){var o=this;$(i.parentsUntil("[data-smartmenus-id]","ul").get().reverse()).add(i).each(function(){o.itemActivate($(this).dataSM("parent-a"))})}if((!this.isCollapsible()||e)&&this.menuHideSubMenus(this.activatedItems[s-1]&&this.activatedItems[s-1][0]==t[0]?s:s-1),this.activatedItems[s-1]=t,this.$root.triggerHandler("activate.smapi",t[0])!==!1){var a=t.dataSM("sub");a&&(this.isTouchMode()||!this.opts.showOnClick||this.clickActivated)&&this.menuShow(a)}},itemBlur:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&this.$root.triggerHandler("blur.smapi",e[0])},itemClick:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(this.$touchScrollingSub&&this.$touchScrollingSub[0]==e.closest("ul")[0])return this.$touchScrollingSub=null,t.stopPropagation(),!1;if(this.$root.triggerHandler("click.smapi",e[0])===!1)return!1;var i=$(t.target).is(".sub-arrow"),s=e.dataSM("sub"),o=s?2==s.dataSM("level"):!1,a=this.isCollapsible(),n=/toggle$/.test(this.opts.collapsibleBehavior),r=/link$/.test(this.opts.collapsibleBehavior),h=/^accordion/.test(this.opts.collapsibleBehavior);if(s&&!s.is(":visible")){if((!r||!a||i)&&(this.opts.showOnClick&&o&&(this.clickActivated=!0),this.itemActivate(e,h),s.is(":visible")))return this.focusActivated=!0,!1}else if(a&&(n||i))return this.itemActivate(e,h),this.menuHide(s),n&&(this.focusActivated=!1),!1;return this.opts.showOnClick&&o||e.hasClass("disabled")||this.$root.triggerHandler("select.smapi",e[0])===!1?!1:void 0}},itemDown:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&e.dataSM("mousedown",!0)},itemEnter:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(!this.isTouchMode()){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);var i=this;this.showTimeout=setTimeout(function(){i.itemActivate(e)},this.opts.showOnClick&&1==e.closest("ul").dataSM("level")?1:this.opts.showTimeout)}this.$root.triggerHandler("mouseenter.smapi",e[0])}},itemFocus:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(!this.focusActivated||this.isTouchMode()&&e.dataSM("mousedown")||this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0]==e[0]||this.itemActivate(e,!0),this.$root.triggerHandler("focus.smapi",e[0]))},itemLeave:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(this.isTouchMode()||(e[0].blur(),this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0)),e.removeDataSM("mousedown"),this.$root.triggerHandler("mouseleave.smapi",e[0]))},menuHide:function(t){if(this.$root.triggerHandler("beforehide.smapi",t[0])!==!1&&(canAnimate&&t.stop(!0,!0),"none"!=t.css("display"))){var e=function(){t.css("z-index","")};this.isCollapsible()?canAnimate&&this.opts.collapsibleHideFunction?this.opts.collapsibleHideFunction.call(this,t,e):t.hide(this.opts.collapsibleHideDuration,e):canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,t,e):t.hide(this.opts.hideDuration,e),t.dataSM("scroll")&&(this.menuScrollStop(t),t.css({"touch-action":"","-ms-touch-action":"","-webkit-transform":"",transform:""}).off(".smartmenus_scroll").removeDataSM("scroll").dataSM("scroll-arrows").hide()),t.dataSM("parent-a").removeClass("highlighted").attr("aria-expanded","false"),t.attr({"aria-expanded":"false","aria-hidden":"true"});var i=t.dataSM("level");this.activatedItems.splice(i-1,1),this.visibleSubMenus.splice($.inArray(t,this.visibleSubMenus),1),this.$root.triggerHandler("hide.smapi",t[0])}},menuHideAll:function(){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);for(var t=this.opts.isPopup?1:0,e=this.visibleSubMenus.length-1;e>=t;e--)this.menuHide(this.visibleSubMenus[e]);this.opts.isPopup&&(canAnimate&&this.$root.stop(!0,!0),this.$root.is(":visible")&&(canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,this.$root):this.$root.hide(this.opts.hideDuration))),this.activatedItems=[],this.visibleSubMenus=[],this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.$root.triggerHandler("hideAll.smapi")},menuHideSubMenus:function(t){for(var e=this.activatedItems.length-1;e>=t;e--){var i=this.activatedItems[e].dataSM("sub");i&&this.menuHide(i)}},menuInit:function(t){if(!t.dataSM("in-mega")){t.hasClass("mega-menu")&&t.find("ul").dataSM("in-mega",!0);for(var e=2,i=t[0];(i=i.parentNode.parentNode)!=this.$root[0];)e++;var s=t.prevAll("a").eq(-1);s.length||(s=t.prevAll().find("a").eq(-1)),s.addClass("has-submenu").dataSM("sub",t),t.dataSM("parent-a",s).dataSM("level",e).parent().dataSM("sub",t);var o=s.attr("id")||this.accessIdPrefix+ ++this.idInc,a=t.attr("id")||this.accessIdPrefix+ ++this.idInc;s.attr({id:o,"aria-haspopup":"true","aria-controls":a,"aria-expanded":"false"}),t.attr({id:a,role:"group","aria-hidden":"true","aria-labelledby":o,"aria-expanded":"false"}),this.opts.subIndicators&&s[this.opts.subIndicatorsPos](this.$subArrow.clone())}},menuPosition:function(t){var e,i,s=t.dataSM("parent-a"),o=s.closest("li"),a=o.parent(),n=t.dataSM("level"),r=this.getWidth(t),h=this.getHeight(t),u=s.offset(),l=u.left,c=u.top,d=this.getWidth(s),m=this.getHeight(s),p=$(window),f=p.scrollLeft(),v=p.scrollTop(),b=this.getViewportWidth(),S=this.getViewportHeight(),g=a.parent().is("[data-sm-horizontal-sub]")||2==n&&!a.hasClass("sm-vertical"),M=this.opts.rightToLeftSubMenus&&!o.is("[data-sm-reverse]")||!this.opts.rightToLeftSubMenus&&o.is("[data-sm-reverse]"),w=2==n?this.opts.mainMenuSubOffsetX:this.opts.subMenusSubOffsetX,T=2==n?this.opts.mainMenuSubOffsetY:this.opts.subMenusSubOffsetY;if(g?(e=M?d-r-w:w,i=this.opts.bottomToTopSubMenus?-h-T:m+T):(e=M?w-r:d-w,i=this.opts.bottomToTopSubMenus?m-T-h:T),this.opts.keepInViewport){var y=l+e,I=c+i;if(M&&f>y?e=g?f-y+e:d-w:!M&&y+r>f+b&&(e=g?f+b-r-y+e:w-r),g||(S>h&&I+h>v+S?i+=v+S-h-I:(h>=S||v>I)&&(i+=v-I)),g&&(I+h>v+S+.49||v>I)||!g&&h>S+.49){var x=this;t.dataSM("scroll-arrows")||t.dataSM("scroll-arrows",$([$('<span class="scroll-up"><span class="scroll-up-arrow"></span></span>')[0],$('<span class="scroll-down"><span class="scroll-down-arrow"></span></span>')[0]]).on({mouseenter:function(){t.dataSM("scroll").up=$(this).hasClass("scroll-up"),x.menuScroll(t)},mouseleave:function(e){x.menuScrollStop(t),x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(t){t.preventDefault()}}).insertAfter(t));var A=".smartmenus_scroll";if(t.dataSM("scroll",{y:this.cssTransforms3d?0:i-m,step:1,itemH:m,subH:h,arrowDownH:this.getHeight(t.dataSM("scroll-arrows").eq(1))}).on(getEventsNS({mouseover:function(e){x.menuScrollOver(t,e)},mouseout:function(e){x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(e){x.menuScrollMousewheel(t,e)}},A)).dataSM("scroll-arrows").css({top:"auto",left:"0",marginLeft:e+(parseInt(t.css("border-left-width"))||0),width:r-(parseInt(t.css("border-left-width"))||0)-(parseInt(t.css("border-right-width"))||0),zIndex:t.css("z-index")}).eq(g&&this.opts.bottomToTopSubMenus?0:1).show(),this.isFixed()){var C={};C[touchEvents?"touchstart touchmove touchend":"pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp"]=function(e){x.menuScrollTouch(t,e)},t.css({"touch-action":"none","-ms-touch-action":"none"}).on(getEventsNS(C,A))}}}t.css({top:"auto",left:"0",marginLeft:e,marginTop:i-m})},menuScroll:function(t,e,i){var s,o=t.dataSM("scroll"),a=t.dataSM("scroll-arrows"),n=o.up?o.upEnd:o.downEnd;if(!e&&o.momentum){if(o.momentum*=.92,s=o.momentum,.5>s)return this.menuScrollStop(t),void 0}else s=i||(e||!this.opts.scrollAccelerate?this.opts.scrollStep:Math.floor(o.step));var r=t.dataSM("level");if(this.activatedItems[r-1]&&this.activatedItems[r-1].dataSM("sub")&&this.activatedItems[r-1].dataSM("sub").is(":visible")&&this.menuHideSubMenus(r-1),o.y=o.up&&o.y>=n||!o.up&&n>=o.y?o.y:Math.abs(n-o.y)>s?o.y+(o.up?s:-s):n,t.css(this.cssTransforms3d?{"-webkit-transform":"translate3d(0, "+o.y+"px, 0)",transform:"translate3d(0, "+o.y+"px, 0)"}:{marginTop:o.y}),mouse&&(o.up&&o.y>o.downEnd||!o.up&&o.y<o.upEnd)&&a.eq(o.up?1:0).show(),o.y==n)mouse&&a.eq(o.up?0:1).hide(),this.menuScrollStop(t);else if(!e){this.opts.scrollAccelerate&&o.step<this.opts.scrollStep&&(o.step+=.2);var h=this;this.scrollTimeout=requestAnimationFrame(function(){h.menuScroll(t)})}},menuScrollMousewheel:function(t,e){if(this.getClosestMenu(e.target)==t[0]){e=e.originalEvent;var i=(e.wheelDelta||-e.detail)>0;t.dataSM("scroll-arrows").eq(i?0:1).is(":visible")&&(t.dataSM("scroll").up=i,this.menuScroll(t,!0))}e.preventDefault()},menuScrollOut:function(t,e){mouse&&(/^scroll-(up|down)/.test((e.relatedTarget||"").className)||(t[0]==e.relatedTarget||$.contains(t[0],e.relatedTarget))&&this.getClosestMenu(e.relatedTarget)==t[0]||t.dataSM("scroll-arrows").css("visibility","hidden"))},menuScrollOver:function(t,e){if(mouse&&!/^scroll-(up|down)/.test(e.target.className)&&this.getClosestMenu(e.target)==t[0]){this.menuScrollRefreshData(t);var i=t.dataSM("scroll"),s=$(window).scrollTop()-t.dataSM("parent-a").offset().top-i.itemH;t.dataSM("scroll-arrows").eq(0).css("margin-top",s).end().eq(1).css("margin-top",s+this.getViewportHeight()-i.arrowDownH).end().css("visibility","visible")}},menuScrollRefreshData:function(t){var e=t.dataSM("scroll"),i=$(window).scrollTop()-t.dataSM("parent-a").offset().top-e.itemH;this.cssTransforms3d&&(i=-(parseFloat(t.css("margin-top"))-i)),$.extend(e,{upEnd:i,downEnd:i+this.getViewportHeight()-e.subH})},menuScrollStop:function(t){return this.scrollTimeout?(cancelAnimationFrame(this.scrollTimeout),this.scrollTimeout=0,t.dataSM("scroll").step=1,!0):void 0},menuScrollTouch:function(t,e){if(e=e.originalEvent,isTouchEvent(e)){var i=this.getTouchPoint(e);if(this.getClosestMenu(i.target)==t[0]){var s=t.dataSM("scroll");if(/(start|down)$/i.test(e.type))this.menuScrollStop(t)?(e.preventDefault(),this.$touchScrollingSub=t):this.$touchScrollingSub=null,this.menuScrollRefreshData(t),$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp});else if(/move$/i.test(e.type)){var o=void 0!==s.touchY?s.touchY:s.touchStartY;if(void 0!==o&&o!=i.pageY){this.$touchScrollingSub=t;var a=i.pageY>o;void 0!==s.up&&s.up!=a&&$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp}),$.extend(s,{up:a,touchY:i.pageY}),this.menuScroll(t,!0,Math.abs(i.pageY-o))}e.preventDefault()}else void 0!==s.touchY&&((s.momentum=15*Math.pow(Math.abs(i.pageY-s.touchStartY)/(e.timeStamp-s.touchStartTime),2))&&(this.menuScrollStop(t),this.menuScroll(t),e.preventDefault()),delete s.touchY)}}},menuShow:function(t){if((t.dataSM("beforefirstshowfired")||(t.dataSM("beforefirstshowfired",!0),this.$root.triggerHandler("beforefirstshow.smapi",t[0])!==!1))&&this.$root.triggerHandler("beforeshow.smapi",t[0])!==!1&&(t.dataSM("shown-before",!0),canAnimate&&t.stop(!0,!0),!t.is(":visible"))){var e=t.dataSM("parent-a"),i=this.isCollapsible();if((this.opts.keepHighlighted||i)&&e.addClass("highlighted"),i)t.removeClass("sm-nowrap").css({zIndex:"",width:"auto",minWidth:"",maxWidth:"",top:"",left:"",marginLeft:"",marginTop:""});else{if(t.css("z-index",this.zIndexInc=(this.zIndexInc||this.getStartZIndex())+1),(this.opts.subMenusMinWidth||this.opts.subMenusMaxWidth)&&(t.css({width:"auto",minWidth:"",maxWidth:""}).addClass("sm-nowrap"),this.opts.subMenusMinWidth&&t.css("min-width",this.opts.subMenusMinWidth),this.opts.subMenusMaxWidth)){var s=this.getWidth(t);t.css("max-width",this.opts.subMenusMaxWidth),s>this.getWidth(t)&&t.removeClass("sm-nowrap").css("width",this.opts.subMenusMaxWidth)}this.menuPosition(t)}var o=function(){t.css("overflow","")};i?canAnimate&&this.opts.collapsibleShowFunction?this.opts.collapsibleShowFunction.call(this,t,o):t.show(this.opts.collapsibleShowDuration,o):canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,t,o):t.show(this.opts.showDuration,o),e.attr("aria-expanded","true"),t.attr({"aria-expanded":"true","aria-hidden":"false"}),this.visibleSubMenus.push(t),this.$root.triggerHandler("show.smapi",t[0])}},popupHide:function(t){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},t?1:this.opts.hideTimeout)},popupShow:function(t,e){if(!this.opts.isPopup)return alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.'),void 0;if(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),this.$root.dataSM("shown-before",!0),canAnimate&&this.$root.stop(!0,!0),!this.$root.is(":visible")){this.$root.css({left:t,top:e});var i=this,s=function(){i.$root.css("overflow","")};canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,this.$root,s):this.$root.show(this.opts.showDuration,s),this.visibleSubMenus[0]=this.$root}},refresh:function(){this.destroy(!0),this.init(!0)},rootKeyDown:function(t){if(this.handleEvents())switch(t.keyCode){case 27:var e=this.activatedItems[0];if(e){this.menuHideAll(),e[0].focus();var i=e.dataSM("sub");i&&this.menuHide(i)}break;case 32:var s=$(t.target);if(s.is("a")&&this.handleItemEvents(s)){var i=s.dataSM("sub");i&&!i.is(":visible")&&(this.itemClick({currentTarget:t.target}),t.preventDefault())}}},rootOut:function(t){if(this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),!this.opts.showOnClick||!this.opts.hideOnClick)){var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},this.opts.hideTimeout)}},rootOver:function(t){this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0)},winResize:function(t){if(this.handleEvents()){if(!("onorientationchange"in window)||"orientationchange"==t.type){var e=this.isCollapsible();this.wasCollapsible&&e||(this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0].blur(),this.menuHideAll()),this.wasCollapsible=e}}else if(this.$disableOverlay){var i=this.$root.offset();this.$disableOverlay.css({top:i.top,left:i.left,width:this.$root.outerWidth(),height:this.$root.outerHeight()})}}}}),$.fn.dataSM=function(t,e){return e?this.data(t+"_smartmenus",e):this.data(t+"_smartmenus")},$.fn.removeDataSM=function(t){return this.removeData(t+"_smartmenus")},$.fn.smartmenus=function(options){if("string"==typeof options){var args=arguments,method=options;return Array.prototype.shift.call(args),this.each(function(){var t=$(this).data("smartmenus");t&&t[method]&&t[method].apply(t,args)})}return this.each(function(){var dataOpts=$(this).data("sm-options")||null;if(dataOpts)try{dataOpts=eval("("+dataOpts+")")}catch(e){dataOpts=null,alert('ERROR\n\nSmartMenus jQuery init:\nInvalid "data-sm-options" attribute value syntax.')}new $.SmartMenus(this,$.extend({},$.fn.smartmenus.defaults,options,dataOpts))})},$.fn.smartmenus.defaults={isPopup:!1,mainMenuSubOffsetX:0,mainMenuSubOffsetY:0,subMenusSubOffsetX:0,subMenusSubOffsetY:0,subMenusMinWidth:"10em",subMenusMaxWidth:"20em",subIndicators:!0,subIndicatorsPos:"append",subIndicatorsText:"",scrollStep:30,scrollAccelerate:!0,showTimeout:250,hideTimeout:500,showDuration:0,showFunction:null,hideDuration:0,hideFunction:function(t,e){t.fadeOut(200,e)},collapsibleShowDuration:0,collapsibleShowFunction:function(t,e){t.slideDown(200,e)},collapsibleHideDuration:0,collapsibleHideFunction:function(t,e){t.slideUp(200,e)},showOnClick:!1,hideOnClick:!0,noMouseOver:!1,keepInViewport:!0,keepHighlighted:!0,markCurrentItem:!1,markCurrentTree:!0,rightToLeftSubMenus:!1,bottomToTopSubMenus:!1,collapsibleBehavior:"default"},$}); \ No newline at end of file diff --git a/Doxygen/html/menu.js b/Doxygen/html/menu.js new file mode 100644 index 0000000..b0b2693 --- /dev/null +++ b/Doxygen/html/menu.js @@ -0,0 +1,136 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + 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. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function initMenu(relPath,searchEnabled,serverSide,searchPage,search) { + function makeTree(data,relPath) { + var result=''; + if ('children' in data) { + result+='<ul>'; + for (var i in data.children) { + var url; + var link; + link = data.children[i].url; + if (link.substring(0,1)=='^') { + url = link.substring(1); + } else { + url = relPath+link; + } + result+='<li><a href="'+url+'">'+ + data.children[i].text+'</a>'+ + makeTree(data.children[i],relPath)+'</li>'; + } + result+='</ul>'; + } + return result; + } + var searchBoxHtml; + if (searchEnabled) { + if (serverSide) { + searchBoxHtml='<div id="MSearchBox" class="MSearchBoxInactive">'+ + '<div class="left">'+ + '<form id="FSearchBox" action="'+relPath+searchPage+ + '" method="get"><span id="MSearchSelectExt"> </span>'+ + '<input type="text" id="MSearchField" name="query" value="" placeholder="'+search+ + '" size="20" accesskey="S" onfocus="searchBox.OnSearchFieldFocus(true)"'+ + ' onblur="searchBox.OnSearchFieldFocus(false)"/>'+ + '</form>'+ + '</div>'+ + '<div class="right"></div>'+ + '</div>'; + } else { + searchBoxHtml='<div id="MSearchBox" class="MSearchBoxInactive">'+ + '<span class="left">'+ + '<span id="MSearchSelect" onmouseover="return searchBox.OnSearchSelectShow()"'+ + ' onmouseout="return searchBox.OnSearchSelectHide()"> </span>'+ + '<input type="text" id="MSearchField" value="" placeholder="'+search+ + '" accesskey="S" onfocus="searchBox.OnSearchFieldFocus(true)" '+ + 'onblur="searchBox.OnSearchFieldFocus(false)" '+ + 'onkeyup="searchBox.OnSearchFieldChange(event)"/>'+ + '</span>'+ + '<span class="right"><a id="MSearchClose" '+ + 'href="javascript:searchBox.CloseResultsWindow()">'+ + '<img id="MSearchCloseImg" border="0" src="'+relPath+ + 'search/close.svg" alt=""/></a>'+ + '</span>'+ + '</div>'; + } + } + + $('#main-nav').before('<div class="sm sm-dox"><input id="main-menu-state" type="checkbox"/>'+ + '<label class="main-menu-btn" for="main-menu-state">'+ + '<span class="main-menu-btn-icon"></span> '+ + 'Toggle main menu visibility</label>'+ + '<span id="searchBoxPos1" style="position:absolute;right:8px;top:8px;height:36px;"></span>'+ + '</div>'); + $('#main-nav').append(makeTree(menudata,relPath)); + $('#main-nav').children(':first').addClass('sm sm-dox').attr('id','main-menu'); + if (searchBoxHtml) { + $('#main-menu').append('<li id="searchBoxPos2" style="float:right"></li>'); + } + var $mainMenuState = $('#main-menu-state'); + var prevWidth = 0; + if ($mainMenuState.length) { + function initResizableIfExists() { + if (typeof initResizable==='function') initResizable(); + } + // animate mobile menu + $mainMenuState.change(function(e) { + var $menu = $('#main-menu'); + var options = { duration: 250, step: initResizableIfExists }; + if (this.checked) { + options['complete'] = function() { $menu.css('display', 'block') }; + $menu.hide().slideDown(options); + } else { + options['complete'] = function() { $menu.css('display', 'none') }; + $menu.show().slideUp(options); + } + }); + // set default menu visibility + function resetState() { + var $menu = $('#main-menu'); + var $mainMenuState = $('#main-menu-state'); + var newWidth = $(window).outerWidth(); + if (newWidth!=prevWidth) { + if ($(window).outerWidth()<768) { + $mainMenuState.prop('checked',false); $menu.hide(); + $('#searchBoxPos1').html(searchBoxHtml); + $('#searchBoxPos2').hide(); + } else { + $menu.show(); + $('#searchBoxPos1').empty(); + $('#searchBoxPos2').html(searchBoxHtml); + $('#searchBoxPos2').show(); + } + if (typeof searchBox!=='undefined') { + searchBox.CloseResultsWindow(); + } + prevWidth = newWidth; + } + } + $(window).ready(function() { resetState(); initResizableIfExists(); }); + $(window).resize(resetState); + } + $('#main-menu').smartmenus(); +} +/* @license-end */ diff --git a/Doxygen/html/menudata.js b/Doxygen/html/menudata.js new file mode 100644 index 0000000..c04f589 --- /dev/null +++ b/Doxygen/html/menudata.js @@ -0,0 +1,67 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + 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. + + @licend The above is the entire license notice for the JavaScript code in this file +*/ +var menudata={children:[ +{text:"Main Page",url:"index.html"}, +{text:"Classes",url:"annotated.html",children:[ +{text:"Class List",url:"annotated.html"}, +{text:"Class Index",url:"classes.html"}, +{text:"Class Hierarchy",url:"hierarchy.html"}, +{text:"Class Members",url:"functions.html",children:[ +{text:"All",url:"functions.html",children:[ +{text:"a",url:"functions.html#index_a"}, +{text:"b",url:"functions.html#index_b"}, +{text:"c",url:"functions.html#index_c"}, +{text:"e",url:"functions.html#index_e"}, +{text:"f",url:"functions.html#index_f"}, +{text:"g",url:"functions.html#index_g"}, +{text:"i",url:"functions.html#index_i"}, +{text:"m",url:"functions.html#index_m"}, +{text:"n",url:"functions.html#index_n"}, +{text:"s",url:"functions.html#index_s"}, +{text:"t",url:"functions.html#index_t"}, +{text:"u",url:"functions.html#index_u"}, +{text:"v",url:"functions.html#index_v"}, +{text:"~",url:"functions.html#index__7E"}]}, +{text:"Functions",url:"functions_func.html",children:[ +{text:"a",url:"functions_func.html#index_a"}, +{text:"b",url:"functions_func.html#index_b"}, +{text:"c",url:"functions_func.html#index_c"}, +{text:"e",url:"functions_func.html#index_e"}, +{text:"f",url:"functions_func.html#index_f"}, +{text:"g",url:"functions_func.html#index_g"}, +{text:"i",url:"functions_func.html#index_i"}, +{text:"m",url:"functions_func.html#index_m"}, +{text:"s",url:"functions_func.html#index_s"}, +{text:"t",url:"functions_func.html#index_t"}, +{text:"u",url:"functions_func.html#index_u"}, +{text:"v",url:"functions_func.html#index_v"}, +{text:"~",url:"functions_func.html#index__7E"}]}, +{text:"Variables",url:"functions_vars.html"}, +{text:"Typedefs",url:"functions_type.html"}]}]}, +{text:"Files",url:"files.html",children:[ +{text:"File List",url:"files.html"}, +{text:"File Members",url:"globals.html",children:[ +{text:"All",url:"globals.html"}, +{text:"Functions",url:"globals_func.html"}]}]}]} diff --git a/Doxygen/html/nav_f.png b/Doxygen/html/nav_f.png new file mode 100644 index 0000000..4b24ffc --- /dev/null +++ b/Doxygen/html/nav_f.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bdc6a72666e1b300fdfc5ecf102714c9fd57df76fdf47139f8dbf8ae5986364 +size 153 diff --git a/Doxygen/html/nav_fd.png b/Doxygen/html/nav_fd.png new file mode 100644 index 0000000..efed867 --- /dev/null +++ b/Doxygen/html/nav_fd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68c43d3eb0bccfc8ae7de4e17b1d6f98455b527b8ef7e3a6f600abe2b13c1a28 +size 169 diff --git a/Doxygen/html/nav_g.png b/Doxygen/html/nav_g.png new file mode 100644 index 0000000..34942a1 --- /dev/null +++ b/Doxygen/html/nav_g.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3171164c16baa5953c17821a7ce5feac673c05a56b287453ce3a0bf5048f0999 +size 95 diff --git a/Doxygen/html/nav_h.png b/Doxygen/html/nav_h.png new file mode 100644 index 0000000..5e2fe5d --- /dev/null +++ b/Doxygen/html/nav_h.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7711604d6cdeaf3cdfd661fa21fc5bf18de929671c801f00415eaecd35abda3 +size 98 diff --git a/Doxygen/html/nav_hd.png b/Doxygen/html/nav_hd.png new file mode 100644 index 0000000..ab84f22 --- /dev/null +++ b/Doxygen/html/nav_hd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0714b74a43b5e56a05414eb1b18d9f817aa91339359efb23dba4c411434ed7a5 +size 114 diff --git a/Doxygen/html/open.png b/Doxygen/html/open.png new file mode 100644 index 0000000..fae6d8f --- /dev/null +++ b/Doxygen/html/open.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a04665e9dd0ef5ef2c6ddf02b4a12472984485adb027fd12ae6c60ffd203b1a4 +size 123 diff --git a/Doxygen/html/search/all_0.js b/Doxygen/html/search/all_0.js new file mode 100644 index 0000000..004e3bb --- /dev/null +++ b/Doxygen/html/search/all_0.js @@ -0,0 +1,6 @@ +var searchData= +[ + ['acustomproceduralmeshcomponent_0',['ACustomProceduralMeshComponent',['../class_a_custom_procedural_mesh_component.html',1,'ACustomProceduralMeshComponent'],['../class_a_custom_procedural_mesh_component.html#ad2a083e99c4303ff692c6de7fd7c89d7',1,'ACustomProceduralMeshComponent::ACustomProceduralMeshComponent()']]], + ['aexcavatorcharacter_1',['AExcavatorCharacter',['../class_a_excavator_character.html',1,'AExcavatorCharacter'],['../class_a_excavator_character.html#a897f85cbfeb62c8cf9357124030678f0',1,'AExcavatorCharacter::AExcavatorCharacter()']]], + ['aexcavatorsimulatorgamemodebase_2',['AExcavatorSimulatorGameModeBase',['../class_a_excavator_simulator_game_mode_base.html',1,'']]] +]; diff --git a/Doxygen/html/search/all_1.js b/Doxygen/html/search/all_1.js new file mode 100644 index 0000000..96e35f6 --- /dev/null +++ b/Doxygen/html/search/all_1.js @@ -0,0 +1,12 @@ +var searchData= +[ + ['beambottomrotation_0',['BeamBottomRotation',['../class_a_excavator_character.html#a4c0322adf74c147b25e0a56eb82e8b63',1,'AExcavatorCharacter']]], + ['beambottomrotationvr_1',['BeamBottomRotationVR',['../class_a_excavator_character.html#a2fd6354fdc9f48c9ec37aa8a0881bb39',1,'AExcavatorCharacter']]], + ['beamtoprotation_2',['BeamTopRotation',['../class_a_excavator_character.html#a700a5e4f8e43868c15d6b60460006eb9',1,'AExcavatorCharacter']]], + ['beamtoprotationvr_3',['BeamTopRotationVR',['../class_a_excavator_character.html#a0fa69b8a1aa0f05dad74a41da45f5551',1,'AExcavatorCharacter']]], + ['beginplay_4',['BeginPlay',['../class_a_custom_procedural_mesh_component.html#a91a659f1e60e2aadddc5ffd88372f66d',1,'ACustomProceduralMeshComponent::BeginPlay()'],['../class_a_excavator_character.html#aff0f3c6a77a15a8476981c7845dff158',1,'AExcavatorCharacter::BeginPlay() override']]], + ['bodyrotation_5',['BodyRotation',['../class_a_excavator_character.html#a63014640642f4964440cd3923cc8396f',1,'AExcavatorCharacter']]], + ['bodyrotationvr_6',['BodyRotationVR',['../class_a_excavator_character.html#a52c7b1ac54ccb49c0cd5e9baf25dc338',1,'AExcavatorCharacter']]], + ['bucketrotationreleacerocks_7',['BucketRotationReleaceRocks',['../class_a_excavator_character.html#a18933e6161a7489652e64e06d6dc9d72',1,'AExcavatorCharacter']]], + ['bucketrotationvr_8',['BucketRotationVR',['../class_a_excavator_character.html#a5eaf154b0d816e682669609f6a1145b4',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/all_2.js b/Doxygen/html/search/all_2.js new file mode 100644 index 0000000..97ea3c1 --- /dev/null +++ b/Doxygen/html/search/all_2.js @@ -0,0 +1,10 @@ +var searchData= +[ + ['checkcollisioncomponent_2ecpp_0',['CheckCollisionComponent.cpp',['../_check_collision_component_8cpp.html',1,'']]], + ['checkcollisioncomponent_2eh_1',['CheckCollisionComponent.h',['../_check_collision_component_8h.html',1,'']]], + ['createtriangle_2',['CreateTriangle',['../class_a_custom_procedural_mesh_component.html#a9d663c4e5f670976a2e9a9de0b9e9268',1,'ACustomProceduralMeshComponent']]], + ['createvertices_3',['CreateVertices',['../class_a_custom_procedural_mesh_component.html#a722837f83f1b0db0cb97f47b50d310b3',1,'ACustomProceduralMeshComponent']]], + ['createwall_4',['CreateWall',['../class_a_custom_procedural_mesh_component.html#ac1c8d09545e81f4cee3a33d8e1ff0810',1,'ACustomProceduralMeshComponent']]], + ['customproceduralmeshcomponent_2ecpp_5',['CustomProceduralMeshComponent.cpp',['../_custom_procedural_mesh_component_8cpp.html',1,'']]], + ['customproceduralmeshcomponent_2eh_6',['CustomProceduralMeshComponent.h',['../_custom_procedural_mesh_component_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/all_3.js b/Doxygen/html/search/all_3.js new file mode 100644 index 0000000..920b4c1 --- /dev/null +++ b/Doxygen/html/search/all_3.js @@ -0,0 +1,13 @@ +var searchData= +[ + ['excavatoranim_2ecpp_0',['ExcavatorAnim.cpp',['../_excavator_anim_8cpp.html',1,'']]], + ['excavatoranim_2eh_1',['ExcavatorAnim.h',['../_excavator_anim_8h.html',1,'']]], + ['excavatorcharacter_2ecpp_2',['ExcavatorCharacter.cpp',['../_excavator_character_8cpp.html',1,'']]], + ['excavatorcharacter_2eh_3',['ExcavatorCharacter.h',['../_excavator_character_8h.html',1,'']]], + ['excavatorsimulator_4',['ExcavatorSimulator',['../class_excavator_simulator.html',1,'ExcavatorSimulator'],['../class_excavator_simulator.html#ac3cbb6777236f4b4900e850d58050951',1,'ExcavatorSimulator.ExcavatorSimulator()']]], + ['excavatorsimulator_2ebuild_2ecs_5',['ExcavatorSimulator.Build.cs',['../_excavator_simulator_8_build_8cs.html',1,'']]], + ['excavatorsimulator_2ecpp_6',['ExcavatorSimulator.cpp',['../_excavator_simulator_8cpp.html',1,'']]], + ['excavatorsimulator_2eh_7',['ExcavatorSimulator.h',['../_excavator_simulator_8h.html',1,'']]], + ['excavatorsimulatorgamemodebase_2ecpp_8',['ExcavatorSimulatorGameModeBase.cpp',['../_excavator_simulator_game_mode_base_8cpp.html',1,'']]], + ['excavatorsimulatorgamemodebase_2eh_9',['ExcavatorSimulatorGameModeBase.h',['../_excavator_simulator_game_mode_base_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/all_4.js b/Doxygen/html/search/all_4.js new file mode 100644 index 0000000..ed4e5b9 --- /dev/null +++ b/Doxygen/html/search/all_4.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['fworldgeneratorinstance_0',['FWorldGeneratorInstance',['../class_f_world_generator_instance.html',1,'FWorldGeneratorInstance'],['../class_f_world_generator_instance.html#a9340a460da1c5a05a25e496f5bbe3e47',1,'FWorldGeneratorInstance::FWorldGeneratorInstance()']]] +]; diff --git a/Doxygen/html/search/all_5.js b/Doxygen/html/search/all_5.js new file mode 100644 index 0000000..a7f7e53 --- /dev/null +++ b/Doxygen/html/search/all_5.js @@ -0,0 +1,20 @@ +var searchData= +[ + ['getbeambottomrotation_0',['GetBeamBottomRotation',['../class_u_excavator_anim.html#affb325f375a952a31fd6c7edbda878b2',1,'UExcavatorAnim']]], + ['getbeamtoprotation_1',['GetBeamTopRotation',['../class_u_excavator_anim.html#a1cf8d06387c0c8bf61315209a2277b89',1,'UExcavatorAnim']]], + ['getbodyrotation_2',['GetBodyRotation',['../class_u_excavator_anim.html#adf9be335c9520d1b730d0c983ad42734',1,'UExcavatorAnim']]], + ['getbottommaxrotation_3',['GetBottomMaxRotation',['../class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79',1,'UExcavatorAnim']]], + ['getbottomminrotation_4',['GetBottomMinRotation',['../class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b',1,'UExcavatorAnim']]], + ['getbucketmaxrotation_5',['GetBucketMaxRotation',['../class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f',1,'UExcavatorAnim']]], + ['getbucketminrotation_6',['GetBucketMinRotation',['../class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc',1,'UExcavatorAnim']]], + ['getbucketrotation_7',['GetBucketRotation',['../class_u_excavator_anim.html#ad206f925afc96462b27ebd5c33273da5',1,'UExcavatorAnim']]], + ['getinstance_8',['GetInstance',['../class_u_world_generator.html#a92908af10f0cdfee492c5e21ba1000d1',1,'UWorldGenerator']]], + ['getmaterialimpl_9',['GetMaterialImpl',['../class_f_world_generator_instance.html#a5d982bbb90e6a7368d4eab2bbf4107a0',1,'FWorldGeneratorInstance']]], + ['gettopmaxrotation_10',['GetTopMaxRotation',['../class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1',1,'UExcavatorAnim']]], + ['gettopminrotation_11',['GetTopMinRotation',['../class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793',1,'UExcavatorAnim']]], + ['getupvector_12',['GetUpVector',['../class_f_world_generator_instance.html#a41a32aee12e9228c3ec2bdfa4be7a92a',1,'FWorldGeneratorInstance']]], + ['getvalueimpl_13',['GetValueImpl',['../class_f_world_generator_instance.html#a9a18ba21423f11dc01d3a2e54fbd214f',1,'FWorldGeneratorInstance']]], + ['getvaluerangeimpl_14',['GetValueRangeImpl',['../class_f_world_generator_instance.html#aa18eccf536acdf8d2115f32ac0dab1ef',1,'FWorldGeneratorInstance']]], + ['grounddeformercomponent_2ecpp_15',['GroundDeformerComponent.cpp',['../_ground_deformer_component_8cpp.html',1,'']]], + ['grounddeformercomponent_2eh_16',['GroundDeformerComponent.h',['../_ground_deformer_component_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/all_6.js b/Doxygen/html/search/all_6.js new file mode 100644 index 0000000..ecacf38 --- /dev/null +++ b/Doxygen/html/search/all_6.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['implement_5fprimary_5fgame_5fmodule_0',['IMPLEMENT_PRIMARY_GAME_MODULE',['../_excavator_simulator_8cpp.html#abdfae4c413c9ef6bcaa5cf0c657807d7',1,'ExcavatorSimulator.cpp']]], + ['init_1',['Init',['../class_f_world_generator_instance.html#acb9442be2e711fb3ba4d146bf584bd5d',1,'FWorldGeneratorInstance']]] +]; diff --git a/Doxygen/html/search/all_7.js b/Doxygen/html/search/all_7.js new file mode 100644 index 0000000..537c9de --- /dev/null +++ b/Doxygen/html/search/all_7.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['movement_0',['Movement',['../class_a_excavator_character.html#abfc64bb0a310badadb9e85e7336b0735',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/all_8.js b/Doxygen/html/search/all_8.js new file mode 100644 index 0000000..8b35e20 --- /dev/null +++ b/Doxygen/html/search/all_8.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['noiseheight_0',['NoiseHeight',['../class_u_world_generator.html#a97672fed88363bef5359e4a9e1bc22d2',1,'UWorldGenerator']]] +]; diff --git a/Doxygen/html/search/all_9.js b/Doxygen/html/search/all_9.js new file mode 100644 index 0000000..ceb79ef --- /dev/null +++ b/Doxygen/html/search/all_9.js @@ -0,0 +1,10 @@ +var searchData= +[ + ['seed_0',['Seed',['../class_u_world_generator.html#ae3e0313a980ab00d26cb7691a9d178e7',1,'UWorldGenerator']]], + ['setbeambottomrotation_1',['SetBeamBottomRotation',['../class_u_excavator_anim.html#a09b8e1814d38428a70423d3d81922448',1,'UExcavatorAnim']]], + ['setbeamtoprotation_2',['SetBeamTopRotation',['../class_u_excavator_anim.html#a59c863bb954f22c019bc20b6d4b947df',1,'UExcavatorAnim']]], + ['setbodyrotation_3',['SetBodyRotation',['../class_u_excavator_anim.html#a215798edeb92f27e3e5ad9ad5c25626a',1,'UExcavatorAnim']]], + ['setbucketrotation_4',['SetBucketRotation',['../class_u_excavator_anim.html#aba254277652747c4c1dabd60308b4c17',1,'UExcavatorAnim']]], + ['setupplayerinputcomponent_5',['SetupPlayerInputComponent',['../class_a_excavator_character.html#ae0c6e52ac2b153baa789be4e0b2b75fc',1,'AExcavatorCharacter']]], + ['super_6',['Super',['../class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01',1,'FWorldGeneratorInstance']]] +]; diff --git a/Doxygen/html/search/all_a.js b/Doxygen/html/search/all_a.js new file mode 100644 index 0000000..aa0726a --- /dev/null +++ b/Doxygen/html/search/all_a.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['tick_0',['Tick',['../class_a_custom_procedural_mesh_component.html#a9b6c8c619a8d0396e2d3d19f61007427',1,'ACustomProceduralMeshComponent::Tick()'],['../class_a_excavator_character.html#a080c503815b1f096cf7704e5bfa47df1',1,'AExcavatorCharacter::Tick()']]] +]; diff --git a/Doxygen/html/search/all_b.js b/Doxygen/html/search/all_b.js new file mode 100644 index 0000000..5e011b6 --- /dev/null +++ b/Doxygen/html/search/all_b.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['uexcavatoranim_0',['UExcavatorAnim',['../class_u_excavator_anim.html',1,'UExcavatorAnim'],['../class_u_excavator_anim.html#a17a5fcd9bd609ed5ad1538906286184c',1,'UExcavatorAnim::UExcavatorAnim()']]], + ['uworldgenerator_1',['UWorldGenerator',['../class_u_world_generator.html',1,'UWorldGenerator'],['../class_u_world_generator.html#a4ea59a679fc8eff20b3b143461020977',1,'UWorldGenerator::UWorldGenerator()']]] +]; diff --git a/Doxygen/html/search/all_c.js b/Doxygen/html/search/all_c.js new file mode 100644 index 0000000..72f5f93 --- /dev/null +++ b/Doxygen/html/search/all_c.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['vehiclerotation_0',['VehicleRotation',['../class_a_excavator_character.html#a3f262813a65448739540f41c73498f56',1,'AExcavatorCharacter']]], + ['vehiclerotationvr_1',['VehicleRotationVR',['../class_a_excavator_character.html#a173be62746e6adc66879eea680f21d56',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/all_d.js b/Doxygen/html/search/all_d.js new file mode 100644 index 0000000..dbc9bf2 --- /dev/null +++ b/Doxygen/html/search/all_d.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['worldgenerator_2ecpp_0',['WorldGenerator.cpp',['../_world_generator_8cpp.html',1,'']]], + ['worldgenerator_2eh_1',['WorldGenerator.h',['../_world_generator_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/all_e.js b/Doxygen/html/search/all_e.js new file mode 100644 index 0000000..d3df789 --- /dev/null +++ b/Doxygen/html/search/all_e.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['_7euworldgenerator_0',['~UWorldGenerator',['../class_u_world_generator.html#ad2e31d185549690f85ddec11f48a709e',1,'UWorldGenerator']]] +]; diff --git a/Doxygen/html/search/classes_0.js b/Doxygen/html/search/classes_0.js new file mode 100644 index 0000000..84069b1 --- /dev/null +++ b/Doxygen/html/search/classes_0.js @@ -0,0 +1,6 @@ +var searchData= +[ + ['acustomproceduralmeshcomponent_0',['ACustomProceduralMeshComponent',['../class_a_custom_procedural_mesh_component.html',1,'']]], + ['aexcavatorcharacter_1',['AExcavatorCharacter',['../class_a_excavator_character.html',1,'']]], + ['aexcavatorsimulatorgamemodebase_2',['AExcavatorSimulatorGameModeBase',['../class_a_excavator_simulator_game_mode_base.html',1,'']]] +]; diff --git a/Doxygen/html/search/classes_1.js b/Doxygen/html/search/classes_1.js new file mode 100644 index 0000000..5020854 --- /dev/null +++ b/Doxygen/html/search/classes_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['excavatorsimulator_0',['ExcavatorSimulator',['../class_excavator_simulator.html',1,'']]] +]; diff --git a/Doxygen/html/search/classes_2.js b/Doxygen/html/search/classes_2.js new file mode 100644 index 0000000..6efff70 --- /dev/null +++ b/Doxygen/html/search/classes_2.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['fworldgeneratorinstance_0',['FWorldGeneratorInstance',['../class_f_world_generator_instance.html',1,'']]] +]; diff --git a/Doxygen/html/search/classes_3.js b/Doxygen/html/search/classes_3.js new file mode 100644 index 0000000..20e02a0 --- /dev/null +++ b/Doxygen/html/search/classes_3.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['uexcavatoranim_0',['UExcavatorAnim',['../class_u_excavator_anim.html',1,'']]], + ['uworldgenerator_1',['UWorldGenerator',['../class_u_world_generator.html',1,'']]] +]; diff --git a/Doxygen/html/search/close.svg b/Doxygen/html/search/close.svg new file mode 100644 index 0000000..a933eea --- /dev/null +++ b/Doxygen/html/search/close.svg @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 11 11" + height="11" + width="11" + id="svg2" + version="1.1"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <path + id="path12" + d="M 5.5 0.5 A 5 5 0 0 0 0.5 5.5 A 5 5 0 0 0 5.5 10.5 A 5 5 0 0 0 10.5 5.5 A 5 5 0 0 0 5.5 0.5 z M 3.5820312 3 A 0.58291923 0.58291923 0 0 1 4 3.1757812 L 5.5 4.6757812 L 7 3.1757812 A 0.58291923 0.58291923 0 0 1 7.4003906 3 A 0.58291923 0.58291923 0 0 1 7.8242188 4 L 6.3242188 5.5 L 7.8242188 7 A 0.58291923 0.58291923 0 1 1 7 7.8242188 L 5.5 6.3242188 L 4 7.8242188 A 0.58291923 0.58291923 0 1 1 3.1757812 7 L 4.6757812 5.5 L 3.1757812 4 A 0.58291923 0.58291923 0 0 1 3.5820312 3 z " + style="stroke-width:1.09870648;fill:#bababa;fill-opacity:1" /> +</svg> diff --git a/Doxygen/html/search/files_0.js b/Doxygen/html/search/files_0.js new file mode 100644 index 0000000..e893235 --- /dev/null +++ b/Doxygen/html/search/files_0.js @@ -0,0 +1,7 @@ +var searchData= +[ + ['checkcollisioncomponent_2ecpp_0',['CheckCollisionComponent.cpp',['../_check_collision_component_8cpp.html',1,'']]], + ['checkcollisioncomponent_2eh_1',['CheckCollisionComponent.h',['../_check_collision_component_8h.html',1,'']]], + ['customproceduralmeshcomponent_2ecpp_2',['CustomProceduralMeshComponent.cpp',['../_custom_procedural_mesh_component_8cpp.html',1,'']]], + ['customproceduralmeshcomponent_2eh_3',['CustomProceduralMeshComponent.h',['../_custom_procedural_mesh_component_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/files_1.js b/Doxygen/html/search/files_1.js new file mode 100644 index 0000000..064923f --- /dev/null +++ b/Doxygen/html/search/files_1.js @@ -0,0 +1,12 @@ +var searchData= +[ + ['excavatoranim_2ecpp_0',['ExcavatorAnim.cpp',['../_excavator_anim_8cpp.html',1,'']]], + ['excavatoranim_2eh_1',['ExcavatorAnim.h',['../_excavator_anim_8h.html',1,'']]], + ['excavatorcharacter_2ecpp_2',['ExcavatorCharacter.cpp',['../_excavator_character_8cpp.html',1,'']]], + ['excavatorcharacter_2eh_3',['ExcavatorCharacter.h',['../_excavator_character_8h.html',1,'']]], + ['excavatorsimulator_2ebuild_2ecs_4',['ExcavatorSimulator.Build.cs',['../_excavator_simulator_8_build_8cs.html',1,'']]], + ['excavatorsimulator_2ecpp_5',['ExcavatorSimulator.cpp',['../_excavator_simulator_8cpp.html',1,'']]], + ['excavatorsimulator_2eh_6',['ExcavatorSimulator.h',['../_excavator_simulator_8h.html',1,'']]], + ['excavatorsimulatorgamemodebase_2ecpp_7',['ExcavatorSimulatorGameModeBase.cpp',['../_excavator_simulator_game_mode_base_8cpp.html',1,'']]], + ['excavatorsimulatorgamemodebase_2eh_8',['ExcavatorSimulatorGameModeBase.h',['../_excavator_simulator_game_mode_base_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/files_2.js b/Doxygen/html/search/files_2.js new file mode 100644 index 0000000..6c09624 --- /dev/null +++ b/Doxygen/html/search/files_2.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['grounddeformercomponent_2ecpp_0',['GroundDeformerComponent.cpp',['../_ground_deformer_component_8cpp.html',1,'']]], + ['grounddeformercomponent_2eh_1',['GroundDeformerComponent.h',['../_ground_deformer_component_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/files_3.js b/Doxygen/html/search/files_3.js new file mode 100644 index 0000000..dbc9bf2 --- /dev/null +++ b/Doxygen/html/search/files_3.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['worldgenerator_2ecpp_0',['WorldGenerator.cpp',['../_world_generator_8cpp.html',1,'']]], + ['worldgenerator_2eh_1',['WorldGenerator.h',['../_world_generator_8h.html',1,'']]] +]; diff --git a/Doxygen/html/search/functions_0.js b/Doxygen/html/search/functions_0.js new file mode 100644 index 0000000..9c1b86b --- /dev/null +++ b/Doxygen/html/search/functions_0.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['acustomproceduralmeshcomponent_0',['ACustomProceduralMeshComponent',['../class_a_custom_procedural_mesh_component.html#ad2a083e99c4303ff692c6de7fd7c89d7',1,'ACustomProceduralMeshComponent']]], + ['aexcavatorcharacter_1',['AExcavatorCharacter',['../class_a_excavator_character.html#a897f85cbfeb62c8cf9357124030678f0',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/functions_1.js b/Doxygen/html/search/functions_1.js new file mode 100644 index 0000000..96e35f6 --- /dev/null +++ b/Doxygen/html/search/functions_1.js @@ -0,0 +1,12 @@ +var searchData= +[ + ['beambottomrotation_0',['BeamBottomRotation',['../class_a_excavator_character.html#a4c0322adf74c147b25e0a56eb82e8b63',1,'AExcavatorCharacter']]], + ['beambottomrotationvr_1',['BeamBottomRotationVR',['../class_a_excavator_character.html#a2fd6354fdc9f48c9ec37aa8a0881bb39',1,'AExcavatorCharacter']]], + ['beamtoprotation_2',['BeamTopRotation',['../class_a_excavator_character.html#a700a5e4f8e43868c15d6b60460006eb9',1,'AExcavatorCharacter']]], + ['beamtoprotationvr_3',['BeamTopRotationVR',['../class_a_excavator_character.html#a0fa69b8a1aa0f05dad74a41da45f5551',1,'AExcavatorCharacter']]], + ['beginplay_4',['BeginPlay',['../class_a_custom_procedural_mesh_component.html#a91a659f1e60e2aadddc5ffd88372f66d',1,'ACustomProceduralMeshComponent::BeginPlay()'],['../class_a_excavator_character.html#aff0f3c6a77a15a8476981c7845dff158',1,'AExcavatorCharacter::BeginPlay() override']]], + ['bodyrotation_5',['BodyRotation',['../class_a_excavator_character.html#a63014640642f4964440cd3923cc8396f',1,'AExcavatorCharacter']]], + ['bodyrotationvr_6',['BodyRotationVR',['../class_a_excavator_character.html#a52c7b1ac54ccb49c0cd5e9baf25dc338',1,'AExcavatorCharacter']]], + ['bucketrotationreleacerocks_7',['BucketRotationReleaceRocks',['../class_a_excavator_character.html#a18933e6161a7489652e64e06d6dc9d72',1,'AExcavatorCharacter']]], + ['bucketrotationvr_8',['BucketRotationVR',['../class_a_excavator_character.html#a5eaf154b0d816e682669609f6a1145b4',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/functions_2.js b/Doxygen/html/search/functions_2.js new file mode 100644 index 0000000..55aaa74 --- /dev/null +++ b/Doxygen/html/search/functions_2.js @@ -0,0 +1,6 @@ +var searchData= +[ + ['createtriangle_0',['CreateTriangle',['../class_a_custom_procedural_mesh_component.html#a9d663c4e5f670976a2e9a9de0b9e9268',1,'ACustomProceduralMeshComponent']]], + ['createvertices_1',['CreateVertices',['../class_a_custom_procedural_mesh_component.html#a722837f83f1b0db0cb97f47b50d310b3',1,'ACustomProceduralMeshComponent']]], + ['createwall_2',['CreateWall',['../class_a_custom_procedural_mesh_component.html#ac1c8d09545e81f4cee3a33d8e1ff0810',1,'ACustomProceduralMeshComponent']]] +]; diff --git a/Doxygen/html/search/functions_3.js b/Doxygen/html/search/functions_3.js new file mode 100644 index 0000000..064ff99 --- /dev/null +++ b/Doxygen/html/search/functions_3.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['excavatorsimulator_0',['ExcavatorSimulator',['../class_excavator_simulator.html#ac3cbb6777236f4b4900e850d58050951',1,'ExcavatorSimulator']]] +]; diff --git a/Doxygen/html/search/functions_4.js b/Doxygen/html/search/functions_4.js new file mode 100644 index 0000000..d5e7e93 --- /dev/null +++ b/Doxygen/html/search/functions_4.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['fworldgeneratorinstance_0',['FWorldGeneratorInstance',['../class_f_world_generator_instance.html#a9340a460da1c5a05a25e496f5bbe3e47',1,'FWorldGeneratorInstance']]] +]; diff --git a/Doxygen/html/search/functions_5.js b/Doxygen/html/search/functions_5.js new file mode 100644 index 0000000..86cb592 --- /dev/null +++ b/Doxygen/html/search/functions_5.js @@ -0,0 +1,18 @@ +var searchData= +[ + ['getbeambottomrotation_0',['GetBeamBottomRotation',['../class_u_excavator_anim.html#affb325f375a952a31fd6c7edbda878b2',1,'UExcavatorAnim']]], + ['getbeamtoprotation_1',['GetBeamTopRotation',['../class_u_excavator_anim.html#a1cf8d06387c0c8bf61315209a2277b89',1,'UExcavatorAnim']]], + ['getbodyrotation_2',['GetBodyRotation',['../class_u_excavator_anim.html#adf9be335c9520d1b730d0c983ad42734',1,'UExcavatorAnim']]], + ['getbottommaxrotation_3',['GetBottomMaxRotation',['../class_u_excavator_anim.html#a19af34e8fb205cccbe3796330e5fff79',1,'UExcavatorAnim']]], + ['getbottomminrotation_4',['GetBottomMinRotation',['../class_u_excavator_anim.html#a290c2158c3bd36b4cf9f6cf53395c44b',1,'UExcavatorAnim']]], + ['getbucketmaxrotation_5',['GetBucketMaxRotation',['../class_u_excavator_anim.html#a7e4af257d3369d934d999c5889bfe60f',1,'UExcavatorAnim']]], + ['getbucketminrotation_6',['GetBucketMinRotation',['../class_u_excavator_anim.html#a6542745f31287654904430a08208f4dc',1,'UExcavatorAnim']]], + ['getbucketrotation_7',['GetBucketRotation',['../class_u_excavator_anim.html#ad206f925afc96462b27ebd5c33273da5',1,'UExcavatorAnim']]], + ['getinstance_8',['GetInstance',['../class_u_world_generator.html#a92908af10f0cdfee492c5e21ba1000d1',1,'UWorldGenerator']]], + ['getmaterialimpl_9',['GetMaterialImpl',['../class_f_world_generator_instance.html#a5d982bbb90e6a7368d4eab2bbf4107a0',1,'FWorldGeneratorInstance']]], + ['gettopmaxrotation_10',['GetTopMaxRotation',['../class_u_excavator_anim.html#a56e5697f1cb92abaf41d0af039e641b1',1,'UExcavatorAnim']]], + ['gettopminrotation_11',['GetTopMinRotation',['../class_u_excavator_anim.html#ad7c23fdcc7208d1e925a0ea8ac09b793',1,'UExcavatorAnim']]], + ['getupvector_12',['GetUpVector',['../class_f_world_generator_instance.html#a41a32aee12e9228c3ec2bdfa4be7a92a',1,'FWorldGeneratorInstance']]], + ['getvalueimpl_13',['GetValueImpl',['../class_f_world_generator_instance.html#a9a18ba21423f11dc01d3a2e54fbd214f',1,'FWorldGeneratorInstance']]], + ['getvaluerangeimpl_14',['GetValueRangeImpl',['../class_f_world_generator_instance.html#aa18eccf536acdf8d2115f32ac0dab1ef',1,'FWorldGeneratorInstance']]] +]; diff --git a/Doxygen/html/search/functions_6.js b/Doxygen/html/search/functions_6.js new file mode 100644 index 0000000..ecacf38 --- /dev/null +++ b/Doxygen/html/search/functions_6.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['implement_5fprimary_5fgame_5fmodule_0',['IMPLEMENT_PRIMARY_GAME_MODULE',['../_excavator_simulator_8cpp.html#abdfae4c413c9ef6bcaa5cf0c657807d7',1,'ExcavatorSimulator.cpp']]], + ['init_1',['Init',['../class_f_world_generator_instance.html#acb9442be2e711fb3ba4d146bf584bd5d',1,'FWorldGeneratorInstance']]] +]; diff --git a/Doxygen/html/search/functions_7.js b/Doxygen/html/search/functions_7.js new file mode 100644 index 0000000..537c9de --- /dev/null +++ b/Doxygen/html/search/functions_7.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['movement_0',['Movement',['../class_a_excavator_character.html#abfc64bb0a310badadb9e85e7336b0735',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/functions_8.js b/Doxygen/html/search/functions_8.js new file mode 100644 index 0000000..54f298b --- /dev/null +++ b/Doxygen/html/search/functions_8.js @@ -0,0 +1,8 @@ +var searchData= +[ + ['setbeambottomrotation_0',['SetBeamBottomRotation',['../class_u_excavator_anim.html#a09b8e1814d38428a70423d3d81922448',1,'UExcavatorAnim']]], + ['setbeamtoprotation_1',['SetBeamTopRotation',['../class_u_excavator_anim.html#a59c863bb954f22c019bc20b6d4b947df',1,'UExcavatorAnim']]], + ['setbodyrotation_2',['SetBodyRotation',['../class_u_excavator_anim.html#a215798edeb92f27e3e5ad9ad5c25626a',1,'UExcavatorAnim']]], + ['setbucketrotation_3',['SetBucketRotation',['../class_u_excavator_anim.html#aba254277652747c4c1dabd60308b4c17',1,'UExcavatorAnim']]], + ['setupplayerinputcomponent_4',['SetupPlayerInputComponent',['../class_a_excavator_character.html#ae0c6e52ac2b153baa789be4e0b2b75fc',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/functions_9.js b/Doxygen/html/search/functions_9.js new file mode 100644 index 0000000..aa0726a --- /dev/null +++ b/Doxygen/html/search/functions_9.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['tick_0',['Tick',['../class_a_custom_procedural_mesh_component.html#a9b6c8c619a8d0396e2d3d19f61007427',1,'ACustomProceduralMeshComponent::Tick()'],['../class_a_excavator_character.html#a080c503815b1f096cf7704e5bfa47df1',1,'AExcavatorCharacter::Tick()']]] +]; diff --git a/Doxygen/html/search/functions_a.js b/Doxygen/html/search/functions_a.js new file mode 100644 index 0000000..607a51d --- /dev/null +++ b/Doxygen/html/search/functions_a.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['uexcavatoranim_0',['UExcavatorAnim',['../class_u_excavator_anim.html#a17a5fcd9bd609ed5ad1538906286184c',1,'UExcavatorAnim']]], + ['uworldgenerator_1',['UWorldGenerator',['../class_u_world_generator.html#a4ea59a679fc8eff20b3b143461020977',1,'UWorldGenerator']]] +]; diff --git a/Doxygen/html/search/functions_b.js b/Doxygen/html/search/functions_b.js new file mode 100644 index 0000000..72f5f93 --- /dev/null +++ b/Doxygen/html/search/functions_b.js @@ -0,0 +1,5 @@ +var searchData= +[ + ['vehiclerotation_0',['VehicleRotation',['../class_a_excavator_character.html#a3f262813a65448739540f41c73498f56',1,'AExcavatorCharacter']]], + ['vehiclerotationvr_1',['VehicleRotationVR',['../class_a_excavator_character.html#a173be62746e6adc66879eea680f21d56',1,'AExcavatorCharacter']]] +]; diff --git a/Doxygen/html/search/functions_c.js b/Doxygen/html/search/functions_c.js new file mode 100644 index 0000000..d3df789 --- /dev/null +++ b/Doxygen/html/search/functions_c.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['_7euworldgenerator_0',['~UWorldGenerator',['../class_u_world_generator.html#ad2e31d185549690f85ddec11f48a709e',1,'UWorldGenerator']]] +]; diff --git a/Doxygen/html/search/mag.svg b/Doxygen/html/search/mag.svg new file mode 100644 index 0000000..9f46b30 --- /dev/null +++ b/Doxygen/html/search/mag.svg @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 20 19" + height="19" + width="20" + id="svg2" + version="1.1"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <circle + r="3.5" + cy="8.5" + cx="5.5" + id="path4611" + style="fill:#000000;fill-opacity:0;stroke:#656565;stroke-width:1.4;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" /> + <path + id="path4630" + d="m 8.1085854,11.109059 2.7823556,2.782356" + style="fill:none;stroke:#656565;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> +</svg> diff --git a/Doxygen/html/search/mag_d.svg b/Doxygen/html/search/mag_d.svg new file mode 100644 index 0000000..b9a814c --- /dev/null +++ b/Doxygen/html/search/mag_d.svg @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 20 19" + height="19" + width="20" + id="svg2" + version="1.1"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <circle + r="3.5" + cy="8.5" + cx="5.5" + id="path4611" + style="fill:#000000;fill-opacity:0;stroke:#C5C5C5;stroke-width:1.4;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" /> + <path + id="path4630" + d="m 8.1085854,11.109059 2.7823556,2.782356" + style="fill:none;stroke:#C5C5C5;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> +</svg> diff --git a/Doxygen/html/search/mag_sel.svg b/Doxygen/html/search/mag_sel.svg new file mode 100644 index 0000000..03626f6 --- /dev/null +++ b/Doxygen/html/search/mag_sel.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + width="20" + height="19" + viewBox="0 0 20 19" + sodipodi:docname="mag_sel.svg" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="2096" + id="namedview4" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="32" + inkscape:cx="5.9792688" + inkscape:cy="1.1436277" + inkscape:window-x="1920" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <circle + style="fill:#000000;fill-opacity:0;stroke:#656565;stroke-width:1.4;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" + id="path4611" + cx="5.5" + cy="8.5" + r="3.5" /> + <path + style="fill:#656565;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 11,7 13.5,10 16,7 Z" + id="path4609" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#656565;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 8.1085854,11.109059 2.7823556,2.782356" + id="path4630" + inkscape:connector-curvature="0" /> +</svg> diff --git a/Doxygen/html/search/mag_seld.svg b/Doxygen/html/search/mag_seld.svg new file mode 100644 index 0000000..6e720dc --- /dev/null +++ b/Doxygen/html/search/mag_seld.svg @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + width="20" + height="19" + viewBox="0 0 20 19" + sodipodi:docname="mag_sel.svg" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="2096" + id="namedview4" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="32" + inkscape:cx="5.9792688" + inkscape:cy="1.1436277" + inkscape:window-x="1920" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <circle + style="fill:#000000;fill-opacity:0;stroke:#C5C5C5;stroke-width:1.4;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" + id="path4611" + cx="5.5" + cy="8.5" + r="3.5" /> + <path + style="fill:#C5C5C5;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 11,7 13.5,10 16,7 Z" + id="path4609" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" /> + <path + style="fill:none;stroke:#C5C5C5;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 8.1085854,11.109059 2.7823556,2.782356" + id="path4630" + inkscape:connector-curvature="0" /> +</svg> diff --git a/Doxygen/html/search/search.css b/Doxygen/html/search/search.css new file mode 100644 index 0000000..19f76f9 --- /dev/null +++ b/Doxygen/html/search/search.css @@ -0,0 +1,291 @@ +/*---------------- Search Box positioning */ + +#main-menu > li:last-child { + /* This <li> object is the parent of the search bar */ + display: flex; + justify-content: center; + align-items: center; + height: 36px; + margin-right: 1em; +} + +/*---------------- Search box styling */ + +.SRPage * { + font-weight: normal; + line-height: normal; +} + +dark-mode-toggle { + margin-left: 5px; + display: flex; + float: right; +} + +#MSearchBox { + display: inline-block; + white-space : nowrap; + background: var(--search-background-color); + border-radius: 0.65em; + box-shadow: var(--search-box-shadow); + z-index: 102; +} + +#MSearchBox .left { + display: inline-block; + vertical-align: middle; + height: 1.4em; +} + +#MSearchSelect { + display: inline-block; + vertical-align: middle; + width: 20px; + height: 19px; + background-image: var(--search-magnification-select-image); + margin: 0 0 0 0.3em; + padding: 0; +} + +#MSearchSelectExt { + display: inline-block; + vertical-align: middle; + width: 10px; + height: 19px; + background-image: var(--search-magnification-image); + margin: 0 0 0 0.5em; + padding: 0; +} + + +#MSearchField { + display: inline-block; + vertical-align: middle; + width: 7.5em; + height: 19px; + margin: 0 0.15em; + padding: 0; + line-height: 1em; + border:none; + color: var(--search-foreground-color); + outline: none; + font-family: var(--font-family-search); + -webkit-border-radius: 0px; + border-radius: 0px; + background: none; +} + +@media(hover: none) { + /* to avoid zooming on iOS */ + #MSearchField { + font-size: 16px; + } +} + +#MSearchBox .right { + display: inline-block; + vertical-align: middle; + width: 1.4em; + height: 1.4em; +} + +#MSearchClose { + display: none; + font-size: inherit; + background : none; + border: none; + margin: 0; + padding: 0; + outline: none; + +} + +#MSearchCloseImg { + padding: 0.3em; + margin: 0; +} + +.MSearchBoxActive #MSearchField { + color: var(--search-active-color); +} + + + +/*---------------- Search filter selection */ + +#MSearchSelectWindow { + display: none; + position: absolute; + left: 0; top: 0; + border: 1px solid var(--search-filter-border-color); + background-color: var(--search-filter-background-color); + z-index: 10001; + padding-top: 4px; + padding-bottom: 4px; + -moz-border-radius: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +.SelectItem { + font: 8pt var(--font-family-search); + padding-left: 2px; + padding-right: 12px; + border: 0px; +} + +span.SelectionMark { + margin-right: 4px; + font-family: var(--font-family-monospace); + outline-style: none; + text-decoration: none; +} + +a.SelectItem { + display: block; + outline-style: none; + color: var(--search-filter-foreground-color); + text-decoration: none; + padding-left: 6px; + padding-right: 12px; +} + +a.SelectItem:focus, +a.SelectItem:active { + color: var(--search-filter-foreground-color); + outline-style: none; + text-decoration: none; +} + +a.SelectItem:hover { + color: var(--search-filter-highlight-text-color); + background-color: var(--search-filter-highlight-bg-color); + outline-style: none; + text-decoration: none; + cursor: pointer; + display: block; +} + +/*---------------- Search results window */ + +iframe#MSearchResults { + /*width: 60ex;*/ + height: 15em; +} + +#MSearchResultsWindow { + display: none; + position: absolute; + left: 0; top: 0; + border: 1px solid var(--search-results-border-color); + background-color: var(--search-results-background-color); + z-index:10000; + width: 300px; + height: 400px; + overflow: auto; +} + +/* ----------------------------------- */ + + +#SRIndex { + clear:both; +} + +.SREntry { + font-size: 10pt; + padding-left: 1ex; +} + +.SRPage .SREntry { + font-size: 8pt; + padding: 1px 5px; +} + +div.SRPage { + margin: 5px 2px; + background-color: var(--search-results-background-color); +} + +.SRChildren { + padding-left: 3ex; padding-bottom: .5em +} + +.SRPage .SRChildren { + display: none; +} + +.SRSymbol { + font-weight: bold; + color: var(--search-results-foreground-color); + font-family: var(--font-family-search); + text-decoration: none; + outline: none; +} + +a.SRScope { + display: block; + color: var(--search-results-foreground-color); + font-family: var(--font-family-search); + font-size: 8pt; + text-decoration: none; + outline: none; +} + +a.SRSymbol:focus, a.SRSymbol:active, +a.SRScope:focus, a.SRScope:active { + text-decoration: underline; +} + +span.SRScope { + padding-left: 4px; + font-family: var(--font-family-search); +} + +.SRPage .SRStatus { + padding: 2px 5px; + font-size: 8pt; + font-style: italic; + font-family: var(--font-family-search); +} + +.SRResult { + display: none; +} + +div.searchresults { + margin-left: 10px; + margin-right: 10px; +} + +/*---------------- External search page results */ + +.pages b { + color: white; + padding: 5px 5px 3px 5px; + background-image: var(--nav-gradient-active-image-parent); + background-repeat: repeat-x; + text-shadow: 0 1px 1px #000000; +} + +.pages { + line-height: 17px; + margin-left: 4px; + text-decoration: none; +} + +.hl { + font-weight: bold; +} + +#searchresults { + margin-bottom: 20px; +} + +.searchpages { + margin-top: 10px; +} + diff --git a/Doxygen/html/search/search.js b/Doxygen/html/search/search.js new file mode 100644 index 0000000..e103a26 --- /dev/null +++ b/Doxygen/html/search/search.js @@ -0,0 +1,816 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + 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. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function convertToId(search) +{ + var result = ''; + for (i=0;i<search.length;i++) + { + var c = search.charAt(i); + var cn = c.charCodeAt(0); + if (c.match(/[a-z0-9\u0080-\uFFFF]/)) + { + result+=c; + } + else if (cn<16) + { + result+="_0"+cn.toString(16); + } + else + { + result+="_"+cn.toString(16); + } + } + return result; +} + +function getXPos(item) +{ + var x = 0; + if (item.offsetWidth) + { + while (item && item!=document.body) + { + x += item.offsetLeft; + item = item.offsetParent; + } + } + return x; +} + +function getYPos(item) +{ + var y = 0; + if (item.offsetWidth) + { + while (item && item!=document.body) + { + y += item.offsetTop; + item = item.offsetParent; + } + } + return y; +} + +var searchResults = new SearchResults("searchResults"); + +/* A class handling everything associated with the search panel. + + Parameters: + name - The name of the global variable that will be + storing this instance. Is needed to be able to set timeouts. + resultPath - path to use for external files +*/ +function SearchBox(name, resultsPath, extension) +{ + if (!name || !resultsPath) { alert("Missing parameters to SearchBox."); } + if (!extension || extension == "") { extension = ".html"; } + + // ---------- Instance variables + this.name = name; + this.resultsPath = resultsPath; + this.keyTimeout = 0; + this.keyTimeoutLength = 500; + this.closeSelectionTimeout = 300; + this.lastSearchValue = ""; + this.lastResultsPage = ""; + this.hideTimeout = 0; + this.searchIndex = 0; + this.searchActive = false; + this.extension = extension; + + // ----------- DOM Elements + + this.DOMSearchField = function() + { return document.getElementById("MSearchField"); } + + this.DOMSearchSelect = function() + { return document.getElementById("MSearchSelect"); } + + this.DOMSearchSelectWindow = function() + { return document.getElementById("MSearchSelectWindow"); } + + this.DOMPopupSearchResults = function() + { return document.getElementById("MSearchResults"); } + + this.DOMPopupSearchResultsWindow = function() + { return document.getElementById("MSearchResultsWindow"); } + + this.DOMSearchClose = function() + { return document.getElementById("MSearchClose"); } + + this.DOMSearchBox = function() + { return document.getElementById("MSearchBox"); } + + // ------------ Event Handlers + + // Called when focus is added or removed from the search field. + this.OnSearchFieldFocus = function(isActive) + { + this.Activate(isActive); + } + + this.OnSearchSelectShow = function() + { + var searchSelectWindow = this.DOMSearchSelectWindow(); + var searchField = this.DOMSearchSelect(); + + var left = getXPos(searchField); + var top = getYPos(searchField); + top += searchField.offsetHeight; + + // show search selection popup + searchSelectWindow.style.display='block'; + searchSelectWindow.style.left = left + 'px'; + searchSelectWindow.style.top = top + 'px'; + + // stop selection hide timer + if (this.hideTimeout) + { + clearTimeout(this.hideTimeout); + this.hideTimeout=0; + } + return false; // to avoid "image drag" default event + } + + this.OnSearchSelectHide = function() + { + this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()", + this.closeSelectionTimeout); + } + + // Called when the content of the search field is changed. + this.OnSearchFieldChange = function(evt) + { + if (this.keyTimeout) // kill running timer + { + clearTimeout(this.keyTimeout); + this.keyTimeout = 0; + } + + var e = (evt) ? evt : window.event; // for IE + if (e.keyCode==40 || e.keyCode==13) + { + if (e.shiftKey==1) + { + this.OnSearchSelectShow(); + var win=this.DOMSearchSelectWindow(); + for (i=0;i<win.childNodes.length;i++) + { + var child = win.childNodes[i]; // get span within a + if (child.className=='SelectItem') + { + child.focus(); + return; + } + } + return; + } + else + { + var elem = searchResults.NavNext(0); + if (elem) elem.focus(); + } + } + else if (e.keyCode==27) // Escape out of the search field + { + this.DOMSearchField().blur(); + this.DOMPopupSearchResultsWindow().style.display = 'none'; + this.DOMSearchClose().style.display = 'none'; + this.lastSearchValue = ''; + this.Activate(false); + return; + } + + // strip whitespaces + var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); + + if (searchValue != this.lastSearchValue) // search value has changed + { + if (searchValue != "") // non-empty search + { + // set timer for search update + this.keyTimeout = setTimeout(this.name + '.Search()', + this.keyTimeoutLength); + } + else // empty search field + { + this.DOMPopupSearchResultsWindow().style.display = 'none'; + this.DOMSearchClose().style.display = 'none'; + this.lastSearchValue = ''; + } + } + } + + this.SelectItemCount = function(id) + { + var count=0; + var win=this.DOMSearchSelectWindow(); + for (i=0;i<win.childNodes.length;i++) + { + var child = win.childNodes[i]; // get span within a + if (child.className=='SelectItem') + { + count++; + } + } + return count; + } + + this.SelectItemSet = function(id) + { + var i,j=0; + var win=this.DOMSearchSelectWindow(); + for (i=0;i<win.childNodes.length;i++) + { + var child = win.childNodes[i]; // get span within a + if (child.className=='SelectItem') + { + var node = child.firstChild; + if (j==id) + { + node.innerHTML='•'; + } + else + { + node.innerHTML=' '; + } + j++; + } + } + } + + // Called when an search filter selection is made. + // set item with index id as the active item + this.OnSelectItem = function(id) + { + this.searchIndex = id; + this.SelectItemSet(id); + var searchValue = this.DOMSearchField().value.replace(/ +/g, ""); + if (searchValue!="" && this.searchActive) // something was found -> do a search + { + this.Search(); + } + } + + this.OnSearchSelectKey = function(evt) + { + var e = (evt) ? evt : window.event; // for IE + if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down + { + this.searchIndex++; + this.OnSelectItem(this.searchIndex); + } + else if (e.keyCode==38 && this.searchIndex>0) // Up + { + this.searchIndex--; + this.OnSelectItem(this.searchIndex); + } + else if (e.keyCode==13 || e.keyCode==27) + { + this.OnSelectItem(this.searchIndex); + this.CloseSelectionWindow(); + this.DOMSearchField().focus(); + } + return false; + } + + // --------- Actions + + // Closes the results window. + this.CloseResultsWindow = function() + { + this.DOMPopupSearchResultsWindow().style.display = 'none'; + this.DOMSearchClose().style.display = 'none'; + this.Activate(false); + } + + this.CloseSelectionWindow = function() + { + this.DOMSearchSelectWindow().style.display = 'none'; + } + + // Performs a search. + this.Search = function() + { + this.keyTimeout = 0; + + // strip leading whitespace + var searchValue = this.DOMSearchField().value.replace(/^ +/, ""); + + var code = searchValue.toLowerCase().charCodeAt(0); + var idxChar = searchValue.substr(0, 1).toLowerCase(); + if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair + { + idxChar = searchValue.substr(0, 2); + } + + var jsFile; + + var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar); + if (idx!=-1) + { + var hexCode=idx.toString(16); + jsFile = this.resultsPath + indexSectionNames[this.searchIndex] + '_' + hexCode + '.js'; + } + + var loadJS = function(url, impl, loc){ + var scriptTag = document.createElement('script'); + scriptTag.src = url; + scriptTag.onload = impl; + scriptTag.onreadystatechange = impl; + loc.appendChild(scriptTag); + } + + var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow(); + var domSearchBox = this.DOMSearchBox(); + var domPopupSearchResults = this.DOMPopupSearchResults(); + var domSearchClose = this.DOMSearchClose(); + var resultsPath = this.resultsPath; + + var handleResults = function() { + document.getElementById("Loading").style.display="none"; + if (typeof searchData !== 'undefined') { + createResults(resultsPath); + document.getElementById("NoMatches").style.display="none"; + } + + searchResults.Search(searchValue); + + if (domPopupSearchResultsWindow.style.display!='block') + { + domSearchClose.style.display = 'inline-block'; + var left = getXPos(domSearchBox) + 150; + var top = getYPos(domSearchBox) + 20; + domPopupSearchResultsWindow.style.display = 'block'; + left -= domPopupSearchResults.offsetWidth; + var maxWidth = document.body.clientWidth; + var maxHeight = document.body.clientHeight; + var width = 300; + if (left<10) left=10; + if (width+left+8>maxWidth) width=maxWidth-left-8; + var height = 400; + if (height+top+8>maxHeight) height=maxHeight-top-8; + domPopupSearchResultsWindow.style.top = top + 'px'; + domPopupSearchResultsWindow.style.left = left + 'px'; + domPopupSearchResultsWindow.style.width = width + 'px'; + domPopupSearchResultsWindow.style.height = height + 'px'; + } + } + + if (jsFile) { + loadJS(jsFile, handleResults, this.DOMPopupSearchResultsWindow()); + } else { + handleResults(); + } + + this.lastSearchValue = searchValue; + } + + // -------- Activation Functions + + // Activates or deactivates the search panel, resetting things to + // their default values if necessary. + this.Activate = function(isActive) + { + if (isActive || // open it + this.DOMPopupSearchResultsWindow().style.display == 'block' + ) + { + this.DOMSearchBox().className = 'MSearchBoxActive'; + this.searchActive = true; + } + else if (!isActive) // directly remove the panel + { + this.DOMSearchBox().className = 'MSearchBoxInactive'; + this.searchActive = false; + this.lastSearchValue = '' + this.lastResultsPage = ''; + this.DOMSearchField().value = ''; + } + } +} + +// ----------------------------------------------------------------------- + +// The class that handles everything on the search results page. +function SearchResults(name) +{ + // The number of matches from the last run of <Search()>. + this.lastMatchCount = 0; + this.lastKey = 0; + this.repeatOn = false; + + // Toggles the visibility of the passed element ID. + this.FindChildElement = function(id) + { + var parentElement = document.getElementById(id); + var element = parentElement.firstChild; + + while (element && element!=parentElement) + { + if (element.nodeName.toLowerCase() == 'div' && element.className == 'SRChildren') + { + return element; + } + + if (element.nodeName.toLowerCase() == 'div' && element.hasChildNodes()) + { + element = element.firstChild; + } + else if (element.nextSibling) + { + element = element.nextSibling; + } + else + { + do + { + element = element.parentNode; + } + while (element && element!=parentElement && !element.nextSibling); + + if (element && element!=parentElement) + { + element = element.nextSibling; + } + } + } + } + + this.Toggle = function(id) + { + var element = this.FindChildElement(id); + if (element) + { + if (element.style.display == 'block') + { + element.style.display = 'none'; + } + else + { + element.style.display = 'block'; + } + } + } + + // Searches for the passed string. If there is no parameter, + // it takes it from the URL query. + // + // Always returns true, since other documents may try to call it + // and that may or may not be possible. + this.Search = function(search) + { + if (!search) // get search word from URL + { + search = window.location.search; + search = search.substring(1); // Remove the leading '?' + search = unescape(search); + } + + search = search.replace(/^ +/, ""); // strip leading spaces + search = search.replace(/ +$/, ""); // strip trailing spaces + search = search.toLowerCase(); + search = convertToId(search); + + var resultRows = document.getElementsByTagName("div"); + var matches = 0; + + var i = 0; + while (i < resultRows.length) + { + var row = resultRows.item(i); + if (row.className == "SRResult") + { + var rowMatchName = row.id.toLowerCase(); + rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_' + + if (search.length<=rowMatchName.length && + rowMatchName.substr(0, search.length)==search) + { + row.style.display = 'block'; + matches++; + } + else + { + row.style.display = 'none'; + } + } + i++; + } + document.getElementById("Searching").style.display='none'; + if (matches == 0) // no results + { + document.getElementById("NoMatches").style.display='block'; + } + else // at least one result + { + document.getElementById("NoMatches").style.display='none'; + } + this.lastMatchCount = matches; + return true; + } + + // return the first item with index index or higher that is visible + this.NavNext = function(index) + { + var focusItem; + while (1) + { + var focusName = 'Item'+index; + focusItem = document.getElementById(focusName); + if (focusItem && focusItem.parentNode.parentNode.style.display=='block') + { + break; + } + else if (!focusItem) // last element + { + break; + } + focusItem=null; + index++; + } + return focusItem; + } + + this.NavPrev = function(index) + { + var focusItem; + while (1) + { + var focusName = 'Item'+index; + focusItem = document.getElementById(focusName); + if (focusItem && focusItem.parentNode.parentNode.style.display=='block') + { + break; + } + else if (!focusItem) // last element + { + break; + } + focusItem=null; + index--; + } + return focusItem; + } + + this.ProcessKeys = function(e) + { + if (e.type == "keydown") + { + this.repeatOn = false; + this.lastKey = e.keyCode; + } + else if (e.type == "keypress") + { + if (!this.repeatOn) + { + if (this.lastKey) this.repeatOn = true; + return false; // ignore first keypress after keydown + } + } + else if (e.type == "keyup") + { + this.lastKey = 0; + this.repeatOn = false; + } + return this.lastKey!=0; + } + + this.Nav = function(evt,itemIndex) + { + var e = (evt) ? evt : window.event; // for IE + if (e.keyCode==13) return true; + if (!this.ProcessKeys(e)) return false; + + if (this.lastKey==38) // Up + { + var newIndex = itemIndex-1; + var focusItem = this.NavPrev(newIndex); + if (focusItem) + { + var child = this.FindChildElement(focusItem.parentNode.parentNode.id); + if (child && child.style.display == 'block') // children visible + { + var n=0; + var tmpElem; + while (1) // search for last child + { + tmpElem = document.getElementById('Item'+newIndex+'_c'+n); + if (tmpElem) + { + focusItem = tmpElem; + } + else // found it! + { + break; + } + n++; + } + } + } + if (focusItem) + { + focusItem.focus(); + } + else // return focus to search field + { + document.getElementById("MSearchField").focus(); + } + } + else if (this.lastKey==40) // Down + { + var newIndex = itemIndex+1; + var focusItem; + var item = document.getElementById('Item'+itemIndex); + var elem = this.FindChildElement(item.parentNode.parentNode.id); + if (elem && elem.style.display == 'block') // children visible + { + focusItem = document.getElementById('Item'+itemIndex+'_c0'); + } + if (!focusItem) focusItem = this.NavNext(newIndex); + if (focusItem) focusItem.focus(); + } + else if (this.lastKey==39) // Right + { + var item = document.getElementById('Item'+itemIndex); + var elem = this.FindChildElement(item.parentNode.parentNode.id); + if (elem) elem.style.display = 'block'; + } + else if (this.lastKey==37) // Left + { + var item = document.getElementById('Item'+itemIndex); + var elem = this.FindChildElement(item.parentNode.parentNode.id); + if (elem) elem.style.display = 'none'; + } + else if (this.lastKey==27) // Escape + { + searchBox.CloseResultsWindow(); + document.getElementById("MSearchField").focus(); + } + else if (this.lastKey==13) // Enter + { + return true; + } + return false; + } + + this.NavChild = function(evt,itemIndex,childIndex) + { + var e = (evt) ? evt : window.event; // for IE + if (e.keyCode==13) return true; + if (!this.ProcessKeys(e)) return false; + + if (this.lastKey==38) // Up + { + if (childIndex>0) + { + var newIndex = childIndex-1; + document.getElementById('Item'+itemIndex+'_c'+newIndex).focus(); + } + else // already at first child, jump to parent + { + document.getElementById('Item'+itemIndex).focus(); + } + } + else if (this.lastKey==40) // Down + { + var newIndex = childIndex+1; + var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex); + if (!elem) // last child, jump to parent next parent + { + elem = this.NavNext(itemIndex+1); + } + if (elem) + { + elem.focus(); + } + } + else if (this.lastKey==27) // Escape + { + searchBox.CloseResultsWindow(); + document.getElementById("MSearchField").focus(); + } + else if (this.lastKey==13) // Enter + { + return true; + } + return false; + } +} + +function setKeyActions(elem,action) +{ + elem.setAttribute('onkeydown',action); + elem.setAttribute('onkeypress',action); + elem.setAttribute('onkeyup',action); +} + +function setClassAttr(elem,attr) +{ + elem.setAttribute('class',attr); + elem.setAttribute('className',attr); +} + +function createResults(resultsPath) +{ + var results = document.getElementById("SRResults"); + results.innerHTML = ''; + for (var e=0; e<searchData.length; e++) + { + var id = searchData[e][0]; + var srResult = document.createElement('div'); + srResult.setAttribute('id','SR_'+id); + setClassAttr(srResult,'SRResult'); + var srEntry = document.createElement('div'); + setClassAttr(srEntry,'SREntry'); + var srLink = document.createElement('a'); + srLink.setAttribute('id','Item'+e); + setKeyActions(srLink,'return searchResults.Nav(event,'+e+')'); + setClassAttr(srLink,'SRSymbol'); + srLink.innerHTML = searchData[e][1][0]; + srEntry.appendChild(srLink); + if (searchData[e][1].length==2) // single result + { + srLink.setAttribute('href',resultsPath+searchData[e][1][1][0]); + srLink.setAttribute('onclick','searchBox.CloseResultsWindow()'); + if (searchData[e][1][1][1]) + { + srLink.setAttribute('target','_parent'); + } + else + { + srLink.setAttribute('target','_blank'); + } + var srScope = document.createElement('span'); + setClassAttr(srScope,'SRScope'); + srScope.innerHTML = searchData[e][1][1][2]; + srEntry.appendChild(srScope); + } + else // multiple results + { + srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")'); + var srChildren = document.createElement('div'); + setClassAttr(srChildren,'SRChildren'); + for (var c=0; c<searchData[e][1].length-1; c++) + { + var srChild = document.createElement('a'); + srChild.setAttribute('id','Item'+e+'_c'+c); + setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')'); + setClassAttr(srChild,'SRScope'); + srChild.setAttribute('href',resultsPath+searchData[e][1][c+1][0]); + srChild.setAttribute('onclick','searchBox.CloseResultsWindow()'); + if (searchData[e][1][c+1][1]) + { + srChild.setAttribute('target','_parent'); + } + else + { + srChild.setAttribute('target','_blank'); + } + srChild.innerHTML = searchData[e][1][c+1][2]; + srChildren.appendChild(srChild); + } + srEntry.appendChild(srChildren); + } + srResult.appendChild(srEntry); + results.appendChild(srResult); + } +} + +function init_search() +{ + var results = document.getElementById("MSearchSelectWindow"); + for (var key in indexSectionLabels) + { + var link = document.createElement('a'); + link.setAttribute('class','SelectItem'); + link.setAttribute('onclick','searchBox.OnSelectItem('+key+')'); + link.href='javascript:void(0)'; + link.innerHTML='<span class="SelectionMark"> </span>'+indexSectionLabels[key]; + results.appendChild(link); + } + searchBox.OnSelectItem(0); +} +/* @license-end */ diff --git a/Doxygen/html/search/searchdata.js b/Doxygen/html/search/searchdata.js new file mode 100644 index 0000000..0deac85 --- /dev/null +++ b/Doxygen/html/search/searchdata.js @@ -0,0 +1,30 @@ +var indexSectionsWithContent = +{ + 0: "abcefgimnstuvw~", + 1: "aefu", + 2: "cegw", + 3: "abcefgimstuv~", + 4: "ns", + 5: "s" +}; + +var indexSectionNames = +{ + 0: "all", + 1: "classes", + 2: "files", + 3: "functions", + 4: "variables", + 5: "typedefs" +}; + +var indexSectionLabels = +{ + 0: "All", + 1: "Classes", + 2: "Files", + 3: "Functions", + 4: "Variables", + 5: "Typedefs" +}; + diff --git a/Doxygen/html/search/typedefs_0.js b/Doxygen/html/search/typedefs_0.js new file mode 100644 index 0000000..cf8d4b1 --- /dev/null +++ b/Doxygen/html/search/typedefs_0.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['super_0',['Super',['../class_f_world_generator_instance.html#a6835c93d686a4f8871078253233f7a01',1,'FWorldGeneratorInstance']]] +]; diff --git a/Doxygen/html/search/variables_0.js b/Doxygen/html/search/variables_0.js new file mode 100644 index 0000000..8b35e20 --- /dev/null +++ b/Doxygen/html/search/variables_0.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['noiseheight_0',['NoiseHeight',['../class_u_world_generator.html#a97672fed88363bef5359e4a9e1bc22d2',1,'UWorldGenerator']]] +]; diff --git a/Doxygen/html/search/variables_1.js b/Doxygen/html/search/variables_1.js new file mode 100644 index 0000000..5c4c38f --- /dev/null +++ b/Doxygen/html/search/variables_1.js @@ -0,0 +1,4 @@ +var searchData= +[ + ['seed_0',['Seed',['../class_u_world_generator.html#ae3e0313a980ab00d26cb7691a9d178e7',1,'UWorldGenerator']]] +]; diff --git a/Doxygen/html/splitbar.png b/Doxygen/html/splitbar.png new file mode 100644 index 0000000..3148d9c --- /dev/null +++ b/Doxygen/html/splitbar.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c555557ca84ba3598f52c281780dd7e22655db21ac383712e62d4540a1bc525 +size 314 diff --git a/Doxygen/html/splitbard.png b/Doxygen/html/splitbard.png new file mode 100644 index 0000000..0709d0c --- /dev/null +++ b/Doxygen/html/splitbard.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc185f49e6cf1385cd1dcbbc96511185e7db3b82170a228e6a4325d3a8884a96 +size 282 diff --git a/Doxygen/html/sync_off.png b/Doxygen/html/sync_off.png new file mode 100644 index 0000000..9fc7d81 --- /dev/null +++ b/Doxygen/html/sync_off.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39bcba0c183ec442cc90fe27a2dbafd4e1c791aff374b5326ba16880a16d9826 +size 853 diff --git a/Doxygen/html/sync_on.png b/Doxygen/html/sync_on.png new file mode 100644 index 0000000..c93c8e2 --- /dev/null +++ b/Doxygen/html/sync_on.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9abb731904dd1f8eb00aaea66bfef72d5252931d84cc01cfabde3bea854b5b14 +size 845 diff --git a/Doxygen/html/tab_a.png b/Doxygen/html/tab_a.png new file mode 100644 index 0000000..2997353 --- /dev/null +++ b/Doxygen/html/tab_a.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ddd37bdced843340e0679b6b4e7ed2fe318fd0cef76d160543722e0c3eac11f +size 142 diff --git a/Doxygen/html/tab_ad.png b/Doxygen/html/tab_ad.png new file mode 100644 index 0000000..6826810 --- /dev/null +++ b/Doxygen/html/tab_ad.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f4783d1f7ed442e14caa8124ffa47e9adb0264b5115b10885223a921ceab458 +size 135 diff --git a/Doxygen/html/tab_b.png b/Doxygen/html/tab_b.png new file mode 100644 index 0000000..97ef1fb --- /dev/null +++ b/Doxygen/html/tab_b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:901ae15db25905dca7a17b81c6d51869fd12ea569fc4b072d217786b4b4d73bd +size 169 diff --git a/Doxygen/html/tab_bd.png b/Doxygen/html/tab_bd.png new file mode 100644 index 0000000..d5325a0 --- /dev/null +++ b/Doxygen/html/tab_bd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5ecd262d002acd5d15b8691504d1ecd8f25eb8c03a11bd79e8547c330c71065 +size 173 diff --git a/Doxygen/html/tab_h.png b/Doxygen/html/tab_h.png new file mode 100644 index 0000000..62f8271 --- /dev/null +++ b/Doxygen/html/tab_h.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4b9bd9425bc87b33d6b1911e6398673939aa2f15ac505b9a1ab029b8452dd08 +size 177 diff --git a/Doxygen/html/tab_hd.png b/Doxygen/html/tab_hd.png new file mode 100644 index 0000000..524059b --- /dev/null +++ b/Doxygen/html/tab_hd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28986f7b21b7378028ae6c6668801f323db800a582e2ea52f94379fa97faa790 +size 180 diff --git a/Doxygen/html/tab_s.png b/Doxygen/html/tab_s.png new file mode 100644 index 0000000..e7f5766 --- /dev/null +++ b/Doxygen/html/tab_s.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69f392daa28adc942272615ff2db16bcf084f01ec9fcc2f7f6a632b2bba8c468 +size 184 diff --git a/Doxygen/html/tab_sd.png b/Doxygen/html/tab_sd.png new file mode 100644 index 0000000..44b1fb3 --- /dev/null +++ b/Doxygen/html/tab_sd.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f9e4f3a2e041411fa4d7195fd1c1690bbbf273afd6846417992590acd443ded +size 188 diff --git a/Doxygen/html/tabs.css b/Doxygen/html/tabs.css new file mode 100644 index 0000000..71c8a47 --- /dev/null +++ b/Doxygen/html/tabs.css @@ -0,0 +1 @@ +.sm{position:relative;z-index:9999}.sm,.sm ul,.sm li{display:block;list-style:none;margin:0;padding:0;line-height:normal;direction:ltr;text-align:left;-webkit-tap-highlight-color:rgba(0,0,0,0)}.sm-rtl,.sm-rtl ul,.sm-rtl li{direction:rtl;text-align:right}.sm>li>h1,.sm>li>h2,.sm>li>h3,.sm>li>h4,.sm>li>h5,.sm>li>h6{margin:0;padding:0}.sm ul{display:none}.sm li,.sm a{position:relative}.sm a{display:block}.sm a.disabled{cursor:not-allowed}.sm:after{content:"\00a0";display:block;height:0;font:0/0 serif;clear:both;visibility:hidden;overflow:hidden}.sm,.sm *,.sm *:before,.sm *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.main-menu-btn{position:relative;display:inline-block;width:36px;height:36px;text-indent:36px;margin-left:8px;white-space:nowrap;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.main-menu-btn-icon,.main-menu-btn-icon:before,.main-menu-btn-icon:after{position:absolute;top:50%;left:2px;height:2px;width:24px;background:var(--nav-menu-button-color);-webkit-transition:all .25s;transition:all .25s}.main-menu-btn-icon:before{content:'';top:-7px;left:0}.main-menu-btn-icon:after{content:'';top:7px;left:0}#main-menu-state:checked ~ .main-menu-btn .main-menu-btn-icon{height:0}#main-menu-state:checked ~ .main-menu-btn .main-menu-btn-icon:before{top:0;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}#main-menu-state:checked ~ .main-menu-btn .main-menu-btn-icon:after{top:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}#main-menu-state{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;overflow:hidden;clip:rect(1px,1px,1px,1px)}#main-menu-state:not(:checked) ~ #main-menu{display:none}#main-menu-state:checked ~ #main-menu{display:block}@media(min-width:768px){.main-menu-btn{position:absolute;top:-99999px}#main-menu-state:not(:checked) ~ #main-menu{display:block}}.sm-dox{background-image:var(--nav-gradient-image)}.sm-dox a,.sm-dox a:focus,.sm-dox a:hover,.sm-dox a:active{padding:0 12px;padding-right:43px;font-family:var(--font-family-nav);font-size:13px;font-weight:bold;line-height:36px;text-decoration:none;text-shadow:var(--nav-text-normal-shadow);color:var(--nav-text-normal-color);outline:0}.sm-dox a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox a.current{color:#d23600}.sm-dox a.disabled{color:#bbb}.sm-dox a span.sub-arrow{position:absolute;top:50%;margin-top:-14px;left:auto;right:3px;width:28px;height:28px;overflow:hidden;font:bold 12px/28px monospace !important;text-align:center;text-shadow:none;background:var(--nav-menu-toggle-color);-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.sm-dox a span.sub-arrow:before{display:block;content:'+'}.sm-dox a.highlighted span.sub-arrow:before{display:block;content:'-'}.sm-dox>li:first-child>a,.sm-dox>li:first-child>:not(ul) a{-moz-border-radius:5px 5px 0 0;-webkit-border-radius:5px;border-radius:5px 5px 0 0}.sm-dox>li:last-child>a,.sm-dox>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul{-moz-border-radius:0 0 5px 5px;-webkit-border-radius:0;border-radius:0 0 5px 5px}.sm-dox>li:last-child>a.highlighted,.sm-dox>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>a.highlighted,.sm-dox>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>ul>li:last-child>*:not(ul) a.highlighted{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.sm-dox ul{background:var(--nav-menu-background-color)}.sm-dox ul a,.sm-dox ul a:focus,.sm-dox ul a:hover,.sm-dox ul a:active{font-size:12px;border-left:8px solid transparent;line-height:36px;text-shadow:none;background-color:var(--nav-menu-background-color);background-image:none}.sm-dox ul a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:0 1px 1px black}.sm-dox ul ul a,.sm-dox ul ul a:hover,.sm-dox ul ul a:focus,.sm-dox ul ul a:active{border-left:16px solid transparent}.sm-dox ul ul ul a,.sm-dox ul ul ul a:hover,.sm-dox ul ul ul a:focus,.sm-dox ul ul ul a:active{border-left:24px solid transparent}.sm-dox ul ul ul ul a,.sm-dox ul ul ul ul a:hover,.sm-dox ul ul ul ul a:focus,.sm-dox ul ul ul ul a:active{border-left:32px solid transparent}.sm-dox ul ul ul ul ul a,.sm-dox ul ul ul ul ul a:hover,.sm-dox ul ul ul ul ul a:focus,.sm-dox ul ul ul ul ul a:active{border-left:40px solid transparent}@media(min-width:768px){.sm-dox ul{position:absolute;width:12em}.sm-dox li{float:left}.sm-dox.sm-rtl li{float:right}.sm-dox ul li,.sm-dox.sm-rtl ul li,.sm-dox.sm-vertical li{float:none}.sm-dox a{white-space:nowrap}.sm-dox ul a,.sm-dox.sm-vertical a{white-space:normal}.sm-dox .sm-nowrap>li>a,.sm-dox .sm-nowrap>li>:not(ul) a{white-space:nowrap}.sm-dox{padding:0 10px;background-image:var(--nav-gradient-image);line-height:36px}.sm-dox a span.sub-arrow{top:50%;margin-top:-2px;right:12px;width:0;height:0;border-width:4px;border-style:solid dashed dashed dashed;border-color:var(--nav-text-normal-color) transparent transparent transparent;background:transparent;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.sm-dox a,.sm-dox a:focus,.sm-dox a:active,.sm-dox a:hover,.sm-dox a.highlighted{padding:0 12px;background-image:var(--nav-separator-image);background-repeat:no-repeat;background-position:right;-moz-border-radius:0 !important;-webkit-border-radius:0;border-radius:0 !important}.sm-dox a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox a:hover span.sub-arrow{border-color:var(--nav-text-hover-color) transparent transparent transparent}.sm-dox a.has-submenu{padding-right:24px}.sm-dox li{border-top:0}.sm-dox>li>ul:before,.sm-dox>li>ul:after{content:'';position:absolute;top:-18px;left:30px;width:0;height:0;overflow:hidden;border-width:9px;border-style:dashed dashed solid dashed;border-color:transparent transparent #bbb transparent}.sm-dox>li>ul:after{top:-16px;left:31px;border-width:8px;border-color:transparent transparent var(--nav-menu-background-color) transparent}.sm-dox ul{border:1px solid #bbb;padding:5px 0;background:var(--nav-menu-background-color);-moz-border-radius:5px !important;-webkit-border-radius:5px;border-radius:5px !important;-moz-box-shadow:0 5px 9px rgba(0,0,0,0.2);-webkit-box-shadow:0 5px 9px rgba(0,0,0,0.2);box-shadow:0 5px 9px rgba(0,0,0,0.2)}.sm-dox ul a span.sub-arrow{right:8px;top:50%;margin-top:-5px;border-width:5px;border-color:transparent transparent transparent var(--nav-menu-foreground-color);border-style:dashed dashed dashed solid}.sm-dox ul a,.sm-dox ul a:hover,.sm-dox ul a:focus,.sm-dox ul a:active,.sm-dox ul a.highlighted{color:var(--nav-menu-foreground-color);background-image:none;border:0 !important;color:var(--nav-menu-foreground-color);background-image:none}.sm-dox ul a:hover{background-image:var(--nav-gradient-active-image);background-repeat:repeat-x;color:var(--nav-text-hover-color);text-shadow:var(--nav-text-hover-shadow)}.sm-dox ul a:hover span.sub-arrow{border-color:transparent transparent transparent var(--nav-text-hover-color)}.sm-dox span.scroll-up,.sm-dox span.scroll-down{position:absolute;display:none;visibility:hidden;overflow:hidden;background:var(--nav-menu-background-color);height:36px}.sm-dox span.scroll-up:hover,.sm-dox span.scroll-down:hover{background:#eee}.sm-dox span.scroll-up:hover span.scroll-up-arrow,.sm-dox span.scroll-up:hover span.scroll-down-arrow{border-color:transparent transparent #d23600 transparent}.sm-dox span.scroll-down:hover span.scroll-down-arrow{border-color:#d23600 transparent transparent transparent}.sm-dox span.scroll-up-arrow,.sm-dox span.scroll-down-arrow{position:absolute;top:0;left:50%;margin-left:-6px;width:0;height:0;overflow:hidden;border-width:6px;border-style:dashed dashed solid dashed;border-color:transparent transparent var(--nav-menu-foreground-color) transparent}.sm-dox span.scroll-down-arrow{top:8px;border-style:solid dashed dashed dashed;border-color:var(--nav-menu-foreground-color) transparent transparent transparent}.sm-dox.sm-rtl a.has-submenu{padding-right:12px;padding-left:24px}.sm-dox.sm-rtl a span.sub-arrow{right:auto;left:12px}.sm-dox.sm-rtl.sm-vertical a.has-submenu{padding:10px 20px}.sm-dox.sm-rtl.sm-vertical a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-rtl>li>ul:before{left:auto;right:30px}.sm-dox.sm-rtl>li>ul:after{left:auto;right:31px}.sm-dox.sm-rtl ul a.has-submenu{padding:10px 20px !important}.sm-dox.sm-rtl ul a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-vertical{padding:10px 0;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px}.sm-dox.sm-vertical a{padding:10px 20px}.sm-dox.sm-vertical a:hover,.sm-dox.sm-vertical a:focus,.sm-dox.sm-vertical a:active,.sm-dox.sm-vertical a.highlighted{background:#fff}.sm-dox.sm-vertical a.disabled{background-image:var(--nav-gradient-image)}.sm-dox.sm-vertical a span.sub-arrow{right:8px;top:50%;margin-top:-5px;border-width:5px;border-style:dashed dashed dashed solid;border-color:transparent transparent transparent #555}.sm-dox.sm-vertical>li>ul:before,.sm-dox.sm-vertical>li>ul:after{display:none}.sm-dox.sm-vertical ul a{padding:10px 20px}.sm-dox.sm-vertical ul a:hover,.sm-dox.sm-vertical ul a:focus,.sm-dox.sm-vertical ul a:active,.sm-dox.sm-vertical ul a.highlighted{background:#eee}.sm-dox.sm-vertical ul a.disabled{background:var(--nav-menu-background-color)}} \ No newline at end of file diff --git a/Doxygen/latex/Makefile b/Doxygen/latex/Makefile new file mode 100644 index 0000000..07f226d --- /dev/null +++ b/Doxygen/latex/Makefile @@ -0,0 +1,27 @@ +LATEX_CMD?=pdflatex +MKIDX_CMD?=makeindex +BIBTEX_CMD?=bibtex +LATEX_COUNT?=8 +MANUAL_FILE?=refman + +all: $(MANUAL_FILE).pdf + +pdf: $(MANUAL_FILE).pdf + +$(MANUAL_FILE).pdf: clean $(MANUAL_FILE).tex + $(LATEX_CMD) $(MANUAL_FILE) + $(MKIDX_CMD) $(MANUAL_FILE).idx + $(LATEX_CMD) $(MANUAL_FILE) + latex_count=$(LATEX_COUNT) ; \ + while egrep -s 'Rerun (LaTeX|to get cross-references right|to get bibliographical references right)' $(MANUAL_FILE).log && [ $$latex_count -gt 0 ] ;\ + do \ + echo "Rerunning latex...." ;\ + $(LATEX_CMD) $(MANUAL_FILE) ;\ + latex_count=`expr $$latex_count - 1` ;\ + done + $(MKIDX_CMD) $(MANUAL_FILE).idx + $(LATEX_CMD) $(MANUAL_FILE) + + +clean: + rm -f *.ps *.dvi *.aux *.toc *.idx *.ind *.ilg *.log *.out *.brf *.blg *.bbl $(MANUAL_FILE).pdf diff --git a/Doxygen/latex/_check_collision_component_8cpp.tex b/Doxygen/latex/_check_collision_component_8cpp.tex new file mode 100644 index 0000000..fef7942 --- /dev/null +++ b/Doxygen/latex/_check_collision_component_8cpp.tex @@ -0,0 +1,5 @@ +\hypertarget{_check_collision_component_8cpp}{}\doxysection{Check\+Collision\+Component.\+cpp File Reference} +\label{_check_collision_component_8cpp}\index{CheckCollisionComponent.cpp@{CheckCollisionComponent.cpp}} +{\ttfamily \#include \char`\"{}Check\+Collision\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Excavator\+Character.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Engine/\+World.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_check_collision_component_8h.tex b/Doxygen/latex/_check_collision_component_8h.tex new file mode 100644 index 0000000..142b682 --- /dev/null +++ b/Doxygen/latex/_check_collision_component_8h.tex @@ -0,0 +1,8 @@ +\hypertarget{_check_collision_component_8h}{}\doxysection{Check\+Collision\+Component.\+h File Reference} +\label{_check_collision_component_8h}\index{CheckCollisionComponent.h@{CheckCollisionComponent.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Components/\+Actor\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include $<$Components/\+Box\+Component.\+h$>$}\newline +{\ttfamily \#include \char`\"{}Excavator\+Anim.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Voxel\+World.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Check\+Collision\+Component.\+generated.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_check_collision_component_8h_source.tex b/Doxygen/latex/_check_collision_component_8h_source.tex new file mode 100644 index 0000000..1767bdf --- /dev/null +++ b/Doxygen/latex/_check_collision_component_8h_source.tex @@ -0,0 +1,70 @@ +\hypertarget{_check_collision_component_8h_source}{}\doxysection{Check\+Collision\+Component.\+h} +\mbox{\hyperlink{_check_collision_component_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Fill out your copyright notice in the Description page of Project Settings.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 \textcolor{preprocessor}{\#include "{}Components/ActorComponent.h"{}}} +\DoxyCodeLine{7 \textcolor{preprocessor}{\#include <Components/BoxComponent.h>}} +\DoxyCodeLine{8 \textcolor{preprocessor}{\#include "{}\mbox{\hyperlink{_excavator_anim_8h}{ExcavatorAnim.h}}"{}}} +\DoxyCodeLine{9 \textcolor{preprocessor}{\#include "{}VoxelWorld.h"{}}} +\DoxyCodeLine{10 \textcolor{preprocessor}{\#include "{}CheckCollisionComponent.generated.h"{}}} +\DoxyCodeLine{11 } +\DoxyCodeLine{12 UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )} +\DoxyCodeLine{13 \textcolor{keyword}{class }EXCAVATORSIMULATOR\_API UCheckCollisionComponent : \textcolor{keyword}{public} UActorComponent} +\DoxyCodeLine{14 \{} +\DoxyCodeLine{15 GENERATED\_BODY()} +\DoxyCodeLine{16 } +\DoxyCodeLine{17 public: } +\DoxyCodeLine{18 \textcolor{comment}{// Sets default values for this component's properties}} +\DoxyCodeLine{19 UCheckCollisionComponent();} +\DoxyCodeLine{20 } +\DoxyCodeLine{22 \textcolor{keywordtype}{void} CollisionCheck();} +\DoxyCodeLine{23 } +\DoxyCodeLine{28 \textcolor{keywordtype}{bool} IsCollidingLR(\textcolor{keywordtype}{float} rotationDirection);} +\DoxyCodeLine{29 } +\DoxyCodeLine{34 \textcolor{keywordtype}{bool} IsCollidingFB(\textcolor{keywordtype}{float} movingDirection);} +\DoxyCodeLine{35 } +\DoxyCodeLine{36 \textcolor{comment}{// Called every frame}} +\DoxyCodeLine{37 virtual \textcolor{keywordtype}{void} TickComponent(\textcolor{keywordtype}{float} DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;} +\DoxyCodeLine{38 } +\DoxyCodeLine{39 protected:} +\DoxyCodeLine{40 \textcolor{comment}{// Called when the game starts}} +\DoxyCodeLine{41 virtual \textcolor{keywordtype}{void} BeginPlay() override; } +\DoxyCodeLine{42 } +\DoxyCodeLine{43 private:} +\DoxyCodeLine{45 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Collision Bools"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{46 \textcolor{keywordtype}{bool} BucketCollidingFromDown;} +\DoxyCodeLine{47 } +\DoxyCodeLine{49 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Collision Bools"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{50 \textcolor{keywordtype}{bool} BucketCollidingFromFront;} +\DoxyCodeLine{51 } +\DoxyCodeLine{53 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Collision Bools"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{54 \textcolor{keywordtype}{bool} BucketCollidingFromLeft;} +\DoxyCodeLine{55 } +\DoxyCodeLine{57 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Collision Bools"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{58 \textcolor{keywordtype}{bool} BucketCollidingFromRight;} +\DoxyCodeLine{59 } +\DoxyCodeLine{61 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Collision varbles"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{62 \textcolor{keywordtype}{float} CollisionLengthDown;} +\DoxyCodeLine{63 } +\DoxyCodeLine{65 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Collision varbles"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{66 \textcolor{keywordtype}{float} CollisionLengthLeftAndRight;} +\DoxyCodeLine{67 } +\DoxyCodeLine{69 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Collision varbles"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{70 \textcolor{keywordtype}{float} CollisionLengthFront;} +\DoxyCodeLine{71 } +\DoxyCodeLine{73 USkeletalMeshComponent* mesh;} +\DoxyCodeLine{74 } +\DoxyCodeLine{76 ACharacter* CharacterREF;} +\DoxyCodeLine{77 } +\DoxyCodeLine{79 AVoxelWorld* VoxelWorldREF;} +\DoxyCodeLine{80 } +\DoxyCodeLine{82 \mbox{\hyperlink{class_u_excavator_anim}{UExcavatorAnim}}* ExcavatorAnimREF;} +\DoxyCodeLine{83 } +\DoxyCodeLine{85 TArray<AActor*, FDefaultAllocator> PhysicksObjects;} +\DoxyCodeLine{86 \};} + +\end{DoxyCode} diff --git a/Doxygen/latex/_custom_procedural_mesh_component_8cpp.tex b/Doxygen/latex/_custom_procedural_mesh_component_8cpp.tex new file mode 100644 index 0000000..882edbb --- /dev/null +++ b/Doxygen/latex/_custom_procedural_mesh_component_8cpp.tex @@ -0,0 +1,3 @@ +\hypertarget{_custom_procedural_mesh_component_8cpp}{}\doxysection{Custom\+Procedural\+Mesh\+Component.\+cpp File Reference} +\label{_custom_procedural_mesh_component_8cpp}\index{CustomProceduralMeshComponent.cpp@{CustomProceduralMeshComponent.cpp}} +{\ttfamily \#include \char`\"{}Custom\+Procedural\+Mesh\+Component.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_custom_procedural_mesh_component_8h.tex b/Doxygen/latex/_custom_procedural_mesh_component_8h.tex new file mode 100644 index 0000000..d64ecc6 --- /dev/null +++ b/Doxygen/latex/_custom_procedural_mesh_component_8h.tex @@ -0,0 +1,11 @@ +\hypertarget{_custom_procedural_mesh_component_8h}{}\doxysection{Custom\+Procedural\+Mesh\+Component.\+h File Reference} +\label{_custom_procedural_mesh_component_8h}\index{CustomProceduralMeshComponent.h@{CustomProceduralMeshComponent.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Game\+Framework/\+Actor.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Procedural\+Mesh\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Custom\+Procedural\+Mesh\+Component.\+generated.\+h\char`\"{}}\newline +\doxysubsection*{Classes} +\begin{DoxyCompactItemize} +\item +class \mbox{\hyperlink{class_a_custom_procedural_mesh_component}{ACustom\+Procedural\+Mesh\+Component}} +\end{DoxyCompactItemize} diff --git a/Doxygen/latex/_custom_procedural_mesh_component_8h_source.tex b/Doxygen/latex/_custom_procedural_mesh_component_8h_source.tex new file mode 100644 index 0000000..f010ede --- /dev/null +++ b/Doxygen/latex/_custom_procedural_mesh_component_8h_source.tex @@ -0,0 +1,79 @@ +\hypertarget{_custom_procedural_mesh_component_8h_source}{}\doxysection{Custom\+Procedural\+Mesh\+Component.\+h} +\mbox{\hyperlink{_custom_procedural_mesh_component_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Fill out your copyright notice in the Description page of Project Settings.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 \textcolor{preprocessor}{\#include "{}GameFramework/Actor.h"{}}} +\DoxyCodeLine{7 \textcolor{preprocessor}{\#include "{}ProceduralMeshComponent.h"{}}} +\DoxyCodeLine{8 \textcolor{preprocessor}{\#include "{}CustomProceduralMeshComponent.generated.h"{}}} +\DoxyCodeLine{9 } +\DoxyCodeLine{10 UCLASS()} +\DoxyCodeLine{11 class EXCAVATORSIMULATOR\_API \mbox{\hyperlink{class_a_custom_procedural_mesh_component}{ACustomProceduralMeshComponent}} : public AActor} +\DoxyCodeLine{12 \{} +\DoxyCodeLine{13 GENERATED\_BODY()} +\DoxyCodeLine{14 } +\DoxyCodeLine{15 \textcolor{keyword}{public}: } +\DoxyCodeLine{16 \textcolor{comment}{// Sets default values for this actor's properties}} +\DoxyCodeLine{17 \mbox{\hyperlink{class_a_custom_procedural_mesh_component}{ACustomProceduralMeshComponent}}();} +\DoxyCodeLine{18 } +\DoxyCodeLine{19 \textcolor{comment}{// Called every frame}} +\DoxyCodeLine{20 \textcolor{keyword}{virtual} \textcolor{keywordtype}{void} Tick(\textcolor{keywordtype}{float} DeltaTime) \textcolor{keyword}{override};} +\DoxyCodeLine{21 } +\DoxyCodeLine{22 \textcolor{keywordtype}{void} CreateTriangle();} +\DoxyCodeLine{23 \textcolor{keywordtype}{void} CreateWall();} +\DoxyCodeLine{24 } +\DoxyCodeLine{25 \textcolor{keywordtype}{void} CreateVertices(\textcolor{keywordtype}{int} layer);} +\DoxyCodeLine{26 } +\DoxyCodeLine{27 \textcolor{keyword}{protected}:} +\DoxyCodeLine{28 \textcolor{comment}{// Called when the game starts or when spawned}} +\DoxyCodeLine{29 \textcolor{keyword}{virtual} \textcolor{keywordtype}{void} BeginPlay() \textcolor{keyword}{override};} +\DoxyCodeLine{30 } +\DoxyCodeLine{31 \textcolor{keyword}{private}:} +\DoxyCodeLine{33 UPROPERTY(VisibleAnywhere)} +\DoxyCodeLine{34 UProceduralMeshComponent* Mesh;} +\DoxyCodeLine{35 } +\DoxyCodeLine{36 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{37 TArray<FVector> Vertices;} +\DoxyCodeLine{38 } +\DoxyCodeLine{39 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{40 TArray<int32> Triangles;} +\DoxyCodeLine{41 } +\DoxyCodeLine{42 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{43 TArray<FVector> normals;} +\DoxyCodeLine{44 } +\DoxyCodeLine{45 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{46 TArray<FVector2D> UV0;} +\DoxyCodeLine{47 } +\DoxyCodeLine{48 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{49 TArray<FProcMeshTangent> tangents;} +\DoxyCodeLine{50 } +\DoxyCodeLine{51 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{52 TArray<FLinearColor> vertexColors;} +\DoxyCodeLine{53 } +\DoxyCodeLine{54 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{55 UMaterialInterface* Material;} +\DoxyCodeLine{56 } +\DoxyCodeLine{57 UPROPERTY(EditAnywhere, Meta = (ClampMin = 0))} +\DoxyCodeLine{58 \textcolor{keywordtype}{int} Xsize = 0;} +\DoxyCodeLine{59 } +\DoxyCodeLine{60 UPROPERTY(EditAnywhere, Meta = (ClampMin = 0))} +\DoxyCodeLine{61 \textcolor{keywordtype}{int} Ysize = 0;} +\DoxyCodeLine{62 } +\DoxyCodeLine{63 UPROPERTY(EditAnywhere, Meta = (ClampMin = 0))} +\DoxyCodeLine{64 \textcolor{keywordtype}{int} Zsize = 0;} +\DoxyCodeLine{65 } +\DoxyCodeLine{66 UPROPERTY(EditAnywhere, Meta = (ClampMin = 0.000001))} +\DoxyCodeLine{67 \textcolor{keywordtype}{float} scale = 0;} +\DoxyCodeLine{68 } +\DoxyCodeLine{69 UPROPERTY(EditAnywhere, Meta = (ClampMin = 0.000001))} +\DoxyCodeLine{70 \textcolor{keywordtype}{float} UVscale = 0;} +\DoxyCodeLine{71 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{72 \textcolor{keywordtype}{float} Zmin = 0;} +\DoxyCodeLine{73 UPROPERTY(EditAnywhere, BlueprintReadWrite, category = \textcolor{stringliteral}{"{}Arrays"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{74 \textcolor{keywordtype}{float} Zmax = 0;} +\DoxyCodeLine{75 \};} + +\end{DoxyCode} diff --git a/Doxygen/latex/_excavator_anim_8cpp.tex b/Doxygen/latex/_excavator_anim_8cpp.tex new file mode 100644 index 0000000..e4c7a98 --- /dev/null +++ b/Doxygen/latex/_excavator_anim_8cpp.tex @@ -0,0 +1,3 @@ +\hypertarget{_excavator_anim_8cpp}{}\doxysection{Excavator\+Anim.\+cpp File Reference} +\label{_excavator_anim_8cpp}\index{ExcavatorAnim.cpp@{ExcavatorAnim.cpp}} +{\ttfamily \#include \char`\"{}Excavator\+Anim.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_excavator_anim_8h.tex b/Doxygen/latex/_excavator_anim_8h.tex new file mode 100644 index 0000000..f8077d4 --- /dev/null +++ b/Doxygen/latex/_excavator_anim_8h.tex @@ -0,0 +1,10 @@ +\hypertarget{_excavator_anim_8h}{}\doxysection{Excavator\+Anim.\+h File Reference} +\label{_excavator_anim_8h}\index{ExcavatorAnim.h@{ExcavatorAnim.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Animation/\+Anim\+Instance.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Excavator\+Anim.\+generated.\+h\char`\"{}}\newline +\doxysubsection*{Classes} +\begin{DoxyCompactItemize} +\item +class \mbox{\hyperlink{class_u_excavator_anim}{UExcavator\+Anim}} +\end{DoxyCompactItemize} diff --git a/Doxygen/latex/_excavator_anim_8h_source.tex b/Doxygen/latex/_excavator_anim_8h_source.tex new file mode 100644 index 0000000..f36d2eb --- /dev/null +++ b/Doxygen/latex/_excavator_anim_8h_source.tex @@ -0,0 +1,107 @@ +\hypertarget{_excavator_anim_8h_source}{}\doxysection{Excavator\+Anim.\+h} +\mbox{\hyperlink{_excavator_anim_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Fill out your copyright notice in the Description page of Project Settings.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 \textcolor{preprocessor}{\#include "{}Animation/AnimInstance.h"{}}} +\DoxyCodeLine{7 \textcolor{preprocessor}{\#include "{}ExcavatorAnim.generated.h"{}}} +\DoxyCodeLine{8 } +\DoxyCodeLine{12 UCLASS()} +\DoxyCodeLine{13 class EXCAVATORSIMULATOR\_API \mbox{\hyperlink{class_u_excavator_anim}{UExcavatorAnim}} : public UAnimInstance} +\DoxyCodeLine{14 \{} +\DoxyCodeLine{15 GENERATED\_BODY()} +\DoxyCodeLine{16 \textcolor{keyword}{public}:} +\DoxyCodeLine{17 } +\DoxyCodeLine{19 \mbox{\hyperlink{class_u_excavator_anim}{UExcavatorAnim}}(\textcolor{keyword}{const} FObjectInitializer\& ObjecInitializer);} +\DoxyCodeLine{20 } +\DoxyCodeLine{22 UFUNCTION(BlueprintCallable, BlueprintPure, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{23 FRotator GetBodyRotation();} +\DoxyCodeLine{24 } +\DoxyCodeLine{28 UFUNCTION(BlueprintCallable, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{29 \textcolor{keywordtype}{void} SetBodyRotation(FRotator rotator);} +\DoxyCodeLine{30 } +\DoxyCodeLine{32 UFUNCTION(BlueprintCallable, BlueprintPure, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{33 FRotator GetBeamTopRotation();} +\DoxyCodeLine{34 } +\DoxyCodeLine{38 UFUNCTION(BlueprintCallable, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{39 \textcolor{keywordtype}{void} SetBeamTopRotation(FRotator rotator);} +\DoxyCodeLine{40 } +\DoxyCodeLine{42 UFUNCTION(BlueprintCallable, BlueprintPure, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{43 FRotator GetBeamBottomRotation();} +\DoxyCodeLine{44 } +\DoxyCodeLine{48 UFUNCTION(BlueprintCallable, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{49 \textcolor{keywordtype}{void} SetBeamBottomRotation(FRotator rotator);} +\DoxyCodeLine{50 } +\DoxyCodeLine{52 UFUNCTION(BlueprintCallable, BlueprintPure, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{53 FRotator GetBucketRotation();} +\DoxyCodeLine{54 } +\DoxyCodeLine{58 UFUNCTION(BlueprintCallable, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (BlueprintThreadSafe = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{59 \textcolor{keywordtype}{void} SetBucketRotation(FRotator rotator);} +\DoxyCodeLine{60 } +\DoxyCodeLine{62 \textcolor{keywordtype}{float} \mbox{\hyperlink{class_u_excavator_anim_a7e4af257d3369d934d999c5889bfe60f}{GetBucketMaxRotation}}()} +\DoxyCodeLine{63 \{} +\DoxyCodeLine{64 \textcolor{keywordflow}{return} BucketRotationMAX;} +\DoxyCodeLine{65 \}} +\DoxyCodeLine{66 } +\DoxyCodeLine{68 \textcolor{keywordtype}{float} \mbox{\hyperlink{class_u_excavator_anim_a6542745f31287654904430a08208f4dc}{GetBucketMinRotation}}()} +\DoxyCodeLine{69 \{} +\DoxyCodeLine{70 \textcolor{keywordflow}{return} BucketRotationMIN;} +\DoxyCodeLine{71 \}} +\DoxyCodeLine{72 } +\DoxyCodeLine{74 \textcolor{keywordtype}{float} \mbox{\hyperlink{class_u_excavator_anim_a56e5697f1cb92abaf41d0af039e641b1}{GetTopMaxRotation}}()} +\DoxyCodeLine{75 \{} +\DoxyCodeLine{76 \textcolor{keywordflow}{return} TopRotationMAX;} +\DoxyCodeLine{77 \}} +\DoxyCodeLine{78 } +\DoxyCodeLine{80 \textcolor{keywordtype}{float} \mbox{\hyperlink{class_u_excavator_anim_ad7c23fdcc7208d1e925a0ea8ac09b793}{GetTopMinRotation}}()} +\DoxyCodeLine{81 \{} +\DoxyCodeLine{82 \textcolor{keywordflow}{return} TopRotationMIN;} +\DoxyCodeLine{83 \}} +\DoxyCodeLine{84 } +\DoxyCodeLine{86 \textcolor{keywordtype}{float} \mbox{\hyperlink{class_u_excavator_anim_a19af34e8fb205cccbe3796330e5fff79}{GetBottomMaxRotation}}()} +\DoxyCodeLine{87 \{} +\DoxyCodeLine{88 \textcolor{keywordflow}{return} BottomRotationMAX;} +\DoxyCodeLine{89 \}} +\DoxyCodeLine{90 } +\DoxyCodeLine{92 \textcolor{keywordtype}{float} \mbox{\hyperlink{class_u_excavator_anim_a290c2158c3bd36b4cf9f6cf53395c44b}{GetBottomMinRotation}}()} +\DoxyCodeLine{93 \{} +\DoxyCodeLine{94 \textcolor{keywordflow}{return} BottomRotationMIN;} +\DoxyCodeLine{95 \}} +\DoxyCodeLine{96 } +\DoxyCodeLine{97 \textcolor{keyword}{private}:} +\DoxyCodeLine{98 } +\DoxyCodeLine{100 UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = \textcolor{stringliteral}{"{}Rotations"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{101 FRotator BodyRotation;} +\DoxyCodeLine{102 } +\DoxyCodeLine{104 UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "{}Rotations"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{105 FRotator BeamTopRotation;} +\DoxyCodeLine{106 } +\DoxyCodeLine{108 UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "{}Rotations"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{109 FRotator BeamBottomRotation;} +\DoxyCodeLine{110 } +\DoxyCodeLine{112 UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "{}Rotations"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{113 FRotator BucketRotation;} +\DoxyCodeLine{114 } +\DoxyCodeLine{116 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Clap values"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{117 \textcolor{keywordtype}{float} BucketRotationMIN;} +\DoxyCodeLine{118 } +\DoxyCodeLine{120 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Clap values"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{121 \textcolor{keywordtype}{float} BucketRotationMAX;} +\DoxyCodeLine{122 } +\DoxyCodeLine{124 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Clap values"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{125 \textcolor{keywordtype}{float} TopRotationMIN;} +\DoxyCodeLine{126 } +\DoxyCodeLine{128 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Clap values"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{129 \textcolor{keywordtype}{float} TopRotationMAX;} +\DoxyCodeLine{130 } +\DoxyCodeLine{132 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Clap values"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{133 \textcolor{keywordtype}{float} BottomRotationMIN;} +\DoxyCodeLine{134 } +\DoxyCodeLine{136 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Clap values"{}, meta = (AllowPrivateAccess = "{}true"{}))} +\DoxyCodeLine{137 \textcolor{keywordtype}{float} BottomRotationMAX;} +\DoxyCodeLine{138 \};} + +\end{DoxyCode} diff --git a/Doxygen/latex/_excavator_character_8cpp.tex b/Doxygen/latex/_excavator_character_8cpp.tex new file mode 100644 index 0000000..f9def49 --- /dev/null +++ b/Doxygen/latex/_excavator_character_8cpp.tex @@ -0,0 +1,4 @@ +\hypertarget{_excavator_character_8cpp}{}\doxysection{Excavator\+Character.\+cpp File Reference} +\label{_excavator_character_8cpp}\index{ExcavatorCharacter.cpp@{ExcavatorCharacter.cpp}} +{\ttfamily \#include \char`\"{}Excavator\+Character.\+h\char`\"{}}\newline +{\ttfamily \#include $<$Kismet/\+Kismet\+Math\+Library.\+h$>$}\newline diff --git a/Doxygen/latex/_excavator_character_8h.tex b/Doxygen/latex/_excavator_character_8h.tex new file mode 100644 index 0000000..449e1fe --- /dev/null +++ b/Doxygen/latex/_excavator_character_8h.tex @@ -0,0 +1,15 @@ +\hypertarget{_excavator_character_8h}{}\doxysection{Excavator\+Character.\+h File Reference} +\label{_excavator_character_8h}\index{ExcavatorCharacter.h@{ExcavatorCharacter.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Game\+Framework/\+Character.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Kismet/\+Gameplay\+Statics.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Game\+Framework/\+Input\+Settings.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Check\+Collision\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Ground\+Deformer\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Excavator\+Anim.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Excavator\+Character.\+generated.\+h\char`\"{}}\newline +\doxysubsection*{Classes} +\begin{DoxyCompactItemize} +\item +class \mbox{\hyperlink{class_a_excavator_character}{AExcavator\+Character}} +\end{DoxyCompactItemize} diff --git a/Doxygen/latex/_excavator_character_8h_source.tex b/Doxygen/latex/_excavator_character_8h_source.tex new file mode 100644 index 0000000..6ef651a --- /dev/null +++ b/Doxygen/latex/_excavator_character_8h_source.tex @@ -0,0 +1,122 @@ +\hypertarget{_excavator_character_8h_source}{}\doxysection{Excavator\+Character.\+h} +\mbox{\hyperlink{_excavator_character_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Fill out your copyright notice in the Description page of Project Settings.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 \textcolor{preprocessor}{\#include "{}GameFramework/Character.h"{}}} +\DoxyCodeLine{7 \textcolor{preprocessor}{\#include "{}Kismet/GameplayStatics.h"{}}} +\DoxyCodeLine{8 \textcolor{preprocessor}{\#include "{}GameFramework/InputSettings.h"{}}} +\DoxyCodeLine{9 \textcolor{preprocessor}{\#include "{}\mbox{\hyperlink{_check_collision_component_8h}{CheckCollisionComponent.h}}"{}}} +\DoxyCodeLine{10 \textcolor{preprocessor}{\#include "{}\mbox{\hyperlink{_ground_deformer_component_8h}{GroundDeformerComponent.h}}"{}}} +\DoxyCodeLine{11 \textcolor{preprocessor}{\#include "{}\mbox{\hyperlink{_excavator_anim_8h}{ExcavatorAnim.h}}"{}}} +\DoxyCodeLine{12 \textcolor{preprocessor}{\#include "{}ExcavatorCharacter.generated.h"{}}} +\DoxyCodeLine{13 } +\DoxyCodeLine{14 UCLASS()} +\DoxyCodeLine{15 class EXCAVATORSIMULATOR\_API \mbox{\hyperlink{class_a_excavator_character}{AExcavatorCharacter}} : public ACharacter} +\DoxyCodeLine{16 \{} +\DoxyCodeLine{17 GENERATED\_BODY()} +\DoxyCodeLine{18 } +\DoxyCodeLine{19 \textcolor{keyword}{public}:} +\DoxyCodeLine{21 \mbox{\hyperlink{class_a_excavator_character}{AExcavatorCharacter}}();} +\DoxyCodeLine{22 } +\DoxyCodeLine{24 \textcolor{keyword}{virtual} \textcolor{keywordtype}{void} Tick(\textcolor{keywordtype}{float} DeltaTime) \textcolor{keyword}{override};} +\DoxyCodeLine{25 } +\DoxyCodeLine{27 \textcolor{keyword}{virtual} \textcolor{keywordtype}{void} SetupPlayerInputComponent(\textcolor{keyword}{class} UInputComponent* PlayerInputComponent) \textcolor{keyword}{override};} +\DoxyCodeLine{28 } +\DoxyCodeLine{33 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{34 \textcolor{keywordtype}{void} Movement(\textcolor{keywordtype}{float} Direction);} +\DoxyCodeLine{35 } +\DoxyCodeLine{40 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{41 \textcolor{keywordtype}{bool} BucketRotationReleaceRocks(\textcolor{keywordtype}{float} RotationDirection);} +\DoxyCodeLine{42 } +\DoxyCodeLine{43 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{44 \textcolor{keywordtype}{void} BucketRotationVR(\textcolor{keywordtype}{float} RightJoyStickRotationX);} +\DoxyCodeLine{45 } +\DoxyCodeLine{50 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{51 \textcolor{keywordtype}{void} BeamTopRotation(\textcolor{keywordtype}{float} RotationDirection);} +\DoxyCodeLine{52 } +\DoxyCodeLine{53 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{54 \textcolor{keywordtype}{void} BeamTopRotationVR(\textcolor{keywordtype}{float} RightJoyStickRotationY);} +\DoxyCodeLine{55 } +\DoxyCodeLine{60 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{61 \textcolor{keywordtype}{void} BeamBottomRotation(\textcolor{keywordtype}{float} RotationDirection);} +\DoxyCodeLine{62 } +\DoxyCodeLine{63 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{64 \textcolor{keywordtype}{void} BeamBottomRotationVR(\textcolor{keywordtype}{float} LeftJoyStickRotationY);} +\DoxyCodeLine{65 } +\DoxyCodeLine{70 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{71 \textcolor{keywordtype}{void} BodyRotation(\textcolor{keywordtype}{float} RotationDirection);} +\DoxyCodeLine{72 } +\DoxyCodeLine{73 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{74 \textcolor{keywordtype}{void} BodyRotationVR(\textcolor{keywordtype}{float} LeftJoyStickRotationX);} +\DoxyCodeLine{75 } +\DoxyCodeLine{80 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{81 \textcolor{keywordtype}{void} VehicleRotation(\textcolor{keywordtype}{float} RotationDirection);} +\DoxyCodeLine{82 } +\DoxyCodeLine{83 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{84 \textcolor{keywordtype}{void} VehicleRotationVR(\textcolor{keywordtype}{float} AxisValue);} +\DoxyCodeLine{85 } +\DoxyCodeLine{86 \textcolor{keyword}{protected}:} +\DoxyCodeLine{87 } +\DoxyCodeLine{89 \textcolor{keyword}{virtual} \textcolor{keywordtype}{void} BeginPlay() \textcolor{keyword}{override};} +\DoxyCodeLine{90 } +\DoxyCodeLine{91 \textcolor{keyword}{private}:} +\DoxyCodeLine{92 UInputSettings* Inputsettings = UInputSettings::GetInputSettings();} +\DoxyCodeLine{93 } +\DoxyCodeLine{94 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Right Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{95 \textcolor{keywordtype}{float} RightJoyStickRotXMin;} +\DoxyCodeLine{96 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Right Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{97 \textcolor{keywordtype}{float} RightJoyStickRotXMax;} +\DoxyCodeLine{98 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Right Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{99 \textcolor{keywordtype}{float} RightJoyStickRotYMin;} +\DoxyCodeLine{100 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Right Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{101 \textcolor{keywordtype}{float} RightJoyStickRotYMax;} +\DoxyCodeLine{102 } +\DoxyCodeLine{103 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Left Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{104 \textcolor{keywordtype}{float} LeftJoyStickRotXMin;} +\DoxyCodeLine{105 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Left Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{106 \textcolor{keywordtype}{float} LeftJoyStickRotXMax;} +\DoxyCodeLine{107 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Left Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{108 \textcolor{keywordtype}{float} LeftJoyStickRotYMin;} +\DoxyCodeLine{109 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Left Joy Stick values"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{110 \textcolor{keywordtype}{float} LeftJoyStickRotYMax;} +\DoxyCodeLine{111 } +\DoxyCodeLine{113 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Movement variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{114 \textcolor{keywordtype}{float} AccelerationMultiplier;} +\DoxyCodeLine{115 } +\DoxyCodeLine{117 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Movement variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{118 \textcolor{keywordtype}{float} BucketRotationMultiplier;} +\DoxyCodeLine{119 } +\DoxyCodeLine{121 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Movement variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{122 \textcolor{keywordtype}{float} BeamTopRotationMultiplier;} +\DoxyCodeLine{123 } +\DoxyCodeLine{125 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Movement variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{126 \textcolor{keywordtype}{float} BeamBottomRotationMultiplier;} +\DoxyCodeLine{127 } +\DoxyCodeLine{129 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Movement variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{130 \textcolor{keywordtype}{float} BodyRotationMultiplier;} +\DoxyCodeLine{131 } +\DoxyCodeLine{133 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Movement variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{134 \textcolor{keywordtype}{float} VehicleRotationMultiplier;} +\DoxyCodeLine{135 } +\DoxyCodeLine{137 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Movement variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{138 \textcolor{keywordtype}{float} DetachRocksAngle;} +\DoxyCodeLine{139 } +\DoxyCodeLine{141 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Collision variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{142 UCheckCollisionComponent* collisionComponent;} +\DoxyCodeLine{143 } +\DoxyCodeLine{144 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Ground variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{145 UGroundDeformerComponent* groundDeformComponent;} +\DoxyCodeLine{146 } +\DoxyCodeLine{148 UPROPERTY(BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Ground variables"{}}, meta = (AllowPrivateAccess = \textcolor{stringliteral}{"{}true"{}}))} +\DoxyCodeLine{149 \mbox{\hyperlink{class_u_excavator_anim}{UExcavatorAnim}}* ExcavatorAnimREF;} +\DoxyCodeLine{150 } +\DoxyCodeLine{152 USkeletalMeshComponent* mesh;} +\DoxyCodeLine{153 } +\DoxyCodeLine{154 \textcolor{keywordtype}{float} deltaTime;} +\DoxyCodeLine{155 \};} + +\end{DoxyCode} diff --git a/Doxygen/latex/_excavator_simulator_8_build_8cs.tex b/Doxygen/latex/_excavator_simulator_8_build_8cs.tex new file mode 100644 index 0000000..1b316b7 --- /dev/null +++ b/Doxygen/latex/_excavator_simulator_8_build_8cs.tex @@ -0,0 +1,7 @@ +\hypertarget{_excavator_simulator_8_build_8cs}{}\doxysection{Excavator\+Simulator.\+Build.\+cs File Reference} +\label{_excavator_simulator_8_build_8cs}\index{ExcavatorSimulator.Build.cs@{ExcavatorSimulator.Build.cs}} +\doxysubsection*{Classes} +\begin{DoxyCompactItemize} +\item +class \mbox{\hyperlink{class_excavator_simulator}{Excavator\+Simulator}} +\end{DoxyCompactItemize} diff --git a/Doxygen/latex/_excavator_simulator_8cpp.tex b/Doxygen/latex/_excavator_simulator_8cpp.tex new file mode 100644 index 0000000..366141b --- /dev/null +++ b/Doxygen/latex/_excavator_simulator_8cpp.tex @@ -0,0 +1,18 @@ +\hypertarget{_excavator_simulator_8cpp}{}\doxysection{Excavator\+Simulator.\+cpp File Reference} +\label{_excavator_simulator_8cpp}\index{ExcavatorSimulator.cpp@{ExcavatorSimulator.cpp}} +{\ttfamily \#include \char`\"{}Excavator\+Simulator.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Modules/\+Module\+Manager.\+h\char`\"{}}\newline +\doxysubsection*{Functions} +\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_excavator_simulator_8cpp_abdfae4c413c9ef6bcaa5cf0c657807d7}{IMPLEMENT\+\_\+\+PRIMARY\+\_\+\+GAME\+\_\+\+MODULE}} (FDefault\+Game\+Module\+Impl, \mbox{\hyperlink{class_excavator_simulator}{Excavator\+Simulator}}, \char`\"{}Excavator\+Simulator\char`\"{}) +\end{DoxyCompactItemize} + + +\doxysubsection{Function Documentation} +\mbox{\Hypertarget{_excavator_simulator_8cpp_abdfae4c413c9ef6bcaa5cf0c657807d7}\label{_excavator_simulator_8cpp_abdfae4c413c9ef6bcaa5cf0c657807d7}} +\index{ExcavatorSimulator.cpp@{ExcavatorSimulator.cpp}!IMPLEMENT\_PRIMARY\_GAME\_MODULE@{IMPLEMENT\_PRIMARY\_GAME\_MODULE}} +\index{IMPLEMENT\_PRIMARY\_GAME\_MODULE@{IMPLEMENT\_PRIMARY\_GAME\_MODULE}!ExcavatorSimulator.cpp@{ExcavatorSimulator.cpp}} +\doxysubsubsection{\texorpdfstring{IMPLEMENT\_PRIMARY\_GAME\_MODULE()}{IMPLEMENT\_PRIMARY\_GAME\_MODULE()}} +{\footnotesize\ttfamily IMPLEMENT\+\_\+\+PRIMARY\+\_\+\+GAME\+\_\+\+MODULE (\begin{DoxyParamCaption}\item[{FDefault\+Game\+Module\+Impl}]{, }\item[{\mbox{\hyperlink{class_excavator_simulator}{Excavator\+Simulator}}}]{, }\item[{\char`\"{}Excavator\+Simulator\char`\"{}}]{ }\end{DoxyParamCaption})} + diff --git a/Doxygen/latex/_excavator_simulator_8h.tex b/Doxygen/latex/_excavator_simulator_8h.tex new file mode 100644 index 0000000..1ebe000 --- /dev/null +++ b/Doxygen/latex/_excavator_simulator_8h.tex @@ -0,0 +1,3 @@ +\hypertarget{_excavator_simulator_8h}{}\doxysection{Excavator\+Simulator.\+h File Reference} +\label{_excavator_simulator_8h}\index{ExcavatorSimulator.h@{ExcavatorSimulator.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_excavator_simulator_8h_source.tex b/Doxygen/latex/_excavator_simulator_8h_source.tex new file mode 100644 index 0000000..ac070a5 --- /dev/null +++ b/Doxygen/latex/_excavator_simulator_8h_source.tex @@ -0,0 +1,11 @@ +\hypertarget{_excavator_simulator_8h_source}{}\doxysection{Excavator\+Simulator.\+h} +\mbox{\hyperlink{_excavator_simulator_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Copyright Epic Games, Inc. All Rights Reserved.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 } + +\end{DoxyCode} diff --git a/Doxygen/latex/_excavator_simulator_game_mode_base_8cpp.tex b/Doxygen/latex/_excavator_simulator_game_mode_base_8cpp.tex new file mode 100644 index 0000000..fc4adc2 --- /dev/null +++ b/Doxygen/latex/_excavator_simulator_game_mode_base_8cpp.tex @@ -0,0 +1,3 @@ +\hypertarget{_excavator_simulator_game_mode_base_8cpp}{}\doxysection{Excavator\+Simulator\+Game\+Mode\+Base.\+cpp File Reference} +\label{_excavator_simulator_game_mode_base_8cpp}\index{ExcavatorSimulatorGameModeBase.cpp@{ExcavatorSimulatorGameModeBase.cpp}} +{\ttfamily \#include \char`\"{}Excavator\+Simulator\+Game\+Mode\+Base.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_excavator_simulator_game_mode_base_8h.tex b/Doxygen/latex/_excavator_simulator_game_mode_base_8h.tex new file mode 100644 index 0000000..e0e13d6 --- /dev/null +++ b/Doxygen/latex/_excavator_simulator_game_mode_base_8h.tex @@ -0,0 +1,11 @@ +\hypertarget{_excavator_simulator_game_mode_base_8h}{}\doxysection{Excavator\+Simulator\+Game\+Mode\+Base.\+h File Reference} +\label{_excavator_simulator_game_mode_base_8h}\index{ExcavatorSimulatorGameModeBase.h@{ExcavatorSimulatorGameModeBase.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Game\+Framework/\+Game\+Mode\+Base.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Custom\+Procedural\+Mesh\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Excavator\+Simulator\+Game\+Mode\+Base.\+generated.\+h\char`\"{}}\newline +\doxysubsection*{Classes} +\begin{DoxyCompactItemize} +\item +class \mbox{\hyperlink{class_a_excavator_simulator_game_mode_base}{AExcavator\+Simulator\+Game\+Mode\+Base}} +\end{DoxyCompactItemize} diff --git a/Doxygen/latex/_excavator_simulator_game_mode_base_8h_source.tex b/Doxygen/latex/_excavator_simulator_game_mode_base_8h_source.tex new file mode 100644 index 0000000..7ff9f5b --- /dev/null +++ b/Doxygen/latex/_excavator_simulator_game_mode_base_8h_source.tex @@ -0,0 +1,22 @@ +\hypertarget{_excavator_simulator_game_mode_base_8h_source}{}\doxysection{Excavator\+Simulator\+Game\+Mode\+Base.\+h} +\mbox{\hyperlink{_excavator_simulator_game_mode_base_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Copyright Epic Games, Inc. All Rights Reserved.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 \textcolor{preprocessor}{\#include "{}GameFramework/GameModeBase.h"{}}} +\DoxyCodeLine{7 \textcolor{preprocessor}{\#include "{}\mbox{\hyperlink{_custom_procedural_mesh_component_8h}{CustomProceduralMeshComponent.h}}"{}}} +\DoxyCodeLine{8 \textcolor{preprocessor}{\#include "{}ExcavatorSimulatorGameModeBase.generated.h"{}}} +\DoxyCodeLine{9 } +\DoxyCodeLine{13 UCLASS()} +\DoxyCodeLine{14 class EXCAVATORSIMULATOR\_API \mbox{\hyperlink{class_a_excavator_simulator_game_mode_base}{AExcavatorSimulatorGameModeBase}} : public AGameModeBase} +\DoxyCodeLine{15 \{} +\DoxyCodeLine{16 GENERATED\_BODY()} +\DoxyCodeLine{17 } +\DoxyCodeLine{18 \textcolor{keyword}{private}:} +\DoxyCodeLine{19 \mbox{\hyperlink{class_a_custom_procedural_mesh_component}{ACustomProceduralMeshComponent}}* ProceduralmeshComponent;} +\DoxyCodeLine{20 \};} + +\end{DoxyCode} diff --git a/Doxygen/latex/_ground_deformer_component_8cpp.tex b/Doxygen/latex/_ground_deformer_component_8cpp.tex new file mode 100644 index 0000000..ac22d2e --- /dev/null +++ b/Doxygen/latex/_ground_deformer_component_8cpp.tex @@ -0,0 +1,11 @@ +\hypertarget{_ground_deformer_component_8cpp}{}\doxysection{Ground\+Deformer\+Component.\+cpp File Reference} +\label{_ground_deformer_component_8cpp}\index{GroundDeformerComponent.cpp@{GroundDeformerComponent.cpp}} +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Voxel\+Surface\+Edits.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Impl/\+Voxel\+Surface\+Edit\+Tools\+Impl.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Voxel\+Surface\+Tools.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Debug/\+Voxel\+Debug\+Utilities.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Gen/\+Voxel\+Surface\+Edit\+Tools.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Voxel\+Projection\+Tools.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Voxel\+Data\+Tools.\+h$>$}\newline +{\ttfamily \#include \char`\"{}World\+Generator.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Ground\+Deformer\+Component.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_ground_deformer_component_8h.tex b/Doxygen/latex/_ground_deformer_component_8h.tex new file mode 100644 index 0000000..0155576 --- /dev/null +++ b/Doxygen/latex/_ground_deformer_component_8h.tex @@ -0,0 +1,14 @@ +\hypertarget{_ground_deformer_component_8h}{}\doxysection{Ground\+Deformer\+Component.\+h File Reference} +\label{_ground_deformer_component_8h}\index{GroundDeformerComponent.h@{GroundDeformerComponent.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Components/\+Actor\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Components/\+Static\+Mesh\+Component.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Kismet/\+Kismet\+Math\+Library.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Collision\+Query\+Params.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Kismet/\+Gameplay\+Statics.\+h\char`\"{}}\newline +{\ttfamily \#include \char`\"{}Voxel\+World.\+h\char`\"{}}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Gen/\+Voxel\+Sphere\+Tools.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Gen/\+Voxel\+Box\+Tools.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Int\+Box\+Library.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Tools/\+Voxel\+Blueprint\+Library.\+h$>$}\newline +{\ttfamily \#include \char`\"{}Ground\+Deformer\+Component.\+generated.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_ground_deformer_component_8h_source.tex b/Doxygen/latex/_ground_deformer_component_8h_source.tex new file mode 100644 index 0000000..251898d --- /dev/null +++ b/Doxygen/latex/_ground_deformer_component_8h_source.tex @@ -0,0 +1,46 @@ +\hypertarget{_ground_deformer_component_8h_source}{}\doxysection{Ground\+Deformer\+Component.\+h} +\mbox{\hyperlink{_ground_deformer_component_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Fill out your copyright notice in the Description page of Project Settings.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 \textcolor{preprocessor}{\#include "{}Components/ActorComponent.h"{}}} +\DoxyCodeLine{7 \textcolor{preprocessor}{\#include "{}Components/StaticMeshComponent.h"{}}} +\DoxyCodeLine{8 \textcolor{preprocessor}{\#include "{}Kismet/KismetMathLibrary.h"{}}} +\DoxyCodeLine{9 \textcolor{preprocessor}{\#include "{}CollisionQueryParams.h"{}}} +\DoxyCodeLine{10 \textcolor{preprocessor}{\#include "{}Kismet/GameplayStatics.h"{}}} +\DoxyCodeLine{11 \textcolor{preprocessor}{\#include "{}VoxelWorld.h"{}}} +\DoxyCodeLine{12 \textcolor{preprocessor}{\#include <Voxel/Public/VoxelTools/Gen/VoxelSphereTools.h>}} +\DoxyCodeLine{13 \textcolor{preprocessor}{\#include <Voxel/Public/VoxelTools/Gen/VoxelBoxTools.h>}} +\DoxyCodeLine{14 \textcolor{preprocessor}{\#include <Voxel/Public/VoxelIntBoxLibrary.h>}} +\DoxyCodeLine{15 \textcolor{preprocessor}{\#include <Voxel/Public/VoxelTools/VoxelBlueprintLibrary.h>}} +\DoxyCodeLine{16 \textcolor{preprocessor}{\#include "{}GroundDeformerComponent.generated.h"{}}} +\DoxyCodeLine{17 } +\DoxyCodeLine{18 } +\DoxyCodeLine{19 UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )} +\DoxyCodeLine{20 \textcolor{keyword}{class }EXCAVATORSIMULATOR\_API UGroundDeformerComponent : \textcolor{keyword}{public} UActorComponent} +\DoxyCodeLine{21 \{} +\DoxyCodeLine{22 GENERATED\_BODY()} +\DoxyCodeLine{23 } +\DoxyCodeLine{24 public: } +\DoxyCodeLine{26 UGroundDeformerComponent();} +\DoxyCodeLine{27 } +\DoxyCodeLine{29 virtual \textcolor{keywordtype}{void} TickComponent(\textcolor{keywordtype}{float} DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;} +\DoxyCodeLine{30 } +\DoxyCodeLine{35 UFUNCTION(BlueprintCallable)} +\DoxyCodeLine{36 \textcolor{keywordtype}{void} DeformGround(UStaticMeshComponent *Mesh);} +\DoxyCodeLine{37 } +\DoxyCodeLine{38 protected:} +\DoxyCodeLine{40 virtual \textcolor{keywordtype}{void} BeginPlay() override;} +\DoxyCodeLine{41 } +\DoxyCodeLine{42 private:} +\DoxyCodeLine{44 AVoxelWorld* VoxelWorldREF;} +\DoxyCodeLine{45 } +\DoxyCodeLine{47 \textcolor{keywordtype}{float} RemoveSphereRadius;} +\DoxyCodeLine{48 } +\DoxyCodeLine{50 \textcolor{keywordtype}{float} ForwardVectorMultiplier;} +\DoxyCodeLine{51 \};} + +\end{DoxyCode} diff --git a/Doxygen/latex/_world_generator_8cpp.tex b/Doxygen/latex/_world_generator_8cpp.tex new file mode 100644 index 0000000..d7d5c27 --- /dev/null +++ b/Doxygen/latex/_world_generator_8cpp.tex @@ -0,0 +1,5 @@ +\hypertarget{_world_generator_8cpp}{}\doxysection{World\+Generator.\+cpp File Reference} +\label{_world_generator_8cpp}\index{WorldGenerator.cpp@{WorldGenerator.cpp}} +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Material\+Builder.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Fast\+Noise/\+Voxel\+Fast\+Noise.\+inl$>$}\newline +{\ttfamily \#include \char`\"{}World\+Generator.\+h\char`\"{}}\newline diff --git a/Doxygen/latex/_world_generator_8h.tex b/Doxygen/latex/_world_generator_8h.tex new file mode 100644 index 0000000..d225acd --- /dev/null +++ b/Doxygen/latex/_world_generator_8h.tex @@ -0,0 +1,13 @@ +\hypertarget{_world_generator_8h}{}\doxysection{World\+Generator.\+h File Reference} +\label{_world_generator_8h}\index{WorldGenerator.h@{WorldGenerator.h}} +{\ttfamily \#include \char`\"{}Core\+Minimal.\+h\char`\"{}}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Fast\+Noise/\+Voxel\+Fast\+Noise.\+h$>$}\newline +{\ttfamily \#include $<$Voxel/\+Public/\+Voxel\+Generators/\+Voxel\+Generator\+Helpers.\+h$>$}\newline +{\ttfamily \#include \char`\"{}World\+Generator.\+generated.\+h\char`\"{}}\newline +\doxysubsection*{Classes} +\begin{DoxyCompactItemize} +\item +class \mbox{\hyperlink{class_u_world_generator}{UWorld\+Generator}} +\item +class \mbox{\hyperlink{class_f_world_generator_instance}{FWorld\+Generator\+Instance}} +\end{DoxyCompactItemize} diff --git a/Doxygen/latex/_world_generator_8h_source.tex b/Doxygen/latex/_world_generator_8h_source.tex new file mode 100644 index 0000000..2601e01 --- /dev/null +++ b/Doxygen/latex/_world_generator_8h_source.tex @@ -0,0 +1,52 @@ +\hypertarget{_world_generator_8h_source}{}\doxysection{World\+Generator.\+h} +\mbox{\hyperlink{_world_generator_8h}{Go to the documentation of this file.}} +\begin{DoxyCode}{0} +\DoxyCodeLine{1 \textcolor{comment}{// Fill out your copyright notice in the Description page of Project Settings.}} +\DoxyCodeLine{2 } +\DoxyCodeLine{3 \textcolor{preprocessor}{\#pragma once}} +\DoxyCodeLine{4 } +\DoxyCodeLine{5 \textcolor{preprocessor}{\#include "{}CoreMinimal.h"{}}} +\DoxyCodeLine{6 \textcolor{preprocessor}{\#include <Voxel/Public/FastNoise/VoxelFastNoise.h>}} +\DoxyCodeLine{7 \textcolor{preprocessor}{\#include <Voxel/Public/VoxelGenerators/VoxelGeneratorHelpers.h>}} +\DoxyCodeLine{8 \textcolor{preprocessor}{\#include "{}WorldGenerator.generated.h"{}}} +\DoxyCodeLine{9 } +\DoxyCodeLine{13 UCLASS(Blueprintable)} +\DoxyCodeLine{14 class EXCAVATORSIMULATOR\_API \mbox{\hyperlink{class_u_world_generator}{UWorldGenerator}} : public UVoxelGenerator} +\DoxyCodeLine{15 \{} +\DoxyCodeLine{16 GENERATED\_BODY()} +\DoxyCodeLine{17 \textcolor{keyword}{public}:} +\DoxyCodeLine{18 \mbox{\hyperlink{class_u_world_generator}{UWorldGenerator}}();} +\DoxyCodeLine{19 \mbox{\hyperlink{class_u_world_generator}{\string~UWorldGenerator}}();} +\DoxyCodeLine{20 \textcolor{keyword}{virtual} TVoxelSharedRef<FVoxelGeneratorInstance> GetInstance() \textcolor{keyword}{override};} +\DoxyCodeLine{21 } +\DoxyCodeLine{22 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = \textcolor{stringliteral}{"{}Generator"{}})} +\DoxyCodeLine{23 float NoiseHeight = 10.0f;} +\DoxyCodeLine{24 } +\DoxyCodeLine{25 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "{}Generator"{})} +\DoxyCodeLine{26 int32 Seed = 1337;} +\DoxyCodeLine{27 \};} +\DoxyCodeLine{28 } +\DoxyCodeLine{29 class \mbox{\hyperlink{class_f_world_generator_instance}{FWorldGeneratorInstance}} : public TVoxelGeneratorInstanceHelper<\mbox{\hyperlink{class_f_world_generator_instance}{FWorldGeneratorInstance}}, \mbox{\hyperlink{class_u_world_generator}{UWorldGenerator}} >} +\DoxyCodeLine{30 \{} +\DoxyCodeLine{31 \textcolor{keyword}{public}:} +\DoxyCodeLine{32 } +\DoxyCodeLine{33 \textcolor{keyword}{using }\mbox{\hyperlink{class_f_world_generator_instance_a6835c93d686a4f8871078253233f7a01}{Super}} = TVoxelGeneratorInstanceHelper<FWorldGeneratorInstance, UWorldGenerator >;} +\DoxyCodeLine{34 } +\DoxyCodeLine{35 \textcolor{keyword}{explicit} \mbox{\hyperlink{class_f_world_generator_instance}{FWorldGeneratorInstance}}(\textcolor{keyword}{const} \mbox{\hyperlink{class_u_world_generator}{UWorldGenerator}}\& MyGenerator);} +\DoxyCodeLine{36 } +\DoxyCodeLine{37 \textcolor{keyword}{virtual} \textcolor{keywordtype}{void} Init(\textcolor{keyword}{const} FVoxelGeneratorInit\& InitStruct) \textcolor{keyword}{override};} +\DoxyCodeLine{38 } +\DoxyCodeLine{39 v\_flt GetValueImpl(v\_flt X, v\_flt Y, v\_flt Z, int32 LOD, \textcolor{keyword}{const} FVoxelItemStack\& Items) \textcolor{keyword}{const};} +\DoxyCodeLine{40 } +\DoxyCodeLine{41 FVoxelMaterial GetMaterialImpl(v\_flt X, v\_flt Y, v\_flt Z, int32 LOD, \textcolor{keyword}{const} FVoxelItemStack\& Items) \textcolor{keyword}{const};} +\DoxyCodeLine{42 } +\DoxyCodeLine{43 TVoxelRange<v\_flt> GetValueRangeImpl(\textcolor{keyword}{const} FVoxelIntBox\& Bounds, int32 LOD, \textcolor{keyword}{const} FVoxelItemStack\& Items) \textcolor{keyword}{const};} +\DoxyCodeLine{44 } +\DoxyCodeLine{45 \textcolor{keyword}{virtual} FVector GetUpVector(v\_flt X, v\_flt Y, v\_flt Z) \textcolor{keyword}{const} \textcolor{keyword}{override} \textcolor{keyword}{final};} +\DoxyCodeLine{46 \textcolor{keyword}{private}:} +\DoxyCodeLine{47 \textcolor{keyword}{const} \textcolor{keywordtype}{float} NoiseHeight;} +\DoxyCodeLine{48 \textcolor{keyword}{const} int32 Seed;} +\DoxyCodeLine{49 FVoxelFastNoise Noise;} +\DoxyCodeLine{50 \};} + +\end{DoxyCode} diff --git a/Doxygen/latex/annotated.tex b/Doxygen/latex/annotated.tex new file mode 100644 index 0000000..04fe031 --- /dev/null +++ b/Doxygen/latex/annotated.tex @@ -0,0 +1,10 @@ +\doxysection{Class List} +Here are the classes, structs, unions and interfaces with brief descriptions\+:\begin{DoxyCompactList} +\item\contentsline{section}{\mbox{\hyperlink{class_a_custom_procedural_mesh_component}{ACustom\+Procedural\+Mesh\+Component}} }{\pageref{class_a_custom_procedural_mesh_component}}{} +\item\contentsline{section}{\mbox{\hyperlink{class_a_excavator_character}{AExcavator\+Character}} }{\pageref{class_a_excavator_character}}{} +\item\contentsline{section}{\mbox{\hyperlink{class_a_excavator_simulator_game_mode_base}{AExcavator\+Simulator\+Game\+Mode\+Base}} }{\pageref{class_a_excavator_simulator_game_mode_base}}{} +\item\contentsline{section}{\mbox{\hyperlink{class_excavator_simulator}{Excavator\+Simulator}} }{\pageref{class_excavator_simulator}}{} +\item\contentsline{section}{\mbox{\hyperlink{class_f_world_generator_instance}{FWorld\+Generator\+Instance}} }{\pageref{class_f_world_generator_instance}}{} +\item\contentsline{section}{\mbox{\hyperlink{class_u_excavator_anim}{UExcavator\+Anim}} }{\pageref{class_u_excavator_anim}}{} +\item\contentsline{section}{\mbox{\hyperlink{class_u_world_generator}{UWorld\+Generator}} }{\pageref{class_u_world_generator}}{} +\end{DoxyCompactList} diff --git a/Doxygen/latex/class_a_custom_procedural_mesh_component.eps b/Doxygen/latex/class_a_custom_procedural_mesh_component.eps new file mode 100644 index 0000000..65df5fa --- /dev/null +++ b/Doxygen/latex/class_a_custom_procedural_mesh_component.eps @@ -0,0 +1,197 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: ClassName +%%Creator: Doxygen +%%CreationDate: Time +%%For: +%Magnification: 1.00 +%%Orientation: Portrait +%%BoundingBox: 0 0 500 176.211456 +%%Pages: 0 +%%BeginSetup +%%EndSetup +%%EndComments + +% ----- variables ----- + +/boxwidth 0 def +/boxheight 40 def +/fontheight 24 def +/marginwidth 10 def +/distx 20 def +/disty 40 def +/boundaspect 2.837500 def % aspect ratio of the BoundingBox (width/height) +/boundx 500 def +/boundy boundx boundaspect div def +/xspacing 0 def +/yspacing 0 def +/rows 2 def +/cols 1 def +/scalefactor 0 def +/boxfont /Times-Roman findfont fontheight scalefont def + +% ----- procedures ----- + +/dotted { [1 4] 0 setdash } def +/dashed { [5] 0 setdash } def +/solid { [] 0 setdash } def + +/max % result = MAX(arg1,arg2) +{ + /a exch def + /b exch def + a b gt {a} {b} ifelse +} def + +/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2) +{ + 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max +} def + +/cw % boxwidth = MAX(boxwidth, stringwidth(arg1)) +{ + /str exch def + /boxwidth boxwidth str stringwidth pop max def +} def + +/box % draws a box with text 'arg1' at grid pos (arg2,arg3) +{ gsave + 2 setlinewidth + newpath + exch xspacing mul xoffset add + exch yspacing mul + moveto + boxwidth 0 rlineto + 0 boxheight rlineto + boxwidth neg 0 rlineto + 0 boxheight neg rlineto + closepath + dup stringwidth pop neg boxwidth add 2 div + boxheight fontheight 2 div sub 2 div + rmoveto show stroke + grestore +} def + +/mark +{ newpath + exch xspacing mul xoffset add boxwidth add + exch yspacing mul + moveto + 0 boxheight 4 div rlineto + boxheight neg 4 div boxheight neg 4 div rlineto + closepath + eofill + stroke +} def + +/arrow +{ newpath + moveto + 3 -8 rlineto + -6 0 rlineto + 3 8 rlineto + closepath + eofill + stroke +} def + +/out % draws an output connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight add + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/in % draws an input connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul disty 2 div sub + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/hedge +{ + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight 2 div sub + /y exch def + /x exch def + newpath + x y moveto + boxwidth 2 div distx add 0 rlineto + stroke + 1 eq + { newpath x boxwidth 2 div distx add add y moveto + -8 3 rlineto + 0 -6 rlineto + 8 3 rlineto + closepath + eofill + stroke + } if +} def + +/vedge +{ + /ye exch def + /ys exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add dup + ys yspacing mul boxheight 2 div sub + moveto + ye yspacing mul boxheight 2 div sub + lineto + stroke +} def + +/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3' +{ + /ys exch def + /xe exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add + ys yspacing mul disty 2 div sub + moveto + xspacing xe xs sub mul 0 + rlineto + stroke +} def + +% ----- main ------ + +boxfont setfont +1 boundaspect scale +(ACustomProceduralMeshComponent) cw +(AActor) cw +/boxwidth boxwidth marginwidth 2 mul add def +/xspacing boxwidth distx add def +/yspacing boxheight disty add def +/scalefactor + boxwidth cols mul distx cols 1 sub mul add + boxheight rows mul disty rows 1 sub mul add boundaspect mul + max def +boundx scalefactor div boundy scalefactor div scale + +% ----- classes ----- + + (ACustomProceduralMeshComponent) 0.000000 0.000000 box + (AActor) 0.000000 1.000000 box + +% ----- relations ----- + +solid +0 0.000000 0.000000 out +solid +1 0.000000 1.000000 in diff --git a/Doxygen/latex/class_a_custom_procedural_mesh_component.tex b/Doxygen/latex/class_a_custom_procedural_mesh_component.tex new file mode 100644 index 0000000..ef1062a --- /dev/null +++ b/Doxygen/latex/class_a_custom_procedural_mesh_component.tex @@ -0,0 +1,78 @@ +\hypertarget{class_a_custom_procedural_mesh_component}{}\doxysection{ACustom\+Procedural\+Mesh\+Component Class Reference} +\label{class_a_custom_procedural_mesh_component}\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} + + +{\ttfamily \#include $<$Custom\+Procedural\+Mesh\+Component.\+h$>$} + +Inheritance diagram for ACustom\+Procedural\+Mesh\+Component\+:\begin{figure}[H] +\begin{center} +\leavevmode +\includegraphics[height=2.000000cm]{class_a_custom_procedural_mesh_component} +\end{center} +\end{figure} +\doxysubsection*{Public Member Functions} +\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{class_a_custom_procedural_mesh_component_ad2a083e99c4303ff692c6de7fd7c89d7}{ACustom\+Procedural\+Mesh\+Component}} () +\item +virtual void \mbox{\hyperlink{class_a_custom_procedural_mesh_component_a9b6c8c619a8d0396e2d3d19f61007427}{Tick}} (float Delta\+Time) override +\item +void \mbox{\hyperlink{class_a_custom_procedural_mesh_component_a9d663c4e5f670976a2e9a9de0b9e9268}{Create\+Triangle}} () +\item +void \mbox{\hyperlink{class_a_custom_procedural_mesh_component_ac1c8d09545e81f4cee3a33d8e1ff0810}{Create\+Wall}} () +\item +void \mbox{\hyperlink{class_a_custom_procedural_mesh_component_a722837f83f1b0db0cb97f47b50d310b3}{Create\+Vertices}} (int layer) +\end{DoxyCompactItemize} +\doxysubsection*{Protected Member Functions} +\begin{DoxyCompactItemize} +\item +virtual void \mbox{\hyperlink{class_a_custom_procedural_mesh_component_a91a659f1e60e2aadddc5ffd88372f66d}{Begin\+Play}} () override +\end{DoxyCompactItemize} + + +\doxysubsection{Constructor \& Destructor Documentation} +\mbox{\Hypertarget{class_a_custom_procedural_mesh_component_ad2a083e99c4303ff692c6de7fd7c89d7}\label{class_a_custom_procedural_mesh_component_ad2a083e99c4303ff692c6de7fd7c89d7}} +\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}!ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} +\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}!ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} +\doxysubsubsection{\texorpdfstring{ACustomProceduralMeshComponent()}{ACustomProceduralMeshComponent()}} +{\footnotesize\ttfamily ACustom\+Procedural\+Mesh\+Component\+::\+ACustom\+Procedural\+Mesh\+Component (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + + + +\doxysubsection{Member Function Documentation} +\mbox{\Hypertarget{class_a_custom_procedural_mesh_component_a91a659f1e60e2aadddc5ffd88372f66d}\label{class_a_custom_procedural_mesh_component_a91a659f1e60e2aadddc5ffd88372f66d}} +\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}!BeginPlay@{BeginPlay}} +\index{BeginPlay@{BeginPlay}!ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} +\doxysubsubsection{\texorpdfstring{BeginPlay()}{BeginPlay()}} +{\footnotesize\ttfamily void ACustom\+Procedural\+Mesh\+Component\+::\+Begin\+Play (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [override]}, {\ttfamily [protected]}, {\ttfamily [virtual]}} + +\mbox{\Hypertarget{class_a_custom_procedural_mesh_component_a9d663c4e5f670976a2e9a9de0b9e9268}\label{class_a_custom_procedural_mesh_component_a9d663c4e5f670976a2e9a9de0b9e9268}} +\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}!CreateTriangle@{CreateTriangle}} +\index{CreateTriangle@{CreateTriangle}!ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} +\doxysubsubsection{\texorpdfstring{CreateTriangle()}{CreateTriangle()}} +{\footnotesize\ttfamily void ACustom\+Procedural\+Mesh\+Component\+::\+Create\+Triangle (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_a_custom_procedural_mesh_component_a722837f83f1b0db0cb97f47b50d310b3}\label{class_a_custom_procedural_mesh_component_a722837f83f1b0db0cb97f47b50d310b3}} +\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}!CreateVertices@{CreateVertices}} +\index{CreateVertices@{CreateVertices}!ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} +\doxysubsubsection{\texorpdfstring{CreateVertices()}{CreateVertices()}} +{\footnotesize\ttfamily void ACustom\+Procedural\+Mesh\+Component\+::\+Create\+Vertices (\begin{DoxyParamCaption}\item[{int}]{layer }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_a_custom_procedural_mesh_component_ac1c8d09545e81f4cee3a33d8e1ff0810}\label{class_a_custom_procedural_mesh_component_ac1c8d09545e81f4cee3a33d8e1ff0810}} +\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}!CreateWall@{CreateWall}} +\index{CreateWall@{CreateWall}!ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} +\doxysubsubsection{\texorpdfstring{CreateWall()}{CreateWall()}} +{\footnotesize\ttfamily void ACustom\+Procedural\+Mesh\+Component\+::\+Create\+Wall (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_a_custom_procedural_mesh_component_a9b6c8c619a8d0396e2d3d19f61007427}\label{class_a_custom_procedural_mesh_component_a9b6c8c619a8d0396e2d3d19f61007427}} +\index{ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}!Tick@{Tick}} +\index{Tick@{Tick}!ACustomProceduralMeshComponent@{ACustomProceduralMeshComponent}} +\doxysubsubsection{\texorpdfstring{Tick()}{Tick()}} +{\footnotesize\ttfamily void ACustom\+Procedural\+Mesh\+Component\+::\+Tick (\begin{DoxyParamCaption}\item[{float}]{Delta\+Time }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [override]}, {\ttfamily [virtual]}} + + + +The documentation for this class was generated from the following files\+:\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_custom_procedural_mesh_component_8h}{Custom\+Procedural\+Mesh\+Component.\+h}}\item +\mbox{\hyperlink{_custom_procedural_mesh_component_8cpp}{Custom\+Procedural\+Mesh\+Component.\+cpp}}\end{DoxyCompactItemize} diff --git a/Doxygen/latex/class_a_excavator_character.eps b/Doxygen/latex/class_a_excavator_character.eps new file mode 100644 index 0000000..5c47f78 --- /dev/null +++ b/Doxygen/latex/class_a_excavator_character.eps @@ -0,0 +1,197 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: ClassName +%%Creator: Doxygen +%%CreationDate: Time +%%For: +%Magnification: 1.00 +%%Orientation: Portrait +%%BoundingBox: 0 0 500 279.720276 +%%Pages: 0 +%%BeginSetup +%%EndSetup +%%EndComments + +% ----- variables ----- + +/boxwidth 0 def +/boxheight 40 def +/fontheight 24 def +/marginwidth 10 def +/distx 20 def +/disty 40 def +/boundaspect 1.787500 def % aspect ratio of the BoundingBox (width/height) +/boundx 500 def +/boundy boundx boundaspect div def +/xspacing 0 def +/yspacing 0 def +/rows 2 def +/cols 1 def +/scalefactor 0 def +/boxfont /Times-Roman findfont fontheight scalefont def + +% ----- procedures ----- + +/dotted { [1 4] 0 setdash } def +/dashed { [5] 0 setdash } def +/solid { [] 0 setdash } def + +/max % result = MAX(arg1,arg2) +{ + /a exch def + /b exch def + a b gt {a} {b} ifelse +} def + +/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2) +{ + 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max +} def + +/cw % boxwidth = MAX(boxwidth, stringwidth(arg1)) +{ + /str exch def + /boxwidth boxwidth str stringwidth pop max def +} def + +/box % draws a box with text 'arg1' at grid pos (arg2,arg3) +{ gsave + 2 setlinewidth + newpath + exch xspacing mul xoffset add + exch yspacing mul + moveto + boxwidth 0 rlineto + 0 boxheight rlineto + boxwidth neg 0 rlineto + 0 boxheight neg rlineto + closepath + dup stringwidth pop neg boxwidth add 2 div + boxheight fontheight 2 div sub 2 div + rmoveto show stroke + grestore +} def + +/mark +{ newpath + exch xspacing mul xoffset add boxwidth add + exch yspacing mul + moveto + 0 boxheight 4 div rlineto + boxheight neg 4 div boxheight neg 4 div rlineto + closepath + eofill + stroke +} def + +/arrow +{ newpath + moveto + 3 -8 rlineto + -6 0 rlineto + 3 8 rlineto + closepath + eofill + stroke +} def + +/out % draws an output connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight add + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/in % draws an input connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul disty 2 div sub + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/hedge +{ + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight 2 div sub + /y exch def + /x exch def + newpath + x y moveto + boxwidth 2 div distx add 0 rlineto + stroke + 1 eq + { newpath x boxwidth 2 div distx add add y moveto + -8 3 rlineto + 0 -6 rlineto + 8 3 rlineto + closepath + eofill + stroke + } if +} def + +/vedge +{ + /ye exch def + /ys exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add dup + ys yspacing mul boxheight 2 div sub + moveto + ye yspacing mul boxheight 2 div sub + lineto + stroke +} def + +/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3' +{ + /ys exch def + /xe exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add + ys yspacing mul disty 2 div sub + moveto + xspacing xe xs sub mul 0 + rlineto + stroke +} def + +% ----- main ------ + +boxfont setfont +1 boundaspect scale +(AExcavatorCharacter) cw +(ACharacter) cw +/boxwidth boxwidth marginwidth 2 mul add def +/xspacing boxwidth distx add def +/yspacing boxheight disty add def +/scalefactor + boxwidth cols mul distx cols 1 sub mul add + boxheight rows mul disty rows 1 sub mul add boundaspect mul + max def +boundx scalefactor div boundy scalefactor div scale + +% ----- classes ----- + + (AExcavatorCharacter) 0.000000 0.000000 box + (ACharacter) 0.000000 1.000000 box + +% ----- relations ----- + +solid +0 0.000000 0.000000 out +solid +1 0.000000 1.000000 in diff --git a/Doxygen/latex/class_a_excavator_character.tex b/Doxygen/latex/class_a_excavator_character.tex new file mode 100644 index 0000000..bd85dd9 --- /dev/null +++ b/Doxygen/latex/class_a_excavator_character.tex @@ -0,0 +1,150 @@ +\hypertarget{class_a_excavator_character}{}\doxysection{AExcavator\+Character Class Reference} +\label{class_a_excavator_character}\index{AExcavatorCharacter@{AExcavatorCharacter}} + + +{\ttfamily \#include $<$Excavator\+Character.\+h$>$} + +Inheritance diagram for AExcavator\+Character\+:\begin{figure}[H] +\begin{center} +\leavevmode +\includegraphics[height=2.000000cm]{class_a_excavator_character} +\end{center} +\end{figure} +\doxysubsection*{Public Member Functions} +\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{class_a_excavator_character_a897f85cbfeb62c8cf9357124030678f0}{AExcavator\+Character}} () +\item +virtual void \mbox{\hyperlink{class_a_excavator_character_a080c503815b1f096cf7704e5bfa47df1}{Tick}} (float Delta\+Time) override +\item +virtual void \mbox{\hyperlink{class_a_excavator_character_ae0c6e52ac2b153baa789be4e0b2b75fc}{Setup\+Player\+Input\+Component}} (class UInput\+Component $\ast$Player\+Input\+Component) override +\item +void \mbox{\hyperlink{class_a_excavator_character_abfc64bb0a310badadb9e85e7336b0735}{Movement}} (float Direction) +\item +bool \mbox{\hyperlink{class_a_excavator_character_a18933e6161a7489652e64e06d6dc9d72}{Bucket\+Rotation\+Releace\+Rocks}} (float Rotation\+Direction) +\item +void \mbox{\hyperlink{class_a_excavator_character_a5eaf154b0d816e682669609f6a1145b4}{Bucket\+Rotation\+VR}} (float Right\+Joy\+Stick\+RotationX) +\item +void \mbox{\hyperlink{class_a_excavator_character_a700a5e4f8e43868c15d6b60460006eb9}{Beam\+Top\+Rotation}} (float Rotation\+Direction) +\item +void \mbox{\hyperlink{class_a_excavator_character_a0fa69b8a1aa0f05dad74a41da45f5551}{Beam\+Top\+Rotation\+VR}} (float Right\+Joy\+Stick\+RotationY) +\item +void \mbox{\hyperlink{class_a_excavator_character_a4c0322adf74c147b25e0a56eb82e8b63}{Beam\+Bottom\+Rotation}} (float Rotation\+Direction) +\item +void \mbox{\hyperlink{class_a_excavator_character_a2fd6354fdc9f48c9ec37aa8a0881bb39}{Beam\+Bottom\+Rotation\+VR}} (float Left\+Joy\+Stick\+RotationY) +\item +void \mbox{\hyperlink{class_a_excavator_character_a63014640642f4964440cd3923cc8396f}{Body\+Rotation}} (float Rotation\+Direction) +\item +void \mbox{\hyperlink{class_a_excavator_character_a52c7b1ac54ccb49c0cd5e9baf25dc338}{Body\+Rotation\+VR}} (float Left\+Joy\+Stick\+RotationX) +\item +void \mbox{\hyperlink{class_a_excavator_character_a3f262813a65448739540f41c73498f56}{Vehicle\+Rotation}} (float Rotation\+Direction) +\item +void \mbox{\hyperlink{class_a_excavator_character_a173be62746e6adc66879eea680f21d56}{Vehicle\+Rotation\+VR}} (float Axis\+Value) +\end{DoxyCompactItemize} +\doxysubsection*{Protected Member Functions} +\begin{DoxyCompactItemize} +\item +virtual void \mbox{\hyperlink{class_a_excavator_character_aff0f3c6a77a15a8476981c7845dff158}{Begin\+Play}} () override +\end{DoxyCompactItemize} + + +\doxysubsection{Constructor \& Destructor Documentation} +\mbox{\Hypertarget{class_a_excavator_character_a897f85cbfeb62c8cf9357124030678f0}\label{class_a_excavator_character_a897f85cbfeb62c8cf9357124030678f0}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!AExcavatorCharacter@{AExcavatorCharacter}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{AExcavatorCharacter()}{AExcavatorCharacter()}} +{\footnotesize\ttfamily AExcavator\+Character\+::\+AExcavator\+Character (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +Sets default values for this character\textquotesingle{}s properties. + +\doxysubsection{Member Function Documentation} +\mbox{\Hypertarget{class_a_excavator_character_a4c0322adf74c147b25e0a56eb82e8b63}\label{class_a_excavator_character_a4c0322adf74c147b25e0a56eb82e8b63}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BeamBottomRotation@{BeamBottomRotation}} +\index{BeamBottomRotation@{BeamBottomRotation}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BeamBottomRotation()}{BeamBottomRotation()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Beam\+Bottom\+Rotation (\begin{DoxyParamCaption}\item[{float}]{Rotation\+Direction }\end{DoxyParamCaption})} + +Rotates beam bottom @\+Param Rotation\+Direction beam bottom rotation direction \mbox{\Hypertarget{class_a_excavator_character_a2fd6354fdc9f48c9ec37aa8a0881bb39}\label{class_a_excavator_character_a2fd6354fdc9f48c9ec37aa8a0881bb39}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BeamBottomRotationVR@{BeamBottomRotationVR}} +\index{BeamBottomRotationVR@{BeamBottomRotationVR}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BeamBottomRotationVR()}{BeamBottomRotationVR()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Beam\+Bottom\+Rotation\+VR (\begin{DoxyParamCaption}\item[{float}]{Left\+Joy\+Stick\+RotationY }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_a_excavator_character_a700a5e4f8e43868c15d6b60460006eb9}\label{class_a_excavator_character_a700a5e4f8e43868c15d6b60460006eb9}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BeamTopRotation@{BeamTopRotation}} +\index{BeamTopRotation@{BeamTopRotation}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BeamTopRotation()}{BeamTopRotation()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Beam\+Top\+Rotation (\begin{DoxyParamCaption}\item[{float}]{Rotation\+Direction }\end{DoxyParamCaption})} + +Rotates beam top @\+Param Rotation\+Direction beam top rotation direction \mbox{\Hypertarget{class_a_excavator_character_a0fa69b8a1aa0f05dad74a41da45f5551}\label{class_a_excavator_character_a0fa69b8a1aa0f05dad74a41da45f5551}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BeamTopRotationVR@{BeamTopRotationVR}} +\index{BeamTopRotationVR@{BeamTopRotationVR}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BeamTopRotationVR()}{BeamTopRotationVR()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Beam\+Top\+Rotation\+VR (\begin{DoxyParamCaption}\item[{float}]{Right\+Joy\+Stick\+RotationY }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_a_excavator_character_aff0f3c6a77a15a8476981c7845dff158}\label{class_a_excavator_character_aff0f3c6a77a15a8476981c7845dff158}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BeginPlay@{BeginPlay}} +\index{BeginPlay@{BeginPlay}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BeginPlay()}{BeginPlay()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Begin\+Play (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [override]}, {\ttfamily [protected]}, {\ttfamily [virtual]}} + +Called when the game starts or when spawned. \mbox{\Hypertarget{class_a_excavator_character_a63014640642f4964440cd3923cc8396f}\label{class_a_excavator_character_a63014640642f4964440cd3923cc8396f}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BodyRotation@{BodyRotation}} +\index{BodyRotation@{BodyRotation}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BodyRotation()}{BodyRotation()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Body\+Rotation (\begin{DoxyParamCaption}\item[{float}]{Rotation\+Direction }\end{DoxyParamCaption})} + +Rotates excavator body @\+Param Rotation\+Direction body rotation direction \mbox{\Hypertarget{class_a_excavator_character_a52c7b1ac54ccb49c0cd5e9baf25dc338}\label{class_a_excavator_character_a52c7b1ac54ccb49c0cd5e9baf25dc338}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BodyRotationVR@{BodyRotationVR}} +\index{BodyRotationVR@{BodyRotationVR}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BodyRotationVR()}{BodyRotationVR()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Body\+Rotation\+VR (\begin{DoxyParamCaption}\item[{float}]{Left\+Joy\+Stick\+RotationX }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_a_excavator_character_a18933e6161a7489652e64e06d6dc9d72}\label{class_a_excavator_character_a18933e6161a7489652e64e06d6dc9d72}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BucketRotationReleaceRocks@{BucketRotationReleaceRocks}} +\index{BucketRotationReleaceRocks@{BucketRotationReleaceRocks}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BucketRotationReleaceRocks()}{BucketRotationReleaceRocks()}} +{\footnotesize\ttfamily bool AExcavator\+Character\+::\+Bucket\+Rotation\+Releace\+Rocks (\begin{DoxyParamCaption}\item[{float}]{Rotation\+Direction }\end{DoxyParamCaption})} + +Rotates bucket and will release rocks at angle @\+Param Rotation\+Direction buckets rotation direction \mbox{\Hypertarget{class_a_excavator_character_a5eaf154b0d816e682669609f6a1145b4}\label{class_a_excavator_character_a5eaf154b0d816e682669609f6a1145b4}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!BucketRotationVR@{BucketRotationVR}} +\index{BucketRotationVR@{BucketRotationVR}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{BucketRotationVR()}{BucketRotationVR()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Bucket\+Rotation\+VR (\begin{DoxyParamCaption}\item[{float}]{Right\+Joy\+Stick\+RotationX }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_a_excavator_character_abfc64bb0a310badadb9e85e7336b0735}\label{class_a_excavator_character_abfc64bb0a310badadb9e85e7336b0735}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!Movement@{Movement}} +\index{Movement@{Movement}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{Movement()}{Movement()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Movement (\begin{DoxyParamCaption}\item[{float}]{Direction }\end{DoxyParamCaption})} + +Movement, move the player @\+Param direction player is going \mbox{\Hypertarget{class_a_excavator_character_ae0c6e52ac2b153baa789be4e0b2b75fc}\label{class_a_excavator_character_ae0c6e52ac2b153baa789be4e0b2b75fc}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!SetupPlayerInputComponent@{SetupPlayerInputComponent}} +\index{SetupPlayerInputComponent@{SetupPlayerInputComponent}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{SetupPlayerInputComponent()}{SetupPlayerInputComponent()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Setup\+Player\+Input\+Component (\begin{DoxyParamCaption}\item[{class UInput\+Component $\ast$}]{Player\+Input\+Component }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [override]}, {\ttfamily [virtual]}} + +Called to bind functionality to input. \mbox{\Hypertarget{class_a_excavator_character_a080c503815b1f096cf7704e5bfa47df1}\label{class_a_excavator_character_a080c503815b1f096cf7704e5bfa47df1}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!Tick@{Tick}} +\index{Tick@{Tick}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{Tick()}{Tick()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Tick (\begin{DoxyParamCaption}\item[{float}]{Delta\+Time }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [override]}, {\ttfamily [virtual]}} + +Called every frame. \mbox{\Hypertarget{class_a_excavator_character_a3f262813a65448739540f41c73498f56}\label{class_a_excavator_character_a3f262813a65448739540f41c73498f56}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!VehicleRotation@{VehicleRotation}} +\index{VehicleRotation@{VehicleRotation}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{VehicleRotation()}{VehicleRotation()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Vehicle\+Rotation (\begin{DoxyParamCaption}\item[{float}]{Rotation\+Direction }\end{DoxyParamCaption})} + +Rotates whole vehicle @\+Param Rotation\+Direction vehicle rotation direction \mbox{\Hypertarget{class_a_excavator_character_a173be62746e6adc66879eea680f21d56}\label{class_a_excavator_character_a173be62746e6adc66879eea680f21d56}} +\index{AExcavatorCharacter@{AExcavatorCharacter}!VehicleRotationVR@{VehicleRotationVR}} +\index{VehicleRotationVR@{VehicleRotationVR}!AExcavatorCharacter@{AExcavatorCharacter}} +\doxysubsubsection{\texorpdfstring{VehicleRotationVR()}{VehicleRotationVR()}} +{\footnotesize\ttfamily void AExcavator\+Character\+::\+Vehicle\+Rotation\+VR (\begin{DoxyParamCaption}\item[{float}]{Axis\+Value }\end{DoxyParamCaption})} + + + +The documentation for this class was generated from the following files\+:\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_excavator_character_8h}{Excavator\+Character.\+h}}\item +\mbox{\hyperlink{_excavator_character_8cpp}{Excavator\+Character.\+cpp}}\end{DoxyCompactItemize} diff --git a/Doxygen/latex/class_a_excavator_simulator_game_mode_base.eps b/Doxygen/latex/class_a_excavator_simulator_game_mode_base.eps new file mode 100644 index 0000000..1877410 --- /dev/null +++ b/Doxygen/latex/class_a_excavator_simulator_game_mode_base.eps @@ -0,0 +1,197 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: ClassName +%%Creator: Doxygen +%%CreationDate: Time +%%For: +%Magnification: 1.00 +%%Orientation: Portrait +%%BoundingBox: 0 0 500 173.913040 +%%Pages: 0 +%%BeginSetup +%%EndSetup +%%EndComments + +% ----- variables ----- + +/boxwidth 0 def +/boxheight 40 def +/fontheight 24 def +/marginwidth 10 def +/distx 20 def +/disty 40 def +/boundaspect 2.875000 def % aspect ratio of the BoundingBox (width/height) +/boundx 500 def +/boundy boundx boundaspect div def +/xspacing 0 def +/yspacing 0 def +/rows 2 def +/cols 1 def +/scalefactor 0 def +/boxfont /Times-Roman findfont fontheight scalefont def + +% ----- procedures ----- + +/dotted { [1 4] 0 setdash } def +/dashed { [5] 0 setdash } def +/solid { [] 0 setdash } def + +/max % result = MAX(arg1,arg2) +{ + /a exch def + /b exch def + a b gt {a} {b} ifelse +} def + +/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2) +{ + 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max +} def + +/cw % boxwidth = MAX(boxwidth, stringwidth(arg1)) +{ + /str exch def + /boxwidth boxwidth str stringwidth pop max def +} def + +/box % draws a box with text 'arg1' at grid pos (arg2,arg3) +{ gsave + 2 setlinewidth + newpath + exch xspacing mul xoffset add + exch yspacing mul + moveto + boxwidth 0 rlineto + 0 boxheight rlineto + boxwidth neg 0 rlineto + 0 boxheight neg rlineto + closepath + dup stringwidth pop neg boxwidth add 2 div + boxheight fontheight 2 div sub 2 div + rmoveto show stroke + grestore +} def + +/mark +{ newpath + exch xspacing mul xoffset add boxwidth add + exch yspacing mul + moveto + 0 boxheight 4 div rlineto + boxheight neg 4 div boxheight neg 4 div rlineto + closepath + eofill + stroke +} def + +/arrow +{ newpath + moveto + 3 -8 rlineto + -6 0 rlineto + 3 8 rlineto + closepath + eofill + stroke +} def + +/out % draws an output connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight add + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/in % draws an input connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul disty 2 div sub + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/hedge +{ + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight 2 div sub + /y exch def + /x exch def + newpath + x y moveto + boxwidth 2 div distx add 0 rlineto + stroke + 1 eq + { newpath x boxwidth 2 div distx add add y moveto + -8 3 rlineto + 0 -6 rlineto + 8 3 rlineto + closepath + eofill + stroke + } if +} def + +/vedge +{ + /ye exch def + /ys exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add dup + ys yspacing mul boxheight 2 div sub + moveto + ye yspacing mul boxheight 2 div sub + lineto + stroke +} def + +/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3' +{ + /ys exch def + /xe exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add + ys yspacing mul disty 2 div sub + moveto + xspacing xe xs sub mul 0 + rlineto + stroke +} def + +% ----- main ------ + +boxfont setfont +1 boundaspect scale +(AExcavatorSimulatorGameModeBase) cw +(AGameModeBase) cw +/boxwidth boxwidth marginwidth 2 mul add def +/xspacing boxwidth distx add def +/yspacing boxheight disty add def +/scalefactor + boxwidth cols mul distx cols 1 sub mul add + boxheight rows mul disty rows 1 sub mul add boundaspect mul + max def +boundx scalefactor div boundy scalefactor div scale + +% ----- classes ----- + + (AExcavatorSimulatorGameModeBase) 0.000000 0.000000 box + (AGameModeBase) 0.000000 1.000000 box + +% ----- relations ----- + +solid +0 0.000000 0.000000 out +solid +1 0.000000 1.000000 in diff --git a/Doxygen/latex/class_a_excavator_simulator_game_mode_base.tex b/Doxygen/latex/class_a_excavator_simulator_game_mode_base.tex new file mode 100644 index 0000000..70b0101 --- /dev/null +++ b/Doxygen/latex/class_a_excavator_simulator_game_mode_base.tex @@ -0,0 +1,17 @@ +\hypertarget{class_a_excavator_simulator_game_mode_base}{}\doxysection{AExcavator\+Simulator\+Game\+Mode\+Base Class Reference} +\label{class_a_excavator_simulator_game_mode_base}\index{AExcavatorSimulatorGameModeBase@{AExcavatorSimulatorGameModeBase}} + + +{\ttfamily \#include $<$Excavator\+Simulator\+Game\+Mode\+Base.\+h$>$} + +Inheritance diagram for AExcavator\+Simulator\+Game\+Mode\+Base\+:\begin{figure}[H] +\begin{center} +\leavevmode +\includegraphics[height=2.000000cm]{class_a_excavator_simulator_game_mode_base} +\end{center} +\end{figure} + + +The documentation for this class was generated from the following file\+:\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_excavator_simulator_game_mode_base_8h}{Excavator\+Simulator\+Game\+Mode\+Base.\+h}}\end{DoxyCompactItemize} diff --git a/Doxygen/latex/class_excavator_simulator.eps b/Doxygen/latex/class_excavator_simulator.eps new file mode 100644 index 0000000..79951fa --- /dev/null +++ b/Doxygen/latex/class_excavator_simulator.eps @@ -0,0 +1,197 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: ClassName +%%Creator: Doxygen +%%CreationDate: Time +%%For: +%Magnification: 1.00 +%%Orientation: Portrait +%%BoundingBox: 0 0 500 310.077515 +%%Pages: 0 +%%BeginSetup +%%EndSetup +%%EndComments + +% ----- variables ----- + +/boxwidth 0 def +/boxheight 40 def +/fontheight 24 def +/marginwidth 10 def +/distx 20 def +/disty 40 def +/boundaspect 1.612500 def % aspect ratio of the BoundingBox (width/height) +/boundx 500 def +/boundy boundx boundaspect div def +/xspacing 0 def +/yspacing 0 def +/rows 2 def +/cols 1 def +/scalefactor 0 def +/boxfont /Times-Roman findfont fontheight scalefont def + +% ----- procedures ----- + +/dotted { [1 4] 0 setdash } def +/dashed { [5] 0 setdash } def +/solid { [] 0 setdash } def + +/max % result = MAX(arg1,arg2) +{ + /a exch def + /b exch def + a b gt {a} {b} ifelse +} def + +/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2) +{ + 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max +} def + +/cw % boxwidth = MAX(boxwidth, stringwidth(arg1)) +{ + /str exch def + /boxwidth boxwidth str stringwidth pop max def +} def + +/box % draws a box with text 'arg1' at grid pos (arg2,arg3) +{ gsave + 2 setlinewidth + newpath + exch xspacing mul xoffset add + exch yspacing mul + moveto + boxwidth 0 rlineto + 0 boxheight rlineto + boxwidth neg 0 rlineto + 0 boxheight neg rlineto + closepath + dup stringwidth pop neg boxwidth add 2 div + boxheight fontheight 2 div sub 2 div + rmoveto show stroke + grestore +} def + +/mark +{ newpath + exch xspacing mul xoffset add boxwidth add + exch yspacing mul + moveto + 0 boxheight 4 div rlineto + boxheight neg 4 div boxheight neg 4 div rlineto + closepath + eofill + stroke +} def + +/arrow +{ newpath + moveto + 3 -8 rlineto + -6 0 rlineto + 3 8 rlineto + closepath + eofill + stroke +} def + +/out % draws an output connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight add + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/in % draws an input connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul disty 2 div sub + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/hedge +{ + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight 2 div sub + /y exch def + /x exch def + newpath + x y moveto + boxwidth 2 div distx add 0 rlineto + stroke + 1 eq + { newpath x boxwidth 2 div distx add add y moveto + -8 3 rlineto + 0 -6 rlineto + 8 3 rlineto + closepath + eofill + stroke + } if +} def + +/vedge +{ + /ye exch def + /ys exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add dup + ys yspacing mul boxheight 2 div sub + moveto + ye yspacing mul boxheight 2 div sub + lineto + stroke +} def + +/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3' +{ + /ys exch def + /xe exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add + ys yspacing mul disty 2 div sub + moveto + xspacing xe xs sub mul 0 + rlineto + stroke +} def + +% ----- main ------ + +boxfont setfont +1 boundaspect scale +(ExcavatorSimulator) cw +(ModuleRules) cw +/boxwidth boxwidth marginwidth 2 mul add def +/xspacing boxwidth distx add def +/yspacing boxheight disty add def +/scalefactor + boxwidth cols mul distx cols 1 sub mul add + boxheight rows mul disty rows 1 sub mul add boundaspect mul + max def +boundx scalefactor div boundy scalefactor div scale + +% ----- classes ----- + + (ExcavatorSimulator) 0.000000 0.000000 box + (ModuleRules) 0.000000 1.000000 box + +% ----- relations ----- + +solid +0 0.000000 0.000000 out +solid +1 0.000000 1.000000 in diff --git a/Doxygen/latex/class_excavator_simulator.tex b/Doxygen/latex/class_excavator_simulator.tex new file mode 100644 index 0000000..04fd4a7 --- /dev/null +++ b/Doxygen/latex/class_excavator_simulator.tex @@ -0,0 +1,27 @@ +\hypertarget{class_excavator_simulator}{}\doxysection{Excavator\+Simulator Class Reference} +\label{class_excavator_simulator}\index{ExcavatorSimulator@{ExcavatorSimulator}} +Inheritance diagram for Excavator\+Simulator\+:\begin{figure}[H] +\begin{center} +\leavevmode +\includegraphics[height=2.000000cm]{class_excavator_simulator} +\end{center} +\end{figure} +\doxysubsection*{Public Member Functions} +\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{class_excavator_simulator_ac3cbb6777236f4b4900e850d58050951}{Excavator\+Simulator}} (Read\+Only\+Target\+Rules Target) +\end{DoxyCompactItemize} + + +\doxysubsection{Constructor \& Destructor Documentation} +\mbox{\Hypertarget{class_excavator_simulator_ac3cbb6777236f4b4900e850d58050951}\label{class_excavator_simulator_ac3cbb6777236f4b4900e850d58050951}} +\index{ExcavatorSimulator@{ExcavatorSimulator}!ExcavatorSimulator@{ExcavatorSimulator}} +\index{ExcavatorSimulator@{ExcavatorSimulator}!ExcavatorSimulator@{ExcavatorSimulator}} +\doxysubsubsection{\texorpdfstring{ExcavatorSimulator()}{ExcavatorSimulator()}} +{\footnotesize\ttfamily Excavator\+Simulator.\+Excavator\+Simulator (\begin{DoxyParamCaption}\item[{Read\+Only\+Target\+Rules}]{Target }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [inline]}} + + + +The documentation for this class was generated from the following file\+:\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_excavator_simulator_8_build_8cs}{Excavator\+Simulator.\+Build.\+cs}}\end{DoxyCompactItemize} diff --git a/Doxygen/latex/class_f_world_generator_instance.eps b/Doxygen/latex/class_f_world_generator_instance.eps new file mode 100644 index 0000000..47fcab2 --- /dev/null +++ b/Doxygen/latex/class_f_world_generator_instance.eps @@ -0,0 +1,197 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: ClassName +%%Creator: Doxygen +%%CreationDate: Time +%%For: +%Magnification: 1.00 +%%Orientation: Portrait +%%BoundingBox: 0 0 500 83.857445 +%%Pages: 0 +%%BeginSetup +%%EndSetup +%%EndComments + +% ----- variables ----- + +/boxwidth 0 def +/boxheight 40 def +/fontheight 24 def +/marginwidth 10 def +/distx 20 def +/disty 40 def +/boundaspect 5.962500 def % aspect ratio of the BoundingBox (width/height) +/boundx 500 def +/boundy boundx boundaspect div def +/xspacing 0 def +/yspacing 0 def +/rows 2 def +/cols 1 def +/scalefactor 0 def +/boxfont /Times-Roman findfont fontheight scalefont def + +% ----- procedures ----- + +/dotted { [1 4] 0 setdash } def +/dashed { [5] 0 setdash } def +/solid { [] 0 setdash } def + +/max % result = MAX(arg1,arg2) +{ + /a exch def + /b exch def + a b gt {a} {b} ifelse +} def + +/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2) +{ + 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max +} def + +/cw % boxwidth = MAX(boxwidth, stringwidth(arg1)) +{ + /str exch def + /boxwidth boxwidth str stringwidth pop max def +} def + +/box % draws a box with text 'arg1' at grid pos (arg2,arg3) +{ gsave + 2 setlinewidth + newpath + exch xspacing mul xoffset add + exch yspacing mul + moveto + boxwidth 0 rlineto + 0 boxheight rlineto + boxwidth neg 0 rlineto + 0 boxheight neg rlineto + closepath + dup stringwidth pop neg boxwidth add 2 div + boxheight fontheight 2 div sub 2 div + rmoveto show stroke + grestore +} def + +/mark +{ newpath + exch xspacing mul xoffset add boxwidth add + exch yspacing mul + moveto + 0 boxheight 4 div rlineto + boxheight neg 4 div boxheight neg 4 div rlineto + closepath + eofill + stroke +} def + +/arrow +{ newpath + moveto + 3 -8 rlineto + -6 0 rlineto + 3 8 rlineto + closepath + eofill + stroke +} def + +/out % draws an output connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight add + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/in % draws an input connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul disty 2 div sub + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/hedge +{ + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight 2 div sub + /y exch def + /x exch def + newpath + x y moveto + boxwidth 2 div distx add 0 rlineto + stroke + 1 eq + { newpath x boxwidth 2 div distx add add y moveto + -8 3 rlineto + 0 -6 rlineto + 8 3 rlineto + closepath + eofill + stroke + } if +} def + +/vedge +{ + /ye exch def + /ys exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add dup + ys yspacing mul boxheight 2 div sub + moveto + ye yspacing mul boxheight 2 div sub + lineto + stroke +} def + +/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3' +{ + /ys exch def + /xe exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add + ys yspacing mul disty 2 div sub + moveto + xspacing xe xs sub mul 0 + rlineto + stroke +} def + +% ----- main ------ + +boxfont setfont +1 boundaspect scale +(FWorldGeneratorInstance) cw +(TVoxelGeneratorInstanceHelper< FWorldGeneratorInstance, UWorldGenerator >) cw +/boxwidth boxwidth marginwidth 2 mul add def +/xspacing boxwidth distx add def +/yspacing boxheight disty add def +/scalefactor + boxwidth cols mul distx cols 1 sub mul add + boxheight rows mul disty rows 1 sub mul add boundaspect mul + max def +boundx scalefactor div boundy scalefactor div scale + +% ----- classes ----- + + (FWorldGeneratorInstance) 0.000000 0.000000 box + (TVoxelGeneratorInstanceHelper< FWorldGeneratorInstance, UWorldGenerator >) 0.000000 1.000000 box + +% ----- relations ----- + +solid +0 0.000000 0.000000 out +solid +1 0.000000 1.000000 in diff --git a/Doxygen/latex/class_f_world_generator_instance.tex b/Doxygen/latex/class_f_world_generator_instance.tex new file mode 100644 index 0000000..c310888 --- /dev/null +++ b/Doxygen/latex/class_f_world_generator_instance.tex @@ -0,0 +1,89 @@ +\hypertarget{class_f_world_generator_instance}{}\doxysection{FWorld\+Generator\+Instance Class Reference} +\label{class_f_world_generator_instance}\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}} + + +{\ttfamily \#include $<$World\+Generator.\+h$>$} + +Inheritance diagram for FWorld\+Generator\+Instance\+:\begin{figure}[H] +\begin{center} +\leavevmode +\includegraphics[height=2.000000cm]{class_f_world_generator_instance} +\end{center} +\end{figure} +\doxysubsection*{Public Types} +\begin{DoxyCompactItemize} +\item +using \mbox{\hyperlink{class_f_world_generator_instance_a6835c93d686a4f8871078253233f7a01}{Super}} = TVoxel\+Generator\+Instance\+Helper$<$ \mbox{\hyperlink{class_f_world_generator_instance}{FWorld\+Generator\+Instance}}, \mbox{\hyperlink{class_u_world_generator}{UWorld\+Generator}} $>$ +\end{DoxyCompactItemize} +\doxysubsection*{Public Member Functions} +\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{class_f_world_generator_instance_a9340a460da1c5a05a25e496f5bbe3e47}{FWorld\+Generator\+Instance}} (const \mbox{\hyperlink{class_u_world_generator}{UWorld\+Generator}} \&My\+Generator) +\item +virtual void \mbox{\hyperlink{class_f_world_generator_instance_acb9442be2e711fb3ba4d146bf584bd5d}{Init}} (const FVoxel\+Generator\+Init \&Init\+Struct) override +\item +v\+\_\+flt \mbox{\hyperlink{class_f_world_generator_instance_a9a18ba21423f11dc01d3a2e54fbd214f}{Get\+Value\+Impl}} (v\+\_\+flt X, v\+\_\+flt Y, v\+\_\+flt Z, int32 LOD, const FVoxel\+Item\+Stack \&Items) const +\item +FVoxel\+Material \mbox{\hyperlink{class_f_world_generator_instance_a5d982bbb90e6a7368d4eab2bbf4107a0}{Get\+Material\+Impl}} (v\+\_\+flt X, v\+\_\+flt Y, v\+\_\+flt Z, int32 LOD, const FVoxel\+Item\+Stack \&Items) const +\item +TVoxel\+Range$<$ v\+\_\+flt $>$ \mbox{\hyperlink{class_f_world_generator_instance_aa18eccf536acdf8d2115f32ac0dab1ef}{Get\+Value\+Range\+Impl}} (const FVoxel\+Int\+Box \&Bounds, int32 LOD, const FVoxel\+Item\+Stack \&Items) const +\item +virtual FVector \mbox{\hyperlink{class_f_world_generator_instance_a41a32aee12e9228c3ec2bdfa4be7a92a}{Get\+Up\+Vector}} (v\+\_\+flt X, v\+\_\+flt Y, v\+\_\+flt Z) const override final +\end{DoxyCompactItemize} + + +\doxysubsection{Member Typedef Documentation} +\mbox{\Hypertarget{class_f_world_generator_instance_a6835c93d686a4f8871078253233f7a01}\label{class_f_world_generator_instance_a6835c93d686a4f8871078253233f7a01}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!Super@{Super}} +\index{Super@{Super}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\doxysubsubsection{\texorpdfstring{Super}{Super}} +{\footnotesize\ttfamily using \mbox{\hyperlink{class_f_world_generator_instance_a6835c93d686a4f8871078253233f7a01}{FWorld\+Generator\+Instance\+::\+Super}} = TVoxel\+Generator\+Instance\+Helper$<$\mbox{\hyperlink{class_f_world_generator_instance}{FWorld\+Generator\+Instance}}, \mbox{\hyperlink{class_u_world_generator}{UWorld\+Generator}} $>$} + + + +\doxysubsection{Constructor \& Destructor Documentation} +\mbox{\Hypertarget{class_f_world_generator_instance_a9340a460da1c5a05a25e496f5bbe3e47}\label{class_f_world_generator_instance_a9340a460da1c5a05a25e496f5bbe3e47}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\doxysubsubsection{\texorpdfstring{FWorldGeneratorInstance()}{FWorldGeneratorInstance()}} +{\footnotesize\ttfamily FWorld\+Generator\+Instance\+::\+FWorld\+Generator\+Instance (\begin{DoxyParamCaption}\item[{const \mbox{\hyperlink{class_u_world_generator}{UWorld\+Generator}} \&}]{My\+Generator }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [explicit]}} + + + +\doxysubsection{Member Function Documentation} +\mbox{\Hypertarget{class_f_world_generator_instance_a5d982bbb90e6a7368d4eab2bbf4107a0}\label{class_f_world_generator_instance_a5d982bbb90e6a7368d4eab2bbf4107a0}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!GetMaterialImpl@{GetMaterialImpl}} +\index{GetMaterialImpl@{GetMaterialImpl}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\doxysubsubsection{\texorpdfstring{GetMaterialImpl()}{GetMaterialImpl()}} +{\footnotesize\ttfamily FVoxel\+Material FWorld\+Generator\+Instance\+::\+Get\+Material\+Impl (\begin{DoxyParamCaption}\item[{v\+\_\+flt}]{X, }\item[{v\+\_\+flt}]{Y, }\item[{v\+\_\+flt}]{Z, }\item[{int32}]{LOD, }\item[{const FVoxel\+Item\+Stack \&}]{Items }\end{DoxyParamCaption}) const} + +\mbox{\Hypertarget{class_f_world_generator_instance_a41a32aee12e9228c3ec2bdfa4be7a92a}\label{class_f_world_generator_instance_a41a32aee12e9228c3ec2bdfa4be7a92a}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!GetUpVector@{GetUpVector}} +\index{GetUpVector@{GetUpVector}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\doxysubsubsection{\texorpdfstring{GetUpVector()}{GetUpVector()}} +{\footnotesize\ttfamily FVector FWorld\+Generator\+Instance\+::\+Get\+Up\+Vector (\begin{DoxyParamCaption}\item[{v\+\_\+flt}]{X, }\item[{v\+\_\+flt}]{Y, }\item[{v\+\_\+flt}]{Z }\end{DoxyParamCaption}) const\hspace{0.3cm}{\ttfamily [final]}, {\ttfamily [override]}, {\ttfamily [virtual]}} + +\mbox{\Hypertarget{class_f_world_generator_instance_a9a18ba21423f11dc01d3a2e54fbd214f}\label{class_f_world_generator_instance_a9a18ba21423f11dc01d3a2e54fbd214f}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!GetValueImpl@{GetValueImpl}} +\index{GetValueImpl@{GetValueImpl}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\doxysubsubsection{\texorpdfstring{GetValueImpl()}{GetValueImpl()}} +{\footnotesize\ttfamily v\+\_\+flt FWorld\+Generator\+Instance\+::\+Get\+Value\+Impl (\begin{DoxyParamCaption}\item[{v\+\_\+flt}]{X, }\item[{v\+\_\+flt}]{Y, }\item[{v\+\_\+flt}]{Z, }\item[{int32}]{LOD, }\item[{const FVoxel\+Item\+Stack \&}]{Items }\end{DoxyParamCaption}) const} + +\mbox{\Hypertarget{class_f_world_generator_instance_aa18eccf536acdf8d2115f32ac0dab1ef}\label{class_f_world_generator_instance_aa18eccf536acdf8d2115f32ac0dab1ef}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!GetValueRangeImpl@{GetValueRangeImpl}} +\index{GetValueRangeImpl@{GetValueRangeImpl}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\doxysubsubsection{\texorpdfstring{GetValueRangeImpl()}{GetValueRangeImpl()}} +{\footnotesize\ttfamily TVoxel\+Range$<$ v\+\_\+flt $>$ FWorld\+Generator\+Instance\+::\+Get\+Value\+Range\+Impl (\begin{DoxyParamCaption}\item[{const FVoxel\+Int\+Box \&}]{Bounds, }\item[{int32}]{LOD, }\item[{const FVoxel\+Item\+Stack \&}]{Items }\end{DoxyParamCaption}) const} + +\mbox{\Hypertarget{class_f_world_generator_instance_acb9442be2e711fb3ba4d146bf584bd5d}\label{class_f_world_generator_instance_acb9442be2e711fb3ba4d146bf584bd5d}} +\index{FWorldGeneratorInstance@{FWorldGeneratorInstance}!Init@{Init}} +\index{Init@{Init}!FWorldGeneratorInstance@{FWorldGeneratorInstance}} +\doxysubsubsection{\texorpdfstring{Init()}{Init()}} +{\footnotesize\ttfamily void FWorld\+Generator\+Instance\+::\+Init (\begin{DoxyParamCaption}\item[{const FVoxel\+Generator\+Init \&}]{Init\+Struct }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [override]}, {\ttfamily [virtual]}} + + + +The documentation for this class was generated from the following files\+:\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_world_generator_8h}{World\+Generator.\+h}}\item +\mbox{\hyperlink{_world_generator_8cpp}{World\+Generator.\+cpp}}\end{DoxyCompactItemize} diff --git a/Doxygen/latex/class_u_excavator_anim.eps b/Doxygen/latex/class_u_excavator_anim.eps new file mode 100644 index 0000000..38dab4b --- /dev/null +++ b/Doxygen/latex/class_u_excavator_anim.eps @@ -0,0 +1,197 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: ClassName +%%Creator: Doxygen +%%CreationDate: Time +%%For: +%Magnification: 1.00 +%%Orientation: Portrait +%%BoundingBox: 0 0 500 353.982300 +%%Pages: 0 +%%BeginSetup +%%EndSetup +%%EndComments + +% ----- variables ----- + +/boxwidth 0 def +/boxheight 40 def +/fontheight 24 def +/marginwidth 10 def +/distx 20 def +/disty 40 def +/boundaspect 1.412500 def % aspect ratio of the BoundingBox (width/height) +/boundx 500 def +/boundy boundx boundaspect div def +/xspacing 0 def +/yspacing 0 def +/rows 2 def +/cols 1 def +/scalefactor 0 def +/boxfont /Times-Roman findfont fontheight scalefont def + +% ----- procedures ----- + +/dotted { [1 4] 0 setdash } def +/dashed { [5] 0 setdash } def +/solid { [] 0 setdash } def + +/max % result = MAX(arg1,arg2) +{ + /a exch def + /b exch def + a b gt {a} {b} ifelse +} def + +/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2) +{ + 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max +} def + +/cw % boxwidth = MAX(boxwidth, stringwidth(arg1)) +{ + /str exch def + /boxwidth boxwidth str stringwidth pop max def +} def + +/box % draws a box with text 'arg1' at grid pos (arg2,arg3) +{ gsave + 2 setlinewidth + newpath + exch xspacing mul xoffset add + exch yspacing mul + moveto + boxwidth 0 rlineto + 0 boxheight rlineto + boxwidth neg 0 rlineto + 0 boxheight neg rlineto + closepath + dup stringwidth pop neg boxwidth add 2 div + boxheight fontheight 2 div sub 2 div + rmoveto show stroke + grestore +} def + +/mark +{ newpath + exch xspacing mul xoffset add boxwidth add + exch yspacing mul + moveto + 0 boxheight 4 div rlineto + boxheight neg 4 div boxheight neg 4 div rlineto + closepath + eofill + stroke +} def + +/arrow +{ newpath + moveto + 3 -8 rlineto + -6 0 rlineto + 3 8 rlineto + closepath + eofill + stroke +} def + +/out % draws an output connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight add + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/in % draws an input connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul disty 2 div sub + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/hedge +{ + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight 2 div sub + /y exch def + /x exch def + newpath + x y moveto + boxwidth 2 div distx add 0 rlineto + stroke + 1 eq + { newpath x boxwidth 2 div distx add add y moveto + -8 3 rlineto + 0 -6 rlineto + 8 3 rlineto + closepath + eofill + stroke + } if +} def + +/vedge +{ + /ye exch def + /ys exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add dup + ys yspacing mul boxheight 2 div sub + moveto + ye yspacing mul boxheight 2 div sub + lineto + stroke +} def + +/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3' +{ + /ys exch def + /xe exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add + ys yspacing mul disty 2 div sub + moveto + xspacing xe xs sub mul 0 + rlineto + stroke +} def + +% ----- main ------ + +boxfont setfont +1 boundaspect scale +(UExcavatorAnim) cw +(UAnimInstance) cw +/boxwidth boxwidth marginwidth 2 mul add def +/xspacing boxwidth distx add def +/yspacing boxheight disty add def +/scalefactor + boxwidth cols mul distx cols 1 sub mul add + boxheight rows mul disty rows 1 sub mul add boundaspect mul + max def +boundx scalefactor div boundy scalefactor div scale + +% ----- classes ----- + + (UExcavatorAnim) 0.000000 0.000000 box + (UAnimInstance) 0.000000 1.000000 box + +% ----- relations ----- + +solid +0 0.000000 0.000000 out +solid +1 0.000000 1.000000 in diff --git a/Doxygen/latex/class_u_excavator_anim.tex b/Doxygen/latex/class_u_excavator_anim.tex new file mode 100644 index 0000000..2d142a1 --- /dev/null +++ b/Doxygen/latex/class_u_excavator_anim.tex @@ -0,0 +1,147 @@ +\hypertarget{class_u_excavator_anim}{}\doxysection{UExcavator\+Anim Class Reference} +\label{class_u_excavator_anim}\index{UExcavatorAnim@{UExcavatorAnim}} + + +{\ttfamily \#include $<$Excavator\+Anim.\+h$>$} + +Inheritance diagram for UExcavator\+Anim\+:\begin{figure}[H] +\begin{center} +\leavevmode +\includegraphics[height=2.000000cm]{class_u_excavator_anim} +\end{center} +\end{figure} +\doxysubsection*{Public Member Functions} +\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{class_u_excavator_anim_a17a5fcd9bd609ed5ad1538906286184c}{UExcavator\+Anim}} (const FObject\+Initializer \&Objec\+Initializer) +\item +FRotator \mbox{\hyperlink{class_u_excavator_anim_adf9be335c9520d1b730d0c983ad42734}{Get\+Body\+Rotation}} () +\item +void \mbox{\hyperlink{class_u_excavator_anim_a215798edeb92f27e3e5ad9ad5c25626a}{Set\+Body\+Rotation}} (FRotator rotator) +\item +FRotator \mbox{\hyperlink{class_u_excavator_anim_a1cf8d06387c0c8bf61315209a2277b89}{Get\+Beam\+Top\+Rotation}} () +\item +void \mbox{\hyperlink{class_u_excavator_anim_a59c863bb954f22c019bc20b6d4b947df}{Set\+Beam\+Top\+Rotation}} (FRotator rotator) +\item +FRotator \mbox{\hyperlink{class_u_excavator_anim_affb325f375a952a31fd6c7edbda878b2}{Get\+Beam\+Bottom\+Rotation}} () +\item +void \mbox{\hyperlink{class_u_excavator_anim_a09b8e1814d38428a70423d3d81922448}{Set\+Beam\+Bottom\+Rotation}} (FRotator rotator) +\item +FRotator \mbox{\hyperlink{class_u_excavator_anim_ad206f925afc96462b27ebd5c33273da5}{Get\+Bucket\+Rotation}} () +\item +void \mbox{\hyperlink{class_u_excavator_anim_aba254277652747c4c1dabd60308b4c17}{Set\+Bucket\+Rotation}} (FRotator rotator) +\item +float \mbox{\hyperlink{class_u_excavator_anim_a7e4af257d3369d934d999c5889bfe60f}{Get\+Bucket\+Max\+Rotation}} () +\item +float \mbox{\hyperlink{class_u_excavator_anim_a6542745f31287654904430a08208f4dc}{Get\+Bucket\+Min\+Rotation}} () +\item +float \mbox{\hyperlink{class_u_excavator_anim_a56e5697f1cb92abaf41d0af039e641b1}{Get\+Top\+Max\+Rotation}} () +\item +float \mbox{\hyperlink{class_u_excavator_anim_ad7c23fdcc7208d1e925a0ea8ac09b793}{Get\+Top\+Min\+Rotation}} () +\item +float \mbox{\hyperlink{class_u_excavator_anim_a19af34e8fb205cccbe3796330e5fff79}{Get\+Bottom\+Max\+Rotation}} () +\item +float \mbox{\hyperlink{class_u_excavator_anim_a290c2158c3bd36b4cf9f6cf53395c44b}{Get\+Bottom\+Min\+Rotation}} () +\end{DoxyCompactItemize} + + +\doxysubsection{Constructor \& Destructor Documentation} +\mbox{\Hypertarget{class_u_excavator_anim_a17a5fcd9bd609ed5ad1538906286184c}\label{class_u_excavator_anim_a17a5fcd9bd609ed5ad1538906286184c}} +\index{UExcavatorAnim@{UExcavatorAnim}!UExcavatorAnim@{UExcavatorAnim}} +\index{UExcavatorAnim@{UExcavatorAnim}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{UExcavatorAnim()}{UExcavatorAnim()}} +{\footnotesize\ttfamily UExcavator\+Anim\+::\+UExcavator\+Anim (\begin{DoxyParamCaption}\item[{const FObject\+Initializer \&}]{Objec\+Initializer }\end{DoxyParamCaption})} + +Constructor. + +\doxysubsection{Member Function Documentation} +\mbox{\Hypertarget{class_u_excavator_anim_affb325f375a952a31fd6c7edbda878b2}\label{class_u_excavator_anim_affb325f375a952a31fd6c7edbda878b2}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBeamBottomRotation@{GetBeamBottomRotation}} +\index{GetBeamBottomRotation@{GetBeamBottomRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBeamBottomRotation()}{GetBeamBottomRotation()}} +{\footnotesize\ttfamily FRotator UExcavator\+Anim\+::\+Get\+Beam\+Bottom\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +Getter for Beam\+Bottom\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_a1cf8d06387c0c8bf61315209a2277b89}\label{class_u_excavator_anim_a1cf8d06387c0c8bf61315209a2277b89}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBeamTopRotation@{GetBeamTopRotation}} +\index{GetBeamTopRotation@{GetBeamTopRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBeamTopRotation()}{GetBeamTopRotation()}} +{\footnotesize\ttfamily FRotator UExcavator\+Anim\+::\+Get\+Beam\+Top\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +Getter for Beam\+Top\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_adf9be335c9520d1b730d0c983ad42734}\label{class_u_excavator_anim_adf9be335c9520d1b730d0c983ad42734}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBodyRotation@{GetBodyRotation}} +\index{GetBodyRotation@{GetBodyRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBodyRotation()}{GetBodyRotation()}} +{\footnotesize\ttfamily FRotator UExcavator\+Anim\+::\+Get\+Body\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +Getter for Body\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_a19af34e8fb205cccbe3796330e5fff79}\label{class_u_excavator_anim_a19af34e8fb205cccbe3796330e5fff79}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBottomMaxRotation@{GetBottomMaxRotation}} +\index{GetBottomMaxRotation@{GetBottomMaxRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBottomMaxRotation()}{GetBottomMaxRotation()}} +{\footnotesize\ttfamily float UExcavator\+Anim\+::\+Get\+Bottom\+Max\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [inline]}} + +Getter for Bottom\+Max\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_a290c2158c3bd36b4cf9f6cf53395c44b}\label{class_u_excavator_anim_a290c2158c3bd36b4cf9f6cf53395c44b}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBottomMinRotation@{GetBottomMinRotation}} +\index{GetBottomMinRotation@{GetBottomMinRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBottomMinRotation()}{GetBottomMinRotation()}} +{\footnotesize\ttfamily float UExcavator\+Anim\+::\+Get\+Bottom\+Min\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [inline]}} + +Getter for Bottom\+Min\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_a7e4af257d3369d934d999c5889bfe60f}\label{class_u_excavator_anim_a7e4af257d3369d934d999c5889bfe60f}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBucketMaxRotation@{GetBucketMaxRotation}} +\index{GetBucketMaxRotation@{GetBucketMaxRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBucketMaxRotation()}{GetBucketMaxRotation()}} +{\footnotesize\ttfamily float UExcavator\+Anim\+::\+Get\+Bucket\+Max\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [inline]}} + +Getter for Bucket\+Max\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_a6542745f31287654904430a08208f4dc}\label{class_u_excavator_anim_a6542745f31287654904430a08208f4dc}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBucketMinRotation@{GetBucketMinRotation}} +\index{GetBucketMinRotation@{GetBucketMinRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBucketMinRotation()}{GetBucketMinRotation()}} +{\footnotesize\ttfamily float UExcavator\+Anim\+::\+Get\+Bucket\+Min\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [inline]}} + +Getter for Bucket\+Min\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_ad206f925afc96462b27ebd5c33273da5}\label{class_u_excavator_anim_ad206f925afc96462b27ebd5c33273da5}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetBucketRotation@{GetBucketRotation}} +\index{GetBucketRotation@{GetBucketRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetBucketRotation()}{GetBucketRotation()}} +{\footnotesize\ttfamily FRotator UExcavator\+Anim\+::\+Get\+Bucket\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +Getter for Bucket\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_a56e5697f1cb92abaf41d0af039e641b1}\label{class_u_excavator_anim_a56e5697f1cb92abaf41d0af039e641b1}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetTopMaxRotation@{GetTopMaxRotation}} +\index{GetTopMaxRotation@{GetTopMaxRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetTopMaxRotation()}{GetTopMaxRotation()}} +{\footnotesize\ttfamily float UExcavator\+Anim\+::\+Get\+Top\+Max\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [inline]}} + +Getter for Top\+Max\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_ad7c23fdcc7208d1e925a0ea8ac09b793}\label{class_u_excavator_anim_ad7c23fdcc7208d1e925a0ea8ac09b793}} +\index{UExcavatorAnim@{UExcavatorAnim}!GetTopMinRotation@{GetTopMinRotation}} +\index{GetTopMinRotation@{GetTopMinRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{GetTopMinRotation()}{GetTopMinRotation()}} +{\footnotesize\ttfamily float UExcavator\+Anim\+::\+Get\+Top\+Min\+Rotation (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [inline]}} + +Getter for Top\+Min\+Rotation. \mbox{\Hypertarget{class_u_excavator_anim_a09b8e1814d38428a70423d3d81922448}\label{class_u_excavator_anim_a09b8e1814d38428a70423d3d81922448}} +\index{UExcavatorAnim@{UExcavatorAnim}!SetBeamBottomRotation@{SetBeamBottomRotation}} +\index{SetBeamBottomRotation@{SetBeamBottomRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{SetBeamBottomRotation()}{SetBeamBottomRotation()}} +{\footnotesize\ttfamily void UExcavator\+Anim\+::\+Set\+Beam\+Bottom\+Rotation (\begin{DoxyParamCaption}\item[{FRotator}]{rotator }\end{DoxyParamCaption})} + +Setter for Beam\+Bottom\+Rotation @\+Param rotator sets new rotation to Beam\+Bottom\+Rotator \mbox{\Hypertarget{class_u_excavator_anim_a59c863bb954f22c019bc20b6d4b947df}\label{class_u_excavator_anim_a59c863bb954f22c019bc20b6d4b947df}} +\index{UExcavatorAnim@{UExcavatorAnim}!SetBeamTopRotation@{SetBeamTopRotation}} +\index{SetBeamTopRotation@{SetBeamTopRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{SetBeamTopRotation()}{SetBeamTopRotation()}} +{\footnotesize\ttfamily void UExcavator\+Anim\+::\+Set\+Beam\+Top\+Rotation (\begin{DoxyParamCaption}\item[{FRotator}]{rotator }\end{DoxyParamCaption})} + +Setter for Beam\+Top\+Rotation @\+Param rotator sets new rotation to Beam\+Top\+Rotation \mbox{\Hypertarget{class_u_excavator_anim_a215798edeb92f27e3e5ad9ad5c25626a}\label{class_u_excavator_anim_a215798edeb92f27e3e5ad9ad5c25626a}} +\index{UExcavatorAnim@{UExcavatorAnim}!SetBodyRotation@{SetBodyRotation}} +\index{SetBodyRotation@{SetBodyRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{SetBodyRotation()}{SetBodyRotation()}} +{\footnotesize\ttfamily void UExcavator\+Anim\+::\+Set\+Body\+Rotation (\begin{DoxyParamCaption}\item[{FRotator}]{rotator }\end{DoxyParamCaption})} + +Setter for Body\+Rotation @\+Param rotator sets new rotation to Body\+Rotation \mbox{\Hypertarget{class_u_excavator_anim_aba254277652747c4c1dabd60308b4c17}\label{class_u_excavator_anim_aba254277652747c4c1dabd60308b4c17}} +\index{UExcavatorAnim@{UExcavatorAnim}!SetBucketRotation@{SetBucketRotation}} +\index{SetBucketRotation@{SetBucketRotation}!UExcavatorAnim@{UExcavatorAnim}} +\doxysubsubsection{\texorpdfstring{SetBucketRotation()}{SetBucketRotation()}} +{\footnotesize\ttfamily void UExcavator\+Anim\+::\+Set\+Bucket\+Rotation (\begin{DoxyParamCaption}\item[{FRotator}]{rotator }\end{DoxyParamCaption})} + +Setter for Bucket\+Rotation @\+Param rotator sets new rotation to Bucket\+Rotation + +The documentation for this class was generated from the following files\+:\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_excavator_anim_8h}{Excavator\+Anim.\+h}}\item +\mbox{\hyperlink{_excavator_anim_8cpp}{Excavator\+Anim.\+cpp}}\end{DoxyCompactItemize} diff --git a/Doxygen/latex/class_u_world_generator.eps b/Doxygen/latex/class_u_world_generator.eps new file mode 100644 index 0000000..82e916d --- /dev/null +++ b/Doxygen/latex/class_u_world_generator.eps @@ -0,0 +1,197 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: ClassName +%%Creator: Doxygen +%%CreationDate: Time +%%For: +%Magnification: 1.00 +%%Orientation: Portrait +%%BoundingBox: 0 0 500 338.983063 +%%Pages: 0 +%%BeginSetup +%%EndSetup +%%EndComments + +% ----- variables ----- + +/boxwidth 0 def +/boxheight 40 def +/fontheight 24 def +/marginwidth 10 def +/distx 20 def +/disty 40 def +/boundaspect 1.475000 def % aspect ratio of the BoundingBox (width/height) +/boundx 500 def +/boundy boundx boundaspect div def +/xspacing 0 def +/yspacing 0 def +/rows 2 def +/cols 1 def +/scalefactor 0 def +/boxfont /Times-Roman findfont fontheight scalefont def + +% ----- procedures ----- + +/dotted { [1 4] 0 setdash } def +/dashed { [5] 0 setdash } def +/solid { [] 0 setdash } def + +/max % result = MAX(arg1,arg2) +{ + /a exch def + /b exch def + a b gt {a} {b} ifelse +} def + +/xoffset % result = MAX(0,(scalefactor-(boxwidth*cols+distx*(cols-1)))/2) +{ + 0 scalefactor boxwidth cols mul distx cols 1 sub mul add sub 2 div max +} def + +/cw % boxwidth = MAX(boxwidth, stringwidth(arg1)) +{ + /str exch def + /boxwidth boxwidth str stringwidth pop max def +} def + +/box % draws a box with text 'arg1' at grid pos (arg2,arg3) +{ gsave + 2 setlinewidth + newpath + exch xspacing mul xoffset add + exch yspacing mul + moveto + boxwidth 0 rlineto + 0 boxheight rlineto + boxwidth neg 0 rlineto + 0 boxheight neg rlineto + closepath + dup stringwidth pop neg boxwidth add 2 div + boxheight fontheight 2 div sub 2 div + rmoveto show stroke + grestore +} def + +/mark +{ newpath + exch xspacing mul xoffset add boxwidth add + exch yspacing mul + moveto + 0 boxheight 4 div rlineto + boxheight neg 4 div boxheight neg 4 div rlineto + closepath + eofill + stroke +} def + +/arrow +{ newpath + moveto + 3 -8 rlineto + -6 0 rlineto + 3 8 rlineto + closepath + eofill + stroke +} def + +/out % draws an output connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight add + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/in % draws an input connector for the block at (arg1,arg2) +{ + newpath + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul disty 2 div sub + /y exch def + /x exch def + x y moveto + 0 disty 2 div rlineto + stroke + 1 eq { x y disty 2 div add arrow } if +} def + +/hedge +{ + exch xspacing mul xoffset add boxwidth 2 div add + exch yspacing mul boxheight 2 div sub + /y exch def + /x exch def + newpath + x y moveto + boxwidth 2 div distx add 0 rlineto + stroke + 1 eq + { newpath x boxwidth 2 div distx add add y moveto + -8 3 rlineto + 0 -6 rlineto + 8 3 rlineto + closepath + eofill + stroke + } if +} def + +/vedge +{ + /ye exch def + /ys exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add dup + ys yspacing mul boxheight 2 div sub + moveto + ye yspacing mul boxheight 2 div sub + lineto + stroke +} def + +/conn % connections the blocks from col 'arg1' to 'arg2' of row 'arg3' +{ + /ys exch def + /xe exch def + /xs exch def + newpath + xs xspacing mul xoffset add boxwidth 2 div add + ys yspacing mul disty 2 div sub + moveto + xspacing xe xs sub mul 0 + rlineto + stroke +} def + +% ----- main ------ + +boxfont setfont +1 boundaspect scale +(UWorldGenerator) cw +(UVoxelGenerator) cw +/boxwidth boxwidth marginwidth 2 mul add def +/xspacing boxwidth distx add def +/yspacing boxheight disty add def +/scalefactor + boxwidth cols mul distx cols 1 sub mul add + boxheight rows mul disty rows 1 sub mul add boundaspect mul + max def +boundx scalefactor div boundy scalefactor div scale + +% ----- classes ----- + + (UWorldGenerator) 0.000000 0.000000 box + (UVoxelGenerator) 0.000000 1.000000 box + +% ----- relations ----- + +solid +0 0.000000 0.000000 out +solid +1 0.000000 1.000000 in diff --git a/Doxygen/latex/class_u_world_generator.tex b/Doxygen/latex/class_u_world_generator.tex new file mode 100644 index 0000000..ef866fb --- /dev/null +++ b/Doxygen/latex/class_u_world_generator.tex @@ -0,0 +1,73 @@ +\hypertarget{class_u_world_generator}{}\doxysection{UWorld\+Generator Class Reference} +\label{class_u_world_generator}\index{UWorldGenerator@{UWorldGenerator}} + + +{\ttfamily \#include $<$World\+Generator.\+h$>$} + +Inheritance diagram for UWorld\+Generator\+:\begin{figure}[H] +\begin{center} +\leavevmode +\includegraphics[height=2.000000cm]{class_u_world_generator} +\end{center} +\end{figure} +\doxysubsection*{Public Member Functions} +\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{class_u_world_generator_a4ea59a679fc8eff20b3b143461020977}{UWorld\+Generator}} () +\item +\mbox{\hyperlink{class_u_world_generator_ad2e31d185549690f85ddec11f48a709e}{$\sim$\+UWorld\+Generator}} () +\item +virtual TVoxel\+Shared\+Ref$<$ FVoxel\+Generator\+Instance $>$ \mbox{\hyperlink{class_u_world_generator_a92908af10f0cdfee492c5e21ba1000d1}{Get\+Instance}} () override +\end{DoxyCompactItemize} +\doxysubsection*{Public Attributes} +\begin{DoxyCompactItemize} +\item +float \mbox{\hyperlink{class_u_world_generator_a97672fed88363bef5359e4a9e1bc22d2}{Noise\+Height}} = 10.\+0f +\item +int32 \mbox{\hyperlink{class_u_world_generator_ae3e0313a980ab00d26cb7691a9d178e7}{Seed}} = 1337 +\end{DoxyCompactItemize} + + +\doxysubsection{Constructor \& Destructor Documentation} +\mbox{\Hypertarget{class_u_world_generator_a4ea59a679fc8eff20b3b143461020977}\label{class_u_world_generator_a4ea59a679fc8eff20b3b143461020977}} +\index{UWorldGenerator@{UWorldGenerator}!UWorldGenerator@{UWorldGenerator}} +\index{UWorldGenerator@{UWorldGenerator}!UWorldGenerator@{UWorldGenerator}} +\doxysubsubsection{\texorpdfstring{UWorldGenerator()}{UWorldGenerator()}} +{\footnotesize\ttfamily UWorld\+Generator\+::\+UWorld\+Generator (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + +\mbox{\Hypertarget{class_u_world_generator_ad2e31d185549690f85ddec11f48a709e}\label{class_u_world_generator_ad2e31d185549690f85ddec11f48a709e}} +\index{UWorldGenerator@{UWorldGenerator}!````~UWorldGenerator@{$\sim$UWorldGenerator}} +\index{````~UWorldGenerator@{$\sim$UWorldGenerator}!UWorldGenerator@{UWorldGenerator}} +\doxysubsubsection{\texorpdfstring{$\sim$UWorldGenerator()}{~UWorldGenerator()}} +{\footnotesize\ttfamily UWorld\+Generator\+::$\sim$\+UWorld\+Generator (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})} + + + +\doxysubsection{Member Function Documentation} +\mbox{\Hypertarget{class_u_world_generator_a92908af10f0cdfee492c5e21ba1000d1}\label{class_u_world_generator_a92908af10f0cdfee492c5e21ba1000d1}} +\index{UWorldGenerator@{UWorldGenerator}!GetInstance@{GetInstance}} +\index{GetInstance@{GetInstance}!UWorldGenerator@{UWorldGenerator}} +\doxysubsubsection{\texorpdfstring{GetInstance()}{GetInstance()}} +{\footnotesize\ttfamily TVoxel\+Shared\+Ref$<$ FVoxel\+Generator\+Instance $>$ UWorld\+Generator\+::\+Get\+Instance (\begin{DoxyParamCaption}{ }\end{DoxyParamCaption})\hspace{0.3cm}{\ttfamily [override]}, {\ttfamily [virtual]}} + + + +\doxysubsection{Member Data Documentation} +\mbox{\Hypertarget{class_u_world_generator_a97672fed88363bef5359e4a9e1bc22d2}\label{class_u_world_generator_a97672fed88363bef5359e4a9e1bc22d2}} +\index{UWorldGenerator@{UWorldGenerator}!NoiseHeight@{NoiseHeight}} +\index{NoiseHeight@{NoiseHeight}!UWorldGenerator@{UWorldGenerator}} +\doxysubsubsection{\texorpdfstring{NoiseHeight}{NoiseHeight}} +{\footnotesize\ttfamily float UWorld\+Generator\+::\+Noise\+Height = 10.\+0f} + +\mbox{\Hypertarget{class_u_world_generator_ae3e0313a980ab00d26cb7691a9d178e7}\label{class_u_world_generator_ae3e0313a980ab00d26cb7691a9d178e7}} +\index{UWorldGenerator@{UWorldGenerator}!Seed@{Seed}} +\index{Seed@{Seed}!UWorldGenerator@{UWorldGenerator}} +\doxysubsubsection{\texorpdfstring{Seed}{Seed}} +{\footnotesize\ttfamily int32 UWorld\+Generator\+::\+Seed = 1337} + + + +The documentation for this class was generated from the following files\+:\begin{DoxyCompactItemize} +\item +\mbox{\hyperlink{_world_generator_8h}{World\+Generator.\+h}}\item +\mbox{\hyperlink{_world_generator_8cpp}{World\+Generator.\+cpp}}\end{DoxyCompactItemize} diff --git a/Doxygen/latex/doxygen.sty b/Doxygen/latex/doxygen.sty new file mode 100644 index 0000000..bde67ed --- /dev/null +++ b/Doxygen/latex/doxygen.sty @@ -0,0 +1,596 @@ +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{doxygen} + +% Packages used by this style file +\RequirePackage{alltt} +%%\RequirePackage{array} %% moved to refman.tex due to workaround for LaTex 2019 version and unmaintained tabu package +\RequirePackage{calc} +\RequirePackage{float} +%%\RequirePackage{ifthen} %% moved to refman.tex due to workaround for LaTex 2019 version and unmaintained tabu package +\RequirePackage{verbatim} +\RequirePackage[table]{xcolor} +\RequirePackage{longtable_doxygen} +\RequirePackage{tabu_doxygen} +\RequirePackage{fancyvrb} +\RequirePackage{tabularx} +\RequirePackage{multicol} +\RequirePackage{multirow} +\RequirePackage{hanging} +\RequirePackage{ifpdf} +\RequirePackage{adjustbox} +\RequirePackage{amssymb} +\RequirePackage{stackengine} +\RequirePackage{enumitem} +\RequirePackage{alphalph} +\RequirePackage[normalem]{ulem} % for strikeout, but don't modify emphasis +\RequirePackage{enumitem} + +%---------- Internal commands used in this style file ---------------- + +\newcommand{\ensurespace}[1]{% + \begingroup% + \setlength{\dimen@}{#1}% + \vskip\z@\@plus\dimen@% + \penalty -100\vskip\z@\@plus -\dimen@% + \vskip\dimen@% + \penalty 9999% + \vskip -\dimen@% + \vskip\z@skip% hide the previous |\vskip| from |\addvspace| + \endgroup% +} + +\newcommand{\DoxyHorRuler}[1]{% + \setlength{\parskip}{0ex plus 0ex minus 0ex}% + \ifthenelse{#1=0}% + {% + \hrule% + }% + {% + \hrulefilll% + }% +} +\newcommand{\DoxyLabelFont}{} +\newcommand{\entrylabel}[1]{% + {% + \parbox[b]{\labelwidth-4pt}{% + \makebox[0pt][l]{\DoxyLabelFont#1}% + \vspace{1.5\baselineskip}% + }% + }% +} + +\newenvironment{DoxyDesc}[1]{% + \ensurespace{4\baselineskip}% + \begin{list}{}{% + \settowidth{\labelwidth}{20pt}% + %\setlength{\parsep}{0pt}% + \setlength{\itemsep}{0pt}% + \setlength{\leftmargin}{\labelwidth+\labelsep}% + \renewcommand{\makelabel}{\entrylabel}% + }% + \item[#1]% +}{% + \end{list}% +} + +\newsavebox{\xrefbox} +\newlength{\xreflength} +\newcommand{\xreflabel}[1]{% + \sbox{\xrefbox}{#1}% + \setlength{\xreflength}{\wd\xrefbox}% + \ifthenelse{\xreflength>\labelwidth}{% + \begin{minipage}{\textwidth}% + \setlength{\parindent}{0pt}% + \hangindent=15pt\bfseries #1\vspace{1.2\itemsep}% + \end{minipage}% + }{% + \parbox[b]{\labelwidth}{\makebox[0pt][l]{\textbf{#1}}}% + }% +} + +%---------- Commands used by doxygen LaTeX output generator ---------- + +% Used by <pre> ... </pre> +\newenvironment{DoxyPre}{% + \small% + \begin{alltt}% +}{% + \end{alltt}% + \normalsize% +} +% Necessary for redefining not defined characters, i.e. "Replacement Character" in tex output. +\newlength{\CodeWidthChar} +\newlength{\CodeHeightChar} +\settowidth{\CodeWidthChar}{?} +\settoheight{\CodeHeightChar}{?} +% Necessary for hanging indent +\newlength{\DoxyCodeWidth} + +\newcommand\DoxyCodeLine[1]{\hangpara{\DoxyCodeWidth}{1}{#1}\par} + +\newcommand\NiceSpace{% + \discretionary{}{\kern\fontdimen2\font}{\kern\fontdimen2\font}% +} + +% Used by @code ... @endcode +\newenvironment{DoxyCode}[1]{% + \par% + \scriptsize% + \normalfont\ttfamily% + \rightskip0pt plus 1fil% + \settowidth{\DoxyCodeWidth}{000000}% + \settowidth{\CodeWidthChar}{?}% + \settoheight{\CodeHeightChar}{?}% + \setlength{\parskip}{0ex plus 0ex minus 0ex}% + \ifthenelse{\equal{#1}{0}} + { + {\lccode`~32 \lowercase{\global\let~}\NiceSpace}\obeyspaces% + } + { + {\lccode`~32 \lowercase{\global\let~}}\obeyspaces% + } + +}{% + \normalfont% + \normalsize% + \settowidth{\CodeWidthChar}{?}% + \settoheight{\CodeHeightChar}{?}% +} + +% Redefining not defined characters, i.e. "Replacement Character" in tex output. +\def\ucr{\adjustbox{width=\CodeWidthChar,height=\CodeHeightChar}{\stackinset{c}{}{c}{-.2pt}{% + \textcolor{white}{\sffamily\bfseries\small ?}}{% + \rotatebox{45}{$\blacksquare$}}}} + +% Used by @example, @include, @includelineno and @dontinclude +\newenvironment{DoxyCodeInclude}[1]{% + \DoxyCode{#1}% +}{% + \endDoxyCode% +} + +% Used by @verbatim ... @endverbatim +\newenvironment{DoxyVerb}{% + \par% + \footnotesize% + \verbatim% +}{% + \endverbatim% + \normalsize% +} + +% Used by @verbinclude +\newenvironment{DoxyVerbInclude}{% + \DoxyVerb% +}{% + \endDoxyVerb% +} + +% Used by numbered lists (using '-#' or <ol> ... </ol>) +\setlistdepth{12} +\newlist{DoxyEnumerate}{enumerate}{12} +\setlist[DoxyEnumerate,1]{label=\arabic*.} +\setlist[DoxyEnumerate,2]{label=(\enumalphalphcnt*)} +\setlist[DoxyEnumerate,3]{label=\roman*.} +\setlist[DoxyEnumerate,4]{label=\enumAlphAlphcnt*.} +\setlist[DoxyEnumerate,5]{label=\arabic*.} +\setlist[DoxyEnumerate,6]{label=(\enumalphalphcnt*)} +\setlist[DoxyEnumerate,7]{label=\roman*.} +\setlist[DoxyEnumerate,8]{label=\enumAlphAlphcnt*.} +\setlist[DoxyEnumerate,9]{label=\arabic*.} +\setlist[DoxyEnumerate,10]{label=(\enumalphalphcnt*)} +\setlist[DoxyEnumerate,11]{label=\roman*.} +\setlist[DoxyEnumerate,12]{label=\enumAlphAlphcnt*.} + +% Used by bullet lists (using '-', @li, @arg, or <ul> ... </ul>) +\setlistdepth{12} +\newlist{DoxyItemize}{itemize}{12} +\setlist[DoxyItemize]{label=\textperiodcentered} + +\setlist[DoxyItemize,1]{label=\textbullet} +\setlist[DoxyItemize,2]{label=\normalfont\bfseries \textendash} +\setlist[DoxyItemize,3]{label=\textasteriskcentered} +\setlist[DoxyItemize,4]{label=\textperiodcentered} + +% Used by description lists (using <dl> ... </dl>) +\newenvironment{DoxyDescription}{% + \description% +}{% + \enddescription% +} + +% Used by @image, @dotfile, @dot ... @enddot, and @msc ... @endmsc +% (only if caption is specified) +\newenvironment{DoxyImage}{% + \begin{figure}[H]% + \centering% +}{% + \end{figure}% +} + +% Used by @image, @dotfile, @dot ... @enddot, and @msc ... @endmsc +% (only if no caption is specified) +\newenvironment{DoxyImageNoCaption}{% + \begin{center}% +}{% + \end{center}% +} + +% Used by @image +% (only if inline is specified) +\newenvironment{DoxyInlineImage}{% +}{% +} + +% Used by @attention +\newenvironment{DoxyAttention}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @author and @authors +\newenvironment{DoxyAuthor}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @date +\newenvironment{DoxyDate}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @invariant +\newenvironment{DoxyInvariant}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @note +\newenvironment{DoxyNote}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @post +\newenvironment{DoxyPostcond}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @pre +\newenvironment{DoxyPrecond}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @copyright +\newenvironment{DoxyCopyright}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @remark +\newenvironment{DoxyRemark}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @return and @returns +\newenvironment{DoxyReturn}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @since +\newenvironment{DoxySince}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @see +\newenvironment{DoxySeeAlso}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @version +\newenvironment{DoxyVersion}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @warning +\newenvironment{DoxyWarning}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by @par and @paragraph +\newenvironment{DoxyParagraph}[1]{% + \begin{DoxyDesc}{#1}% +}{% + \end{DoxyDesc}% +} + +% Used by parameter lists +\newenvironment{DoxyParams}[2][]{% + \tabulinesep=1mm% + \par% + \ifthenelse{\equal{#1}{}}% + {\begin{longtabu*}spread 0pt [l]{|X[-1,l]|X[-1,l]|}}% name + description + {\ifthenelse{\equal{#1}{1}}% + {\begin{longtabu*}spread 0pt [l]{|X[-1,l]|X[-1,l]|X[-1,l]|}}% in/out + name + desc + {\begin{longtabu*}spread 0pt [l]{|X[-1,l]|X[-1,l]|X[-1,l]|X[-1,l]|}}% in/out + type + name + desc + } + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #2}\\[1ex]% + \hline% + \endfirsthead% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #2}\\[1ex]% + \hline% + \endhead% +}{% + \end{longtabu*}% + \vspace{6pt}% +} + +% Used for fields of simple structs +\newenvironment{DoxyFields}[1]{% + \tabulinesep=1mm% + \par% + \begin{longtabu*}spread 0pt [l]{|X[-1,r]|X[-1,l]|X[-1,l]|}% + \multicolumn{3}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endfirsthead% + \multicolumn{3}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endhead% +}{% + \end{longtabu*}% + \vspace{6pt}% +} + +% Used for fields simple class style enums +\newenvironment{DoxyEnumFields}[1]{% + \tabulinesep=1mm% + \par% + \begin{longtabu*}spread 0pt [l]{|X[-1,r]|X[-1,l]|}% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endfirsthead% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endhead% +}{% + \end{longtabu*}% + \vspace{6pt}% +} + +% Used for parameters within a detailed function description +\newenvironment{DoxyParamCaption}{% + \renewcommand{\item}[2][]{\\ \hspace*{2.0cm} ##1 {\em ##2}}% +}{% +} + +% Used by return value lists +\newenvironment{DoxyRetVals}[1]{% + \tabulinesep=1mm% + \par% + \begin{longtabu*}spread 0pt [l]{|X[-1,r]|X[-1,l]|}% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endfirsthead% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endhead% +}{% + \end{longtabu*}% + \vspace{6pt}% +} + +% Used by exception lists +\newenvironment{DoxyExceptions}[1]{% + \tabulinesep=1mm% + \par% + \begin{longtabu*}spread 0pt [l]{|X[-1,r]|X[-1,l]|}% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endfirsthead% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endhead% +}{% + \end{longtabu*}% + \vspace{6pt}% +} + +% Used by template parameter lists +\newenvironment{DoxyTemplParams}[1]{% + \tabulinesep=1mm% + \par% + \begin{longtabu*}spread 0pt [l]{|X[-1,r]|X[-1,l]|}% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endfirsthead% + \multicolumn{2}{l}{\hspace{-6pt}\bfseries\fontseries{bc}\selectfont\color{darkgray} #1}\\[1ex]% + \hline% + \endhead% +}{% + \end{longtabu*}% + \vspace{6pt}% +} + +% Used for member lists +\newenvironment{DoxyCompactItemize}{% + \begin{itemize}% + \setlength{\itemsep}{-3pt}% + \setlength{\parsep}{0pt}% + \setlength{\topsep}{0pt}% + \setlength{\partopsep}{0pt}% +}{% + \end{itemize}% +} + +% Used for member descriptions +\newenvironment{DoxyCompactList}{% + \begin{list}{}{% + \setlength{\leftmargin}{0.5cm}% + \setlength{\itemsep}{0pt}% + \setlength{\parsep}{0pt}% + \setlength{\topsep}{0pt}% + \renewcommand{\makelabel}{\hfill}% + }% +}{% + \end{list}% +} + +% Used for reference lists (@bug, @deprecated, @todo, etc.) +\newenvironment{DoxyRefList}{% + \begin{list}{}{% + \setlength{\labelwidth}{10pt}% + \setlength{\leftmargin}{\labelwidth}% + \addtolength{\leftmargin}{\labelsep}% + \renewcommand{\makelabel}{\xreflabel}% + }% +}{% + \end{list}% +} + +% Used by @bug, @deprecated, @todo, etc. +\newenvironment{DoxyRefDesc}[1]{% + \begin{list}{}{% + \renewcommand\makelabel[1]{\textbf{##1}}% + \settowidth\labelwidth{\makelabel{#1}}% + \setlength\leftmargin{\labelwidth+\labelsep}% + }% +}{% + \end{list}% +} + +% Used by parameter lists and simple sections +\newenvironment{Desc} +{\begin{list}{}{% + \settowidth{\labelwidth}{20pt}% + \setlength{\parsep}{0pt}% + \setlength{\itemsep}{0pt}% + \setlength{\leftmargin}{\labelwidth+\labelsep}% + \renewcommand{\makelabel}{\entrylabel}% + } +}{% + \end{list}% +} + +% Used by tables +\newcommand{\PBS}[1]{\let\temp=\\#1\let\\=\temp}% +\newenvironment{TabularC}[1]% +{\tabulinesep=1mm +\begin{longtabu*}spread 0pt [c]{*#1{|X[-1]}|}}% +{\end{longtabu*}\par}% + +\newenvironment{TabularNC}[1]% +{\begin{tabu}spread 0pt [l]{*#1{|X[-1]}|}}% +{\end{tabu}\par}% + +% Used for member group headers +\newenvironment{Indent}{% + \begin{list}{}{% + \setlength{\leftmargin}{0.5cm}% + }% + \item[]\ignorespaces% +}{% + \unskip% + \end{list}% +} + +% Used when hyperlinks are turned off +\newcommand{\doxyref}[3]{% + \textbf{#1} (\textnormal{#2}\,\pageref{#3})% +} + +% Used to link to a table when hyperlinks are turned on +\newcommand{\doxytablelink}[2]{% + \ref{#1}% +} + +% Used to link to a table when hyperlinks are turned off +\newcommand{\doxytableref}[3]{% + \ref{#3}% +} + +% Used by @addindex +\newcommand{\lcurly}{\{} +\newcommand{\rcurly}{\}} + +% Colors used for syntax highlighting +\definecolor{comment}{rgb}{0.5,0.0,0.0} +\definecolor{keyword}{rgb}{0.0,0.5,0.0} +\definecolor{keywordtype}{rgb}{0.38,0.25,0.125} +\definecolor{keywordflow}{rgb}{0.88,0.5,0.0} +\definecolor{preprocessor}{rgb}{0.5,0.38,0.125} +\definecolor{stringliteral}{rgb}{0.0,0.125,0.25} +\definecolor{charliteral}{rgb}{0.0,0.5,0.5} +\definecolor{vhdldigit}{rgb}{1.0,0.0,1.0} +\definecolor{vhdlkeyword}{rgb}{0.43,0.0,0.43} +\definecolor{vhdllogic}{rgb}{1.0,0.0,0.0} +\definecolor{vhdlchar}{rgb}{0.0,0.0,0.0} + +% Color used for table heading +\newcommand{\tableheadbgcolor}{lightgray}% + +% Version of hypertarget with correct landing location +\newcommand{\Hypertarget}[1]{\Hy@raisedlink{\hypertarget{#1}{}}} + +% possibility to have sections etc. be within the margins +% unfortunately had to copy part of book.cls and add \raggedright +\makeatletter +\newcommand\doxysection{\@startsection {section}{1}{\z@}% + {-3.5ex \@plus -1ex \@minus -.2ex}% + {2.3ex \@plus.2ex}% + {\raggedright\normalfont\Large\bfseries}} +\newcommand\doxysubsection{\@startsection{subsection}{2}{\z@}% + {-3.25ex\@plus -1ex \@minus -.2ex}% + {1.5ex \@plus .2ex}% + {\raggedright\normalfont\large\bfseries}} +\newcommand\doxysubsubsection{\@startsection{subsubsection}{3}{\z@}% + {-3.25ex\@plus -1ex \@minus -.2ex}% + {1.5ex \@plus .2ex}% + {\raggedright\normalfont\normalsize\bfseries}} +\newcommand\doxyparagraph{\@startsection{paragraph}{4}{\z@}% + {3.25ex \@plus1ex \@minus.2ex}% + {-1em}% + {\raggedright\normalfont\normalsize\bfseries}} +\newcommand\doxysubparagraph{\@startsection{subparagraph}{5}{\parindent}% + {3.25ex \@plus1ex \@minus .2ex}% + {-1em}% + {\raggedright\normalfont\normalsize\bfseries}} +\makeatother +% Define caption that is also suitable in a table +\makeatletter +\def\doxyfigcaption{% +\H@refstepcounter{figure}% +\@dblarg{\@caption{figure}}} +\makeatother + +% Define alpha enumarative names for counters > 26 +\makeatletter +\def\enumalphalphcnt#1{\expandafter\@enumalphalphcnt\csname c@#1\endcsname} +\def\@enumalphalphcnt#1{\alphalph{#1}} +\def\enumAlphAlphcnt#1{\expandafter\@enumAlphAlphcnt\csname c@#1\endcsname} +\def\@enumAlphAlphcnt#1{\AlphAlph{#1}} +\makeatother +\AddEnumerateCounter{\enumalphalphcnt}{\@enumalphalphcnt}{aa} +\AddEnumerateCounter{\enumAlphAlphcnt}{\@enumAlphAlphcnt}{AA} diff --git a/Doxygen/latex/files.tex b/Doxygen/latex/files.tex new file mode 100644 index 0000000..6e30b71 --- /dev/null +++ b/Doxygen/latex/files.tex @@ -0,0 +1,20 @@ +\doxysection{File List} +Here is a list of all files with brief descriptions\+:\begin{DoxyCompactList} +\item\contentsline{section}{\mbox{\hyperlink{_check_collision_component_8cpp}{Check\+Collision\+Component.\+cpp}} }{\pageref{_check_collision_component_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_check_collision_component_8h}{Check\+Collision\+Component.\+h}} }{\pageref{_check_collision_component_8h}}{} +\item\contentsline{section}{\mbox{\hyperlink{_custom_procedural_mesh_component_8cpp}{Custom\+Procedural\+Mesh\+Component.\+cpp}} }{\pageref{_custom_procedural_mesh_component_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_custom_procedural_mesh_component_8h}{Custom\+Procedural\+Mesh\+Component.\+h}} }{\pageref{_custom_procedural_mesh_component_8h}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_anim_8cpp}{Excavator\+Anim.\+cpp}} }{\pageref{_excavator_anim_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_anim_8h}{Excavator\+Anim.\+h}} }{\pageref{_excavator_anim_8h}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_character_8cpp}{Excavator\+Character.\+cpp}} }{\pageref{_excavator_character_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_character_8h}{Excavator\+Character.\+h}} }{\pageref{_excavator_character_8h}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_simulator_8_build_8cs}{Excavator\+Simulator.\+Build.\+cs}} }{\pageref{_excavator_simulator_8_build_8cs}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_simulator_8cpp}{Excavator\+Simulator.\+cpp}} }{\pageref{_excavator_simulator_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_simulator_8h}{Excavator\+Simulator.\+h}} }{\pageref{_excavator_simulator_8h}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_simulator_game_mode_base_8cpp}{Excavator\+Simulator\+Game\+Mode\+Base.\+cpp}} }{\pageref{_excavator_simulator_game_mode_base_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_excavator_simulator_game_mode_base_8h}{Excavator\+Simulator\+Game\+Mode\+Base.\+h}} }{\pageref{_excavator_simulator_game_mode_base_8h}}{} +\item\contentsline{section}{\mbox{\hyperlink{_ground_deformer_component_8cpp}{Ground\+Deformer\+Component.\+cpp}} }{\pageref{_ground_deformer_component_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_ground_deformer_component_8h}{Ground\+Deformer\+Component.\+h}} }{\pageref{_ground_deformer_component_8h}}{} +\item\contentsline{section}{\mbox{\hyperlink{_world_generator_8cpp}{World\+Generator.\+cpp}} }{\pageref{_world_generator_8cpp}}{} +\item\contentsline{section}{\mbox{\hyperlink{_world_generator_8h}{World\+Generator.\+h}} }{\pageref{_world_generator_8h}}{} +\end{DoxyCompactList} diff --git a/Doxygen/latex/hierarchy.tex b/Doxygen/latex/hierarchy.tex new file mode 100644 index 0000000..e338f42 --- /dev/null +++ b/Doxygen/latex/hierarchy.tex @@ -0,0 +1,24 @@ +\doxysection{Class Hierarchy} +This inheritance list is sorted roughly, but not completely, alphabetically\+:\begin{DoxyCompactList} +\item AActor\begin{DoxyCompactList} +\item \contentsline{section}{ACustom\+Procedural\+Mesh\+Component}{\pageref{class_a_custom_procedural_mesh_component}}{} +\end{DoxyCompactList} +\item ACharacter\begin{DoxyCompactList} +\item \contentsline{section}{AExcavator\+Character}{\pageref{class_a_excavator_character}}{} +\end{DoxyCompactList} +\item AGame\+Mode\+Base\begin{DoxyCompactList} +\item \contentsline{section}{AExcavator\+Simulator\+Game\+Mode\+Base}{\pageref{class_a_excavator_simulator_game_mode_base}}{} +\end{DoxyCompactList} +\item Module\+Rules\begin{DoxyCompactList} +\item \contentsline{section}{Excavator\+Simulator}{\pageref{class_excavator_simulator}}{} +\end{DoxyCompactList} +\item TVoxel\+Generator\+Instance\+Helper\begin{DoxyCompactList} +\item \contentsline{section}{FWorld\+Generator\+Instance}{\pageref{class_f_world_generator_instance}}{} +\end{DoxyCompactList} +\item UAnim\+Instance\begin{DoxyCompactList} +\item \contentsline{section}{UExcavator\+Anim}{\pageref{class_u_excavator_anim}}{} +\end{DoxyCompactList} +\item UVoxel\+Generator\begin{DoxyCompactList} +\item \contentsline{section}{UWorld\+Generator}{\pageref{class_u_world_generator}}{} +\end{DoxyCompactList} +\end{DoxyCompactList} diff --git a/Doxygen/latex/longtable_doxygen.sty b/Doxygen/latex/longtable_doxygen.sty new file mode 100644 index 0000000..a0eb314 --- /dev/null +++ b/Doxygen/latex/longtable_doxygen.sty @@ -0,0 +1,448 @@ +%% +%% This is file `longtable.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% longtable.dtx (with options: `package') +%% +%% This is a generated file. +%% +%% The source is maintained by the LaTeX Project team and bug +%% reports for it can be opened at http://latex-project.org/bugs.html +%% (but please observe conditions on bug reports sent to that address!) +%% +%% Copyright 1993-2016 +%% The LaTeX3 Project and any individual authors listed elsewhere +%% in this file. +%% +%% This file was generated from file(s) of the Standard LaTeX `Tools Bundle'. +%% -------------------------------------------------------------------------- +%% +%% It may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either version 1.3c +%% of this license or (at your option) any later version. +%% The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% and version 1.3c or later is part of all distributions of LaTeX +%% version 2005/12/01 or later. +%% +%% This file may only be distributed together with a copy of the LaTeX +%% `Tools Bundle'. You may however distribute the LaTeX `Tools Bundle' +%% without such generated files. +%% +%% The list of all files belonging to the LaTeX `Tools Bundle' is +%% given in the file `manifest.txt'. +%% +%% File: longtable.dtx Copyright (C) 1990-2001 David Carlisle +\NeedsTeXFormat{LaTeX2e}[1995/06/01] +\ProvidesPackage{longtable_doxygen} + [2014/10/28 v4.11 Multi-page Table package (DPC) - frozen version for doxygen] +\def\LT@err{\PackageError{longtable}} +\def\LT@warn{\PackageWarning{longtable}} +\def\LT@final@warn{% + \AtEndDocument{% + \LT@warn{Table \@width s have changed. Rerun LaTeX.\@gobbletwo}}% + \global\let\LT@final@warn\relax} +\DeclareOption{errorshow}{% + \def\LT@warn{\PackageInfo{longtable}}} +\DeclareOption{pausing}{% + \def\LT@warn#1{% + \LT@err{#1}{This is not really an error}}} +\DeclareOption{set}{} +\DeclareOption{final}{} +\ProcessOptions +\newskip\LTleft \LTleft=\fill +\newskip\LTright \LTright=\fill +\newskip\LTpre \LTpre=\bigskipamount +\newskip\LTpost \LTpost=\bigskipamount +\newcount\LTchunksize \LTchunksize=20 +\let\c@LTchunksize\LTchunksize +\newdimen\LTcapwidth \LTcapwidth=4in +\newbox\LT@head +\newbox\LT@firsthead +\newbox\LT@foot +\newbox\LT@lastfoot +\newcount\LT@cols +\newcount\LT@rows +\newcounter{LT@tables} +\newcounter{LT@chunks}[LT@tables] +\ifx\c@table\undefined + \newcounter{table} + \def\fnum@table{\tablename~\thetable} +\fi +\ifx\tablename\undefined + \def\tablename{Table} +\fi +\newtoks\LT@p@ftn +\mathchardef\LT@end@pen=30000 +\def\longtable{% + \par + \ifx\multicols\@undefined + \else + \ifnum\col@number>\@ne + \@twocolumntrue + \fi + \fi + \if@twocolumn + \LT@err{longtable not in 1-column mode}\@ehc + \fi + \begingroup + \@ifnextchar[\LT@array{\LT@array[x]}} +\def\LT@array[#1]#2{% + \refstepcounter{table}\stepcounter{LT@tables}% + \if l#1% + \LTleft\z@ \LTright\fill + \else\if r#1% + \LTleft\fill \LTright\z@ + \else\if c#1% + \LTleft\fill \LTright\fill + \fi\fi\fi + \let\LT@mcol\multicolumn + \let\LT@@tabarray\@tabarray + \let\LT@@hl\hline + \def\@tabarray{% + \let\hline\LT@@hl + \LT@@tabarray}% + \let\\\LT@tabularcr\let\tabularnewline\\% + \def\newpage{\noalign{\break}}% + \def\pagebreak{\noalign{\ifnum`}=0\fi\@testopt{\LT@no@pgbk-}4}% + \def\nopagebreak{\noalign{\ifnum`}=0\fi\@testopt\LT@no@pgbk4}% + \let\hline\LT@hline \let\kill\LT@kill\let\caption\LT@caption + \@tempdima\ht\strutbox + \let\@endpbox\LT@endpbox + \ifx\extrarowheight\@undefined + \let\@acol\@tabacol + \let\@classz\@tabclassz \let\@classiv\@tabclassiv + \def\@startpbox{\vtop\LT@startpbox}% + \let\@@startpbox\@startpbox + \let\@@endpbox\@endpbox + \let\LT@LL@FM@cr\@tabularcr + \else + \advance\@tempdima\extrarowheight + \col@sep\tabcolsep + \let\@startpbox\LT@startpbox\let\LT@LL@FM@cr\@arraycr + \fi + \setbox\@arstrutbox\hbox{\vrule + \@height \arraystretch \@tempdima + \@depth \arraystretch \dp \strutbox + \@width \z@}% + \let\@sharp##\let\protect\relax + \begingroup + \@mkpream{#2}% + \xdef\LT@bchunk{% + \global\advance\c@LT@chunks\@ne + \global\LT@rows\z@\setbox\z@\vbox\bgroup + \LT@setprevdepth + \tabskip\LTleft \noexpand\halign to\hsize\bgroup + \tabskip\z@ \@arstrut \@preamble \tabskip\LTright \cr}% + \endgroup + \expandafter\LT@nofcols\LT@bchunk&\LT@nofcols + \LT@make@row + \m@th\let\par\@empty + \everycr{}\lineskip\z@\baselineskip\z@ + \LT@bchunk} +\def\LT@no@pgbk#1[#2]{\penalty #1\@getpen{#2}\ifnum`{=0\fi}} +\def\LT@start{% + \let\LT@start\endgraf + \endgraf\penalty\z@\vskip\LTpre + \dimen@\pagetotal + \advance\dimen@ \ht\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi + \advance\dimen@ \dp\ifvoid\LT@firsthead\LT@head\else\LT@firsthead\fi + \advance\dimen@ \ht\LT@foot + \dimen@ii\vfuzz + \vfuzz\maxdimen + \setbox\tw@\copy\z@ + \setbox\tw@\vsplit\tw@ to \ht\@arstrutbox + \setbox\tw@\vbox{\unvbox\tw@}% + \vfuzz\dimen@ii + \advance\dimen@ \ht + \ifdim\ht\@arstrutbox>\ht\tw@\@arstrutbox\else\tw@\fi + \advance\dimen@\dp + \ifdim\dp\@arstrutbox>\dp\tw@\@arstrutbox\else\tw@\fi + \advance\dimen@ -\pagegoal + \ifdim \dimen@>\z@\vfil\break\fi + \global\@colroom\@colht + \ifvoid\LT@foot\else + \advance\vsize-\ht\LT@foot + \global\advance\@colroom-\ht\LT@foot + \dimen@\pagegoal\advance\dimen@-\ht\LT@foot\pagegoal\dimen@ + \maxdepth\z@ + \fi + \ifvoid\LT@firsthead\copy\LT@head\else\box\LT@firsthead\fi\nobreak + \output{\LT@output}} +\def\endlongtable{% + \crcr + \noalign{% + \let\LT@entry\LT@entry@chop + \xdef\LT@save@row{\LT@save@row}}% + \LT@echunk + \LT@start + \unvbox\z@ + \LT@get@widths + \if@filesw + {\let\LT@entry\LT@entry@write\immediate\write\@auxout{% + \gdef\expandafter\noexpand + \csname LT@\romannumeral\c@LT@tables\endcsname + {\LT@save@row}}}% + \fi + \ifx\LT@save@row\LT@@save@row + \else + \LT@warn{Column \@width s have changed\MessageBreak + in table \thetable}% + \LT@final@warn + \fi + \endgraf\penalty -\LT@end@pen + \endgroup + \global\@mparbottom\z@ + \pagegoal\vsize + \endgraf\penalty\z@\addvspace\LTpost + \ifvoid\footins\else\insert\footins{}\fi} +\def\LT@nofcols#1&{% + \futurelet\@let@token\LT@n@fcols} +\def\LT@n@fcols{% + \advance\LT@cols\@ne + \ifx\@let@token\LT@nofcols + \expandafter\@gobble + \else + \expandafter\LT@nofcols + \fi} +\def\LT@tabularcr{% + \relax\iffalse{\fi\ifnum0=`}\fi + \@ifstar + {\def\crcr{\LT@crcr\noalign{\nobreak}}\let\cr\crcr + \LT@t@bularcr}% + {\LT@t@bularcr}} +\let\LT@crcr\crcr +\let\LT@setprevdepth\relax +\def\LT@t@bularcr{% + \global\advance\LT@rows\@ne + \ifnum\LT@rows=\LTchunksize + \gdef\LT@setprevdepth{% + \prevdepth\z@\global + \global\let\LT@setprevdepth\relax}% + \expandafter\LT@xtabularcr + \else + \ifnum0=`{}\fi + \expandafter\LT@LL@FM@cr + \fi} +\def\LT@xtabularcr{% + \@ifnextchar[\LT@argtabularcr\LT@ntabularcr} +\def\LT@ntabularcr{% + \ifnum0=`{}\fi + \LT@echunk + \LT@start + \unvbox\z@ + \LT@get@widths + \LT@bchunk} +\def\LT@argtabularcr[#1]{% + \ifnum0=`{}\fi + \ifdim #1>\z@ + \unskip\@xargarraycr{#1}% + \else + \@yargarraycr{#1}% + \fi + \LT@echunk + \LT@start + \unvbox\z@ + \LT@get@widths + \LT@bchunk} +\def\LT@echunk{% + \crcr\LT@save@row\cr\egroup + \global\setbox\@ne\lastbox + \unskip + \egroup} +\def\LT@entry#1#2{% + \ifhmode\@firstofone{&}\fi\omit + \ifnum#1=\c@LT@chunks + \else + \kern#2\relax + \fi} +\def\LT@entry@chop#1#2{% + \noexpand\LT@entry + {\ifnum#1>\c@LT@chunks + 1}{0pt% + \else + #1}{#2% + \fi}} +\def\LT@entry@write{% + \noexpand\LT@entry^^J% + \@spaces} +\def\LT@kill{% + \LT@echunk + \LT@get@widths + \expandafter\LT@rebox\LT@bchunk} +\def\LT@rebox#1\bgroup{% + #1\bgroup + \unvbox\z@ + \unskip + \setbox\z@\lastbox} +\def\LT@blank@row{% + \xdef\LT@save@row{\expandafter\LT@build@blank + \romannumeral\number\LT@cols 001 }} +\def\LT@build@blank#1{% + \if#1m% + \noexpand\LT@entry{1}{0pt}% + \expandafter\LT@build@blank + \fi} +\def\LT@make@row{% + \global\expandafter\let\expandafter\LT@save@row + \csname LT@\romannumeral\c@LT@tables\endcsname + \ifx\LT@save@row\relax + \LT@blank@row + \else + {\let\LT@entry\or + \if!% + \ifcase\expandafter\expandafter\expandafter\LT@cols + \expandafter\@gobble\LT@save@row + \or + \else + \relax + \fi + !% + \else + \aftergroup\LT@blank@row + \fi}% + \fi} +\let\setlongtables\relax +\def\LT@get@widths{% + \setbox\tw@\hbox{% + \unhbox\@ne + \let\LT@old@row\LT@save@row + \global\let\LT@save@row\@empty + \count@\LT@cols + \loop + \unskip + \setbox\tw@\lastbox + \ifhbox\tw@ + \LT@def@row + \advance\count@\m@ne + \repeat}% + \ifx\LT@@save@row\@undefined + \let\LT@@save@row\LT@save@row + \fi} +\def\LT@def@row{% + \let\LT@entry\or + \edef\@tempa{% + \ifcase\expandafter\count@\LT@old@row + \else + {1}{0pt}% + \fi}% + \let\LT@entry\relax + \xdef\LT@save@row{% + \LT@entry + \expandafter\LT@max@sel\@tempa + \LT@save@row}} +\def\LT@max@sel#1#2{% + {\ifdim#2=\wd\tw@ + #1% + \else + \number\c@LT@chunks + \fi}% + {\the\wd\tw@}} +\def\LT@hline{% + \noalign{\ifnum0=`}\fi + \penalty\@M + \futurelet\@let@token\LT@@hline} +\def\LT@@hline{% + \ifx\@let@token\hline + \global\let\@gtempa\@gobble + \gdef\LT@sep{\penalty-\@medpenalty\vskip\doublerulesep}% + \else + \global\let\@gtempa\@empty + \gdef\LT@sep{\penalty-\@lowpenalty\vskip-\arrayrulewidth}% + \fi + \ifnum0=`{\fi}% + \multispan\LT@cols + \unskip\leaders\hrule\@height\arrayrulewidth\hfill\cr + \noalign{\LT@sep}% + \multispan\LT@cols + \unskip\leaders\hrule\@height\arrayrulewidth\hfill\cr + \noalign{\penalty\@M}% + \@gtempa} +\def\LT@caption{% + \noalign\bgroup + \@ifnextchar[{\egroup\LT@c@ption\@firstofone}\LT@capti@n} +\def\LT@c@ption#1[#2]#3{% + \LT@makecaption#1\fnum@table{#3}% + \def\@tempa{#2}% + \ifx\@tempa\@empty\else + {\let\\\space + \addcontentsline{lot}{table}{\protect\numberline{\thetable}{#2}}}% + \fi} +\def\LT@capti@n{% + \@ifstar + {\egroup\LT@c@ption\@gobble[]}% + {\egroup\@xdblarg{\LT@c@ption\@firstofone}}} +\def\LT@makecaption#1#2#3{% + \LT@mcol\LT@cols c{\hbox to\z@{\hss\parbox[t]\LTcapwidth{% + \sbox\@tempboxa{#1{#2: }#3}% + \ifdim\wd\@tempboxa>\hsize + #1{#2: }#3% + \else + \hbox to\hsize{\hfil\box\@tempboxa\hfil}% + \fi + \endgraf\vskip\baselineskip}% + \hss}}} +\def\LT@output{% + \ifnum\outputpenalty <-\@Mi + \ifnum\outputpenalty > -\LT@end@pen + \LT@err{floats and marginpars not allowed in a longtable}\@ehc + \else + \setbox\z@\vbox{\unvbox\@cclv}% + \ifdim \ht\LT@lastfoot>\ht\LT@foot + \dimen@\pagegoal + \advance\dimen@-\ht\LT@lastfoot + \ifdim\dimen@<\ht\z@ + \setbox\@cclv\vbox{\unvbox\z@\copy\LT@foot\vss}% + \@makecol + \@outputpage + \setbox\z@\vbox{\box\LT@head}% + \fi + \fi + \global\@colroom\@colht + \global\vsize\@colht + \vbox + {\unvbox\z@\box\ifvoid\LT@lastfoot\LT@foot\else\LT@lastfoot\fi}% + \fi + \else + \setbox\@cclv\vbox{\unvbox\@cclv\copy\LT@foot\vss}% + \@makecol + \@outputpage + \global\vsize\@colroom + \copy\LT@head\nobreak + \fi} +\def\LT@end@hd@ft#1{% + \LT@echunk + \ifx\LT@start\endgraf + \LT@err + {Longtable head or foot not at start of table}% + {Increase LTchunksize}% + \fi + \setbox#1\box\z@ + \LT@get@widths + \LT@bchunk} +\def\endfirsthead{\LT@end@hd@ft\LT@firsthead} +\def\endhead{\LT@end@hd@ft\LT@head} +\def\endfoot{\LT@end@hd@ft\LT@foot} +\def\endlastfoot{\LT@end@hd@ft\LT@lastfoot} +\def\LT@startpbox#1{% + \bgroup + \let\@footnotetext\LT@p@ftntext + \setlength\hsize{#1}% + \@arrayparboxrestore + \vrule \@height \ht\@arstrutbox \@width \z@} +\def\LT@endpbox{% + \@finalstrut\@arstrutbox + \egroup + \the\LT@p@ftn + \global\LT@p@ftn{}% + \hfil} +\def\LT@p@ftntext#1{% + \edef\@tempa{\the\LT@p@ftn\noexpand\footnotetext[\the\c@footnote]}% + \global\LT@p@ftn\expandafter{\@tempa{#1}}}% + +\@namedef{ver@longtable.sty}{2014/10/28 v4.11 Multi-page Table package (DPC) - frozen version for doxygen} +\endinput +%% +%% End of file `longtable.sty'. diff --git a/Doxygen/latex/make.bat b/Doxygen/latex/make.bat new file mode 100644 index 0000000..e4a3290 --- /dev/null +++ b/Doxygen/latex/make.bat @@ -0,0 +1,56 @@ +set Dir_Old=%cd% +cd /D %~dp0 + + +set ORG_LATEX_CMD=%LATEX_CMD% +set ORG_MKIDX_CMD=%MKIDX_CMD% +set ORG_BIBTEX_CMD=%BIBTEX_CMD% +set ORG_LATEX_COUNT=%LATEX_COUNT% +set ORG_MANUAL_FILE=%MANUAL_FILE% +if "X"%LATEX_CMD% == "X" set LATEX_CMD=pdflatex +if "X"%MKIDX_CMD% == "X" set MKIDX_CMD=makeindex +if "X"%BIBTEX_CMD% == "X" set BIBTEX_CMD=bibtex +if "X"%LATEX_COUNT% == "X" set LATEX_COUNT=8 +if "X"%MANUAL_FILE% == "X" set MANUAL_FILE=refman + +del /s /f *.ps *.dvi *.aux *.toc *.idx *.ind *.ilg *.log *.out *.brf *.blg *.bbl %MANUAL_FILE%.pdf + + +%LATEX_CMD% %MANUAL_FILE% +echo ---- +%MKIDX_CMD% %MANUAL_FILE%.idx +echo ---- +%LATEX_CMD% %MANUAL_FILE% + +setlocal enabledelayedexpansion +set count=%LATEX_COUNT% +:repeat +set content=X +for /F "tokens=*" %%T in ( 'findstr /C:"Rerun LaTeX" %MANUAL_FILE%.log' ) do set content="%%~T" +if !content! == X for /F "tokens=*" %%T in ( 'findstr /C:"Rerun to get cross-references right" %MANUAL_FILE%.log' ) do set content="%%~T" +if !content! == X for /F "tokens=*" %%T in ( 'findstr /C:"Rerun to get bibliographical references right" %MANUAL_FILE%.log' ) do set content="%%~T" +if !content! == X goto :skip +set /a count-=1 +if !count! EQU 0 goto :skip + +echo ---- +%LATEX_CMD% %MANUAL_FILE% +goto :repeat +:skip +endlocal +%MKIDX_CMD% %MANUAL_FILE%.idx +%LATEX_CMD% %MANUAL_FILE% + +@REM reset environment +cd /D %Dir_Old% +set Dir_Old= +set LATEX_CMD=%ORG_LATEX_CMD% +set ORG_LATEX_CMD= +set MKIDX_CMD=%ORG_MKIDX_CMD% +set ORG_MKIDX_CMD= +set BIBTEX_CMD=%ORG_BIBTEX_CMD% +set ORG_BIBTEX_CMD= +set MANUAL_FILE=%ORG_MANUAL_FILE% +set ORG_MANUAL_FILE= +set LATEX_COUNT=%ORG_LATEX_COUNT% +set ORG_LATEX_COUNT= diff --git a/Doxygen/latex/refman.tex b/Doxygen/latex/refman.tex new file mode 100644 index 0000000..6e050d2 --- /dev/null +++ b/Doxygen/latex/refman.tex @@ -0,0 +1,228 @@ + % Handle batch mode + % to overcome problems with too many open files + \let\mypdfximage\pdfximage\def\pdfximage{\immediate\mypdfximage} + % Set document class depending on configuration + \documentclass[twoside]{book} + %% moved from doxygen.sty due to workaround for LaTex 2019 version and unmaintained tabu package + \usepackage{ifthen} + \ifx\requestedLaTeXdate\undefined + \usepackage{array} + \else + \usepackage{array}[=2016-10-06] + \fi + %% + % Packages required by doxygen + \usepackage{fixltx2e} % for \textsubscript + \usepackage{doxygen} + \usepackage{graphicx} + \usepackage[utf8]{inputenc} + \usepackage{makeidx} + \PassOptionsToPackage{warn}{textcomp} + \usepackage{textcomp} + \usepackage[nointegrals]{wasysym} + \usepackage{ifxetex} + % NLS support packages + % Define default fonts + % Font selection + \usepackage[T1]{fontenc} + % set main and monospaced font + \usepackage[scaled=.90]{helvet} +\usepackage{courier} +\renewcommand{\familydefault}{\sfdefault} + \usepackage{sectsty} + \allsectionsfont{% + \fontseries{bc}\selectfont% + \color{darkgray}% + } + \renewcommand{\DoxyLabelFont}{% + \fontseries{bc}\selectfont% + \color{darkgray}% + } + \newcommand{\+}{\discretionary{\mbox{\scriptsize$\hookleftarrow$}}{}{}} + % Arguments of doxygenemoji: + % 1) ':<text>:' form of the emoji, already LaTeX-escaped + % 2) file with the name of the emoji without the .png extension + % in case image exist use this otherwise use the ':<text>:' form + \newcommand{\doxygenemoji}[2]{% + \IfFileExists{./#2.png}{\raisebox{-0.1em}{\includegraphics[height=0.9em]{./#2.png}}}{#1}% + } + % Page & text layout + \usepackage{geometry} + \geometry{% + a4paper,% + top=2.5cm,% + bottom=2.5cm,% + left=2.5cm,% + right=2.5cm% + } + % Allow a bit of overflow to go unnoticed by other means + \tolerance=750 + \hfuzz=15pt + \hbadness=750 + \setlength{\emergencystretch}{15pt} + \setlength{\parindent}{0cm} + \newcommand{\doxynormalparskip}{\setlength{\parskip}{3ex plus 2ex minus 2ex}} + \newcommand{\doxytocparskip}{\setlength{\parskip}{1ex plus 0ex minus 0ex}} + \doxynormalparskip + % Redefine paragraph/subparagraph environments, using sectsty fonts + \makeatletter + \renewcommand{\paragraph}{% + \@startsection{paragraph}{4}{0ex}{-1.0ex}{1.0ex}{% + \normalfont\normalsize\bfseries\SS@parafont% + }% + } + \renewcommand{\subparagraph}{% + \@startsection{subparagraph}{5}{0ex}{-1.0ex}{1.0ex}{% + \normalfont\normalsize\bfseries\SS@subparafont% + }% + } + \makeatother + \makeatletter + \newcommand\hrulefilll{\leavevmode\leaders\hrule\hskip 0pt plus 1filll\kern\z@} + \makeatother + % Headers & footers + \usepackage{fancyhdr} + \pagestyle{fancyplain} + \renewcommand{\footrulewidth}{0.4pt} + \fancypagestyle{fancyplain}{ + \fancyhf{} + \fancyhead[LE, RO]{\bfseries\thepage} + \fancyhead[LO]{\bfseries\rightmark} + \fancyhead[RE]{\bfseries\leftmark} + \fancyfoot[LO, RE]{\bfseries\scriptsize Generated by Doxygen } + } + \fancypagestyle{plain}{ + \fancyhf{} + \fancyfoot[LO, RE]{\bfseries\scriptsize Generated by Doxygen } + \renewcommand{\headrulewidth}{0pt} + } + \pagestyle{fancyplain} + \renewcommand{\chaptermark}[1]{% + \markboth{#1}{}% + } + \renewcommand{\sectionmark}[1]{% + \markright{\thesection\ #1}% + } + % ToC, LoF, LoT, bibliography, and index + % Indices & bibliography + \usepackage{natbib} + \usepackage[titles]{tocloft} + \setcounter{tocdepth}{3} + \setcounter{secnumdepth}{5} + % creating indexes + \makeindex + \usepackage{newunicodechar} + \newunicodechar{â»}{${}^{-}$}% Superscript minus + \newunicodechar{²}{${}^{2}$}% Superscript two + \newunicodechar{³}{${}^{3}$}% Superscript three + % Hyperlinks + % Hyperlinks (required, but should be loaded last) + \ifpdf + \usepackage[pdftex,pagebackref=true]{hyperref} + \else + \ifxetex + \usepackage[pagebackref=true]{hyperref} + \else + \usepackage[ps2pdf,pagebackref=true]{hyperref} + \fi + \fi + \hypersetup{% + colorlinks=true,% + linkcolor=blue,% + citecolor=blue,% + unicode,% + pdftitle={Excavator\+Simulator},% + pdfsubject={}% + } + % Custom commands used by the header + % Custom commands + \newcommand{\clearemptydoublepage}{% + \newpage{\pagestyle{empty}\cleardoublepage}% + } + % caption style definition + \usepackage{caption} + \captionsetup{labelsep=space,justification=centering,font={bf},singlelinecheck=off,skip=4pt,position=top} + % in page table of contents + \usepackage{etoc} + \etocsettocstyle{\doxytocparskip}{\doxynormalparskip} + % prevent numbers overlap the titles in toc + \renewcommand{\numberline}[1]{#1~} +% End of preamble, now comes the document contents +%===== C O N T E N T S ===== +\begin{document} + \raggedbottom + % Titlepage & ToC + % To avoid duplicate page anchors due to reuse of same numbers for + % the index (be it as roman numbers) + \hypersetup{pageanchor=false, + bookmarksnumbered=true, + pdfencoding=unicode + } + \pagenumbering{alph} + \begin{titlepage} + \vspace*{7cm} + \begin{center}% + {\Large Excavator\+Simulator}\\ + [1ex]\large 0.\+5.\+0 \\ + \vspace*{1cm} + {\large Generated by Doxygen 1.9.5}\\ + \end{center} + \end{titlepage} + \clearemptydoublepage + \pagenumbering{roman} + \tableofcontents + \clearemptydoublepage + \pagenumbering{arabic} + % re-enable anchors again + \hypersetup{pageanchor=true} +%--- Begin generated contents --- +\chapter{Hierarchical Index} +\input{hierarchy} +\chapter{Class Index} +\input{annotated} +\chapter{File Index} +\input{files} +\chapter{Class Documentation} +\input{class_a_custom_procedural_mesh_component} +\input{class_a_excavator_character} +\input{class_a_excavator_simulator_game_mode_base} +\input{class_excavator_simulator} +\input{class_f_world_generator_instance} +\input{class_u_excavator_anim} +\input{class_u_world_generator} +\chapter{File Documentation} +\input{_check_collision_component_8cpp} +\input{_check_collision_component_8h} +\input{_check_collision_component_8h_source} +\input{_custom_procedural_mesh_component_8cpp} +\input{_custom_procedural_mesh_component_8h} +\input{_custom_procedural_mesh_component_8h_source} +\input{_excavator_anim_8cpp} +\input{_excavator_anim_8h} +\input{_excavator_anim_8h_source} +\input{_excavator_character_8cpp} +\input{_excavator_character_8h} +\input{_excavator_character_8h_source} +\input{_excavator_simulator_8_build_8cs} +\input{_excavator_simulator_8cpp} +\input{_excavator_simulator_8h} +\input{_excavator_simulator_8h_source} +\input{_excavator_simulator_game_mode_base_8cpp} +\input{_excavator_simulator_game_mode_base_8h} +\input{_excavator_simulator_game_mode_base_8h_source} +\input{_ground_deformer_component_8cpp} +\input{_ground_deformer_component_8h} +\input{_ground_deformer_component_8h_source} +\input{_world_generator_8cpp} +\input{_world_generator_8h} +\input{_world_generator_8h_source} +%--- End generated contents --- +% Index + \backmatter + \newpage + \phantomsection + \clearemptydoublepage + \addcontentsline{toc}{chapter}{\indexname} + \printindex +% Required for some languages (in combination with latexdocumentpre from the header) +\end{document} diff --git a/Doxygen/latex/tabu_doxygen.sty b/Doxygen/latex/tabu_doxygen.sty new file mode 100644 index 0000000..3f17d1d --- /dev/null +++ b/Doxygen/latex/tabu_doxygen.sty @@ -0,0 +1,2557 @@ +%% +%% This is file `tabu.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% tabu.dtx (with options: `package') +%% +%% This is a generated file. +%% Copyright (FC) 2010-2011 - lppl +%% +%% tabu : 2011/02/26 v2.8 - tabu : Flexible LaTeX tabulars +%% +%% ********************************************************************************************** +%% \begin{tabu} { preamble } => default target: \linewidth or \linegoal +%% \begin{tabu} to <dimen>{ preamble } => target specified +%% \begin{tabu} spread <dimen>{ preamble } => target relative to the ``natural width'' +%% +%% tabu works in text and in math modes. +%% +%% X columns: automatic width adjustment + horizontal and vertical alignment +%% \begin{tabu} { X[4c] X[1c] X[-2ml] } +%% +%% Horizontal lines and / or leaders: +%% \hline\hline => double horizontal line +%% \firsthline\hline => for nested tabulars +%% \lasthline\hline => for nested tabulars +%% \tabucline[line spec]{column-column} => ``funny'' lines (dash/leader) +%% Automatic lines / leaders : +%% \everyrow{\hline\hline} +%% +%% Vertical lines and / or leaders: +%% \begin{tabu} { |[3pt red] X[4c] X[1c] X[-2ml] |[3pt blue] } +%% \begin{tabu} { |[3pt red] X[4c] X[1c] X[-2ml] |[3pt on 2pt off 4pt blue] } +%% +%% Fixed vertical spacing adjustment: +%% \extrarowheight=<dimen> \extrarowdepth=<dimen> +%% or: \extrarowsep=<dimen> => may be prefixed by \global +%% +%% Dynamic vertical spacing adjustment: +%% \abovetabulinesep=<dimen> \belowtabulinesep=<dimen> +%% or: \tabulinesep=<dimen> => may be prefixed by \global +%% +%% delarray.sty shortcuts: in math and text modes +%% \begin{tabu} .... \({ preamble }\) +%% +%% Algorithms reports: +%% \tracingtabu=1 \tracingtabu=2 +%% +%% ********************************************************************************************** +%% +%% This work may be distributed and/or modified under the +%% conditions of the LaTeX Project Public License, either +%% version 1.3 of this license or (at your option) any later +%% version. The latest version of this license is in +%% http://www.latex-project.org/lppl.txt +%% +%% This work consists of the main source file tabu.dtx +%% and the derived files +%% tabu.sty, tabu.pdf, tabu.ins +%% +%% tabu : Flexible LaTeX tabulars +%% lppl copyright 2010-2011 by FC <florent.chervet@free.fr> +%% + +\NeedsTeXFormat{LaTeX2e}[2005/12/01] +\ProvidesPackage{tabu_doxygen}[2011/02/26 v2.8 - flexible LaTeX tabulars (FC), frozen version for doxygen] +\RequirePackage{array}[2008/09/09] +\RequirePackage{varwidth}[2009/03/30] +\AtEndOfPackage{\tabu@AtEnd \let\tabu@AtEnd \@undefined} +\let\tabu@AtEnd\@empty +\def\TMP@EnsureCode#1={% + \edef\tabu@AtEnd{\tabu@AtEnd + \catcode#1 \the\catcode#1}% + \catcode#1=% +}% \TMP@EnsureCode +\TMP@EnsureCode 33 = 12 % ! +\TMP@EnsureCode 58 = 12 % : (for siunitx) +\TMP@EnsureCode124 = 12 % | +\TMP@EnsureCode 36 = 3 % $ = math shift +\TMP@EnsureCode 38 = 4 % & = tab alignment character +\TMP@EnsureCode 32 = 10 % space +\TMP@EnsureCode 94 = 7 % ^ +\TMP@EnsureCode 95 = 8 % _ +%% Constants -------------------------------------------------------- +\newcount \c@taburow \def\thetaburow {\number\c@taburow} +\newcount \tabu@nbcols +\newcount \tabu@cnt +\newcount \tabu@Xcol +\let\tabu@start \@tempcnta +\let\tabu@stop \@tempcntb +\newcount \tabu@alloc \tabu@alloc=\m@ne +\newcount \tabu@nested +\def\tabu@alloc@{\global\advance\tabu@alloc \@ne \tabu@nested\tabu@alloc} +\newdimen \tabu@target +\newdimen \tabu@spreadtarget +\newdimen \tabu@naturalX +\newdimen \tabucolX +\let\tabu@DELTA \@tempdimc +\let\tabu@thick \@tempdima +\let\tabu@on \@tempdimb +\let\tabu@off \@tempdimc +\newdimen \tabu@Xsum +\newdimen \extrarowdepth +\newdimen \abovetabulinesep +\newdimen \belowtabulinesep +\newdimen \tabustrutrule \tabustrutrule \z@ +\newtoks \tabu@thebody +\newtoks \tabu@footnotes +\newsavebox \tabu@box +\newsavebox \tabu@arstrutbox +\newsavebox \tabu@hleads +\newsavebox \tabu@vleads +\newif \iftabu@colortbl +\newif \iftabu@siunitx +\newif \iftabu@measuring +\newif \iftabu@spread +\newif \iftabu@negcoef +\newif \iftabu@everyrow +\def\tabu@everyrowtrue {\global\let\iftabu@everyrow \iftrue} +\def\tabu@everyrowfalse{\global\let\iftabu@everyrow \iffalse} +\newif \iftabu@long +\newif \iftabuscantokens +\def\tabu@rescan {\tabu@verbatim \scantokens } +%% Utilities (for internal usage) ----------------------------------- +\def\tabu@gobblespace #1 {#1} +\def\tabu@gobbletoken #1#2{#1} +\def\tabu@gobbleX{\futurelet\@let@token \tabu@gobblex} +\def\tabu@gobblex{\if ^^J\noexpand\@let@token \expandafter\@gobble + \else\ifx \@sptoken\@let@token + \expandafter\tabu@gobblespace\expandafter\tabu@gobbleX + \fi\fi +}% \tabu@gobblex +\def\tabu@X{^^J} +{\obeyspaces +\global\let\tabu@spxiii= % saves an active space (for \ifx) +\gdef\tabu@@spxiii{ }} +\def\tabu@ifenvir {% only for \multicolumn + \expandafter\tabu@if@nvir\csname\@currenvir\endcsname +}% \tabu@ifenvir +\def\tabu@if@nvir #1{\csname @\ifx\tabu#1first\else + \ifx\longtabu#1first\else + second\fi\fi oftwo\endcsname +}% \tabu@ifenvir +\def\tabu@modulo #1#2{\numexpr\ifnum\numexpr#1=\z@ 0\else #1-(#1-(#2-1)/2)/(#2)*(#2)\fi} +{\catcode`\&=3 +\gdef\tabu@strtrim #1{% #1 = control sequence to trim + \ifodd 1\ifx #1\@empty \else \ifx #1\space \else 0\fi \fi + \let\tabu@c@l@r \@empty \let#1\@empty + \else \expandafter \tabu@trimspaces #1\@nnil + \fi +}% \tabu@strtrim +\gdef\tabu@trimspaces #1\@nnil{\let\tabu@c@l@r=#2\tabu@firstspace .#1& }% +\gdef\tabu@firstspace #1#2#3 &{\tabu@lastspace #2#3&} +\gdef\tabu@lastspace #1{\def #3{#1}% + \ifx #3\tabu@c@l@r \def\tabu@c@l@r{\protect\color{#1}}\expandafter\remove@to@nnil \fi + \tabu@trimspaces #1\@nnil} +}% \catcode +\def\tabu@sanitizearg #1#2{{% + \csname \ifcsname if@safe@actives\endcsname % <babel> + @safe@activestrue\else + relax\fi \endcsname + \edef#2{#1}\tabu@strtrim#2\@onelevel@sanitize#2% + \expandafter}\expandafter\def\expandafter#2\expandafter{#2}% +}% \tabu@sanitizearg +\def\tabu@textbar #1{\begingroup \endlinechar\m@ne \scantokens{\def\:{|}}% + \expandafter\endgroup \expandafter#1\:% !!! semi simple group !!! +}% \tabu@textbar +\def\tabu@everyrow@bgroup{\iftabu@everyrow \begingroup \else \noalign{\ifnum0=`}\fi \fi} +\def\tabu@everyrow@egroup{% + \iftabu@everyrow \expandafter \endgroup \the\toks@ + \else \ifnum0=`{\fi}% + \fi +}% \tabu@everyrow@egroup +\def\tabu@arstrut {\global\setbox\@arstrutbox \hbox{\vrule + height \arraystretch \dimexpr\ht\strutbox+\extrarowheight + depth \arraystretch \dimexpr\dp\strutbox+\extrarowdepth + width \z@}% +}% \tabu@arstrut +\def\tabu@rearstrut {% + \@tempdima \arraystretch\dimexpr\ht\strutbox+\extrarowheight \relax + \@tempdimb \arraystretch\dimexpr\dp\strutbox+\extrarowdepth \relax + \ifodd 1\ifdim \ht\@arstrutbox=\@tempdima + \ifdim \dp\@arstrutbox=\@tempdimb 0 \fi\fi + \tabu@mkarstrut + \fi +}% \tabu@rearstrut +\def\tabu@@DBG #1{\ifdim\tabustrutrule>\z@ \color{#1}\fi} +\def\tabu@DBG@arstrut {\global\setbox\@arstrutbox + \hbox to\z@{\hbox to\z@{\hss + {\tabu@DBG{cyan}\vrule + height \arraystretch \dimexpr\ht\strutbox+\extrarowheight + depth \z@ + width \tabustrutrule}\kern-\tabustrutrule + {\tabu@DBG{pink}\vrule + height \z@ + depth \arraystretch \dimexpr\dp\strutbox+\extrarowdepth + width \tabustrutrule}}}% +}% \tabu@DBG@arstrut +\def\tabu@save@decl{\toks\count@ \expandafter{\the\toks\expandafter\count@ + \@nextchar}}% +\def\tabu@savedecl{\ifcat$\d@llarend\else + \let\save@decl \tabu@save@decl \fi % no inversion of tokens in text mode +}% \tabu@savedecl +\def\tabu@finalstrut #1{\unskip\ifhmode\nobreak\fi\vrule height\z@ depth\z@ width\z@} +\newcommand*\tabuDisableCommands {\g@addto@macro\tabu@trialh@@k } +\let\tabu@trialh@@k \@empty +\def\tabu@nowrite #1#{{\afterassignment}\toks@} +\let\tabu@write\write +\let\tabu@immediate\immediate +\def\tabu@WRITE{\begingroup + \def\immediate\write{\aftergroup\endgroup + \tabu@immediate\tabu@write}% +}% \tabu@WRITE +\expandafter\def\expandafter\tabu@GenericError\expandafter{% + \expandafter\tabu@WRITE\GenericError} +\def\tabu@warn{\tabu@WRITE\PackageWarning{tabu}} +\def\tabu@noxfootnote [#1]{\@gobble} +\def\tabu@nocolor #1#{\@gobble} +\newcommand*\tabu@norowcolor[2][]{} +\def\tabu@maybesiunitx #1{\def\tabu@temp{#1}% + \futurelet\@let@token \tabu@m@ybesiunitx} +\def\tabu@m@ybesiunitx #1{\def\tabu@m@ybesiunitx {% + \ifx #1\@let@token \let\tabu@cellleft \@empty \let\tabu@cellright \@empty \fi + \tabu@temp}% \tabu@m@ybesiunitx +}\expandafter\tabu@m@ybesiunitx \csname siunitx_table_collect_begin:Nn\endcsname +\def\tabu@celllalign@def #1{\def\tabu@celllalign{\tabu@maybesiunitx{#1}}}% +%% Fixed vertical spacing adjustment: \extrarowsep ------------------ +\newcommand*\extrarowsep{\edef\tabu@C@extra{\the\numexpr\tabu@C@extra+1}% + \iftabu@everyrow \aftergroup\tabu@Gextra + \else \aftergroup\tabu@n@Gextra + \fi + \@ifnextchar={\tabu@gobbletoken\tabu@extra} \tabu@extra +}% \extrarowsep +\def\tabu@extra {\@ifnextchar_% + {\tabu@gobbletoken{\tabu@setextra\extrarowheight \extrarowdepth}} + {\ifx ^\@let@token \def\tabu@temp{% + \tabu@gobbletoken{\tabu@setextra\extrarowdepth \extrarowheight}}% + \else \let\tabu@temp \@empty + \afterassignment \tabu@setextrasep \extrarowdepth + \fi \tabu@temp}% +}% \tabu@extra +\def\tabu@setextra #1#2{\def\tabu@temp{\tabu@extr@#1#2}\afterassignment\tabu@temp#2} +\def\tabu@extr@ #1#2{\@ifnextchar^% + {\tabu@gobbletoken{\tabu@setextra\extrarowdepth \extrarowheight}} + {\ifx _\@let@token \def\tabu@temp{% + \tabu@gobbletoken{\tabu@setextra\extrarowheight \extrarowdepth}}% + \else \let\tabu@temp \@empty + \tabu@Gsave \tabu@G@extra \tabu@C@extra \extrarowheight \extrarowdepth + \fi \tabu@temp}% +}% \tabu@extr@ +\def\tabu@setextrasep {\extrarowheight=\extrarowdepth + \tabu@Gsave \tabu@G@extra \tabu@C@extra \extrarowheight \extrarowdepth +}% \tabu@setextrasep +\def\tabu@Gextra{\ifx \tabu@G@extra\@empty \else {\tabu@Rextra}\fi} +\def\tabu@n@Gextra{\ifx \tabu@G@extra\@empty \else \noalign{\tabu@Rextra}\fi} +\def\tabu@Rextra{\tabu@Grestore \tabu@G@extra \tabu@C@extra} +\let\tabu@C@extra \z@ +\let\tabu@G@extra \@empty +%% Dynamic vertical spacing adjustment: \tabulinesep ---------------- +\newcommand*\tabulinesep{\edef\tabu@C@linesep{\the\numexpr\tabu@C@linesep+1}% + \iftabu@everyrow \aftergroup\tabu@Glinesep + \else \aftergroup\tabu@n@Glinesep + \fi + \@ifnextchar={\tabu@gobbletoken\tabu@linesep} \tabu@linesep +}% \tabulinesep +\def\tabu@linesep {\@ifnextchar_% + {\tabu@gobbletoken{\tabu@setsep\abovetabulinesep \belowtabulinesep}} + {\ifx ^\@let@token \def\tabu@temp{% + \tabu@gobbletoken{\tabu@setsep\belowtabulinesep \abovetabulinesep}}% + \else \let\tabu@temp \@empty + \afterassignment \tabu@setlinesep \abovetabulinesep + \fi \tabu@temp}% +}% \tabu@linesep +\def\tabu@setsep #1#2{\def\tabu@temp{\tabu@sets@p#1#2}\afterassignment\tabu@temp#2} +\def\tabu@sets@p #1#2{\@ifnextchar^% + {\tabu@gobbletoken{\tabu@setsep\belowtabulinesep \abovetabulinesep}} + {\ifx _\@let@token \def\tabu@temp{% + \tabu@gobbletoken{\tabu@setsep\abovetabulinesep \belowtabulinesep}}% + \else \let\tabu@temp \@empty + \tabu@Gsave \tabu@G@linesep \tabu@C@linesep \abovetabulinesep \belowtabulinesep + \fi \tabu@temp}% +}% \tabu@sets@p +\def\tabu@setlinesep {\belowtabulinesep=\abovetabulinesep + \tabu@Gsave \tabu@G@linesep \tabu@C@linesep \abovetabulinesep \belowtabulinesep +}% \tabu@setlinesep +\def\tabu@Glinesep{\ifx \tabu@G@linesep\@empty \else {\tabu@Rlinesep}\fi} +\def\tabu@n@Glinesep{\ifx \tabu@G@linesep\@empty \else \noalign{\tabu@Rlinesep}\fi} +\def\tabu@Rlinesep{\tabu@Grestore \tabu@G@linesep \tabu@C@linesep} +\let\tabu@C@linesep \z@ +\let\tabu@G@linesep \@empty +%% \global\extrarowsep and \global\tabulinesep ------------------- +\def\tabu@Gsave #1#2#3#4{\xdef#1{#1% + \toks#2{\toks\the\currentgrouplevel{\global#3\the#3\global#4\the#4}}}% +}% \tabu@Gsave +\def\tabu@Grestore#1#2{% + \toks#2{}#1\toks\currentgrouplevel\expandafter{\expandafter}\the\toks#2\relax + \ifcat$\the\toks\currentgrouplevel$\else + \global\let#1\@empty \global\let#2\z@ + \the\toks\currentgrouplevel + \fi +}% \tabu@Grestore +%% Setting code for every row --------------------------------------- +\newcommand*\everyrow{\tabu@everyrow@bgroup + \tabu@start \z@ \tabu@stop \z@ \tabu@evrstartstop +}% \everyrow +\def\tabu@evrstartstop {\@ifnextchar^% + {\afterassignment \tabu@evrstartstop \tabu@stop=}% + {\ifx ^\@let@token + \afterassignment\tabu@evrstartstop \tabu@start=% + \else \afterassignment\tabu@everyr@w \toks@ + \fi}% +}% \tabu@evrstartstop +\def\tabu@everyr@w {% + \xdef\tabu@everyrow{% + \noexpand\tabu@everyrowfalse + \let\noalign \relax + \noexpand\tabu@rowfontreset + \iftabu@colortbl \noexpand\tabu@rc@ \fi % \taburowcolors + \let\noexpand\tabu@docline \noexpand\tabu@docline@evr + \the\toks@ + \noexpand\tabu@evrh@@k + \noexpand\tabu@rearstrut + \global\advance\c@taburow \@ne}% + \iftabu@everyrow \toks@\expandafter + {\expandafter\def\expandafter\tabu@evr@L\expandafter{\the\toks@}\ignorespaces}% + \else \xdef\tabu@evr@G{\the\toks@}% + \fi + \tabu@everyrow@egroup +}% \tabu@everyr@w +\def\tabu@evr {\def\tabu@evrh@@k} % for internal use only +\tabu@evr{} +%% line style and leaders ------------------------------------------- +\newcommand*\newtabulinestyle [1]{% + {\@for \@tempa :=#1\do{\expandafter\tabu@newlinestyle \@tempa==\@nil}}% +}% \newtabulinestyle +\def\tabu@newlinestyle #1=#2=#3\@nil{\tabu@getline {#2}% + \tabu@sanitizearg {#1}\@tempa + \ifodd 1\ifx \@tempa\@empty \ifdefined\tabu@linestyle@ 0 \fi\fi + \global\expandafter\let + \csname tabu@linestyle@\@tempa \endcsname =\tabu@thestyle \fi +}% \tabu@newlinestyle +\newcommand*\tabulinestyle [1]{\tabu@everyrow@bgroup \tabu@getline{#1}% + \iftabu@everyrow + \toks@\expandafter{\expandafter \def \expandafter + \tabu@ls@L\expandafter{\tabu@thestyle}\ignorespaces}% + \gdef\tabu@ls@{\tabu@ls@L}% + \else + \global\let\tabu@ls@G \tabu@thestyle + \gdef\tabu@ls@{\tabu@ls@G}% + \fi + \tabu@everyrow@egroup +}% \tabulinestyle +\newcommand*\taburulecolor{\tabu@everyrow@bgroup \tabu@textbar \tabu@rulecolor} +\def\tabu@rulecolor #1{\toks@{}% + \def\tabu@temp #1##1#1{\tabu@ruledrsc{##1}}\@ifnextchar #1% + \tabu@temp + \tabu@rulearc +}% \tabu@rulecolor +\def\tabu@ruledrsc #1{\edef\tabu@temp{#1}\tabu@strtrim\tabu@temp + \ifx \tabu@temp\@empty \def\tabu@temp{\tabu@rule@drsc@ {}{}}% + \else \edef\tabu@temp{\noexpand\tabu@rule@drsc@ {}{\tabu@temp}}% + \fi + \tabu@temp +}% \tabu@ruledrsc@ +\def\tabu@ruledrsc@ #1#{\tabu@rule@drsc@ {#1}} +\def\tabu@rule@drsc@ #1#2{% + \iftabu@everyrow + \ifx \\#1#2\\\toks@{\let\CT@drsc@ \relax}% + \else \toks@{\def\CT@drsc@{\color #1{#2}}}% + \fi + \else + \ifx \\#1#2\\\global\let\CT@drsc@ \relax + \else \gdef\CT@drsc@{\color #1{#2}}% + \fi + \fi + \tabu@rulearc +}% \tabu@rule@drsc@ +\def\tabu@rulearc #1#{\tabu@rule@arc@ {#1}} +\def\tabu@rule@arc@ #1#2{% + \iftabu@everyrow + \ifx \\#1#2\\\toks@\expandafter{\the\toks@ \def\CT@arc@{}}% + \else \toks@\expandafter{\the\toks@ \def\CT@arc@{\color #1{#2}}}% + \fi + \toks@\expandafter{\the\toks@ + \let\tabu@arc@L \CT@arc@ + \let\tabu@drsc@L \CT@drsc@ + \ignorespaces}% + \else + \ifx \\#1#2\\\gdef\CT@arc@{}% + \else \gdef\CT@arc@{\color #1{#2}}% + \fi + \global\let\tabu@arc@G \CT@arc@ + \global\let\tabu@drsc@G \CT@drsc@ + \fi + \tabu@everyrow@egroup +}% \tabu@rule@arc@ +\def\taburowcolors {\tabu@everyrow@bgroup \@testopt \tabu@rowcolors 1} +\def\tabu@rowcolors [#1]#2#{\tabu@rowc@lors{#1}{#2}} +\def\tabu@rowc@lors #1#2#3{% + \toks@{}\@defaultunits \count@ =\number0#2\relax \@nnil + \@defaultunits \tabu@start =\number0#1\relax \@nnil + \ifnum \count@<\tw@ \count@=\tw@ \fi + \advance\tabu@start \m@ne + \ifnum \tabu@start<\z@ \tabu@start \z@ \fi + \tabu@rowcolorseries #3\in@..\in@ \@nnil +}% \tabu@rowcolors +\def\tabu@rowcolorseries #1..#2\in@ #3\@nnil {% + \ifx \in@#1\relax + \iftabu@everyrow \toks@{\def\tabu@rc@{}\let\tabu@rc@L \tabu@rc@}% + \else \gdef\tabu@rc@{}\global\let\tabu@rc@G \tabu@rc@ + \fi + \else + \ifx \\#2\\\tabu@rowcolorserieserror \fi + \tabu@sanitizearg{#1}\tabu@temp + \tabu@sanitizearg{#2}\@tempa + \advance\count@ \m@ne + \iftabu@everyrow + \def\tabu@rc@ ##1##2##3##4{\def\tabu@rc@{% + \ifnum ##2=\c@taburow + \definecolorseries{tabu@rcseries@\the\tabu@nested}{rgb}{last}{##3}{##4}\fi + \ifnum \c@taburow<##2 \else + \ifnum \tabu@modulo {\c@taburow-##2}{##1+1}=\z@ + \resetcolorseries[{##1}]{tabu@rcseries@\the\tabu@nested}\fi + \xglobal\colorlet{tabu@rc@\the\tabu@nested}{tabu@rcseries@\the\tabu@nested!!+}% + \rowcolor{tabu@rc@\the\tabu@nested}\fi}% + }\edef\x{\noexpand\tabu@rc@ {\the\count@} + {\the\tabu@start} + {\tabu@temp} + {\@tempa}% + }\x + \toks@\expandafter{\expandafter\def\expandafter\tabu@rc@\expandafter{\tabu@rc@}}% + \toks@\expandafter{\the\toks@ \let\tabu@rc@L \tabu@rc@ \ignorespaces}% + \else % inside \noalign + \definecolorseries{tabu@rcseries@\the\tabu@nested}{rgb}{last}{\tabu@temp}{\@tempa}% + \expandafter\resetcolorseries\expandafter[\the\count@]{tabu@rcseries@\the\tabu@nested}% + \xglobal\colorlet{tabu@rc@\the\tabu@nested}{tabu@rcseries@\the\tabu@nested!!+}% + \let\noalign \relax \rowcolor{tabu@rc@\the\tabu@nested}% + \def\tabu@rc@ ##1##2{\gdef\tabu@rc@{% + \ifnum \tabu@modulo {\c@taburow-##2}{##1+1}=\@ne + \resetcolorseries[{##1}]{tabu@rcseries@\the\tabu@nested}\fi + \xglobal\colorlet{tabu@rc@\the\tabu@nested}{tabu@rcseries@\the\tabu@nested!!+}% + \rowcolor{tabu@rc@\the\tabu@nested}}% + }\edef\x{\noexpand\tabu@rc@{\the\count@}{\the\c@taburow}}\x + \global\let\tabu@rc@G \tabu@rc@ + \fi + \fi + \tabu@everyrow@egroup +}% \tabu@rowcolorseries +\tabuDisableCommands {\let\tabu@rc@ \@empty } +\def\tabu@rowcolorserieserror {\PackageError{tabu} + {Invalid syntax for \string\taburowcolors + \MessageBreak Please look at the documentation!}\@ehd +}% \tabu@rowcolorserieserror +\newcommand*\tabureset {% + \tabulinesep=\z@ \extrarowsep=\z@ \extratabsurround=\z@ + \tabulinestyle{}\everyrow{}\taburulecolor||{}\taburowcolors{}% +}% \tabureset +%% Parsing the line styles ------------------------------------------ +\def\tabu@getline #1{\begingroup + \csname \ifcsname if@safe@actives\endcsname % <babel> + @safe@activestrue\else + relax\fi \endcsname + \edef\tabu@temp{#1}\tabu@sanitizearg{#1}\@tempa + \let\tabu@thestyle \relax + \ifcsname tabu@linestyle@\@tempa \endcsname + \edef\tabu@thestyle{\endgroup + \def\tabu@thestyle{\expandafter\noexpand + \csname tabu@linestyle@\@tempa\endcsname}% + }\tabu@thestyle + \else \expandafter\tabu@definestyle \tabu@temp \@nil + \fi +}% \tabu@getline +\def\tabu@definestyle #1#2\@nil {\endlinechar \m@ne \makeatletter + \tabu@thick \maxdimen \tabu@on \maxdimen \tabu@off \maxdimen + \let\tabu@c@lon \@undefined \let\tabu@c@loff \@undefined + \ifodd 1\ifcat .#1\else\ifcat\relax #1\else 0\fi\fi % catcode 12 or non expandable cs + \def\tabu@temp{\tabu@getparam{thick}}% + \else \def\tabu@temp{\tabu@getparam{thick}\maxdimen}% + \fi + {% + \let\tabu@ \relax + \def\:{\obeyspaces \tabu@oXIII \tabu@commaXIII \edef\:}% (space active \: happy ;-)) + \scantokens{\:{\tabu@temp #1#2 \tabu@\tabu@}}% + \expandafter}\expandafter + \def\expandafter\:\expandafter{\:}% line spec rewritten now ;-) + \def\;{\def\:}% + \scantokens\expandafter{\expandafter\;\expandafter{\:}}% space is now inactive (catcode 10) + \let\tabu@ \tabu@getcolor \:% all arguments are ready now ;-) + \ifdefined\tabu@c@lon \else \let\tabu@c@lon\@empty \fi + \ifx \tabu@c@lon\@empty \def\tabu@c@lon{\CT@arc@}\fi + \ifdefined\tabu@c@loff \else \let\tabu@c@loff \@empty \fi + \ifdim \tabu@on=\maxdimen \ifdim \tabu@off<\maxdimen + \tabu@on \tabulineon \fi\fi + \ifdim \tabu@off=\maxdimen \ifdim \tabu@on<\maxdimen + \tabu@off \tabulineoff \fi\fi + \ifodd 1\ifdim \tabu@off=\maxdimen \ifdim \tabu@on=\maxdimen 0 \fi\fi + \in@true % <leaders> + \else \in@false % <rule> + \fi + \ifdim\tabu@thick=\maxdimen \def\tabu@thick{\arrayrulewidth}% + \else \edef\tabu@thick{\the\tabu@thick}% + \fi + \edef \tabu@thestyle ##1##2{\endgroup + \def\tabu@thestyle{% + \ifin@ \noexpand\tabu@leadersstyle {\tabu@thick} + {\the\tabu@on}{##1} + {\the\tabu@off}{##2}% + \else \noexpand\tabu@rulesstyle + {##1\vrule width \tabu@thick}% + {##1\leaders \hrule height \tabu@thick \hfil}% + \fi}% + }\expandafter \expandafter + \expandafter \tabu@thestyle \expandafter + \expandafter \expandafter + {\expandafter\tabu@c@lon\expandafter}\expandafter{\tabu@c@loff}% +}% \tabu@definestyle +{\catcode`\O=\active \lccode`\O=`\o \catcode`\,=\active + \lowercase{\gdef\tabu@oXIII {\catcode`\o=\active \let O=\tabu@oxiii}} + \gdef\tabu@commaXIII {\catcode`\,=\active \let ,=\space} +}% \catcode +\def\tabu@oxiii #1{% + \ifcase \ifx n#1\z@ \else + \ifx f#1\@ne\else + \tw@ \fi\fi + \expandafter\tabu@onxiii + \or \expandafter\tabu@ofxiii + \else o% + \fi#1}% +\def\tabu@onxiii #1#2{% + \ifcase \ifx !#2\tw@ \else + \ifcat.\noexpand#2\z@ \else + \ifx \tabu@spxiii#2\@ne\else + \tw@ \fi\fi\fi + \tabu@getparam{on}#2\expandafter\@gobble + \or \expandafter\tabu@onxiii % (space is active) + \else o\expandafter\@firstofone + \fi{#1#2}}% +\def\tabu@ofxiii #1#2{% + \ifx #2f\expandafter\tabu@offxiii + \else o\expandafter\@firstofone + \fi{#1#2}} +\def\tabu@offxiii #1#2{% + \ifcase \ifx !#2\tw@ \else + \ifcat.\noexpand#2\z@ \else + \ifx\tabu@spxiii#2\@ne \else + \tw@ \fi\fi\fi + \tabu@getparam{off}#2\expandafter\@gobble + \or \expandafter\tabu@offxiii % (space is active) + \else o\expandafter\@firstofone + \fi{#1#2}} +\def\tabu@getparam #1{\tabu@ \csname tabu@#1\endcsname=} +\def\tabu@getcolor #1{% \tabu@ <- \tabu@getcolor after \edef + \ifx \tabu@#1\else % no more spec + \let\tabu@theparam=#1\afterassignment \tabu@getc@l@r #1\fi +}% \tabu@getcolor +\def\tabu@getc@l@r #1\tabu@ {% + \def\tabu@temp{#1}\tabu@strtrim \tabu@temp + \ifx \tabu@temp\@empty + \else%\ifcsname \string\color@\tabu@temp \endcsname % if the color exists + \ifx \tabu@theparam \tabu@off \let\tabu@c@loff \tabu@c@l@r + \else \let\tabu@c@lon \tabu@c@l@r + \fi + %\else \tabu@warncolour{\tabu@temp}% + \fi%\fi + \tabu@ % next spec +}% \tabu@getc@l@r +\def\tabu@warncolour #1{\PackageWarning{tabu} + {Color #1 is not defined. Default color used}% +}% \tabu@warncolour +\def\tabu@leadersstyle #1#2#3#4#5{\def\tabu@leaders{{#1}{#2}{#3}{#4}{#5}}% + \ifx \tabu@leaders\tabu@leaders@G \else + \tabu@LEADERS{#1}{#2}{#3}{#4}{#5}\fi +}% \tabu@leadersstyle +\def\tabu@rulesstyle #1#2{\let\tabu@leaders \@undefined + \gdef\tabu@thevrule{#1}\gdef\tabu@thehrule{#2}% +}% \tabu@rulesstyle +%% The leaders boxes ------------------------------------------------ +\def\tabu@LEADERS #1#2#3#4#5{%% width, dash, dash color, gap, gap color + {\let\color \tabu@color % => during trials -> \color = \tabu@nocolor + {% % but the leaders boxes should have colors ! + \def\@therule{\vrule}\def\@thick{height}\def\@length{width}% + \def\@box{\hbox}\def\@unbox{\unhbox}\def\@elt{\wd}% + \def\@skip{\hskip}\def\@ss{\hss}\def\tabu@leads{\tabu@hleads}% + \tabu@l@@d@rs {#1}{#2}{#3}{#4}{#5}% + \global\let\tabu@thehleaders \tabu@theleaders + }% + {% + \def\@therule{\hrule}\def\@thick{width}\def\@length{height}% + \def\@box{\vbox}\def\@unbox{\unvbox}\def\@elt{\ht}% + \def\@skip{\vskip}\def\@ss{\vss}\def\tabu@leads{\tabu@vleads}% + \tabu@l@@d@rs {#1}{#2}{#3}{#4}{#5}% + \global\let\tabu@thevleaders \tabu@theleaders + }% + \gdef\tabu@leaders@G{{#1}{#2}{#3}{#4}{#5}}% + }% +}% \tabu@LEADERS +\def\tabu@therule #1#2{\@therule \@thick#1\@length\dimexpr#2/2 \@depth\z@} +\def\tabu@l@@d@rs #1#2#3#4#5{%% width, dash, dash color, gap, gap color + \global\setbox \tabu@leads=\@box{% + {#3\tabu@therule{#1}{#2}}% + \ifx\\#5\\\@skip#4\else{#5\tabu@therule{#1}{#4*2}}\fi + {#3\tabu@therule{#1}{#2}}}% + \global\setbox\tabu@leads=\@box to\@elt\tabu@leads{\@ss + {#3\tabu@therule{#1}{#2}}\@unbox\tabu@leads}% + \edef\tabu@theleaders ##1{\def\noexpand\tabu@theleaders {% + {##1\tabu@therule{#1}{#2}}% + \xleaders \copy\tabu@leads \@ss + \tabu@therule{0pt}{-#2}{##1\tabu@therule{#1}{#2}}}% + }\tabu@theleaders{#3}% +}% \tabu@l@@d@rs +%% \tabu \endtabu \tabu* \longtabu \endlongtabu \longtabu* ---------- +\newcommand*\tabu {\tabu@longfalse + \ifmmode \def\tabu@ {\array}\def\endtabu {\endarray}% + \else \def\tabu@ {\tabu@tabular}\def\endtabu {\endtabular}\fi + \expandafter\let\csname tabu*\endcsname \tabu + \expandafter\def\csname endtabu*\endcsname{\endtabu}% + \tabu@spreadfalse \tabu@negcoeffalse \tabu@settarget +}% {tabu} +\let\tabu@tabular \tabular % <For LyX: some users redefine \tabular...> +\expandafter\def\csname tabu*\endcsname{\tabuscantokenstrue \tabu} +\newcommand*\longtabu {\tabu@longtrue + \ifmmode\PackageError{tabu}{longtabu not allowed in math mode}\fi + \def\tabu@{\longtable}\def\endlongtabu{\endlongtable}% + \LTchunksize=\@M + \expandafter\let\csname tabu*\endcsname \tabu + \expandafter\def\csname endlongtabu*\endcsname{\endlongtabu}% + \let\LT@startpbox \tabu@LT@startpbox % \everypar{ array struts } + \tabu@spreadfalse \tabu@negcoeffalse \tabu@settarget +}% {longtabu} +\expandafter\def\csname longtabu*\endcsname{\tabuscantokenstrue \longtabu} +\def\tabu@nolongtabu{\PackageError{tabu} + {longtabu requires the longtable package}\@ehd} +%% Read the target and then : \tabular or \@array ------------------ +\def\tabu@settarget {\futurelet\@let@token \tabu@sett@rget } +\def\tabu@sett@rget {\tabu@target \z@ + \ifcase \ifx \bgroup\@let@token \z@ \else + \ifx \@sptoken\@let@token \@ne \else + \if t\@let@token \tw@ \else + \if s\@let@token \thr@@\else + \z@\fi\fi\fi\fi + \expandafter\tabu@begin + \or \expandafter\tabu@gobblespace\expandafter\tabu@settarget + \or \expandafter\tabu@to + \or \expandafter\tabu@spread + \fi +}% \tabu@sett@rget +\def\tabu@to to{\def\tabu@halignto{to}\tabu@gettarget} +\def\tabu@spread spread{\tabu@spreadtrue\def\tabu@halignto{spread}\tabu@gettarget} +\def\tabu@gettarget {\afterassignment\tabu@linegoaltarget \tabu@target } +\def\tabu@linegoaltarget {\futurelet\tabu@temp \tabu@linegoalt@rget } +\def\tabu@linegoalt@rget {% + \ifx \tabu@temp\LNGL@setlinegoal + \LNGL@setlinegoal \expandafter \@firstoftwo \fi % @gobbles \LNGL@setlinegoal + \tabu@begin +}% \tabu@linegoalt@rget +\def\tabu@begin #1#{% + \iftabu@measuring \expandafter\tabu@nestedmeasure \fi + \ifdim \tabu@target=\z@ \let\tabu@halignto \@empty + \else \edef\tabu@halignto{\tabu@halignto\the\tabu@target}% + \fi + \@testopt \tabu@tabu@ \tabu@aligndefault #1\@nil +}% \tabu@begin +\long\def\tabu@tabu@ [#1]#2\@nil #3{\tabu@setup + \def\tabu@align {#1}\def\tabu@savedpream{\NC@find #3}% + \tabu@ [\tabu@align ]#2{#3\tabu@rewritefirst }% +}% \tabu@tabu@ +\def\tabu@nestedmeasure {% + \ifodd 1\iftabu@spread \else \ifdim\tabu@target=\z@ \else 0 \fi\fi\relax + \tabu@spreadtrue + \else \begingroup \iffalse{\fi \ifnum0=`}\fi + \toks@{}\def\tabu@stack{b}% + \expandafter\tabu@collectbody\expandafter\tabu@quickrule + \expandafter\endgroup + \fi +}% \tabu@nestedmeasure +\def\tabu@quickrule {\indent\vrule height\z@ depth\z@ width\tabu@target} +%% \tabu@setup \tabu@init \tabu@indent +\def\tabu@setup{\tabu@alloc@ + \ifcase \tabu@nested + \ifmmode \else \iftabu@spread\else \ifdim\tabu@target=\z@ + \let\tabu@afterendpar \par + \fi\fi\fi + \def\tabu@aligndefault{c}\tabu@init \tabu@indent + \else % <nested tabu> + \def\tabu@aligndefault{t}\let\tabudefaulttarget \linewidth + \fi + \let\tabu@thetarget \tabudefaulttarget \let\tabu@restored \@undefined + \edef\tabu@NC@list{\the\NC@list}\NC@list{\NC@do \tabu@rewritefirst}% + \everycr{}\let\@startpbox \tabu@startpbox % for nested tabu inside longtabu... + \let\@endpbox \tabu@endpbox % idem " " " " " " + \let\@tabarray \tabu@tabarray % idem " " " " " " + \tabu@setcleanup \tabu@setreset +}% \tabu@setup +\def\tabu@init{\tabu@starttimer \tabu@measuringfalse + \edef\tabu@hfuzz {\the\dimexpr\hfuzz+1sp}\global\tabu@footnotes{}% + \let\firsthline \tabu@firsthline \let\lasthline \tabu@lasthline + \let\firstline \tabu@firstline \let\lastline \tabu@lastline + \let\hline \tabu@hline \let\@xhline \tabu@xhline + \let\color \tabu@color \let\@arstrutbox \tabu@arstrutbox + \iftabu@colortbl\else\let\LT@@hline \tabu@LT@@hline \fi + \tabu@trivlist %<restore \\=\@normalcr inside lists> + \let\@footnotetext \tabu@footnotetext \let\@xfootnotetext \tabu@xfootnotetext + \let\@xfootnote \tabu@xfootnote \let\centering \tabu@centering + \let\raggedright \tabu@raggedright \let\raggedleft \tabu@raggedleft + \let\tabudecimal \tabu@tabudecimal \let\Centering \tabu@Centering + \let\RaggedRight \tabu@RaggedRight \let\RaggedLeft \tabu@RaggedLeft + \let\justifying \tabu@justifying \let\rowfont \tabu@rowfont + \let\fbox \tabu@fbox \let\color@b@x \tabu@color@b@x + \let\tabu@@everycr \everycr \let\tabu@@everypar \everypar + \let\tabu@prepnext@tokORI \prepnext@tok\let\prepnext@tok \tabu@prepnext@tok + \let\tabu@multicolumnORI\multicolumn \let\multicolumn \tabu@multicolumn + \let\tabu@startpbox \@startpbox % for nested tabu inside longtabu pfff !!! + \let\tabu@endpbox \@endpbox % idem " " " " " " " + \let\tabu@tabarray \@tabarray % idem " " " " " " " + \tabu@adl@fix \let\endarray \tabu@endarray % <fix> colortbl & arydshln (delarray) + \iftabu@colortbl\CT@everycr\expandafter{\expandafter\iftabu@everyrow \the\CT@everycr \fi}\fi +}% \tabu@init +\def\tabu@indent{% correction for indentation + \ifdim \parindent>\z@\ifx \linewidth\tabudefaulttarget + \everypar\expandafter{% + \the\everypar\everypar\expandafter{\the\everypar}% + \setbox\z@=\lastbox + \ifdim\wd\z@>\z@ \edef\tabu@thetarget + {\the\dimexpr -\wd\z@+\tabudefaulttarget}\fi + \box\z@}% + \fi\fi +}% \tabu@indent +\def\tabu@setcleanup {% saves last global assignments + \ifodd 1\ifmmode \else \iftabu@long \else 0\fi\fi\relax + \def\tabu@aftergroupcleanup{% + \def\tabu@aftergroupcleanup{\aftergroup\tabu@cleanup}}% + \else + \def\tabu@aftergroupcleanup{% + \aftergroup\aftergroup\aftergroup\tabu@cleanup + \let\tabu@aftergroupcleanup \relax}% + \fi + \let\tabu@arc@Gsave \tabu@arc@G + \let\tabu@arc@G \tabu@arc@L % <init> + \let\tabu@drsc@Gsave \tabu@drsc@G + \let\tabu@drsc@G \tabu@drsc@L % <init> + \let\tabu@ls@Gsave \tabu@ls@G + \let\tabu@ls@G \tabu@ls@L % <init> + \let\tabu@rc@Gsave \tabu@rc@G + \let\tabu@rc@G \tabu@rc@L % <init> + \let\tabu@evr@Gsave \tabu@evr@G + \let\tabu@evr@G \tabu@evr@L % <init> + \let\tabu@celllalign@save \tabu@celllalign + \let\tabu@cellralign@save \tabu@cellralign + \let\tabu@cellleft@save \tabu@cellleft + \let\tabu@cellright@save \tabu@cellright + \let\tabu@@celllalign@save \tabu@@celllalign + \let\tabu@@cellralign@save \tabu@@cellralign + \let\tabu@@cellleft@save \tabu@@cellleft + \let\tabu@@cellright@save \tabu@@cellright + \let\tabu@rowfontreset@save \tabu@rowfontreset + \let\tabu@@rowfontreset@save\tabu@@rowfontreset + \let\tabu@rowfontreset \@empty + \edef\tabu@alloc@save {\the\tabu@alloc}% restore at \tabu@reset + \edef\c@taburow@save {\the\c@taburow}% + \edef\tabu@naturalX@save {\the\tabu@naturalX}% + \let\tabu@naturalXmin@save \tabu@naturalXmin + \let\tabu@naturalXmax@save \tabu@naturalXmax + \let\tabu@mkarstrut@save \tabu@mkarstrut + \edef\tabu@clarstrut{% + \extrarowheight \the\dimexpr \ht\@arstrutbox-\ht\strutbox \relax + \extrarowdepth \the\dimexpr \dp\@arstrutbox-\dp\strutbox \relax + \let\noexpand\@arraystretch \@ne \noexpand\tabu@rearstrut}% +}% \tabu@setcleanup +\def\tabu@cleanup {\begingroup + \globaldefs\@ne \tabu@everyrowtrue + \let\tabu@arc@G \tabu@arc@Gsave + \let\CT@arc@ \tabu@arc@G + \let\tabu@drsc@G \tabu@drsc@Gsave + \let\CT@drsc@ \tabu@drsc@G + \let\tabu@ls@G \tabu@ls@Gsave + \let\tabu@ls@ \tabu@ls@G + \let\tabu@rc@G \tabu@rc@Gsave + \let\tabu@rc@ \tabu@rc@G + \let\CT@do@color \relax + \let\tabu@evr@G \tabu@evr@Gsave + \let\tabu@celllalign \tabu@celllalign@save + \let\tabu@cellralign \tabu@cellralign@save + \let\tabu@cellleft \tabu@cellleft@save + \let\tabu@cellright \tabu@cellright@save + \let\tabu@@celllalign \tabu@@celllalign@save + \let\tabu@@cellralign \tabu@@cellralign@save + \let\tabu@@cellleft \tabu@@cellleft@save + \let\tabu@@cellright \tabu@@cellright@save + \let\tabu@rowfontreset \tabu@rowfontreset@save + \let\tabu@@rowfontreset \tabu@@rowfontreset@save + \tabu@naturalX =\tabu@naturalX@save + \let\tabu@naturalXmax \tabu@naturalXmax@save + \let\tabu@naturalXmin \tabu@naturalXmin@save + \let\tabu@mkarstrut \tabu@mkarstrut@save + \c@taburow =\c@taburow@save + \ifcase \tabu@nested \tabu@alloc \m@ne\fi + \endgroup % <end of \globaldefs> + \ifcase \tabu@nested + \the\tabu@footnotes \global\tabu@footnotes{}% + \tabu@afterendpar \tabu@elapsedtime + \fi + \tabu@clarstrut + \everyrow\expandafter {\tabu@evr@G}% +}% \tabu@cleanup +\let\tabu@afterendpar \relax +\def\tabu@setreset {% + \edef\tabu@savedparams {% \relax for \tabu@message@save + \ifmmode \col@sep \the\arraycolsep + \else \col@sep \the\tabcolsep \fi \relax + \arrayrulewidth \the\arrayrulewidth \relax + \doublerulesep \the\doublerulesep \relax + \extratabsurround \the\extratabsurround \relax + \extrarowheight \the\extrarowheight \relax + \extrarowdepth \the\extrarowdepth \relax + \abovetabulinesep \the\abovetabulinesep \relax + \belowtabulinesep \the\belowtabulinesep \relax + \def\noexpand\arraystretch{\arraystretch}% + \ifdefined\minrowclearance \minrowclearance\the\minrowclearance\relax\fi}% + \begingroup + \@temptokena\expandafter{\tabu@savedparams}% => only for \savetabu / \usetabu + \ifx \tabu@arc@L\relax \else \tabu@setsave \tabu@arc@L \fi + \ifx \tabu@drsc@L\relax \else \tabu@setsave \tabu@drsc@L \fi + \tabu@setsave \tabu@ls@L \tabu@setsave \tabu@evr@L + \expandafter \endgroup \expandafter + \def\expandafter\tabu@saved@ \expandafter{\the\@temptokena + \let\tabu@arc@G \tabu@arc@L + \let\tabu@drsc@G \tabu@drsc@L + \let\tabu@ls@G \tabu@ls@L + \let\tabu@rc@G \tabu@rc@L + \let\tabu@evr@G \tabu@evr@L}% + \def\tabu@reset{\tabu@savedparams + \tabu@everyrowtrue \c@taburow \z@ + \let\CT@arc@ \tabu@arc@L + \let\CT@drsc@ \tabu@drsc@L + \let\tabu@ls@ \tabu@ls@L + \let\tabu@rc@ \tabu@rc@L + \global\tabu@alloc \tabu@alloc@save + \everyrow\expandafter{\tabu@evr@L}}% +}% \tabu@reset +\def\tabu@setsave #1{\expandafter\tabu@sets@ve #1\@nil{#1}} +\long\def\tabu@sets@ve #1\@nil #2{\@temptokena\expandafter{\the\@temptokena \def#2{#1}}} +%% The Rewriting Process ------------------------------------------- +\def\tabu@newcolumntype #1{% + \expandafter\tabu@new@columntype + \csname NC@find@\string#1\expandafter\endcsname + \csname NC@rewrite@\string#1\endcsname + {#1}% +}% \tabu@newcolumntype +\def\tabu@new@columntype #1#2#3{% + \def#1##1#3{\NC@{##1}}% + \let#2\relax \newcommand*#2% +}% \tabu@new@columntype +\def\tabu@privatecolumntype #1{% + \expandafter\tabu@private@columntype + \csname NC@find@\string#1\expandafter\endcsname + \csname NC@rewrite@\string#1\expandafter\endcsname + \csname tabu@NC@find@\string#1\expandafter\endcsname + \csname tabu@NC@rewrite@\string#1\endcsname + {#1}% +}% \tabu@privatecolumntype +\def\tabu@private@columntype#1#2#3#4{% + \g@addto@macro\tabu@privatecolumns{\let#1#3\let#2#4}% + \tabu@new@columntype#3#4% +}% \tabu@private@columntype +\let\tabu@privatecolumns \@empty +\newcommand*\tabucolumn [1]{\expandafter \def \expandafter + \tabu@highprioritycolumns\expandafter{\tabu@highprioritycolumns + \NC@do #1}}% +\let\tabu@highprioritycolumns \@empty +%% The | ``column'' : rewriting process -------------------------- +\tabu@privatecolumntype |{\tabu@rewritevline} +\newcommand*\tabu@rewritevline[1][]{\tabu@vlinearg{#1}% + \expandafter \NC@find \tabu@rewritten} +\def\tabu@lines #1{% + \ifx|#1\else \tabu@privatecolumntype #1{\tabu@rewritevline}\fi + \NC@list\expandafter{\the\NC@list \NC@do #1}% +}% \tabu@lines@ +\def\tabu@vlinearg #1{% + \ifx\\#1\\\def\tabu@thestyle {\tabu@ls@}% + \else\tabu@getline {#1}% + \fi + \def\tabu@rewritten ##1{\def\tabu@rewritten{!{##1\tabu@thevline}}% + }\expandafter\tabu@rewritten\expandafter{\tabu@thestyle}% + \expandafter \tabu@keepls \tabu@thestyle \@nil +}% \tabu@vlinearg +\def\tabu@keepls #1\@nil{% + \ifcat $\@cdr #1\@nil $% + \ifx \relax#1\else + \ifx \tabu@ls@#1\else + \let#1\relax + \xdef\tabu@mkpreambuffer{\tabu@mkpreambuffer + \tabu@savels\noexpand#1}\fi\fi\fi +}% \tabu@keepls +\def\tabu@thevline {\begingroup + \ifdefined\tabu@leaders + \setbox\@tempboxa=\vtop to\dimexpr + \ht\@arstrutbox+\dp\@arstrutbox{{\tabu@thevleaders}}% + \ht\@tempboxa=\ht\@arstrutbox \dp\@tempboxa=\dp\@arstrutbox + \box\@tempboxa + \else + \tabu@thevrule + \fi \endgroup +}% \tabu@thevline +\def\tabu@savels #1{% + \expandafter\let\csname\string#1\endcsname #1% + \expandafter\def\expandafter\tabu@reset\expandafter{\tabu@reset + \tabu@resetls#1}}% +\def\tabu@resetls #1{\expandafter\let\expandafter#1\csname\string#1\endcsname}% +%% \multicolumn inside tabu environment ----------------------------- +\tabu@newcolumntype \tabu@rewritemulticolumn{% + \aftergroup \tabu@endrewritemulticolumn % after \@mkpream group + \NC@list{\NC@do *}\tabu@textbar \tabu@lines + \tabu@savedecl + \tabu@privatecolumns + \NC@list\expandafter{\the\expandafter\NC@list \tabu@NC@list}% + \let\tabu@savels \relax + \NC@find +}% \tabu@rewritemulticolumn +\def\tabu@endrewritemulticolumn{\gdef\tabu@mkpreambuffer{}\endgroup} +\def\tabu@multicolumn{\tabu@ifenvir \tabu@multic@lumn \tabu@multicolumnORI} +\long\def\tabu@multic@lumn #1#2#3{\multispan{#1}\begingroup + \tabu@everyrowtrue + \NC@list{\NC@do \tabu@rewritemulticolumn}% + \expandafter\@gobbletwo % gobbles \multispan{#1} + \tabu@multicolumnORI{#1}{\tabu@rewritemulticolumn #2}% + {\iftabuscantokens \tabu@rescan \else \expandafter\@firstofone \fi + {#3}}% +}% \tabu@multic@lumn +%% The X column(s): rewriting process ----------------------------- +\tabu@privatecolumntype X[1][]{\begingroup \tabu@siunitx{\endgroup \tabu@rewriteX {#1}}} +\def\tabu@nosiunitx #1{#1{}{}\expandafter \NC@find \tabu@rewritten } +\def\tabu@siunitx #1{\@ifnextchar \bgroup + {\tabu@rewriteX@Ss{#1}} + {\tabu@nosiunitx{#1}}} +\def\tabu@rewriteX@Ss #1#2{\@temptokena{}% + \@defaultunits \let\tabu@temp =#2\relax\@nnil + \ifodd 1\ifx S\tabu@temp \else \ifx s\tabu@temp \else 0 \fi\fi + \def\NC@find{\def\NC@find >####1####2<####3\relax{#1 {####1}{####3}% + }\expandafter\NC@find \the\@temptokena \relax + }\expandafter\NC@rewrite@S \@gobble #2\relax + \else \tabu@siunitxerror + \fi + \expandafter \NC@find \tabu@rewritten +}% \tabu@rewriteX@Ss +\def\tabu@siunitxerror {\PackageError{tabu}{Not a S nor s column ! + \MessageBreak X column can only embed siunitx S or s columns}\@ehd +}% \tabu@siunitxerror +\def\tabu@rewriteX #1#2#3{\tabu@Xarg {#1}{#2}{#3}% + \iftabu@measuring + \else \tabu@measuringtrue % first X column found in the preamble + \let\@halignto \relax \let\tabu@halignto \relax + \iftabu@spread \tabu@spreadtarget \tabu@target \tabu@target \z@ + \else \tabu@spreadtarget \z@ \fi + \ifdim \tabu@target=\z@ + \setlength\tabu@target \tabu@thetarget + \tabu@message{\tabu@message@defaulttarget}% + \else \tabu@message{\tabu@message@target}\fi + \fi +}% \tabu@rewriteX +\def\tabu@rewriteXrestore #1#2#3{\let\@halignto \relax + \def\tabu@rewritten{l}} +\def\tabu@Xarg #1#2#3{% + \advance\tabu@Xcol \@ne \let\tabu@Xlcr \@empty + \let\tabu@Xdisp \@empty \let\tabu@Xmath \@empty + \ifx\\#1\\% <shortcut when no option> + \def\tabu@rewritten{p}\tabucolX \p@ % <default coef = 1> + \else + \let\tabu@rewritten \@empty \let\tabu@temp \@empty \tabucolX \z@ + \tabu@Xparse {}#1\relax + \fi + \tabu@Xrewritten{#2}{#3}% +}% \tabu@Xarg +\def\tabu@Xparse #1{\futurelet\@let@token \tabu@Xtest} +\expandafter\def\expandafter\tabu@Xparsespace\space{\tabu@Xparse{}} +\def\tabu@Xtest{% + \ifcase \ifx \relax\@let@token \z@ \else + \if ,\@let@token \m@ne\else + \if p\@let@token 1\else + \if m\@let@token 2\else + \if b\@let@token 3\else + \if l\@let@token 4\else + \if c\@let@token 5\else + \if r\@let@token 6\else + \if j\@let@token 7\else + \if L\@let@token 8\else + \if C\@let@token 9\else + \if R\@let@token 10\else + \if J\@let@token 11\else + \ifx \@sptoken\@let@token 12\else + \if .\@let@token 13\else + \if -\@let@token 13\else + \ifcat $\@let@token 14\else + 15\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\relax + \or \tabu@Xtype {p}% + \or \tabu@Xtype {m}% + \or \tabu@Xtype {b}% + \or \tabu@Xalign \raggedright\relax + \or \tabu@Xalign \centering\relax + \or \tabu@Xalign \raggedleft\relax + \or \tabu@Xalign \tabu@justify\relax + \or \tabu@Xalign \RaggedRight\raggedright + \or \tabu@Xalign \Centering\centering + \or \tabu@Xalign \RaggedLeft\raggedleft + \or \tabu@Xalign \justifying\tabu@justify + \or \expandafter \tabu@Xparsespace + \or \expandafter \tabu@Xcoef + \or \expandafter \tabu@Xm@th + \or \tabu@Xcoef{}% + \else\expandafter \tabu@Xparse + \fi +}% \tabu@Xtest +\def\tabu@Xalign #1#2{% + \ifx \tabu@Xlcr\@empty \else \PackageWarning{tabu} + {Duplicate horizontal alignment specification}\fi + \ifdefined#1\def\tabu@Xlcr{#1}\let#1\relax + \else \def\tabu@Xlcr{#2}\let#2\relax\fi + \expandafter\tabu@Xparse +}% \tabu@Xalign +\def\tabu@Xtype #1{% + \ifx \tabu@rewritten\@empty \else \PackageWarning{tabu} + {Duplicate vertical alignment specification}\fi + \def\tabu@rewritten{#1}\expandafter\tabu@Xparse +}% \tabu@Xtype +\def\tabu@Xcoef#1{\edef\tabu@temp{\tabu@temp#1}% + \afterassignment\tabu@Xc@ef \tabu@cnt\number\if-#10\fi +}% \tabu@Xcoef +\def\tabu@Xc@ef{\advance\tabucolX \tabu@temp\the\tabu@cnt\p@ + \tabu@Xparse{}% +}% \tabu@Xc@ef +\def\tabu@Xm@th #1{\futurelet \@let@token \tabu@Xd@sp} +\def\tabu@Xd@sp{\let\tabu@Xmath=$% + \ifx $\@let@token \def\tabu@Xdisp{\displaystyle}% + \expandafter\tabu@Xparse + \else \expandafter\tabu@Xparse\expandafter{\expandafter}% + \fi +}% \tabu@Xd@sp +\def\tabu@Xrewritten {% + \ifx \tabu@rewritten\@empty \def\tabu@rewritten{p}\fi + \ifdim \tabucolX<\z@ \tabu@negcoeftrue + \else\ifdim \tabucolX=\z@ \tabucolX \p@ + \fi\fi + \edef\tabu@temp{{\the\tabu@Xcol}{\tabu@strippt\tabucolX}}% + \edef\tabu@Xcoefs{\tabu@Xcoefs \tabu@ \tabu@temp}% + \edef\tabu@rewritten ##1##2{\def\noexpand\tabu@rewritten{% + >{\tabu@Xlcr \ifx$\tabu@Xmath$\tabu@Xdisp\fi ##1}% + \tabu@rewritten {\tabu@hsize \tabu@temp}% + <{##2\ifx$\tabu@Xmath$\fi}}% + }\tabu@rewritten +}% \tabu@Xrewritten +\def\tabu@hsize #1#2{% + \ifdim #2\p@<\z@ + \ifdim \tabucolX=\maxdimen \tabu@wd{#1}\else + \ifdim \tabu@wd{#1}<-#2\tabucolX \tabu@wd{#1}\else -#2\tabucolX\fi + \fi + \else #2\tabucolX + \fi +}% \tabu@hsize +%% \usetabu and \preamble: rewriting process --------------------- +\tabu@privatecolumntype \usetabu [1]{% + \ifx\\#1\\\tabu@saveerr{}\else + \@ifundefined{tabu@saved@\string#1} + {\tabu@saveerr{#1}} + {\let\tabu@rewriteX \tabu@rewriteXrestore + \csname tabu@saved@\string#1\expandafter\endcsname\expandafter\@ne}% + \fi +}% \NC@rewrite@\usetabu +\tabu@privatecolumntype \preamble [1]{% + \ifx\\#1\\\tabu@saveerr{}\else + \@ifundefined{tabu@saved@\string#1} + {\tabu@saveerr{#1}} + {\csname tabu@saved@\string#1\expandafter\endcsname\expandafter\z@}% + \fi +}% \NC@rewrite@\preamble +%% Controlling the rewriting process ------------------------------- +\tabu@newcolumntype \tabu@rewritefirst{% + \iftabu@long \aftergroup \tabu@longpream % <the whole implementation is here !> + \else \aftergroup \tabu@pream + \fi + \let\tabu@ \relax \let\tabu@hsize \relax + \let\tabu@Xcoefs \@empty \let\tabu@savels \relax + \tabu@Xcol \z@ \tabu@cnt \tw@ + \gdef\tabu@mkpreambuffer{\tabu@{}}\tabu@measuringfalse + \global\setbox\@arstrutbox \box\@arstrutbox + \NC@list{\NC@do *}\tabu@textbar \tabu@lines + \NC@list\expandafter{\the\NC@list \NC@do X}% + \iftabu@siunitx % <siunitx S and s columns> + \NC@list\expandafter{\the\NC@list \NC@do S\NC@do s}\fi + \NC@list\expandafter{\the\expandafter\NC@list \tabu@highprioritycolumns}% + \expandafter\def\expandafter\tabu@NC@list\expandafter{% + \the\expandafter\NC@list \tabu@NC@list}% % * | X S <original> + \NC@list\expandafter{\expandafter \NC@do \expandafter\usetabu + \expandafter \NC@do \expandafter\preamble + \the\NC@list \NC@do \tabu@rewritemiddle + \NC@do \tabu@rewritelast}% + \tabu@savedecl + \tabu@privatecolumns + \edef\tabu@prev{\the\@temptokena}\NC@find \tabu@rewritemiddle +}% NC@rewrite@\tabu@rewritefirst +\tabu@newcolumntype \tabu@rewritemiddle{% + \edef\tabu@temp{\the\@temptokena}\NC@find \tabu@rewritelast +}% \NC@rewrite@\tabu@rewritemiddle +\tabu@newcolumntype \tabu@rewritelast{% + \ifx \tabu@temp\tabu@prev \advance\tabu@cnt \m@ne + \NC@list\expandafter{\tabu@NC@list \NC@do \tabu@rewritemiddle + \NC@do \tabu@rewritelast}% + \else \let\tabu@prev\tabu@temp + \fi + \ifcase \tabu@cnt \expandafter\tabu@endrewrite + \else \expandafter\NC@find \expandafter\tabu@rewritemiddle + \fi +}% \NC@rewrite@\tabu@rewritelast +%% Choosing the strategy -------------------------------------------- +\def\tabu@endrewrite {% + \let\tabu@temp \NC@find + \ifx \@arrayright\relax \let\@arrayright \@empty \fi + \count@=% + \ifx \@finalstrut\tabu@finalstrut \z@ % outer in mode 0 print + \iftabu@measuring + \xdef\tabu@mkpreambuffer{\tabu@mkpreambuffer + \tabu@target \csname tabu@\the\tabu@nested.T\endcsname + \tabucolX \csname tabu@\the\tabu@nested.X\endcsname + \edef\@halignto {\ifx\@arrayright\@empty to\tabu@target\fi}}% + \fi + \else\iftabu@measuring 4 % X columns + \xdef\tabu@mkpreambuffer{\tabu@{\tabu@mkpreambuffer + \tabu@target \the\tabu@target + \tabu@spreadtarget \the\tabu@spreadtarget}% + \def\noexpand\tabu@Xcoefs{\tabu@Xcoefs}% + \edef\tabu@halignto{\ifx \@arrayright\@empty to\tabu@target\fi}}% + \let\tabu@Xcoefs \relax + \else\ifcase\tabu@nested \thr@@ % outer, no X + \global\let\tabu@afterendpar \relax + \else \@ne % inner, no X, outer in mode 1 or 2 + \fi + \ifdefined\tabu@usetabu + \else \ifdim\tabu@target=\z@ + \else \let\tabu@temp \tabu@extracolsep + \fi\fi + \fi + \fi + \xdef\tabu@mkpreambuffer{\count@ \the\count@ \tabu@mkpreambuffer}% + \tabu@temp +}% \tabu@endrewrite +\def\tabu@extracolsep{\@defaultunits \expandafter\let + \expandafter\tabu@temp \expandafter=\the\@temptokena \relax\@nnil + \ifx \tabu@temp\@sptoken + \expandafter\tabu@gobblespace \expandafter\tabu@extracolsep + \else + \edef\tabu@temp{\noexpand\NC@find + \if |\noexpand\tabu@temp @% + \else\if !\noexpand\tabu@temp @% + \else !% + \fi\fi + {\noexpand\extracolsep\noexpand\@flushglue}}% + \fi + \tabu@temp +}% \tabu@extrac@lsep +%% Implementing the strategy ---------------------------------------- +\long\def\tabu@pream #1\@preamble {% + \let\tabu@ \tabu@@ \tabu@mkpreambuffer \tabu@aftergroupcleanup + \NC@list\expandafter {\tabu@NC@list}% in case of nesting... + \ifdefined\tabu@usetabu \tabu@usetabu \tabu@target \z@ \fi + \let\tabu@savedpreamble \@preamble + \global\let\tabu@elapsedtime \relax + \tabu@thebody ={#1\tabu@aftergroupcleanup}% + \tabu@thebody =\expandafter{\the\expandafter\tabu@thebody + \@preamble}% + \edef\tabuthepreamble {\the\tabu@thebody}% ( no @ allowed for \scantokens ) + \tabu@select +}% \tabu@pream +\long\def\tabu@longpream #1\LT@bchunk #2\LT@bchunk{% + \let\tabu@ \tabu@@ \tabu@mkpreambuffer \tabu@aftergroupcleanup + \NC@list\expandafter {\tabu@NC@list}% in case of nesting... + \let\tabu@savedpreamble \@preamble + \global\let\tabu@elapsedtime \relax + \tabu@thebody ={#1\LT@bchunk #2\tabu@aftergroupcleanup \LT@bchunk}% + \edef\tabuthepreamble {\the\tabu@thebody}% ( no @ allowed for \scantokens ) + \tabu@select +}% \tabu@longpream +\def\tabu@select {% + \ifnum\tabu@nested>\z@ \tabuscantokensfalse \fi + \ifnum \count@=\@ne \iftabu@measuring \count@=\tw@ \fi\fi + \ifcase \count@ + \global\let\tabu@elapsedtime \relax + \tabu@seteverycr + \expandafter \tabuthepreamble % vertical adjustment (inherited from outer) + \or % exit in vertical measure + struts per cell because no X and outer in mode 3 + \tabu@evr{\tabu@verticalinit}\tabu@celllalign@def{\tabu@verticalmeasure}% + \def\tabu@cellralign{\tabu@verticalspacing}% + \tabu@seteverycr + \expandafter \tabuthepreamble + \or % exit without measure because no X and outer in mode 4 + \tabu@evr{}\tabu@celllalign@def{}\let\tabu@cellralign \@empty + \tabu@seteverycr + \expandafter \tabuthepreamble + \else % needs trials + \tabu@evr{}\tabu@celllalign@def{}\let\tabu@cellralign \@empty + \tabu@savecounters + \expandafter \tabu@setstrategy + \fi +}% \tabu@select +\def\tabu@@ {\gdef\tabu@mkpreambuffer} +%% Protections to set up before trials ------------------------------ +\def\tabu@setstrategy {\begingroup % <trials group> + \tabu@trialh@@k \tabu@cnt \z@ % number of trials + \hbadness \@M \let\hbadness \@tempcnta + \hfuzz \maxdimen \let\hfuzz \@tempdima + \let\write \tabu@nowrite\let\GenericError \tabu@GenericError + \let\savetabu \@gobble \let\tabudefaulttarget \linewidth + \let\@footnotetext \@gobble \let\@xfootnote \tabu@xfootnote + \let\color \tabu@nocolor\let\rowcolor \tabu@norowcolor + \let\tabu@aftergroupcleanup \relax % only after the last trial + \tabu@mkpreambuffer + \ifnum \count@>\thr@@ \let\@halignto \@empty \tabucolX@init + \def\tabu@lasttry{\m@ne\p@}\fi + \begingroup \iffalse{\fi \ifnum0=`}\fi + \toks@{}\def\tabu@stack{b}\iftabuscantokens \endlinechar=10 \obeyspaces \fi % + \tabu@collectbody \tabu@strategy % +}% \tabu@setstrategy +\def\tabu@savecounters{% + \def\@elt ##1{\csname c@##1\endcsname\the\csname c@##1\endcsname}% + \edef\tabu@clckpt {\begingroup \globaldefs=\@ne \cl@@ckpt \endgroup}\let\@elt \relax +}% \tabu@savecounters +\def\tabucolX@init {% \tabucolX <= \tabu@target / (sum coefs > 0) + \dimen@ \z@ \tabu@Xsum \z@ \tabucolX \z@ \let\tabu@ \tabu@Xinit \tabu@Xcoefs + \ifdim \dimen@>\z@ + \@tempdima \dimexpr \tabu@target *\p@/\dimen@ + \tabu@hfuzz\relax + \ifdim \tabucolX<\@tempdima \tabucolX \@tempdima \fi + \fi +}% \tabucolX@init +\def\tabu@Xinit #1#2{\tabu@Xcol #1 \advance \tabu@Xsum + \ifdim #2\p@>\z@ #2\p@ \advance\dimen@ #2\p@ + \else -#2\p@ \tabu@negcoeftrue + \@tempdima \dimexpr \tabu@target*\p@/\dimexpr-#2\p@\relax \relax + \ifdim \tabucolX<\@tempdima \tabucolX \@tempdima \fi + \tabu@wddef{#1}{0pt}% + \fi +}% \tabu@Xinit +%% Collecting the environment body ---------------------------------- +\long\def\tabu@collectbody #1#2\end #3{% + \edef\tabu@stack{\tabu@pushbegins #2\begin\end\expandafter\@gobble\tabu@stack}% + \ifx \tabu@stack\@empty + \toks@\expandafter{\expandafter\tabu@thebody\expandafter{\the\toks@ #2}% + \def\tabu@end@envir{\end{#3}}% + \iftabuscantokens + \iftabu@long \def\tabu@endenvir {\end{#3}\tabu@gobbleX}% + \else \def\tabu@endenvir {\let\endarray \@empty + \end{#3}\tabu@gobbleX}% + \fi + \else \def\tabu@endenvir {\end{#3}}\fi}% + \let\tabu@collectbody \tabu@endofcollect + \else\def\tabu@temp{#3}% + \ifx \tabu@temp\@empty \toks@\expandafter{\the\toks@ #2\end }% + \else \ifx\tabu@temp\tabu@@spxiii \toks@\expandafter{\the\toks@ #2\end #3}% + \else \ifx\tabu@temp\tabu@X \toks@\expandafter{\the\toks@ #2\end #3}% + \else \toks@\expandafter{\the\toks@ #2\end{#3}}% + \fi\fi\fi + \fi + \tabu@collectbody{#1}% +}% \tabu@collectbody +\long\def\tabu@pushbegins#1\begin#2{\ifx\end#2\else b\expandafter\tabu@pushbegins\fi}% +\def\tabu@endofcollect #1{\ifnum0=`{}\fi + \expandafter\endgroup \the\toks@ #1% +}% \tabu@endofcollect +%% The trials: switching between strategies ------------------------- +\def\tabu@strategy {\relax % stops \count@ assignment ! + \ifcase\count@ % case 0 = print with vertical adjustment (outer is finished) + \expandafter \tabu@endoftrials + \or % case 1 = exit in vertical measure (outer in mode 3) + \expandafter\xdef\csname tabu@\the\tabu@nested.T\endcsname{\the\tabu@target}% + \expandafter\xdef\csname tabu@\the\tabu@nested.X\endcsname{\the\tabucolX}% + \expandafter \tabu@endoftrials + \or % case 2 = exit with a rule replacing the table (outer in mode 4) + \expandafter \tabu@quickend + \or % case 3 = outer is in mode 3 because of no X + \begingroup + \tabu@evr{\tabu@verticalinit}\tabu@celllalign@def{\tabu@verticalmeasure}% + \def\tabu@cellralign{\tabu@verticalspacing}% + \expandafter \tabu@measuring + \else % case 4 = horizontal measure + \begingroup + \global\let\tabu@elapsedtime \tabu@message@etime + \long\def\multicolumn##1##2##3{\multispan{##1}}% + \let\tabu@startpboxORI \@startpbox + \iftabu@spread + \def\tabu@naturalXmax {\z@}% + \let\tabu@naturalXmin \tabu@naturalXmax + \tabu@evr{\global\tabu@naturalX \z@}% + \let\@startpbox \tabu@startpboxmeasure + \else\iftabu@negcoef + \let\@startpbox \tabu@startpboxmeasure + \else \let\@startpbox \tabu@startpboxquick + \fi\fi + \expandafter \tabu@measuring + \fi +}% \tabu@strategy +\def\tabu@measuring{\expandafter \tabu@trial \expandafter + \count@ \the\count@ \tabu@endtrial +}% \tabu@measuring +\def\tabu@trial{\iftabu@long \tabu@longtrial \else \tabu@shorttrial \fi} +\def\tabu@shorttrial {\setbox\tabu@box \hbox\bgroup \tabu@seteverycr + \ifx \tabu@savecounters\relax \else + \let\tabu@savecounters \relax \tabu@clckpt \fi + $\iftabuscantokens \tabu@rescan \else \expandafter\@secondoftwo \fi + \expandafter{\expandafter \tabuthepreamble + \the\tabu@thebody + \csname tabu@adl@endtrial\endcsname + \endarray}$\egroup % got \tabu@box +}% \tabu@shorttrial +\def\tabu@longtrial {\setbox\tabu@box \hbox\bgroup \tabu@seteverycr + \ifx \tabu@savecounters\relax \else + \let\tabu@savecounters \relax \tabu@clckpt \fi + \iftabuscantokens \tabu@rescan \else \expandafter\@secondoftwo \fi + \expandafter{\expandafter \tabuthepreamble + \the\tabu@thebody + \tabuendlongtrial}\egroup % got \tabu@box +}% \tabu@longtrial +\def\tabuendlongtrial{% no @ allowed for \scantokens + \LT@echunk \global\setbox\@ne \hbox{\unhbox\@ne}\kern\wd\@ne + \LT@get@widths +}% \tabuendlongtrial +\def\tabu@adl@endtrial{% <arydshln in nested trials - problem for global column counters!> + \crcr \noalign{\global\adl@ncol \tabu@nbcols}}% anything global is crap, junky and fails ! +\def\tabu@seteverycr {\tabu@reset + \everycr \expandafter{\the\everycr \tabu@everycr}% + \let\everycr \tabu@noeverycr % <for ialign> +}% \tabu@seteverycr +\def\tabu@noeverycr{{\aftergroup\tabu@restoreeverycr \afterassignment}\toks@} +\def\tabu@restoreeverycr {\let\everycr \tabu@@everycr} +\def\tabu@everycr {\iftabu@everyrow \noalign{\tabu@everyrow}\fi} +\def\tabu@endoftrials {% + \iftabuscantokens \expandafter\@firstoftwo + \else \expandafter\@secondoftwo + \fi + {\expandafter \tabu@closetrialsgroup \expandafter + \tabu@rescan \expandafter{% + \expandafter\tabuthepreamble + \the\expandafter\tabu@thebody + \iftabu@long \else \endarray \fi}} + {\expandafter\tabu@closetrialsgroup \expandafter + \tabuthepreamble + \the\tabu@thebody}% + \tabu@endenvir % Finish ! +}% \tabu@endoftrials +\def\tabu@closetrialsgroup {% + \toks@\expandafter{\tabu@endenvir}% + \edef\tabu@bufferX{\endgroup + \tabucolX \the\tabucolX + \tabu@target \the\tabu@target + \tabu@cnt \the\tabu@cnt + \def\noexpand\tabu@endenvir{\the\toks@}% + %Quid de \@halignto = \tabu@halignto ?? + }% \tabu@bufferX + \tabu@bufferX + \ifcase\tabu@nested % print out (outer in mode 0) + \global\tabu@cnt \tabu@cnt + \tabu@evr{\tabu@verticaldynamicadjustment}% + \tabu@celllalign@def{\everypar{}}\let\tabu@cellralign \@empty + \let\@finalstrut \tabu@finalstrut + \else % vertical measure of nested tabu + \tabu@evr{\tabu@verticalinit}% + \tabu@celllalign@def{\tabu@verticalmeasure}% + \def\tabu@cellralign{\tabu@verticalspacing}% + \fi + \tabu@clckpt \let\@halignto \tabu@halignto + \let\@halignto \@empty + \tabu@seteverycr + \ifdim \tabustrutrule>\z@ \ifnum\tabu@nested=\z@ + \setbox\@arstrutbox \box\voidb@x % force \@arstrutbox to be rebuilt (visible struts) + \fi\fi +}% \tabu@closetrialsgroup +\def\tabu@quickend {\expandafter \endgroup \expandafter + \tabu@target \the\tabu@target \tabu@quickrule + \let\endarray \relax \tabu@endenvir +}% \tabu@quickend +\def\tabu@endtrial {\relax % stops \count@ assignment ! + \ifcase \count@ \tabu@err % case 0 = impossible here + \or \tabu@err % case 1 = impossible here + \or \tabu@err % case 2 = impossible here + \or % case 3 = outer goes into mode 0 + \def\tabu@bufferX{\endgroup}\count@ \z@ + \else % case 4 = outer goes into mode 3 + \iftabu@spread \tabu@spreadarith % inner into mode 1 (outer in mode 3) + \else \tabu@arith % or 2 (outer in mode 4) + \fi + \count@=% + \ifcase\tabu@nested \thr@@ % outer goes into mode 3 + \else\iftabu@measuring \tw@ % outer is in mode 4 + \else \@ne % outer is in mode 3 + \fi\fi + \edef\tabu@bufferX{\endgroup + \tabucolX \the\tabucolX + \tabu@target \the\tabu@target}% + \fi + \expandafter \tabu@bufferX \expandafter + \count@ \the\count@ \tabu@strategy +}% \tabu@endtrial +\def\tabu@err{\errmessage{(tabu) Internal impossible error! (\count@=\the\count@)}} +%% The algorithms: compute the widths / stop or go on --------------- +\def\tabu@arithnegcoef {% + \@tempdima \z@ \dimen@ \z@ \let\tabu@ \tabu@arith@negcoef \tabu@Xcoefs +}% \tabu@arithnegcoef +\def\tabu@arith@negcoef #1#2{% + \ifdim #2\p@>\z@ \advance\dimen@ #2\p@ % saturated by definition + \advance\@tempdima #2\tabucolX + \else + \ifdim -#2\tabucolX <\tabu@wd{#1}% c_i X < natural width <= \tabu@target-> saturated + \advance\dimen@ -#2\p@ + \advance\@tempdima -#2\tabucolX + \else + \advance\@tempdima \tabu@wd{#1}% natural width <= c_i X => neutralised + \ifdim \tabu@wd{#1}<\tabu@target \else % neutralised + \advance\dimen@ -#2\p@ % saturated (natural width = tabu@target) + \fi + \fi + \fi +}% \tabu@arith@negcoef +\def\tabu@givespace #1#2{% here \tabu@DELTA < \z@ + \ifdim \@tempdima=\z@ + \tabu@wddef{#1}{\the\dimexpr -\tabu@DELTA*\p@/\tabu@Xsum}% + \else + \tabu@wddef{#1}{\the\dimexpr \tabu@hsize{#1}{#2} + *(\p@ -\tabu@DELTA*\p@/\@tempdima)/\p@\relax}% + \fi +}% \tabu@givespace +\def\tabu@arith {\advance\tabu@cnt \@ne + \ifnum \tabu@cnt=\@ne \tabu@message{\tabu@titles}\fi + \tabu@arithnegcoef + \@tempdimb \dimexpr \wd\tabu@box -\@tempdima \relax % <incompressible material> + \tabu@DELTA = \dimexpr \wd\tabu@box - \tabu@target \relax + \tabu@message{\tabu@message@arith}% + \ifdim \tabu@DELTA <\tabu@hfuzz + \ifdim \tabu@DELTA<\z@ % wd (tabu)<\tabu@target ? + \let\tabu@ \tabu@givespace \tabu@Xcoefs + \advance\@tempdima \@tempdimb \advance\@tempdima -\tabu@DELTA % for message + \else % already converged: nothing to do but nearly impossible... + \fi + \tabucolX \maxdimen + \tabu@measuringfalse + \else % need for narrower X columns + \tabucolX =\dimexpr (\@tempdima -\tabu@DELTA) *\p@/\tabu@Xsum \relax + \tabu@measuringtrue + \@whilesw \iftabu@measuring\fi {% + \advance\tabu@cnt \@ne + \tabu@arithnegcoef + \tabu@DELTA =\dimexpr \@tempdima+\@tempdimb -\tabu@target \relax % always < 0 here + \tabu@message{\tabu@header + \tabu@msgalign \tabucolX { }{ }{ }{ }{ }\@@ + \tabu@msgalign \@tempdima+\@tempdimb { }{ }{ }{ }{ }\@@ + \tabu@msgalign \tabu@target { }{ }{ }{ }{ }\@@ + \tabu@msgalign@PT \dimen@ { }{}{}{}{}{}{}\@@ + \ifdim -\tabu@DELTA<\tabu@hfuzz \tabu@spaces target ok\else + \tabu@msgalign \dimexpr -\tabu@DELTA *\p@/\dimen@ {}{}{}{}{}\@@ + \fi}% + \ifdim -\tabu@DELTA<\tabu@hfuzz + \advance\@tempdima \@tempdimb % for message + \tabu@measuringfalse + \else + \advance\tabucolX \dimexpr -\tabu@DELTA *\p@/\dimen@ \relax + \fi + }% + \fi + \tabu@message{\tabu@message@reached}% + \edef\tabu@bufferX{\endgroup \tabu@cnt \the\tabu@cnt + \tabucolX \the\tabucolX + \tabu@target \the\tabu@target}% +}% \tabu@arith +\def\tabu@spreadarith {% + \dimen@ \z@ \@tempdima \tabu@naturalXmax \let\tabu@ \tabu@spread@arith \tabu@Xcoefs + \edef\tabu@naturalXmin {\the\dimexpr\tabu@naturalXmin*\dimen@/\p@}% + \@tempdimc =\dimexpr \wd\tabu@box -\tabu@naturalXmax+\tabu@naturalXmin \relax + \iftabu@measuring + \tabu@target =\dimexpr \@tempdimc+\tabu@spreadtarget \relax + \edef\tabu@bufferX{\endgroup \tabucolX \the\tabucolX \tabu@target\the\tabu@target}% + \else + \tabu@message{\tabu@message@spreadarith}% + \ifdim \dimexpr \@tempdimc+\tabu@spreadtarget >\tabu@target + \tabu@message{(tabu) spread + \ifdim \@tempdimc>\tabu@target useless here: default target used% + \else too large: reduced to fit default target\fi.}% + \else + \tabu@target =\dimexpr \@tempdimc+\tabu@spreadtarget \relax + \tabu@message{(tabu) spread: New target set to \the\tabu@target^^J}% + \fi + \begingroup \let\tabu@wddef \@gobbletwo + \@tempdimb \@tempdima + \tabucolX@init + \tabu@arithnegcoef + \wd\tabu@box =\dimexpr \wd\tabu@box +\@tempdima-\@tempdimb \relax + \expandafter\endgroup \expandafter\tabucolX \the\tabucolX + \tabu@arith + \fi +}% \tabu@spreadarith +\def\tabu@spread@arith #1#2{% + \ifdim #2\p@>\z@ \advance\dimen@ #2\p@ + \else \advance\@tempdima \tabu@wd{#1}\relax + \fi +}% \tabu@spread@arith +%% Reporting in the .log file --------------------------------------- +\def\tabu@message@defaulttarget{% + \ifnum\tabu@nested=\z@^^J(tabu) Default target: + \ifx\tabudefaulttarget\linewidth \string\linewidth + \ifdim \tabu@thetarget=\linewidth \else + -\the\dimexpr\linewidth-\tabu@thetarget\fi = + \else\ifx\tabudefaulttarget\linegoal\string\linegoal= + \fi\fi + \else (tabu) Default target (nested): \fi + \the\tabu@target \on@line + \ifnum\tabu@nested=\z@ , page \the\c@page\fi} +\def\tabu@message@target {^^J(tabu) Target specified: + \the\tabu@target \on@line, page \the\c@page} +\def\tabu@message@arith {\tabu@header + \tabu@msgalign \tabucolX { }{ }{ }{ }{ }\@@ + \tabu@msgalign \wd\tabu@box { }{ }{ }{ }{ }\@@ + \tabu@msgalign \tabu@target { }{ }{ }{ }{ }\@@ + \tabu@msgalign@PT \dimen@ { }{}{}{}{}{}{}\@@ + \ifdim \tabu@DELTA<\tabu@hfuzz giving space\else + \tabu@msgalign \dimexpr (\@tempdima-\tabu@DELTA) *\p@/\tabu@Xsum -\tabucolX {}{}{}{}{}\@@ + \fi +}% \tabu@message@arith +\def\tabu@message@spreadarith {\tabu@spreadheader + \tabu@msgalign \tabu@spreadtarget { }{ }{ }{ }{}\@@ + \tabu@msgalign \wd\tabu@box { }{ }{ }{ }{}\@@ + \tabu@msgalign -\tabu@naturalXmax { }{}{}{}{}\@@ + \tabu@msgalign \tabu@naturalXmin { }{ }{ }{ }{}\@@ + \tabu@msgalign \ifdim \dimexpr\@tempdimc>\tabu@target \tabu@target + \else \@tempdimc+\tabu@spreadtarget \fi + {}{}{}{}{}\@@} +\def\tabu@message@negcoef #1#2{ + \tabu@spaces\tabu@spaces\space * #1. X[\rem@pt#2]: + \space width = \tabu@wd {#1} + \expandafter\string\csname tabu@\the\tabu@nested.W\number#1\endcsname + \ifdim -\tabu@pt#2\tabucolX<\tabu@target + < \number-\rem@pt#2 X + = \the\dimexpr -\tabu@pt#2\tabucolX \relax + \else + <= \the\tabu@target\space < \number-\rem@pt#2 X\fi} +\def\tabu@message@reached{\tabu@header + ******* Reached Target: + hfuzz = \tabu@hfuzz\on@line\space *******} +\def\tabu@message@etime{\edef\tabu@stoptime{\the\pdfelapsedtime}% + \tabu@message{(tabu)\tabu@spaces Time elapsed during measure: + \the\numexpr(\tabu@stoptime-\tabu@starttime-32767)/65536\relax sec + \the\numexpr\numexpr(\tabu@stoptime-\tabu@starttime) + -\numexpr(\tabu@stoptime-\tabu@starttime-32767)/65536\relax*65536\relax + *1000/65536\relax ms \tabu@spaces(\the\tabu@cnt\space + cycle\ifnum\tabu@cnt>\@ne s\fi)^^J^^J}} +\def\tabu@message@verticalsp {% + \ifdim \@tempdima>\tabu@ht + \ifdim \@tempdimb>\tabu@dp + \expandafter\expandafter\expandafter\string\tabu@ht = + \tabu@msgalign \@tempdima { }{ }{ }{ }{ }\@@ + \expandafter\expandafter\expandafter\string\tabu@dp = + \tabu@msgalign \@tempdimb { }{ }{ }{ }{ }\@@^^J% + \else + \expandafter\expandafter\expandafter\string\tabu@ht = + \tabu@msgalign \@tempdima { }{ }{ }{ }{ }\@@^^J% + \fi + \else\ifdim \@tempdimb>\tabu@dp + \tabu@spaces\tabu@spaces\tabu@spaces + \expandafter\expandafter\expandafter\string\tabu@dp = + \tabu@msgalign \@tempdimb { }{ }{ }{ }{ }\@@^^J\fi + \fi +}% \tabu@message@verticalsp +\edef\tabu@spaces{\@spaces} +\def\tabu@strippt{\expandafter\tabu@pt\the} +{\@makeother\P \@makeother\T\lowercase{\gdef\tabu@pt #1PT{#1}}} +\def\tabu@msgalign{\expandafter\tabu@msg@align\the\dimexpr} +\def\tabu@msgalign@PT{\expandafter\tabu@msg@align\romannumeral-`\0\tabu@strippt} +\def\do #1{% + \def\tabu@msg@align##1.##2##3##4##5##6##7##8##9\@@{% + \ifnum##1<10 #1 #1\else + \ifnum##1<100 #1 \else + \ifnum##1<\@m #1\fi\fi\fi + ##1.##2##3##4##5##6##7##8#1}% + \def\tabu@header{(tabu) \ifnum\tabu@cnt<10 #1\fi\the\tabu@cnt) }% + \def\tabu@titles{\ifnum \tabu@nested=\z@ + (tabu) Try#1 #1 tabu X #1 #1 #1tabu Width #1 #1 Target + #1 #1 #1 Coefs #1 #1 #1 Update^^J\fi}% + \def\tabu@spreadheader{% + (tabu) Try#1 #1 Spread #1 #1 tabu Width #1 #1 #1 Nat. X #1 #1 #1 #1Nat. Min. + #1 New Target^^J% + (tabu) sprd} + \def\tabu@message@save {\begingroup + \def\x ####1{\tabu@msg@align ####1{ }{ }{ }{ }{}\@@} + \def\z ####1{\expandafter\x\expandafter{\romannumeral-`\0\tabu@strippt + \dimexpr####1\p@{ }{ }}}% + \let\color \relax \def\tabu@rulesstyle ####1####2{\detokenize{####1}}% + \let\CT@arc@ \relax \let\@preamble \@gobble + \let\tabu@savedpream \@firstofone + \let\tabu@savedparams \@firstofone + \def\tabu@target ####1\relax {(tabu) target #1 #1 #1 #1 #1 = \x{####1}^^J}% + \def\tabucolX ####1\relax {(tabu) X columns width#1 = \x{####1}^^J}% + \def\tabu@nbcols ####1\relax {(tabu) Number of columns: \z{####1}^^J}% + \def\tabu@aligndefault ####1{(tabu) Default alignment: #1 #1 ####1^^J}% + \def\col@sep ####1\relax {(tabu) column sep #1 #1 #1 = \x{####1}^^J}% + \def\arrayrulewidth ####1\relax{(tabu) arrayrulewidth #1 = \x{####1}}% + \def\doublerulesep ####1\relax { doublerulesep = \x{####1}^^J}% + \def\extratabsurround####1\relax{(tabu) extratabsurround = \x{####1}^^J}% + \def\extrarowheight ####1\relax{(tabu) extrarowheight #1 = \x{####1}}% + \def\extrarowdepth ####1\relax {extrarowdepth = \x{####1}^^J}% + \def\abovetabulinesep####1\relax{(tabu) abovetabulinesep=\x{####1} }% + \def\belowtabulinesep####1\relax{ belowtabulinesep=\x{####1}^^J}% + \def\arraystretch ####1{(tabu) arraystretch #1 #1 = \z{####1}^^J}% + \def\minrowclearance####1\relax{(tabu) minrowclearance #1 = \x{####1}^^J}% + \def\tabu@arc@L ####1{(tabu) taburulecolor #1 #1 = ####1^^J}% + \def\tabu@drsc@L ####1{(tabu) tabudoublerulecolor= ####1^^J}% + \def\tabu@evr@L ####1{(tabu) everyrow #1 #1 #1 #1 = \detokenize{####1}^^J}% + \def\tabu@ls@L ####1{(tabu) line style = \detokenize{####1}^^J}% + \def\NC@find ####1\@nil{(tabu) tabu preamble#1 #1 = \detokenize{####1}^^J}% + \def\tabu@wddef####1####2{(tabu) Natural width ####1 = \x{####2}^^J}% + \let\edef \@gobbletwo \let\def \@empty \let\let \@gobbletwo + \tabu@message{% + (tabu) \string\savetabu{\tabu@temp}: \on@line^^J% + \tabu@usetabu \@nil^^J}% + \endgroup} +}\do{ } +%% Measuring the natural width (varwidth) - store the results ------- +\def\tabu@startpboxmeasure #1{\bgroup % entering \vtop + \edef\tabu@temp{\expandafter\@secondoftwo \ifx\tabu@hsize #1\else\relax\fi}% + \ifodd 1\ifx \tabu@temp\@empty 0 \else % starts with \tabu@hsize ? + \iftabu@spread \else % if spread -> measure + \ifdim \tabu@temp\p@>\z@ 0 \fi\fi\fi% if coef>0 -> do not measure + \let\@startpbox \tabu@startpboxORI % restore immediately (nesting) + \tabu@measuringtrue % for the quick option... + \tabu@Xcol =\expandafter\@firstoftwo\ifx\tabu@hsize #1\fi + \ifdim \tabu@temp\p@>\z@ \ifdim \tabu@temp\tabucolX<\tabu@target + \tabu@target=\tabu@temp\tabucolX \fi\fi + \setbox\tabu@box \hbox \bgroup + \begin{varwidth}\tabu@target + \let\FV@ListProcessLine \tabu@FV@ListProcessLine % \hbox to natural width... + \narrowragged \arraybackslash \parfillskip \@flushglue + \ifdefined\pdfadjustspacing \pdfadjustspacing\z@ \fi + \bgroup \aftergroup\tabu@endpboxmeasure + \ifdefined \cellspacetoplimit \tabu@cellspacepatch \fi + \else \expandafter\@gobble + \tabu@startpboxquick{#1}% \@gobble \bgroup + \fi +}% \tabu@startpboxmeasure +\def\tabu@cellspacepatch{\def\bcolumn##1\@nil{}\let\ecolumn\@empty + \bgroup\color@begingroup} +\def\tabu@endpboxmeasure {% + \@finalstrut \@arstrutbox + \end{varwidth}\egroup % <got my \tabu@box> + \ifdim \tabu@temp\p@ <\z@ % neg coef + \ifdim \tabu@wd\tabu@Xcol <\wd\tabu@box + \tabu@wddef\tabu@Xcol {\the\wd\tabu@box}% + \tabu@debug{\tabu@message@endpboxmeasure}% + \fi + \else % spread coef>0 + \global\advance \tabu@naturalX \wd\tabu@box + \@tempdima =\dimexpr \wd\tabu@box *\p@/\dimexpr \tabu@temp\p@\relax \relax + \ifdim \tabu@naturalXmax <\tabu@naturalX + \xdef\tabu@naturalXmax {\the\tabu@naturalX}\fi + \ifdim \tabu@naturalXmin <\@tempdima + \xdef\tabu@naturalXmin {\the\@tempdima}\fi + \fi + \box\tabu@box \egroup % end of \vtop (measure) restore \tabu@target +}% \tabu@endpboxmeasure +\def\tabu@wddef #1{\expandafter\xdef + \csname tabu@\the\tabu@nested.W\number#1\endcsname} +\def\tabu@wd #1{\csname tabu@\the\tabu@nested.W\number#1\endcsname} +\def\tabu@message@endpboxmeasure{\tabu@spaces\tabu@spaces<-> % <-> save natural wd + \the\tabu@Xcol. X[\tabu@temp]: + target = \the\tabucolX \space + \expandafter\expandafter\expandafter\string\tabu@wd\tabu@Xcol + =\tabu@wd\tabu@Xcol +}% \tabu@message@endpboxmeasure +\def\tabu@startpboxquick {\bgroup + \let\@startpbox \tabu@startpboxORI % restore immediately + \let\tabu \tabu@quick % \begin is expanded before... + \expandafter\@gobble \@startpbox % gobbles \bgroup +}% \tabu@startpboxquick +\def\tabu@quick {\begingroup \iffalse{\fi \ifnum0=`}\fi + \toks@{}\def\tabu@stack{b}\tabu@collectbody \tabu@endquick +}% \tabu@quick +\def\tabu@endquick {% + \ifodd 1\ifx\tabu@end@envir\tabu@endtabu \else + \ifx\tabu@end@envir\tabu@endtabus \else 0\fi\fi\relax + \endgroup + \else \let\endtabu \relax + \tabu@end@envir + \fi +}% \tabu@quick +\def\tabu@endtabu {\end{tabu}} +\def\tabu@endtabus {\end{tabu*}} +%% Measuring the heights and depths - store the results ------------- +\def\tabu@verticalmeasure{\everypar{}% + \ifnum \currentgrouptype>12 % 14=semi-simple, 15=math shift group + \setbox\tabu@box =\hbox\bgroup + \let\tabu@verticalspacing \tabu@verticalsp@lcr + \d@llarbegin % after \hbox ... + \else + \edef\tabu@temp{\ifnum\currentgrouptype=5\vtop + \else\ifnum\currentgrouptype=12\vcenter + \else\vbox\fi\fi}% + \setbox\tabu@box \hbox\bgroup$\tabu@temp \bgroup + \let\tabu@verticalspacing \tabu@verticalsp@pmb + \fi +}% \tabu@verticalmeasure +\def\tabu@verticalsp@lcr{% + \d@llarend \egroup % <got my \tabu@box> + \@tempdima \dimexpr \ht\tabu@box+\abovetabulinesep + \@tempdimb \dimexpr \dp\tabu@box+\belowtabulinesep \relax + \ifdim\tabustrutrule>\z@ \tabu@debug{\tabu@message@verticalsp}\fi + \ifdim \tabu@ht<\@tempdima \tabu@htdef{\the\@tempdima}\fi + \ifdim \tabu@dp<\@tempdimb \tabu@dpdef{\the\@tempdimb}\fi + \noindent\vrule height\@tempdima depth\@tempdimb +}% \tabu@verticalsp@lcr +\def\tabu@verticalsp@pmb{% inserts struts as needed + \par \expandafter\egroup + \expandafter$\expandafter + \egroup \expandafter + \@tempdimc \the\prevdepth + \@tempdima \dimexpr \ht\tabu@box+\abovetabulinesep + \@tempdimb \dimexpr \dp\tabu@box+\belowtabulinesep \relax + \ifdim\tabustrutrule>\z@ \tabu@debug{\tabu@message@verticalsp}\fi + \ifdim \tabu@ht<\@tempdima \tabu@htdef{\the\@tempdima}\fi + \ifdim \tabu@dp<\@tempdimb \tabu@dpdef{\the\@tempdimb}\fi + \let\@finalstrut \@gobble + \hrule height\@tempdima depth\@tempdimb width\hsize +%% \box\tabu@box +}% \tabu@verticalsp@pmb + +\def\tabu@verticalinit{% + \ifnum \c@taburow=\z@ \tabu@rearstrut \fi % after \tabu@reset ! + \advance\c@taburow \@ne + \tabu@htdef{\the\ht\@arstrutbox}\tabu@dpdef{\the\dp\@arstrutbox}% + \advance\c@taburow \m@ne +}% \tabu@verticalinit +\def\tabu@htdef {\expandafter\xdef \csname tabu@\the\tabu@nested.H\the\c@taburow\endcsname} +\def\tabu@ht {\csname tabu@\the\tabu@nested.H\the\c@taburow\endcsname} +\def\tabu@dpdef {\expandafter\xdef \csname tabu@\the\tabu@nested.D\the\c@taburow\endcsname} +\def\tabu@dp {\csname tabu@\the\tabu@nested.D\the\c@taburow\endcsname} +\def\tabu@verticaldynamicadjustment {% + \advance\c@taburow \@ne + \extrarowheight \dimexpr\tabu@ht - \ht\strutbox + \extrarowdepth \dimexpr\tabu@dp - \dp\strutbox + \let\arraystretch \@empty + \advance\c@taburow \m@ne +}% \tabu@verticaldynamicadjustment +\def\tabuphantomline{\crcr \noalign{% + {\globaldefs \@ne + \setbox\@arstrutbox \box\voidb@x + \let\tabu@@celllalign \tabu@celllalign + \let\tabu@@cellralign \tabu@cellralign + \let\tabu@@cellleft \tabu@cellleft + \let\tabu@@cellright \tabu@cellright + \let\tabu@@thevline \tabu@thevline + \let\tabu@celllalign \@empty + \let\tabu@cellralign \@empty + \let\tabu@cellright \@empty + \let\tabu@cellleft \@empty + \let\tabu@thevline \relax}% + \edef\tabu@temp{\tabu@multispan \tabu@nbcols{\noindent &}}% + \toks@\expandafter{\tabu@temp \noindent\tabu@everyrowfalse \cr + \noalign{\tabu@rearstrut + {\globaldefs\@ne + \let\tabu@celllalign \tabu@@celllalign + \let\tabu@cellralign \tabu@@cellralign + \let\tabu@cellleft \tabu@@cellleft + \let\tabu@cellright \tabu@@cellright + \let\tabu@thevline \tabu@@thevline}}}% + \expandafter}\the\toks@ +}% \tabuphantomline +%% \firsthline and \lasthline corrections --------------------------- +\def\tabu@firstline {\tabu@hlineAZ \tabu@firsthlinecorrection {}} +\def\tabu@firsthline{\tabu@hlineAZ \tabu@firsthlinecorrection \hline} +\def\tabu@lastline {\tabu@hlineAZ \tabu@lasthlinecorrection {}} +\def\tabu@lasthline {\tabu@hlineAZ \tabu@lasthlinecorrection \hline} +\def\tabu@hline {% replaces \hline if no colortbl (see \AtBeginDocument) + \noalign{\ifnum0=`}\fi + {\CT@arc@\hrule height\arrayrulewidth}% + \futurelet \tabu@temp \tabu@xhline +}% \tabu@hline +\def\tabu@xhline{% + \ifx \tabu@temp \hline + {\ifx \CT@drsc@\relax \vskip + \else\ifx \CT@drsc@\@empty \vskip + \else \CT@drsc@\hrule height + \fi\fi + \doublerulesep}% + \fi + \ifnum0=`{\fi}% +}% \tabu@xhline +\def\tabu@hlineAZ #1#2{\noalign{\ifnum0=`}\fi \dimen@ \z@ \count@ \z@ + \toks@{}\def\tabu@hlinecorrection{#1}\def\tabu@temp{#2}% + \tabu@hlineAZsurround +}% \tabu@hlineAZ +\newcommand*\tabu@hlineAZsurround[1][\extratabsurround]{% + \extratabsurround #1\let\tabucline \tabucline@scan + \let\hline \tabu@hlinescan \let\firsthline \hline + \let\cline \tabu@clinescan \let\lasthline \hline + \expandafter \futurelet \expandafter \tabu@temp + \expandafter \tabu@nexthlineAZ \tabu@temp +}% \tabu@hlineAZsurround +\def\tabu@hlinescan {\tabu@thick \arrayrulewidth \tabu@xhlineAZ \hline} +\def\tabu@clinescan #1{\tabu@thick \arrayrulewidth \tabu@xhlineAZ {\cline{#1}}} +\def\tabucline@scan{\@testopt \tabucline@sc@n {}} +\def\tabucline@sc@n #1[#2]{\tabu@xhlineAZ {\tabucline[{#1}]{#2}}} +\def\tabu@nexthlineAZ{% + \ifx \tabu@temp\hline \else + \ifx \tabu@temp\cline \else + \ifx \tabu@temp\tabucline \else + \tabu@hlinecorrection + \fi\fi\fi +}% \tabu@nexthlineAZ +\def\tabu@xhlineAZ #1{% + \toks@\expandafter{\the\toks@ #1}% + \@tempdimc \tabu@thick % The last line width + \ifcase\count@ \@tempdimb \tabu@thick % The first line width + \else \advance\dimen@ \dimexpr \tabu@thick+\doublerulesep \relax + \fi + \advance\count@ \@ne \futurelet \tabu@temp \tabu@nexthlineAZ +}% \tabu@xhlineAZ +\def\tabu@firsthlinecorrection{% \count@ = number of \hline -1 + \@tempdima \dimexpr \ht\@arstrutbox+\dimen@ + \edef\firsthline{% <local in \noalign> + \omit \hbox to\z@{\hss{\noexpand\tabu@DBG{yellow}\vrule + height \the\dimexpr\@tempdima+\extratabsurround + depth \dp\@arstrutbox + width \tabustrutrule}\hss}\cr + \noalign{\vskip -\the\dimexpr \@tempdima+\@tempdimb + +\dp\@arstrutbox \relax}% + \the\toks@ + }\ifnum0=`{\fi + \expandafter}\firsthline % we are then ! +}% \tabu@firsthlinecorrection +\def\tabu@lasthlinecorrection{% + \@tempdima \dimexpr \dp\@arstrutbox+\dimen@+\@tempdimb+\@tempdimc + \edef\lasthline{% <local in \noalign> + \the\toks@ + \noalign{\vskip -\the\dimexpr\dimen@+\@tempdimb+\dp\@arstrutbox}% + \omit \hbox to\z@{\hss{\noexpand\tabu@DBG{yellow}\vrule + depth \the\dimexpr \dp\@arstrutbox+\@tempdimb+\dimen@ + +\extratabsurround-\@tempdimc + height \z@ + width \tabustrutrule}\hss}\cr + }\ifnum0=`{\fi + \expandafter}\lasthline % we are then ! +}% \tabu@lasthlinecorrection +\def\tabu@LT@@hline{% + \ifx\LT@next\hline + \global\let\LT@next \@gobble + \ifx \CT@drsc@\relax + \gdef\CT@LT@sep{% + \noalign{\penalty-\@medpenalty\vskip\doublerulesep}}% + \else + \gdef\CT@LT@sep{% + \multispan\LT@cols{% + \CT@drsc@\leaders\hrule\@height\doublerulesep\hfill}\cr}% + \fi + \else + \global\let\LT@next\empty + \gdef\CT@LT@sep{% + \noalign{\penalty-\@lowpenalty\vskip-\arrayrulewidth}}% + \fi + \ifnum0=`{\fi}% + \multispan\LT@cols + {\CT@arc@\leaders\hrule\@height\arrayrulewidth\hfill}\cr + \CT@LT@sep + \multispan\LT@cols + {\CT@arc@\leaders\hrule\@height\arrayrulewidth\hfill}\cr + \noalign{\penalty\@M}% + \LT@next +}% \tabu@LT@@hline +%% Horizontal lines : \tabucline ------------------------------------ +\let\tabu@start \@tempcnta +\let\tabu@stop \@tempcntb +\newcommand*\tabucline{\noalign{\ifnum0=`}\fi \tabu@cline} +\newcommand*\tabu@cline[2][]{\tabu@startstop{#2}% + \ifnum \tabu@stop<\z@ \toks@{}% + \else \tabu@clinearg{#1}\tabu@thestyle + \edef\tabucline{\toks@{% + \ifnum \tabu@start>\z@ \omit + \tabu@multispan\tabu@start {\span\omit}&\fi + \omit \tabu@multispan\tabu@stop {\span\omit}% + \tabu@thehline\cr + }}\tabucline + \tabu@tracinglines{(tabu:tabucline) Style: #1^^J\the\toks@^^J^^J}% + \fi + \futurelet \tabu@temp \tabu@xcline +}% \tabu@cline +\def\tabu@clinearg #1{% + \ifx\\#1\\\let\tabu@thestyle \tabu@ls@ + \else \@defaultunits \expandafter\let\expandafter\@tempa + \romannumeral-`\0#1\relax \@nnil + \ifx \hbox\@tempa \tabu@clinebox{#1}% + \else\ifx \box\@tempa \tabu@clinebox{#1}% + \else\ifx \vbox\@tempa \tabu@clinebox{#1}% + \else\ifx \vtop\@tempa \tabu@clinebox{#1}% + \else\ifx \copy\@tempa \tabu@clinebox{#1}% + \else\ifx \leaders\@tempa \tabu@clineleads{#1}% + \else\ifx \cleaders\@tempa \tabu@clineleads{#1}% + \else\ifx \xleaders\@tempa \tabu@clineleads{#1}% + \else\tabu@getline {#1}% + \fi\fi\fi\fi\fi\fi\fi\fi + \fi +}% \tabu@clinearg +\def\tabu@clinebox #1{\tabu@clineleads{\xleaders#1\hss}} +\def\tabu@clineleads #1{% + \let\tabu@thestyle \relax \let\tabu@leaders \@undefined + \gdef\tabu@thehrule{#1}} +\def\tabu@thehline{\begingroup + \ifdefined\tabu@leaders + \noexpand\tabu@thehleaders + \else \noexpand\tabu@thehrule + \fi \endgroup +}% \tabu@thehline +\def\tabu@xcline{% + \ifx \tabu@temp\tabucline + \toks@\expandafter{\the\toks@ \noalign + {\ifx\CT@drsc@\relax \vskip + \else \CT@drsc@\hrule height + \fi + \doublerulesep}}% + \fi + \tabu@docline +}% \tabu@xcline +\def\tabu@docline {\ifnum0=`{\fi \expandafter}\the\toks@} +\def\tabu@docline@evr {\xdef\tabu@doclineafter{\the\toks@}% + \ifnum0=`{\fi}\aftergroup\tabu@doclineafter} +\def\tabu@multispan #1#2{% + \ifnum\numexpr#1>\@ne #2\expandafter\tabu@multispan + \else \expandafter\@gobbletwo + \fi {#1-1}{#2}% +}% \tabu@multispan +\def\tabu@startstop #1{\tabu@start@stop #1\relax 1-\tabu@nbcols \@nnil} +\def\tabu@start@stop #1-#2\@nnil{% + \@defaultunits \tabu@start\number 0#1\relax \@nnil + \@defaultunits \tabu@stop \number 0#2\relax \@nnil + \tabu@stop \ifnum \tabu@start>\tabu@nbcols \m@ne + \else\ifnum \tabu@stop=\z@ \tabu@nbcols + \else\ifnum \tabu@stop>\tabu@nbcols \tabu@nbcols + \else \tabu@stop + \fi\fi\fi + \advance\tabu@start \m@ne + \ifnum \tabu@start>\z@ \advance\tabu@stop -\tabu@start \fi +}% \tabu@start@stop +%% Numbers: siunitx S columns (and \tabudecimal) ------------------- +\def\tabu@tabudecimal #1{% + \def\tabu@decimal{#1}\@temptokena{}% + \let\tabu@getdecimal@ \tabu@getdecimal@ignorespaces + \tabu@scandecimal +}% \tabu@tabudecimal +\def\tabu@scandecimal{\futurelet \tabu@temp \tabu@getdecimal@} +\def\tabu@skipdecimal#1{#1\tabu@scandecimal} +\def\tabu@getdecimal@ignorespaces{% + \ifcase 0\ifx\tabu@temp\ignorespaces\else + \ifx\tabu@temp\@sptoken1\else + 2\fi\fi\relax + \let\tabu@getdecimal@ \tabu@getdecimal + \expandafter\tabu@skipdecimal + \or \expandafter\tabu@gobblespace\expandafter\tabu@scandecimal + \else \expandafter\tabu@skipdecimal + \fi +}% \tabu@getdecimal@ignorespaces +\def\tabu@get@decimal#1{\@temptokena\expandafter{\the\@temptokena #1}% + \tabu@scandecimal} +\def\do#1{% + \def\tabu@get@decimalspace#1{% + \@temptokena\expandafter{\the\@temptokena #1}\tabu@scandecimal}% +}\do{ } +\let\tabu@@tabudecimal \tabu@tabudecimal +\def\tabu@getdecimal{% + \ifcase 0\ifx 0\tabu@temp\else + \ifx 1\tabu@temp\else + \ifx 2\tabu@temp\else + \ifx 3\tabu@temp\else + \ifx 4\tabu@temp\else + \ifx 5\tabu@temp\else + \ifx 6\tabu@temp\else + \ifx 7\tabu@temp\else + \ifx 8\tabu@temp\else + \ifx 9\tabu@temp\else + \ifx .\tabu@temp\else + \ifx ,\tabu@temp\else + \ifx -\tabu@temp\else + \ifx +\tabu@temp\else + \ifx e\tabu@temp\else + \ifx E\tabu@temp\else + \ifx\tabu@cellleft\tabu@temp1\else + \ifx\ignorespaces\tabu@temp1\else + \ifx\@sptoken\tabu@temp2\else + 3\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\fi\relax + \expandafter\tabu@get@decimal + \or \expandafter\tabu@skipdecimal + \or \expandafter\tabu@get@decimalspace + \else\expandafter\tabu@printdecimal + \fi +}% \tabu@getdecimal +\def\tabu@printdecimal{% + \edef\tabu@temp{\the\@temptokena}% + \ifx\tabu@temp\@empty\else + \ifx\tabu@temp\space\else + \expandafter\tabu@decimal\expandafter{\the\@temptokena}% + \fi\fi +}% \tabu@printdecimal +%% Verbatim inside X columns ---------------------------------------- +\def\tabu@verbatim{% + \let\verb \tabu@verb + \let\FV@DefineCheckEnd \tabu@FV@DefineCheckEnd +}% \tabu@verbatim +\let\tabu@ltx@verb \verb +\def\tabu@verb{\@ifstar {\tabu@ltx@verb*} \tabu@ltx@verb} +\def\tabu@fancyvrb {% + \def\tabu@FV@DefineCheckEnd ##1{% + \def\tabu@FV@DefineCheckEnd{% + ##1% <original definition (if fancyvrb is loaded)> + \let\FV@CheckEnd \tabu@FV@CheckEnd + \let\FV@@CheckEnd \tabu@FV@@CheckEnd + \let\FV@@@CheckEnd \tabu@FV@@@CheckEnd + \edef\FV@EndScanning{% + \def\noexpand\next{\noexpand\end{\FV@EnvironName}}% + \global\let\noexpand\FV@EnvironName\relax + \noexpand\next}% + \xdef\FV@EnvironName{\detokenize\expandafter{\FV@EnvironName}}}% + }\expandafter\tabu@FV@DefineCheckEnd\expandafter{\FV@DefineCheckEnd} +}% \tabu@fancyvrb +\def\tabu@FV@CheckEnd #1{\expandafter\FV@@CheckEnd \detokenize{#1\end{}}\@nil} +\edef\tabu@FV@@@CheckEnd {\detokenize{\end{}}} +\begingroup +\catcode`\[1 \catcode`\]2 +\@makeother\{ \@makeother\} + \edef\x[\endgroup + \def\noexpand\tabu@FV@@CheckEnd ##1\detokenize[\end{]##2\detokenize[}]##3% + ]\x \@nil{\def\@tempa{#2}\def\@tempb{#3}} +\def\tabu@FV@ListProcessLine #1{% + \hbox {%to \hsize{% + \kern\leftmargin + \hbox {%to \linewidth{% + \FV@LeftListNumber + \FV@LeftListFrame + \FancyVerbFormatLine{#1}\hss +%% DG/SR modification begin - Jan. 28, 1998 (for numbers=right add-on) +%% \FV@RightListFrame}% + \FV@RightListFrame + \FV@RightListNumber}% +%% DG/SR modification end + \hss}} +%% \savetabu -------------------------------------------------------- +\newcommand*\savetabu[1]{\noalign{% + \tabu@sanitizearg{#1}\tabu@temp + \ifx \tabu@temp\@empty \tabu@savewarn{}{The tabu will not be saved}\else + \@ifundefined{tabu@saved@\tabu@temp}{}{\tabu@savewarn{#1}{Overwriting}}% + \ifdefined\tabu@restored \expandafter\let + \csname tabu@saved@\tabu@temp \endcsname \tabu@restored + \else {\tabu@save}% + \fi + \fi}% +}% \savetabu +\def\tabu@save {% + \toks0\expandafter{\tabu@saved@}% + \iftabu@negcoef + \let\tabu@wddef \relax \let\tabu@ \tabu@savewd \edef\tabu@savewd{\tabu@Xcoefs}% + \toks0\expandafter{\the\toks\expandafter0\tabu@savewd}\fi + \toks1\expandafter{\tabu@savedpream}% + \toks2\expandafter{\tabu@savedpreamble}% + \let\@preamble \relax + \let\tabu@savedpream \relax \let\tabu@savedparams \relax + \edef\tabu@preamble{% + \def\noexpand\tabu@aligndefault{\tabu@align}% + \def\tabu@savedparams {\noexpand\the\toks0}% + \def\tabu@savedpream {\noexpand\the\toks1}}% + \edef\tabu@usetabu{% + \def\@preamble {\noexpand\the\toks2}% + \tabu@target \the\tabu@target \relax + \tabucolX \the\tabucolX \relax + \tabu@nbcols \the\tabu@nbcols \relax + \def\noexpand\tabu@aligndefault{\tabu@align}% + \def\tabu@savedparams {\noexpand\the\toks0}% + \def\tabu@savedpream {\noexpand\the\toks1}}% + \let\tabu@aligndefault \relax \let\@sharp \relax + \edef\@tempa{\noexpand\tabu@s@ved + {\tabu@usetabu} + {\tabu@preamble} + {\the\toks1}}\@tempa + \tabu@message@save +}% \tabu@save +\long\def\tabu@s@ved #1#2#3{% + \def\tabu@usetabu{#1}% <for \tabu@message@save> + \expandafter\gdef\csname tabu@saved@\tabu@temp\endcsname ##1{% + \ifodd ##1% \usetabu + \tabu@measuringfalse \tabu@spreadfalse % Just in case... + \gdef\tabu@usetabu {% + \ifdim \tabu@target>\z@ \tabu@warn@usetabu \fi + \global\let\tabu@usetabu \@undefined + \def\@halignto {to\tabu@target}% + #1% + \ifx \tabu@align\tabu@aligndefault@text + \ifnum \tabu@nested=\z@ + \let\tabu@align \tabu@aligndefault \fi\fi}% + \else % \preamble + \gdef\tabu@preamble {% + \global\let\tabu@preamble \@undefined + #2% + \ifx \tabu@align\tabu@aligndefault@text + \ifnum \tabu@nested=\z@ + \let\tabu@align \tabu@aligndefault \fi\fi}% + \fi + #3}% +}% \tabu@s@ved +\def\tabu@aligndefault@text {\tabu@aligndefault}% +\def\tabu@warn@usetabu {\PackageWarning{tabu} + {Specifying a target with \string\usetabu\space is useless + \MessageBreak The target cannot be changed!}} +\def\tabu@savewd #1#2{\ifdim #2\p@<\z@ \tabu@wddef{#1}{\tabu@wd{#1}}\fi} +\def\tabu@savewarn#1#2{\PackageInfo{tabu} + {User-name `#1' already used for \string\savetabu + \MessageBreak #2}}% +\def\tabu@saveerr#1{\PackageError{tabu} + {User-name `#1' is unknown for \string\usetabu + \MessageBreak I cannot restore an unknown preamble!}\@ehd} +%% \rowfont --------------------------------------------------------- +\newskip \tabu@cellskip +\def\tabu@rowfont{\ifdim \baselineskip=\z@\noalign\fi + {\ifnum0=`}\fi \tabu@row@font} +\newcommand*\tabu@row@font[2][]{% + \ifnum7=\currentgrouptype + \global\let\tabu@@cellleft \tabu@cellleft + \global\let\tabu@@cellright \tabu@cellright + \global\let\tabu@@celllalign \tabu@celllalign + \global\let\tabu@@cellralign \tabu@cellralign + \global\let\tabu@@rowfontreset\tabu@rowfontreset + \fi + \global\let\tabu@rowfontreset \tabu@rowfont@reset + \expandafter\gdef\expandafter\tabu@cellleft\expandafter{\tabu@cellleft #2}% + \ifcsname tabu@cell@#1\endcsname % row alignment + \csname tabu@cell@#1\endcsname \fi + \ifnum0=`{\fi}% end of group / noalign group +}% \rowfont +\def\tabu@ifcolorleavevmode #1{\let\color \tabu@leavevmodecolor #1\let\color\tabu@color}% +\def\tabu@rowfont@reset{% + \global\let\tabu@rowfontreset \tabu@@rowfontreset + \global\let\tabu@cellleft \tabu@@cellleft + \global\let\tabu@cellright \tabu@@cellright + \global\let\tabu@cellfont \@empty + \global\let\tabu@celllalign \tabu@@celllalign + \global\let\tabu@cellralign \tabu@@cellralign +}% \tabu@@rowfontreset +\let\tabu@rowfontreset \@empty % overwritten \AtBeginDocument if colortbl +%% \tabu@prepnext@tok ----------------------------------------------- +\newif \iftabu@cellright +\def\tabu@prepnext@tok{% + \ifnum \count@<\z@ % <first initialisation> + \@tempcnta \@M % <not initialized by array.sty> + \tabu@nbcols\z@ + \let\tabu@fornoopORI \@fornoop + \tabu@cellrightfalse + \else + \ifcase \numexpr \count@-\@tempcnta \relax % (case 0): prev. token is left + \advance \tabu@nbcols \@ne + \iftabu@cellright % before-previous token is right and is finished + \tabu@cellrightfalse % <only once> + \tabu@righttok + \fi + \tabu@lefttok + \or % (case 1) previous token is right + \tabu@cellrighttrue \let\@fornoop \tabu@lastnoop + \else % special column: do not change the token + \iftabu@cellright % before-previous token is right + \tabu@cellrightfalse + \tabu@righttok + \fi + \fi % \ifcase + \fi + \tabu@prepnext@tokORI +}% \tabu@prepnext@tok +\long\def\tabu@lastnoop#1\@@#2#3{\tabu@lastn@@p #2\@nextchar \in@\in@@} +\def\tabu@lastn@@p #1\@nextchar #2#3\in@@{% + \ifx \in@#2\else + \let\@fornoop \tabu@fornoopORI + \xdef\tabu@mkpreambuffer{\tabu@nbcols\the\tabu@nbcols \tabu@mkpreambuffer}% + \toks0\expandafter{\expandafter\tabu@everyrowtrue \the\toks0}% + \expandafter\prepnext@tok + \fi +}% \tabu@lastnoop +\def\tabu@righttok{% + \advance \count@ \m@ne + \toks\count@\expandafter {\the\toks\count@ \tabu@cellright \tabu@cellralign}% + \advance \count@ \@ne +}% \tabu@righttok +\def\tabu@lefttok{\toks\count@\expandafter{\expandafter\tabu@celllalign + \the\toks\count@ \tabu@cellleft}% after because of $ +}% \tabu@lefttok +%% Neutralisation of glues ------------------------------------------ +\let\tabu@cellleft \@empty +\let\tabu@cellright \@empty +\tabu@celllalign@def{\tabu@cellleft}% +\let\tabu@cellralign \@empty +\def\tabu@cell@align #1#2#3{% + \let\tabu@maybesiunitx \toks@ \tabu@celllalign + \global \expandafter \tabu@celllalign@def \expandafter {\the\toks@ #1}% + \toks@\expandafter{\tabu@cellralign #2}% + \xdef\tabu@cellralign{\the\toks@}% + \toks@\expandafter{\tabu@cellleft #3}% + \xdef\tabu@cellleft{\the\toks@}% +}% \tabu@cell@align +\def\tabu@cell@l{% force alignment to left + \tabu@cell@align + {\tabu@removehfil \raggedright \tabu@cellleft}% left + {\tabu@flush1\tabu@ignorehfil}% right + \raggedright +}% \tabu@cell@l +\def\tabu@cell@c{% force alignment to center + \tabu@cell@align + {\tabu@removehfil \centering \tabu@flush{.5}\tabu@cellleft} + {\tabu@flush{.5}\tabu@ignorehfil} + \centering +}% \tabu@cell@c +\def\tabu@cell@r{% force alignment to right + \tabu@cell@align + {\tabu@removehfil \raggedleft \tabu@flush1\tabu@cellleft} + \tabu@ignorehfil + \raggedleft +}% \tabu@cell@r +\def\tabu@cell@j{% force justification (for p, m, b columns) + \tabu@cell@align + {\tabu@justify\tabu@cellleft} + {} + \tabu@justify +}% \tabu@cell@j +\def\tabu@justify{% + \leftskip\z@skip \@rightskip\leftskip \rightskip\@rightskip + \parfillskip\@flushglue +}% \tabu@justify +%% ragged2e settings +\def\tabu@cell@L{% force alignment to left (ragged2e) + \tabu@cell@align + {\tabu@removehfil \RaggedRight \tabu@cellleft} + {\tabu@flush 1\tabu@ignorehfil} + \RaggedRight +}% \tabu@cell@L +\def\tabu@cell@C{% force alignment to center (ragged2e) + \tabu@cell@align + {\tabu@removehfil \Centering \tabu@flush{.5}\tabu@cellleft} + {\tabu@flush{.5}\tabu@ignorehfil} + \Centering +}% \tabu@cell@C +\def\tabu@cell@R{% force alignment to right (ragged2e) + \tabu@cell@align + {\tabu@removehfil \RaggedLeft \tabu@flush 1\tabu@cellleft} + \tabu@ignorehfil + \RaggedLeft +}% \tabu@cell@R +\def\tabu@cell@J{% force justification (ragged2e) + \tabu@cell@align + {\justifying \tabu@cellleft} + {} + \justifying +}% \tabu@cell@J +\def\tabu@flush#1{% + \iftabu@colortbl % colortbl uses \hfill rather than \hfil + \hskip \ifnum13<\currentgrouptype \stretch{#1}% + \else \ifdim#1pt<\p@ \tabu@cellskip + \else \stretch{#1} + \fi\fi \relax + \else % array.sty + \ifnum 13<\currentgrouptype + \hfil \hskip1sp \relax \fi + \fi +}% \tabu@flush +\let\tabu@hfil \hfil +\let\tabu@hfill \hfill +\let\tabu@hskip \hskip +\def\tabu@removehfil{% + \iftabu@colortbl + \unkern \tabu@cellskip =\lastskip + \ifnum\gluestretchorder\tabu@cellskip =\tw@ \hskip-\tabu@cellskip + \else \tabu@cellskip \z@skip + \fi + \else + \ifdim\lastskip=1sp\unskip\fi + \ifnum\gluestretchorder\lastskip =\@ne + \hfilneg % \hfilneg for array.sty but not for colortbl... + \fi + \fi +}% \tabu@removehfil +\def\tabu@ignorehfil{\aftergroup \tabu@nohfil} +\def\tabu@nohfil{% \hfil -> do nothing + restore original \hfil + \def\hfil{\let\hfil \tabu@hfil}% local to (alignment template) group +}% \tabu@nohfil +\def\tabu@colortblalignments {% if colortbl + \def\tabu@nohfil{% + \def\hfil {\let\hfil \tabu@hfil}% local to (alignment template) group + \def\hfill {\let\hfill \tabu@hfill}% (colortbl uses \hfill) pfff... + \def\hskip ####1\relax{\let\hskip \tabu@hskip}}% local +}% \tabu@colortblalignments +%% Taking care of footnotes and hyperfootnotes ---------------------- +\long\def\tabu@footnotetext #1{% + \edef\@tempa{\the\tabu@footnotes + \noexpand\footnotetext [\the\csname c@\@mpfn\endcsname]}% + \global\tabu@footnotes\expandafter{\@tempa {#1}}}% +\long\def\tabu@xfootnotetext [#1]#2{% + \global\tabu@footnotes\expandafter{\the\tabu@footnotes + \footnotetext [{#1}]{#2}}} +\let\tabu@xfootnote \@xfootnote +\long\def\tabu@Hy@ftntext{\tabu@Hy@ftntxt {\the \c@footnote }} +\long\def\tabu@Hy@xfootnote [#1]{% + \begingroup + \value\@mpfn #1\relax + \protected@xdef \@thefnmark {\thempfn}% + \endgroup + \@footnotemark \tabu@Hy@ftntxt {#1}% +}% \tabu@Hy@xfootnote +\long\def\tabu@Hy@ftntxt #1#2{% + \edef\@tempa{% + \the\tabu@footnotes + \begingroup + \value\@mpfn #1\relax + \noexpand\protected@xdef\noexpand\@thefnmark {\noexpand\thempfn}% + \expandafter \noexpand \expandafter + \tabu@Hy@footnotetext \expandafter{\Hy@footnote@currentHref}% + }% + \global\tabu@footnotes\expandafter{\@tempa {#2}% + \endgroup}% +}% \tabu@Hy@ftntxt +\long\def\tabu@Hy@footnotetext #1#2{% + \H@@footnotetext{% + \ifHy@nesting + \hyper@@anchor {#1}{#2}% + \else + \Hy@raisedlink{% + \hyper@@anchor {#1}{\relax}% + }% + \def\@currentHref {#1}% + \let\@currentlabelname \@empty + #2% + \fi + }% +}% \tabu@Hy@footnotetext +%% No need for \arraybackslash ! ------------------------------------ +\def\tabu@latextwoe {% +\def\tabu@temp##1##2##3{{\toks@\expandafter{##2##3}\xdef##1{\the\toks@}}} +\tabu@temp \tabu@centering \centering \arraybackslash +\tabu@temp \tabu@raggedleft \raggedleft \arraybackslash +\tabu@temp \tabu@raggedright \raggedright \arraybackslash +}% \tabu@latextwoe +\def\tabu@raggedtwoe {% +\def\tabu@temp ##1##2##3{{\toks@\expandafter{##2##3}\xdef##1{\the\toks@}}} +\tabu@temp \tabu@Centering \Centering \arraybackslash +\tabu@temp \tabu@RaggedLeft \RaggedLeft \arraybackslash +\tabu@temp \tabu@RaggedRight \RaggedRight \arraybackslash +\tabu@temp \tabu@justifying \justifying \arraybackslash +}% \tabu@raggedtwoe +\def\tabu@normalcrbackslash{\let\\\@normalcr} +\def\tabu@trivlist{\expandafter\def\expandafter\@trivlist\expandafter{% + \expandafter\tabu@normalcrbackslash \@trivlist}} +%% Utilities: \fbox \fcolorbox and \tabudecimal ------------------- +\def\tabu@fbox {\leavevmode\afterassignment\tabu@beginfbox \setbox\@tempboxa\hbox} +\def\tabu@beginfbox {\bgroup \kern\fboxsep + \bgroup\aftergroup\tabu@endfbox} +\def\tabu@endfbox {\kern\fboxsep\egroup\egroup + \@frameb@x\relax} +\def\tabu@color@b@x #1#2{\leavevmode \bgroup + \def\tabu@docolor@b@x{#1{#2\color@block{\wd\z@}{\ht\z@}{\dp\z@}\box\z@}}% + \afterassignment\tabu@begincolor@b@x \setbox\z@ \hbox +}% \tabu@color@b@x +\def\tabu@begincolor@b@x {\kern\fboxsep \bgroup + \aftergroup\tabu@endcolor@b@x \set@color} +\def\tabu@endcolor@b@x {\kern\fboxsep \egroup + \dimen@\ht\z@ \advance\dimen@ \fboxsep \ht\z@ \dimen@ + \dimen@\dp\z@ \advance\dimen@ \fboxsep \dp\z@ \dimen@ + \tabu@docolor@b@x \egroup +}% \tabu@endcolor@b@x +%% Corrections (arydshln, delarray, colortbl) ----------------------- +\def\tabu@fix@arrayright {%% \@arrayright is missing from \endarray + \iftabu@colortbl + \ifdefined\adl@array % <colortbl + arydshln> + \def\tabu@endarray{% + \adl@endarray \egroup \adl@arrayrestore \CT@end \egroup %<original> + \@arrayright % <FC> + \gdef\@preamble{}}% <FC> + \else % <colortbl / no arydshln> + \def\tabu@endarray{% + \crcr \egroup \egroup %<original> + \@arrayright % <FC> + \gdef\@preamble{}\CT@end}% + \fi + \else + \ifdefined\adl@array % <arydshln / no colortbl> + \def\tabu@endarray{% + \adl@endarray \egroup \adl@arrayrestore \egroup %<original> + \@arrayright % <FC> + \gdef\@preamble{}}% <FC> + \else % <no arydshln / no colotbl + \@arrayright missing> + \PackageWarning{tabu} + {\string\@arrayright\space is missing from the + \MessageBreak definition of \string\endarray. + \MessageBreak Compatibility with delarray.sty is broken.}% + \fi\fi +}% \tabu@fix@arrayright +\def\tabu@adl@xarraydashrule #1#2#3{% + \ifnum\@lastchclass=\adl@class@start\else + \ifnum\@lastchclass=\@ne\else + \ifnum\@lastchclass=5 \else % <FC> @-arg (class 5) and !-arg (class 1) + \adl@leftrulefalse \fi\fi % must be treated the same + \fi + \ifadl@zwvrule\else \ifadl@inactive\else + \@addtopreamble{\vrule\@width\arrayrulewidth + \@height\z@ \@depth\z@}\fi \fi + \ifadl@leftrule + \@addtopreamble{\adl@vlineL{\CT@arc@}{\adl@dashgapcolor}% + {\number#1}#3}% + \else \@addtopreamble{\adl@vlineR{\CT@arc@}{\adl@dashgapcolor}% + {\number#2}#3} + \fi +}% \tabu@adl@xarraydashrule +\def\tabu@adl@act@endpbox {% + \unskip \ifhmode \nobreak \fi \@finalstrut \@arstrutbox + \egroup \egroup + \adl@colhtdp \box\adl@box \hfil +}% \tabu@adl@act@endpbox +\def\tabu@adl@fix {% + \let\adl@xarraydashrule \tabu@adl@xarraydashrule % <fix> arydshln + \let\adl@act@endpbox \tabu@adl@act@endpbox % <fix> arydshln + \let\adl@act@@endpbox \tabu@adl@act@endpbox % <fix> arydshln + \let\@preamerror \@preamerr % <fix> arydshln +}% \tabu@adl@fix +%% Correction for longtable' \@startbox definition ------------------ +%% => \everypar is ``missing'' : TeX should be in vertical mode +\def\tabu@LT@startpbox #1{% + \bgroup + \let\@footnotetext\LT@p@ftntext + \setlength\hsize{#1}% + \@arrayparboxrestore + \everypar{% + \vrule \@height \ht\@arstrutbox \@width \z@ + \everypar{}}% +}% \tabu@LT@startpbox +%% \tracingtabu and the package options ------------------ +\DeclareOption{delarray}{\AtEndOfPackage{\RequirePackage{delarray}}} +\DeclareOption{linegoal}{% + \AtEndOfPackage{% + \RequirePackage{linegoal}[2010/12/07]% + \let\tabudefaulttarget \linegoal% \linegoal is \linewidth if not pdfTeX +}} +\DeclareOption{scantokens}{\tabuscantokenstrue} +\DeclareOption{debugshow}{\AtEndOfPackage{\tracingtabu=\tw@}} +\def\tracingtabu {\begingroup\@ifnextchar=% + {\afterassignment\tabu@tracing\count@} + {\afterassignment\tabu@tracing\count@1\relax}} +\def\tabu@tracing{\expandafter\endgroup + \expandafter\tabu@tr@cing \the\count@ \relax +}% \tabu@tracing +\def\tabu@tr@cing #1\relax {% + \ifnum#1>\thr@@ \let\tabu@tracinglines\message + \else \let\tabu@tracinglines\@gobble + \fi + \ifnum#1>\tw@ \let\tabu@DBG \tabu@@DBG + \def\tabu@mkarstrut {\tabu@DBG@arstrut}% + \tabustrutrule 1.5\p@ + \else \let\tabu@DBG \@gobble + \def\tabu@mkarstrut {\tabu@arstrut}% + \tabustrutrule \z@ + \fi + \ifnum#1>\@ne \let\tabu@debug \message + \else \let\tabu@debug \@gobble + \fi + \ifnum#1>\z@ + \let\tabu@message \message + \let\tabu@tracing@save \tabu@message@save + \let\tabu@starttimer \tabu@pdftimer + \else + \let\tabu@message \@gobble + \let\tabu@tracing@save \@gobble + \let\tabu@starttimer \relax + \fi +}% \tabu@tr@cing +%% Setup \AtBeginDocument +\AtBeginDocument{\tabu@AtBeginDocument} +\def\tabu@AtBeginDocument{\let\tabu@AtBeginDocument \@undefined + \ifdefined\arrayrulecolor \tabu@colortbltrue % <colortbl> + \tabu@colortblalignments % different glues are used + \else \tabu@colortblfalse \fi + \ifdefined\CT@arc@ \else \let\CT@arc@ \relax \fi + \ifdefined\CT@drsc@\else \let\CT@drsc@ \relax \fi + \let\tabu@arc@L \CT@arc@ \let\tabu@drsc@L \CT@drsc@ + \ifodd 1\ifcsname siunitx_table_collect_begin:Nn\endcsname % <siunitx: ok> + \expandafter\ifx + \csname siunitx_table_collect_begin:Nn\endcsname\relax 0\fi\fi\relax + \tabu@siunitxtrue + \else \let\tabu@maybesiunitx \@firstofone % <not siunitx: setup> + \let\tabu@siunitx \tabu@nosiunitx + \tabu@siunitxfalse + \fi + \ifdefined\adl@array % <arydshln> + \else \let\tabu@adl@fix \relax + \let\tabu@adl@endtrial \@empty \fi + \ifdefined\longtable % <longtable> + \else \let\longtabu \tabu@nolongtabu \fi + \ifdefined\cellspacetoplimit \tabu@warn@cellspace\fi + \csname\ifcsname ifHy@hyperfootnotes\endcsname % <hyperfootnotes> + ifHy@hyperfootnotes\else iffalse\fi\endcsname + \let\tabu@footnotetext \tabu@Hy@ftntext + \let\tabu@xfootnote \tabu@Hy@xfootnote \fi + \ifdefined\FV@DefineCheckEnd% <fancyvrb> + \tabu@fancyvrb \fi + \ifdefined\color % <color / xcolor> + \let\tabu@color \color + \def\tabu@leavevmodecolor ##1{% + \def\tabu@leavevmodecolor {\leavevmode ##1}% + }\expandafter\tabu@leavevmodecolor\expandafter{\color}% + \else + \let\tabu@color \tabu@nocolor + \let\tabu@leavevmodecolor \@firstofone \fi + \tabu@latextwoe + \ifdefined\@raggedtwoe@everyselectfont % <ragged2e> + \tabu@raggedtwoe + \else + \let\tabu@cell@L \tabu@cell@l + \let\tabu@cell@R \tabu@cell@r + \let\tabu@cell@C \tabu@cell@c + \let\tabu@cell@J \tabu@cell@j \fi + \expandafter\in@ \expandafter\@arrayright \expandafter{\endarray}% + \ifin@ \let\tabu@endarray \endarray + \else \tabu@fix@arrayright \fi% <fix for colortbl & arydshln (delarray)> + \everyrow{}% +}% \tabu@AtBeginDocument +\def\tabu@warn@cellspace{% + \PackageWarning{tabu}{% + Package cellspace has some limitations + \MessageBreak And redefines some macros of array.sty. + \MessageBreak Please use \string\tabulinesep\space to control + \MessageBreak vertical spacing of lines inside tabu environment}% +}% \tabu@warn@cellspace +%% tabu Package initialisation +\tabuscantokensfalse +\let\tabu@arc@G \relax +\let\tabu@drsc@G \relax +\let\tabu@evr@G \@empty +\let\tabu@rc@G \@empty +\def\tabu@ls@G {\tabu@linestyle@}% +\let\tabu@@rowfontreset \@empty % <init> +\let\tabu@@celllalign \@empty +\let\tabu@@cellralign \@empty +\let\tabu@@cellleft \@empty +\let\tabu@@cellright \@empty +\def\tabu@naturalXmin {\z@} +\def\tabu@naturalXmax {\z@} +\let\tabu@rowfontreset \@empty +\def\tabulineon {4pt}\let\tabulineoff \tabulineon +\tabu@everyrowtrue +\ifdefined\pdfelapsedtime % <pdfTeX> + \def\tabu@pdftimer {\xdef\tabu@starttime{\the\pdfelapsedtime}}% +\else \let\tabu@pdftimer \relax \let\tabu@message@etime \relax +\fi +\tracingtabu=\z@ +\newtabulinestyle {=\maxdimen}% creates the 'factory' settings \tabu@linestyle@ +\tabulinestyle{} +\taburowcolors{} +\let\tabudefaulttarget \linewidth +\ProcessOptions* % \ProcessOptions* is quicker ! +\endinput +%% +%% End of file `tabu.sty'. -- GitLab