From 1b38083d1fae883e9961f200c66349658b2f7317 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Mon, 12 May 2025 16:04:53 -0400 Subject: [PATCH 01/28] Migrated interactivity work into UnityGLTF. --- Runtime/Resources/animation_test.bytes | Bin 0 -> 2788 bytes Runtime/Resources/animation_test.bytes.meta | 7 + Runtime/Resources/pointer_test.bytes | Bin 0 -> 1456 bytes Runtime/Resources/pointer_test.bytes.meta | 7 + Runtime/Scripts/Interactivity/Playback.meta | 8 + .../Playback/AnimationWrapper.cs | 203 ++++ .../Playback/AnimationWrapper.cs.meta | 11 + .../Interactivity/Playback/BehaviourEngine.cs | 159 +++ .../Playback/BehaviourEngine.cs.meta | 11 + .../Playback/BehaviourEngineNode.cs | 119 +++ .../Playback/BehaviourEngineNode.cs.meta | 11 + .../Interactivity/Playback/Common.meta | 8 + .../Playback/Common/MatrixExtensions.cs | 45 + .../Playback/Common/MatrixExtensions.cs.meta | 11 + .../Interactivity/Playback/Common/Parser.cs | 190 ++++ .../Playback/Common/Parser.cs.meta | 11 + .../Playback/Common/StringSpanReader.cs | 120 +++ .../Playback/Common/StringSpanReader.cs.meta | 11 + .../Playback/Common/Transforms.cs | 179 ++++ .../Playback/Common/Transforms.cs.meta | 11 + ...yGLTF.Interactivity.Playback.Common.asmdef | 16 + ....Interactivity.Playback.Common.asmdef.meta | 7 + .../Playback/Common/ValueStringBuilder.cs | 108 ++ .../Common/ValueStringBuilder.cs.meta | 11 + .../Interactivity/Playback/Context.meta | 8 + .../Playback/Context/Export.meta | 8 + .../Export/InteractivityExportContext.cs | 77 ++ .../Export/InteractivityExportContext.cs.meta | 11 + .../Export/InteractivityExportPlugin.cs | 20 + .../Export/InteractivityExportPlugin.cs.meta | 11 + .../Playback/Context/Import.meta | 8 + .../Import/InteractivityImportContext.cs | 68 ++ .../Import/InteractivityImportContext.cs.meta | 11 + .../Import/InteractivityImportPlugin.cs | 22 + .../Import/InteractivityImportPlugin.cs.meta | 11 + .../Playback/Context/Serialization.meta | 8 + .../InteractivityGraphExtension.cs | 51 + .../InteractivityGraphExtension.cs.meta | 11 + .../InteractivityGraphFactory.cs | 23 + .../InteractivityGraphFactory.cs.meta | 11 + .../Scripts/Interactivity/Playback/Data.meta | 8 + .../Playback/Data/Deserializers.meta | 8 + .../Data/Deserializers/Declarations.cs | 58 + .../Data/Deserializers/Declarations.cs.meta | 11 + .../Playback/Data/Deserializers/Events.cs | 40 + .../Data/Deserializers/Events.cs.meta | 11 + .../Playback/Data/Deserializers/Nodes.cs | 173 +++ .../Playback/Data/Deserializers/Nodes.cs.meta | 11 + .../Playback/Data/Deserializers/Types.cs | 25 + .../Playback/Data/Deserializers/Types.cs.meta | 11 + .../Playback/Data/Deserializers/Variables.cs | 50 + .../Data/Deserializers/Variables.cs.meta | 11 + .../Playback/Data/GraphConverter.cs | 77 ++ .../Playback/Data/GraphConverter.cs.meta | 11 + .../Playback/Data/GraphSerializer.cs | 40 + .../Playback/Data/GraphSerializer.cs.meta | 11 + .../Interactivity/Playback/Data/Helpers.meta | 8 + .../Playback/Data/Helpers/Constants.cs | 152 +++ .../Playback/Data/Helpers/Constants.cs.meta | 11 + .../Playback/Data/Helpers/Extensions.meta | 8 + .../Data/Helpers/Extensions/Arrays.cs | 50 + .../Data/Helpers/Extensions/Arrays.cs.meta | 11 + .../Helpers/Extensions/BehaviourEngine.cs | 38 + .../Extensions/BehaviourEngine.cs.meta | 11 + .../Helpers/Extensions/BehaviourEngineNode.cs | 107 ++ .../Extensions/BehaviourEngineNode.cs.meta | 11 + .../Data/Helpers/Extensions/Conversions.cs | 48 + .../Helpers/Extensions/Conversions.cs.meta | 11 + .../Playback/Data/Helpers/Extensions/Flows.cs | 32 + .../Data/Helpers/Extensions/Flows.cs.meta | 11 + .../Data/Helpers/Extensions/Transform.cs | 21 + .../Data/Helpers/Extensions/Transform.cs.meta | 11 + .../Playback/Data/Helpers/FilePaths.cs | 39 + .../Playback/Data/Helpers/FilePaths.cs.meta | 11 + .../Playback/Data/Helpers/Interpolation.cs | 130 +++ .../Data/Helpers/Interpolation.cs.meta | 11 + .../Playback/Data/Helpers/Pointers.cs | 71 ++ .../Playback/Data/Helpers/Pointers.cs.meta | 11 + .../Playback/Data/Helpers/Properties.cs | 172 +++ .../Playback/Data/Helpers/Properties.cs.meta | 11 + .../Playback/Data/Helpers/Util.cs | 43 + .../Playback/Data/Helpers/Util.cs.meta | 11 + .../Interactivity/Playback/Data/Model.meta | 8 + .../Playback/Data/Model/Customevent.cs | 28 + .../Playback/Data/Model/Customevent.cs.meta | 11 + .../Interactivity/Playback/Data/Model/Flow.cs | 39 + .../Playback/Data/Model/Flow.cs.meta | 11 + .../Playback/Data/Model/Graph.cs | 219 ++++ .../Playback/Data/Model/Graph.cs.meta | 11 + .../Interactivity/Playback/Data/Model/Node.cs | 260 +++++ .../Playback/Data/Model/Node.cs.meta | 11 + .../Playback/Data/Model/Property.cs | 38 + .../Playback/Data/Model/Property.cs.meta | 11 + .../Playback/Data/Model/Value.cs | 65 ++ .../Playback/Data/Model/Value.cs.meta | 11 + .../Playback/Data/Model/Various.cs | 68 ++ .../Playback/Data/Model/Various.cs.meta | 11 + .../Playback/Data/Serializers.meta | 8 + .../Playback/Data/Serializers/Declarations.cs | 140 +++ .../Data/Serializers/Declarations.cs.meta | 11 + .../Playback/Data/Serializers/Events.cs | 58 + .../Playback/Data/Serializers/Events.cs.meta | 11 + .../Playback/Data/Serializers/Nodes.cs | 246 +++++ .../Playback/Data/Serializers/Nodes.cs.meta | 11 + .../Playback/Data/Serializers/Types.cs | 52 + .../Playback/Data/Serializers/Types.cs.meta | 11 + .../Playback/Data/Serializers/Variables.cs | 34 + .../Data/Serializers/Variables.cs.meta | 11 + .../Interactivity/Playback/EventWrapper.cs | 147 +++ .../Playback/EventWrapper.cs.meta | 11 + .../Interactivity/Playback/ICancelToken.cs | 38 + .../Playback/ICancelToken.cs.meta | 11 + .../Playback/NodeDelayManager.cs | 77 ++ .../Playback/NodeDelayManager.cs.meta | 11 + .../Interactivity/Playback/NodeRegistry.cs | 281 +++++ .../Playback/NodeRegistry.cs.meta | 11 + .../Interactivity/Playback/NodeSpecs.meta | 8 + .../Playback/NodeSpecs/Animation.meta | 8 + .../Playback/NodeSpecs/Animation/Start.cs | 39 + .../NodeSpecs/Animation/Start.cs.meta | 11 + .../Playback/NodeSpecs/Animation/Stop.cs | 35 + .../Playback/NodeSpecs/Animation/Stop.cs.meta | 11 + .../Playback/NodeSpecs/Animation/StopAt.cs | 37 + .../NodeSpecs/Animation/StopAt.cs.meta | 11 + .../Playback/NodeSpecs/Debugging.meta | 8 + .../NodeSpecs/Debugging/DebugAssert.cs | 20 + .../NodeSpecs/Debugging/DebugAssert.cs.meta | 11 + .../Playback/NodeSpecs/Debugging/DebugLog.cs | 33 + .../NodeSpecs/Debugging/DebugLog.cs.meta | 11 + .../Playback/NodeSpecs/Event.meta | 8 + .../Playback/NodeSpecs/Event/OnStart.cs | 15 + .../Playback/NodeSpecs/Event/OnStart.cs.meta | 11 + .../Playback/NodeSpecs/Event/OnTick.cs | 23 + .../Playback/NodeSpecs/Event/OnTick.cs.meta | 11 + .../Playback/NodeSpecs/Event/Receive.cs | 23 + .../Playback/NodeSpecs/Event/Receive.cs.meta | 11 + .../Playback/NodeSpecs/Event/Send.cs | 33 + .../Playback/NodeSpecs/Event/Send.cs.meta | 11 + .../Playback/NodeSpecs/Flow.meta | 8 + .../Playback/NodeSpecs/Flow/Branch.cs | 18 + .../Playback/NodeSpecs/Flow/Branch.cs.meta | 11 + .../Playback/NodeSpecs/Flow/CancelDelay.cs | 27 + .../NodeSpecs/Flow/CancelDelay.cs.meta | 11 + .../Playback/NodeSpecs/Flow/DoN.cs | 32 + .../Playback/NodeSpecs/Flow/DoN.cs.meta | 11 + .../Playback/NodeSpecs/Flow/For.cs | 39 + .../Playback/NodeSpecs/Flow/For.cs.meta | 11 + .../Playback/NodeSpecs/Flow/MultiGate.cs | 37 + .../Playback/NodeSpecs/Flow/MultiGate.cs.meta | 11 + .../Playback/NodeSpecs/Flow/Sequence.cs | 13 + .../Playback/NodeSpecs/Flow/Sequence.cs.meta | 11 + .../Playback/NodeSpecs/Flow/SetDelay.cs | 33 + .../Playback/NodeSpecs/Flow/SetDelay.cs.meta | 11 + .../Playback/NodeSpecs/Flow/Switch.cs | 34 + .../Playback/NodeSpecs/Flow/Switch.cs.meta | 11 + .../Playback/NodeSpecs/Flow/Throttle.cs | 34 + .../Playback/NodeSpecs/Flow/Throttle.cs.meta | 11 + .../Playback/NodeSpecs/Flow/WaitAll.cs | 37 + .../Playback/NodeSpecs/Flow/WaitAll.cs.meta | 11 + .../Playback/NodeSpecs/Flow/While.cs | 28 + .../Playback/NodeSpecs/Flow/While.cs.meta | 11 + .../Playback/NodeSpecs/Math.meta | 8 + .../Playback/NodeSpecs/Math/Abs.cs | 29 + .../Playback/NodeSpecs/Math/Abs.cs.meta | 11 + .../Playback/NodeSpecs/Math/Add.cs | 30 + .../Playback/NodeSpecs/Math/Add.cs.meta | 11 + .../Playback/NodeSpecs/Math/Constant.cs | 31 + .../Playback/NodeSpecs/Math/Constant.cs.meta | 11 + .../Playback/NodeSpecs/Math/Extract.cs | 62 ++ .../Playback/NodeSpecs/Math/Extract.cs.meta | 11 + .../Playback/NodeSpecs/Math/MatCompose.cs | 31 + .../NodeSpecs/Math/MatCompose.cs.meta | 11 + .../Playback/NodeSpecs/Math/MatDecompose.cs | 31 + .../NodeSpecs/Math/MatDecompose.cs.meta | 11 + .../Playback/NodeSpecs/Math/OneOperand.cs | 257 +++++ .../NodeSpecs/Math/OneOperand.cs.meta | 11 + .../Playback/NodeSpecs/Math/Random.cs | 28 + .../Playback/NodeSpecs/Math/Random.cs.meta | 11 + .../Playback/NodeSpecs/Math/Rotate3D.cs | 31 + .../Playback/NodeSpecs/Math/Rotate3D.cs.meta | 11 + .../Playback/NodeSpecs/Math/Select.cs | 31 + .../Playback/NodeSpecs/Math/Select.cs.meta | 11 + .../Playback/NodeSpecs/Math/Switch.cs | 39 + .../Playback/NodeSpecs/Math/Switch.cs.meta | 11 + .../Playback/NodeSpecs/Math/ThreeOperands.cs | 49 + .../NodeSpecs/Math/ThreeOperands.cs.meta | 11 + .../Playback/NodeSpecs/Math/Transform.cs | 30 + .../Playback/NodeSpecs/Math/Transform.cs.meta | 11 + .../Playback/NodeSpecs/Math/TwoOperands.cs | 258 +++++ .../NodeSpecs/Math/TwoOperands.cs.meta | 11 + .../Playback/NodeSpecs/NodeSpecifications.cs | 97 ++ .../NodeSpecs/NodeSpecifications.cs.meta | 11 + .../Playback/NodeSpecs/Pointer.meta | 8 + .../Playback/NodeSpecs/Pointer/Get.cs | 27 + .../Playback/NodeSpecs/Pointer/Get.cs.meta | 11 + .../Playback/NodeSpecs/Pointer/Interpolate.cs | 47 + .../NodeSpecs/Pointer/Interpolate.cs.meta | 11 + .../Playback/NodeSpecs/Pointer/Set.cs | 44 + .../Playback/NodeSpecs/Pointer/Set.cs.meta | 11 + .../Playback/NodeSpecs/Variable.meta | 8 + .../Playback/NodeSpecs/Variable/Get.cs | 27 + .../Playback/NodeSpecs/Variable/Get.cs.meta | 11 + .../NodeSpecs/Variable/Interpolate.cs | 47 + .../NodeSpecs/Variable/Interpolate.cs.meta | 11 + .../Playback/NodeSpecs/Variable/Set.cs | 42 + .../Playback/NodeSpecs/Variable/Set.cs.meta | 11 + .../NodeSpecs/Variable/SetMultiple.cs | 37 + .../NodeSpecs/Variable/SetMultiple.cs.meta | 11 + .../Scripts/Interactivity/Playback/Nodes.meta | 8 + .../Playback/Nodes/Animation.meta | 8 + .../Playback/Nodes/Animation/Start.cs | 83 ++ .../Playback/Nodes/Animation/Start.cs.meta | 11 + .../Playback/Nodes/Animation/Stop.cs | 48 + .../Playback/Nodes/Animation/Stop.cs.meta | 11 + .../Playback/Nodes/Animation/StopAt.cs | 56 + .../Playback/Nodes/Animation/StopAt.cs.meta | 11 + .../Interactivity/Playback/Nodes/Debug.meta | 8 + .../Interactivity/Playback/Nodes/Event.meta | 8 + .../Playback/Nodes/Event/OnHoverIn.cs | 72 ++ .../Playback/Nodes/Event/OnHoverIn.cs.meta | 11 + .../Playback/Nodes/Event/OnHoverOut.cs | 65 ++ .../Playback/Nodes/Event/OnHoverOut.cs.meta | 11 + .../Playback/Nodes/Event/OnSelect.cs | 72 ++ .../Playback/Nodes/Event/OnSelect.cs.meta | 11 + .../Playback/Nodes/Event/OnStart.cs | 15 + .../Playback/Nodes/Event/OnStart.cs.meta | 11 + .../Playback/Nodes/Event/OnTick.cs | 46 + .../Playback/Nodes/Event/OnTick.cs.meta | 11 + .../Playback/Nodes/Event/Receive.cs | 60 ++ .../Playback/Nodes/Event/Receive.cs.meta | 11 + .../Playback/Nodes/Event/Send.cs | 57 + .../Playback/Nodes/Event/Send.cs.meta | 11 + .../Interactivity/Playback/Nodes/Flow.meta | 8 + .../Playback/Nodes/Flow/Branch.cs | 36 + .../Playback/Nodes/Flow/Branch.cs.meta | 11 + .../Playback/Nodes/Flow/CancelDelay.cs | 18 + .../Playback/Nodes/Flow/CancelDelay.cs.meta | 11 + .../Interactivity/Playback/Nodes/Flow/DoN.cs | 44 + .../Playback/Nodes/Flow/DoN.cs.meta | 11 + .../Interactivity/Playback/Nodes/Flow/For.cs | 50 + .../Playback/Nodes/Flow/For.cs.meta | 11 + .../Playback/Nodes/Flow/MultiGate.cs | 115 ++ .../Playback/Nodes/Flow/MultiGate.cs.meta | 11 + .../Playback/Nodes/Flow/Sequence.cs | 31 + .../Playback/Nodes/Flow/Sequence.cs.meta | 11 + .../Playback/Nodes/Flow/SetDelay.cs | 58 + .../Playback/Nodes/Flow/SetDelay.cs.meta | 11 + .../Playback/Nodes/Flow/Switch.cs | 65 ++ .../Playback/Nodes/Flow/Switch.cs.meta | 11 + .../Playback/Nodes/Flow/Throttle.cs | 61 ++ .../Playback/Nodes/Flow/Throttle.cs.meta | 11 + .../Playback/Nodes/Flow/WaitAll.cs | 53 + .../Playback/Nodes/Flow/WaitAll.cs.meta | 11 + .../Playback/Nodes/Flow/While.cs | 32 + .../Playback/Nodes/Flow/While.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math.meta | 8 + .../Interactivity/Playback/Nodes/Math/ACos.cs | 27 + .../Playback/Nodes/Math/ACos.cs.meta | 11 + .../Playback/Nodes/Math/ACosH.cs | 48 + .../Playback/Nodes/Math/ACosH.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/ASin.cs | 27 + .../Playback/Nodes/Math/ASin.cs.meta | 11 + .../Playback/Nodes/Math/ASinH.cs | 48 + .../Playback/Nodes/Math/ASinH.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/ATan.cs | 27 + .../Playback/Nodes/Math/ATan.cs.meta | 11 + .../Playback/Nodes/Math/ATan2.cs | 28 + .../Playback/Nodes/Math/ATan2.cs.meta | 11 + .../Playback/Nodes/Math/ATanH.cs | 48 + .../Playback/Nodes/Math/ATanH.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Abs.cs | 28 + .../Playback/Nodes/Math/Abs.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Add.cs | 29 + .../Playback/Nodes/Math/Add.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/And.cs | 25 + .../Playback/Nodes/Math/And.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Asr.cs | 25 + .../Playback/Nodes/Math/Asr.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Cbrt.cs | 43 + .../Playback/Nodes/Math/Cbrt.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Ceil.cs | 27 + .../Playback/Nodes/Math/Ceil.cs.meta | 11 + .../Playback/Nodes/Math/Clamp.cs | 35 + .../Playback/Nodes/Math/Clamp.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Clz.cs | 43 + .../Playback/Nodes/Math/Clz.cs.meta | 11 + .../Playback/Nodes/Math/Combine2.cs | 27 + .../Playback/Nodes/Math/Combine2.cs.meta | 11 + .../Playback/Nodes/Math/Combine2x2.cs | 32 + .../Playback/Nodes/Math/Combine2x2.cs.meta | 11 + .../Playback/Nodes/Math/Combine3.cs | 31 + .../Playback/Nodes/Math/Combine3.cs.meta | 11 + .../Playback/Nodes/Math/Combine3x3.cs | 33 + .../Playback/Nodes/Math/Combine3x3.cs.meta | 11 + .../Playback/Nodes/Math/Combine4.cs | 35 + .../Playback/Nodes/Math/Combine4.cs.meta | 11 + .../Playback/Nodes/Math/Combine4x4.cs | 35 + .../Playback/Nodes/Math/Combine4x4.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Cos.cs | 27 + .../Playback/Nodes/Math/Cos.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/CosH.cs | 27 + .../Playback/Nodes/Math/CosH.cs.meta | 11 + .../Playback/Nodes/Math/Cross.cs | 25 + .../Playback/Nodes/Math/Cross.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Ctz.cs | 34 + .../Playback/Nodes/Math/Ctz.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Deg.cs | 27 + .../Playback/Nodes/Math/Deg.cs.meta | 11 + .../Playback/Nodes/Math/Determinant.cs | 26 + .../Playback/Nodes/Math/Determinant.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Div.cs | 29 + .../Playback/Nodes/Math/Div.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Dot.cs | 27 + .../Playback/Nodes/Math/Dot.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/E.cs | 16 + .../Playback/Nodes/Math/E.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Eq.cs | 75 ++ .../Playback/Nodes/Math/Eq.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Exp.cs | 27 + .../Playback/Nodes/Math/Exp.cs.meta | 11 + .../Playback/Nodes/Math/Extract2.cs | 32 + .../Playback/Nodes/Math/Extract2.cs.meta | 11 + .../Playback/Nodes/Math/Extract2x2.cs | 30 + .../Playback/Nodes/Math/Extract2x2.cs.meta | 11 + .../Playback/Nodes/Math/Extract3.cs | 35 + .../Playback/Nodes/Math/Extract3.cs.meta | 11 + .../Playback/Nodes/Math/Extract3x3.cs | 36 + .../Playback/Nodes/Math/Extract3x3.cs.meta | 11 + .../Playback/Nodes/Math/Extract4.cs | 38 + .../Playback/Nodes/Math/Extract4.cs.meta | 11 + .../Playback/Nodes/Math/Extract4x4.cs | 43 + .../Playback/Nodes/Math/Extract4x4.cs.meta | 11 + .../Playback/Nodes/Math/Floor.cs | 27 + .../Playback/Nodes/Math/Floor.cs.meta | 11 + .../Playback/Nodes/Math/Fract.cs | 27 + .../Playback/Nodes/Math/Fract.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Ge.cs | 28 + .../Playback/Nodes/Math/Ge.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Gt.cs | 29 + .../Playback/Nodes/Math/Gt.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Inf.cs | 16 + .../Playback/Nodes/Math/Inf.cs.meta | 11 + .../Playback/Nodes/Math/Inverse.cs | 80 ++ .../Playback/Nodes/Math/Inverse.cs.meta | 11 + .../Playback/Nodes/Math/IsInf.cs | 22 + .../Playback/Nodes/Math/IsInf.cs.meta | 11 + .../Playback/Nodes/Math/IsNaN.cs | 22 + .../Playback/Nodes/Math/IsNaN.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Le.cs | 28 + .../Playback/Nodes/Math/Le.cs.meta | 11 + .../Playback/Nodes/Math/Length.cs | 26 + .../Playback/Nodes/Math/Length.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Log.cs | 27 + .../Playback/Nodes/Math/Log.cs.meta | 11 + .../Playback/Nodes/Math/Log10.cs | 27 + .../Playback/Nodes/Math/Log10.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Log2.cs | 27 + .../Playback/Nodes/Math/Log2.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Lsl.cs | 25 + .../Playback/Nodes/Math/Lsl.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Lt.cs | 28 + .../Playback/Nodes/Math/Lt.cs.meta | 11 + .../Playback/Nodes/Math/MatCompose.cs | 27 + .../Playback/Nodes/Math/MatCompose.cs.meta | 11 + .../Playback/Nodes/Math/MatDecompose.cs | 32 + .../Playback/Nodes/Math/MatDecompose.cs.meta | 11 + .../Playback/Nodes/Math/MatMul.cs | 27 + .../Playback/Nodes/Math/MatMul.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Max.cs | 29 + .../Playback/Nodes/Math/Max.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Min.cs | 29 + .../Playback/Nodes/Math/Min.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Mix.cs | 29 + .../Playback/Nodes/Math/Mix.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Mul.cs | 29 + .../Playback/Nodes/Math/Mul.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/NaN.cs | 16 + .../Playback/Nodes/Math/NaN.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Neg.cs | 28 + .../Playback/Nodes/Math/Neg.cs.meta | 11 + .../Playback/Nodes/Math/Normalize.cs | 26 + .../Playback/Nodes/Math/Normalize.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Not.cs | 24 + .../Playback/Nodes/Math/Not.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Or.cs | 25 + .../Playback/Nodes/Math/Or.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Pi.cs | 16 + .../Playback/Nodes/Math/Pi.cs.meta | 11 + .../Playback/Nodes/Math/Popcnt.cs | 24 + .../Playback/Nodes/Math/Popcnt.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Pow.cs | 28 + .../Playback/Nodes/Math/Pow.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Rad.cs | 27 + .../Playback/Nodes/Math/Rad.cs.meta | 11 + .../Playback/Nodes/Math/Random.cs | 30 + .../Playback/Nodes/Math/Random.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Rem.cs | 29 + .../Playback/Nodes/Math/Rem.cs.meta | 11 + .../Playback/Nodes/Math/Rotate2D.cs | 34 + .../Playback/Nodes/Math/Rotate2D.cs.meta | 11 + .../Playback/Nodes/Math/Rotate3D.cs | 32 + .../Playback/Nodes/Math/Rotate3D.cs.meta | 11 + .../Playback/Nodes/Math/Round.cs | 27 + .../Playback/Nodes/Math/Round.cs.meta | 11 + .../Playback/Nodes/Math/Saturate.cs | 27 + .../Playback/Nodes/Math/Saturate.cs.meta | 11 + .../Playback/Nodes/Math/Select.cs | 27 + .../Playback/Nodes/Math/Select.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Sign.cs | 28 + .../Playback/Nodes/Math/Sign.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Sin.cs | 27 + .../Playback/Nodes/Math/Sin.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/SinH.cs | 27 + .../Playback/Nodes/Math/SinH.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Sqrt.cs | 27 + .../Playback/Nodes/Math/Sqrt.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Sub.cs | 29 + .../Playback/Nodes/Math/Sub.cs.meta | 11 + .../Playback/Nodes/Math/Switch.cs | 25 + .../Playback/Nodes/Math/Switch.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Tan.cs | 27 + .../Playback/Nodes/Math/Tan.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/TanH.cs | 27 + .../Playback/Nodes/Math/TanH.cs.meta | 11 + .../Playback/Nodes/Math/Transform.cs | 27 + .../Playback/Nodes/Math/Transform.cs.meta | 11 + .../Playback/Nodes/Math/Transpose.cs | 26 + .../Playback/Nodes/Math/Transpose.cs.meta | 11 + .../Playback/Nodes/Math/Trunc.cs | 27 + .../Playback/Nodes/Math/Trunc.cs.meta | 11 + .../Interactivity/Playback/Nodes/Math/Xor.cs | 25 + .../Playback/Nodes/Math/Xor.cs.meta | 11 + .../Interactivity/Playback/Nodes/NoOp.cs | 48 + .../Interactivity/Playback/Nodes/NoOp.cs.meta | 11 + .../Interactivity/Playback/Nodes/Pointer.meta | 8 + .../Playback/Nodes/Pointer/Get.cs | 72 ++ .../Playback/Nodes/Pointer/Get.cs.meta | 11 + .../Playback/Nodes/Pointer/Interpolate.cs | 94 ++ .../Nodes/Pointer/Interpolate.cs.meta | 11 + .../Playback/Nodes/Pointer/Set.cs | 80 ++ .../Playback/Nodes/Pointer/Set.cs.meta | 11 + .../Interactivity/Playback/Nodes/Type.meta | 8 + .../Playback/Nodes/Type/BoolToFloat.cs | 21 + .../Playback/Nodes/Type/BoolToFloat.cs.meta | 11 + .../Playback/Nodes/Type/BoolToInt.cs | 21 + .../Playback/Nodes/Type/BoolToInt.cs.meta | 11 + .../Playback/Nodes/Type/FloatToBool.cs | 21 + .../Playback/Nodes/Type/FloatToBool.cs.meta | 11 + .../Playback/Nodes/Type/FloatToInt.cs | 21 + .../Playback/Nodes/Type/FloatToInt.cs.meta | 11 + .../Playback/Nodes/Type/IntToBool.cs | 21 + .../Playback/Nodes/Type/IntToBool.cs.meta | 11 + .../Playback/Nodes/Type/IntToFloat.cs | 21 + .../Playback/Nodes/Type/IntToFloat.cs.meta | 11 + .../Playback/Nodes/Variable.meta | 8 + .../Playback/Nodes/Variable/Get.cs | 23 + .../Playback/Nodes/Variable/Get.cs.meta | 11 + .../Playback/Nodes/Variable/Interpolate.cs | 88 ++ .../Nodes/Variable/Interpolate.cs.meta | 11 + .../Playback/Nodes/Variable/Set.cs | 53 + .../Playback/Nodes/Variable/Set.cs.meta | 11 + .../Playback/Nodes/Variable/SetMultiple.cs | 59 ++ .../Nodes/Variable/SetMultiple.cs.meta | 11 + .../Playback/PointerInterpolationManager.cs | 166 +++ .../PointerInterpolationManager.cs.meta | 11 + .../Interactivity/Playback/Pointers.meta | 8 + .../Playback/Pointers/ActiveCameraPointers.cs | 52 + .../Pointers/ActiveCameraPointers.cs.meta | 11 + .../Playback/Pointers/AnimationPointers.cs | 48 + .../Pointers/AnimationPointers.cs.meta | 11 + .../Playback/Pointers/CameraPointers.cs | 116 ++ .../Playback/Pointers/CameraPointers.cs.meta | 11 + .../Playback/Pointers/Materials.meta | 8 + .../Playback/Pointers/Materials/BaseColor.cs | 48 + .../Pointers/Materials/BaseColor.cs.meta | 11 + .../Playback/Pointers/Materials/Clearcoat.cs | 65 ++ .../Pointers/Materials/Clearcoat.cs.meta | 11 + .../Pointers/Materials/ClearcoatRoughness.cs | 46 + .../Materials/ClearcoatRoughness.cs.meta | 11 + .../Playback/Pointers/Materials/Emissive.cs | 46 + .../Pointers/Materials/Emissive.cs.meta | 11 + .../Pointers/Materials/Iridescence.cs | 67 ++ .../Pointers/Materials/Iridescence.cs.meta | 11 + .../Materials/IridescenceThickness.cs | 49 + .../Materials/IridescenceThickness.cs.meta | 11 + .../Pointers/Materials/MaterialPointers.cs | 174 +++ .../Materials/MaterialPointers.cs.meta | 11 + .../Pointers/Materials/MetallicRoughness.cs | 51 + .../Materials/MetallicRoughness.cs.meta | 11 + .../Playback/Pointers/Materials/Normal.cs | 48 + .../Pointers/Materials/Normal.cs.meta | 11 + .../Playback/Pointers/Materials/Occlusion.cs | 46 + .../Pointers/Materials/Occlusion.cs.meta | 11 + .../Playback/Pointers/Materials/Sheen.cs | 62 ++ .../Playback/Pointers/Materials/Sheen.cs.meta | 11 + .../Pointers/Materials/SheenRoughness.cs | 46 + .../Pointers/Materials/SheenRoughness.cs.meta | 11 + .../Playback/Pointers/Materials/Specular.cs | 62 ++ .../Pointers/Materials/Specular.cs.meta | 11 + .../Pointers/Materials/SpecularColor.cs | 46 + .../Pointers/Materials/SpecularColor.cs.meta | 11 + .../Playback/Pointers/Materials/Thickness.cs | 62 ++ .../Pointers/Materials/Thickness.cs.meta | 11 + .../Pointers/Materials/Transmission.cs | 60 ++ .../Pointers/Materials/Transmission.cs.meta | 11 + .../Playback/Pointers/MeshPointers.cs | 67 ++ .../Playback/Pointers/MeshPointers.cs.meta | 11 + .../Playback/Pointers/NodePointers.cs | 206 ++++ .../Playback/Pointers/NodePointers.cs.meta | 11 + .../Playback/Pointers/PointerResolver.cs | 147 +++ .../Playback/Pointers/PointerResolver.cs.meta | 11 + .../Playback/Pointers/Pointers.cs | 74 ++ .../Playback/Pointers/Pointers.cs.meta | 11 + .../Playback/Pointers/ScenePointers.cs | 24 + .../Playback/Pointers/ScenePointers.cs.meta | 11 + .../UnityGLTF.Interactivity.Playback.asmdef | 24 + ...ityGLTF.Interactivity.Playback.asmdef.meta | 7 + .../Playback/VariableInterpolationManager.cs | 178 ++++ .../VariableInterpolationManager.cs.meta | 11 + Runtime/Scripts/Loader/ResourcesLoader.cs | 23 + .../Scripts/Loader/ResourcesLoader.cs.meta | 11 + ...nityGLTF.Interactivity.Tests.Editor.asmdef | 6 +- Tests/Runtime/Interactivity.meta | 8 + Tests/Runtime/Interactivity/Common.meta | 8 + .../Interactivity/Common/MatrixTests.cs | 150 +++ .../Interactivity/Common/MatrixTests.cs.meta | 11 + .../Interactivity/Common/ParserTests.cs | 116 ++ .../Interactivity/Common/ParserTests.cs.meta | 11 + ...tyGLTF.Interactivity.Playback.Tests.asmdef | 26 + ...F.Interactivity.Playback.Tests.asmdef.meta | 7 + Tests/Runtime/Interactivity/Nodes.meta | 8 + .../Interactivity/Nodes/Animation.meta | 8 + .../Nodes/Animation/AnimationNodeTests.cs | 398 +++++++ .../Animation/AnimationNodeTests.cs.meta | 11 + ...eractivity.Playback.Tests.Animation.asmdef | 27 + ...ivity.Playback.Tests.Animation.asmdef.meta | 7 + Tests/Runtime/Interactivity/Nodes/Common.meta | 8 + .../Interactivity/Nodes/Common/Extensions.cs | 21 + .../Nodes/Common/Extensions.cs.meta | 11 + .../Nodes/Common/NodeTestHelpers.cs | 560 ++++++++++ .../Nodes/Common/NodeTestHelpers.cs.meta | 11 + .../Interactivity/Nodes/Common/SubGraphs.cs | 96 ++ .../Nodes/Common/SubGraphs.cs.meta | 11 + ...Interactivity.Playback.Tests.Common.asmdef | 26 + ...activity.Playback.Tests.Common.asmdef.meta | 7 + Tests/Runtime/Interactivity/Nodes/Event.meta | 8 + .../Nodes/Event/EventNodeTests.cs | 400 +++++++ .../Nodes/Event/EventNodeTests.cs.meta | 11 + ....Interactivity.Playback.Tests.Event.asmdef | 27 + ...ractivity.Playback.Tests.Event.asmdef.meta | 7 + Tests/Runtime/Interactivity/Nodes/Flow.meta | 8 + .../Interactivity/Nodes/Flow/DoNTests.cs | 197 ++++ .../Interactivity/Nodes/Flow/DoNTests.cs.meta | 11 + .../Nodes/Flow/FlowNodesTests.cs | 386 +++++++ .../Nodes/Flow/FlowNodesTests.cs.meta | 11 + .../Nodes/Flow/MultiGateTests.cs | 279 +++++ .../Nodes/Flow/MultiGateTests.cs.meta | 11 + .../Interactivity/Nodes/Flow/SetDelayTests.cs | 239 +++++ .../Nodes/Flow/SetDelayTests.cs.meta | 11 + .../Interactivity/Nodes/Flow/ThrottleTests.cs | 300 ++++++ .../Nodes/Flow/ThrottleTests.cs.meta | 11 + ...F.Interactivity.Playback.Tests.Flow.asmdef | 27 + ...eractivity.Playback.Tests.Flow.asmdef.meta | 7 + .../Interactivity/Nodes/Flow/WaitAllTests.cs | 121 +++ .../Nodes/Flow/WaitAllTests.cs.meta | 11 + Tests/Runtime/Interactivity/Nodes/Math.meta | 8 + .../Nodes/Math/MathNodesTests.cs | 994 ++++++++++++++++++ .../Nodes/Math/MathNodesTests.cs.meta | 11 + ...F.Interactivity.Playback.Tests.Math.asmdef | 27 + ...eractivity.Playback.Tests.Math.asmdef.meta | 7 + .../Runtime/Interactivity/Nodes/Pointer.meta | 8 + .../Nodes/Pointer/MaterialPointers.cs | 105 ++ .../Nodes/Pointer/MaterialPointers.cs.meta | 11 + .../Nodes/Pointer/PointerNodesTests.cs | 552 ++++++++++ .../Nodes/Pointer/PointerNodesTests.cs.meta | 11 + ...nteractivity.Playback.Tests.Pointer.asmdef | 27 + ...ctivity.Playback.Tests.Pointer.asmdef.meta | 7 + Tests/Runtime/Interactivity/Nodes/Type.meta | 8 + .../Nodes/Type/TypeNodesTests.cs | 60 ++ .../Nodes/Type/TypeNodesTests.cs.meta | 11 + ...F.Interactivity.Playback.Tests.Type.asmdef | 27 + ...eractivity.Playback.Tests.Type.asmdef.meta | 7 + .../Runtime/Interactivity/Nodes/Variable.meta | 8 + ...teractivity.Playback.Tests.Variable.asmdef | 27 + ...tivity.Playback.Tests.Variable.asmdef.meta | 7 + .../Nodes/Variable/VariableNodeTests.cs | 326 ++++++ .../Nodes/Variable/VariableNodeTests.cs.meta | 11 + 587 files changed, 21794 insertions(+), 1 deletion(-) create mode 100644 Runtime/Resources/animation_test.bytes create mode 100644 Runtime/Resources/animation_test.bytes.meta create mode 100644 Runtime/Resources/pointer_test.bytes create mode 100644 Runtime/Resources/pointer_test.bytes.meta create mode 100644 Runtime/Scripts/Interactivity/Playback.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Common.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/Parser.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/Parser.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Export.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Import.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Serialization.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/EventWrapper.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/EventWrapper.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/ICancelToken.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Animation.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Debug.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Pointer.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef create mode 100644 Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs.meta create mode 100644 Runtime/Scripts/Loader/ResourcesLoader.cs create mode 100644 Runtime/Scripts/Loader/ResourcesLoader.cs.meta create mode 100644 Tests/Runtime/Interactivity.meta create mode 100644 Tests/Runtime/Interactivity/Common.meta create mode 100644 Tests/Runtime/Interactivity/Common/MatrixTests.cs create mode 100644 Tests/Runtime/Interactivity/Common/MatrixTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Common/ParserTests.cs create mode 100644 Tests/Runtime/Interactivity/Common/ParserTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef create mode 100644 Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Animation.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Common.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Event.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Math.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Pointer.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Type.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Variable.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef create mode 100644 Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef.meta create mode 100644 Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs create mode 100644 Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs.meta diff --git a/Runtime/Resources/animation_test.bytes b/Runtime/Resources/animation_test.bytes new file mode 100644 index 0000000000000000000000000000000000000000..5e5486d0ceac6c96947aad2e191da23b21b30a95 GIT binary patch literal 2788 zcmd5;Pj4GV6rY9yZJ;fbHb_WF)^Mw~?(TZ+*p?bQHL2v1*eK4nNKr-B_QY9a@0$G+ zTC7NW;45%K;sZE*01hB};LtAsM=tclN8rS7W_KKW*!C-kaT|*WY`% zLI`=aiscPL9<`c{5w(1u1++Y(J?1da3S5tt=_f~?>$rZVhb@^0edctTm#N=tW{!=l zo-LH9LXVl}+pYs=J*(0ah58)?pk*~dJ}vK$sACPFqb*@msN;4SH>gifo}Nh~I$-{h z*qZp)!Zt&Wz_T2`Zv`UHzN#2mO)nG-L(3U?T{DV?5(lyzdmy~xq@5$naacbVd}}c5 zGaL$q1(w%~56cS?s-YDi^Gv1_QCnO%FJ!YL3c$JIP;p$_8HVVap-|fim^XA~opih2 z*xKAh`ED2ppZtk9E=pvcZ6$Se-LTzfExXIQv>bRLLqFo2JBc464BOrg3#@+M?(Di@ z@5o_3vX5xn@>$L8yWT^qBPO=5W{awLG_9noCB3Bl8;yo343Y#g6#C@JToYY3FS^3# zBnCK{Y%-~#XAkVaK2D5Q5O{Vw47fO=pEg_dy?V1D8s2E`?rd(;vZm0ztof&F{KPh{?fzX&5x-CbVi$e9RmJDhZPs->E)8+fZSnas<}ggmG> z@CPBPB=pM?%{i>h?5CKV<`*A%1=Xxn~JAV$qBiOhcC3azej_bL9RJla1k@v_Y(3|8c z{yDmd^(MJZ-Y2)nb*$IPyW|~mnXF=6C2Qn$vO=z4y+Yn1H^|!vcmwnrIUV1*e>xuD zhp+*>VVb7MW18PW9*caY`5W+Gz@&0IUagqsZPb+kz5|>Gt^z*-Hh~X-zW@$_dHsYu zyY@{LECP`a|Bk9)5r}-f_GJ|;0+Gs}PpV)Mh#|#_B5`Pv9SZ j{(gdA{!yLa7a#q|>l8lG@4xbM2@KAkNUZVlzrgW~WO`vIj1rl;LKlX_4F{@F;<)CGkVRinwz!{5n}AR05A^ zEEYiwlm{u52tLk8PMJs|q4`deVhh18ru+;gHUNy=n?wZIIdQGxNnq)WFq+^Rnd+QL zSh@n0uqDtnc+8haoeD%0QSvkUr>1ImkE#<(ZSNMW+ zY?s^vN#i&Qs+y|tq*$z&Ou|olvYJ=977KOL0$0ixN+Lavl1N7TB2A^_(Q+qMn33=P zbTAwEW9`A%pLo3yaZ!cY&CkB?PrjfRu7d+aY!w9x(mEJ}m|WXQ15AmOKRdtNt=GWs zB6!fGL7Hq+h7-@8wrJj!?Q~G&bjR?}w-wnV`fHo$9Xgjpc%bJN`luqQ5#;Ws-@*f) zkvg+->SgAa(9VCLowM4g!Fg@HsHejDirUkDZ`7OU(|qW?u98-0`6OWkER^^rv_F|d z#r}G&KMVL$Ny-jdMmbtg4D}))eLW}YoB0k=cI5;9nk0bi`i=f v+ii7-8o%+ocs&^7wf_6{xgHrt8LNBwT<)mHuRWLef9<<4E{zN09bW$eUG2AR literal 0 HcmV?d00001 diff --git a/Runtime/Resources/pointer_test.bytes.meta b/Runtime/Resources/pointer_test.bytes.meta new file mode 100644 index 000000000..f2502bc6f --- /dev/null +++ b/Runtime/Resources/pointer_test.bytes.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6713fee65a2319c41b19b8fa33e245ba +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback.meta b/Runtime/Scripts/Interactivity/Playback.meta new file mode 100644 index 000000000..6d4b64da8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aecfb60c706f2d4458f46087b27fcb89 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs b/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs new file mode 100644 index 000000000..525a8cb83 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Pool; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct AnimationPlayData + { + public int index; + public float startTime; + public float endTime; + public float stopTime; + public float speed; + public float unityStartTime; + public Action endDone; + public Action stopDone; + } + + public class AnimationData + { + public float playhead; + public float virtualPlayhead; + public AnimationState anim; + + public AnimationData(AnimationState anim) + { + this.anim = anim; + } + } + + public class AnimationWrapper : MonoBehaviour + { + public Animation animationComponent { get; private set; } + + private AnimationData _currentAnimation; + + private readonly Dictionary _animationsInProgress = new(); + private AnimationData[] _animations; + + private BehaviourEngine _engine; + + public void SetData(BehaviourEngine behaviourEngine, Animation animationComponent) + { + if (_engine != null) + _engine.onTick -= OnTick; + + _engine = behaviourEngine; + _engine.onTick += OnTick; + this.animationComponent = animationComponent; + + var clipCount = animationComponent.GetClipCount(); + _animations = new AnimationData[clipCount]; + + var j = 0; + + foreach (AnimationState state in animationComponent) + { + state.speed = 0f; + _animations[j++] = new AnimationData(state); + } + } + + private void OnTick() + { + // Avoiding iterating over a changing collection by grabbing a pooled dictionary. + var temp = DictionaryPool.Get(); + try + { + foreach (var anim in _animationsInProgress) + { + temp.Add(anim.Key, anim.Value); + } + + foreach (var anim in temp) + { + SampleAnimation(anim.Value); + } + } + finally + { + DictionaryPool.Release(temp); + } + } + + // This logic path hurts my soul but it's taken directly from the spec. + // A lot harder to follow than what we had before. + private bool SampleAnimation(AnimationPlayData a) + { + float r, T; + + T = _animations[a.index].anim.length; + + if (a.startTime == a.endTime) + { + r = a.startTime; + CompleteAnimation(r, a.endDone); + return false; + } + + var scaledElapsedTime = (Time.time - a.unityStartTime) * a.speed; + + if (a.startTime > a.endTime) + scaledElapsedTime *= -1; + + r = scaledElapsedTime + a.startTime; + + var c1 = a.startTime < a.endTime && r >= a.stopTime && a.stopTime >= a.startTime && a.stopTime < a.endTime; + var c2 = a.startTime > a.endTime && r <= a.stopTime && a.stopTime <= a.startTime && a.stopTime > a.endTime; + + if (c1 || c2) + { + r = a.stopTime; + Util.Log($"Stopping Animation {a.index}."); + CompleteAnimation(r, a.stopDone); + return false; + } + + var c3 = a.startTime < a.endTime && r >= a.endTime; + var c4 = a.startTime > a.endTime && r <= a.endTime; + + if (c3 || c4) + { + r = a.endTime; + Util.Log($"Done Animation {a.index}."); + CompleteAnimation(r, a.endDone); + return false; + } + + SampleAnimationAtTime(r); + + return true; + + float GetTimeStamp(float r) + { + var s = r > 0 ? Mathf.Ceil((r - T) / T) : Mathf.Floor(r / T); + return T == 0 ? 0 : r - s * T; + } + + void SampleAnimationAtTime(float r) + { + var t = GetTimeStamp(r); + _animations[a.index].playhead = t; + _animations[a.index].virtualPlayhead = r; + _animations[a.index].anim.time = t; + animationComponent.Sample(); + } + + void CompleteAnimation(float t, Action callback) + { + SampleAnimationAtTime(t); + StopAnimation(a.index); + callback(); + } + } + + public void PlayAnimation(in AnimationPlayData data) + { + StopAnimation(data.index); + + _animationsInProgress.Add(data.index, data); + + _currentAnimation = _animations[data.index]; + animationComponent.clip = _currentAnimation.anim.clip; + animationComponent.Play(); + } + + internal void StopAnimationAt(int animationIndex, float stopTime, Action callback) + { + var anim = _animationsInProgress[animationIndex]; + + anim.stopTime = stopTime; + anim.stopDone = callback; + + _animationsInProgress[animationIndex] = anim; + } + + internal void StopAnimation(int index) + { + _animationsInProgress.Remove(index); + } + + public bool IsAnimationPlaying(int index) + { + return _animationsInProgress.ContainsKey(index); + } + + public float GetAnimationMaxTime(int index) + { + return _animations[index].anim.length; + } + + public float GetPlayhead(int index) + { + return _animations[index].playhead; + } + + public float GetVirtualPlayhead(int index) + { + return _animations[index].virtualPlayhead; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs.meta b/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs.meta new file mode 100644 index 000000000..fd221a677 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a5e102fdf6fdfa4cb2c9c3f28ea8b32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs new file mode 100644 index 000000000..137ec73e2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Assertions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class BehaviourEngine + { + public readonly Graph graph; + public readonly Dictionary engineNodes = new(); + public AnimationWrapper animationWrapper { get; private set; } + public readonly PointerInterpolationManager pointerInterpolationManager = new(); + public readonly VariableInterpolationManager variableInterpolationManager = new(); + public readonly NodeDelayManager nodeDelayManager = new(); + + public event Action onStart; + + public event Action onSelect; + public event Action onHoverIn; + public event Action onHoverOut; + public event Action onTick; + public event Action> onCustomEventFired; + public event Action onFlowTriggered; + + public readonly PointerResolver pointerResolver; + + public BehaviourEngine(Graph graph, GLTFSceneImporter importer) + { + this.graph = graph; + pointerResolver = importer != null ? new PointerResolver(importer) : null; + + for (int i = 0; i < graph.nodes.Count; i++) + { + engineNodes.Add(graph.nodes[i], NodeRegistry.CreateBehaviourEngineNode(this, graph.nodes[i])); + } + } + + public void StartPlayback() + { + onStart?.Invoke(); + } + + public void Tick() + { + pointerInterpolationManager.OnTick(); + variableInterpolationManager.OnTick(); + nodeDelayManager.OnTick(); + + onTick?.Invoke(); + } + + public void Select(in RayArgs args) + { + onSelect?.Invoke(args); + } + + public void HoverIn(in RayArgs args) + { + onHoverIn?.Invoke(args); + } + + public void HoverOut(in RayArgs args) + { + onHoverOut?.Invoke(args); + } + + public void ExecuteFlow(Flow flow) + { + Assert.IsNotNull(flow.toNode); + + var node = engineNodes[flow.toNode]; + + onFlowTriggered?.Invoke(flow); + + node.ValidateAndExecute(flow.toSocket); + } + + public void FireCustomEvent(int eventIndex, Dictionary outValues = null) + { + if (eventIndex < 0 || eventIndex >= graph.customEvents.Count) + return; // TODO: Add error handling. + + onCustomEventFired?.Invoke(eventIndex, outValues); + } + + public IProperty ParseValue(Value v) + { + if (v.node == null) + return v.property; + + var node = engineNodes[v.node]; + return node.GetOutputValue(v.socket); + } + + public IProperty GetVariableProperty(int variableIndex) + { + return graph.variables[variableIndex].property; + } + + public bool TryGetPointer(string pointerString, BehaviourEngineNode engineNode, out IPointer pointer) + { + try + { + pointer = pointerResolver.GetPointer(pointerString, engineNode); + return true; + } + catch (Exception ex) + { + Debug.LogWarning(ex); + + pointer = default; + return false; + } + } + + public void SetAnimationWrapper(AnimationWrapper wrapper, Animation animation) + { + animationWrapper = wrapper; + wrapper.SetData(this, animation); + pointerResolver.RegisterAnimations(wrapper); + } + + public void PlayAnimation(in AnimationPlayData data) + { + if (!HasAnimationWrapper()) + return; + + animationWrapper.PlayAnimation(data); + } + + public void StopAnimation(int index) + { + if (!HasAnimationWrapper()) + return; + + animationWrapper.StopAnimation(index); + } + + public void StopAnimationAt(int index, float stopTime, Action callback) + { + if (!HasAnimationWrapper()) + return; + + animationWrapper.StopAnimationAt(index, stopTime, callback); + } + + public bool HasAnimationWrapper() + { + if (animationWrapper == null) + { + Util.LogWarning("Tried to play an animation on a glb that has no animations."); + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs.meta b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs.meta new file mode 100644 index 000000000..875cb32ee --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b91a43a02fbbd094a8528199845b6c0d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs new file mode 100644 index 000000000..0b31a07b7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Unity.Properties; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + // Made partial so that extension methods could be added elsewhere without bloating this code file with a bunch of overloaded methods. + // Did not use actual extension methods for this since they would require the use of "this" keyword to access if used in a node script. + public abstract partial class BehaviourEngineNode + { + public enum ValidationResult + { + Valid = 0, + InvalidConfiguration = 1, + InvalidFlow = 2, + InvalidValue = 3 + } + + public readonly BehaviourEngine engine; + public readonly Node node; + + public readonly Dictionary values = new(); + public readonly Dictionary flows = new(); + public readonly Dictionary configuration = new(); + + public BehaviourEngineNode(BehaviourEngine engine, Node node) + { + this.node = node; + this.engine = engine; + + for (int i = 0; i < node.values.Count; i++) + { + values.Add(node.values[i].id, node.values[i]); + Util.Log($"Adding value {node.values[i].id} to BehaviourGraphNode {node.type}"); + } + + for (int i = 0; i < node.flows.Count; i++) + { + flows.Add(node.flows[i].fromSocket, node.flows[i]); + Util.Log($"Adding flow {node.flows[i].fromSocket} to BehaviourGraphNode {node.type}"); + } + + for (int i = 0; i < node.configuration.Count; i++) + { + configuration.Add(node.configuration[i].id, node.configuration[i]); + Util.Log($"Adding config {node.configuration[i].id} to BehaviourGraphNode {node.type}"); + } + + Util.Log($"Finished creating BehaviourGraphNode {node.type}"); + } + + public void ValidateAndExecute(string socket) + { + Execute(socket, Validate(socket)); + } + + protected virtual void Execute(string socket, ValidationResult validationResult) { } + public virtual IProperty GetOutputValue(string socket) => null; + public virtual bool ValidateConfiguration(string socket) => true; + public virtual bool ValidateFlows(string socket) => true; + public virtual bool ValidateValues(string socket) => true; + + public ValidationResult Validate(string socket) + { + if (!ValidateConfiguration(socket)) + return ValidationResult.InvalidConfiguration; + + if (!ValidateFlows(socket)) + return ValidationResult.InvalidFlow; + + if (!ValidateValues(socket)) + return ValidationResult.InvalidValue; + + return ValidationResult.Valid; + } + + public bool TryExecuteFlow(string outputSocketName) + { + var hasFlow = flows.TryGetValue(outputSocketName, out Flow flow); + + if (hasFlow) + engine.ExecuteFlow(flow); + + return hasFlow; + } + + public bool TryEvaluateValue(string valueId, out IProperty value) + { + try + { + value = engine.ParseValue(values[valueId]); + return true; + } + catch (Exception ex) + { + Debug.LogException(ex); + value = default; + return false; + } + } + + public bool TryGetConfig(string id, out T value) + { + try + { + value = ((Property)Helpers.CreateProperty(typeof(T), configuration[id].value)).value; + return true; + } + catch (Exception ex) + { + Debug.LogException(ex); + value = default; + return false; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs.meta b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs.meta new file mode 100644 index 000000000..1d8b25640 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba651792abaac4c48914a906d17e93c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Common.meta b/Runtime/Scripts/Interactivity/Playback/Common.meta new file mode 100644 index 000000000..4fa9456a0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 997200d57dfb9004aa30ab0c78bbdb6d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs b/Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs new file mode 100644 index 000000000..577d03437 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs @@ -0,0 +1,45 @@ +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Extensions +{ + public static class MatrixExtensions + { + public static float4x4 LerpToComponentwise(this float4x4 from, float4x4 to, float t) + { + var c0 = math.lerp(from.c0, to.c0, t); + var c1 = math.lerp(from.c1, to.c1, t); + var c2 = math.lerp(from.c2, to.c2, t); + var c3 = math.lerp(from.c3, to.c3, t); + + var m = new float4x4(c0, c1, c2, c3); + + return m; + } + + ///// + ///// Added because Unity's Mathematics library has a bug where float4x4.TRS creates a TSR matrix. + ///// + //public static float4x4 TRS(float3 translation, quaternion rotation, float3 scale) + //{ + // float3x3 m = math.mul(Unity.Mathematics.float3x3.Scale(scale), new float3x3(rotation)); + // return new float4x4(new float4(m.c0, 0.0f), + // new float4(m.c1, 0.0f), + // new float4(m.c2, 0.0f), + // new float4(translation, 1.0f)); + //} + + /// + /// Added because Unity's Mathematics library has a bug where float4x4.TRS creates a TSR matrix. + /// + public static float4x4 TRS(float3 translation, quaternion rotation, float3 scale) + { + var c0 = new float4(math.mul(rotation, new float3(scale.x, 0f, 0f)), 0f); + var c1 = new float4(math.mul(rotation, new float3(0f, scale.y, 0f)), 0f); + var c2 = new float4(math.mul(rotation, new float3(0f, 0f, scale.z)), 0f); + var c3 = new float4(math.mul(rotation, translation), 1f); + + return new float4x4(c0, c1, c2, c3); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs.meta b/Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs.meta new file mode 100644 index 000000000..ff218f6a0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/MatrixExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e351ffc454324d740a064950931bded6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Common/Parser.cs b/Runtime/Scripts/Interactivity/Playback/Common/Parser.cs new file mode 100644 index 000000000..6f0ff7b03 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/Parser.cs @@ -0,0 +1,190 @@ +using Newtonsoft.Json.Linq; +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct Color3 + { + public float r; + public float g; + public float b; + + public Color3(float r, float g, float b) + { + this.r = r; + this.g = g; + this.b = b; + } + + public static implicit operator Color(Color3 c) => new Color(c.r, c.g, c.b, 1f); + public static implicit operator Color3(Color c) => new Color3(c.r, c.g, c.b); + } + + public static class Parser + { + public static float ToFloat(JArray jArray) + { + if (jArray == null) + return float.NaN; + + return jArray[0].Value(); + } + + public static int ToInt(JArray jArray) + { + if (jArray == null) + return 0; + + return jArray[0].Value(); + } + + public static bool ToBool(JArray jArray) + { + if (jArray == null) + return false; + + return jArray[0].Value(); + } + + public static string ToString(JArray jArray) + { + if (jArray == null) + return ""; + + return jArray[0].Value(); + } + + public static float2 ToFloat2(JArray jArray) + { + if (jArray == null) + return new float2(float.NaN, float.NaN); + + return new float2(jArray[0].Value(), jArray[1].Value()); + } + + public static float3 ToFloat3(JArray jArray) + { + if (jArray == null) + return new float3(float.NaN, float.NaN, float.NaN); + + return new float3(jArray[0].Value(), jArray[1].Value(), jArray[2].Value()); + } + + public static float4 ToFloat4(JArray jArray) + { + if (jArray == null) + return new float4(float.NaN, float.NaN, float.NaN, float.NaN); + + return new float4(jArray[0].Value(), jArray[1].Value(), jArray[2].Value(), jArray[3].Value()); + } + + public static int[] ToIntArray(JArray jArray) + { + if (jArray == null) + return null; + + var arr = new int[jArray.Count]; + + for (int i = 0; i < arr.Length; i++) + { + arr[i] = jArray[i].Value(); + } + + return arr; + } + + public static float2x2 ToFloat2x2(JArray jArray) + { + const int MATRIX_SIZE = 4; + + if (jArray == null) + { + var m = new float2x2(); + + for (int i = 0; i < MATRIX_SIZE; i++) + { + m[i] = float.NaN; + } + + return m; + } + + // GLTF floatNxN are column-major and Unity.Mathematics floatNxN are ROW-MAJOR so we need to be careful. + var c0 = new Vector2(v(0), v(1)); + var c1 = new Vector2(v(2), v(3)); + + return new float2x2(c0, c1); + + // Helper to reduce bloat. + float v(int index) + { + return jArray[index].Value(); + } + } + + public static float3x3 ToFloat3x3(JArray jArray) + { + const int MATRIX_SIZE = 9; + + + if (jArray == null) + { + var m = new float3x3(); + + for (int i = 0; i < MATRIX_SIZE; i++) + { + m[i] = float.NaN; + } + + return m; + } + + // GLTF floatNxN are column-major and Unity.Mathematics floatNxN are ROW-MAJOR so we need to be careful. + var c0 = new float3(v(0), v(1), v(2)); + var c1 = new float3(v(3), v(4), v(5)); + var c2 = new float3(v(6), v(7), v(8)); + + return new float3x3(c0, c1, c2); + + // Helper to reduce bloat. + float v(int index) + { + return jArray[index].Value(); + } + } + + public static float4x4 ToFloat4x4(JArray jArray) + { + const int MATRIX_SIZE = 16; + + if (jArray == null) + { + var m = new float4x4(); + + for (int i = 0; i < MATRIX_SIZE; i++) + { + m[i] = float.NaN; + } + + return m; + } + + // Unity Matrix4x4 and GLTF both use Column-Major matrices so we can do a 1:1 transfer. + // Unity.Mathematics.float4x4 is ROW-MAJOR so we need to be careful. + var c0 = new float4(v(0), v(1), v(2), v(3)); + var c1 = new float4(v(4), v(5), v(6), v(7)); + var c2 = new float4(v(8), v(9), v(10), v(11)); + var c3 = new float4(v(12), v(13), v(14), v(15)); + + return new float4x4(c0, c1, c2, c3); + + // Helper to reduce bloat. + float v(int index) + { + return jArray[index].Value(); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Common/Parser.cs.meta b/Runtime/Scripts/Interactivity/Playback/Common/Parser.cs.meta new file mode 100644 index 000000000..15e5169e2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/Parser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02831f24b24175b4f8d51859fbe0e348 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs b/Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs new file mode 100644 index 000000000..398f8ed82 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs @@ -0,0 +1,120 @@ +#define DEBUG_MESSAGES + +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public ref struct StringSpanReader + { + private int _start; + private int _end; + private readonly ReadOnlySpan _buffer; + public char this[int i] + { + get + { + if (_start + i > _end) + throw new IndexOutOfRangeException(); + + return _buffer[_start + i]; + } + } + + public StringSpanReader(ReadOnlySpan buffer) + { + _buffer = buffer; + _start = 0; + _end = buffer.Length; + } + + public bool AdvanceHeadToNextInstanceOfChar(char c, bool inclusive = false) + { + // When the character we're already starting from is the one we want to advance to, advance the head by 1. + if (_buffer[_start] == c) + _start++; + + for (int i = _start; i < _buffer.Length; i++) + { + if (_buffer[i] != c) + continue; + + _start = i + (inclusive ? 0 : 1); + return true; + } + + return false; + } + + public bool AdvanceTailToFirstInstanceOfChar(char c, bool inclusive = false) + { + for (int i = _start; i < _buffer.Length; i++) + { + if (_buffer[i] != c) + continue; + + _end = i + (inclusive ? 1 : 0); + return true; + } + + return false; + } + + public bool AdvanceToNextToken(char c) + { + if (!AdvanceHeadToNextInstanceOfChar(c)) + return false; + + if (!AdvanceTailToFirstInstanceOfChar(c)) + _end = _buffer.Length; + + return true; + } + + public void Slice(char startChar, char endChar, bool inclusive = false) + { + for (int i = _start; i < _end; i++) + { + if (_buffer[i] != startChar) + continue; + + _start = i + (inclusive ? 0 : 1); + break; + } + + for (int i = _start; i < _end; i++) + { + if (_buffer[i] != endChar) + continue; + + _end = i + (inclusive ? 1 : 0); + break; + } + } + + public ReadOnlySpan AsReadOnlySpan() + { + return _buffer.Slice(_start, _end - _start); + } + + public bool AnyMatch(char a, ReadOnlySpan characters) + { + for (int i = 0; i < characters.Length; i++) + { + if (characters[i] == a) + return true; + } + + return false; + } + + public override string ToString() + { + return AsReadOnlySpan().ToString(); + } + + internal void SetStartIndexToEndIndex() + { + _start = _end; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs.meta b/Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs.meta new file mode 100644 index 000000000..a68313305 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/StringSpanReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3853859abbb40d4da6936aeeace06c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs b/Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs new file mode 100644 index 000000000..9769936ec --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs @@ -0,0 +1,179 @@ +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Extensions +{ + public static class TransformExtensions + { + public static void Decompose( + this Matrix4x4 m, + out Vector3 translation, + out Quaternion rotation, + out Vector3 scale + ) + { + translation = new Vector3(m.m03, m.m13, m.m23); + var mRotScale = new float3x3( + m.m00, m.m01, m.m02, + m.m10, m.m11, m.m12, + m.m20, m.m21, m.m22 + ); + mRotScale.Decompose(out float4 mRotation, out float3 mScale); + rotation = new Quaternion(mRotation.x, mRotation.y, mRotation.z, mRotation.w); + scale = new Vector3(mScale.x, mScale.y, mScale.z); + } + + /// + /// Decomposes a 4x4 TRS matrix into separate transforms (translation * rotation * scale) + /// Matrix may not contain skew + /// + /// Translation + /// Rotation + /// Scale + public static void Decompose( + this float4x4 m, + out float3 translation, + out float4 rotation, + out float3 scale + ) + { + var mRotScale = new float3x3( + m.c0.xyz, + m.c1.xyz, + m.c2.xyz + ); + mRotScale.Decompose(out rotation, out scale); + translation = m.c3.xyz; + } + + /// + /// Decomposes a 3x3 matrix into rotation and scale + /// + /// Rotation quaternion values + /// Scale + public static void Decompose(this float3x3 m, out float4 rotation, out float3 scale) + { + var lenC0 = math.length(m.c0); + var lenC1 = math.length(m.c1); + var lenC2 = math.length(m.c2); + + float3x3 rotationMatrix; + rotationMatrix.c0 = m.c0 / lenC0; + rotationMatrix.c1 = m.c1 / lenC1; + rotationMatrix.c2 = m.c2 / lenC2; + + scale.x = lenC0; + scale.y = lenC1; + scale.z = lenC2; + + if (rotationMatrix.IsNegative()) + { + rotationMatrix *= -1f; + scale *= -1f; + } + + // Inlined normalize(rotationMatrix) + rotationMatrix.c0 = math.normalize(rotationMatrix.c0); + rotationMatrix.c1 = math.normalize(rotationMatrix.c1); + rotationMatrix.c2 = math.normalize(rotationMatrix.c2); + + rotation = new quaternion(rotationMatrix).value; + } + + static bool IsNegative(this float3x3 m) + { + var cross = math.cross(m.c0, m.c1); + return math.dot(cross, m.c2) < 0f; + } + + public static float4x4 GetWorldMatrix(this Transform t, bool worldSpace, bool rightHanded) + { + float3 pos; + quaternion rot; + float3 scale; + + if (worldSpace) + { + pos = t.position; + rot = t.rotation; + scale = t.lossyScale; + } + else + { + pos = t.localPosition; + rot = t.localRotation; + scale = t.localScale; + } + + var m = float4x4.TRS(pos, rot, scale); + + if (rightHanded) + { + var c0 = new float4(-1f, 0f, 0f, 0f); + var c1 = new float4(0f, 1f, 0f, 0f); + var c2 = new float4(0f, 0f, 1f, 0f); + var c3 = new float4(0f, 0f, 0f, 1f); + var c = new float4x4(c0, c1, c2, c3); + + m = math.mul(math.mul(c, m), math.transpose(c)); + } + + return m; + } + + public static void SetWorldMatrix(this Transform t, float4x4 m, bool worldSpace, bool rightHanded) + { + if (rightHanded) + { + var c0 = new float4(-1f, 0f, 0f, 0f); + var c1 = new float4(0f, 1f, 0f, 0f); + var c2 = new float4(0f, 0f, 1f, 0f); + var c3 = new float4(0f, 0f, 0f, 1f); + var c = new float4x4(c0, c1, c2, c3); + + m = math.mul(math.mul(c, m), math.transpose(c)); + } + + Decompose((Matrix4x4)m, out var pos, out var rot, out var scale); + + if (worldSpace) + { + t.SetPositionAndRotation(pos, rot); + t.SetGlobalScale(scale); + } + else + { + t.SetLocalPositionAndRotation(pos, rot); + t.localScale = scale; + } + + } + + public static void SetGlobalScale(this Transform transform, Vector3 globalScale) + { + transform.localScale = Vector3.one; + transform.localScale = new Vector3(globalScale.x / transform.lossyScale.x, globalScale.y / transform.lossyScale.y, globalScale.z / transform.lossyScale.z); + } + + public static float3 SwapHandedness(this float3 v) + { + return new float3(-v.x, v.y, v.z); + } + + public static Vector3 SwapHandedness(this Vector3 v) + { + return new Vector3(-v.x, v.y, v.z); + } + + public static Quaternion SwapHandedness(this Quaternion q) + { + // TODO: Figure out if there's a way to do this without converting to euler angles and back as it's really slow. + var euler = q.eulerAngles; + + euler.y *= -1; + euler.z *= -1; + + return Quaternion.Euler(euler); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs.meta b/Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs.meta new file mode 100644 index 000000000..3a46e0164 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/Transforms.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db8f24ee4e775634b98a5d8e41431d86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef b/Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef new file mode 100644 index 000000000..37dad54e7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef @@ -0,0 +1,16 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Common", + "rootNamespace": "", + "references": [ + "GUID:d8b63aba1907145bea998dd612889d6b" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef.meta b/Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef.meta new file mode 100644 index 000000000..f3beacc58 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/UnityGLTF.Interactivity.Playback.Common.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b5e009300cbc0584fa5c0fa797ef4c5f +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs b/Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs new file mode 100644 index 000000000..f433b6e37 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs @@ -0,0 +1,108 @@ +using System; +using System.Buffers; + +namespace UnityGLTF.Interactivity.Playback +{ + public ref struct ValueStringBuilder + { + private int _bufferPosition; + private Span _buffer; +#nullable enable + private char[]? _arrayFromPool; +#nullable disable + + public int length => _bufferPosition; + + public ValueStringBuilder(int initialSize) + { + _bufferPosition = 0; + _buffer = ArrayPool.Shared.Rent(initialSize); + _arrayFromPool = null; + } + + public ref char this[int index] => ref _buffer[index]; + + public int FirstIndexOf(char c) + { + for (int i = 0; i < _bufferPosition; i++) + { + if (_buffer[i] == c) + return i; + } + + return -1; + } + + public int LastIndexOf(char c) + { + var lastIndex = -1; + for (int i = 0; i < _bufferPosition; i++) + { + if (_buffer[i] == c) + lastIndex = i; + } + + return lastIndex; + } + + public void Append(char c) + { + if (_bufferPosition >= _buffer.Length - 1) + { + Grow(); + } + + _buffer[_bufferPosition++] = c; + } + + public void Clear() + { + _bufferPosition = 0; + } + + public void Append(ReadOnlySpan str) + { + var newSize = str.Length + _bufferPosition; + if (newSize > _buffer.Length) + Grow(newSize * 2); + + str.CopyTo(_buffer[_bufferPosition..]); + _bufferPosition += str.Length; + } + + public void AppendLine(ReadOnlySpan str) + { + Append(str); + Append(Environment.NewLine); + } + public void MoveBufferPositionBack(int numIndices) + { + _bufferPosition -= numIndices; + } + + public override string ToString() => new(_buffer[.._bufferPosition]); + public string ToString(int start, int end) => new(_buffer[start..end]); + + public void Dispose() + { + if (_arrayFromPool is not null) + { + ArrayPool.Shared.Return(_arrayFromPool); + } + } + + private void Grow(int capacity = 0) + { + var currentSize = _buffer.Length; + var newSize = capacity > 0 ? capacity : currentSize * 2; + var rented = ArrayPool.Shared.Rent(newSize); + var oldBuffer = _arrayFromPool; + _buffer.CopyTo(rented); + _buffer = _arrayFromPool = rented; + if (oldBuffer is not null) + { + ArrayPool.Shared.Return(oldBuffer); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs.meta b/Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs.meta new file mode 100644 index 000000000..91e45330d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Common/ValueStringBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6c19f21043541db4293e37879dcddb66 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context.meta b/Runtime/Scripts/Interactivity/Playback/Context.meta new file mode 100644 index 000000000..f385bcd46 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f6bfdbecd52b066499518637387da115 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export.meta b/Runtime/Scripts/Interactivity/Playback/Context/Export.meta new file mode 100644 index 000000000..d4e3afcad --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0bf929f47782ad44bbf4ee6671189509 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs new file mode 100644 index 000000000..94df388c6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs @@ -0,0 +1,77 @@ +using GLTF.Schema; +using System.Linq; +using UnityEngine; +using UnityGLTF.Plugins; + +namespace UnityGLTF.Interactivity.Playback +{ + + public class InteractivityExportContext : GLTFExportPluginContext + { + public override void AfterMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) + { + Util.Log($"InteractivityExportContext::AfterMaterialExport "); + } + public override void AfterMeshExport(GLTFSceneExporter exporter, Mesh mesh, GLTFMesh gltfMesh, int index) + { + Util.Log($"InteractivityExportContext::AfterMeshExport "); + } + public override void AfterNodeExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, GLTF.Schema.Node node) + { + Util.Log($"InteractivityExportContext::AfterNodeExport "); + } + public override void AfterPrimitiveExport(GLTFSceneExporter exporter, Mesh mesh, MeshPrimitive primitive, int index) + { + Util.Log($"InteractivityExportContext::AfterPrimitiveExport "); + } + public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) + { + Util.Log($"InteractivityExportContext::AfterSceneExport "); + + if (exporter.RootTransforms == null) return; + EventWrapper wrapper = null; + Transform t; + + // This assumes that EventWrapper exists on one of the root transforms which I think must be true due to how we import. + foreach (var transform in exporter.RootTransforms) + { + t = transform; + while (t.parent != null) + { + if (t.parent.TryGetComponent(out wrapper)) + break; + + t = t.parent; + } + } + + if (wrapper == null) + return; + + exporter.DeclareExtensionUsage(InteractivityGraphExtension.EXTENSION_NAME, true); + gltfRoot.AddExtension(InteractivityGraphExtension.EXTENSION_NAME, new InteractivityGraphExtension(wrapper.extensionData)); + } + public override void AfterTextureExport(GLTFSceneExporter exporter, GLTFSceneExporter.UniqueTexture texture, int index, GLTFTexture tex) + { + Util.Log($"InteractivityExportContext::AfterTextureExport "); + } + public override bool BeforeMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) + { + Util.Log($"InteractivityExportContext::BeforeMaterialExport "); + return false; + } + public override void BeforeNodeExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, GLTF.Schema.Node node) + { + Util.Log($"InteractivityExportContext::BeforeNodeExport "); + } + public override void BeforeSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) + { + Util.Log($"InteractivityExportContext::BeforeSceneExport "); + } + public override void BeforeTextureExport(GLTFSceneExporter exporter, ref GLTFSceneExporter.UniqueTexture texture, string textureSlot) + { + Util.Log($"InteractivityExportContext::BeforeTextureExport "); + } + } + +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs.meta b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs.meta new file mode 100644 index 000000000..5f73acdd9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e8364a07bd0e8944877ff4fc0853243 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs new file mode 100644 index 000000000..c8debd263 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs @@ -0,0 +1,20 @@ +using UnityGLTF.Plugins; + +namespace UnityGLTF.Interactivity.Playback +{ + public class InteractivityExportPlugin : GLTFExportPlugin + { + public override string DisplayName => "KHR_interactivity_Exporter"; + public override string Description => "Exports KHR compliant interactivity graphs"; + + private InteractivityExportContext _context; + public readonly KHR_interactivity extensionData; + + public override GLTFExportPluginContext CreateInstance(ExportContext context) + { + _context = new InteractivityExportContext(); + + return _context; + } + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs.meta b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs.meta new file mode 100644 index 000000000..b632c9cab --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a74a558f2573ac46b14f1687826d8aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import.meta b/Runtime/Scripts/Interactivity/Playback/Context/Import.meta new file mode 100644 index 000000000..8d2b3e6e1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a764c48bbcc254e4f8d600f514b6151c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs new file mode 100644 index 000000000..47a310066 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -0,0 +1,68 @@ +using GLTF.Schema; +using UnityEngine; +using UnityGLTF.Plugins; + +namespace UnityGLTF.Interactivity.Playback +{ + public class InteractivityImportContext : GLTFImportPluginContext + { + internal readonly InteractivityImportPlugin settings; + + public InteractivityImportContext(InteractivityImportPlugin interactivityLoader) + { + settings = interactivityLoader; + } + + /// + /// Called before import starts + /// + public override void OnBeforeImport() + { + Util.Log($"InteractivityImportContext::OnBeforeImport Complete"); + } + + public override void OnBeforeImportRoot() + { + Util.Log($"InteractivityImportContext::OnBeforeImportRoot Complete"); + } + + /// + /// Called when the GltfRoot has been deserialized + /// + public override void OnAfterImportRoot(GLTFRoot gltfRoot) + { + Util.Log($"InteractivityImportContext::OnAfterImportRoot Complete: {gltfRoot.ToString()}"); + } + + public override void OnBeforeImportScene(GLTFScene scene) + { + Util.Log($"InteractivityImportContext::OnBeforeImportScene Complete: {scene.ToString()}"); + } + + public override void OnAfterImportNode(GLTF.Schema.Node node, int nodeIndex, GameObject nodeObject) + { + Util.Log($"InteractivityImportContext::OnAfterImportNode Complete: {node.ToString()}"); + } + + public override void OnAfterImportMaterial(GLTFMaterial material, int materialIndex, Material materialObject) + { + Util.Log($"InteractivityImportContext::OnAfterImportMaterial Complete: {material.ToString()}"); + } + + public override void OnAfterImportTexture(GLTFTexture texture, int textureIndex, Texture textureObject) + { + Util.Log($"InteractivityImportContext::OnAfterImportTexture Complete: {texture.ToString()}"); + } + + public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObject sceneObject) + { + Util.Log($"InteractivityImportContext::OnAfterImportScene Complete: {scene.Extensions}"); + } + + public override void OnAfterImport() + { + Util.Log($"InteractivityImportContext::OnAfterImport Complete"); + } + } + +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs.meta b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs.meta new file mode 100644 index 000000000..9eb2ba433 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f4103b48f99b7e48b1a07030835b99a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs new file mode 100644 index 000000000..a99dff11e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs @@ -0,0 +1,22 @@ +using GLTF.Schema; +using UnityGLTF.Plugins; + +namespace UnityGLTF.Interactivity.Playback +{ + public class InteractivityImportPlugin : GLTFImportPlugin + { + public override string DisplayName => "KHR_interactivity_Importer"; + public override string Description => "Imports KHR compliant interactivity graphs"; + + private InteractivityImportContext _context; + + public override GLTFImportPluginContext CreateInstance(GLTFImportContext context) + { + _context = new InteractivityImportContext(this); + GLTFProperty.RegisterExtension(new InteractivityGraphFactory()); + + return _context; + } + + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs.meta b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs.meta new file mode 100644 index 000000000..4ced1816d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b76a2c96557b5d644b11d836f8ddb347 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Serialization.meta b/Runtime/Scripts/Interactivity/Playback/Context/Serialization.meta new file mode 100644 index 000000000..4d83b7ba7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Serialization.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: be8f6ab7f308d144c872a7e97b66afda +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs new file mode 100644 index 000000000..f0094eaf6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs @@ -0,0 +1,51 @@ +using GLTF.Schema; +using Newtonsoft.Json.Linq; +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class InteractivityGraphExtension : IExtension + { + public const string EXTENSION_NAME = "KHR_interactivity"; + + public KHR_interactivity extensionData { get; private set; } + + private readonly GraphSerializer _serializer = new(); + + public InteractivityGraphExtension(KHR_interactivity extensionData = null) + { + this.extensionData = extensionData; + } + + public IExtension Clone(GLTFRoot root) + { + throw new NotImplementedException(); + } + + public void Deserialize(JProperty extensionToken) + { + if (!extensionToken.Name.Equals(EXTENSION_NAME)) + return; + + extensionData = _serializer.Deserialize(extensionToken.Value.ToString()); + } + + public JProperty Serialize() + { + try + { + var json = _serializer.Serialize(extensionData); + + JObject jobject = JObject.Parse(json); + + return new JProperty(EXTENSION_NAME, jobject); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + return null; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs.meta b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs.meta new file mode 100644 index 000000000..6b37df61f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00888a46c163f1440bb360c7542aeb56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs new file mode 100644 index 000000000..04cf3ff2a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs @@ -0,0 +1,23 @@ +using GLTF.Schema; +using Newtonsoft.Json.Linq; + +namespace UnityGLTF.Interactivity.Playback +{ + public class InteractivityGraphFactory : ExtensionFactory + { + public InteractivityGraphFactory() + { + ExtensionName = InteractivityGraphExtension.EXTENSION_NAME; + } + + public override IExtension Deserialize(GLTFRoot root, JProperty extensionToken) + { + if (extensionToken == null) + return null; + + var graph = new InteractivityGraphExtension(); + graph.Deserialize(extensionToken); + return graph; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs.meta b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs.meta new file mode 100644 index 000000000..259fd789d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afd9971d7c53ab94db47e73a27803a05 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data.meta b/Runtime/Scripts/Interactivity/Playback/Data.meta new file mode 100644 index 000000000..e83674751 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ca3ce927c1761224c9fb0ff8c838fd68 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers.meta b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers.meta new file mode 100644 index 000000000..9ed7bb828 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aeeb8555e14a01e42adb843cb1376598 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs new file mode 100644 index 000000000..94904854e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class DeclarationsDeserializer + { + public static List GetDeclarations(JObject jObj, List types) + { + var jDeclarations = jObj[ConstStrings.DECLARATIONS].Children(); + var declarations = new List(jDeclarations.Count()); + + foreach (var v in jDeclarations) + { + var declaration = new Declaration(); + declaration.op = v[ConstStrings.OP].Value(); + + var jExtension = v[ConstStrings.EXTENSION]; + + if (jExtension != null) + { + declaration.extension = jExtension.Value(); + PopulateValueSockets(v, declaration, types); + } + + declarations.Add(declaration); + } + + return declarations; + } + + private static void PopulateValueSockets(JToken v, Declaration declaration, List types) + { + var inputs = v[ConstStrings.INPUT_VALUE_SOCKETS] as JObject; + var outputs = v[ConstStrings.OUTPUT_VALUE_SOCKETS] as JObject; + + declaration.inputValueSockets = GetValueSockets(inputs); + declaration.outputValueSockets = GetValueSockets(outputs); + } + + private static List GetValueSockets(JObject jList) + { + if (jList == null || jList.Count <= 0) + return null; + + var valueSockets = new List(); + + foreach (var kvp in jList) + { + valueSockets.Add(new ValueSocket(kvp.Key, kvp.Value[ConstStrings.TYPE].Value())); + } + + return valueSockets; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs.meta new file mode 100644 index 000000000..c6d70ae1b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Declarations.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3107698ab64dd740b025c3771aa619d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs new file mode 100644 index 000000000..39d6d9297 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class EventsDeserializer + { + public static List GetEvents(JObject jObj, List systemTypes) + { + var jEvents = jObj[ConstStrings.EVENTS].Children(); + var events = new List(jEvents.Count()); + + foreach (var v in jEvents) + { + events.Add(new Customevent() + { + id = v[ConstStrings.ID].Value(), + values = GetEventValues(v[ConstStrings.VALUES] as JObject, systemTypes) + }); + } + + return events; + } + + private static List GetEventValues(JObject jValues, List systemTypes) + { + var valueCount = jValues.Count; + var values = new List(valueCount); + + foreach (var kvp in jValues) + { + var typeIndex = kvp.Value[ConstStrings.TYPE].Value(); + values.Add(new EventValue(kvp.Key, Helpers.GetDefaultProperty(typeIndex, systemTypes))); + } + + return values; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs.meta new file mode 100644 index 000000000..330b64889 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Events.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0eefa1b737f6ffd4396b6e3e7a1053b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs new file mode 100644 index 000000000..8bc5f99c7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs @@ -0,0 +1,173 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.Assertions; +using UnityEngine.Pool; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class NodesDeserializer + { + private struct NodePair + { + public Node node; + public JToken jToken; + } + + public static List GetNodes(JObject jObj, List types, List declarations) + { + var jNodes = jObj[ConstStrings.NODES].Children(); + + var nodeCount = jNodes.Count(); + + var nodes = new List(nodeCount); + + var nodePairs = ListPool.Get(); + + Node node; + + try + { + foreach (var jToken in jNodes) + { + var declarationIndex = jToken[ConstStrings.DECLARATION].Value(); + + node = new Node() + { + type = declarations[declarationIndex].op, + metadata = GetMetadata(jToken[ConstStrings.METADATA]), + configuration = GetConfiguration(jToken[ConstStrings.CONFIGURATION]) + }; + + nodes.Add(node); + nodePairs.Add(new NodePair() + { + node = node, + jToken = jToken + }); + } + + foreach (var nodePair in nodePairs) + { + nodePair.node.values = GetValues(nodePair.jToken[ConstStrings.VALUES], nodes, types); + nodePair.node.flows = GetFlows(nodePair.node, nodePair.jToken[ConstStrings.FLOWS], nodes); + } + } + finally + { + ListPool.Release(nodePairs); + } + + return nodes; + } + + private static List GetFlows(Node fromNode, JToken jToken, List nodes) + { + var count = jToken.Count(); + var flows = new List(count); + var jFlows = jToken as JObject; + + foreach (var v in jFlows) + { + var jToNode = v.Value[ConstStrings.NODE]; + var jToSocket = v.Value[ConstStrings.SOCKET]; + + // Ignore this flow if it's empty/not connected. + if (jToNode == null || jToSocket == null) + continue; + + var fromSocket = v.Key; + var toNode = nodes[v.Value[ConstStrings.NODE].Value()]; + var toSocket = v.Value[ConstStrings.SOCKET].Value(); + + flows.Add(new Flow( + fromNode, + fromSocket, + toNode, + toSocket)); + } + + return flows; + } + + private static List GetValues(JToken jToken, List nodes, List types) + { + var count = jToken.Count(); + var values = new List(count); + var jValues = jToken as JObject; + + foreach (var kvp in jValues) + { + var jType = kvp.Value[ConstStrings.TYPE]; + var jNode = kvp.Value[ConstStrings.NODE]; + var jSocket = kvp.Value[ConstStrings.SOCKET]; + var jValue = kvp.Value[ConstStrings.VALUE]; + + int type = Constants.INVALID_TYPE_INDEX; + Node node = null; + var socket = Constants.EMPTY_SOCKET_STRING; + IProperty value = null; + + if (jType != null) + { + type = jType.Value(); + + Assert.IsNotNull(jValue); + + value = Helpers.CreateProperty(types[type], jValue as JArray); + } + + if (jNode != null) + node = nodes[jNode.Value()]; + + if (jSocket != null) + socket = jSocket.Value(); + + values.Add(new Value() + { + id = kvp.Key, + property = value, + node = node, + socket = socket + }); + + Util.Log($"Created property {kvp.Key} connected to node {node?.type} and socket {socket} with type {type}"); + } + + return values; + } + + private static List GetConfiguration(JToken jToken) + { + var jConfiguration = jToken as JObject; + + var count = jConfiguration.Count; + var configuration = new List(count); + + foreach (var v in jConfiguration) + { + configuration.Add(new Configuration() + { + id = v.Key, + value = v.Value[ConstStrings.VALUE] as JArray + }); + } + + return configuration; + } + + private static Metadata GetMetadata(JToken jToken) + { + if (jToken == null) + return new Metadata(); + + return new Metadata() + { + positionX = double.Parse(jToken["positionX"].Value()), + positionY = double.Parse(jToken["positionY"].Value()), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs.meta new file mode 100644 index 000000000..4ebc1497a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7d009707325233429667196eac5b5cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs new file mode 100644 index 000000000..904c0c6f4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class TypesDeserializer + { + public static List GetSystemTypes(List types) + { + var systemTypes = new List(types.Count); + + for (int i = 0; i < types.Count; i++) + { + systemTypes.Add(Helpers.GetSystemType(types[i])); + } + + return systemTypes; + } + + public static List GetTypes(JObject jObj) + { + return jObj["types"].ToObject>(); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs.meta new file mode 100644 index 000000000..399930150 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Types.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ceea39faf76669c4d8bf289859655c1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs new file mode 100644 index 000000000..3865ef816 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs @@ -0,0 +1,50 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class VariablesDeserializer + { + public static List GetVariables(JObject jObj, List types) + { + var jVariables = jObj[ConstStrings.VARIABLES].Children(); + + var variables = new List(jVariables.Count()); + + foreach (var v in jVariables) + { + variables.Add(CreateVariable(v, types)); + } + + return variables; + } + + private static Variable CreateVariable(JToken token, List types) + { + + // ID is not part of the spec but it's a nice to have. + // Needle exporter uses id for this field. + var id = ""; + JToken jId = token[ConstStrings.ID]; + + // React app uses name for this field. + if (jId == null) + jId = token[ConstStrings.NAME]; + + if(jId != null) + id = jId.Value(); + + var typeIndex = token[ConstStrings.TYPE].Value(); + var valueArray = token[ConstStrings.VALUE] as JArray; + + return new Variable() + { + id = id, + property = Helpers.CreateProperty(types[typeIndex], valueArray), + initialValue = Helpers.CreateProperty(types[typeIndex], valueArray), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs.meta new file mode 100644 index 000000000..293e6789a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Variables.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2123dc40360aaa544bfb35910c5487b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs b/Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs new file mode 100644 index 000000000..7d474f750 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs @@ -0,0 +1,77 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class GraphConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, KHR_interactivity value, JsonSerializer serializer) + { + writer.WriteStartObject(); + writer.WritePropertyName(ConstStrings.GRAPHS); + writer.WriteStartArray(); + + for (int i = 0; i < value.graphs.Count; i++) + { + WriteGraph(writer, value.graphs[i]); + } + + writer.WriteEndArray(); + writer.WritePropertyName(ConstStrings.GRAPH); + writer.WriteValue(0); // TODO: Default graph selection for users? + writer.WriteEndObject(); + } + + private void WriteGraph(JsonWriter writer, Graph graph) + { + writer.WriteStartObject(); + var typeIndexByType = TypesSerializer.GetSystemTypeByIndexDictionary(graph); + var declarations = DeclarationsSerializer.GetDeclarations(graph.nodes, typeIndexByType); + TypesSerializer.WriteJson(writer, graph.types); + VariablesSerializer.WriteJson(writer, graph.variables, typeIndexByType); + EventsSerializer.WriteJson(writer, graph.customEvents, typeIndexByType); + DeclarationsSerializer.WriteJson(writer, declarations); + NodesSerializer.WriteJson(writer, graph.nodes, declarations, typeIndexByType); + writer.WriteEndObject(); + } + + public override KHR_interactivity ReadJson(JsonReader reader, System.Type objectType, KHR_interactivity existingValue, bool hasExistingValue, JsonSerializer serializer) + { + JObject jObj = JObject.Load(reader); + + var jGraphs = jObj[ConstStrings.GRAPHS]; + + var interactivity = new KHR_interactivity(); + + foreach (JObject jGraph in jGraphs) + { + interactivity.graphs.Add(GenerateGraph(jGraph)); + } + + interactivity.defaultGraphIndex = jObj[ConstStrings.GRAPH].Value(); + + return interactivity; + } + + private static Graph GenerateGraph(JObject jObj) + { + var types = TypesDeserializer.GetTypes(jObj); + var systemTypes = TypesDeserializer.GetSystemTypes(types); + var variables = VariablesDeserializer.GetVariables(jObj, systemTypes); + var events = EventsDeserializer.GetEvents(jObj, systemTypes); + var declarations = DeclarationsDeserializer.GetDeclarations(jObj, systemTypes); + var nodes = NodesDeserializer.GetNodes(jObj, systemTypes, declarations); + + return new Graph() + { + types = types, + variables = variables, + customEvents = events, + nodes = nodes, + declarations = declarations, + systemTypes = systemTypes + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs.meta new file mode 100644 index 000000000..2d82ce4b7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/GraphConverter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 072904e6e51c9724ca23ecbc7c5b938a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs b/Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs new file mode 100644 index 000000000..db26f40a9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; + +namespace UnityGLTF.Interactivity.Playback +{ + public class GraphSerializer + { + private readonly JsonSerializerSettings _serializationSettings; + private readonly JsonSerializerSettings _deserializerSettings; + + public GraphSerializer(Formatting formatting = Formatting.None) + { + _serializationSettings = new JsonSerializerSettings + { + Formatting = formatting, + Converters = + { + new GraphConverter(), + }, + }; + + _deserializerSettings = new JsonSerializerSettings + { + Converters = + { + new GraphConverter(), + }, + }; + } + + public string Serialize(KHR_interactivity extensionData) + { + return JsonConvert.SerializeObject(extensionData, _serializationSettings); + } + + public KHR_interactivity Deserialize(string json) + { + return JsonConvert.DeserializeObject(json, _deserializerSettings); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs.meta new file mode 100644 index 000000000..31fad61f6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/GraphSerializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84914673cb9fc654ca957d222c345067 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers.meta new file mode 100644 index 000000000..3383d3742 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ed20cc3c257da2a4b98682add27c2086 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs new file mode 100644 index 000000000..cb19ef978 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs @@ -0,0 +1,152 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public static class Constants + { + public const int UNCONNECTED_NODE_INDEX = -1; + public const int INVALID_TYPE_INDEX = -1; + public const string EMPTY_SOCKET_STRING = ""; + } + + public static class Pointers + { + public const string ANIMATIONS_LENGTH = "animations.length"; + public const string MATERIALS_LENGTH = "materials.length"; + public const string MESHES_LENGTH = "meshes.length"; + public const string NODES_LENGTH = "nodes.length"; + public const string WEIGHTS_LENGTH = "weights.length"; + public const string WEIGHTS = "weights"; + public const string EXTENSIONS = "extensions"; + public const string TRANSLATION = "translation"; + public const string ROTATION = "rotation"; + public const string SCALE = "scale"; + public const string MATRIX = "matrix"; + public const string GLOBAL_MATRIX = "globalMatrix"; + public const string IS_PLAYING = "isPlaying"; + public const string PLAYHEAD = "playhead"; + public const string VIRTUAL_PLAYHEAD = "virtualPlayhead"; + public const string MIN_TIME = "minTime"; + public const string MAX_TIME = "maxTime"; + + } + + public static class ConstStrings + { + public const string REMAINING_INPUTS = "remainingInputs"; + public const string LAST_INDEX = "lastIndex"; + public const string LAST_REMAINING_TIME = "lastRemainingTime"; + public const string TIME_SINCE_START = "timeSinceStart"; + public const string TIME_SINCE_LAST_TICK = "timeSinceLastTick"; + public const string EXPECTED = "expected"; + public const string ACTUAL = "actual"; + + public const string MESSAGE = "message"; + public const string NODE_INDEX = "nodeIndex"; + public const string DEFAULT = "default"; + public const string SELECTION = "selection"; + public const string CASES = "cases"; + public const string GRAPHS = "graphs"; + public const string GRAPH = "graph"; + public const string EXTENSION = "extension"; + public const string OP = "op"; + public const string INPUT_VALUE_SOCKETS = "inputValueSockets"; + public const string OUTPUT_VALUE_SOCKETS = "outputValueSockets"; + public const string DECLARATIONS = "declarations"; + public const string DECLARATION = "declaration"; + public const string EVENTS = "events"; + public const string EVENT = "event"; + public const string NODES = "nodes"; + public const string NODE = "node"; + public const string SOCKET = "socket"; + public const string SIGNATURE = "signature"; + public const string CONDITION = "condition"; + public const string TRUE = "true"; + public const string FALSE = "false"; + public const string POINTER = "pointer"; + public const string COMPLETED = "completed"; + public const string LOOP_BODY = "loopBody"; + public const string START_INDEX = "startIndex"; + public const string END_INDEX = "endIndex"; + public const string LAST_DELAY_INDEX = "lastDelayIndex"; + public const string DELAY_INDEX = "delayIndex"; + public const string INITIAL_INDEX = "initialIndex"; + public const string INDEX = "index"; + public const string ANIMATION = "animation"; + public const string SPEED = "speed"; + public const string START_TIME = "startTime"; + public const string STOP_TIME = "stopTime"; + public const string END_TIME = "endTime"; + public const string CANCEL = "cancel"; + public const string CURRENT_COUNT = "currentCount"; + + public const string VARIABLES = "variables"; + public const string VARIABLE = "variable"; + + public const string TYPES = "types"; + public const string IS_VALID = "isValid"; + + public const string VALUE = "value"; + public const string VALUES = "values"; + public const string ID = "id"; + public const string NAME = "name"; + public const string TYPE = "type"; + public const string DESCRIPTION = "description"; + public const string METADATA = "metadata"; + public const string CONFIGURATION = "configuration"; + public const string FLOWS = "flows"; + public const string INPUT_FLOWS = "inputFlows"; + public const string OUTPUT_FLOWS = "outputFlows"; + public const string RESET = "reset"; + public const string IS_LOOP = "isLoop"; + public const string IS_RANDOM = "isRandom"; + + public const string A = "a"; + public const string B = "b"; + public const string C = "c"; + public const string D = "d"; + public const string E = "e"; + public const string F = "f"; + public const string G = "g"; + public const string H = "h"; + public const string I = "i"; + public const string J = "j"; + public const string K = "k"; + public const string L = "l"; + public const string M = "m"; + public const string N = "n"; + public const string O = "o"; + public const string P = "p"; + + public static readonly string[] Letters = new string[] + { + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P + }; + + public static readonly string[] Numbers = new string[] + { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", + "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", + "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", + "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64" + }; + + public const string DONE = "done"; + public const string IN = "in"; + public const string OUT = "out"; + public const string ERR = "err"; + public const string P1 = "p1"; + public const string P2 = "p2"; + public const string DURATION = "duration"; + + public const string TRANSLATION = "translation"; + public const string ROTATION = "rotation"; + public const string SCALE = "scale"; + public const string USE_SLERP = "useSlerp"; + + public const string CONTROLLER_INDEX = "controllerIndex"; + public const string SELECTED_NODE_INDEX = "selectedNodeIndex"; + public const string SELECTION_POINT = "selectionPoint"; + public const string SELECTION_RAY_ORIGIN = "selectionRayOrigin"; + public const string STOP_PROPAGATION = "stopPropagation"; + public const string HOVER_NODE_INDEX = "hoverNodeIndex"; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs.meta new file mode 100644 index 000000000..b054dcf79 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5dc0454a6bc2f6145832fbd3bad4131a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions.meta new file mode 100644 index 000000000..e9c914293 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 03333efac374a0f4fb2d4ef555f602de +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs new file mode 100644 index 000000000..a5320fd66 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Extensions +{ + public static partial class ExtensionMethods + { + public static bool IsNullOrEmpty(this T[] arr) + { + if (arr == null) + return true; + + if (arr.Length <= 0) + return true; + + return false; + } + + public static bool IsNullOrEmpty(this List arr) + { + if (arr == null) + return true; + + if (arr.Count <= 0) + return true; + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Is(this ReadOnlySpan span, string str) + { + return span.SequenceEqual(str.AsSpan()); + } + + public static void Shuffle(this System.Random rng, T[] array) + { + int n = array.Length; + while (n > 1) + { + int k = rng.Next(n--); + T temp = array[n]; + array[n] = array[k]; + array[k] = temp; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs.meta new file mode 100644 index 000000000..8dfc9f2bf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Arrays.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe1303f4fddc720479c3505c4453b84f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs new file mode 100644 index 000000000..66350f07b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs @@ -0,0 +1,38 @@ +namespace UnityGLTF.Interactivity.Playback.Extensions +{ + public static partial class ExtensionMethods + { + public static bool TryGetPointer(this BehaviourEngine engine, string pointerString, BehaviourEngineNode engineNode, out Pointer pointer) + { + pointer = default; + + if (!engine.TryGetPointer(pointerString, engineNode, out IPointer p)) + return false; + + pointer = (Pointer)p; + return true; + } + + public static bool TryGetPointer(this BehaviourEngine engine, string pointerString, BehaviourEngineNode engineNode, out IReadOnlyPointer readOnlyPointer) + { + readOnlyPointer = default; + + if (!engine.TryGetPointer(pointerString, engineNode, out IPointer pointer)) + return false; + + readOnlyPointer = (IReadOnlyPointer)pointer; + return true; + } + + public static bool TryGetPointer(this BehaviourEngine engine, string pointerString, BehaviourEngineNode engineNode, out ReadOnlyPointer pointer) + { + pointer = default; + + if (!engine.TryGetPointer(pointerString, engineNode, out IReadOnlyPointer readOnlyPointer)) + return false; + + pointer = (ReadOnlyPointer)readOnlyPointer; + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs.meta new file mode 100644 index 000000000..125dca78b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 656cd63475f229e41a1b6e47ff4492d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs new file mode 100644 index 000000000..1a3b1e7c1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs @@ -0,0 +1,107 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + partial class BehaviourEngineNode + { + public bool TryGetPointer(string pointerString, out IPointer pointer) + { + return engine.TryGetPointer(pointerString, this, out pointer); + } + + public bool TryGetPointer(string pointerString, out Pointer pointer) + { + return engine.TryGetPointer(pointerString, this, out pointer); + } + + public bool TryGetReadOnlyPointer(string pointerString, out IReadOnlyPointer pointer) + { + return engine.TryGetPointer(pointerString, this, out pointer); + } + + public bool TryGetReadOnlyPointer(string pointerString, out ReadOnlyPointer pointer) + { + return engine.TryGetPointer(pointerString, this, out pointer); + } + + public bool TryGetPointerFromConfiguration(out IPointer pointer) + { + pointer = default; + + if (!configuration.TryGetValue(ConstStrings.POINTER, out Configuration config)) + return false; + + var pointerPath = Parser.ToString(config.value); + + return TryGetPointer(pointerPath, out pointer); + } + + public bool TryGetPointerFromConfiguration(out Pointer pointer) + { + pointer = default; + + if (!TryGetPointerFromConfiguration(out IPointer p)) + return false; + + pointer = (Pointer)p; + return true; + } + + public bool TryGetPointerFromConfiguration(out IReadOnlyPointer readOnlyPointer) + { + readOnlyPointer = default; + + if (!TryGetPointerFromConfiguration(out IPointer p)) + return false; + + readOnlyPointer = (IReadOnlyPointer)p; + return true; + } + + public bool TryGetPointerFromConfiguration(out ReadOnlyPointer readOnlyPointer) + { + readOnlyPointer = default; + + if (!TryGetPointerFromConfiguration(out IPointer p)) + return false; + + readOnlyPointer = (ReadOnlyPointer)p; + return true; + } + + public bool TryEvaluateValue(string valueId, out T value) + { + value = default; + + if (!TryEvaluateValue(valueId, out IProperty property)) + return false; + + value = ((Property)property).value; + return true; + } + + public bool TryGetVariableFromConfiguration(out Variable variable, out int index) + { + variable = null; + index = -1; + + if (!configuration.TryGetValue(ConstStrings.VARIABLE, out Configuration config)) + return false; + + try + { + index = Parser.ToInt(config.value); + variable = engine.graph.variables[index]; + } + catch (Exception e) + { + Debug.LogException(e); + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs.meta new file mode 100644 index 000000000..66c46efb4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 829bc17d3169b534f89c0ecae3a0a372 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs new file mode 100644 index 000000000..ed622ccff --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs @@ -0,0 +1,48 @@ +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Extensions +{ + public static partial class ExtensionMethods + { + public static float3 ToFloat3(this Color c) + { + return new float3(c.r, c.g, c.b); + } + + public static float3 ToFloat3(this Color3 c) + { + return new float3(c.r, c.g, c.b); + } + + public static Color ToColor(this float3 v) + { + return new Color(v.x, v.y, v.z, 1f); + } + + public static float4 ToFloat4(this Color c) + { + return new float4(c.r, c.g, c.b, c.a); + } + + public static Color ToColor(this float4 v) + { + return new Color(v.x, v.y, v.z, v.w); + } + + public static float4 ToFloat4(this quaternion q) + { + return q.value; + } + + public static float4 ToFloat4(this Quaternion q) + { + return new float4(q.x, q.y, q.z, q.w); + } + + public static quaternion ToQuaternion(this float4 v) + { + return new quaternion(v.x, v.y, v.z, v.w); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs.meta new file mode 100644 index 000000000..463a0ab05 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Conversions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62dada23b35907f47acd0b9f5130b3d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs new file mode 100644 index 000000000..192c94f99 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs @@ -0,0 +1,32 @@ +using System.IO; +using System; +using UnityEngine; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public static partial class Helpers + { + public static int CompareTo(this Flow a, Flow b) + { + var unitsA = a.fromSocket.AsSpan(); + var unitsB = b.fromSocket.AsSpan(); + + var lengthA = unitsA.Length; + var lengthB = unitsB.Length; + + var minLength = math.min(lengthA, lengthB); + + for (int i = 0; i < minLength; i++) + { + if (unitsA[i] < unitsB[i]) + return -1; + + if (unitsA[i] > unitsB[i]) + return 1; + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs.meta new file mode 100644 index 000000000..36c572784 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Flows.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5341dd37acd19394fb050a059f4a818d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs new file mode 100644 index 000000000..60f097e46 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs @@ -0,0 +1,21 @@ +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Extensions +{ + public static partial class ExtensionMethods + { + public static bool IsAncestorOf(this Transform potentialAncestor, Transform child) + { + while (child.parent != null) + { + if (child.parent == potentialAncestor) + return true; + + child = child.parent; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs.meta new file mode 100644 index 000000000..fa18cfc00 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/Transform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7208b889982ed949928f28152629629 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs new file mode 100644 index 000000000..26cd747f0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs @@ -0,0 +1,39 @@ +using System.IO; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public static partial class Helpers + { + public static (string directory, string fileName) GetFilePath(string filePath) + { + const int MAX_PATH_LENGTH_LINUX = 4096; + + var sb = new ValueStringBuilder(MAX_PATH_LENGTH_LINUX); + + try + { + sb.Append(Application.streamingAssetsPath); + + if (!(filePath.StartsWith(Path.DirectorySeparatorChar) || filePath.StartsWith(Path.AltDirectorySeparatorChar))) + sb.Append('/'); + + sb.Append(filePath); + + var lastIndex1 = sb.LastIndexOf(Path.DirectorySeparatorChar); + var lastIndex2 = sb.LastIndexOf(Path.AltDirectorySeparatorChar); + + var lastIndex = Mathf.Max(lastIndex1, lastIndex2); + + var directory = sb.ToString(0, lastIndex); + var fileName = sb.ToString(lastIndex + 1, sb.length); + + return (directory, fileName); + } + finally + { + sb.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs.meta new file mode 100644 index 000000000..376e05e0e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/FilePaths.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15ca94c0b736d694eb776efa6858e04a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs new file mode 100644 index 000000000..2584f6fec --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct BezierInterpolateData + { + public IPointer pointer; + public float duration; + public float2 cp0; + public float2 cp1; + public NodeEngineCancelToken cancellationToken; + } + + public static partial class Helpers + { + public static async Task InterpolateAsync(T from, T to, Action setter, Func evaluator, float duration, V cancellationToken) where V : struct, ICancelToken + { + for (float t = 0f; t < 1f; t += Time.deltaTime / duration) + { + if (cancellationToken.isCancelled) + return false; + + setter(evaluator(from, to, t)); + await Task.Yield(); + } + + return true; + } + + public static async Task LinearInterpolateAsync(T to, Pointer pointer, float duration, V cancellationToken) where V : struct, ICancelToken + { + return await InterpolateAsync(pointer.getter(), to, pointer.setter, pointer.evaluator, duration, cancellationToken); + } + + public static async Task InterpolateBezierAsync(Property to, BezierInterpolateData d) + { + var v = to.value; + return await InterpolateBezierAsync(v, d); + } + + public static async Task InterpolateBezierAsync(T to, BezierInterpolateData d) + { + var p = (Pointer)d.pointer; + + var evaluator = new Func((a, b, t) => p.evaluator(a, b, CubicBezier(t, d.cp0, d.cp1).y)); + + return await InterpolateAsync(p.getter(), to, p.setter, evaluator, d.duration, d.cancellationToken); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float2 CubicBezier(float t, float2 cp0, float2 cp1) + { + var omt = 1 - t; + return 3f * t * omt * omt * cp0 + 3f * t * t * omt * cp1 + t * t * t * (new float2(1f,1f)); + } + + public static float4 nlerp(float4 q1, float4 q2, float t) + { + float dt = math.dot(q1, q2); + if (dt < 0.0f) + { + q2 = -q2; + } + + return math.normalize(math.lerp(q1, q2, t)); + } + + public static float4 Slerpfloat4(float4 q1, float4 q2, float t) + { + float dt = math.dot(q1, q2); + if (dt < 0.0f) + { + dt = -dt; + q2 = -q2; + } + + if (dt < 0.9995f) + { + float angle = math.acos(dt); + float s = math.rsqrt(1.0f - dt * dt); // 1.0f / sin(angle) + float w1 = math.sin(angle * (1.0f - t)) * s; + float w2 = math.sin(angle * t) * s; + return q1 * w1 + q2 * w2; + } + else + { + // if the angle is small, use linear interpolation + return nlerp(q1, q2, t); + } + } + + public static float2x2 LerpComponentwise(float2x2 from, float2x2 to, float t) + { + var c0 = math.lerp(from.c0, to.c0, t); + var c1 = math.lerp(from.c1, to.c1, t); + + var m = new float2x2(c0, c1); + + return m; + } + + public static float3x3 LerpComponentwise(float3x3 from, float3x3 to, float t) + { + var c0 = math.lerp(from.c0, to.c0, t); + var c1 = math.lerp(from.c1, to.c1, t); + var c2 = math.lerp(from.c2, to.c2, t); + + var m = new float3x3(c0, c1, c2); + + return m; + } + + public static float4x4 LerpComponentwise(float4x4 from, float4x4 to, float t) + { + var c0 = math.lerp(from.c0, to.c0, t); + var c1 = math.lerp(from.c1, to.c1, t); + var c2 = math.lerp(from.c2, to.c2, t); + var c3 = math.lerp(from.c3, to.c3, t); + + var m = new float4x4(c0, c1, c2, c3); + + return m; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs.meta new file mode 100644 index 000000000..56e1c214c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ef1d483d40dac641ae2bb4b9bb77309 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs new file mode 100644 index 000000000..3e7587bd1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public static class PointerHelpers + { + public static Pointer CreatePointer(Action setter, Func getter, Func evaluator) + { + return new Pointer() + { + setter = setter, + getter = getter, + evaluator = evaluator + }; + } + + public static Pointer CreateFloatPointer(Material mat, int hash) + { + return new Pointer() + { + setter = (v) => mat.SetFloat(hash, v), + getter = () => mat.GetFloat(hash), + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + } + + public static Pointer CreateColorRGBPointer(Material mat, int hash) + { + return new Pointer() + { + setter = (v) => mat.SetColor(hash, v), + getter = () => mat.GetColor(hash), + evaluator = (a, b, t) => Color.Lerp(a, b, t) + }; + } + + public static Pointer CreateColorRGBAPointer(Material mat, int hash) + { + return new Pointer() + { + setter = (v) => mat.SetColor(hash, v), + getter = () => mat.GetColor(hash), + evaluator = (a, b, t) => Color.Lerp(a, b, t) + }; + } + + public static Pointer CreateOffsetPointer(Material mat, int hash) + { + return new Pointer() + { + setter = (v) => mat.SetTextureOffset(hash, v), + getter = () => mat.GetTextureOffset(hash), + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + } + + public static Pointer CreateScalePointer(Material mat, int hash) + { + return new Pointer() + { + setter = (v) => mat.SetTextureScale(hash, v), + getter = () => mat.GetTextureScale(hash), + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs.meta new file mode 100644 index 000000000..9f51495bf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80200061335708c4e8dcd960a13ab76f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs new file mode 100644 index 000000000..31b3cb270 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs @@ -0,0 +1,172 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public static partial class Helpers + { + public static Type GetSystemType(InteractivityType type) + { + return GetSystemTypeBySignature(type.signature); + } + + public static Type GetSystemTypeBySignature(string signature) + { + switch (signature) + { + case "bool": return typeof(bool); + case "int": return typeof(int); + case "float": return typeof(float); + case "float2": return typeof(float2); + case "float3": return typeof(float3); + case "float4": return typeof(float4); + case "float2x2": return typeof(float2x2); + case "float3x3": return typeof(float3x3); + case "float4x4": return typeof(float4x4); + case "int[]": return typeof(int[]); + default: return typeof(string); + } + } + + public static string GetSignatureBySystemType(Type type) + { + if (type == typeof(bool)) return "bool"; + if (type == typeof(int)) return "int"; + if (type == typeof(float)) return "float"; + if (type == typeof(float2)) return "float2"; + if (type == typeof(float3)) return "float3"; + if (type == typeof(float4)) return "float4"; + if (type == typeof(float2x2)) return "float2x2"; + if (type == typeof(float3x3)) return "float3x3"; + if (type == typeof(float4x4)) return "float4x4"; + if (type == typeof(int[])) return "int[]"; + throw new InvalidOperationException($"Invalid type {type} used!"); + } + + public static IProperty CreateProperty(Type type, JArray value) + { + if (type == typeof(int)) + { + return new Property(Parser.ToInt(value)); + } + else if (type == typeof(float)) + { + return new Property(Parser.ToFloat(value)); + } + else if (type == typeof(bool)) + { + return new Property(Parser.ToBool(value)); + } + else if (type == typeof(float2)) + { + return new Property(Parser.ToFloat2(value)); + } + else if (type == typeof(float3)) + { + return new Property(Parser.ToFloat3(value)); + } + else if (type == typeof(float4)) + { + return new Property(Parser.ToFloat4(value)); + } + else if (type == typeof(float2x2)) + { + return new Property(Parser.ToFloat2x2(value)); + } + else if (type == typeof(float3x3)) + { + return new Property(Parser.ToFloat3x3(value)); + } + else if (type == typeof(float4x4)) + { + return new Property(Parser.ToFloat4x4(value)); + } + else if (type == typeof(int[])) + { + return new Property(Parser.ToIntArray(value)); + } + else if (type == typeof(string)) + { + return new Property(Parser.ToString(value)); + } + + throw new InvalidOperationException($"Type {type} is unsupported in this spec."); + } + + public static T GetPropertyValue(IProperty property) + { + if (property is not Property typedProperty) + throw new InvalidCastException($"Property is not of type {typeof(T)}"); + + return typedProperty.value; + } + + public static IProperty GetDefaultProperty(int typeIndex, List systemTypes) + { + var type = systemTypes[typeIndex]; + + if (type == typeof(int)) + { + return new Property(0); + } + else if (type == typeof(float)) + { + return new Property(float.NaN); + } + else if (type == typeof(bool)) + { + return new Property(false); + } + else if (type == typeof(float2)) + { + return new Property(new float2(float.NaN, float.NaN)); + } + else if (type == typeof(float3)) + { + return new Property(new float3(float.NaN, float.NaN, float.NaN)); + } + else if (type == typeof(float4)) + { + return new Property(new float4(float.NaN, float.NaN, float.NaN, float.NaN)); + } + else if (type == typeof(float2x2)) + { + var m = new float2x2(); + + for (int i = 0; i < 4; i++) + { + m[i] = float.NaN; + } + + return new Property(m); + } + else if (type == typeof(float3x3)) + { + var m = new float3x3(); + + for (int i = 0; i < 9; i++) + { + m[i] = float.NaN; + } + + return new Property(m); + } + else if (type == typeof(float4x4)) + { + var m = new float4x4(); + + for (int i = 0; i < 16; i++) + { + m[i] = float.NaN; + } + + return new Property(m); + } + + throw new InvalidOperationException($"No default value for {type} included in this spec."); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs.meta new file mode 100644 index 000000000..2cbaa3829 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Properties.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89d86f476de3ffb479f398e90c3ef328 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs new file mode 100644 index 000000000..fe12463fb --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs @@ -0,0 +1,43 @@ +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class Util + { + [System.Diagnostics.Conditional("DEBUG_MESSAGES")] + public static void Log( string message, UnityEngine.Object context = null) + { + Debug.Log(message, context); + } + + [System.Diagnostics.Conditional("DEBUG_MESSAGES")] + public static void LogWarning(string message, UnityEngine.Object context = null) + { + Debug.LogWarning(message, context); + } + + [System.Diagnostics.Conditional("DEBUG_MESSAGES")] + public static void LogError(string message, UnityEngine.Object context = null) + { + Debug.LogError(message, context); + } + + [System.Diagnostics.Conditional("DEBUG_MESSAGES")] + public static void Log(string className, string message, UnityEngine.Object context = null) + { + Debug.Log($"{className}: {message}", context); + } + + [System.Diagnostics.Conditional("DEBUG_MESSAGES")] + public static void LogWarning(string className, string message, UnityEngine.Object context = null) + { + Debug.LogWarning($"{className}: {message}", context); + } + + [System.Diagnostics.Conditional("DEBUG_MESSAGES")] + public static void LogError(string className, string message, UnityEngine.Object context = null) + { + Debug.LogError($"{className}: {message}", context); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs.meta new file mode 100644 index 000000000..593106bf8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Util.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18d8f661ca43f39418fa42d865985241 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model.meta new file mode 100644 index 000000000..816ad9d5a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c5ceddd5d2d395d4490fc1e99c7e79fc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs new file mode 100644 index 000000000..ebfd5e943 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventValue + { + public string id { get; set; } + public IProperty property { get; set; } + + public EventValue(string id, IProperty property) + { + this.id = id; + this.property = property; + } + } + + public class Customevent + { + public string id { get; set; } + public List values { get; set; } = new(); + + public void AddValue(string id, T value) + { + values.Add(new EventValue(id, new Property(value))); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs.meta new file mode 100644 index 000000000..da050cd28 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Customevent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d58c6bdffc5912d47a260c1f3789b9bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs new file mode 100644 index 000000000..1c50d8d1b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs @@ -0,0 +1,39 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class Flow + { + public Node fromNode { get; private set; } + public string fromSocket { get; private set; } + public Node toNode { get; private set; } + public string toSocket { get; private set; } + + public Flow(Node fromNode, string fromSocket, Node toNode, string toSocket) + { + this.fromNode = fromNode; + this.fromSocket = fromSocket; + this.toNode = toNode; + this.toSocket = toSocket; + + fromNode.onRemovedFromGraph += OnFromNodeRemovedFromGraph; + toNode.onRemovedFromGraph += OnToNodeRemovedFromGraph; + + } + + private void OnFromNodeRemovedFromGraph() + { + UnsubscribeFromNodes(); + } + + private void OnToNodeRemovedFromGraph() + { + UnsubscribeFromNodes(); + fromNode.RemoveFlow(this); + } + + private void UnsubscribeFromNodes() + { + toNode.onRemovedFromGraph -= OnToNodeRemovedFromGraph; + fromNode.onRemovedFromGraph -= OnFromNodeRemovedFromGraph; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs.meta new file mode 100644 index 000000000..78e967a46 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1547334e9c4b8df4fb5e68dec4cd5570 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs new file mode 100644 index 000000000..d37285141 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class Graph + { + public List systemTypes { get; set; } = new(); + public List nodes { get; set; } = new(); + public List variables { get; set; } = new(); + public List customEvents { get; set; } = new(); + public List types { get; set; } = new(); + public List declarations { get; set; } = new(); + + public event Action onNodeAdded; + public event Action onVariableAdded; + public event Action onEventAdded; + public event Action onTypeAdded; + + public event Action onNodeRemoved; + public event Action onVariableRemoved; + + public Variable AddVariable(string id, T initialValue) + { + // Due to how this is overloaded the IProperty cast is required. + return AddVariable(id, (IProperty)new Property(initialValue)); + } + + public Variable AddVariable(string id, IProperty initialValue) + { + for (int i = 0; i < variables.Count; i++) + { + if (variables[i].id.Equals(id)) + return variables[i]; + } + + var variable = new Variable() + { + id = id, + property = initialValue, + initialValue = initialValue, + }; + + variables.Add(variable); + + onVariableAdded?.Invoke(variable); + + return variable; + } + + public bool RemoveVariable(Variable variable) + { + var success = variables.Remove(variable); + + if (!success) + return false; + + onVariableRemoved?.Invoke(variable); + return true; + } + + public Customevent AddEvent(string id, List eventValues = null) + { + if (eventValues == null) + eventValues = new(); + + var e = new Customevent() + { + id = id, + values = eventValues + }; + + customEvents.Add(e); + + onEventAdded?.Invoke(e); + + return e; + } + + public Node CreateNode(string type) + { + return CreateNode(type, Vector2.zero); + } + + public Node CreateNode(string type, Vector2 position) + { + var e = new Node() + { + type = type + }; + + e.AddDefaultData(); + e.SetPositionMetadata(position.x, position.y); + + nodes.Add(e); + + onNodeAdded?.Invoke(e); + + return e; + } + + public bool RemoveNode(Node node) + { + var success = nodes.Remove(node); + + if (!success) + return false; + + node.OnRemovedFromGraph(); + onNodeRemoved?.Invoke(node); + return true; + } + + public InteractivityType AddType(string signature, TypeExtensions extensions = null) + { + for (int i = 0; i < types.Count; i++) + { + if (types[i].signature == signature) + { + Util.LogWarning($"Type {signature} has already been added to the list of types."); + return types[i]; + } + } + + var newType = new InteractivityType() { signature = signature, extensions = extensions }; + types.Add(newType); + onTypeAdded?.Invoke(newType); + + return newType; + } + + public void AddDefaultTypes() + { + systemTypes = new List() + { + typeof(bool), + typeof(int), + typeof(float), + typeof(float2), + typeof(float3), + typeof(float4), + typeof(float2x2), + typeof(float3x3), + typeof(float4x4), + typeof(string), + }; + + + types = new List() + { + new InteractivityType() { signature = "bool" }, + new InteractivityType() { signature = "int" }, + new InteractivityType() { signature = "float" }, + new InteractivityType() { signature = "float2" }, + new InteractivityType() { signature = "float3" }, + new InteractivityType() { signature = "float4" }, + new InteractivityType() { signature = "float2x2" }, + new InteractivityType() { signature = "float3x3" }, + new InteractivityType() { signature = "float4x4" }, + new InteractivityType() { signature = "string" }, + }; + } + + public int IndexOfType(string signature) + { + for (int i = 0; i < types.Count; i++) + { + if (types[i].signature.Equals(signature)) + return i; + } + + return -1; + } + + public bool TryGetVariable(string id, out Variable variable) + { + variable = null; + for (int i = 0; i < variables.Count; i++) + { + if (variables[i].id != id) + continue; + + variable = variables[i]; + return true; + } + + return false; + } + + public IProperty GetDefaultPropertyForType(int typeIndex) + { + return Helpers.GetDefaultProperty(typeIndex, systemTypes); + } + + public int IndexOfVariable(string variableName) + { + for (int i = 0; i < variables.Count; i++) + { + if (variables[i].id.Equals(variableName)) + return i; + } + + return -1; + } + + public int IndexOfVariable(Variable variable) + { + for (int i = 0; i < variables.Count; i++) + { + if (variables[i] == variable) + return i; + } + + return -1; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs.meta new file mode 100644 index 000000000..2e008e327 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b090f7f297bb8b4a98894662d030f58 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs new file mode 100644 index 000000000..9af9d5574 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs @@ -0,0 +1,260 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class Node + { + public string type { get; internal set; } + public List values { get; internal set; } = new(); + public List configuration { get; internal set; } = new(); + public List flows { get; internal set; } = new(); + public Metadata metadata { get; internal set; } = new(); + + public event Action onFlowAdded; + public event Action onFlowRemoved; + + public event Action onRemovedFromGraph; + + public Value AddConnectedValue(string inputName, Node outputNode, string outputName = ConstStrings.VALUE) + { + var value = AddValue(inputName, 0); + if (!value.TryConnectToSocket(outputNode, outputName)) + throw new InvalidOperationException($"Cannot connect {inputName} from node {type} to {outputName} from node {outputNode.type}"); + + return value; + } + + public Value AddValue(string id, T value) + { + // Explicit cast required for this overload to work. + return AddValue(id, (IProperty)new Property(value)); + } + + public Value AddValue(string id, Value value) + { + for (int i = 0; i < values.Count; i++) + { + if (!values[i].id.Equals(id)) + continue; + + values[i].property = value.property; + return values[i]; + } + + values.Add(value); + + Util.Log($"Added value {id} with payload {value}"); + + return value; + } + + public Value AddValue(string id, IProperty value) + { + for (int i = 0; i < values.Count; i++) + { + if (!values[i].id.Equals(id)) + continue; + + values[i].property = value; + return values[i]; + } + + var v = new Value() + { + id = id, + property = value + }; + + values.Add(v); + + Util.Log($"Added value {id} with payload {value}"); + + return v; + } + + + public bool TryGetValueById(string id, out Value value) + { + value = null; + + for (int i = 0; i < values.Count; i++) + { + if (values[i].id == id) + { + value = values[i]; + return true; + } + } + + return false; + } + + public bool TryChangeValueToReference(string inputSocket, Node outputNode, string outputSocket, out Value value) + { + value = null; + + for (int i = 0; i < values.Count; i++) + { + if (values[i].id == inputSocket) + { + value = values[i]; + + if (!value.TryConnectToSocket(outputNode, outputSocket)) + return false; + + Util.Log($"Changed value {inputSocket} on {type} to reference the output of {outputNode.type}'s value {outputSocket}"); + return true; + } + } + + return false; + } + + public Flow AddFlow(Node toNode, string fromSocket = ConstStrings.OUT, string toSocket = ConstStrings.IN) + { + for (int i = 0; i < flows.Count; i++) + { + if (flows[i].toNode == toNode && flows[i].fromSocket == fromSocket && flows[i].toSocket == toSocket) + throw new InvalidOperationException($"{type} already has a flow from {fromSocket} to {toSocket} on that node {toNode.type}!"); + } + + var flow = new Flow(this, fromSocket, toNode, toSocket); + + flows.Add(flow); + + Util.Log($"Created flow between {type} and {toNode.type} from {fromSocket} to {toSocket}"); + + onFlowAdded?.Invoke(flow); + + return flow; + } + + public bool RemoveFlow(string id) + { + Flow toRemove = null; + + for (int i = 0; i < flows.Count; i++) + { + if (flows[i].fromSocket == id) + { + toRemove = flows[i]; + break; + } + } + + if (toRemove == null) + return false; + + return RemoveFlow(toRemove); + } + + public bool RemoveFlow(Flow flow) + { + onFlowRemoved?.Invoke(flow); + + return flows.Remove(flow); + } + + public Configuration AddConfiguration(string id, T value) + { + return AddConfiguration(id, new JArray(value)); + } + + public Configuration AddConfiguration(string id, JArray value) + { + for (int i = 0; i < configuration.Count; i++) + { + if (!configuration[i].id.Equals(id)) + continue; + + configuration[i].value = value; + return configuration[i]; + } + + var config = new Configuration() + { + id = id, + value = value, + }; + + configuration.Add(config); + + return config; + } + + public void SetPositionMetadata(double x, double y) + { + metadata.positionX = x; + metadata.positionY = y; + } + + internal void AddDefaultData() + { + var specs = NodeRegistry.nodeSpecs[type]; + + var inputs = specs.GetInputs(); + + if (inputs.values != null) + { + for (int i = 0; i < inputs.values.Length; i++) + { + AddDefaultValueByType(inputs.values[i]); + } + } + + // The node data doesn't actually have to know what the output values are. + // The output info is important for validation/visual graph but not the backing data. + //var outputs = specs.GetOutputs(); + + //for (int i = 0; i < outputs.values.Length; i++) + //{ + // AddDefaultValueByType(e, outputs.values[i]); + //} + + var config = specs.GetConfiguration(); + + if (config != null) + { + for (int i = 0; i < config.Length; i++) + { + AddConfiguration(config[i].id, new JArray()); + } + } + } + + private void AddDefaultValueByType(NodeValue valueData) + { + var type = valueData.types[0]; + + if (type == typeof(int)) + AddValue(valueData.id, 0); + else if (type == typeof(float)) + AddValue(valueData.id, 0f); + else if (type == typeof(bool)) + AddValue(valueData.id, false); + else if (type == typeof(float2)) + AddValue(valueData.id, float2.zero); + else if (type == typeof(float3)) + AddValue(valueData.id, float3.zero); + else if (type == typeof(float4)) + AddValue(valueData.id, float4.zero); + else if (type == typeof(float2x2)) + AddValue(valueData.id, float2x2.zero); + else if (type == typeof(float3x3)) + AddValue(valueData.id, float3x3.zero); + else if (type == typeof(float4x4)) + AddValue(valueData.id, float4x4.zero); + else + throw new InvalidOperationException($"No default value available for {type}"); + } + + internal void OnRemovedFromGraph() + { + onRemovedFromGraph?.Invoke(); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs.meta new file mode 100644 index 000000000..9896d348a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aeff17f7b17694147a2e4bd2a291d305 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs new file mode 100644 index 000000000..2098b2385 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs @@ -0,0 +1,38 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public interface IProperty + { + public string ToString(); + + public Type GetSystemType(); + + public string GetTypeSignature(); + } + + public struct Property : IProperty + { + public Property(T value) + { + this.value = value; + } + + public override string ToString() + { + return value.ToString(); + } + + public Type GetSystemType() + { + return typeof(T); + } + + public string GetTypeSignature() + { + return Helpers.GetSignatureBySystemType(typeof(T)); + } + + public T value; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs.meta new file mode 100644 index 000000000..7cedb7f28 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Property.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd8f1a95c50b0454eaa3b63ab1c1940f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs new file mode 100644 index 000000000..757900929 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs @@ -0,0 +1,65 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class Value + { + public IProperty property; + public string id { get; set; } + public Node node { get; set; } = null; + public string socket { get; set; } = Constants.EMPTY_SOCKET_STRING; + + public event Action onConnectionChanged; + + public T GetValue() + { + return Helpers.GetPropertyValue(property); + } + + public bool TryDisconnect() + { + if (node == null && socket == Constants.EMPTY_SOCKET_STRING) + { + Util.LogWarning($"Value {id} is already disconnected from any nodes."); + return false; + } + + node = null; + socket = Constants.EMPTY_SOCKET_STRING; + + onConnectionChanged?.Invoke(this); + + return true; + } + + public bool TryConnectToSocket(Node node, string socket) + { + if (this.node == node && this.socket == socket) + { + Util.LogWarning($"Value {id} is already connected to node {node.type} on socket {socket}."); + return false; + } + + this.node = node; + this.socket = socket; + + node.onRemovedFromGraph += OnConnectedNodeRemovedFromGraph; + onConnectionChanged?.Invoke(this); + + return true; + } + + private void OnConnectedNodeRemovedFromGraph() + { + node.onRemovedFromGraph -= OnConnectedNodeRemovedFromGraph; + + TryDisconnect(); + } + + public void ChangeType(T value) + { + property = new Property(value); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs.meta new file mode 100644 index 000000000..a0204ab93 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Value.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ffa102990954874782ad68b0a544997 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs new file mode 100644 index 000000000..c162b5a61 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs @@ -0,0 +1,68 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback +{ + public class KHR_interactivity + { + public List graphs { get; set; } = new(); + public int defaultGraphIndex { get; set; } + } + + public class Declaration + { + public string op { get; set; } + public string extension { get; set; } + public List inputValueSockets { get; set; } + public List outputValueSockets { get; set; } + } + + public class ValueSocket + { + public ValueSocket(string name, int type) + { + this.name = name; + this.type = type; + } + + public string name { get; set; } + public int type { get; set; } + } + + public class Metadata + { + public double positionX { get; set; } + public double positionY { get; set; } + } + + public class Variable + { + public string id { get; set; } + public IProperty property { get; set; } + public IProperty initialValue { get; set; } + } + + public class Configuration + { + public string id { get; set; } + public JArray value { get; set; } + } + + public class InteractivityType + { + public string signature { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public TypeExtensions extensions { get; set; } + } + + public class TypeExtensions + { + public AMZN_Interactivity_String AMZN_interactivity_string { get; set; } + } + + public class AMZN_Interactivity_String + { + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs.meta new file mode 100644 index 000000000..9c0a65ea5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e5dd06f255a1874aacbf517468a3b9a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers.meta b/Runtime/Scripts/Interactivity/Playback/Data/Serializers.meta new file mode 100644 index 000000000..ab97e6b67 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ebfb073553304547b85fb42620ea7fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs new file mode 100644 index 000000000..0f9aae2c5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs @@ -0,0 +1,140 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class DeclarationsSerializer + { + public struct DeclarationData + { + public int index; + public Declaration declaration; + } + + public static Dictionary GetDeclarations(List nodes, Dictionary typeIndexByType) + { + var declarations = new Dictionary(); + + string nodeName; + var declarationCount = 0; + for (int i = 0; i < nodes.Count; i++) + { + nodeName = nodes[i].type; + + if (declarations.ContainsKey(nodeName)) + continue; + + var dec = new DeclarationData() + { + index = declarationCount++, + declaration = GetDeclaration(nodes[i].type, typeIndexByType) + }; + + declarations.Add(nodeName, dec); + } + + return declarations; + } + + internal static void WriteJson(JsonWriter writer, Dictionary declarations) + { + writer.WritePropertyName(ConstStrings.DECLARATIONS); + writer.WriteStartArray(); + + var orderedDeclarations = new Declaration[declarations.Count]; + + foreach (var kvp in declarations) + { + orderedDeclarations[kvp.Value.index] = kvp.Value.declaration; + } + + for (int i = 0; i < orderedDeclarations.Length; i++) + { + WriteDeclaration(writer, orderedDeclarations[i]); + } + + writer.WriteEndArray(); + } + + private static void WriteDeclaration(JsonWriter writer, Declaration declaration) + { + writer.WriteStartObject(); + + writer.WritePropertyName(ConstStrings.OP); + writer.WriteValue(declaration.op); + + if (!string.IsNullOrWhiteSpace(declaration.extension)) + { + writer.WritePropertyName(ConstStrings.EXTENSION); + writer.WriteValue(declaration.extension); + } + + if (!declaration.outputValueSockets.IsNullOrEmpty()) + WriteValueSockets(writer, ConstStrings.OUTPUT_VALUE_SOCKETS, declaration.outputValueSockets); + + if (!declaration.inputValueSockets.IsNullOrEmpty()) + WriteValueSockets(writer, ConstStrings.INPUT_VALUE_SOCKETS, declaration.inputValueSockets); + + writer.WriteEndObject(); + } + + private static void WriteValueSockets(JsonWriter writer, string propertyName, List valueSockets) + { + writer.WritePropertyName(propertyName); + writer.WriteStartObject(); + for (int i = 0; i < valueSockets.Count; i++) + { + writer.WritePropertyName(valueSockets[i].name); + writer.WriteStartObject(); + writer.WritePropertyName(ConstStrings.TYPE); + writer.WriteValue(valueSockets[i].type); + writer.WriteEndObject(); + } + writer.WriteEndObject(); + } + + private static Declaration GetDeclaration(string id, Dictionary typeIndexByType) + { + // Only extension nodes need fancy declarations. + return id switch + { + "event/onSelect" => new Declaration() + { + op = "event/onSelect", + extension = GLTF.Schema.KHR_node_selectability_Factory.EXTENSION_NAME, + outputValueSockets = new List() + { + new ValueSocket(ConstStrings.SELECTED_NODE_INDEX, typeIndexByType[typeof(int)]), + new ValueSocket(ConstStrings.CONTROLLER_INDEX, typeIndexByType[typeof(int)]), + new ValueSocket(ConstStrings.SELECTION_POINT, typeIndexByType[typeof(float3)]), + new ValueSocket(ConstStrings.SELECTION_RAY_ORIGIN, typeIndexByType[typeof(float3)]), + } + }, + "event/onHoverIn" => new Declaration() + { + op = "event/onHoverIn", + extension = GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME, + outputValueSockets = new List() + { + new ValueSocket(ConstStrings.HOVER_NODE_INDEX, typeIndexByType[typeof(int)]), + new ValueSocket(ConstStrings.CONTROLLER_INDEX, typeIndexByType[typeof(int)]), + } + }, + "event/onHoverOut" => new Declaration() + { + op = "event/onHoverOut", + extension = GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME, + outputValueSockets = new List() + { + new ValueSocket(ConstStrings.HOVER_NODE_INDEX, typeIndexByType[typeof(int)]), + new ValueSocket(ConstStrings.CONTROLLER_INDEX, typeIndexByType[typeof(int)]), + } + }, + _ => new Declaration() { op = id } + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs.meta new file mode 100644 index 000000000..85d0463ee --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Declarations.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afd789db4bda6a9498c354c3465bcd19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs new file mode 100644 index 000000000..4563b249d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class EventsSerializer + { + public static void WriteJson(JsonWriter writer, List events, Dictionary typeIndexByType) + { + writer.WritePropertyName(ConstStrings.EVENTS); + writer.WriteStartArray(); + + for (int i = 0; i < events.Count; i++) + { + WriteEvent(writer, events[i], typeIndexByType); + } + + writer.WriteEndArray(); + } + + private static void WriteEvent(JsonWriter writer, Customevent customevent, Dictionary typeIndexByType) + { + writer.WriteStartObject(); + + writer.WritePropertyName(ConstStrings.ID); + writer.WriteValue(customevent.id); + + WriteEventValues(writer, customevent.values, typeIndexByType); + + writer.WriteEndObject(); + } + + private static void WriteEventValues(JsonWriter writer, List values, Dictionary typeIndexByType) + { + writer.WritePropertyName(ConstStrings.VALUES); + writer.WriteStartObject(); + + for (int i = 0; i < values.Count; i++) + { + WriteEventValue(writer, values[i], typeIndexByType); + } + + writer.WriteEndObject(); + } + + private static void WriteEventValue(JsonWriter writer, EventValue eventValue, Dictionary typeIndexByType) + { + writer.WritePropertyName(eventValue.id); + + writer.WriteStartObject(); + + NodesSerializer.WriteValueLiteral(writer, eventValue.property, typeIndexByType); + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs.meta new file mode 100644 index 000000000..b5ce7e5e6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Events.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efdc2300edea3074fbf91506d780b3c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs new file mode 100644 index 000000000..40d8f8416 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs @@ -0,0 +1,246 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Assertions; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class NodesSerializer + { + public static void WriteJson(JsonWriter writer, List nodes, Dictionary declarations, Dictionary typeIndexByType) + { + writer.WritePropertyName(ConstStrings.NODES); + writer.WriteStartArray(); + + for (int i = 0; i < nodes.Count; i++) + { + WriteNode(writer, nodes, i, declarations, typeIndexByType); + } + + writer.WriteEndArray(); + } + + private static void WriteNode(JsonWriter writer, List nodes, int nodeIndex, Dictionary declarations, Dictionary typeIndexByType) + { + var node = nodes[nodeIndex]; + + writer.WriteStartObject(); + + writer.WritePropertyName(ConstStrings.DECLARATION); + writer.WriteValue(declarations[node.type].index); + + WriteConfiguration(writer, node.configuration); + WriteValues(writer, nodes, nodeIndex, typeIndexByType); + WriteFlows(writer, nodes, nodeIndex); + WriteMetadata(writer, node.metadata); + + writer.WriteEndObject(); + } + + private static void WriteMetadata(JsonWriter writer, Metadata metadata) + { + writer.WritePropertyName(ConstStrings.METADATA); + writer.WriteStartObject(); + + writer.WritePropertyName("positionX"); + writer.WriteValue(metadata.positionX.ToString()); + + writer.WritePropertyName("positionY"); + writer.WriteValue(metadata.positionY.ToString()); + + writer.WriteEndObject(); + } + + private static void WriteFlows(JsonWriter writer, List nodes, int nodeIndex) + { + var flows = nodes[nodeIndex].flows; + + writer.WritePropertyName(ConstStrings.FLOWS); + writer.WriteStartObject(); + + for (int i = 0; i < flows.Count; i++) + { + WriteFlow(writer, nodes, flows[i]); + } + + writer.WriteEndObject(); + } + + private static void WriteFlow(JsonWriter writer, List nodes, Flow flow) + { + writer.WritePropertyName(flow.fromSocket); + writer.WriteStartObject(); + + writer.WritePropertyName(ConstStrings.NODE); + + var targetNodeIndex = nodes.IndexOf(flow.toNode); + Assert.AreNotEqual(targetNodeIndex, -1); + writer.WriteValue(targetNodeIndex); + + writer.WritePropertyName(ConstStrings.SOCKET); + writer.WriteValue(flow.toSocket); + + writer.WriteEndObject(); + } + + private static void WriteConfiguration(JsonWriter writer, List configuration) + { + writer.WritePropertyName(ConstStrings.CONFIGURATION); + writer.WriteStartObject(); + + for (int i = 0; i < configuration.Count; i++) + { + WriteConfigurationEntry(writer, configuration[i]); + } + + writer.WriteEndObject(); + } + + private static void WriteConfigurationEntry(JsonWriter writer, Configuration configuration) + { + writer.WritePropertyName(configuration.id); + + writer.WriteStartObject(); + writer.WritePropertyName(ConstStrings.VALUE); + + if (configuration.value is JArray array) + array.WriteTo(writer); + else + writer.WriteValue(configuration.value); + + writer.WriteEndObject(); + } + + private static void WriteValues(JsonWriter writer, List nodes, int nodeIndex, Dictionary typeIndexByType) + { + var values = nodes[nodeIndex].values; + + writer.WritePropertyName(ConstStrings.VALUES); + writer.WriteStartObject(); + + for (int i = 0; i < values.Count; i++) + { + WriteValue(writer, nodes, values[i], typeIndexByType); + } + + writer.WriteEndObject(); + } + + private static void WriteValue(JsonWriter writer, List nodes, Value value, Dictionary typeIndexByType) + { + writer.WritePropertyName(value.id); + writer.WriteStartObject(); + + if (value.node != null) + { + writer.WritePropertyName(ConstStrings.NODE); + var targetNodeIndex = nodes.IndexOf(value.node); + Assert.AreNotEqual(targetNodeIndex, -1); + writer.WriteValue(targetNodeIndex); + + writer.WritePropertyName(ConstStrings.SOCKET); + writer.WriteValue(value.socket); + } + else + { + WriteValueLiteral(writer, value.property, typeIndexByType); + } + + writer.WriteEndObject(); + } + + public static void WriteValueLiteral(JsonWriter writer, IProperty property, Dictionary typeIndexByType) + { + writer.WritePropertyName(ConstStrings.VALUE); + writer.WriteStartArray(); + + var type = Constants.INVALID_TYPE_INDEX; + + switch (property) + { + case Property iProp: + writer.WriteValue(iProp.value); + type = typeIndexByType[typeof(int)]; + break; + case Property fProp: + writer.WriteValue(fProp.value); + type = typeIndexByType[typeof(float)]; + break; + case Property bProp: + writer.WriteValue(bProp.value); + type = typeIndexByType[typeof(bool)]; + break; + case Property f2Prop: + writer.WriteValue(f2Prop.value.x); + writer.WriteValue(f2Prop.value.y); + type = typeIndexByType[typeof(float2)]; + break; + case Property f3Prop: + writer.WriteValue(f3Prop.value.x); + writer.WriteValue(f3Prop.value.y); + writer.WriteValue(f3Prop.value.z); + type = typeIndexByType[typeof(float3)]; + break; + case Property f4Prop: + writer.WriteValue(f4Prop.value.x); + writer.WriteValue(f4Prop.value.y); + writer.WriteValue(f4Prop.value.z); + writer.WriteValue(f4Prop.value.w); + type = typeIndexByType[typeof(float4)]; + break; + case Property p: + writer.WriteValue(p.value.c0.x); + writer.WriteValue(p.value.c0.y); + writer.WriteValue(p.value.c1.x); + writer.WriteValue(p.value.c0.y); + type = typeIndexByType[typeof(float2x2)]; + break; + case Property p: + writer.WriteValue(p.value.c0.x); + writer.WriteValue(p.value.c0.y); + writer.WriteValue(p.value.c0.z); + writer.WriteValue(p.value.c1.x); + writer.WriteValue(p.value.c1.y); + writer.WriteValue(p.value.c1.z); + writer.WriteValue(p.value.c2.x); + writer.WriteValue(p.value.c2.y); + writer.WriteValue(p.value.c2.z); + type = typeIndexByType[typeof(float3x3)]; + break; + case Property p: + writer.WriteValue(p.value.c0.x); + writer.WriteValue(p.value.c0.y); + writer.WriteValue(p.value.c0.z); + writer.WriteValue(p.value.c0.w); + writer.WriteValue(p.value.c1.x); + writer.WriteValue(p.value.c1.y); + writer.WriteValue(p.value.c1.z); + writer.WriteValue(p.value.c1.w); + writer.WriteValue(p.value.c2.x); + writer.WriteValue(p.value.c2.y); + writer.WriteValue(p.value.c2.z); + writer.WriteValue(p.value.c2.w); + writer.WriteValue(p.value.c3.x); + writer.WriteValue(p.value.c3.y); + writer.WriteValue(p.value.c3.z); + writer.WriteValue(p.value.c3.w); + type = typeIndexByType[typeof(float4x4)]; + break; + case Property p: + writer.WriteValue(p.value); + type = typeIndexByType[typeof(string)]; + break; + default: + throw new NotImplementedException(); + } + + writer.WriteEndArray(); + + writer.WritePropertyName(ConstStrings.TYPE); + writer.WriteValue(type); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs.meta new file mode 100644 index 000000000..66790951a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c9971d5996019942865409e0b436193 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs new file mode 100644 index 000000000..82a42b03e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class TypesSerializer + { + public static Dictionary GetSystemTypeByIndexDictionary(Graph value) + { + var typeIndexByType = new Dictionary(); + + var count = 0; + foreach(var type in value.types) + { + var systemType = Helpers.GetSystemType(type); + + if (typeIndexByType.ContainsKey(systemType)) + continue; + + typeIndexByType.Add(systemType, count++); + } + + return typeIndexByType; + } + + public static void WriteJson(JsonWriter writer, List types) + { + writer.WritePropertyName(ConstStrings.TYPES); + writer.WriteStartArray(); + + for (int i = 0; i < types.Count; i++) + { + WriteType(writer, types[i]); + } + + writer.WriteEndArray(); + } + + private static void WriteType(JsonWriter writer, InteractivityType type) + { + writer.WriteStartObject(); + + writer.WritePropertyName(ConstStrings.SIGNATURE); + writer.WriteValue(type.signature); + + // TODO: Add support for type extensions here. + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs.meta new file mode 100644 index 000000000..2960fefa6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Types.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c142dfcc2a1b91146841325c72c34881 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs new file mode 100644 index 000000000..98e0734d6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class VariablesSerializer + { + public static void WriteJson(JsonWriter writer, List variables, Dictionary typeIndexByType) + { + writer.WritePropertyName("variables"); + writer.WriteStartArray(); + + for (int i = 0; i < variables.Count; i++) + { + WriteVariable(writer, variables[i], typeIndexByType); + } + + writer.WriteEndArray(); + } + + private static void WriteVariable(JsonWriter writer, Variable variable, Dictionary typeIndexByType) + { + writer.WriteStartObject(); + + writer.WritePropertyName(ConstStrings.ID); + writer.WriteValue(variable.id); + + NodesSerializer.WriteValueLiteral(writer, variable.initialValue, typeIndexByType); + + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs.meta b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs.meta new file mode 100644 index 000000000..9e5794565 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Variables.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: acaf735450b91c74ca50988858c7e4b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs b/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs new file mode 100644 index 000000000..76e3b0573 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs @@ -0,0 +1,147 @@ +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventWrapper : MonoBehaviour + { + private const int MAX_RAYCAST_HITS = 32; + + public KHR_interactivity extensionData { get; private set; } + public BehaviourEngine engine { get; private set; } + + private static readonly RaycastHit[] _raycastHits = new RaycastHit[MAX_RAYCAST_HITS]; + private static readonly RaycastHit[] _selectableHits = new RaycastHit[MAX_RAYCAST_HITS]; + private static readonly RaycastHit[] _hoverableHits = new RaycastHit[MAX_RAYCAST_HITS]; + + private RayArgs _currentHover; + private bool _isHovering; + + // TODO: Make this wrapper accept an array of BehaviourEngine objects so we can switch which graphs are being executed. + public void SetData(BehaviourEngine engine, KHR_interactivity extensionData) + { + this.extensionData = extensionData; + this.engine = engine; + } + + private void Start() + { + engine.StartPlayback(); + } + + private void Update() + { + CheckForObjectHoverOrSelect(); + engine.Tick(); + } + + public void Select(in RayArgs args) + { + engine.Select(args); + } + + private void HoverIn(in RayArgs args) + { + _isHovering = true; + _currentHover = args; + engine.HoverIn(args); + } + + private void HoverOut(in RayArgs args) + { + _isHovering = false; + engine.HoverOut(args); + } + + private void CheckForObjectHoverOrSelect() + { + var ray = Camera.main.ScreenPointToRay(Input.mousePosition); + + var hitCount = Physics.RaycastNonAlloc(ray, _raycastHits); + + if (hitCount <= 0) + { + if (_isHovering) + HoverOut(_currentHover); + + return; + } + + NodePointers pointers; + GameObject go; + + var selectableCount = 0; + RaycastHit closestSelectableHit = default; + float closestSelectableHitDistance = float.MaxValue; + + var hoverableCount = 0; + RaycastHit closestHoverableHit = default; + float closestHoverableHitDistance = float.MaxValue; + + for (int i = 0; i < hitCount; i++) + { + go = _raycastHits[i].transform.gameObject; + // If this is an app with both gltf and non-gltf content things may not have pointers. + if (!engine.pointerResolver.TryGetPointersOf(go, out pointers)) + continue; + + if (pointers.selectability.getter()) + { + _selectableHits[selectableCount++] = _raycastHits[i]; + + if (_raycastHits[i].distance < closestSelectableHitDistance) + { + closestSelectableHit = _raycastHits[i]; + closestSelectableHitDistance = _raycastHits[i].distance; + } + } + + if (pointers.hoverability.getter()) + { + _hoverableHits[hoverableCount++] = _raycastHits[i]; + + if (_raycastHits[i].distance < closestHoverableHitDistance) + { + closestHoverableHit = _raycastHits[i]; + closestHoverableHitDistance = _raycastHits[i].distance; + } + } + } + + // Select + if (Input.GetMouseButtonDown(0) && TryGetValidHit(selectableCount, closestSelectableHit, ray, out var selectedArgs)) + Select(selectedArgs); + + // Hover + if (TryGetValidHit(hoverableCount, closestHoverableHit, ray, out var hoveredArgs)) + { + if (_isHovering) + { + if (_currentHover.hit.transform != closestHoverableHit.transform) + HoverOut(_currentHover); + } + else + HoverIn(hoveredArgs); + } + else if (_isHovering) + HoverOut(_currentHover); + } + + private static bool TryGetValidHit(int count, RaycastHit closestHit, in Ray ray, out RayArgs args) + { + args = default; + + if (count <= 0) + return false; + + // TODO: Controller Index is 0 for now, need to extend this for different use-cases. + args = new RayArgs() + { + controllerIndex = 0, + ray = ray, + hit = closestHit + }; + + return true; + } + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs.meta b/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs.meta new file mode 100644 index 000000000..cf41c326c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4555014770a6b06469efb6ab711f9869 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs b/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs new file mode 100644 index 000000000..ea4cc521c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs @@ -0,0 +1,38 @@ +using System.Threading; + +namespace UnityGLTF.Interactivity.Playback +{ + public interface ICancelToken + { + public bool isCancelled { get; } + } + + public struct NodeEngineCancelToken : ICancelToken + { + public CancellationToken engineToken; + public CancellationToken nodeToken; + + public NodeEngineCancelToken(CancellationToken engineToken, CancellationToken nodeToken) + { + this.engineToken = engineToken; + this.nodeToken = nodeToken; + } + + public bool isCancelled => engineToken.IsCancellationRequested || nodeToken.IsCancellationRequested; + } + + public struct EngineCancelToken : ICancelToken + { + public CancellationToken engineToken; + + public EngineCancelToken(CancellationToken engineToken) + { + this.engineToken = engineToken; + } + + public bool isCancelled => engineToken.IsCancellationRequested; + + public static implicit operator CancellationToken(EngineCancelToken d) => d.engineToken; + public static implicit operator EngineCancelToken(CancellationToken d) => new EngineCancelToken(d); + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta b/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta new file mode 100644 index 000000000..4a051385f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39477dd4b73bae340a7144d4ee556f9a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs b/Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs new file mode 100644 index 000000000..6882e9732 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Pool; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct NodeDelayData + { + public int delayIndex; + public FlowSetDelay sourceNode; + public float finishTime; + public Action doneCallback; + } + + public class NodeDelayManager + { + private List _delayedNodes = new(); + private int _currentDelayIndex = -1; + + public void OnTick() + { + // Avoiding iterating over a changing collection by grabbing a pooled list. + var temp = ListPool.Get(); + try + { + for (int i = 0; i < _delayedNodes.Count; i++) + { + if (Time.time >= _delayedNodes[i].finishTime) + temp.Add(_delayedNodes[i]); + } + + for (int i = 0; i < temp.Count; i++) + { + temp[i].doneCallback(); + _delayedNodes.Remove(temp[i]); + } + } + finally + { + ListPool.Release(temp); + } + } + + public int AddDelayNode(FlowSetDelay sourceNode, float duration, Action doneCallback) + { + _currentDelayIndex++; + + _delayedNodes.Add(new NodeDelayData() + { + delayIndex = _currentDelayIndex, + sourceNode = sourceNode, + finishTime = Time.time + duration, + doneCallback = doneCallback + }); + + return _currentDelayIndex; + } + + public void CancelDelayByIndex(int delayIndex) + { + for (int i = 0; i < _delayedNodes.Count; i++) + { + if (_delayedNodes[i].delayIndex != delayIndex) + continue; + + _delayedNodes.RemoveAt(i); + return; + } + } + + public void CancelDelaysFromNode(FlowSetDelay sourceNode) + { + _delayedNodes.RemoveAll(e => e.sourceNode == sourceNode); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs.meta new file mode 100644 index 000000000..7d70022c6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeDelayManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50dcfacd9f61a4c918226ff95ac60677 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs new file mode 100644 index 000000000..3e3cf1500 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public static class NodeRegistry + { + public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engine, Node node) + { + if (!nodeTypes.TryGetValue(node.type, out var creationMethod)) + { + // Using Debug.Log here instead of Util.Log since this should be a message that shows up in prod. + Debug.LogWarning($"No node found for operation {node.type}, creating a NoOp node."); + return new NoOp(engine, node); + } + + return creationMethod(engine, node); + } + + private static readonly Dictionary> nodeTypes = new() + { + ["animation/start"] = (engine, node) => new AnimationStart(engine, node), + ["animation/stop"] = (engine, node) => new AnimationStop(engine, node), + ["animation/stopAt"] = (engine, node) => new AnimationStopAt(engine, node), + ["ADBE/output_console_node"] = (engine, node) => new DebugLog(engine, node), + ["debug/log"] = (engine, node) => new DebugLog(engine, node), + ["event/onHoverIn"] = (engine, node) => new EventOnHoverIn(engine, node), + ["event/onHoverOut"] = (engine, node) => new EventOnHoverOut(engine, node), + ["event/onStart"] = (engine, node) => new EventOnStart(engine, node), + ["event/onSelect"] = (engine, node) => new EventOnSelect(engine, node), + ["event/onTick"] = (engine, node) => new EventOnTick(engine, node), + ["event/receive"] = (engine, node) => new EventReceive(engine, node), + ["event/send"] = (engine, node) => new EventSend(engine, node), + ["flow/branch"] = (engine, node) => new FlowBranch(engine, node), + ["flow/cancelDelay"] = (engine, node) => new FlowCancelDelay(engine, node), + ["flow/doN"] = (engine, node) => new FlowDoN(engine, node), + ["flow/for"] = (engine, node) => new FlowFor(engine, node), + ["flow/multiGate"] = (engine, node) => new FlowMultiGate(engine, node), + ["flow/sequence"] = (engine, node) => new FlowSequence(engine, node), + ["flow/setDelay"] = (engine, node) => new FlowSetDelay(engine, node), + ["flow/switch"] = (engine, node) => new FlowSwitch(engine, node), + ["flow/throttle"] = (engine, node) => new FlowThrottle(engine, node), + ["flow/waitAll"] = (engine, node) => new FlowWaitAll(engine, node), + ["flow/while"] = (engine, node) => new FlowWhile(engine, node), + ["math/abs"] = (engine, node) => new MathAbs(engine, node), + ["math/add"] = (engine, node) => new MathAdd(engine, node), + ["math/and"] = (engine, node) => new MathAnd(engine, node), + ["math/asr"] = (engine, node) => new MathAsr(engine, node), + ["math/cbrt"] = (engine, node) => new MathCbrt(engine, node), + ["math/clamp"] = (engine, node) => new MathClamp(engine, node), + ["math/clz"] = (engine, node) => new MathClz(engine, node), + ["math/ctz"] = (engine, node) => new MathCtz(engine, node), + ["math/combine2"] = (engine, node) => new MathCombine2(engine, node), + ["math/combine3"] = (engine, node) => new MathCombine3(engine, node), + ["math/combine4"] = (engine, node) => new MathCombine4(engine, node), + ["math/combine2x2"] = (engine, node) => new MathCombine2x2(engine, node), + ["math/combine3x3"] = (engine, node) => new MathCombine3x3(engine, node), + ["math/combine4x4"] = (engine, node) => new MathCombine4x4(engine, node), + ["math/cos"] = (engine, node) => new MathCos(engine, node), + ["math/deg"] = (engine, node) => new MathDeg(engine, node), + ["math/div"] = (engine, node) => new MathDiv(engine, node), + ["math/e"] = (engine, node) => new MathE(engine, node), + ["math/eq"] = (engine, node) => new MathEq(engine, node), + ["math/extract2"] = (engine, node) => new MathExtract2(engine, node), + ["math/extract3"] = (engine, node) => new MathExtract3(engine, node), + ["math/extract4"] = (engine, node) => new MathExtract4(engine, node), + ["math/extract2x2"] = (engine, node) => new MathExtract2x2(engine, node), + ["math/extract3x3"] = (engine, node) => new MathExtract3x3(engine, node), + ["math/extract4x4"] = (engine, node) => new MathExtract4x4(engine, node), + ["math/floor"] = (engine, node) => new MathFloor(engine, node), + ["math/ge"] = (engine, node) => new MathGe(engine, node), + ["math/gt"] = (engine, node) => new MathGt(engine, node), + ["math/isnan"] = (engine, node) => new MathIsNaN(engine, node), + ["math/le"] = (engine, node) => new MathLe(engine, node), + ["math/length"] = (engine, node) => new MathLength(engine, node), + ["math/lsl"] = (engine, node) => new MathLsl(engine, node), + ["math/lt"] = (engine, node) => new MathLt(engine, node), + ["math/mix"] = (engine, node) => new MathMix(engine, node), + ["math/mul"] = (engine, node) => new MathMul(engine, node), + ["math/neg"] = (engine, node) => new MathNeg(engine, node), + ["math/not"] = (engine, node) => new MathNot(engine, node), + ["math/popcnt"] = (engine, node) => new MathPopcnt(engine, node), + ["math/pow"] = (engine, node) => new MathPow(engine, node), + ["math/rad"] = (engine, node) => new MathRad(engine, node), + ["math/random"] = (engine, node) => new MathRandom(engine, node), + ["math/rem"] = (engine, node) => new MathRem(engine, node), + ["math/saturate"] = (engine, node) => new MathSaturate(engine, node), + ["math/select"] = (engine, node) => new MathSelect(engine, node), + ["math/sin"] = (engine, node) => new MathSin(engine, node), + ["math/sqrt"] = (engine, node) => new MathSqrt(engine, node), + ["math/sub"] = (engine, node) => new MathSub(engine, node), + ["math/tan"] = (engine, node) => new MathTan(engine, node), + ["pointer/get"] = (engine, node) => new PointerGet(engine, node), + ["pointer/interpolate"] = (engine, node) => new PointerInterpolate(engine, node), + ["pointer/set"] = (engine, node) => new PointerSet(engine, node), + ["type/boolToInt"] = (engine, node) => new TypeBoolToInt(engine, node), + ["type/boolToFloat"] = (engine, node) => new TypeBoolToFloat(engine, node), + ["type/floatToBool"] = (engine, node) => new TypeFloatToBool(engine, node), + ["type/floatToInt"] = (engine, node) => new TypeFloatToInt(engine, node), + ["type/intToBool"] = (engine, node) => new TypeIntToBool(engine, node), + ["type/intToFloat"] = (engine, node) => new TypeIntToFloat(engine, node), + ["variable/get"] = (engine, node) => new VariableGet(engine, node), + ["variable/interpolate"] = (engine, node) => new VariableInterpolate(engine, node), + ["variable/set"] = (engine, node) => new VariableSet(engine, node), + ["variable/setMultiple"] = (engine, node) => new VariableSetMultiple(engine, node), + ["math/acos"] = (engine, node) => new MathACos(engine, node), + ["math/acosh"] = (engine, node) => new MathACosH(engine, node), + ["math/asin"] = (engine, node) => new MathASin(engine, node), + ["math/asinh"] = (engine, node) => new MathASinH(engine, node), + ["math/atan"] = (engine, node) => new MathATan(engine, node), + ["math/atan2"] = (engine, node) => new MathATan2(engine, node), + ["math/atanh"] = (engine, node) => new MathATanH(engine, node), + ["math/ceil"] = (engine, node) => new MathCeil(engine, node), + ["math/cosh"] = (engine, node) => new MathCosH(engine, node), + ["math/cross"] = (engine, node) => new MathCross(engine, node), + ["math/determinant"] = (engine, node) => new MathDeterminant(engine, node), + ["math/dot"] = (engine, node) => new MathDot(engine, node), + ["math/exp"] = (engine, node) => new MathExp(engine, node), + ["math/fract"] = (engine, node) => new MathFract(engine, node), + ["math/inf"] = (engine, node) => new MathInf(engine, node), + ["math/inverse"] = (engine, node) => new MathInverse(engine, node), + ["math/isinf"] = (engine, node) => new MathIsInf(engine, node), + ["math/log"] = (engine, node) => new MathLog(engine, node), + ["math/log10"] = (engine, node) => new MathLog10(engine, node), + ["math/log2"] = (engine, node) => new MathLog2(engine, node), + ["math/matCompose"] = (engine, node) => new MathMatCompose(engine, node), + ["math/matDecompose"] = (engine, node) => new MathMatDecompose(engine, node), + ["math/matmul"] = (engine, node) => new MathMatMul(engine, node), + ["math/min"] = (engine, node) => new MathMin(engine, node), + ["math/max"] = (engine, node) => new MathMax(engine, node), + ["math/nan"] = (engine, node) => new MathNaN(engine, node), + ["math/normalize"] = (engine, node) => new MathNormalize(engine, node), + ["math/or"] = (engine, node) => new MathOr(engine, node), + ["math/pi"] = (engine, node) => new MathPi(engine, node), + ["math/rotate2d"] = (engine, node) => new MathRotate2D(engine, node), + ["math/rotate3d"] = (engine, node) => new MathRotate3D(engine, node), + ["math/round"] = (engine, node) => new MathRound(engine, node), + ["math/sign"] = (engine, node) => new MathSign(engine, node), + ["math/sinh"] = (engine, node) => new MathSinH(engine, node), + ["math/switch"] = (engine, node) => new MathSwitch(engine, node), + ["math/tanh"] = (engine, node) => new MathTanH(engine, node), + ["math/transform"] = (engine, node) => new MathTransform(engine, node), + ["math/transpose"] = (engine, node) => new MathTranspose(engine, node), + ["math/trunc"] = (engine, node) => new MathTrunc(engine, node), + ["math/xor"] = (engine, node) => new MathXor(engine, node), + + }; + + public static readonly Dictionary nodeSpecs = new() + { + ["animation/start"] = new AnimationStartSpec(), + ["animation/stop"] = new AnimationStopSpec(), + ["animation/stopAt"] = new AnimationStopAtSpec(), + ["debug/log"] = new DebugLogSpec(), + ["debug/assert"] = new DebugAssertSpec(), + // ["event/onHoverIn"] = new EventOnHoverInSpec(), + // ["event/onHoverOut"] = new EventOnHoverOutSpec(), + ["event/onStart"] = new EventOnStartSpec(), + ["event/send"] = new EventSendSpec(), + // ["event/onSelect"] = new EventOnSelectSpec(), + ["event/onTick"] = new EventOnTickSpec(), + ["event/receive"] = new EventReceiveSpec(), + ["flow/branch"] = new FlowBranchSpec(), + ["flow/cancelDelay"] = new FlowCancelDelaySpec(), + ["flow/doN"] = new FlowDoNSpec(), + ["flow/for"] = new FlowForSpec(), + ["flow/multiGate"] = new FlowMultiGateSpec(), + ["flow/sequence"] = new FlowSequenceSpec(), + ["flow/setDelay"] = new FlowSetDelaySpec(), + ["flow/switch"] = new FlowSwitchSpec(), + ["flow/throttle"] = new FlowThrottleSpec(), + ["flow/waitAll"] = new FlowWaitAllSpec(), + ["flow/while"] = new FlowWhileSpec(), + ["pointer/interpolate"] = new PointerInterpolateSpecs(), + ["pointer/set"] = new PointerSetSpecs(), + ["pointer/get"] = new PointerGetSpecs(), + ["math/abs"] = new MathOneOperandSpec("The absolute value."), + ["math/add"] = new MathTwoOperandsSpec(), + ["math/and"] = new MathTwoOperandsSpec(), + ["math/asr"] = new MathTwoOperandsSpec(), + ["math/cbrt"] = new MathOneOperandFloatSpec(), + ["math/clamp"] = new MathThreeOperandsSpec(), + ["math/clz"] = new MathOneOperandSpec(), + ["math/ctz"] = new MathOneOperandSpec(), + ["math/combine2"] = new MathOneOperandRetSpec(2), + ["math/combine3"] = new MathOneOperandRetSpec(3), + ["math/combine4"] = new MathOneOperandRetSpec(4), + ["math/combine2x2"] = new MathOneOperandRetSpec(4), + ["math/combine3x3"] = new MathOneOperandRetSpec(9), + ["math/combine4x4"] = new MathOneOperandRetSpec(16), + ["math/cos"] = new MathOneOperandFloatSpec(), + ["math/deg"] = new MathOneOperandFloatSpec(), + ["math/div"] = new MathTwoOperandsSpec(), + ["math/e"] = new MathConstantSpec(), + ["math/eq"] = new MathTwoOperandsRetSpec(), + ["math/supereq"] = new MathTwoOperandsRetSpec(), + ["math/approxeq"] = new MathTwoOperandsRetSpec(), + ["math/extract2"] = new MathExtractSpec(), + ["math/extract3"] = new MathExtractSpec(), + ["math/extract4"] = new MathExtractSpec(), + ["math/extract2x2"] = new MathExtractSpec(), + ["math/extract3x3"] = new MathExtractSpec(), + ["math/extract4x4"] = new MathExtractSpec(), + ["math/floor"] = new MathOneOperandFloatSpec(), + ["math/ge"] = new MathTwoOperandsRetSpec(), + ["math/gt"] = new MathTwoOperandsRetSpec(), + ["math/isnan"] = new MathOneOperandRetSpec(), + ["math/le"] = new MathTwoOperandsRetSpec(), + ["math/length"] = new MathOneOperandSpec(), + ["math/lsl"] = new MathTwoOperandsSpec(), + ["math/lt"] = new MathTwoOperandsRetSpec(), + ["math/mix"] = new MathThreeOperandsSpec(), + ["math/mul"] = new MathTwoOperandsSpec(), + ["math/neg"] = new MathOneOperandSpec(), + ["math/not"] = new MathOneOperandSpec(), + ["math/popcnt"] = new MathOneOperandSpec(), + ["math/pow"] = new MathTwoOperandsFloatSpec(), + ["math/rad"] = new MathOneOperandFloatSpec(), + ["math/random"] = new MathRandomSpec(), + ["math/rem"] = new MathTwoOperandsSpec(), + ["math/saturate"] = new MathOneOperandFloatSpec(), + ["math/select"] = new MathSelectSpec(), + ["math/sin"] = new MathOneOperandFloatSpec(), + ["math/sqrt"] = new MathOneOperandFloatSpec(), + ["math/sub"] = new MathTwoOperandsSpec(), + ["math/tan"] = new MathOneOperandFloatSpec(), + ["type/boolToInt"] = new MathOneOperandRetSpec(), + ["type/boolToFloat"] = new MathOneOperandRetSpec(), + ["type/floatToBool"] = new MathOneOperandRetSpec(), + ["type/floatToInt"] = new MathOneOperandRetSpec(), + ["type/intToBool"] = new MathOneOperandRetSpec(), + ["type/intToFloat"] = new MathOneOperandRetSpec(), + ["math/acos"] = new MathOneOperandSpec(), + ["math/acosh"] = new MathOneOperandSpec(), + ["math/asin"] = new MathOneOperandSpec(), + ["math/asinh"] = new MathOneOperandSpec(), + ["math/atan"] = new MathOneOperandFloatSpec(), + ["math/atan2"] = new MathTwoOperandsFloatSpec(), + ["math/atanh"] = new MathOneOperandFloatSpec(), + ["math/ceil"] = new MathOneOperandFloatSpec(), + ["math/cosh"] = new MathOneOperandFloatSpec(), + ["math/cross"] = new MathTwoOperandsSpec(), + ["math/determinant"] = new MathOneOperandRetSpec(), + ["math/dot"] = new MathTwoOperandsRetSpec(), + ["math/exp"] = new MathOneOperandFloatSpec(), + ["math/fract"] = new MathOneOperandFloatSpec(), + ["math/inf"] = new MathConstantSpec(), + ["math/inverse"] = new MathOneOperandSpec(), + ["math/isinf"] = new MathOneOperandRetSpec(), + ["math/log"] = new MathOneOperandFloatSpec(), + ["math/log10"] = new MathOneOperandFloatSpec(), + ["math/log2"] = new MathOneOperandFloatSpec(), + ["math/matCompose"] = new MathMatComposeSpec(), + ["math/matDecompose"] = new MathMatDecomposeSpec(), + ["math/matmul"] = new MathTwoOperandsSpec(), + ["math/min"] = new MathTwoOperandsSpec(), + ["math/max"] = new MathTwoOperandsSpec(), + ["math/nan"] = new MathConstantSpec(), + ["math/normalize"] = new MathOneOperandSpec(), + ["math/or"] = new MathTwoOperandsSpec(), + ["math/pi"] = new MathConstantSpec(), + ["math/rotate2d"] = new MathTwoOperandsRetSpec(), + ["math/rotate3d"] = new MathRotate3DSpec(), + ["math/round"] = new MathOneOperandFloatSpec(), + ["math/sign"] = new MathOneOperandSpec(), + ["math/sinh"] = new MathOneOperandFloatSpec(), + ["math/switch"] = new MathSwitchSpec(), + ["math/tanh"] = new MathOneOperandFloatSpec(), + ["math/transform"] = new MathTransformSpec(), + ["math/transpose"] = new MathOneOperandSpec(), + ["math/trunc"] = new MathOneOperandFloatSpec(), + ["math/xor"] = new MathTwoOperandsSpec(), + ["variable/get"] = new VariableGetSpec(), + ["variable/set"] = new VariableSetSpec(), + ["variable/interpolate"] = new VariableInterpolateSpec(), + ["variable/setMultiple"] = new VariableSetMultipleSpec(), + }; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs.meta new file mode 100644 index 000000000..9c2265f05 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9850a9179507ca24e9c2d05d4298b7f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs.meta new file mode 100644 index 000000000..63f300192 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 69d64abb4477b0d44ac9fe88f3dccda6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation.meta new file mode 100644 index 000000000..6164cd61d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e1a67c297e2a02d4a9ea2e520f22d84f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs new file mode 100644 index 000000000..0d13c5956 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs @@ -0,0 +1,39 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class AnimationStartSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.ANIMATION, "Animation index.", new Type[] { typeof(int) }), + new NodeValue(ConstStrings.START_TIME, "Start time in seconds.", new Type[] { typeof(float) }), + new NodeValue(ConstStrings.END_TIME, "End time in seconds.", new Type[] { typeof(float) }), + new NodeValue(ConstStrings.SPEED, "Speed multiplier.", new Type[] { typeof(float) }), + }; + + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.DONE, "The flow to be activated after the animation ends."), + new NodeFlow(ConstStrings.OUT, "The flow to trigger immediately after execution."), + new NodeFlow(ConstStrings.ERR, "The flow to trigger in case of an error.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs.meta new file mode 100644 index 000000000..7235ed461 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Start.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 773cc1fdac9a06f4eb590330fdbb68b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs new file mode 100644 index 000000000..76b7fbb96 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs @@ -0,0 +1,35 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class AnimationStopSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.ANIMATION, "Animation index.", new Type[] { typeof(int) }), + }; + + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger immediately after execution."), + new NodeFlow(ConstStrings.ERR, "The flow to trigger in case of an error.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs.meta new file mode 100644 index 000000000..e5d786d64 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/Stop.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2633daa661bd89f4292999724fad0a48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs new file mode 100644 index 000000000..ecbb400d8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs @@ -0,0 +1,37 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class AnimationStopAtSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.ANIMATION, "Animation index.", new Type[] { typeof(int) }), + new NodeValue(ConstStrings.STOP_TIME, "Stop time in seconds.", new Type[] { typeof(float) }), + }; + + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.DONE, "The flow to be activated after the animation stops."), + new NodeFlow(ConstStrings.OUT, "The flow to trigger immediately after execution."), + new NodeFlow(ConstStrings.ERR, "The flow to trigger in case of an error.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs.meta new file mode 100644 index 000000000..362ecb0d8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Animation/StopAt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e5feac405181b048b562b98d6f02a84 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging.meta new file mode 100644 index 000000000..ea21a4d0e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 93f42ab30bbe14d35a4ca84302bdba1e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs new file mode 100644 index 000000000..1133dd93a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs @@ -0,0 +1,20 @@ +using System; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class DebugAssertSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Condition", new Type[] { typeof(bool) }), + new NodeValue(ConstStrings.B, "Parameter 1", new Type[] { typeof(bool), typeof(int), typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }), + new NodeValue(ConstStrings.C, "Parameter 2", new Type[] { typeof(bool), typeof(int), typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }) + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta new file mode 100644 index 000000000..3cdf0e5ee --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 264d8e380a9fa4eef90436c8702950ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs new file mode 100644 index 000000000..3859f664a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs @@ -0,0 +1,33 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class DebugLogSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.MESSAGE, "Message to log.", typeof(string)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + return (flows, null); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger after sending this event."), + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs.meta new file mode 100644 index 000000000..7d00cf87c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugLog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 324a4aac12d5c42efb379d0dac522adc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event.meta new file mode 100644 index 000000000..5db4d0812 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45d65359327c4e045baba7df496dd571 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs new file mode 100644 index 000000000..b954d5412 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs @@ -0,0 +1,15 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class EventOnStartSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger when the session starts.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs.meta new file mode 100644 index 000000000..e7294c900 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnStart.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a1ca830170fc0644bd098ab715499a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs new file mode 100644 index 000000000..c0c992020 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs @@ -0,0 +1,23 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventOnTickSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger after sending this event."), + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.TIME_SINCE_START, "Relative time in seconds since the graph execution start.", new Type[] { typeof(float) }), + new NodeValue(ConstStrings.TIME_SINCE_LAST_TICK, "Relative time in seconds since the last tick occurred.", new Type[] { typeof(float) }), + }; + + return (flows, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs.meta new file mode 100644 index 000000000..84f912371 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/OnTick.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5db165437ed2f34dbba0afdcbbfe10e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs new file mode 100644 index 000000000..563d95328 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs @@ -0,0 +1,23 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class EventReceiveSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.EVENT, "Event id to receive.", typeof(int)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger after sending this event."), + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs.meta new file mode 100644 index 000000000..6166321c8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Receive.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fef5d0bc5b638044b80220807e870af6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs new file mode 100644 index 000000000..9162d1212 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs @@ -0,0 +1,33 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class EventSendSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.EVENT, "Event id to send.", typeof(int)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + return (flows, null); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger after sending this event."), + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs.meta new file mode 100644 index 000000000..e01faf367 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Event/Send.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: faab9f94385cf064aa6b03162bb7ed84 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow.meta new file mode 100644 index 000000000..0496b76a5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6924d14cadbf04cd5a060ab30fd56e45 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs new file mode 100644 index 000000000..d39df18fb --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs @@ -0,0 +1,18 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowBranchSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.CONDITION, "Condition to evaluate.", new System.Type[] { typeof(bool) }) + }; + return (flows, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs.meta new file mode 100644 index 000000000..0995308ef --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Branch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 29f4fff6730af4c68b3fde85d6ec3522 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs new file mode 100644 index 000000000..e11b80f4c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs @@ -0,0 +1,27 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowCancelDelaySpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.DELAY_INDEX, "Index of the delay to cancel.", new System.Type[] { typeof(int) }), + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.OUT, "Activates immediately after the in flow.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs.meta new file mode 100644 index 000000000..4b47d7bac --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/CancelDelay.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2faa1bdc3c819a347b9252566d4198ed +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs new file mode 100644 index 000000000..c5ef741cf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs @@ -0,0 +1,32 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowDoNSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow."), + new NodeFlow(ConstStrings.RESET, "Resets the counter.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.N, "Number of times to repeat the out flow.", new System.Type[] { typeof(int) }), + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.OUT, "Activates immediately after the in flow.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.CURRENT_COUNT, "Current iteration index.", new System.Type[] { typeof(int) }), + }; + + return (flows, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs.meta new file mode 100644 index 000000000..4ff3ba375 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/DoN.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62034f6dc3a35c344ac2dff6fa230a51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs new file mode 100644 index 000000000..72b3ea0d1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs @@ -0,0 +1,39 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowForSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[]{ + new NodeConfiguration(ConstStrings.INITIAL_INDEX, "Initial index.", typeof(int)) + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.START_INDEX, "Index to start at.", new System.Type[] { typeof(int) }), + new NodeValue(ConstStrings.END_INDEX, "Index to end at.", new System.Type[] { typeof(int) }) + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.LOOP_BODY, "The flow to repeat index number times."), + new NodeFlow(ConstStrings.COMPLETED, "The flow to execute once the end index has been reached.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.INDEX, "The current index", new System.Type[] { typeof(int) }) + }; + return (flows, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs.meta new file mode 100644 index 000000000..c2d65ddd0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/For.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de6b201e636b74b098498395962cddf9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs new file mode 100644 index 000000000..2b082ad73 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs @@ -0,0 +1,37 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowMultiGateSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.IS_RANDOM, "If set to true, output flows are activated in random order, picking a random not used output flow each time until all are done; false in the default configuration.", typeof(bool)), + new NodeConfiguration(ConstStrings.IS_LOOP, "If set to true, output flow activations will repeat in a loop continuously after all are done; false in the default configuration.", typeof(bool)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow."), + new NodeFlow(ConstStrings.RESET, "When this flow is activated, the lastIndex value is reset to -1 and all outputs are marked as not used."), + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.DURATION, "The time, in seconds, to wait after an output flow activation before allowing subsequent output flow activations.", new System.Type[] { typeof(float) }), + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[]{ + new NodeValue(ConstStrings.LAST_INDEX, "The index of the last used output; -1 if the node has not been activated.", new System.Type[] { typeof(int) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs.meta new file mode 100644 index 000000000..2a71c2a6d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/MultiGate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93681223be2add049bf8b6c746f9176b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs new file mode 100644 index 000000000..c876330ba --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs @@ -0,0 +1,13 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowSequenceSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs.meta new file mode 100644 index 000000000..d658b5c5a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Sequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d8bb3b5e8503410b95a7cd7b5cd7aa1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs new file mode 100644 index 000000000..6b74c6e9d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs @@ -0,0 +1,33 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowSetDelaySpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow."), + new NodeFlow(ConstStrings.CANCEL, "Cancels all delays from this node.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.DURATION, "Duration of this delay.", new System.Type[] { typeof(float) }), + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.OUT, "Activates immediately after the in flow."), + new NodeFlow(ConstStrings.ERR, "Activates if the duration value is invalid."), + new NodeFlow(ConstStrings.DONE, "Activates after the delay is complete.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.LAST_DELAY_INDEX, "Last unique delay index from this node", new System.Type[] { typeof(int) }) + }; + return (flows, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs.meta new file mode 100644 index 000000000..165175c4c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/SetDelay.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e9a129532ac8a444b824b1cde4402d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs new file mode 100644 index 000000000..f3c55e437 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs @@ -0,0 +1,34 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowSwitchSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[]{ + new NodeConfiguration(ConstStrings.CASES, "The cases on which to perform the switch; empty in the default configuration", typeof(int[])) + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow."), + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.SELECTION, "Which output flow to trigger.", new System.Type[] { typeof(int) }), + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.DEFAULT, "The output flow activated when the selection input value is not present in the cases configuration array"), + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs.meta new file mode 100644 index 000000000..9914ca9e7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Switch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 17983cf4dc7019b48bd9b5fdaedf4d27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs new file mode 100644 index 000000000..06245dbf6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs @@ -0,0 +1,34 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowThrottleSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow."), + new NodeFlow(ConstStrings.RESET, "When this flow is activated, the output flow throttling state is reset."), + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.DURATION, "The time, in seconds, to wait after an output flow activation before allowing subsequent output flow activations.", new System.Type[] { typeof(float) }), + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.OUT, "The flow to be activated if the output flow is not currently throttled"), + new NodeFlow(ConstStrings.ERR, "The flow to be activated if the duration input value is negative, infinite, or NaN"), + + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.LAST_REMAINING_TIME, "The remaining throttling time, in seconds, at the moment of the last valid activation of the input flow or NaN if the input flow has never been activated with a valid duration input value.", new System.Type[] { typeof(float) }), + }; + + return (flows, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs.meta new file mode 100644 index 000000000..48ddd57ce --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/Throttle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f25e87c38838ac429135c179a7b1a94 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs new file mode 100644 index 000000000..6728ee241 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs @@ -0,0 +1,37 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowWaitAllSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.INPUT_FLOWS, "The number of input flows; zero in the default configuration.", typeof(int)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.RESET, "When this flow is activated, all input flows are marked as unused."), + }; + + return (flows, null); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.OUT, "The flow to be activated after every input flow activation except the last missing input."), + new NodeFlow(ConstStrings.COMPLETED, "The flow to be activated when the last missing input flow is activated."), + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.REMAINING_INPUTS, "The number of not yet activated input flows.", new System.Type[] { typeof(int) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs.meta new file mode 100644 index 000000000..b3ea88bf7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/WaitAll.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 955a57a6501eb454ab47f21103dd9999 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs new file mode 100644 index 000000000..e6a064317 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs @@ -0,0 +1,28 @@ + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowWhileSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[]{ + new NodeValue(ConstStrings.CONDITION, "Loop condition.", new System.Type[] { typeof(bool) }), + }; + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.LOOP_BODY, "The flow to repeat index number times."), + new NodeFlow(ConstStrings.COMPLETED, "The flow to execute once the end index has been reached.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs.meta new file mode 100644 index 000000000..7d92273bc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Flow/While.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7426b53aeb2662a4db04bd5a1bec4624 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math.meta new file mode 100644 index 000000000..755ca4759 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6012bd9441c5bd24fa0563aecc31e85c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs new file mode 100644 index 000000000..029eaee32 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathAbsSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Argument.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "If a > 0 then -a, else a.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs.meta new file mode 100644 index 000000000..4ba4f61f0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Abs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f638fc7325877f6468665de09067e923 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs new file mode 100644 index 000000000..6918236c9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs @@ -0,0 +1,30 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathAddSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Argument.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }), + new NodeValue(ConstStrings.B, "Argument.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Add A with B.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs.meta new file mode 100644 index 000000000..8127830de --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Add.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cffe3cd13990d4a0e9d2f5b561cf76ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs new file mode 100644 index 000000000..565925e31 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs @@ -0,0 +1,31 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathConstantSpec : NodeSpecifications + { + protected string _resultDescription; + + public MathConstantSpec(string resultDescription = "Result") + { + _resultDescription = resultDescription; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T) }), + }; + + return (null, values); + } + } + + public class MathConstantSpec : MathConstantSpec + { + + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs.meta new file mode 100644 index 000000000..4485bfbff --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Constant.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6279cc76d2d8c41d58bab582c1929636 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs new file mode 100644 index 000000000..37047a3e5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs @@ -0,0 +1,62 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExtractSpec : NodeSpecifications + { + private int _numOutputs; + public MathExtractSpec() + { + if(typeof(T) == typeof(float2)) + { + _numOutputs = 2; + } + else if(typeof(T) == typeof(float3)) + { + _numOutputs = 3; + } + else if(typeof(T) == typeof(float4)) + { + _numOutputs = 4; + } + else if(typeof(T) == typeof(float2x2)) + { + _numOutputs = 4; + } + else if(typeof(T) == typeof(float3x3)) + { + _numOutputs = 9; + } + else if(typeof(T) == typeof(float4x4)) + { + _numOutputs = 16; + } + else + { + throw new InvalidOperationException($"Invalid type {typeof(T)} used!"); + } + } + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Input", new Type[] { typeof(T) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[_numOutputs]; + for(int i = 0; i < _numOutputs; i++) + { + values[i] = new NodeValue(i.ToString(), "Value", new Type[] { typeof(float) }); + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs.meta new file mode 100644 index 000000000..4e40a556a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Extract.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3443d444ae4741c7ad89f5d311259b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs new file mode 100644 index 000000000..a2958298d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs @@ -0,0 +1,31 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMatComposeSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.TRANSLATION, "Translation", new Type[] { typeof(float3) }), + new NodeValue(ConstStrings.ROTATION, "Rotation", new Type[] { typeof(float4) }), + new NodeValue(ConstStrings.SCALE, "Scale", new Type[] { typeof(float3) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Matrix", new Type[] { typeof(float4x4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs.meta new file mode 100644 index 000000000..5c482b958 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatCompose.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 644191f1f77114f3692a7f444353e71b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs new file mode 100644 index 000000000..0e063d654 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs @@ -0,0 +1,31 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMatDecomposeSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Matrix", new Type[] { typeof(float4x4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.TRANSLATION, "Translation", new Type[] { typeof(float3) }), + new NodeValue(ConstStrings.ROTATION, "Rotation", new Type[] { typeof(float4) }), + new NodeValue(ConstStrings.SCALE, "Scale", new Type[] { typeof(float3) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs.meta new file mode 100644 index 000000000..e484efe8c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b86790c857024d0782a89624acc81e1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs new file mode 100644 index 000000000..47c0f1a1d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs @@ -0,0 +1,257 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public abstract class MathOneOperandSpecBase : NodeSpecifications + { + protected string _valDescription; + protected string _argDescription; + + public MathOneOperandSpecBase(string valDescription = "Value", string argDescription = "Argument.") + { + _valDescription = valDescription; + _argDescription = argDescription; + } + } + + public class MathOneOperandSpec : MathOneOperandSpecBase + { + public MathOneOperandSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }), + }; + + return (null, values); + } + } + + public class MathOneOperandSpec : MathOneOperandSpecBase + { + public MathOneOperandSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3)}), + }; + + return (null, values); + } + } + + public class MathOneOperandSpec : MathOneOperandSpecBase + { + public MathOneOperandSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T), typeof(T1), typeof(T2)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T), typeof(T1), typeof(T2)}), + }; + + return (null, values); + } + } + + public class MathOneOperandSpec : MathOneOperandSpecBase + { + public MathOneOperandSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T) }), + }; + + return (null, values); + } + } + + public class MathOneOperandSpec : MathOneOperandSpecBase + { + public MathOneOperandSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T), typeof(T1) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T), typeof(T1) }), + }; + + return (null, values); + } + } + + public class MathOneOperandRetSpec : MathOneOperandSpecBase + { + private int _numInputs; + + public MathOneOperandRetSpec(int numInputs = 1, string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) + { + _numInputs = numInputs; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[_numInputs]; + for(int i = 0; i < _numInputs; i++) + { + values[i] = new NodeValue(ConstStrings.Letters[i], _argDescription, new Type[] { typeof(T) }); + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T1)}), + }; + + return (null, values); + } + } + + public class MathOneOperandRetSpec : MathOneOperandSpecBase + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T), typeof(T1) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T2)}), + }; + + return (null, values); + } + } + + public class MathOneOperandRetSpec : MathOneOperandSpecBase + { + public MathOneOperandRetSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T), typeof(T1), typeof(T2) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T3)}), + }; + + return (null, values); + } + } + + public class MathOneOperandRetSpec : MathOneOperandSpecBase + { + public MathOneOperandRetSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _argDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _valDescription, new Type[] { typeof(T4)}), + }; + + return (null, values); + } + } + + public class MathOneOperandSpec : MathOneOperandSpec + { + public MathOneOperandSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + } + + public class MathOneOperandFloatSpec : MathOneOperandSpec + { + public MathOneOperandFloatSpec(string valDescription = "Value", string argDescription = "Argument.") : base(valDescription, argDescription) {} + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs.meta new file mode 100644 index 000000000..581c971fe --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/OneOperand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48963f72d12b14dc1ae610ccb0e45adc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs new file mode 100644 index 000000000..8abc28a6e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRandomSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + return (flows, null); + } + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Value", new Type[] { typeof(float) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs.meta new file mode 100644 index 000000000..a363efc95 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Random.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3025169b7ad9b4dc1b1ef952ee9b4b78 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs new file mode 100644 index 000000000..dfe5b14b3 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs @@ -0,0 +1,31 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRotate3DSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Vector", new Type[] { typeof(float3)}), + new NodeValue(ConstStrings.B, "Axis", new Type[] { typeof(float3)}), + new NodeValue(ConstStrings.C, "Angle", new Type[] { typeof(float)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Value", new Type[] { typeof(float3) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs.meta new file mode 100644 index 000000000..c688496e3 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a5b5f86e015940ca9e5f38cecf898fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs new file mode 100644 index 000000000..b1c82f5cb --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs @@ -0,0 +1,31 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSelectSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Argument.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4), typeof(float2x2), typeof(float3x3), typeof(float4) }), // maybe add more types? + new NodeValue(ConstStrings.B, "Argument.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4), typeof(float2x2), typeof(float3x3), typeof(float4) }), + new NodeValue(ConstStrings.CONDITION, "Condition.", new Type[] { typeof(bool) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Select A or B by Condition.", new Type[] { typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4), typeof(float2x2), typeof(float3x3), typeof(float4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs.meta new file mode 100644 index 000000000..faa900095 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Select.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2365166703044c108202fc1f023e0b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs new file mode 100644 index 000000000..c49d3cd98 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs @@ -0,0 +1,39 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSwitchSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.SELECTION, "Selection", new Type[] { typeof(int) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Value", new Type[] { typeof(float) }), // other types? + }; + + return (flows, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs.meta new file mode 100644 index 000000000..4b57cd3e9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Switch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2468f9db3a1574bd9987beb6051361a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs new file mode 100644 index 000000000..aab5cba29 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs @@ -0,0 +1,49 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public abstract class MathThreeOperandsSpecBase : NodeSpecifications + { + protected string _resultDescription; + protected string _op1Description; + protected string _op2Description; + protected string _op3Description; + + public MathThreeOperandsSpecBase(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2", string op3Description = "Operand 3") + { + _op1Description = op1Description; + _op2Description = op2Description; + _op3Description = op3Description; + _resultDescription = resultDescription; + } + } + + public class MathThreeOperandsSpec : MathThreeOperandsSpecBase + { + public MathThreeOperandsSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3) }), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3) }), + new NodeValue(ConstStrings.C, _op3Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3)}), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs.meta new file mode 100644 index 000000000..ad3085cc9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/ThreeOperands.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e282e0de288194e1185996f92e34965c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs new file mode 100644 index 000000000..fe58cb1d0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs @@ -0,0 +1,30 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathTransformSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Vector", new Type[] { typeof(float2), typeof(float3), typeof(float4)}), + new NodeValue(ConstStrings.B, "Matrix", new Type[] { typeof(float2x2), typeof(float3x3), typeof(float4x4)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Value", new Type[] { typeof(float2), typeof(float3), typeof(float4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs.meta new file mode 100644 index 000000000..b18a18361 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Transform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dabffdef2995c4a8d817e11e6edf0066 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs new file mode 100644 index 000000000..f4dfe3163 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs @@ -0,0 +1,258 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public abstract class MathTwoOperandsSpecBase : NodeSpecifications + { + protected string _resultDescription; + protected string _op1Description; + protected string _op2Description; + + + public MathTwoOperandsSpecBase(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") + { + _op1Description = op1Description; + _op2Description = op2Description; + _resultDescription = resultDescription; + } + } + + public class MathTwoOperandsSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3) }), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3)}), + }; + + return (null, values); + } + } + + public class MathTwoOperandsSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }), + }; + + return (null, values); + } + } + + public class MathTwoOperandsSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }), + }; + + return (null, values); + } + } + + public class MathTwoOperandsRetSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsRetSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(TRes) }), + }; + + return (null, values); + } + } + + public class MathTwoOperandsSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T)}), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T) }), + }; + + return (null, values); + } + } + + public class MathTwoOperandsSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1)}), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T), typeof(T1) }), + }; + + return (null, values); + } + } + + public class MathTwoOperandsSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1), typeof(T2)}), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1), typeof(T2)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T), typeof(T1), typeof(T2)}), + }; + + return (null, values); + } + } + + public class MathTwoOperandsRetSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsRetSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1)}), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T2) }), + }; + + return (null, values); + } + } + + public class MathTwoOperandsRetSpec : MathTwoOperandsSpecBase + { + public MathTwoOperandsRetSpec(string resultDescription = "Result", string op1Description = "Operand 1", string op2Description = "Operand 2") : base(resultDescription, op1Description, op2Description) {} + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, _op1Description, new Type[] { typeof(T), typeof(T1), typeof(T2)}), + new NodeValue(ConstStrings.B, _op2Description, new Type[] { typeof(T), typeof(T1), typeof(T2)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, _resultDescription, new Type[] { typeof(T3) }), + }; + + return (null, values); + } + } + + public class MathTwoOperandsSpec : MathTwoOperandsSpec + { + } + + public class MathTwoOperandsFloatSpec : MathTwoOperandsSpec + { + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs.meta new file mode 100644 index 000000000..1aa9c5491 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/TwoOperands.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66b6f3ccebf724bdab854aab902f635e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs new file mode 100644 index 000000000..013ee58ff --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs @@ -0,0 +1,97 @@ +using System; +using UnityEngine.UIElements; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct NodeFlow + { + public string id; + public string description; + + public NodeFlow(string id, string description) + { + this.id = id; + this.description = description; + } + } + + public struct NodeValue + { + public string id; + public string description; + public Type[] types; + + public NodeValue(string id, string description, Type[] types) + { + this.id = id; + this.description = description; + this.types = types; + } + } + + public struct NodeConfiguration + { + public string id; + public string description; + public Type type; + + public NodeConfiguration(string id, string description, Type type) + { + this.id = id; + this.description = description; + this.type = type; + } + } + + public abstract class NodeSpecifications + { + private NodeFlow[] _inputFlows; + private NodeValue[] _inputValues; + private NodeFlow[] _outputFlows; + private NodeValue[] _outputValues; + private NodeConfiguration[] _configuration; + + private bool _inputsGenerated; + private bool _outputsGenerated; + private bool _configurationGenerated; + + public (NodeFlow[] flows, NodeValue[] values) GetInputs() + { + if(_inputsGenerated) + return (_inputFlows, _inputValues); + + return GenerateInputs(); + } + + public (NodeFlow[] flows, NodeValue[] values) GetOutputs() + { + if (_outputsGenerated) + return (_outputFlows, _outputValues); + + return GenerateOutputs(); + } + + public NodeConfiguration[] GetConfiguration() + { + if (_configurationGenerated) + return _configuration; + + return GenerateConfiguration(); + } + + protected virtual (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + return (null, null); + } + + protected virtual (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + return (null, null); + } + + protected virtual NodeConfiguration[] GenerateConfiguration() + { + return null; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs.meta new file mode 100644 index 000000000..9c23a942d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/NodeSpecifications.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed6e83ce4ab4f4747ba34bd53aeb339f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer.meta new file mode 100644 index 000000000..bd6f9a541 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 22db9b634ecc3d447a72be6bfc867cd1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs new file mode 100644 index 000000000..7dc7da460 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class PointerGetSpecs : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.POINTER, "JSON Pointer to get.", typeof(string)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Returned value.", new Type[] { typeof(int), typeof(float), typeof(float2), typeof(float3), typeof(float4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs.meta new file mode 100644 index 000000000..706ce4440 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Get.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56223025babc146c38a0a6e0c2517473 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs new file mode 100644 index 000000000..60f4cf9fc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs @@ -0,0 +1,47 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class PointerInterpolateSpecs : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.POINTER, "JSON Pointer to interpolate.", typeof(string)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.DURATION, "Interpolation duration.", new Type[] { typeof(float) }), + new NodeValue(ConstStrings.VALUE, "Target value to interpolate to.", new Type[] { typeof(int), typeof(float), typeof(float2), typeof(float3), typeof(float4) }), + new NodeValue(ConstStrings.P1, "Control point 1.", new Type[] { typeof(float2) }), + new NodeValue(ConstStrings.P2, "Control point 2.", new Type[] { typeof(float2) }), + }; + + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.DONE, "The flow to trigger after interpolation finishes."), + new NodeFlow(ConstStrings.OUT, "The flow to trigger immediately after execution."), + new NodeFlow(ConstStrings.ERR, "The flow to trigger in case of an error.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs.meta new file mode 100644 index 000000000..f87f5b74b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Interpolate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9545b1494408294f87bc84e8613c9d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs new file mode 100644 index 000000000..2afffd1e9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs @@ -0,0 +1,44 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class PointerSetSpecs : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.POINTER, "JSON Pointer to set.", typeof(string)), + new NodeConfiguration(ConstStrings.TYPE, "JSON Pointer to set.", typeof(int)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Value to set.", new Type[] { typeof(int), typeof(float), typeof(float2), typeof(float3), typeof(float4) }), + }; + + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger immediately after execution."), + new NodeFlow(ConstStrings.ERR, "The flow to trigger in case of an error.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs.meta new file mode 100644 index 000000000..03024a057 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Pointer/Set.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8195bbb49f55c6a41acc7fd846252e9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable.meta new file mode 100644 index 000000000..19bc00301 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 39e53d4f9f8ef044e9b2f0381e01821b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs new file mode 100644 index 000000000..5aa002b94 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableGetSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.VARIABLE, "Variable to get.", typeof(string)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Returned value.", new Type[] { typeof(int), typeof(float), typeof(float2), typeof(float3), typeof(float4), typeof(float2x2), typeof(float3x3), typeof(float4x4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs.meta new file mode 100644 index 000000000..e0e581065 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Get.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 215d94a0854f0ea4ebcc46d286480f02 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs new file mode 100644 index 000000000..d28a891b9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs @@ -0,0 +1,47 @@ +using System; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableInterpolateSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.VARIABLE, "Variable to set.", typeof(int)), + new NodeConfiguration(ConstStrings.USE_SLERP, "Whether to use spherical interpolation for quaternions.", typeof(bool)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Value to set.", new Type[] { typeof(int), typeof(float), typeof(float2), typeof(float3), typeof(float4), typeof(float2x2), typeof(float3x3), typeof(float4x4)}), + new NodeValue(ConstStrings.DURATION, "The time, in seconds, in which the variable SHOULD reach the target value.", new Type[] { typeof(float) }), + new NodeValue(ConstStrings.P1, "Control point P1", new Type[] { typeof(float2)}), + new NodeValue(ConstStrings.P2, "Control point P2.", new Type[] { typeof(float2)}), + + }; + + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[]{ + new NodeFlow(ConstStrings.OUT, "Activates immediately after the in flow."), + new NodeFlow(ConstStrings.ERR, "Activates if the duration value is invalid."), + new NodeFlow(ConstStrings.DONE, "Activates after the delay is complete.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs.meta new file mode 100644 index 000000000..dc71b5674 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Interpolate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0f96919201869214b9ebd7c34fd35391 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs new file mode 100644 index 000000000..4314a3f4f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs @@ -0,0 +1,42 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableSetSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.VARIABLE, "Variable to set.", typeof(int)), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Value to set.", new Type[] { typeof(int), typeof(float), typeof(float2), typeof(float3), typeof(float4), typeof(float2x2), typeof(float3x3), typeof(float4x4)}), + }; + + return (flows, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger immediately after execution.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs.meta new file mode 100644 index 000000000..d2773b9fc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/Set.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd2ce9499f48f604bab27ce1cd80ae17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs new file mode 100644 index 000000000..ccb77357e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs @@ -0,0 +1,37 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableSetMultipleSpec : NodeSpecifications + { + protected override NodeConfiguration[] GenerateConfiguration() + { + return new NodeConfiguration[] + { + new NodeConfiguration(ConstStrings.VARIABLES, "Variables to set.", typeof(int[])), + }; + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.IN, "The in flow.") + }; + + return (flows, null); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var flows = new NodeFlow[] + { + new NodeFlow(ConstStrings.OUT, "The flow to trigger immediately after execution.") + }; + + return (flows, null); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs.meta new file mode 100644 index 000000000..db972997c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Variable/SetMultiple.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c73574cafe32a44d8aebdb650debef8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes.meta b/Runtime/Scripts/Interactivity/Playback/Nodes.meta new file mode 100644 index 000000000..06a272422 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eeed333bb1efac040bab05411378ec70 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Animation.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation.meta new file mode 100644 index 000000000..e0f8759c2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 55014742e8190ce4e83f77a5dc0adcf4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs new file mode 100644 index 000000000..599e7a392 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs @@ -0,0 +1,83 @@ +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class AnimationStart : BehaviourEngineNode + { + private int _animationIndex; + private float _speed; + private float _startTime; + private float _endTime; + + public AnimationStart(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (validationResult != ValidationResult.Valid) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + Util.Log($"Playing animation index {_animationIndex} with speed {_speed} and start/end times of {_startTime}/{_endTime}"); + + var data = new AnimationPlayData() + { + index = _animationIndex, + startTime = _startTime, + endTime = _endTime, + stopTime = _endTime, + speed = _speed, + unityStartTime = Time.time, + endDone = () => TryExecuteFlow(ConstStrings.DONE) + }; + + engine.PlayAnimation(data); + + TryExecuteFlow(ConstStrings.OUT); + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.ANIMATION, out _animationIndex) && + TryEvaluateValue(ConstStrings.SPEED, out _speed) && + TryEvaluateValue(ConstStrings.START_TIME, out _startTime) && + TryEvaluateValue(ConstStrings.END_TIME, out _endTime) && + ValidateAnimationIndex(_animationIndex) && + ValidateStartAndEndTimes(_startTime, _endTime) && + ValidateSpeed(_speed); + } + + private static bool ValidateSpeed(float speed) + { + return speed > 0 && !float.IsNaN(speed) && !float.IsInfinity(speed); + } + + private bool ValidateAnimationIndex(int animationIndex) + { + if (!TryGetReadOnlyPointer($"/{Pointers.ANIMATIONS_LENGTH}", out ReadOnlyPointer animPointer)) + return false; + + var animationCount = animPointer.GetValue(); + + if (animationIndex < 0 || animationIndex >= animationCount) + return false; + + return true; + } + + private static bool ValidateStartAndEndTimes(float startTime, float endTime) + { + if (float.IsNaN(startTime) || float.IsNaN(endTime)) + return false; + + if (float.IsInfinity(startTime)) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs.meta new file mode 100644 index 000000000..af301ea83 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Start.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a023f508eaa3a8f4083eb8badfd14f9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs new file mode 100644 index 000000000..cb62dc414 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs @@ -0,0 +1,48 @@ +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class AnimationStop : BehaviourEngineNode + { + private int _animationIndex; + + public AnimationStop(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (validationResult != ValidationResult.Valid) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + Util.Log($"Stopping animation index {_animationIndex}."); + + engine.StopAnimation(_animationIndex); + + TryExecuteFlow(ConstStrings.OUT); + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.ANIMATION, out _animationIndex) && + ValidateAnimationIndex(_animationIndex); + } + + private bool ValidateAnimationIndex(int animationIndex) + { + if (!TryGetReadOnlyPointer($"/{Pointers.ANIMATIONS_LENGTH}", out ReadOnlyPointer animPointer)) + return false; + + var animationCount = animPointer.GetValue(); + + if (animationIndex < 0 || animationIndex >= animationCount) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs.meta new file mode 100644 index 000000000..33c7df9af --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/Stop.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfe1655eca140704fa77358b26959887 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs new file mode 100644 index 000000000..66ccb1738 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs @@ -0,0 +1,56 @@ +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class AnimationStopAt : BehaviourEngineNode + { + private int _animationIndex; + private float _stopTime; + + public AnimationStopAt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (validationResult != ValidationResult.Valid) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + Util.Log($"Stopping animation index {_animationIndex} at {_stopTime}."); + + engine.StopAnimationAt(_animationIndex, _stopTime, () => TryExecuteFlow(ConstStrings.DONE)); + + TryExecuteFlow(ConstStrings.OUT); + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.ANIMATION, out _animationIndex) && + TryEvaluateValue(ConstStrings.STOP_TIME, out _stopTime) && + ValidateAnimationIndex(_animationIndex) && + ValidateStopTime(_stopTime); + } + + private bool ValidateStopTime(float stopTime) + { + return !float.IsNaN(stopTime); + } + + private bool ValidateAnimationIndex(int animationIndex) + { + if (!TryGetReadOnlyPointer($"/{Pointers.ANIMATIONS_LENGTH}", out ReadOnlyPointer animPointer)) + return false; + + var animationCount = animPointer.GetValue(); + + if (animationIndex < 0 || animationIndex >= animationCount) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs.meta new file mode 100644 index 000000000..74b507835 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Animation/StopAt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bae7f9b34541b5e409549361eced19fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Debug.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Debug.meta new file mode 100644 index 000000000..8b882cb48 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Debug.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0def57d03d35eff4ca98cbef090f6ceb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event.meta new file mode 100644 index 000000000..62bc36a4d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e2309e2d674c26648ab5414f7491c14a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs new file mode 100644 index 000000000..e4488ac30 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs @@ -0,0 +1,72 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct RayArgs + { + public Ray ray; + public RaycastHit hit; + public int controllerIndex; + } + + public class EventOnHoverIn : BehaviourEngineNode + { + // TODO: Add this limitation from spec: + // A behavior graph MUST NOT contain two or more event/onHoverIn nodes with the same nodeIndex configuration value. + + // Default values grabbed from spec + private int _hoverNodeIndex = -1; + private int _controllerIndex = -1; + + private readonly Transform _parentNode = null; + + public EventOnHoverIn(BehaviourEngine engine, Node node) : base(engine, node) + { + engine.onHoverIn += OnHoverIn; + + if (!configuration.TryGetValue(ConstStrings.NODE_INDEX, out Configuration config)) + return; + + var parentIndex = Parser.ToInt(config.value); + + _parentNode = engine.pointerResolver.nodePointers[parentIndex].gameObject.transform; + } + + public override IProperty GetOutputValue(string id) + { + return id switch + { + ConstStrings.HOVER_NODE_INDEX => new Property(_hoverNodeIndex), + ConstStrings.CONTROLLER_INDEX => new Property(_controllerIndex), + _ => throw new InvalidOperationException($"Socket {id} is not valid for this node!"), + }; + } + + private void OnHoverIn(RayArgs args) + { + // TODO: Add support for stopPropagation once we understand what it actually does. + // I've read that part of the spec a handful of times and still am not sure. + var t = args.hit.transform; + var go = t.gameObject; + var nodeIndex = engine.pointerResolver.IndexOf(go); + + var shouldExecute = true; + + // If there's a parent node provided in the config we need to check if what we hit was a child of it (or that specific object itself) + if (_parentNode != null) + shouldExecute = t.IsChildOf(_parentNode); + + // Node was not a child of the nodeIndex from the config, so we shouldn't execute our flow or set any values. + if (!shouldExecute) + return; + + _hoverNodeIndex = nodeIndex; + _controllerIndex = args.controllerIndex; + + Util.Log($"OnHoverIn node {nodeIndex} corresponding to GO {go.name}", go); + + TryExecuteFlow(ConstStrings.OUT); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs.meta new file mode 100644 index 000000000..95b0e155e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: edf3c6d50c4df2649b5dc41882489e35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs new file mode 100644 index 000000000..aa59327ef --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs @@ -0,0 +1,65 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventOnHoverOut : BehaviourEngineNode + { + // TODO: Add this limitation from spec: + // A behavior graph MUST NOT contain two or more event/onHoverOut nodes with the same nodeIndex configuration value. + + // Default values grabbed from spec + private int _hoverNodeIndex = -1; + private int _controllerIndex = -1; + + private readonly Transform _parentNode = null; + + public EventOnHoverOut(BehaviourEngine engine, Node node) : base(engine, node) + { + engine.onHoverOut += OnHoverOut; + + if (!configuration.TryGetValue(ConstStrings.NODE_INDEX, out Configuration config)) + return; + + var parentIndex = Parser.ToInt(config.value); + + _parentNode = engine.pointerResolver.nodePointers[parentIndex].gameObject.transform; + } + + public override IProperty GetOutputValue(string id) + { + return id switch + { + ConstStrings.HOVER_NODE_INDEX => new Property(_hoverNodeIndex), + ConstStrings.CONTROLLER_INDEX => new Property(_controllerIndex), + _ => throw new InvalidOperationException($"Socket {id} is not valid for this node!"), + }; + } + + private void OnHoverOut(RayArgs args) + { + // TODO: Add support for stopPropagation once we understand what it actually does. + // I've read that part of the spec a handful of times and still am not sure. + var t = args.hit.transform; + var go = t.gameObject; + var nodeIndex = engine.pointerResolver.IndexOf(go); + + var shouldExecute = true; + + // If there's a parent node provided in the config we need to check if what we hit was a child of it (or that specific object itself) + if (_parentNode != null) + shouldExecute = t.IsChildOf(_parentNode); + + // Node was not a child of the nodeIndex from the config, so we shouldn't execute our flow or set any values. + if (!shouldExecute) + return; + + _hoverNodeIndex = nodeIndex; + _controllerIndex = args.controllerIndex; + + Util.Log($"OnHoverIn node {nodeIndex} corresponding to GO {go.name}", go); + + TryExecuteFlow(ConstStrings.OUT); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs.meta new file mode 100644 index 000000000..9f4ff6c99 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 59adcd722427bad4bb886995f3d74dda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs new file mode 100644 index 000000000..fb1f53e17 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs @@ -0,0 +1,72 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventOnSelect : BehaviourEngineNode + { + // TODO: Add this limitation from spec: + // A behavior graph MUST NOT contain two or more event/onSelect nodes with the same nodeIndex configuration value. + + // Default values grabbed from spec + private int _selectedNodeIndex = -1; + private float3 _selectionPoint = new float3(float.NaN, float.NaN, float.NaN); + private float3 _selectionRayOrigin = new float3(float.NaN, float.NaN, float.NaN); + private int _controllerIndex = -1; + + private Transform _parentNode = null; + + public EventOnSelect(BehaviourEngine engine, Node node) : base(engine, node) + { + engine.onSelect += OnSelect; + + if (!configuration.TryGetValue(ConstStrings.NODE_INDEX, out Configuration config)) + return; + + var parentIndex = Parser.ToInt(config.value); + + _parentNode = engine.pointerResolver.nodePointers[parentIndex].gameObject.transform; + } + + public override IProperty GetOutputValue(string id) + { + return id switch + { + ConstStrings.SELECTED_NODE_INDEX => new Property(_selectedNodeIndex), + ConstStrings.SELECTION_POINT => new Property(_selectionPoint), + ConstStrings.SELECTION_RAY_ORIGIN => new Property(_selectionRayOrigin), + ConstStrings.CONTROLLER_INDEX => new Property(_controllerIndex), + _ => throw new InvalidOperationException($"Socket {id} is not valid for this node!"), + }; + } + + private void OnSelect(RayArgs args) + { + // TODO: Add support for stopPropagation once we understand what it actually does. + // I've read that part of the spec a handful of times and still am not sure. + var t = args.hit.transform; + var go = t.gameObject; + var nodeIndex = engine.pointerResolver.IndexOf(go); + + var shouldExecute = true; + + // If there's a parent node provided in the config we need to check if what we hit was a child of it (or that specific object itself) + if (_parentNode != null) + shouldExecute = t.IsChildOf(_parentNode); + + // Node was not a child of the nodeIndex from the config, so we shouldn't execute our flow or set any values. + if (!shouldExecute) + return; + + _selectedNodeIndex = nodeIndex; + _selectionPoint = args.hit.point; + _selectionRayOrigin = args.ray.origin; + _controllerIndex = args.controllerIndex; + + Util.Log($"OnSelect node {nodeIndex} corresponding to GO {go.name}", go); + + TryExecuteFlow(ConstStrings.OUT); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs.meta new file mode 100644 index 000000000..7bff68673 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79d8e5a194777d54c9afee60767d4d0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs new file mode 100644 index 000000000..eb864e33b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs @@ -0,0 +1,15 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class EventOnStart : BehaviourEngineNode + { + public EventOnStart(BehaviourEngine engine, Node node) : base(engine, node) + { + engine.onStart += OnStart; + } + + private void OnStart() + { + TryExecuteFlow(ConstStrings.OUT); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs.meta new file mode 100644 index 000000000..619e26f8d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnStart.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84eaae6f12c801c4ea26c0a30ec574d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs new file mode 100644 index 000000000..314c101bf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs @@ -0,0 +1,46 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventOnTick : BehaviourEngineNode + { + private float _timeSinceStart = float.NaN; + private float _timeSinceLastTick = float.NaN; + private float _startTime = -9999f; + + private bool _hasTicked = false; + + public EventOnTick(BehaviourEngine engine, Node node) : base(engine, node) + { + engine.onTick += OnTick; + } + + private void OnTick() + { + if(!_hasTicked) + { + _startTime = Time.time; + _timeSinceStart = 0f; + _hasTicked = true; + } + else + { + _timeSinceStart = Time.time - _startTime; + _timeSinceLastTick = Time.deltaTime; + } + + TryExecuteFlow(ConstStrings.OUT); + } + + public override IProperty GetOutputValue(string id) + { + return id switch + { + ConstStrings.TIME_SINCE_START => new Property(_timeSinceStart), + ConstStrings.TIME_SINCE_LAST_TICK => new Property(_timeSinceLastTick), + _ => throw new InvalidOperationException($"No valid output with name {id}"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs.meta new file mode 100644 index 000000000..ebcb15303 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnTick.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 182925e460b1eed40b6ca9d0a9632a65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs new file mode 100644 index 000000000..e5304e88f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventReceive : BehaviourEngineNode + { + private Dictionary _outValues = new(); + + private int _eventToListenFor; + + public EventReceive(BehaviourEngine engine, Node node) : base(engine, node) + { + engine.onCustomEventFired += OnEventFired; + + // TODO: Putting things here is helpful for performance as we're caching them but does make runtime editing fail. + // Figure out if we want to support runtime editing or if we just want to rebuild the whole graph when the user plays back the interaction. + if (!TryGetConfig(ConstStrings.EVENT, out _eventToListenFor)) + throw new InvalidOperationException("No event provided in the config to listen for."); + + AddDefaultValues(); + } + + public override IProperty GetOutputValue(string socket) + { + if (!_outValues.TryGetValue(socket, out IProperty outValue)) + throw new ArgumentException($"No output value found for socket {socket}"); + + return outValue; + } + + private void OnEventFired(int eventIndex, Dictionary outValues) + { + if (eventIndex != _eventToListenFor) + return; + + Util.Log($"Received event {engine.graph.customEvents[eventIndex].id} with id {eventIndex}."); + _outValues = outValues; + + TryExecuteFlow(ConstStrings.OUT); + } + + private void AddDefaultValues() + { + var eventData = engine.graph.customEvents[_eventToListenFor]; + + if (eventData.values == null) + return; + + EventValue value; + + for (int i = 0; i < eventData.values.Count; i++) + { + value = eventData.values[i]; + + _outValues.Add(value.id, value.property); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs.meta new file mode 100644 index 000000000..1405f6ce7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Receive.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec2dd2ca11f47af4c97c9d430400466a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs new file mode 100644 index 000000000..0ed82c26b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class EventSend : BehaviourEngineNode + { + private int _eventNum; + private Dictionary _outValues; + + public EventSend(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + Util.Log($"Sending event index {_eventNum}"); + + engine.FireCustomEvent(_eventNum, _outValues); + + TryExecuteFlow(ConstStrings.OUT); + } + + public override bool ValidateConfiguration(string socket) + { + if (!TryGetConfig(ConstStrings.EVENT, out int eventNum)) + return false; + + _eventNum = eventNum; + + return true; + } + + public override bool ValidateValues(string socket) + { + var outValues = new Dictionary(); + + try + { + foreach (var v in values) + { + outValues.Add(v.Key, engine.ParseValue(v.Value)); + } + + _outValues = outValues; + return true; + } + catch (Exception e) + { + Debug.LogException(e); + return false; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs.meta new file mode 100644 index 000000000..cf3b0f16c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/Send.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57c20cb7af658fb47b9ecd679b230313 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow.meta new file mode 100644 index 000000000..3235c3282 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: add851669dc9e5946879991a35d70ce0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs new file mode 100644 index 000000000..e3a1072d9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowBranch : BehaviourEngineNode + { + private bool _condition; + + public FlowBranch(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (socket != ConstStrings.IN) + throw new ArgumentException($"Only valid input socket for this node is \"{ConstStrings.IN}\""); + + Util.Log($"Branch condition is {_condition}"); + + var outSocket = _condition ? ConstStrings.TRUE : ConstStrings.FALSE; + + TryExecuteFlow(outSocket); + } + + public override bool ValidateValues(string socket) + { + if (!TryEvaluateValue(ConstStrings.CONDITION, out bool condition)) + return false; + + _condition = condition; + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs.meta new file mode 100644 index 000000000..651d6fea5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Branch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 071dbabe5f400ac4aaabed6a1508c86e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs new file mode 100644 index 000000000..7f563db8e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs @@ -0,0 +1,18 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowCancelDelay : BehaviourEngineNode + { + public FlowCancelDelay(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + TryEvaluateValue(ConstStrings.DELAY_INDEX, out int index); + + engine.nodeDelayManager.CancelDelayByIndex(index); + + TryExecuteFlow(ConstStrings.OUT); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs.meta new file mode 100644 index 000000000..8b60bcaf5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/CancelDelay.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f5e1e177c949a4fe1a14447e0ec95e83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs new file mode 100644 index 000000000..017ba323c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs @@ -0,0 +1,44 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowDoN : BehaviourEngineNode + { + private int _nTimes; + private int _currentCount = 0; + + public FlowDoN(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + switch (socket) + { + case ConstStrings.RESET: + _currentCount = 0; + break; + case ConstStrings.IN: + if (_currentCount < _nTimes) + { + _currentCount++; + Util.Log($"Incrementing currentCount to {_currentCount} for an output that can be run {_nTimes} times"); + TryExecuteFlow(ConstStrings.OUT); + } + break; + default: + throw new InvalidOperationException($"Socket {socket} is not a valid input on this DoN node!"); + } + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.N, out _nTimes); + } + + public override IProperty GetOutputValue(string socket) + { + return new Property(_currentCount); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs.meta new file mode 100644 index 000000000..673b363c0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/DoN.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6a81a02e010449d6b3777bc3a69f006 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs new file mode 100644 index 000000000..0b558d3c5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowFor : BehaviourEngineNode + { + private int _startIndex; + private int _endIndex; + private int _index; + + public FlowFor(BehaviourEngine engine, Node node) : base(engine, node) + { + _index = Parser.ToInt(configuration[ConstStrings.INITIAL_INDEX].value); + } + + public override IProperty GetOutputValue(string socket) + { + if (socket == ConstStrings.INDEX) + return new Property(_index); + + throw new ArgumentException($"Socket {socket} is not valid on this node!"); + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + Util.Log($"Starting a loop with start index {_startIndex} and end index {_endIndex} from initial value {_index}"); + + _index = _startIndex; + + while(_index < _endIndex) + { + TryExecuteFlow(ConstStrings.LOOP_BODY); + _index++; + } + + TryExecuteFlow(ConstStrings.COMPLETED); + } + + public override bool ValidateValues(string socket) + { + if (!TryEvaluateValue(ConstStrings.START_INDEX, out _startIndex) || + !TryEvaluateValue(ConstStrings.END_INDEX, out _endIndex)) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs.meta new file mode 100644 index 000000000..305ae1f4a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40dfbadca9bc8134dbefcb75e7f3b22b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs new file mode 100644 index 000000000..ae2792158 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs @@ -0,0 +1,115 @@ +using System; +using System.Linq; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowMultiGate : BehaviourEngineNode + { + private readonly bool _isRandom = false; + private readonly bool _isLoop = false; + private int _lastIndex = -1; + + private readonly int[] _flowIndices; + private readonly Flow[] _orderedFlows; + private int _executionIndex; + + private const int MAX_OUTPUT_FLOWS = 64; + private static readonly Random _rng = new(); + + public FlowMultiGate(BehaviourEngine engine, Node node) : base(engine, node) + { + if (node.flows.Count > MAX_OUTPUT_FLOWS) + throw new InvalidOperationException($"MultiGate node has {node.flows.Count} output flows, the max is {MAX_OUTPUT_FLOWS}!"); + + // If this node has no output flows there's nothing to do at all so we don't need to worry about the rest. + if (node.flows.Count <= 0) + return; + + // node.flows only contains flows out of the node, so we don't have to filter out the input flows. + _orderedFlows = new Flow[node.flows.Count]; + _flowIndices = new int[node.flows.Count]; + + for (int i = 0; i < _orderedFlows.Length; i++) + { + _orderedFlows[i] = node.flows[i]; + } + + Array.Sort(_orderedFlows, (a, b) => a.CompareTo(b)); + + if (!TryGetConfig(ConstStrings.IS_LOOP, out _isLoop) || !TryGetConfig(ConstStrings.IS_RANDOM, out _isRandom)) + { + _isLoop = _isRandom = false; + } + + for (int i = 0; i < _flowIndices.Length; i++) + { + _flowIndices[i] = i; + } + + if (_isRandom) + { + _rng.Shuffle(_flowIndices); + } + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + // This node does absolutely nothing if there's no output flows. + if (_flowIndices.Length <= 0) + return; + + switch (socket) + { + // Reset indices and shuffle the flow order if isRandom. + case ConstStrings.RESET: + if (_isRandom) + _rng.Shuffle(_flowIndices); + + _lastIndex = -1; + _executionIndex = 0; + break; + case ConstStrings.IN: + var i = -1; + // If we haven't run out of cases, assign i to the next in the array. + if (_executionIndex < _flowIndices.Length) + { + i = _flowIndices[_executionIndex]; + } + + // We have no remaining cases. + if (i < 0) + { + // Are we looping? If so reset the execution index so we start from the beginning. + if (_isLoop) + { + // If we want randomness we also need to reshuffle the execution array here. + if (_isRandom) + _rng.Shuffle(_flowIndices); + + _executionIndex = 0; + i = _flowIndices[_executionIndex]; + } + // We ran out of cases and we're also not looping, so we do nothing. + else + return; + } + + // We arrive here if the loop reset the executionIndex or if we had remaining cases. + _lastIndex = i; + _executionIndex++; + // Using a string array cache to avoid allocating with i.ToString() + // If we support >64 cases we'll have to update this array or add a condition to use i.ToString above 63. + TryExecuteFlow(_orderedFlows[i].fromSocket); + break; + default: + throw new InvalidOperationException($"Socket {socket} is not a valid input on this MultiGate node!"); + } + } + + public override IProperty GetOutputValue(string socket) + { + return new Property(_lastIndex); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs.meta new file mode 100644 index 000000000..f7c8f9781 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/MultiGate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e33d3d123c9bc4d1db84c40a4af4a19a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs new file mode 100644 index 000000000..eb25ac697 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowSequence : BehaviourEngineNode + { + private readonly Flow[] _orderedFlows; + + public FlowSequence(BehaviourEngine engine, Node node) : base(engine, node) + { + _orderedFlows = new Flow[node.flows.Count]; + + for (int i = 0; i < _orderedFlows.Length; i++) + { + _orderedFlows[i] = node.flows[i]; + Util.Log($"{node.flows[i].fromSocket}"); + } + + Array.Sort(_orderedFlows, (a, b) => a.CompareTo(b)); + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + for (int i = 0; i < node.flows.Count; i++) + { + engine.ExecuteFlow(_orderedFlows[i]); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs.meta new file mode 100644 index 000000000..384bcd9e6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Sequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2991d680e885a074a92468dc0e4f8e6c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs new file mode 100644 index 000000000..eaa0eb3ea --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs @@ -0,0 +1,58 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowSetDelay : BehaviourEngineNode + { + private float _duration; + private int _lastDelayIndex = -1; + + public FlowSetDelay(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + switch (socket) + { + case ConstStrings.CANCEL: + engine.nodeDelayManager.CancelDelaysFromNode(this); + _lastDelayIndex = -1; + break; + + case ConstStrings.IN: + if (validationResult != ValidationResult.Valid) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + Util.Log($"Executing delay with duration of {_duration}s"); + _lastDelayIndex = engine.nodeDelayManager.AddDelayNode(this, _duration, () => TryExecuteFlow(ConstStrings.DONE)); + + TryExecuteFlow(ConstStrings.OUT); + break; + + default: + throw new InvalidOperationException($"Socket {socket} is not a valid input on this SetDelay node!"); + } + } + + public override bool ValidateValues(string socket) + { + if (!TryEvaluateValue(ConstStrings.DURATION, out _duration)) + return false; + + if (double.IsNaN(_duration) || double.IsInfinity(_duration) || _duration < 0) + return false; + + return true; + } + + public override IProperty GetOutputValue(string socket) + { + return new Property(_lastDelayIndex); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs.meta new file mode 100644 index 000000000..4d0988f93 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/SetDelay.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f9bf8b58e7fcf746b43f5bcdef67a3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs new file mode 100644 index 000000000..eb2508475 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowSwitch : BehaviourEngineNode + { + private Dictionary _cases; + private int _selection; + + public FlowSwitch(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (!_cases.TryGetValue(_selection, out Flow caseFlow)) + { + Util.Log($"Executing case {ConstStrings.DEFAULT}"); + TryExecuteFlow(ConstStrings.DEFAULT); + return; + } + + Util.Log($"Executing case {_selection}"); + engine.ExecuteFlow(caseFlow); + } + + public override bool ValidateValues(string socket) + { + if (!TryEvaluateValue(ConstStrings.SELECTION, out _selection)) + return false; + + return true; + } + + public override bool ValidateConfiguration(string socket) + { + try + { + if (_cases == null) + { + _cases = new(); + + for (int i = 0; i < node.flows.Count; i++) + { + if (node.flows[i].fromSocket.Equals(ConstStrings.DEFAULT)) + continue; + + _cases.Add(int.Parse(node.flows[i].fromSocket), node.flows[i]); + } + } + + return true; + } + catch (Exception e) + { + Debug.LogException(e); + return false; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs.meta new file mode 100644 index 000000000..8ca92fb21 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Switch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a85e5eb4b2af2ad4ab3a45c740038eb9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs new file mode 100644 index 000000000..1b510536a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs @@ -0,0 +1,61 @@ +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowThrottle : BehaviourEngineNode + { + private float _duration; + private float _timestamp; + private float _elapsed; + private float _lastRemainingTime = float.NaN; + + public FlowThrottle(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (socket.Equals(ConstStrings.RESET)) + { + _lastRemainingTime = float.NaN; + return; + } + + TryEvaluateValue(ConstStrings.DURATION, out _duration); + + if (_duration < 0 || float.IsNaN(_duration) || float.IsInfinity(_duration)) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + if (float.IsNaN(_lastRemainingTime)) + { + ExecuteOutFlow(); + return; + } + + _elapsed = Time.time - _timestamp; + + if (_duration <= _elapsed) + { + ExecuteOutFlow(); + return; + } + + _lastRemainingTime = _duration - _elapsed; + } + + private void ExecuteOutFlow() + { + _timestamp = Time.time; + _lastRemainingTime = 0; + TryExecuteFlow(ConstStrings.OUT); + } + + public override IProperty GetOutputValue(string socket) + { + return new Property(_lastRemainingTime); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs.meta new file mode 100644 index 000000000..2557c57dd --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/Throttle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74d9059ab8ba247c9b7ce90c154c5a43 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs new file mode 100644 index 000000000..934fad69e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs @@ -0,0 +1,53 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowWaitAll : BehaviourEngineNode + { + private readonly int _inputFlows; + private readonly bool[] _activated; + + private int _remainingInputs; + + public FlowWaitAll(BehaviourEngine engine, Node node) : base(engine, node) + { + if (!TryGetConfig(ConstStrings.INPUT_FLOWS, out _inputFlows)) + _inputFlows = 0; + + _remainingInputs = _inputFlows; + _activated = new bool[_inputFlows]; + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (socket.Equals(ConstStrings.RESET)) + { + _remainingInputs = _inputFlows; + ResetBooleanArray(_activated); + return; + } + + var index = int.Parse(socket); // Throws if an unexpected socket id is passed in that isn't in the spec. + + if(!_activated[index]) + _remainingInputs--; + _activated[index] = true; + + if (_remainingInputs == 0) + TryExecuteFlow(ConstStrings.COMPLETED); + else + TryExecuteFlow(ConstStrings.OUT); + } + + public override IProperty GetOutputValue(string socket) + { + return new Property(_remainingInputs); + } + + private static void ResetBooleanArray(bool[] arr) + { + for (int i = 0; i < arr.Length; i++) + { + arr[i] = false; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs.meta new file mode 100644 index 000000000..e203295a1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/WaitAll.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1866c71c380a342d0b1e7e901429086a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs new file mode 100644 index 000000000..c56accf52 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs @@ -0,0 +1,32 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class FlowWhile : BehaviourEngineNode + { + private bool _condition; + + public FlowWhile(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (socket != ConstStrings.IN) + throw new ArgumentException($"Only condition input socket for this node is \"{ConstStrings.IN}\""); + + while (_condition) + { + TryExecuteFlow(ConstStrings.LOOP_BODY); + TryEvaluateValue(ConstStrings.CONDITION, out _condition); + } + + TryExecuteFlow(ConstStrings.COMPLETED); + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.CONDITION, out _condition); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs.meta new file mode 100644 index 000000000..480e6cc28 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/While.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ff6830ce924e446eaaac849dde2b9d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math.meta new file mode 100644 index 000000000..42ddea46a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d0a5bb4e9e19b0348a880c4748243990 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs new file mode 100644 index 000000000..e35c49f20 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathACos : BehaviourEngineNode + { + public MathACos(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.acos(floatProp.value)), + Property float2Prop => new Property(math.acos(float2Prop.value)), + Property float3Prop => new Property(math.acos(float3Prop.value)), + Property float4Prop => new Property(math.acos(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs.meta new file mode 100644 index 000000000..4fc1b791e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACos.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c32d4f9b87994f44db6c3f24f516828b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs new file mode 100644 index 000000000..f05fcef33 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs @@ -0,0 +1,48 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathACosH : BehaviourEngineNode + { + public MathACosH(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(ACosH(floatProp.value)), + Property float2Prop => new Property(ACosH(float2Prop.value)), + Property float3Prop => new Property(ACosH(float3Prop.value)), + Property float4Prop => new Property(ACosH(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private float ACosH(float x) + { + // ln(x + sqrt(x^2 - 1)) + return math.log(x + math.sqrt(x * x - 1)); + } + + private float2 ACosH(float2 x) + { + return math.log(x + math.sqrt(x * x - 1)); + } + + private float3 ACosH(float3 x) + { + return math.log(x + math.sqrt(x * x - 1)); + } + + private float4 ACosH(float4 x) + { + return math.log(x + math.sqrt(x * x - 1)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs.meta new file mode 100644 index 000000000..2c20260e0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ACosH.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 431239fd12d13e243976792c97529aa1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs new file mode 100644 index 000000000..c34ef05a6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathASin : BehaviourEngineNode + { + public MathASin(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.asin(floatProp.value)), + Property float2Prop => new Property(math.asin(float2Prop.value)), + Property float3Prop => new Property(math.asin(float3Prop.value)), + Property float4Prop => new Property(math.asin(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs.meta new file mode 100644 index 000000000..5fa5702d4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bab01a4ed54a6744abd9a4ab2f8f929d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs new file mode 100644 index 000000000..950bb4457 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs @@ -0,0 +1,48 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathASinH : BehaviourEngineNode + { + public MathASinH(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(asinh(floatProp.value)), + Property float2Prop => new Property(asinh(float2Prop.value)), + Property float3Prop => new Property(asinh(float3Prop.value)), + Property float4Prop => new Property(asinh(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private float asinh(float x) + { + // ln(x + sqrt(x^2 + 1)) + return math.log(x + math.sqrt(x * x + 1)); + } + + private float2 asinh(float2 x) + { + return math.log(x + math.sqrt(x * x + 1)); + } + + private float3 asinh(float3 x) + { + return math.log(x + math.sqrt(x * x + 1)); + } + + private float4 asinh(float4 x) + { + return math.log(x + math.sqrt(x * x + 1)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs.meta new file mode 100644 index 000000000..bda4f6b4d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ASinH.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4aeb9f1bd882232469b7f3d86c4c543e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs new file mode 100644 index 000000000..870da463c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathATan : BehaviourEngineNode + { + public MathATan(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.atan(floatProp.value)), + Property float2Prop => new Property(math.atan(float2Prop.value)), + Property float3Prop => new Property(math.atan(float3Prop.value)), + Property float4Prop => new Property(math.atan(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs.meta new file mode 100644 index 000000000..652b3cf08 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54fd3c22519ca594a9a292d8b99c6111 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs new file mode 100644 index 000000000..a92e027c0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathATan2 : BehaviourEngineNode + { + public MathATan2(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(math.atan2(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.atan2(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.atan2(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.atan2(aProp.value, bProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs.meta new file mode 100644 index 000000000..c21d77e8c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATan2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a031d57fd387d4845a5d3c4f38088217 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs new file mode 100644 index 000000000..dd1b3e5cb --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs @@ -0,0 +1,48 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathATanH : BehaviourEngineNode + { + public MathATanH(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(ATanH(floatProp.value)), + Property float2Prop => new Property(ATanH(float2Prop.value)), + Property float3Prop => new Property(ATanH(float3Prop.value)), + Property float4Prop => new Property(ATanH(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private float ATanH(float x) + { + // 0.5 * ln((1+x)/(1-x)) + return 0.5f * math.log((1 + x) / (1 - x)); + } + + private float2 ATanH(float2 x) + { + return 0.5f * math.log((1 + x) / (1 - x)); + } + + private float3 ATanH(float3 x) + { + return 0.5f * math.log((1 + x) / (1 - x)); + } + + private float4 ATanH(float4 x) + { + return 0.5f * math.log((1 + x) / (1 - x)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs.meta new file mode 100644 index 000000000..a3e92ab15 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/ATanH.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f818713a867b21549ae0431891e6608a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs new file mode 100644 index 000000000..f9948f95c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathAbs : BehaviourEngineNode + { + public MathAbs(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property intProp => new Property(math.abs(intProp.value)), + Property floatProp => new Property(math.abs(floatProp.value)), + Property float2Prop => new Property(math.abs(float2Prop.value)), + Property float3Prop => new Property(math.abs(float3Prop.value)), + Property float4Prop => new Property(math.abs(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs.meta new file mode 100644 index 000000000..d695878ac --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Abs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 220ee8132fce96b4581c71aa94ac8e70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs new file mode 100644 index 000000000..5c798e076 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathAdd : BehaviourEngineNode + { + public MathAdd(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value + bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value + bFloat.value), + Property aVec2 when b is Property bVec2 => new Property(aVec2.value + bVec2.value), + Property aVec3 when b is Property bVec3 => new Property(aVec3.value + bVec3.value), + Property aVec4 when b is Property bVec4 => new Property(aVec4.value + bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs.meta new file mode 100644 index 000000000..6691c2932 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Add.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e2e6ebf8ac7e924a9e5e5d8ecffd9c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs new file mode 100644 index 000000000..b19766531 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs @@ -0,0 +1,25 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathAnd : BehaviourEngineNode + { + public MathAnd(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(aProp.value & bProp.value), + Property aProp when b is Property bProp => new Property(aProp.value && bProp.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs.meta new file mode 100644 index 000000000..1c13af920 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/And.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d24f952d663ea094b9e0ab65f4872a52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs new file mode 100644 index 000000000..92fd35dca --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs @@ -0,0 +1,25 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathAsr : BehaviourEngineNode + { + public MathAsr(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(aProp.value >> bProp.value), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs.meta new file mode 100644 index 000000000..3a8318980 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Asr.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49f9d0e93b23ce24ebaf81fe356ecd0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs new file mode 100644 index 000000000..a6e3eaca3 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs @@ -0,0 +1,43 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCbrt : BehaviourEngineNode + { + public MathCbrt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(MathF.Cbrt(floatProp.value)), + Property float2Prop => new Property(Cbrt(float2Prop.value)), + Property float3Prop => new Property(Cbrt(float3Prop.value)), + Property float4Prop => new Property(Cbrt(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private static float2 Cbrt(float2 v) + { + return new float2(MathF.Cbrt(v.x), MathF.Cbrt(v.y)); + } + + private static float3 Cbrt(float3 v) + { + return new float3(MathF.Cbrt(v.x), MathF.Cbrt(v.y), MathF.Cbrt(v.z)); + } + + private static float4 Cbrt(float4 v) + { + return new float4(MathF.Cbrt(v.x), MathF.Cbrt(v.y), MathF.Cbrt(v.z), MathF.Cbrt(v.w)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs.meta new file mode 100644 index 000000000..a606dbe95 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cbrt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2571b40d69a826843ac65aae32666f76 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs new file mode 100644 index 000000000..4d8650b58 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCeil : BehaviourEngineNode + { + public MathCeil(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.ceil(aProp.value)), + Property aProp => new Property(math.ceil(aProp.value)), + Property aProp => new Property(math.ceil(aProp.value)), + Property aProp => new Property(math.ceil(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs.meta new file mode 100644 index 000000000..5db96990c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ceil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5edeb094abe3f54f818f2add23e3f70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs new file mode 100644 index 000000000..85c3e559a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs @@ -0,0 +1,35 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathClamp : BehaviourEngineNode + { + public MathClamp(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + TryEvaluateValue(ConstStrings.C, out IProperty c); + + // if (b is not Property bProp) + // throw new InvalidOperationException($"B must be a float value."); + + // if (c is not Property cProp) + // throw new InvalidOperationException($"C must be a float value."); + + return a switch + { + Property aProp when b is Property bProp && c is Property cProp => new Property(math.clamp(aProp.value, bProp.value, cProp.value)), + Property aProp when b is Property bProp && c is Property cProp => new Property(math.clamp(aProp.value, bProp.value, cProp.value)), + Property aProp when b is Property bProp && c is Property cProp => new Property(math.clamp(aProp.value, bProp.value, cProp.value)), + Property aProp when b is Property bProp && c is Property cProp => new Property(math.clamp(aProp.value, bProp.value, cProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs.meta new file mode 100644 index 000000000..3b83300e2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clamp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9288e3e8b8f0a564990e9841b3efa386 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs new file mode 100644 index 000000000..3f01d467c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs @@ -0,0 +1,43 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathClz : BehaviourEngineNode + { + public MathClz(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(CountLeadingZeros(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private static int CountLeadingZeros(int x) + { + const int numIntBits = sizeof(int) * 8; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + + x -= x >> 1 & 0x55555555; + x = (x >> 2 & 0x33333333) + (x & 0x33333333); + x = (x >> 4) + x & 0x0f0f0f0f; + x += x >> 8; + x += x >> 16; + + return numIntBits - (x & 0x0000003f); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs.meta new file mode 100644 index 000000000..9f65b8449 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Clz.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb7d30cef2adf1e44b54bfec69abe58a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs new file mode 100644 index 000000000..6b25ba3b8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCombine2 : BehaviourEngineNode + { + public MathCombine2(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + if (a is not Property aFloat) + throw new InvalidOperationException("Input A is not a float!"); + + if (b is not Property bFloat) + throw new InvalidOperationException("Input B is not a float!"); + + return new Property(new float2(aFloat.value, bFloat.value)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs.meta new file mode 100644 index 000000000..951f97c36 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a9afbd002b4b5a468614ca14d784345 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs new file mode 100644 index 000000000..d4871338b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs @@ -0,0 +1,32 @@ +using System; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCombine2x2 : BehaviourEngineNode + { + public MathCombine2x2(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + Span> v = stackalloc Property[4]; + + for (int i = 0; i < v.Length; i++) + { + TryEvaluateValue(ConstStrings.Letters[i], out var prop); + + if (prop is not Property aFloat) + throw new InvalidOperationException($"Input {ConstStrings.Letters[i]} is not a float!"); + + v[i] = (Property)prop; + } + + var c0 = new float2(v[0].value, v[1].value); + var c1 = new float2(v[2].value, v[3].value); + + return new Property(new float2x2(c0, c1)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs.meta new file mode 100644 index 000000000..047d51e0b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine2x2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2398ea3d34fdaa4489970c0649acbb60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs new file mode 100644 index 000000000..1d0aaf2d0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs @@ -0,0 +1,31 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCombine3 : BehaviourEngineNode + { + public MathCombine3(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + TryEvaluateValue(ConstStrings.C, out IProperty c); + + if (a is not Property aFloat) + throw new InvalidOperationException("Input A is not a float!"); + + if (b is not Property bFloat) + throw new InvalidOperationException("Input B is not a float!"); + + if (c is not Property cFloat) + throw new InvalidOperationException("Input C is not a float!"); + + return new Property(new float3(aFloat.value, bFloat.value, cFloat.value)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs.meta new file mode 100644 index 000000000..2372bd71a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87d8f72d366337441b15c9cc65c8ba57 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs new file mode 100644 index 000000000..53daa799f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs @@ -0,0 +1,33 @@ +using System; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCombine3x3 : BehaviourEngineNode + { + public MathCombine3x3(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + Span> v = stackalloc Property[9]; + + for (int i = 0; i < v.Length; i++) + { + TryEvaluateValue(ConstStrings.Letters[i], out var prop); + + if (prop is not Property aFloat) + throw new InvalidOperationException($"Input {ConstStrings.Letters[i]} is not a float!"); + + v[i] = aFloat; + } + + var c0 = new float3(v[0].value, v[1].value, v[2].value); + var c1 = new float3(v[3].value, v[4].value, v[5].value); + var c2 = new float3(v[6].value, v[7].value, v[8].value); + + return new Property(new float3x3(c0, c1, c2)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs.meta new file mode 100644 index 000000000..b73dd8a0a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine3x3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ccd337f317b9cb94b81b794592791109 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs new file mode 100644 index 000000000..e4bacfb29 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs @@ -0,0 +1,35 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCombine4 : BehaviourEngineNode + { + public MathCombine4(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + TryEvaluateValue(ConstStrings.C, out IProperty c); + TryEvaluateValue(ConstStrings.D, out IProperty d); + + if (a is not Property aFloat) + throw new InvalidOperationException("Input A is not a float!"); + + if (b is not Property bFloat) + throw new InvalidOperationException("Input B is not a float!"); + + if (c is not Property cFloat) + throw new InvalidOperationException("Input C is not a float!"); + + if (d is not Property dFloat) + throw new InvalidOperationException("Input D is not a float!"); + + return new Property(new float4(aFloat.value, bFloat.value, cFloat.value, dFloat.value)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs.meta new file mode 100644 index 000000000..527b14ce6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4eee7fb72d98c08468b92046f76c0307 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs new file mode 100644 index 000000000..b76ac6958 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs @@ -0,0 +1,35 @@ +using System; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCombine4x4 : BehaviourEngineNode + { + public MathCombine4x4(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + Span> v = stackalloc Property[16]; + + for (int i = 0; i < v.Length; i++) + { + TryEvaluateValue(ConstStrings.Letters[i], out var prop); + + if (prop is not Property aFloat) + throw new InvalidOperationException($"Input {ConstStrings.Letters[i]} is not a float!"); + + v[i] = (Property)prop; + } + + var c0 = new float4(v[0].value, v[1].value, v[2].value, v[3].value); + var c1 = new float4(v[4].value, v[5].value, v[6].value, v[7].value); + var c2 = new float4(v[8].value, v[9].value, v[10].value, v[11].value); + var c3 = new float4(v[12].value, v[13].value, v[14].value, v[15].value); + + + return new Property(new float4x4(c0, c1, c2, c3)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs.meta new file mode 100644 index 000000000..614671de4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Combine4x4.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48aedeba3114e5342912b27cca678083 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs new file mode 100644 index 000000000..6389658fa --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCos : BehaviourEngineNode + { + public MathCos(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.cos(floatProp.value)), + Property float2Prop => new Property(math.cos(float2Prop.value)), + Property float3Prop => new Property(math.cos(float3Prop.value)), + Property float4Prop => new Property(math.cos(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs.meta new file mode 100644 index 000000000..daaf39674 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cos.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92227839642d5cc49b3c63f2af33460d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs new file mode 100644 index 000000000..80bd9b088 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCosH : BehaviourEngineNode + { + public MathCosH(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.cosh(floatProp.value)), + Property float2Prop => new Property(math.cosh(float2Prop.value)), + Property float3Prop => new Property(math.cosh(float3Prop.value)), + Property float4Prop => new Property(math.cosh(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs.meta new file mode 100644 index 000000000..287b78b2c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/CosH.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24226e2ed1b8d7a469587b8c14a5f6dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs new file mode 100644 index 000000000..9a6a4f88f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs @@ -0,0 +1,25 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCross : BehaviourEngineNode + { + public MathCross(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(math.cross(aProp.value, bProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs.meta new file mode 100644 index 000000000..3fcf7e466 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Cross.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 866624f02d277ca4581c92e78cd67879 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs new file mode 100644 index 000000000..ee6276d85 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs @@ -0,0 +1,34 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathCtz : BehaviourEngineNode + { + public MathCtz(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(CountTrailingZeros(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private static int CountTrailingZeros(int n) + { + int mask = 1; + for (int i = 0; i < 32; i++, mask <<= 1) + if ((n & mask) != 0) + return i; + + return 32; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs.meta new file mode 100644 index 000000000..7aed38a5a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ctz.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e50365237fa76e4b888d1318c81b856 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs new file mode 100644 index 000000000..de104bfae --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathDeg : BehaviourEngineNode + { + public MathDeg(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.degrees(floatProp.value)), + Property float2Prop => new Property(math.degrees(float2Prop.value)), + Property float3Prop => new Property(math.degrees(float3Prop.value)), + Property float4Prop => new Property(math.degrees(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs.meta new file mode 100644 index 000000000..b77ffdf44 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Deg.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efc3db29685964f44b3fd6d9bf81674c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs new file mode 100644 index 000000000..928f13eae --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs @@ -0,0 +1,26 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathDeterminant : BehaviourEngineNode + { + public MathDeterminant(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.determinant(aProp.value)), + Property aProp => new Property(math.determinant(aProp.value)), + Property aProp => new Property(math.determinant(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs.meta new file mode 100644 index 000000000..921382dc3 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Determinant.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95bcc72500ef0cc4ba7e46ca38b2c7c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs new file mode 100644 index 000000000..9783bcaa5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathDiv : BehaviourEngineNode + { + public MathDiv(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value / bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value / bFloat.value), + Property aVec2 when b is Property bVec2 => new Property(aVec2.value / bVec2.value), + Property aVec3 when b is Property bVec3 => new Property((float3)aVec3.value / bVec3.value), + Property aVec4 when b is Property bVec4 => new Property((float4)aVec4.value / bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs.meta new file mode 100644 index 000000000..ecbad3c37 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Div.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a93c57c8f07e2843971af27d0984884 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs new file mode 100644 index 000000000..7bf27072e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathDot : BehaviourEngineNode + { + public MathDot(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(math.dot(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.dot(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.dot(aProp.value, bProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs.meta new file mode 100644 index 000000000..aedf951b2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Dot.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18f4f58b699a6ce4a87124b35a0fa790 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs new file mode 100644 index 000000000..7a584cdc6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs @@ -0,0 +1,16 @@ +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathE : BehaviourEngineNode + { + public MathE(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + return new Property(math.E); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs.meta new file mode 100644 index 000000000..8fa99fcbd --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/E.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e109098b5dd5c0941818881be1e2e361 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs new file mode 100644 index 000000000..992a0a36b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs @@ -0,0 +1,75 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathEq : BehaviourEngineNode + { + public MathEq(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(aProp.value == bProp.value), + Property aInt when b is Property bInt => new Property(aInt.value == bInt.value), + Property aFloat when b is Property bFloat => new Property(eq(aFloat.value,bFloat.value)), + Property pA when b is Property pB => new Property(AllEqual(pA.value, pB.value)), + Property pA when b is Property pB => new Property(AllEqual(pA.value, pB.value)), + Property pA when b is Property pB => new Property(AllEqual(pA.value, pB.value)), + Property pA when b is Property pB => new Property(AllEqual(pA.value, pB.value)), + Property pA when b is Property pB => new Property(AllEqual(pA.value, pB.value)), + Property pA when b is Property pB => new Property(AllEqual(pA.value, pB.value)), + _ => throw new InvalidOperationException($"No supported type found or input types did not match. Types were A: {a.GetTypeSignature()}, B: {b.GetTypeSignature()}"), + }; + } + + private static bool AllEqual(float2 a, float2 b) + { + return eq(a.x, b.x) && eq(a.y, b.y); + } + + private static bool AllEqual(float3 a, float3 b) + { + return eq(a.x, b.x) && eq(a.y, b.y) && eq(a.z, b.z); + } + + private static bool AllEqual(float4 a, float4 b) + { + return eq(a.x, b.x) && eq(a.y, b.y) && eq(a.z, b.z) && eq(a.w, b.w); + } + + private static bool AllEqual(float2x2 a, float2x2 b) + { + return eq(a.c0.x, b.c0.x) && eq(a.c0.y, b.c0.y) && + eq(a.c1.x, b.c1.x) && eq(a.c1.y, b.c1.y); + } + + private static bool AllEqual(float3x3 a, float3x3 b) + { + return eq(a.c0.x, b.c0.x) && eq(a.c0.y, b.c0.y) && eq(a.c0.z, b.c0.z) && + eq(a.c1.x, b.c1.x) && eq(a.c1.y, b.c1.y) && eq(a.c1.z, b.c1.z) && + eq(a.c2.x, b.c2.x) && eq(a.c2.y, b.c2.y) && eq(a.c2.z, b.c2.z); + } + + private static bool AllEqual(float4x4 a, float4x4 b) + { + return eq(a.c0.x, b.c0.x) && eq(a.c0.y, b.c0.y) && eq(a.c0.z, b.c0.z) && eq(a.c0.w, b.c0.w) && + eq(a.c1.x, b.c1.x) && eq(a.c1.y, b.c1.y) && eq(a.c1.z, b.c1.z) && eq(a.c1.w, b.c1.w) && + eq(a.c2.x, b.c2.x) && eq(a.c2.y, b.c2.y) && eq(a.c2.z, b.c2.z) && eq(a.c2.w, b.c2.w) && + eq(a.c3.x, b.c3.x) && eq(a.c3.y, b.c3.y) && eq(a.c3.z, b.c3.z) && eq(a.c3.w, b.c3.w); + } + + private static bool eq(float a, float b) + { + // IEEE standard used for this spec says that inf==inf so we have to make sure that's true. + return Mathf.Approximately(a,b) || (float.IsInfinity(a) && float.IsInfinity(b)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs.meta new file mode 100644 index 000000000..5ae015a2a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Eq.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 52205b776c9aff9409d6526e7e30b08f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs new file mode 100644 index 000000000..6a1275169 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExp : BehaviourEngineNode + { + public MathExp(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.exp(floatProp.value)), + Property float2Prop => new Property(math.exp(float2Prop.value)), + Property float3Prop => new Property(math.exp(float3Prop.value)), + Property float4Prop => new Property(math.exp(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs.meta new file mode 100644 index 000000000..507cc585d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Exp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c14ea0c501fd10e4a801a89c44c23988 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs new file mode 100644 index 000000000..4aaf88f4f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs @@ -0,0 +1,32 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExtract2 : BehaviourEngineNode + { + public MathExtract2(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property property) + throw new InvalidOperationException("Input A is not a float2!"); + + switch (id) + { + case "0": + return new Property(property.value.x); + + case "1": + return new Property(property.value.y); + } + + throw new InvalidOperationException($"Socket {id} is not valid for this node!"); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs.meta new file mode 100644 index 000000000..a668dbe11 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 76db8cb44b6bba64394b024ef2c078dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs new file mode 100644 index 000000000..36c55dd1f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs @@ -0,0 +1,30 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExtract2x2 : BehaviourEngineNode + { + public MathExtract2x2(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property property) + throw new InvalidOperationException("Input A is not a float2x2!"); + + return id switch + { + "0" => new Property(property.value.c0.x), + "1" => new Property(property.value.c0.y), + "2" => new Property(property.value.c1.x), + "3" => new Property(property.value.c1.y), + _ => throw new InvalidOperationException($"Socket {id} is not valid for this node!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs.meta new file mode 100644 index 000000000..76f50b4bc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract2x2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 377c66a5f9f9cc6498bcd40ff5f9542a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs new file mode 100644 index 000000000..2f68d0a44 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs @@ -0,0 +1,35 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExtract3 : BehaviourEngineNode + { + public MathExtract3(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property property) + throw new InvalidOperationException("Input A is not a float3!"); + + switch (id) + { + case "0": + return new Property(property.value.x); + + case "1": + return new Property(property.value.y); + + case "2": + return new Property(property.value.z); + } + + throw new InvalidOperationException($"Socket {id} is not valid for this node!"); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs.meta new file mode 100644 index 000000000..dae43036b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66fcb50c44d2d3146a81c5bb8a145d47 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs new file mode 100644 index 000000000..09d48ae49 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs @@ -0,0 +1,36 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExtract3x3 : BehaviourEngineNode + { + public MathExtract3x3(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property property) + throw new InvalidOperationException("Input A is not a 3x3 matrix!"); + + return id switch + { + "0" => new Property(property.value.c0.x), + "1" => new Property(property.value.c0.y), + "2" => new Property(property.value.c0.z), + "3" => new Property(property.value.c1.x), + "4" => new Property(property.value.c1.y), + "5" => new Property(property.value.c1.z), + "6" => new Property(property.value.c2.x), + "7" => new Property(property.value.c2.y), + "8" => new Property(property.value.c2.z), + _ => throw new InvalidOperationException($"Socket {id} is not valid for this node!"), + }; + } + + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs.meta new file mode 100644 index 000000000..fbd3e9369 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract3x3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3618964fca5e4d347afde65bf884ce5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs new file mode 100644 index 000000000..1f31a10c3 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs @@ -0,0 +1,38 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExtract4 : BehaviourEngineNode + { + public MathExtract4(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property property) + throw new InvalidOperationException($"Input A is not a float4! It is {a.GetTypeSignature()}"); + + switch (id) + { + case "0": + return new Property(property.value.x); + + case "1": + return new Property(property.value.y); + + case "2": + return new Property(property.value.z); + + case "3": + return new Property(property.value.w); + } + + throw new InvalidOperationException($"Socket {id} is not valid for this node!"); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs.meta new file mode 100644 index 000000000..d0c3e8791 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b812b164edf975742a7cfe21ea00e10a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs new file mode 100644 index 000000000..a8de8d865 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs @@ -0,0 +1,43 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathExtract4x4 : BehaviourEngineNode + { + public MathExtract4x4(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property property) + throw new InvalidOperationException("Input A is not a 4x4 matrix!"); + + return id switch + { + "0" => new Property(property.value.c0.x), + "1" => new Property(property.value.c0.y), + "2" => new Property(property.value.c0.z), + "3" => new Property(property.value.c0.w), + "4" => new Property(property.value.c1.x), + "5" => new Property(property.value.c1.y), + "6" => new Property(property.value.c1.z), + "7" => new Property(property.value.c1.w), + "8" => new Property(property.value.c2.x), + "9" => new Property(property.value.c2.y), + "10" => new Property(property.value.c2.z), + "11" => new Property(property.value.c2.w), + "12" => new Property(property.value.c3.x), + "13" => new Property(property.value.c3.y), + "14" => new Property(property.value.c3.z), + "15" => new Property(property.value.c3.w), + _ => throw new InvalidOperationException($"Socket {id} is not valid for this node!"), + }; + } + + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs.meta new file mode 100644 index 000000000..d98b16497 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Extract4x4.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47b67f8fc190737469622d9ddef5bd8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs new file mode 100644 index 000000000..8c7393744 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathFloor : BehaviourEngineNode + { + public MathFloor(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.floor(aProp.value)), + Property aProp => new Property(math.floor(aProp.value)), + Property aProp => new Property(math.floor(aProp.value)), + Property aProp => new Property(math.floor(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs.meta new file mode 100644 index 000000000..1d2358d5e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Floor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78bb65a782c0cee4abd4aec77816f574 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs new file mode 100644 index 000000000..392a055a4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathFract : BehaviourEngineNode + { + public MathFract(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property prop => new Property(math.frac(prop.value)), + Property prop => new Property(math.frac(prop.value)), + Property prop => new Property(math.frac(prop.value)), + Property prop => new Property(math.frac(prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs.meta new file mode 100644 index 000000000..ccf27bb68 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Fract.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e6696cbafd385fe458c21ee8faabedfc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs new file mode 100644 index 000000000..6df5ef1ee --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs @@ -0,0 +1,28 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathGe : BehaviourEngineNode + { + public MathGe(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value >= bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value >= bFloat.value), + // TODO: Support these types? + //Property aVec2 when b is Property bVec2 => new Property(aVec2.value > bVec2.value), + //Property aVec3 when b is Property bVec3 => new Property(aVec3.value > bVec3.value), + //Property aVec4 when b is Property bVec4 => new Property(aVec4.value > bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs.meta new file mode 100644 index 000000000..cb42c7ecd --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Ge.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 58739fe4b50ab484a9392a19ee3c2d79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs new file mode 100644 index 000000000..70f4b2e32 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs @@ -0,0 +1,29 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathGt : BehaviourEngineNode + { + public MathGt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value > bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value > bFloat.value), + // TODO: Support these types? + //Property aVec2 when b is Property bVec2 => new Property(aVec2.value > bVec2.value), + //Property aVec3 when b is Property bVec3 => new Property(aVec3.value > bVec3.value), + //Property aVec4 when b is Property bVec4 => new Property(aVec4.value > bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs.meta new file mode 100644 index 000000000..b04e4cf06 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Gt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bef66b08ca1c16c409a2ed5f5d207db7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs new file mode 100644 index 000000000..a40d841e6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs @@ -0,0 +1,16 @@ +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathInf : BehaviourEngineNode + { + public MathInf(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + return new Property(math.INFINITY); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs.meta new file mode 100644 index 000000000..064805537 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inf.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f4dcade13763cb498bb8ad8cddc8dcb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs new file mode 100644 index 000000000..a11aecdd7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs @@ -0,0 +1,80 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathInverse : BehaviourEngineNode + { + public MathInverse(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + bool isValid; + + IProperty prop = a switch + { + Property aProp => new Property(Inverse(aProp.value, out isValid)), + Property aProp => new Property(Inverse(aProp.value, out isValid)), + Property aProp => new Property(Inverse(aProp.value, out isValid)), + _ => throw new InvalidOperationException("No supported type found."), + }; + + return id switch + { + ConstStrings.VALUE => prop, + ConstStrings.IS_VALID => new Property(isValid), + _ => throw new InvalidOperationException($"Requested output {id} is not part of the spec for this node."), + }; + } + + private static float2x2 Inverse(float2x2 m, out bool isValid) + { + var det = math.determinant(m); + if (det == 0f || float.IsInfinity(det) || float.IsNaN(det)) + { + isValid = false; + return float2x2.zero; + } + + var inverse = math.inverse(m); + isValid = true; + + return inverse; + } + + private static float3x3 Inverse(float3x3 m, out bool isValid) + { + var det = math.determinant(m); + if (det == 0f || float.IsInfinity(det) || float.IsNaN(det)) + { + isValid = false; + return float3x3.zero; + } + + var inverse = math.inverse(m); + isValid = true; + + return inverse; + } + + private static float4x4 Inverse(float4x4 m, out bool isValid) + { + var det = math.determinant(m); + if (det == 0f || float.IsInfinity(det) || float.IsNaN(det)) + { + isValid = false; + return float4x4.zero; + } + + var inverse = math.inverse(m); + isValid = true; + + return inverse; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs.meta new file mode 100644 index 000000000..2a8349d3d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Inverse.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c9a8dda6f93b364c9fffd7d74aa7186 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs new file mode 100644 index 000000000..cbe149d65 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs @@ -0,0 +1,22 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathIsInf : BehaviourEngineNode + { + public MathIsInf(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property fProp) + throw new InvalidOperationException("Property must be of type float for IsNan!"); + + return new Property(float.IsInfinity(fProp.value)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs.meta new file mode 100644 index 000000000..4cd057dea --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsInf.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c64fb74832ee9204fa011e82b19b0b79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs new file mode 100644 index 000000000..57e8e3981 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs @@ -0,0 +1,22 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathIsNaN : BehaviourEngineNode + { + public MathIsNaN(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property fProp) + throw new InvalidOperationException("Property must be of type float for IsNan!"); + + return new Property(float.IsNaN(fProp.value)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs.meta new file mode 100644 index 000000000..81919e64c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/IsNaN.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d375dda6c82d8641b1384c72bf42de9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs new file mode 100644 index 000000000..df80aaa1a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs @@ -0,0 +1,28 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathLe : BehaviourEngineNode + { + public MathLe(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value <= bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value <= bFloat.value), + // TODO: Support these types? + //Property aVec2 when b is Property bVec2 => new Property(aVec2.value > bVec2.value), + //Property aVec3 when b is Property bVec3 => new Property(aVec3.value > bVec3.value), + //Property aVec4 when b is Property bVec4 => new Property(aVec4.value > bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs.meta new file mode 100644 index 000000000..ad52a40fc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Le.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 97024f6bf7b27cd4a8da5154515df298 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs new file mode 100644 index 000000000..95dd39b9b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs @@ -0,0 +1,26 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathLength : BehaviourEngineNode + { + public MathLength(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.length(aProp.value)), + Property aProp => new Property(math.length(aProp.value)), + Property aProp => new Property(math.length(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs.meta new file mode 100644 index 000000000..dfd3a0016 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Length.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 26b99771b3c6bc64b8062d10a9280e12 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs new file mode 100644 index 000000000..faaef1b81 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathLog : BehaviourEngineNode + { + public MathLog(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.log(floatProp.value)), + Property float2Prop => new Property(math.log(float2Prop.value)), + Property float3Prop => new Property(math.log(float3Prop.value)), + Property float4Prop => new Property(math.log(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs.meta new file mode 100644 index 000000000..8d714bb83 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cdba6dac0461295478cbcdbe07060dbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs new file mode 100644 index 000000000..528ca5483 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathLog10 : BehaviourEngineNode + { + public MathLog10(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.log10(floatProp.value)), + Property float2Prop => new Property(math.log10(float2Prop.value)), + Property float3Prop => new Property(math.log10(float3Prop.value)), + Property float4Prop => new Property(math.log10(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs.meta new file mode 100644 index 000000000..10d28967b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log10.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8658a612349bc0b4498b0e28194a9737 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs new file mode 100644 index 000000000..39368f0ee --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathLog2 : BehaviourEngineNode + { + public MathLog2(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.log2(floatProp.value)), + Property float2Prop => new Property(math.log2(float2Prop.value)), + Property float3Prop => new Property(math.log2(float3Prop.value)), + Property float4Prop => new Property(math.log2(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs.meta new file mode 100644 index 000000000..6595fa8d6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Log2.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb6b9291273954e41964e92b30707b61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs new file mode 100644 index 000000000..bfe06dcf2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs @@ -0,0 +1,25 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathLsl : BehaviourEngineNode + { + public MathLsl(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(aProp.value << bProp.value), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs.meta new file mode 100644 index 000000000..4ced064df --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lsl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1852016dca08a654fbddecf7691e2773 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs new file mode 100644 index 000000000..9185bfac1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs @@ -0,0 +1,28 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathLt : BehaviourEngineNode + { + public MathLt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value < bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value < bFloat.value), + // TODO: Support these types? + //Property aVec2 when b is Property bVec2 => new Property(aVec2.value > bVec2.value), + //Property aVec3 when b is Property bVec3 => new Property(aVec3.value > bVec3.value), + //Property aVec4 when b is Property bVec4 => new Property(aVec4.value > bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs.meta new file mode 100644 index 000000000..767187f54 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Lt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f56c0995977fa574aba9bba6daaef607 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs new file mode 100644 index 000000000..fb693e7b9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMatCompose : BehaviourEngineNode + { + public MathMatCompose(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.TRANSLATION, out IProperty translation); + TryEvaluateValue(ConstStrings.ROTATION, out IProperty rotation); + TryEvaluateValue(ConstStrings.SCALE, out IProperty scale); + + return translation switch + { + Property tProp when rotation is Property rProp && scale is Property sProp => new Property(float4x4.TRS(tProp.value, rProp.value.ToQuaternion(), sProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs.meta new file mode 100644 index 000000000..fb9070844 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatCompose.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 415afe1be5d21794e98a40a4265c4ad0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs new file mode 100644 index 000000000..ea4dff8e1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs @@ -0,0 +1,32 @@ +using System; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMatDecompose : BehaviourEngineNode + { + public MathMatDecompose(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property mProp) + throw new InvalidOperationException($"Type of value a must be Matrix4x4 but a {a.GetTypeSignature()} was passed in!"); + + mProp.value.Decompose(out var translation, out var rotation, out var scale); + + return id switch + { + ConstStrings.TRANSLATION => new Property(translation), + ConstStrings.ROTATION => new Property(rotation), + ConstStrings.SCALE => new Property(scale), + _ => throw new InvalidOperationException($"Requested output {id} is not part of the spec for this node."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs.meta new file mode 100644 index 000000000..06cf594a6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0cb7dde06d0ff8a47b76b93e8dce35a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs new file mode 100644 index 000000000..81770dec2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMatMul : BehaviourEngineNode + { + public MathMatMul(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(math.mul(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.mul(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.mul(aProp.value, bProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs.meta new file mode 100644 index 000000000..809332eaf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatMul.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 955039a7f4acfa041ac52d35677baf59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs new file mode 100644 index 000000000..322c3aaf0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMax : BehaviourEngineNode + { + public MathMax(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(math.max(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.max(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.max(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.max(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.max(aProp.value, bProp.value)), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs.meta new file mode 100644 index 000000000..f734a72e0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Max.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39952246b8adc8f4bad6175cbfb434ba +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs new file mode 100644 index 000000000..f5d8bdbef --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMin : BehaviourEngineNode + { + public MathMin(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(math.min(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.min(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.min(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.min(aProp.value, bProp.value)), + Property aProp when b is Property bProp => new Property(math.min(aProp.value, bProp.value)), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs.meta new file mode 100644 index 000000000..51527e190 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Min.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 542bba5141bdc6944a5753732ed723f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs new file mode 100644 index 000000000..0ec28b765 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMix : BehaviourEngineNode + { + public MathMix(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + TryEvaluateValue(ConstStrings.C, out IProperty c); + + return a switch + { + Property aProp when b is Property bProp && c is Property cProp => new Property(math.lerp(aProp.value, bProp.value, cProp.value)), + Property aProp when b is Property bProp && c is Property cProp => new Property(math.lerp(aProp.value, bProp.value, cProp.value)), + Property aProp when b is Property bProp && c is Property cProp => new Property(math.lerp(aProp.value, bProp.value, cProp.value)), + Property aProp when b is Property bProp && c is Property cProp => new Property(math.lerp(aProp.value, bProp.value, cProp.value)), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs.meta new file mode 100644 index 000000000..282af6e30 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mix.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bd6aef88911677418bf5d3ec0d25a31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs new file mode 100644 index 000000000..2f347f6bf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathMul : BehaviourEngineNode + { + public MathMul(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value * bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value * bFloat.value), + Property aVec2 when b is Property bVec2 => new Property(aVec2.value * bVec2.value), + Property aVec3 when b is Property bVec3 => new Property(aVec3.value * bVec3.value), + Property aVec4 when b is Property bVec4 => new Property(aVec4.value * bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs.meta new file mode 100644 index 000000000..4e0345c07 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Mul.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0327458751d7aad44b8be77cb54160eb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs new file mode 100644 index 000000000..80518d88a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs @@ -0,0 +1,16 @@ +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathNaN : BehaviourEngineNode + { + public MathNaN(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + return new Property(math.NAN); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs.meta new file mode 100644 index 000000000..5c34a80a6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/NaN.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9bafdb0737d32e1449ac598d6a96f31f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs new file mode 100644 index 000000000..fef7ac02e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathNeg : BehaviourEngineNode + { + public MathNeg(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property intProp => new Property(-intProp.value), + Property floatProp => new Property(-floatProp.value), + Property float2Prop => new Property(-float2Prop.value), + Property float3Prop => new Property(-float3Prop.value), + Property float4Prop => new Property(-float4Prop.value), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs.meta new file mode 100644 index 000000000..8dd89e060 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Neg.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd43a2b0a88608940b6c5a11f8ea60d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs new file mode 100644 index 000000000..d940f2a43 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs @@ -0,0 +1,26 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathNormalize : BehaviourEngineNode + { + public MathNormalize(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.normalize(aProp.value)), + Property aProp => new Property(math.normalize(aProp.value)), + Property aProp => new Property(math.normalize(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs.meta new file mode 100644 index 000000000..5da34aad8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Normalize.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3093a16db06cc4c48b4cc62dc4da09f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs new file mode 100644 index 000000000..a49ab4822 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs @@ -0,0 +1,24 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathNot : BehaviourEngineNode + { + public MathNot(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(~aProp.value), + Property aProp => new Property(!aProp.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs.meta new file mode 100644 index 000000000..a0cd0b782 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Not.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50dfdc847126d114fa76284a779f9975 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs new file mode 100644 index 000000000..e9238d0a1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs @@ -0,0 +1,25 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathOr : BehaviourEngineNode + { + public MathOr(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(aProp.value | bProp.value), + Property aProp when b is Property bProp => new Property(aProp.value || bProp.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs.meta new file mode 100644 index 000000000..1042e0222 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Or.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9fb5cf4e1835d6f4982dee49cc19907c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs new file mode 100644 index 000000000..f642b5819 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs @@ -0,0 +1,16 @@ +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathPi : BehaviourEngineNode + { + public MathPi(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + return new Property(math.PI); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs.meta new file mode 100644 index 000000000..8e068d204 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pi.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9865abeae68bf0844be44aecd49613b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs new file mode 100644 index 000000000..279031cb7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs @@ -0,0 +1,24 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathPopcnt : BehaviourEngineNode + { + public MathPopcnt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.countbits(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs.meta new file mode 100644 index 000000000..84250aa3f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Popcnt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6da644ab782e044ebdd05a30ca837a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs new file mode 100644 index 000000000..573d6150c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathPow : BehaviourEngineNode + { + public MathPow(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aFloat when b is Property bFloat => new Property(math.pow(aFloat.value, bFloat.value)), + Property aVec2 when b is Property bVec2 => new Property(math.pow(aVec2.value, bVec2.value)), + Property aVec3 when b is Property bVec3 => new Property(math.pow(aVec3.value, bVec3.value)), + Property aVec4 when b is Property bVec4 => new Property(math.pow(aVec4.value, bVec4.value)), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs.meta new file mode 100644 index 000000000..f856039e5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Pow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e3ef6829804e55d409044e73df8f3a81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs new file mode 100644 index 000000000..219fb77e2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRad : BehaviourEngineNode + { + public MathRad(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.radians(floatProp.value)), + Property float2Prop => new Property(math.radians(float2Prop.value)), + Property float3Prop => new Property(math.radians(float3Prop.value)), + Property float4Prop => new Property(math.radians(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs.meta new file mode 100644 index 000000000..a213ff37a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rad.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef6050bb9835d844e80a6aa734c4dfa9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs new file mode 100644 index 000000000..175bee8d8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs @@ -0,0 +1,30 @@ +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRandom : BehaviourEngineNode + { + private bool _flowTriggered; + private float _randomValue; + + public MathRandom(BehaviourEngine engine, Node node) : base(engine, node) + { + // Only generate a new value if a flow has been triggered as that's the spec. + engine.onFlowTriggered += OnFlowTriggered; + } + + private void OnFlowTriggered(Flow flow) + { + _flowTriggered = true; + } + + public override IProperty GetOutputValue(string id) + { + if (_flowTriggered) + { + _randomValue = UnityEngine.Random.value; + _flowTriggered = false; + } + + return new Property(_randomValue); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs.meta new file mode 100644 index 000000000..9c9779772 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Random.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95c7f922fc1dbe74a82708bbc4263f46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs new file mode 100644 index 000000000..5ff3d03ad --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRem : BehaviourEngineNode + { + public MathRem(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value % bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value % bFloat.value), + Property aVec2 when b is Property bVec2 => new Property((float2)aVec2.value % bVec2.value), + Property aVec3 when b is Property bVec3 => new Property((float3)aVec3.value % bVec3.value), + Property aVec4 when b is Property bVec4 => new Property((float4)aVec4.value % bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs.meta new file mode 100644 index 000000000..c234efd9d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: badf386077bf1204b97f67cdfd6a4cf2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs new file mode 100644 index 000000000..d148c25a9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs @@ -0,0 +1,34 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRotate2D : BehaviourEngineNode + { + public MathRotate2D(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(rotate(aProp.value, bProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private static float2 rotate(float2 v, float delta) + { + // TODO: Test rotation direction to make sure it matches the spec (counter-clockwise). + return new float2( + v.x * math.cos(delta) - v.y * math.sin(delta), + v.x * math.sin(delta) + v.y * math.cos(delta) + ); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs.meta new file mode 100644 index 000000000..6d457ac26 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bdeb4210f73dc341a6916862aded682 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs new file mode 100644 index 000000000..bd8fd13c6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs @@ -0,0 +1,32 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRotate3D : BehaviourEngineNode + { + public MathRotate3D(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + TryEvaluateValue(ConstStrings.C, out IProperty c); + + return a switch + { + Property aProp when b is Property bProp && c is Property cProp => new Property(rotate(aProp.value, bProp.value, cProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + + private static float3 rotate(float3 vec, float3 axis, float rad) + { + // TODO: Test rotation direction to make sure it matches the spec (counter-clockwise). + return math.mul(quaternion.AxisAngle(axis, rad), vec); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs.meta new file mode 100644 index 000000000..75ba06f2f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ebb2480e5fd0674d971ad494f82d8a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs new file mode 100644 index 000000000..3d331016a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRound : BehaviourEngineNode + { + public MathRound(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property prop => new Property(math.round(prop.value)), + Property prop => new Property(math.round(prop.value)), + Property prop => new Property(math.round(prop.value)), + Property prop => new Property(math.round(prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs.meta new file mode 100644 index 000000000..a89a1d686 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Round.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 50a596f64abddd94d92690eecc3e7c10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs new file mode 100644 index 000000000..b4edd56bc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSaturate : BehaviourEngineNode + { + public MathSaturate(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.saturate(aProp.value)), + Property aProp => new Property(math.saturate(aProp.value)), + Property aProp => new Property(math.saturate(aProp.value)), + Property aProp => new Property(math.saturate(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs.meta new file mode 100644 index 000000000..a980070ab --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Saturate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d1147220eb9d444a960bfcf0bbe0f71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs new file mode 100644 index 000000000..ca6d358b2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs @@ -0,0 +1,27 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSelect : BehaviourEngineNode + { + public MathSelect(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + var typeA = a.GetSystemType(); + var typeB = b.GetSystemType(); + + if (typeA != typeB) + throw new InvalidOperationException($"Select only accepts arguments of the same type. Type A: {typeA}, Type B: {typeB}"); + + TryEvaluateValue(ConstStrings.CONDITION, out bool condition); + + return condition ? a : b; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs.meta new file mode 100644 index 000000000..54a397670 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Select.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d50093fedb4dc3a4a95d2cd118765025 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs new file mode 100644 index 000000000..ea7d982ac --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs @@ -0,0 +1,28 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSign : BehaviourEngineNode + { + public MathSign(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property prop => new Property((int)math.sign(prop.value)), + Property prop => new Property(math.sign(prop.value)), + Property prop => new Property(math.sign(prop.value)), + Property prop => new Property(math.sign(prop.value)), + Property prop => new Property(math.sign(prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs.meta new file mode 100644 index 000000000..3812a0330 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sign.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 149d52c05d5cf4749a6a919ba1d6d28c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs new file mode 100644 index 000000000..f33d5e215 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSin : BehaviourEngineNode + { + public MathSin(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.sin(floatProp.value)), + Property float2Prop => new Property(math.sin(float2Prop.value)), + Property float3Prop => new Property(math.sin(float3Prop.value)), + Property float4Prop => new Property(math.sin(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs.meta new file mode 100644 index 000000000..537942644 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sin.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7184626b4a86ca48ab557ab86a210bb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs new file mode 100644 index 000000000..83b23d492 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSinH : BehaviourEngineNode + { + public MathSinH(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.sinh(floatProp.value)), + Property float2Prop => new Property(math.sinh(float2Prop.value)), + Property float3Prop => new Property(math.sinh(float3Prop.value)), + Property float4Prop => new Property(math.sinh(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs.meta new file mode 100644 index 000000000..2b9838fa7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/SinH.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b19d4ce2d05afa4abbd93afac1c5db1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs new file mode 100644 index 000000000..f4a9221e8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSqrt : BehaviourEngineNode + { + public MathSqrt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.sqrt(floatProp.value)), + Property float2Prop => new Property(math.sqrt(float2Prop.value)), + Property float3Prop => new Property(math.sqrt(float3Prop.value)), + Property float4Prop => new Property(math.sqrt(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs.meta new file mode 100644 index 000000000..1dc3efe1a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sqrt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 14ab2f9345a59724a8119acf5444b813 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs new file mode 100644 index 000000000..e92c020f4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs @@ -0,0 +1,29 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSub : BehaviourEngineNode + { + public MathSub(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aInt when b is Property bInt => new Property(aInt.value - bInt.value), + Property aFloat when b is Property bFloat => new Property(aFloat.value - bFloat.value), + Property aVec2 when b is Property bVec2 => new Property(aVec2.value - bVec2.value), + Property aVec3 when b is Property bVec3 => new Property(aVec3.value - bVec3.value), + Property aVec4 when b is Property bVec4 => new Property(aVec4.value - bVec4.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs.meta new file mode 100644 index 000000000..6911032dc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Sub.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07e4618a59aa05948bf8d7224f1d9a49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs new file mode 100644 index 000000000..de05699ed --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathSwitch : BehaviourEngineNode + { + public MathSwitch(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.SELECTION, out int selection); + + if (!TryEvaluateValue(selection.ToString(), out IProperty value)) + TryEvaluateValue(ConstStrings.DEFAULT, out value); + + return value; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs.meta new file mode 100644 index 000000000..81ba5323c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Switch.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 34599deeaa91c9d4ebd14ca9d8bdc917 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs new file mode 100644 index 000000000..07c6aae59 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathTan : BehaviourEngineNode + { + public MathTan(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.tan(floatProp.value)), + Property float2Prop => new Property(math.tan(float2Prop.value)), + Property float3Prop => new Property(math.tan(float3Prop.value)), + Property float4Prop => new Property(math.tan(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs.meta new file mode 100644 index 000000000..266c71311 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Tan.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef197c6583abf244193799cd3802d212 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs new file mode 100644 index 000000000..6adf0d6a8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathTanH : BehaviourEngineNode + { + public MathTanH(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property floatProp => new Property(math.tanh(floatProp.value)), + Property float2Prop => new Property(math.tanh(float2Prop.value)), + Property float3Prop => new Property(math.tanh(float3Prop.value)), + Property float4Prop => new Property(math.tanh(float4Prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs.meta new file mode 100644 index 000000000..ffcf0d6b3 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/TanH.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6de50ad91da679f47baf727895e337e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs new file mode 100644 index 000000000..842db92a2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathTransform : BehaviourEngineNode + { + public MathTransform(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(math.mul(bProp.value, aProp.value)), + Property aProp when b is Property bProp => new Property(math.mul(bProp.value, aProp.value)), + Property aProp when b is Property bProp => new Property(math.mul(bProp.value, aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs.meta new file mode 100644 index 000000000..07b59c779 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d64823cbcbed5d240a39266af7a45511 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs new file mode 100644 index 000000000..f6c3c58e6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs @@ -0,0 +1,26 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathTranspose : BehaviourEngineNode + { + public MathTranspose(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(math.transpose(aProp.value)), + Property aProp => new Property(math.transpose(aProp.value)), + Property aProp => new Property(math.transpose(aProp.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs.meta new file mode 100644 index 000000000..79378aa77 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Transpose.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae5cd4bccfb86e74a9a78ef5c5149733 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs new file mode 100644 index 000000000..1f3cc3b95 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs @@ -0,0 +1,27 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathTrunc : BehaviourEngineNode + { + public MathTrunc(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property prop => new Property(math.trunc(prop.value)), + Property prop => new Property(math.trunc(prop.value)), + Property prop => new Property(math.trunc(prop.value)), + Property prop => new Property(math.trunc(prop.value)), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs.meta new file mode 100644 index 000000000..bdedfc718 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Trunc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 73193f70ba84cf84db82bf69c80ebd96 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs new file mode 100644 index 000000000..e8f7bd613 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs @@ -0,0 +1,25 @@ +using System; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathXor : BehaviourEngineNode + { + public MathXor(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(aProp.value ^ bProp.value), + Property aProp when b is Property bProp => new Property(aProp.value ^ bProp.value), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs.meta new file mode 100644 index 000000000..65ecfdb34 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Xor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b309c22f791ed464ea14a983d9ff91f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs new file mode 100644 index 000000000..941a86806 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs @@ -0,0 +1,48 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class NoOp : BehaviourEngineNode + { + private Declaration _declaration; + + public NoOp(BehaviourEngine engine, Node node) : base(engine, node) + { + _declaration = FindDeclaration(node.type, engine.graph); + } + + public override IProperty GetOutputValue(string id) + { + Util.Log($"Checking NoOP node for value {id}"); + + var value = FindValueSocket(id); + + return engine.graph.GetDefaultPropertyForType(value.type); + } + + private ValueSocket FindValueSocket(string id) + { + if (_declaration.outputValueSockets == null || _declaration.outputValueSockets.Count <= 0) + throw new InvalidOperationException($"Attempting to find value socket {id} on this NoOp node when it has no output value sockets!"); + + for (int i = 0; i < _declaration.outputValueSockets.Count; i++) + { + if (_declaration.outputValueSockets[i].name.Equals(id)) + return _declaration.outputValueSockets[i]; + } + + throw new InvalidOperationException($"No value socket found for {id}!"); + } + + private static Declaration FindDeclaration(string op, Graph graph) + { + for (int i = 0; i < graph.declarations.Count; i++) + { + if (graph.declarations[i].op.Equals(op)) + return graph.declarations[i]; + } + + throw new InvalidOperationException($"No declaration found for operation {op}!"); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs.meta new file mode 100644 index 000000000..d78c254ba --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/NoOp.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53fa0cc9a44995945b15a6f9cdd12772 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer.meta new file mode 100644 index 000000000..ff91bc852 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b1471d4c0a0f2c74eb25d9f4a2db5abd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs new file mode 100644 index 000000000..c74d3f908 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs @@ -0,0 +1,72 @@ +using System; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class PointerGet : BehaviourEngineNode + { + private int _type; + + public PointerGet(BehaviourEngine engine, Node node) : base(engine, node) + { + if (!TryGetConfig(ConstStrings.TYPE, out _type)) + Debug.LogError("This pointer/get node has no defined type in its config!"); + } + + public override IProperty GetOutputValue(string id) + { + switch (id) + { + case "value": + return ResolvePointerValue(); + + case "isValid": + return IsPointerValid(); + } + + throw new InvalidOperationException($"Socket {id} is not valid for this node!"); + } + + private Property IsPointerValid() + { + return new Property(TryGetPointerFromConfiguration(out IPointer pointer)); + } + + private IProperty ResolvePointerValue() + { + if (!TryGetPointerFromConfiguration(out IPointer pointer)) + return engine.graph.GetDefaultPropertyForType(_type); + + return pointer switch + { + ReadOnlyPointer pBool => new Property(pBool.GetValue()), + ReadOnlyPointer pInt => new Property(pInt.GetValue()), + ReadOnlyPointer pFloat => new Property(pFloat.GetValue()), + ReadOnlyPointer pColor => new Property(pColor.GetValue().ToFloat3()), + ReadOnlyPointer pColor => new Property(pColor.GetValue().ToFloat4()), + ReadOnlyPointer pQuat => new Property(pQuat.GetValue().ToFloat4()), + ReadOnlyPointer pVec2 => new Property(pVec2.GetValue()), + ReadOnlyPointer pVec3 => new Property(pVec3.GetValue()), + ReadOnlyPointer pVec4 => new Property(pVec4.GetValue()), + ReadOnlyPointer p => new Property(p.GetValue()), + ReadOnlyPointer p => new Property(p.GetValue()), + ReadOnlyPointer p => new Property(p.GetValue()), + Pointer pBool => new Property(pBool.GetValue()), + Pointer pInt => new Property(pInt.GetValue()), + Pointer pFloat => new Property(pFloat.GetValue()), + Pointer pColor => new Property(pColor.GetValue().ToFloat3()), + Pointer pColor => new Property(pColor.GetValue().ToFloat4()), + Pointer pQuat => new Property(pQuat.GetValue().ToFloat4()), + Pointer pVec2 => new Property(pVec2.GetValue()), + Pointer pVec3 => new Property(pVec3.GetValue()), + Pointer pVec4 => new Property(pVec4.GetValue()), + Pointer p => new Property(p.GetValue()), + Pointer p => new Property(p.GetValue()), + Pointer p => new Property(p.GetValue()), + _ => throw new InvalidOperationException("No supported type found."), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs.meta new file mode 100644 index 000000000..705a47666 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Get.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4174e985fd0a48247b4b9defe00996cc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs new file mode 100644 index 000000000..00fe2edc8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs @@ -0,0 +1,94 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class PointerInterpolate : BehaviourEngineNode + { + private IPointer _pointer; + private IProperty _interpGoal; + private float _duration; + private float2 _p1, _p2; + + public PointerInterpolate(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if(validationResult != ValidationResult.Valid) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + var data = new PointerInterpolateData() + { + pointer = _pointer, + startTime = Time.time, + duration = _duration, + endValue = _interpGoal, + cp1 = _p1, + cp2 = _p2, + done = () => TryExecuteFlow(ConstStrings.DONE) + }; + + try + { + engine.pointerInterpolationManager.StartInterpolation(ref data); + + TryExecuteFlow(ConstStrings.OUT); + } + catch(InterpolatorException ex) + { + Debug.LogWarning(ex); + TryExecuteFlow(ConstStrings.ERR); + } + } + + public override bool ValidateConfiguration(string socket) + { + return TryGetPointerFromConfiguration(out _pointer) && + _pointer is not IReadOnlyPointer; + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.VALUE, out _interpGoal) && + TryEvaluateValue(ConstStrings.DURATION, out _duration) && + DurationIsValid(_duration) && + TryEvaluateValue(ConstStrings.P1, out _p1) && + ControlPointIsValid(_p1) && + TryEvaluateValue(ConstStrings.P2, out _p2) && + ControlPointIsValid(_p2); + } + + private static bool DurationIsValid(float duration) + { + if (float.IsNaN(duration) || float.IsInfinity(duration) || duration < 0) + return false; + + return true; + } + + private static bool ControlPointIsValid(float2 cp) + { + if (IsInvalid(cp.x)) + return false; + + if (IsInvalid(cp.y)) + return false; + + return true; + + bool IsInvalid(float v) + { + return float.IsNaN(v) || float.IsInfinity(v) || v < 0 || v > 1; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs.meta new file mode 100644 index 000000000..b88c82a6e --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Interpolate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 020b1603fc4328a4dadfd217d1776c56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs new file mode 100644 index 000000000..56d120f25 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class PointerSet : BehaviourEngineNode + { + private IPointer _pointer; + private IProperty _property; + + public PointerSet(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (validationResult != ValidationResult.Valid) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + engine.pointerInterpolationManager.StopInterpolation(_pointer); + + switch (_property) + { + case Property prop when _pointer is Pointer p: + p.setter(prop.value); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value.ToColor()); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value.ToColor()); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value.ToColor()); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value.ToQuaternion()); + break; + case Property prop when _pointer is Pointer p: + p.setter(prop.value); + break; + default: + Debug.LogWarning($"Either the property type you're attempting to set is unsupported ({_property.GetTypeSignature()}) or the pointer type does not match it ({_pointer.GetSystemType()})."); + TryExecuteFlow(ConstStrings.ERR); + return; + } + + TryExecuteFlow(ConstStrings.OUT); + } + + public override bool ValidateConfiguration(string socket) + { + return TryGetPointerFromConfiguration(out _pointer) && + _pointer is not IReadOnlyPointer; + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.VALUE, out _property); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs.meta new file mode 100644 index 000000000..cb5fe2f27 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Pointer/Set.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 77a57d6a39d9b094c9827e2932eb6d92 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Type.meta new file mode 100644 index 000000000..e93c38611 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 561a4bc46a378b34ea7a660a433462f7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs new file mode 100644 index 000000000..c02892155 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs @@ -0,0 +1,21 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class TypeBoolToFloat : BehaviourEngineNode + { + public TypeBoolToFloat(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property aProp) + throw new InvalidOperationException("Value provided is not a bool! Will not cast to float."); + + return new Property(aProp.value ? 1f : 0f); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs.meta new file mode 100644 index 000000000..f849cd050 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToFloat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23719cee08f510347b45f337cbe558a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs new file mode 100644 index 000000000..9c88f0c91 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs @@ -0,0 +1,21 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class TypeBoolToInt : BehaviourEngineNode + { + public TypeBoolToInt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property aProp) + throw new InvalidOperationException("Value provided is not a bool! Will not cast to int."); + + return new Property(aProp.value ? 1 : 0); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs.meta new file mode 100644 index 000000000..439829e0b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/BoolToInt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd7ac8db50f4b0e4f8bb462613f0c1d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs new file mode 100644 index 000000000..24f2c8de3 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs @@ -0,0 +1,21 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class TypeFloatToBool : BehaviourEngineNode + { + public TypeFloatToBool(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property aProp) + throw new InvalidOperationException("Value provided is not a float! Will not cast to bool."); + + return new Property(aProp.value != float.NaN && aProp.value != 0); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs.meta new file mode 100644 index 000000000..e7e7fc183 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToBool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 584172c46e0bdd24696c74789a5c756e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs new file mode 100644 index 000000000..10013ebbc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs @@ -0,0 +1,21 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class TypeFloatToInt : BehaviourEngineNode + { + public TypeFloatToInt(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property intProperty) + throw new InvalidOperationException("Value provided is not a float! Will not cast to int."); + + return new Property((int)intProperty.value); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs.meta new file mode 100644 index 000000000..9ae1ccd30 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/FloatToInt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6cccffa772bfcd4cbb4a38328233e26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs new file mode 100644 index 000000000..f6ddcd5ae --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs @@ -0,0 +1,21 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class TypeIntToBool : BehaviourEngineNode + { + public TypeIntToBool(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property intProperty) + throw new InvalidOperationException("Value provided is not an int! Will not cast to bool."); + + return new Property(intProperty.value != 0); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs.meta new file mode 100644 index 000000000..fc70dde1f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToBool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 75da6009ef63b9645a81619a2d752f44 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs new file mode 100644 index 000000000..5bc937730 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs @@ -0,0 +1,21 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class TypeIntToFloat : BehaviourEngineNode + { + public TypeIntToFloat(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + if (a is not Property intProperty) + throw new InvalidOperationException("Value provided is not an int! Will not cast to float."); + + return new Property(intProperty.value); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs.meta new file mode 100644 index 000000000..00ea1933d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Type/IntToFloat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65652c49e4fbcb24b9ca6cc466d10c09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable.meta new file mode 100644 index 000000000..9570e92fc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1929dfe74ecbcb041a7751bde61f3085 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs new file mode 100644 index 000000000..2f3c30e54 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs @@ -0,0 +1,23 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableGet : BehaviourEngineNode + { + public VariableGet(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + if (!TryGetConfig(ConstStrings.VARIABLE, out int variableIndex)) + throw new InvalidOperationException(); + + var property = engine.GetVariableProperty(variableIndex); ; + + //Util.Log($"Grabbing variable index {variableIndex} with value {property.ToString()}"); + + return property; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs.meta new file mode 100644 index 000000000..34f0786c9 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Get.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8a790cf026a7ac47a5f28e768f7ee23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs new file mode 100644 index 000000000..275f47237 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableInterpolate : BehaviourEngineNode + { + private Variable _variable; + private bool _slerp; + private IProperty _interpGoal; + private float _duration; + private float2 _p1, _p2; + + public VariableInterpolate(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if(validationResult != ValidationResult.Valid) + { + TryExecuteFlow(ConstStrings.ERR); + return; + } + + TryExecuteFlow(ConstStrings.OUT); + + var data = new VariableInterpolateData() + { + variable = _variable, + startTime = Time.time, + duration = _duration, + endValue = _interpGoal, + cp1 = _p1, + cp2 = _p2, + slerp = _slerp, + done = () => TryExecuteFlow(ConstStrings.DONE) + }; + + engine.variableInterpolationManager.StartInterpolation(ref data); + } + + public override bool ValidateConfiguration(string socket) + { + return TryGetVariableFromConfiguration(out _variable, out var _variableIndex) && + TryGetConfig(ConstStrings.USE_SLERP, out _slerp); + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.VALUE, out _interpGoal) && + TryEvaluateValue(ConstStrings.DURATION, out _duration) && + DurationIsValid(_duration) && + TryEvaluateValue(ConstStrings.P1, out _p1) && + ControlPointIsValid(_p1) && + TryEvaluateValue(ConstStrings.P2, out _p2) && + ControlPointIsValid(_p2); + } + + private static bool DurationIsValid(float duration) + { + if (float.IsNaN(duration) || float.IsInfinity(duration) || duration < 0) + return false; + + return true; + } + + private static bool ControlPointIsValid(float2 cp) + { + if (IsInvalid(cp.x)) + return false; + + if (IsInvalid(cp.y)) + return false; + + return true; + + bool IsInvalid(float v) + { + return float.IsNaN(v) || float.IsInfinity(v) || v < 0 || v > 1; + } + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs.meta new file mode 100644 index 000000000..ed5d2176c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Interpolate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: add528cf00155e248ad33b7112f4289d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs new file mode 100644 index 000000000..8cf72fb63 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableSet : BehaviourEngineNode + { + private Variable _graphVariable; + private IProperty _newValue; + + public VariableSet(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (validationResult != ValidationResult.Valid) + throw new InvalidOperationException(); + + Util.Log($"Setting Variable {_graphVariable.id} to {_newValue.ToString()}"); + + engine.variableInterpolationManager.StopInterpolation(_graphVariable); + + _graphVariable.property = _newValue; + + TryExecuteFlow(ConstStrings.OUT); + } + + public override bool ValidateConfiguration(string socket) + { + if (!TryGetConfig(ConstStrings.VARIABLE, out int variableIndex)) + return false; + + try + { + _graphVariable = engine.graph.variables[variableIndex]; + } + catch (Exception e) + { + Debug.LogException(e); + return false; + } + + return true; + } + + public override bool ValidateValues(string socket) + { + return TryEvaluateValue(ConstStrings.VALUE, out _newValue); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs.meta new file mode 100644 index 000000000..e71a2cdaa --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/Set.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b8d919458de03c4abd53f4a74d45194 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs new file mode 100644 index 000000000..74191517d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class VariableSetMultiple : BehaviourEngineNode + { + private int[] _variableIndices; + + public VariableSetMultiple(BehaviourEngine engine, Node node) : base(engine, node) + { + // TODO: ValidateConfiguration allocates for the int array, could move that here but it would break runtime edits. + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + if (validationResult != ValidationResult.Valid) + throw new InvalidOperationException(); + + int index; + Variable variable; + + for (int i = 0; i < _variableIndices.Length; i++) + { + index = _variableIndices[i]; + + if (!TryEvaluateValue(ConstStrings.Numbers[index], out IProperty value)) + continue; + + variable = engine.graph.variables[index]; + + engine.variableInterpolationManager.StopInterpolation(variable); + + Util.Log($"SetMultiple: Setting Variable {variable.id} to {value.ToString()}"); + + variable.property = value; + } + + TryExecuteFlow(ConstStrings.OUT); + } + + public override bool ValidateConfiguration(string socket) + { + if (!TryGetConfig(ConstStrings.VARIABLES, out _variableIndices)) + return false; + + var variableCount = engine.graph.variables.Count; + + for (int i = 0; i < _variableIndices.Length; i++) + { + if (_variableIndices[i] < 0 || _variableIndices[i] >= variableCount) + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs.meta new file mode 100644 index 000000000..8d758063a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7fcdda51d3e2a547ac64462edd85ffe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs b/Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs new file mode 100644 index 000000000..d734fdbbc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Pool; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct PointerInterpolateData + { + public IPointer pointer; + public float startTime; + public float duration; + public IProperty endValue; + public float2 cp1; + public float2 cp2; + public Action done; + public IInterpolator interpolator; + } + + public interface IInterpolator + { + public bool Interpolate(float t); + } + + public class InterpolatorException : Exception + { + public InterpolatorException(){} + public InterpolatorException(string message) : base(message){} + public InterpolatorException(string message, Exception innerException) : base(message, innerException) {} + } + + public class PointerInterpolationManager + { + public struct Interpolator : IInterpolator + { + public Action setter; + public Func evaluator; + public T from; + public T to; + + public bool Interpolate(float t) + { + var end = t >= 1f; + + t = end ? 1f : t; + + setter(evaluator(from, to, t)); + + return end; + } + } + + private Dictionary _interpolationsInProgress = new(); + + public void OnTick() + { + // Avoiding iterating over a changing collection by grabbing a pooled dictionary. + var temp = DictionaryPool.Get(); + try + { + foreach (var interp in _interpolationsInProgress) + { + temp.Add(interp.Key, interp.Value); + } + + foreach (var anim in temp) + { + DoInterpolate(anim.Value); + } + } + finally + { + DictionaryPool.Release(temp); + } + } + + private void DoInterpolate(PointerInterpolateData data) + { + var t = (Time.time - data.startTime) / data.duration; + + var finished = data.interpolator.Interpolate(t); + + if (finished) + { + Util.Log($"Finished interpolating."); + + _interpolationsInProgress.Remove(data.pointer); + data.done(); + } + } + + public void StartInterpolation(ref PointerInterpolateData data) + { + _interpolationsInProgress.Remove(data.pointer); // Stop any in-progress interpolations for this pointer. + + var interpolator = data.endValue switch + { + Property property => GetInterpolator(property, data), + Property property => GetInterpolator(property, data), + Property property => Processfloat3(property, data), + Property property => Processfloat4(property, data), + Property property => GetInterpolator(property, data), + Property property => GetInterpolator(property, data), + Property property => GetInterpolator(property, data), + + _ => throw new InterpolatorException($"Type {data.endValue.GetTypeSignature()} is not supported for interpolation."), + }; + + data.interpolator = interpolator; + + _interpolationsInProgress.Add(data.pointer, data); + + Util.Log($"Starting Interpolation: Start Time {data.startTime}, Duration: {data.duration}"); + } + + public bool StopInterpolation(IPointer pointer) + { + return _interpolationsInProgress.Remove(pointer); // Stop any in-progress interpolations for this pointer. + } + + private IInterpolator Processfloat3(Property property, PointerInterpolateData data) + { + return data.pointer switch + { + Pointer => GetInterpolator(property, data), + Pointer => GetInterpolator(new Property(property.value.ToColor()), data), + Pointer => GetInterpolator(new Property(property.value.ToColor()), data), + Pointer => GetInterpolator(new Property(quaternion.Euler(property.value)), data), + + _ => throw new InterpolatorException($"Pointer type {data.pointer.GetSystemType()} is not supported for this float3 property."), + }; + } + + private IInterpolator Processfloat4(Property property, PointerInterpolateData data) + { + return data.pointer switch + { + Pointer => GetInterpolator(property, data), + Pointer => GetInterpolator(new Property(property.value.ToColor()), data), + Pointer => GetInterpolator(new Property(property.value.ToQuaternion()), data), + + _ => throw new InterpolatorException($"Pointer type {data.pointer.GetSystemType()} is not supported for this float4 property."), + }; + } + + private IInterpolator GetInterpolator(Property property, in PointerInterpolateData data) + { + var p = (Pointer)data.pointer; + var cp1 = data.cp1; + var cp2 = data.cp2; + var evaluator = new Func((a, b, t) => p.evaluator(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + + var interpolator = new Interpolator() + { + setter = p.setter, + evaluator = evaluator, + from = p.getter(), + to = property.value + }; + + return interpolator; + } + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs.meta b/Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs.meta new file mode 100644 index 000000000..4ad0169e4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/PointerInterpolationManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b74c6c463701c642b08acbeacd8d618 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers.meta b/Runtime/Scripts/Interactivity/Playback/Pointers.meta new file mode 100644 index 000000000..69ed24a63 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45dd5331dd05b6840bf3b45512aa8f6a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs new file mode 100644 index 000000000..efa4a71ba --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs @@ -0,0 +1,52 @@ +using System; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct ActiveCameraPointers + { + // These are readonly in the spec but I'm a rebel + public Pointer translation; + public Pointer rotation; + + public static ActiveCameraPointers CreatePointers() + { + // Unity coordinate system differs from the GLTF one. + // Unity is left-handed with y-up and z-forward. + // GLTF is right-handed with y-up and z-forward. + // Handedness is easiest to swap here though we could do it during deserialization for performance. + var pointers = new ActiveCameraPointers(); + + pointers.translation = new Pointer() + { + setter = (v) => Camera.main.transform.localPosition = v.SwapHandedness(), + getter = () => Camera.main.transform.localPosition.SwapHandedness(), + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + pointers.rotation = new Pointer() + { + setter = (v) => Camera.main.transform.localRotation = ((Quaternion)v).SwapHandedness(), + getter = () => Camera.main.transform.localRotation.SwapHandedness(), + evaluator = (a, b, t) => math.slerp(a, b, t) + }; + + return pointers; + } + + public IPointer ProcessActiveCameraPointer(StringSpanReader reader) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /activeCamera/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("translation") => translation, + var a when a.Is("rotation") => rotation, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs.meta new file mode 100644 index 000000000..25791605b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48c0875861d723d43865a21159cfb82c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs new file mode 100644 index 000000000..010261cc2 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct AnimationPointers + { + public ReadOnlyPointer isPlaying; + public ReadOnlyPointer playhead; + public ReadOnlyPointer virtualPlayhead; + public ReadOnlyPointer minTime; + public ReadOnlyPointer maxTime; + + public AnimationPointers(AnimationWrapper wrapper, int animationIndex) + { + isPlaying = new ReadOnlyPointer(() => wrapper.IsAnimationPlaying(animationIndex)); + playhead = new ReadOnlyPointer(() => wrapper.GetPlayhead(animationIndex)); + virtualPlayhead = new ReadOnlyPointer(() => wrapper.GetVirtualPlayhead(animationIndex)); + minTime = new ReadOnlyPointer(() => 0f); + maxTime = new ReadOnlyPointer(() => wrapper.GetAnimationMaxTime(animationIndex)); + } + + public static IPointer ProcessPointer(StringSpanReader reader, BehaviourEngineNode engineNode, List pointers) + { + reader.AdvanceToNextToken('/'); + + var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + + var pointer = pointers[nodeIndex]; + + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /animations/{}/extensions/KHR_interactivity + return reader.AsReadOnlySpan() switch + { + var a when a.Is(Pointers.IS_PLAYING) => pointer.isPlaying, + var a when a.Is(Pointers.MIN_TIME) => pointer.minTime, + var a when a.Is(Pointers.MAX_TIME) => pointer.maxTime, + var a when a.Is(Pointers.PLAYHEAD) => pointer.playhead, + var a when a.Is(Pointers.VIRTUAL_PLAYHEAD) => pointer.virtualPlayhead, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs.meta new file mode 100644 index 000000000..b034f5cf6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 353daba4706383840ab1cfc460421b55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs new file mode 100644 index 000000000..2b1fe9e9c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct CameraPointers + { + public Pointer orthographicXMag; + public Pointer orthographicYMag; + + public Pointer perspectiveAspectRatio; + public Pointer perspectiveYFov; + + public Pointer zFar; + public Pointer zNear; + + public CameraPointers(Camera cam) + { + // Unity does not allow you to set the width of the orthographic window directly. + // cam.orthographicSize is the YMag and the width is then that value multiplied by your aspect ratio. + orthographicXMag = new Pointer() + { + setter = (v) => cam.orthographicSize = v / cam.aspect, + getter = () => cam.orthographicSize * cam.aspect, + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + orthographicYMag = new Pointer() + { + setter = (v) => cam.orthographicSize = v, + getter = () => cam.orthographicSize, + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + perspectiveAspectRatio = new Pointer() + { + setter = (v) => cam.aspect = v, + getter = () => cam.aspect, + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + perspectiveYFov = new Pointer() + { + setter = (v) => cam.fieldOfView = v, + getter = () => cam.fieldOfView, + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + zNear = new Pointer() + { + setter = (v) => cam.nearClipPlane = v, + getter = () => cam.nearClipPlane, + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + zFar = new Pointer() + { + setter = (v) => cam.farClipPlane = v, + getter = () => cam.farClipPlane, + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + } + + public static IPointer ProcessCameraPointer(StringSpanReader reader, BehaviourEngineNode engineNode, List pointers) + { + reader.AdvanceToNextToken('/'); + + var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + + var pointer = pointers[nodeIndex]; + + reader.AdvanceToNextToken('/'); + + // Path so far: /cameras/{}/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("orthographic") => ProcessOrthographicPointer(reader, pointer), + var a when a.Is("perspective") => ProcessPerspectivePointer(reader, pointer), + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessPerspectivePointer(StringSpanReader reader, CameraPointers pointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /cameras/{}/perspective + return reader.AsReadOnlySpan() switch + { + var a when a.Is("aspectRatio") => pointer.perspectiveAspectRatio, + var a when a.Is("yfov") => pointer.perspectiveYFov, + var a when a.Is("zfar") => pointer.zFar, + var a when a.Is("znear") => pointer.zNear, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessOrthographicPointer(StringSpanReader reader, CameraPointers pointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /cameras/{}/orthographic + return reader.AsReadOnlySpan() switch + { + var a when a.Is("xmag") => pointer.orthographicXMag, + var a when a.Is("ymag") => pointer.orthographicYMag, + var a when a.Is("zfar") => pointer.zFar, + var a when a.Is("znear") => pointer.zNear, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs.meta new file mode 100644 index 000000000..e75a373de --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3162751f184bb25428eec5af1aa84157 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials.meta new file mode 100644 index 000000000..58f54e302 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: de5e942c98daff6429f356ca6adf8738 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs new file mode 100644 index 000000000..7f9c7f610 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs @@ -0,0 +1,48 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct BaseColorPointers + { + private static readonly int baseColorFactorHash = Shader.PropertyToID("baseColorFactor"); + private static readonly int rotationHash = Shader.PropertyToID("baseColorTextureRotation"); + private static readonly int textureHash = Shader.PropertyToID("baseColorTexture"); + private static readonly int texCoordHash = Shader.PropertyToID("baseColorTextureTexCoord"); + + public TransformPointers transformPointers; + public Pointer baseColorFactor; + + public BaseColorPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + baseColorFactor = PointerHelpers.CreateColorRGBAPointer(mat, baseColorFactorHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, BaseColorPointers pointers) + { + // TODO: These come in the form of baseColorTexture/extensions/KHR_texture_transform/{PROPERTY} + // We're skipping ahead to get there with this triple-call. + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/pbrMetallicRoughness/baseColorTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs.meta new file mode 100644 index 000000000..4792527bc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79e9c27fe4b79cf479cdd5a9cff0c703 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs new file mode 100644 index 000000000..75cf57051 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs @@ -0,0 +1,65 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct ClearcoatPointers + { + private static readonly int textureHash = Shader.PropertyToID("clearcoatTexture"); + private static readonly int rotationHash = Shader.PropertyToID("clearcoatTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("clearcoatTextureTexCoord"); + private static readonly int factorHash = Shader.PropertyToID("clearcoatFactor"); + + public TransformPointers transformPointers; + public Pointer factor; + + public ClearcoatPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + factor = PointerHelpers.CreateFloatPointer(mat, factorHash); + } + + public static IPointer ProcessClearcoatPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_clearcoat + return reader.AsReadOnlySpan() switch + { + var a when a.Is("clearcoatFactor") => matPointer.clearcoatPointers.factor, + var a when a.Is("clearcoatRoughnessFactor") => matPointer.clearcoatRoughnessPointers.factor, + var a when a.Is("clearcoatTexture") => ProcessExtensionsPointer(reader, matPointer.clearcoatPointers), + var a when a.Is("clearcoatRoughnessTexture") => ClearcoatRoughnessPointers.ProcessExtensionsPointer(reader, matPointer.clearcoatRoughnessPointers), + + // TODO: This property is not mentioned anywhere in the PBRGraph UnityGLTF shader so I didn't include it. + //var a when a.Is("clearcoatNormalTexture") => , + + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, ClearcoatPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_clearcoat/clearcoatTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs.meta new file mode 100644 index 000000000..305cb2a90 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ff933da8354a9034aa3443aa41acdc86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs new file mode 100644 index 000000000..ceb86d8a7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs @@ -0,0 +1,46 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct ClearcoatRoughnessPointers + { + private static readonly int textureHash = Shader.PropertyToID("clearcoatRoughnessTexture"); + private static readonly int rotationHash = Shader.PropertyToID("clearcoatRoughnessTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("clearcoatRoughnessTextureTexCoord"); + private static readonly int factorHash = Shader.PropertyToID("clearcoatRoughnessFactor"); + + public TransformPointers transformPointers; + public Pointer factor; + + public ClearcoatRoughnessPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + factor = PointerHelpers.CreateFloatPointer(mat, factorHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, ClearcoatRoughnessPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_clearcoat/clearcoatRoughnessTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs.meta new file mode 100644 index 000000000..52e8938e4 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e636888c0f78f5c4c9464dc220e63da2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs new file mode 100644 index 000000000..4aae7ca9c --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs @@ -0,0 +1,46 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct EmissivePointers + { + private static readonly int textureHash = Shader.PropertyToID("emissiveTexture"); + private static readonly int rotationHash = Shader.PropertyToID("emissiveTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("emissiveTextureTexCoord"); + private static readonly int emissiveFactorHash = Shader.PropertyToID("emissiveFactor"); + + public TransformPointers transformPointers; + public Pointer emissiveFactor; + + public EmissivePointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + emissiveFactor = PointerHelpers.CreateColorRGBPointer(mat, emissiveFactorHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, EmissivePointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/emissiveTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs.meta new file mode 100644 index 000000000..e2a6ce500 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f537f485cceafc4497ca817ec59571b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs new file mode 100644 index 000000000..b8100ad68 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs @@ -0,0 +1,67 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct IridescencePointers + { + public static readonly int textureHash = Shader.PropertyToID("iridescenceTexture"); + public static readonly int rotationHash = Shader.PropertyToID("iridescenceTextureRotation"); + public static readonly int texCoordHash = Shader.PropertyToID("iridescenceTextureTexCoord"); + public static readonly int iridescenceFactorHash = Shader.PropertyToID("iridescenceFactor"); + public static readonly int iridescenceIorHash = Shader.PropertyToID("iridescenceIor"); + + public TransformPointers transformPointers; + public Pointer iridescenceFactor; + public Pointer iridescenceIor; + + public IridescencePointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + iridescenceFactor = PointerHelpers.CreateFloatPointer(mat, iridescenceFactorHash); + iridescenceIor = PointerHelpers.CreateFloatPointer(mat, iridescenceIorHash); + } + + public static IPointer ProcessPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_iridescence + return reader.AsReadOnlySpan() switch + { + var a when a.Is("iridescenceFactor") => matPointer.iridescencePointers.iridescenceFactor, + var a when a.Is("iridescenceIor") => matPointer.iridescencePointers.iridescenceIor, + var a when a.Is("iridescenceThicknessMinimum") => matPointer.iridescenceThicknessPointers.min, + var a when a.Is("iridescenceThicknessMaximum") => matPointer.iridescenceThicknessPointers.max, + var a when a.Is("iridescenceTexture") => ProcessExtensionsPointer(reader, matPointer.iridescencePointers), + var a when a.Is("iridescenceThicknessTexture") => IridescenceThicknessPointers.ProcessExtensionsPointer(reader, matPointer.iridescenceThicknessPointers), + + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, IridescencePointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_iridescence/iridescenceTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs.meta new file mode 100644 index 000000000..df67eefb5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0504b3e2773e0df4ca70c974712b9f19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs new file mode 100644 index 000000000..a0eeaea78 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs @@ -0,0 +1,49 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct IridescenceThicknessPointers + { + private static readonly int textureHash = Shader.PropertyToID("iridescenceThicknessTexture"); + private static readonly int rotationHash = Shader.PropertyToID("iridescenceThicknessTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("iridescenceThicknessTextureTexCoord"); + private static readonly int minHash = Shader.PropertyToID("iridescenceThicknessMinimum"); + private static readonly int maxHash = Shader.PropertyToID("iridescenceThicknessMaximum"); + + public TransformPointers transformPointers; + public Pointer min; + public Pointer max; + + public IridescenceThicknessPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + min = PointerHelpers.CreateFloatPointer(mat, minHash); + max = PointerHelpers.CreateFloatPointer(mat, maxHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, IridescenceThicknessPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_iridescence/iridescenceThicknessTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs.meta new file mode 100644 index 000000000..7fd2bb01b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 421c2a4986e15b24b8cfb149b9d0f9b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs new file mode 100644 index 000000000..034bbe84f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct TransformPointers + { + public Pointer offset; + public Pointer scale; + public Pointer rotation; + public Pointer texCoord; + } + + public struct MaterialPointers + { + public static readonly int alphaCutoffHash = Shader.PropertyToID("alphaCutoff"); + public static readonly int iorHash = Shader.PropertyToID("ior"); + public static readonly int attenuationDistanceHash = Shader.PropertyToID("attenuationDistance"); + public static readonly int attenuationColorHash = Shader.PropertyToID("attenuationColor"); + public static readonly int dispersionHash = Shader.PropertyToID("dispersion"); + + public Pointer alphaCutoff; + public Pointer ior; + public Pointer attenuationDistance; + public Pointer attenuationColor; + public Pointer dispersion; + + public BaseColorPointers baseColorPointers; + public ClearcoatPointers clearcoatPointers; + public ClearcoatRoughnessPointers clearcoatRoughnessPointers; + public EmissivePointers emissivePointers; + public IridescencePointers iridescencePointers; + public IridescenceThicknessPointers iridescenceThicknessPointers; + public MetallicRoughnessPointers metallicRoughnessPointers; + public NormalPointers normalPointers; + public OcclusionPointers occlusionPointers; + public SheenPointers sheenPointers; + public SheenRoughnessPointers sheenRoughnessPointers; + public SpecularPointers specularPointers; + public SpecularColorPointers specularColorPointers; + public ThicknessPointers thicknessPointers; + public TransmissionPointers transmissionPointers; + + public Material material { get; private set; } + + public MaterialPointers(Material mat) + { + material = mat; + + alphaCutoff = PointerHelpers.CreateFloatPointer(mat, alphaCutoffHash); + ior = PointerHelpers.CreateFloatPointer(mat, iorHash); + attenuationDistance = PointerHelpers.CreateFloatPointer(mat, attenuationDistanceHash); + attenuationColor = PointerHelpers.CreateColorRGBPointer(mat, attenuationColorHash); + dispersion = PointerHelpers.CreateFloatPointer(mat, dispersionHash); + + baseColorPointers = new(mat); + clearcoatPointers = new(mat); + clearcoatRoughnessPointers = new(mat); + emissivePointers = new(mat); + iridescencePointers = new(mat); + iridescenceThicknessPointers = new(mat); + metallicRoughnessPointers = new(mat); + normalPointers = new(mat); + occlusionPointers = new(mat); + sheenPointers = new(mat); + sheenRoughnessPointers = new(mat); + specularPointers = new(mat); + specularColorPointers = new(mat); + thicknessPointers = new(mat); + transmissionPointers = new(mat); + } + + public static IPointer ProcessMaterialPointer(StringSpanReader reader, BehaviourEngineNode engineNode, List pointers) + { + reader.AdvanceToNextToken('/'); + + var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + + var pointer = pointers[nodeIndex]; + + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("alphaCutoff") => pointer.alphaCutoff, + var a when a.Is("emissiveFactor") => pointer.emissivePointers.emissiveFactor, + var a when a.Is("normalTexture") => ProcessNormalMapPointer(reader, pointer), + var a when a.Is("occlusionTexture") => ProcessOcclusionMapPointer(reader, pointer), + var a when a.Is("emissiveTexture") => ProcessEmissiveMapPointer(reader, pointer), + var a when a.Is("pbrMetallicRoughness") => ProcessPBRMetallicRoughnessPointer(reader, pointer), + var a when a.Is("extensions") => ProcessExtensionPointer(reader, pointer), + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessPBRMetallicRoughnessPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/pbrMetallicRoughness/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("baseColorFactor") => matPointer.baseColorPointers.baseColorFactor, + var a when a.Is("baseColorTexture") => BaseColorPointers.ProcessExtensionsPointer(reader, matPointer.baseColorPointers), + var a when a.Is("metallicRoughnessTexture") => MetallicRoughnessPointers.ProcessExtensionsPointer(reader, matPointer.metallicRoughnessPointers), + var a when a.Is("metallicFactor") => matPointer.metallicRoughnessPointers.metallicFactor, + var a when a.Is("roughnessFactor") => matPointer.metallicRoughnessPointers.roughnessFactor, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessOcclusionMapPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/occlusionTexture/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("strength") => matPointer.occlusionPointers.occlusionStrength, + var a when a.Is("extensions") => OcclusionPointers.ProcessExtensionsPointer(reader, matPointer.occlusionPointers), + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessNormalMapPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/normalTexture/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("scale") => matPointer.normalPointers.normalScale, + var a when a.Is("extensions") => NormalPointers.ProcessExtensionsPointer(reader, matPointer.normalPointers), + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessEmissiveMapPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/emissiveTexture/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("extensions") => EmissivePointers.ProcessExtensionsPointer(reader, matPointer.emissivePointers), + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessExtensionPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("KHR_materials_clearcoat") => ClearcoatPointers.ProcessClearcoatPointer(reader, matPointer), + var a when a.Is("KHR_materials_dispersion") => matPointer.dispersion, + var a when a.Is("KHR_materials_ior") => matPointer.ior, + var a when a.Is("KHR_materials_iridescence") => IridescencePointers.ProcessPointer(reader, matPointer), + var a when a.Is("KHR_materials_sheen") => SheenPointers.ProcessPointer(reader, matPointer), + var a when a.Is("KHR_materials_specular") => SpecularPointers.ProcessPointer(reader, matPointer), + var a when a.Is("KHR_materials_transmission") => TransmissionPointers.ProcessPointer(reader, matPointer), + var a when a.Is("KHR_materials_volume") => ThicknessPointers.ProcessPointer(reader, matPointer), + + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs.meta new file mode 100644 index 000000000..7cd9843ba --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f2064cb69ecdae42b4a55951d2fc214 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs new file mode 100644 index 000000000..ba39309c0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs @@ -0,0 +1,51 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct MetallicRoughnessPointers + { + private static readonly int textureHash = Shader.PropertyToID("metallicRoughnessTexture"); + private static readonly int rotationHash = Shader.PropertyToID("metallicRoughnessTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("metallicRoughnessTextureTexCoord"); + private static readonly int metallicFactorHash = Shader.PropertyToID("metallicFactor"); + private static readonly int roughnessFactorHash = Shader.PropertyToID("roughnessFactor"); + + public TransformPointers transformPointers; + public Pointer metallicFactor; + public Pointer roughnessFactor; + + public MetallicRoughnessPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + metallicFactor = PointerHelpers.CreateFloatPointer(mat, metallicFactorHash); + roughnessFactor = PointerHelpers.CreateFloatPointer(mat, roughnessFactorHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, MetallicRoughnessPointers pointers) + { + // TODO: These come in the form of baseColorTexture/extensions/KHR_texture_transform/{PROPERTY} + // We're skipping ahead to get there with this triple-call. + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/pbrMetallicRoughness/metallicRoughnessTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs.meta new file mode 100644 index 000000000..02db557e7 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e594935cb8d065145b6c81dcc8c9d346 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs new file mode 100644 index 000000000..f4bdfc487 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs @@ -0,0 +1,48 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct NormalPointers + { + private static readonly int textureHash = Shader.PropertyToID("normalTexture"); + private static readonly int rotationHash = Shader.PropertyToID("normalTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("normalTextureTexCoord"); + private static readonly int normalScaleHash = Shader.PropertyToID("normalScale"); + + public TransformPointers transformPointers; + public Pointer normalScale; + + public NormalPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + normalScale = PointerHelpers.CreateFloatPointer(mat, normalScaleHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, NormalPointers pointers) + { + // TODO: These come in the form of baseColorTexture/extensions/KHR_texture_transform/{PROPERTY} + // We're skipping ahead to get there with this triple-call. + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/pbrMetallicRoughness/metallicRoughnessTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs.meta new file mode 100644 index 000000000..fc966fb8a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a969c737c59faa4ea1130b3bdfb9c48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs new file mode 100644 index 000000000..f849bad54 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs @@ -0,0 +1,46 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct OcclusionPointers + { + private static readonly int textureHash = Shader.PropertyToID("occlusionTexture"); + private static readonly int rotationHash = Shader.PropertyToID("occlusionTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("occlusionTextureTexCoord"); + private static readonly int occlusionStrengthHash = Shader.PropertyToID("occlusionStrength"); + + public TransformPointers transformPointers; + public Pointer occlusionStrength; + + public OcclusionPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + occlusionStrength = PointerHelpers.CreateFloatPointer(mat, occlusionStrengthHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, OcclusionPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/occlusionTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs.meta new file mode 100644 index 000000000..88d07a0b0 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f11f3499ed4da44799f2571233d6e32 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs new file mode 100644 index 000000000..15e2f53b5 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct SheenPointers + { + private static readonly int rotationHash = Shader.PropertyToID("sheenColorTextureRotation"); + private static readonly int textureHash = Shader.PropertyToID("sheenColorTexture"); + private static readonly int texCoordHash = Shader.PropertyToID("sheenColorTextureTexCoord"); + private static readonly int colorFactorHash = Shader.PropertyToID("sheenColorFactor"); + + public TransformPointers transformPointers; + public Pointer colorFactor; + + public SheenPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + colorFactor = PointerHelpers.CreateColorRGBPointer(mat, colorFactorHash); + } + + public static IPointer ProcessPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_sheen + return reader.AsReadOnlySpan() switch + { + var a when a.Is("sheenColorFactor") => matPointer.sheenPointers.colorFactor, + var a when a.Is("sheenRoughnessFactor") => matPointer.sheenRoughnessPointers.factor, + var a when a.Is("sheenColorTexture") => ProcessExtensionsPointer(reader, matPointer.sheenPointers), + var a when a.Is("sheenRoughnessTexture") => SheenRoughnessPointers.ProcessExtensionsPointer(reader, matPointer.sheenRoughnessPointers), + + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, SheenPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_iridescence/iridescenceTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs.meta new file mode 100644 index 000000000..741b4a9b8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bacc820cd0d5e1f44a94ba891becf1d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs new file mode 100644 index 000000000..afa73ca2f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs @@ -0,0 +1,46 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct SheenRoughnessPointers + { + private static readonly int rotationHash = Shader.PropertyToID("sheenRoughnessTextureRotation"); + private static readonly int textureHash = Shader.PropertyToID("sheenRoughnessTexture"); + private static readonly int texCoordHash = Shader.PropertyToID("sheenRoughnessTextureTexCoord"); + private static readonly int factorHash = Shader.PropertyToID("sheenRoughnessFactor"); + + public TransformPointers transformPointers; + public Pointer factor; + + public SheenRoughnessPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + factor = PointerHelpers.CreateFloatPointer(mat, factorHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, SheenRoughnessPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_iridescence/iridescenceTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs.meta new file mode 100644 index 000000000..7b9c36a5d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d083b37d605e42d4a8f1b9484c0eea7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs new file mode 100644 index 000000000..889e17cd1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct SpecularPointers + { + private static readonly int rotationHash = Shader.PropertyToID("specularTextureRotation"); + private static readonly int textureHash = Shader.PropertyToID("specularTexture"); + private static readonly int texCoordHash = Shader.PropertyToID("specularTextureTexCoord"); + private static readonly int factorHash = Shader.PropertyToID("specularFactor"); + + public TransformPointers transformPointers; + public Pointer specularFactor; + + public SpecularPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + specularFactor = PointerHelpers.CreateFloatPointer(mat, factorHash); + } + + public static IPointer ProcessPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_specular + return reader.AsReadOnlySpan() switch + { + var a when a.Is("specularFactor") => matPointer.specularPointers.specularFactor, + var a when a.Is("specularColorFactor") => matPointer.specularColorPointers.specularColorFactor, + var a when a.Is("specularTexture") => ProcessExtensionsPointer(reader, matPointer.specularPointers), + var a when a.Is("specularColorTexture") => SpecularColorPointers.ProcessExtensionsPointer(reader, matPointer.specularColorPointers), + + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, SpecularPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_specular/specularTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs.meta new file mode 100644 index 000000000..7e51bf857 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 108dde9128315354495b000e0b34962c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs new file mode 100644 index 000000000..2e0fad757 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs @@ -0,0 +1,46 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct SpecularColorPointers + { + private static readonly int rotationHash = Shader.PropertyToID("specularColorTextureRotation"); + private static readonly int textureHash = Shader.PropertyToID("specularColorTexture"); + private static readonly int texCoordHash = Shader.PropertyToID("specularColorTextureTexCoord"); + private static readonly int colorFactorHash = Shader.PropertyToID("specularColorFactor"); + + public TransformPointers transformPointers; + public Pointer specularColorFactor; + + public SpecularColorPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + specularColorFactor = PointerHelpers.CreateColorRGBPointer(mat, colorFactorHash); + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, SpecularColorPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_specular/specularColorTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs.meta new file mode 100644 index 000000000..b8627cc37 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f1aef4aef0edb54a92d0c0ddec2ea49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs new file mode 100644 index 000000000..2f8515f86 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct ThicknessPointers + { + private static readonly int textureHash = Shader.PropertyToID("thicknessTexture"); + private static readonly int rotationHash = Shader.PropertyToID("thicknessTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("thicknessTextureTexCoord"); + private static readonly int thicknessFactorHash = Shader.PropertyToID("thicknessFactor"); + + public TransformPointers transformPointers; + public Pointer thicknessFactor; + + public ThicknessPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + thicknessFactor = PointerHelpers.CreateFloatPointer(mat, thicknessFactorHash); + } + + public static IPointer ProcessPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_volume + return reader.AsReadOnlySpan() switch + { + var a when a.Is("thicknessFactor") => matPointer.thicknessPointers.thicknessFactor, + var a when a.Is("attenuationDistance") => matPointer.attenuationDistance, + var a when a.Is("attenuationColor") => matPointer.attenuationColor, + var a when a.Is("thicknessTexture") => ProcessExtensionsPointer(reader, matPointer.thicknessPointers), + + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, ThicknessPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_transmission/transmissionTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs.meta new file mode 100644 index 000000000..654fbe4cf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3504e318199009649916416c4ca45228 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs new file mode 100644 index 000000000..578a0f62b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs @@ -0,0 +1,60 @@ +using System; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Materials +{ + public struct TransmissionPointers + { + private static readonly int textureHash = Shader.PropertyToID("transmissionTexture"); + private static readonly int rotationHash = Shader.PropertyToID("transmissionTextureRotation"); + private static readonly int texCoordHash = Shader.PropertyToID("transmissionTextureTexCoord"); + private static readonly int transmissionFactorHash = Shader.PropertyToID("transmissionFactor"); + + public TransformPointers transformPointers; + public Pointer transmissionFactor; + + public TransmissionPointers(Material mat) + { + transformPointers = new TransformPointers() + { + offset = PointerHelpers.CreateOffsetPointer(mat, textureHash), + scale = PointerHelpers.CreateScalePointer(mat, textureHash), + rotation = PointerHelpers.CreateFloatPointer(mat, rotationHash), + texCoord = PointerHelpers.CreateFloatPointer(mat, texCoordHash) + }; + + transmissionFactor = PointerHelpers.CreateFloatPointer(mat, transmissionFactorHash); + } + + public static IPointer ProcessPointer(StringSpanReader reader, MaterialPointers matPointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_transmission + return reader.AsReadOnlySpan() switch + { + var a when a.Is("transmissionFactor") => matPointer.transmissionPointers.transmissionFactor, + var a when a.Is("transmissionTexture") => ProcessExtensionsPointer(reader, matPointer.transmissionPointers), + + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + public static IPointer ProcessExtensionsPointer(StringSpanReader reader, TransmissionPointers pointers) + { + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + reader.AdvanceToNextToken('/'); + + // Path so far: /materials/{}/extensions/KHR_materials_transmission/transmissionTexture/extensions/KHR_texture_transform/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is("offset") => pointers.transformPointers.offset, + var a when a.Is("rotation") => pointers.transformPointers.rotation, + var a when a.Is("scale") => pointers.transformPointers.scale, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs.meta new file mode 100644 index 000000000..b00375294 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1f899d504e73d1745b5c44590bd9a901 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs new file mode 100644 index 000000000..107e54b03 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs @@ -0,0 +1,67 @@ +using GLTF.Schema; +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct MeshPointers + { + public ReadOnlyPointer weightsLength; + public Pointer[] weights; + + public MeshPointers(GLTFMesh mesh) + { + if(mesh.Weights == null || mesh.Weights.Count == 0) + { + weightsLength = new ReadOnlyPointer(() => 0); + weights = new Pointer[0]; + return; + } + + weightsLength = new ReadOnlyPointer(() => mesh.Weights.Count); + weights = new Pointer[mesh.Weights.Count]; + + for (int i = 0; i < weights.Length; i++) + { + weights[i] = new Pointer() + { + setter = (v) => { }, // TODO: Figure this out, Unity does not handle blend shapes like GLTF does so setting it directly on a mesh is difficult. + getter = () => (float)mesh.Weights[i], + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + } + } + + public static IPointer ProcessPointer(StringSpanReader reader, BehaviourEngineNode engineNode, List pointers) + { + reader.AdvanceToNextToken('/'); + + var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + + var pointer = pointers[nodeIndex]; + + reader.AdvanceToNextToken('/'); + + // Path so far: /meshes/{}/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is(Pointers.WEIGHTS) => ProcessWeightsPointer(reader, engineNode, pointer), + var a when a.Is(Pointers.WEIGHTS_LENGTH) => pointer.weightsLength, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessWeightsPointer(StringSpanReader reader, BehaviourEngineNode engineNode, MeshPointers pointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /meshes/{}/weights/ + var weightIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + + return pointer.weights[weightIndex]; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs.meta new file mode 100644 index 000000000..781c18f4a --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6cdb9af3b22c0b4081c6aa1818133e2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs new file mode 100644 index 000000000..74600ac54 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct NodePointers + { + public Pointer translation; + public Pointer rotation; + public Pointer scale; + public Pointer visibility; + public Pointer selectability; + public Pointer hoverability; + public Pointer matrix; + public Pointer globalMatrix; + public ReadOnlyPointer weightsLength; + public Pointer[] weights; + public GameObject gameObject; + + public NodePointers(GameObject go, GLTF.Schema.Node schema) + { + gameObject = go; + + // Unity coordinate system differs from the GLTF one. + // Unity is left-handed with y-up and z-forward. + // GLTF is right-handed with y-up and z-forward. + // Handedness is easiest to swap here though we could do it during deserialization for performance. + translation = new Pointer() + { + setter = (v) => go.transform.localPosition = v.SwapHandedness(), + getter = () => go.transform.localPosition.SwapHandedness(), + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + rotation = new Pointer() + { + setter = (v) => go.transform.localRotation = ((Quaternion)v).SwapHandedness(), + getter = () => go.transform.localRotation.SwapHandedness(), + evaluator = (a, b, t) => math.slerp(a, b, t) + }; + + scale = new Pointer() + { + setter = (v) => go.transform.localScale = v, + getter = () => go.transform.localScale, + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + + matrix = new Pointer() + { + setter = (v) => go.transform.SetWorldMatrix(v, worldSpace: false, rightHanded: true), + getter = () => go.transform.GetWorldMatrix(worldSpace: false, rightHanded: true), + evaluator = (a, b, t) => a.LerpToComponentwise(b, t) // Spec has floatNxN lerp componentwise. + }; + + globalMatrix = new Pointer() + { + setter = (v) => go.transform.SetWorldMatrix(v, worldSpace: true, rightHanded: true), + getter = () => go.transform.GetWorldMatrix(worldSpace: true, rightHanded: true), + evaluator = (a, b, t) => a.LerpToComponentwise(b, t) // Spec has floatNxN lerp componentwise. + }; + + // TODO: Handle visibility pointers better? Do we report the value back to the extension? + // Should we make the extension handle the SetActive call so we just change the value of visibility? + visibility = new Pointer() + { + setter = (v) => go.SetActive(v), + getter = () => go.activeSelf, + evaluator = null + }; + + selectability = GetSelectabilityPointers(schema); + hoverability = GetHoverabilityPointers(schema); + + if(go.TryGetComponent(out SkinnedMeshRenderer smr)) + { + weightsLength = new ReadOnlyPointer(() => smr.sharedMesh.blendShapeCount); + weights = new Pointer[smr.sharedMesh.blendShapeCount]; + + for (int i = 0; i < weights.Length; i++) + { + weights[i] = new Pointer() + { + setter = (v) => smr.SetBlendShapeWeight(i, v), + getter = () => smr.GetBlendShapeWeight(i), + evaluator = (a, b, t) => math.lerp(a, b, t) + }; + } + } + else + { + weightsLength = default; + weights = default; + } + } + + private static Pointer GetSelectabilityPointers(GLTF.Schema.Node schema) + { + Pointer selectability; + + if (schema.Extensions != null && schema.Extensions.TryGetValue(GLTF.Schema.KHR_node_selectability_Factory.EXTENSION_NAME, out var extension)) + { + var selectabilityExtension = extension as GLTF.Schema.KHR_node_selectability; + + selectability = new Pointer() + { + setter = (v) => selectabilityExtension.selectable = v, + getter = () => selectabilityExtension.selectable, + evaluator = null + }; + } + else + { + selectability = new Pointer() + { + setter = (v) => { }, + getter = () => true, + evaluator = null + }; + } + + return selectability; + } + + private static Pointer GetHoverabilityPointers(GLTF.Schema.Node schema) + { + Pointer hoverability; + + if (schema.Extensions != null && schema.Extensions.TryGetValue(GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME, out var extension)) + { + var hoverabilityExtension = extension as GLTF.Schema.KHR_node_hoverability; + + hoverability = new Pointer() + { + setter = (v) => hoverabilityExtension.hoverable = v, + getter = () => hoverabilityExtension.hoverable, + evaluator = null + }; + } + else + { + hoverability = new Pointer() + { + setter = (v) => { }, + getter = () => true, + evaluator = null + }; + } + + return hoverability; + } + + public static IPointer ProcessNodePointer(StringSpanReader reader, BehaviourEngineNode engineNode, List pointers) + { + reader.AdvanceToNextToken('/'); + + var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + + var nodePointer = pointers[nodeIndex]; + + reader.AdvanceToNextToken('/'); + + // Path so far: /nodes/{}/ + return reader.AsReadOnlySpan() switch + { + var a when a.Is(Pointers.TRANSLATION) => nodePointer.translation, + var a when a.Is(Pointers.ROTATION) => nodePointer.rotation, + var a when a.Is(Pointers.SCALE) => nodePointer.scale, + var a when a.Is(Pointers.WEIGHTS) => ProcessWeightsPointer(reader, engineNode, nodePointer), + var a when a.Is(Pointers.WEIGHTS_LENGTH) => nodePointer.weightsLength, + var a when a.Is(Pointers.EXTENSIONS) => ProcessExtensionPointer(reader, nodePointer), + var a when a.Is(Pointers.MATRIX) => nodePointer.matrix, + var a when a.Is(Pointers.GLOBAL_MATRIX) => nodePointer.globalMatrix, + _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessExtensionPointer(StringSpanReader reader, NodePointers nodePointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /nodes/{}/extensions/ + return reader.AsReadOnlySpan() switch + { + // TODO: Handle these properly via extensions in UnityGLTF? + var a when a.Is(GLTF.Schema.KHR_node_selectability_Factory.EXTENSION_NAME) => nodePointer.selectability, + var a when a.Is(GLTF.Schema.KHR_node_visibility_Factory.EXTENSION_NAME) => nodePointer.visibility, + var a when a.Is(GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME) => nodePointer.hoverability, + _ => throw new InvalidOperationException($"Extension {reader.ToString()} is unsupported at this time!"), + }; + } + + private static IPointer ProcessWeightsPointer(StringSpanReader reader, BehaviourEngineNode engineNode, NodePointers pointer) + { + reader.AdvanceToNextToken('/'); + + // Path so far: /nodes/{}/weights/ + var weightIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + + return pointer.weights[weightIndex]; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs.meta new file mode 100644 index 000000000..fe855b1cd --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ecfe1a904c44ec47a51d16f22807931 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs new file mode 100644 index 000000000..1764e520d --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; +using UnityGLTF.Interactivity.Playback.Materials; + +namespace UnityGLTF.Interactivity.Playback +{ + public class PointerResolver + { + private readonly List _nodePointers = new(); + private readonly List _materialPointers = new(); + private readonly List _cameraPointers = new(); + private readonly List _animationPointers = new(); + private readonly List _meshPointers = new(); + private readonly ScenePointers _scenePointers; + private readonly ActiveCameraPointers _activeCameraPointers = ActiveCameraPointers.CreatePointers(); + + public ReadOnlyCollection nodePointers { get; private set; } + + public PointerResolver(GLTFSceneImporter importer) + { + RegisterNodes(importer); + RegisterMaterials(importer); + RegisterMeshes(importer); + + _scenePointers = new ScenePointers(importer); + } + + private void RegisterMeshes(GLTFSceneImporter importer) + { + for (int i = 0; i < importer.Root.Meshes.Count; i++) + { + _meshPointers.Add(new MeshPointers(importer.Root.Meshes[i])); + } + } + + public void RegisterAnimations(AnimationWrapper wrapper) + { + for (int i = 0; i < wrapper.animationComponent.GetClipCount(); i++) + { + _animationPointers.Add(new AnimationPointers(wrapper, i)); + } + } + + private void RegisterNodes(GLTFSceneImporter importer) + { + var nodeSchemas = importer.Root.Nodes; + var nodeGameObjects = importer.NodeCache; + + for (int i = 0; i < nodeGameObjects.Length; i++) + { + Util.Log($"Registered Node Pointer {i}", nodeGameObjects[i]); + _nodePointers.Add(new NodePointers(nodeGameObjects[i], nodeSchemas[i])); + + if (nodeGameObjects[i].TryGetComponent(out Camera cam)) + { + Util.Log($"Registered Camera", nodeGameObjects[i]); + _cameraPointers.Add(new CameraPointers(cam)); + } + } + + nodePointers = new(_nodePointers); + } + + private void RegisterMaterials(GLTFSceneImporter importer) + { + var materials = importer.MaterialCache; + for (int i = 0; i < materials.Length; i++) + { + _materialPointers.Add(new MaterialPointers(materials[i].UnityMaterialWithVertexColor)); + } + } + + public bool TryGetPointersOf(GameObject go, out NodePointers pointers) + { + pointers = default; + + for (int i = 0; i < _nodePointers.Count; i++) + { + if (_nodePointers[i].gameObject == go) + { + pointers = _nodePointers[i]; + return true; + } + } + + Debug.LogWarning($"No node pointers found for {go.name}!"); + return false; + } + + public int IndexOf(GameObject go) + { + for (int i = 0; i < _nodePointers.Count; i++) + { + if (_nodePointers[i].gameObject == go) + return i; + } + + return -1; + } + + public IPointer GetPointer(string pointerString, BehaviourEngineNode engineNode) + { + Util.Log($"Getting pointer: {pointerString}"); + + var reader = new StringSpanReader(pointerString); + + reader.Slice('/', '/'); + + return reader.AsReadOnlySpan() switch + { + var a when a.Is("nodes") => NodePointers.ProcessNodePointer(reader, engineNode, _nodePointers), + var a when a.Is("materials") => MaterialPointers.ProcessMaterialPointer(reader, engineNode, _materialPointers), + var a when a.Is("activeCamera") => _activeCameraPointers.ProcessActiveCameraPointer(reader), + var a when a.Is("cameras") => CameraPointers.ProcessCameraPointer(reader, engineNode, _cameraPointers), + var a when a.Is("meshes") => MeshPointers.ProcessPointer(reader, engineNode, _meshPointers), + var a when a.Is("animations") => AnimationPointers.ProcessPointer(reader, engineNode, _animationPointers), + var a when a.Is(Pointers.ANIMATIONS_LENGTH) => _scenePointers.animationsLength, + var a when a.Is(Pointers.MATERIALS_LENGTH) => _scenePointers.materialsLength, + var a when a.Is(Pointers.MESHES_LENGTH) => _scenePointers.meshesLength, + var a when a.Is(Pointers.NODES_LENGTH) => _scenePointers.nodesLength, + _ => throw new InvalidOperationException($"No valid pointer found with name {reader.ToString()}"), + }; + } + + public static int GetIndexFromArgument(StringSpanReader reader, BehaviourEngineNode engineNode) + { + int nodeIndex; + + if (reader[0] == '{') + { + reader.Slice('{', '}'); + // Can't access the values dictionary with a Span, prevents this from being 0 allocation. + var property = (Property)engineNode.engine.ParseValue(engineNode.values[reader.ToString()]); + nodeIndex = property.value; + } + else + { + nodeIndex = int.Parse(reader.AsReadOnlySpan()); + } + + return nodeIndex; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs.meta new file mode 100644 index 000000000..ef54f7f45 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9098811b9313b40458068fb78f816944 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs new file mode 100644 index 000000000..b3ad6bd60 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs @@ -0,0 +1,74 @@ +using System; + +namespace UnityGLTF.Interactivity.Playback +{ + public interface IPointer + { + public Type GetSystemType(); + public string GetTypeSignature(); + } + + public interface IPointer : IPointer + { + public T GetValue(); + } + public interface IReadOnlyPointer : IPointer + { + } + + public interface IReadOnlyPointer : IReadOnlyPointer + { + } + + public struct ReadOnlyPointer : IReadOnlyPointer + { + public Func getter; + + public ReadOnlyPointer(Func getter) + { + this.getter = getter; + } + + public T GetValue() + { + return getter(); + } + + public static implicit operator ReadOnlyPointer(Pointer pointer) + { + return new ReadOnlyPointer() { getter = pointer.getter }; + } + + public Type GetSystemType() + { + return typeof(T); + } + + public string GetTypeSignature() + { + return Helpers.GetSignatureBySystemType(typeof(T)); + } + } + + public struct Pointer : IPointer + { + public Action setter; + public Func getter; + public Func evaluator; + + public T GetValue() + { + return getter(); + } + + public Type GetSystemType() + { + return typeof(T); + } + + public string GetTypeSignature() + { + return Helpers.GetSignatureBySystemType(typeof(T)); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs.meta new file mode 100644 index 000000000..912a35482 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: acae5ae0eaff77045bfecb93ee738576 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs new file mode 100644 index 000000000..c4e2bafc6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs @@ -0,0 +1,24 @@ +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct ScenePointers + { + public ReadOnlyPointer animationsLength; + public ReadOnlyPointer camerasLength; + public ReadOnlyPointer materialsLength; + public ReadOnlyPointer meshesLength; + public ReadOnlyPointer nodesLength; + public ReadOnlyPointer scenesLength; + + public ScenePointers(GLTFSceneImporter importer) + { + animationsLength = new ReadOnlyPointer(() => importer.Root.Animations.Count); + camerasLength = new ReadOnlyPointer(() => importer.Root.Cameras.Count); + materialsLength = new ReadOnlyPointer(() => importer.Root.Materials.Count); + meshesLength = new ReadOnlyPointer(() => importer.Root.Meshes.Count); + nodesLength = new ReadOnlyPointer(() => importer.Root.Nodes.Count); + scenesLength = new ReadOnlyPointer(() => importer.Root.Scenes.Count); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs.meta new file mode 100644 index 000000000..6892f9d39 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f76af4efa69cb1849a13108461d33a61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef b/Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef new file mode 100644 index 000000000..87a28d7ed --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef @@ -0,0 +1,24 @@ +{ + "name": "UnityGLTF.Interactivity.Playback", + "rootNamespace": "", + "references": [ + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:18d18f811ba286c49814567a3cfba688", + "GUID:2bafac87e7f4b9b418d9448d219b01ab", + "GUID:6055be8ebefd69e48b49212b09b47b2f", + "GUID:b5e009300cbc0584fa5c0fa797ef4c5f", + "GUID:d58a4b8a1f92baa4f8100941944470f7", + "GUID:40f39bff7bc9be34182ebe488fcf8228", + "GUID:68550284b645f4b9894995579f34290a", + "GUID:dd1f961792bcc4bf8956b57bbb54bd60" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef.meta b/Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef.meta new file mode 100644 index 000000000..c2b419f6b --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 044607fd7a2ec4483a9d4a82c8ab78a7 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs b/Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs new file mode 100644 index 000000000..09e2620ac --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Pool; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback +{ + public struct VariableInterpolateData + { + public Variable variable; + public float startTime; + public float duration; + public IProperty endValue; + public float2 cp1; + public float2 cp2; + public Action done; + public IInterpolator interpolator; + public bool slerp; + } + + public class VariableInterpolationManager + { + private struct Interpolator : IInterpolator + { + public Variable variable; + public Func> evaluator; + public T from; + public T to; + + public bool Interpolate(float t) + { + var end = t >= 1f; + + t = end ? 1f : t; + + variable.property = evaluator(from, to, t); + + return end; + } + } + + private readonly Dictionary _interpolationsInProgress = new(); + + public void OnTick() + { + // Avoiding iterating over a changing collection by grabbing a pooled dictionary. + var temp = DictionaryPool.Get(); + try + { + foreach (var interp in _interpolationsInProgress) + { + temp.Add(interp.Key, interp.Value); + } + + foreach (var anim in temp) + { + DoInterpolate(anim.Value); + } + } + finally + { + DictionaryPool.Release(temp); + } + } + + private void DoInterpolate(VariableInterpolateData data) + { + var t = (Time.time - data.startTime) / data.duration; + + var finished = data.interpolator.Interpolate(t); + + if (finished) + { + Util.Log($"Finished Variable interpolate."); + + _interpolationsInProgress.Remove(data.variable); + data.done(); + } + } + + public void StartInterpolation(ref VariableInterpolateData data) + { + _interpolationsInProgress.Remove(data.variable); // Stop any in-progress interpolations for this variable. + + var interpolator = GetInterpolator(data); + + data.interpolator = interpolator; + + _interpolationsInProgress.Add(data.variable, data); + + Util.Log($"Starting Variable Interpolation: Start Time {data.startTime}, Duration: {data.duration}"); + } + + public bool StopInterpolation(Variable variable) + { + return _interpolationsInProgress.Remove(variable); + } + + private IInterpolator GetInterpolator(in VariableInterpolateData data) + { + var cp1 = data.cp1; + var cp2 = data.cp2; + + var interpolator = data.variable.property switch + { + Property => GetInterpolator(GetFloatEvaluator(cp1,cp2), data), + Property => GetInterpolator(Getfloat2Evaluator(cp1, cp2), data), + Property => GetInterpolator(Getfloat3Evaluator(cp1, cp2), data), + Property when data.slerp => GetInterpolator(GetquaternionEvaluator(cp1, cp2), data), + Property when !data.slerp=> GetInterpolator(Getfloat4Evaluator(cp1, cp2), data), + Property => GetInterpolator(Getfloat2x2Evaluator(cp1, cp2), data), + Property => GetInterpolator(Getfloat3x3Evaluator(cp1, cp2), data), + Property => GetInterpolator(Getfloat4x4Evaluator(cp1, cp2), data), + + _ => throw new NotImplementedException($"Interpolation has not been defined for type {data.variable.property.GetTypeSignature()}!"), + }; + + return interpolator; + } + + private IInterpolator GetInterpolator(Func> evaluator, in VariableInterpolateData data) + { + var variable = data.variable; + var endValue = (Property)data.endValue; + + return new Interpolator() + { + variable = data.variable, + evaluator = evaluator, + from = ((Property)variable.property).value, + to = endValue.value + }; + } + + private Func> GetFloatEvaluator(float2 cp1, float2 cp2) + { + return (a, b, t) => new Property(math.lerp(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + + private Func> Getfloat2Evaluator(float2 cp1, float2 cp2) + { + return (a, b, t) => new Property(math.lerp(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + + private Func> Getfloat3Evaluator(float2 cp1, float2 cp2) + { + return (a, b, t) => new Property(math.lerp(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + + private Func> Getfloat4Evaluator(float2 cp1, float2 cp2) + { + return (a, b, t) => new Property(math.lerp(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + + private Func> GetquaternionEvaluator(float2 cp1, float2 cp2) + { + // Just a copy from unity mathematics library to avoid a bunch of type conversions. + return (a, b, t) => new Property(Helpers.Slerpfloat4(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + + private Func> Getfloat2x2Evaluator(float2 cp1, float2 cp2) + { + return (a, b, t) => new Property(Helpers.LerpComponentwise(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + + private Func> Getfloat3x3Evaluator(float2 cp1, float2 cp2) + { + return (a, b, t) => new Property(Helpers.LerpComponentwise(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + + private Func> Getfloat4x4Evaluator(float2 cp1, float2 cp2) + { + return (a, b, t) => new Property(Helpers.LerpComponentwise(a, b, Helpers.CubicBezier(t, cp1, cp2).y)); + } + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs.meta b/Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs.meta new file mode 100644 index 000000000..ac792d071 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/VariableInterpolationManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3958318fac7902f4484a72b3e2062c6c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Loader/ResourcesLoader.cs b/Runtime/Scripts/Loader/ResourcesLoader.cs new file mode 100644 index 000000000..82142325a --- /dev/null +++ b/Runtime/Scripts/Loader/ResourcesLoader.cs @@ -0,0 +1,23 @@ +using System.IO; +using System.Threading.Tasks; +using UnityEngine; + +namespace UnityGLTF.Loader +{ + public class ResourcesLoader : IDataLoader + { + public ResourcesLoader() { } + + + + public async Task LoadStreamAsync(string relativeFilePath) + { + var handle = Resources.LoadAsync(relativeFilePath); + + while (!handle.isDone) + await Task.Yield(); + + return new MemoryStream((handle.asset as TextAsset).bytes); + } + } +} diff --git a/Runtime/Scripts/Loader/ResourcesLoader.cs.meta b/Runtime/Scripts/Loader/ResourcesLoader.cs.meta new file mode 100644 index 000000000..0d78e8d94 --- /dev/null +++ b/Runtime/Scripts/Loader/ResourcesLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2cf21c1afc67d9c449b58bcf011f4a03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/Interactivity/UnityGLTF.Interactivity.Tests.Editor.asmdef b/Tests/Editor/Interactivity/UnityGLTF.Interactivity.Tests.Editor.asmdef index 1c5eea1ab..d49dff91a 100644 --- a/Tests/Editor/Interactivity/UnityGLTF.Interactivity.Tests.Editor.asmdef +++ b/Tests/Editor/Interactivity/UnityGLTF.Interactivity.Tests.Editor.asmdef @@ -3,7 +3,11 @@ "rootNamespace": "", "references": [ "UnityEngine.TestRunner", - "UnityEditor.TestRunner" + "UnityEditor.TestRunner", + "Unity.VisualScripting.Core", + "Unity.VisualScripting.Core.Editor", + "Unity.VisualScripting.Flow", + "Unity.VisualScripting.Flow.Editor" ], "includePlatforms": [ "Editor" diff --git a/Tests/Runtime/Interactivity.meta b/Tests/Runtime/Interactivity.meta new file mode 100644 index 000000000..1869d542b --- /dev/null +++ b/Tests/Runtime/Interactivity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 51a9d193fd5c7574fbddfaf244a2a241 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Common.meta b/Tests/Runtime/Interactivity/Common.meta new file mode 100644 index 000000000..5f7db686c --- /dev/null +++ b/Tests/Runtime/Interactivity/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ebbe45fff4974714387aefbe9715e4ed +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Common/MatrixTests.cs b/Tests/Runtime/Interactivity/Common/MatrixTests.cs new file mode 100644 index 000000000..f169d4b6c --- /dev/null +++ b/Tests/Runtime/Interactivity/Common/MatrixTests.cs @@ -0,0 +1,150 @@ +using NUnit.Framework; +using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public class MatrixTests + { + private Transform parent; + private Transform t, t2; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + parent = new GameObject("Parent").transform; + parent.position = new Vector3(1.5f, 2.5f, 3f); + parent.rotation = Quaternion.Euler(new Vector3(30f, 60f, 115f)); + parent.localScale = new Vector3(0.5f, 2f, 3f); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + GameObject.Destroy(parent.gameObject); + } + + [SetUp] + public void SetUp() + { + t = new GameObject("Test").transform; + t.SetParent(parent); + t.localPosition = new Vector3(5f, 6f, 7f); + t.localRotation = Quaternion.Euler(new Vector3(15f, 25f, 55f)); + t.localScale = new Vector3(1.5f, 2.5f, 4f); + + t2 = new GameObject("Test 2").transform; + t2.SetParent(parent); + t2.localPosition = new Vector3(15f, 16f, 17f); + t2.localRotation = Quaternion.Euler(new Vector3(115f, 125f, 155f)); + t2.localScale = new Vector3(0.1f, 0.2f, 0.3f); + } + + [TearDown] + public void TearDown() + { + GameObject.Destroy(t.gameObject); + GameObject.Destroy(t2.gameObject); + } + + [Test] + public void LocalMatrix() + { + var matrix = t.GetWorldMatrix(worldSpace: false, rightHanded: false); + + t2.SetWorldMatrix(matrix, worldSpace: false, rightHanded: false); + + Debug.Log($"Positions: A {t.localPosition}, B {t2.localPosition}"); + Debug.Log($"Rotations: A {t.localRotation.eulerAngles}, B {t2.localRotation.eulerAngles}"); + Debug.Log($"Scales: A {t.localScale}, B {t2.localScale}"); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localPosition.x, t2.localPosition.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localPosition.y, t2.localPosition.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localPosition.z, t2.localPosition.z, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.x, t2.localRotation.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.y, t2.localRotation.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.z, t2.localRotation.z, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.w, t2.localRotation.w, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localScale.x, t2.localScale.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localScale.y, t2.localScale.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localScale.z, t2.localScale.z, 0.01f); + } + + [Test] + public void WorldMatrix() + { + var matrix = t.GetWorldMatrix(worldSpace: true, rightHanded: false); + + t2.SetWorldMatrix(matrix, worldSpace: true, rightHanded: false); + + Debug.Log($"Positions: A {t.position}, B {t2.position}"); + Debug.Log($"Rotations: A {t.rotation.eulerAngles}, B {t2.rotation.eulerAngles}"); + Debug.Log($"Scales: A {t.lossyScale}, B {t2.lossyScale}"); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.position.x, t2.position.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.position.y, t2.position.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.position.z, t2.position.z, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.x, t2.rotation.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.y, t2.rotation.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.z, t2.rotation.z, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.w, t2.rotation.w, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.lossyScale.x, t2.lossyScale.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.lossyScale.y, t2.lossyScale.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.lossyScale.z, t2.lossyScale.z, 0.01f); + } + + [Test] + public void LocalMatrixRightHandedConversion() + { + var matrix = t.GetWorldMatrix(worldSpace: false, rightHanded: true); + + t2.SetWorldMatrix(matrix, worldSpace: false, rightHanded: true); + + Debug.Log($"Positions: A {t.localPosition}, B {t2.localPosition}"); + Debug.Log($"Rotations: A {t.localRotation.eulerAngles}, B {t2.localRotation.eulerAngles}"); + Debug.Log($"Scales: A {t.localScale}, B {t2.localScale}"); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localPosition.x, t2.localPosition.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localPosition.y, t2.localPosition.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localPosition.z, t2.localPosition.z, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.x, t2.localRotation.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.y, t2.localRotation.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.z, t2.localRotation.z, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localRotation.w, t2.localRotation.w, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localScale.x, t2.localScale.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localScale.y, t2.localScale.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.localScale.z, t2.localScale.z, 0.01f); + } + + [Test] + public void WorldMatrixRightHandedConversion() + { + var matrix = t.GetWorldMatrix(worldSpace: true, rightHanded: true); + + t2.SetWorldMatrix(matrix, worldSpace: true, rightHanded: true); + + Debug.Log($"Positions: A {t.position}, B {t2.position}"); + Debug.Log($"Rotations: A {t.rotation.eulerAngles}, B {t2.rotation.eulerAngles}"); + Debug.Log($"Scales: A {t.lossyScale}, B {t2.lossyScale}"); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.position.x, t2.position.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.position.y, t2.position.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.position.z, t2.position.z, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.x, t2.rotation.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.y, t2.rotation.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.z, t2.rotation.z, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.rotation.w, t2.rotation.w, 0.01f); + + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.lossyScale.x, t2.lossyScale.x, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.lossyScale.y, t2.lossyScale.y, 0.01f); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(t.lossyScale.z, t2.lossyScale.z, 0.01f); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Common/MatrixTests.cs.meta b/Tests/Runtime/Interactivity/Common/MatrixTests.cs.meta new file mode 100644 index 000000000..9e872c378 --- /dev/null +++ b/Tests/Runtime/Interactivity/Common/MatrixTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee09657334efbe44a9858f25b03d7741 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Common/ParserTests.cs b/Tests/Runtime/Interactivity/Common/ParserTests.cs new file mode 100644 index 000000000..3f08999d6 --- /dev/null +++ b/Tests/Runtime/Interactivity/Common/ParserTests.cs @@ -0,0 +1,116 @@ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public class ParserTests + { + [Test] + public void ParserTestInt() + { + var expected = 5; + var array = new JArray(); + array.Add(expected); + + var parsed = Parser.ToInt(array); + + Assert.AreEqual(expected, parsed); + } + + [Test] + public void ParserTestFloat() + { + var expected = 5.345f; + var array = new JArray(); + array.Add(expected); + + var parsed = Parser.ToFloat(array); + + Assert.AreEqual(expected, parsed); + } + + [Test] + public void ParserTestVector2() + { + var expected = new float2(1f, 2f); + var array = new JArray(); + array.Add(expected.x); + array.Add(expected.y); + + var parsed = Parser.ToFloat2(array); + + Assert.AreEqual(expected, parsed); + } + + [Test] + public void ParserTestVector3() + { + var expected = new float3(1f, 2f, 3f); + var array = new JArray(); + array.Add(expected.x); + array.Add(expected.y); + array.Add(expected.z); + + var parsed = Parser.ToFloat3(array); + + Assert.AreEqual(expected, parsed); + } + + [Test] + public void ParserTestVector4() + { + var expected = new float4(1f, 2f, 3f, 4f); + var array = new JArray(); + array.Add(expected.x); + array.Add(expected.y); + array.Add(expected.z); + array.Add(expected.w); + + var parsed = Parser.ToFloat4(array); + + Assert.AreEqual(expected, parsed); + } + + [Test] + public void ParserTestIntArray() + { + var expected = new int[] { 1, 3, 2, 5, 4 }; + + var array = new JArray(); + array.Add(expected[0]); + array.Add(expected[1]); + array.Add(expected[2]); + array.Add(expected[3]); + array.Add(expected[4]); + + var parsed = Parser.ToIntArray(array); + + Assert.AreEqual(expected, parsed); + } + + [Test] + public void ParserTestBool() + { + var expected = true; + var array = new JArray(); + array.Add(expected); + + var parsed = Parser.ToBool(array); + + Assert.AreEqual(expected, parsed); + } + + [Test] + public void ParserTestString() + { + var expected = "/nodes/{nodeIndex}/extensions/KHR_node_selectability/selectable"; + var array = new JArray(); + array.Add(expected); + + var parsed = Parser.ToString(array); + + Assert.AreEqual(expected, parsed); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Common/ParserTests.cs.meta b/Tests/Runtime/Interactivity/Common/ParserTests.cs.meta new file mode 100644 index 000000000..0e31e83a8 --- /dev/null +++ b/Tests/Runtime/Interactivity/Common/ParserTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6beca67c358e8843829f5bca0160573 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef b/Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef new file mode 100644 index 000000000..db16ed82e --- /dev/null +++ b/Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef @@ -0,0 +1,26 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef.meta b/Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef.meta new file mode 100644 index 000000000..ad71790b8 --- /dev/null +++ b/Tests/Runtime/Interactivity/Common/UnityGLTF.Interactivity.Playback.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f9bc29d9b72ee524691a2b0adacf74b5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes.meta b/Tests/Runtime/Interactivity/Nodes.meta new file mode 100644 index 000000000..7f6c6fe43 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 63a618e2973120244b05bd35d925cdab +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Animation.meta b/Tests/Runtime/Interactivity/Nodes/Animation.meta new file mode 100644 index 000000000..0dd49ff89 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce0929b5f567f9e46b18470558094468 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs b/Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs new file mode 100644 index 000000000..608f21009 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs @@ -0,0 +1,398 @@ +using System.Collections; +using System.Collections.Generic; +using Unity.Mathematics; +using UnityEngine.TestTools; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public class AnimationNodeTests : NodeTestHelpers + { + private const string TEST_GLB = "animation_test"; + protected override string _subDirectory => "Animation"; + + [UnityTest] + public IEnumerator AnimationStart_Basic() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateAnimationStartGraph(); + QueueTest("animation/start", GetCallerName(), "Animation Start Basic", "Activates animation/start node with valid values. Test fails if out or done flows fail to activate or if err flow activates.", g, importer.Result); + } + + [UnityTest] + public IEnumerator AnimationStart_ObjectPositionEndsWhereItShould() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateAnimationStartCheckEndPositionGraph(); + QueueTest("animation/start", GetCallerName(), "Animation Start Test Object Position", "Activates animation/start node with valid values. Test fails if object position is not where it should be when the done flow activates.", g, importer.Result); + } + + [UnityTest] + public IEnumerator AnimationStop_Basic() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateAnimationStopGraph(); + QueueTest("animation/stop", GetCallerName(), "Animation Stop Basic", "Activates animation/start node with valid values and triggers animation/stop midway through the animation. Test fails if done flow activates or err flow activates on stop node.", g, importer.Result); + } + + [UnityTest] + public IEnumerator AnimationStop_InvalidInputValues() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = GenerateAnimationInvalidInputTestGraph("animation/stop", + new Dictionary() { ["animation"] = new Property(-1) }, + new Dictionary() { ["animation"] = new Property(9999) } + ); + + QueueTest("animation/stop", GetCallerName(), "Animation Stop Invalid Input Values", "Test fails if out flow is activated for any incorrect input or if the err flow fails to trigger for all invalid inputs.", g, importer.Result); + } + + [UnityTest] + public IEnumerator AnimationStopAt_Basic() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateAnimationStopAtGraph(); + QueueTest("animation/stopAt", GetCallerName(), "Animation StopAt Basic", "Activates animation/start node with valid values and triggers animation/stopAt in its out flow which stops the animation after a short delay. Test fails if done flow activates on the start node, the err flow activates on the stop node, or the out/done flows do not activate on the stopAt node.", g, importer.Result); + } + + [UnityTest] + public IEnumerator AnimationStopAt_InvalidInputValues() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = GenerateAnimationInvalidInputTestGraph("animation/stopAt", + new Dictionary() { ["animation"] = new Property(-1), ["stopTime"] = new Property(0.5f) }, + new Dictionary() { ["animation"] = new Property(9999), ["stopTime"] = new Property(0.5f) }, + new Dictionary() { ["animation"] = new Property(0), ["stopTime"] = new Property(float.NaN) } + ); + + QueueTest("animation/stopAt", GetCallerName(), "Animation StopAt Invalid Input Values", "Test fails if out flow is activated for any incorrect input or if the err flow fails to trigger for all invalid inputs.", g, importer.Result); + } + + + + [UnityTest] + public IEnumerator AnimationStart_InvalidInputValues() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var negativeAnimation = GetDefaultAnimationStartValues();negativeAnimation["animation"] = new Property(-1); + var outOfRangeAnimation = GetDefaultAnimationStartValues(); outOfRangeAnimation["animation"] = new Property(9999); + var startTimeNaN = GetDefaultAnimationStartValues(); startTimeNaN["startTime"] = new Property(float.NaN); + var startTimeInf = GetDefaultAnimationStartValues(); startTimeInf["startTime"] = new Property(float.PositiveInfinity); + var endTimeNaN = GetDefaultAnimationStartValues(); endTimeNaN["endTime"] = new Property(float.NaN); + var speedNaN = GetDefaultAnimationStartValues(); speedNaN["speed"] = new Property(float.NaN); + var speedInf = GetDefaultAnimationStartValues(); speedInf["speed"] = new Property(float.PositiveInfinity); + var speedLEZero = GetDefaultAnimationStartValues(); speedLEZero["speed"] = new Property(-1f); + + var g = GenerateAnimationInvalidInputTestGraph("animation/start", + negativeAnimation, + outOfRangeAnimation, + startTimeNaN, + startTimeInf, + endTimeNaN, + speedNaN, + speedInf, + speedLEZero); + QueueTest("animation/start", GetCallerName(), "Animation Start Invalid Input Values", "Test fails if out or done flow is activated for any incorrect input or if the err flow fails to trigger for all invalid inputs.", g, importer.Result); + } + + static Dictionary GetDefaultAnimationStartValues() + { + return new Dictionary() + { + ["animation"] = new Property(0), + ["startTime"] = new Property(0f), + ["endTime"] = new Property(0.7f), + ["speed"] = new Property(1f), + }; + } + + private static Graph CreateAnimationStartGraph() + { + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var animation = g.CreateNode("animation/start"); + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + onStartNode.AddFlow(animation); + + var values = GetDefaultAnimationStartValues(); + + foreach(var value in values) + { + animation.AddValue(value.Key, value.Value); + } + + var failErr = CreateFailSubGraph(g, "Err flow should not activate in this test."); + + var outSet = g.CreateNode("variable/set"); + var outGet = g.CreateNode("variable/get"); + var outVar = g.AddVariable("outFlowActivated", false); + var outVarIndex = g.IndexOfVariable(outVar); + var outBranch = g.CreateNode("flow/branch"); + var outFail = CreateFailSubGraph(g, "Out flow did not trigger during this test."); + + outSet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outGet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outSet.AddValue(ConstStrings.VALUE, true); + + outBranch.AddConnectedValue(ConstStrings.CONDITION, outGet); + outBranch.AddFlow(complete, ConstStrings.TRUE); + outBranch.AddFlow(outFail, ConstStrings.FALSE); + + animation.AddFlow(failErr, ConstStrings.ERR); + animation.AddFlow(outSet, ConstStrings.OUT); + animation.AddFlow(outBranch, ConstStrings.DONE); + + return g; + } + + private static Graph CreateAnimationStartCheckEndPositionGraph() + { + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var animation = g.CreateNode("animation/start"); + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + onStartNode.AddFlow(animation); + + var values = GetDefaultAnimationStartValues(); + + foreach (var value in values) + { + animation.AddValue(value.Key, value.Value); + } + + var get = g.CreateNode("pointer/get"); + get.AddConfiguration(ConstStrings.TYPE, g.IndexOfType("float3")); + get.AddConfiguration(ConstStrings.POINTER, "/nodes/0/translation"); + + var eq = g.CreateNode("math/eq"); + eq.AddValue(ConstStrings.A, float3.zero); + eq.AddConnectedValue(ConstStrings.B, get); + + var branch = g.CreateNode("flow/branch"); + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + + var failErr = CreateFailSubGraph(g, "Object position is not where it should be. Expected: {expected}, Actual: {actual}"); + failErr.AddValue(ConstStrings.EXPECTED, float3.zero); + failErr.AddConnectedValue(ConstStrings.ACTUAL, get); + + branch.AddFlow(failErr, ConstStrings.FALSE); + branch.AddFlow(complete, ConstStrings.TRUE); + + animation.AddFlow(branch, ConstStrings.DONE); + + return g; + } + + private static Graph CreateAnimationStopGraph() + { + const float TEST_DURATION = 1f; + const float STOP_TIME = 0.4f; + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var animation = g.CreateNode("animation/start"); + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + onStartNode.AddFlow(animation); + + var values = GetDefaultAnimationStartValues(); + + foreach (var value in values) + { + animation.AddValue(value.Key, value.Value); + } + + var failErr = CreateFailSubGraph(g, "Err flow should not activate on stop node."); + var failDone = CreateFailSubGraph(g, "Done flow should not activate on start node in this test."); + animation.AddFlow(failDone, ConstStrings.DONE); + + var onTick = g.CreateNode("event/onTick"); + var ge = g.CreateNode("math/ge"); + var timeBranch = g.CreateNode("flow/branch"); + var stop = g.CreateNode("animation/stop"); + stop.AddValue(ConstStrings.ANIMATION, values[ConstStrings.ANIMATION]); + onTick.AddFlow(timeBranch); + + ge.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + ge.AddValue(ConstStrings.B, STOP_TIME); + + timeBranch.AddConnectedValue(ConstStrings.CONDITION, ge); + timeBranch.AddFlow(stop, ConstStrings.TRUE); + + var donege = g.CreateNode("math/ge"); + var doneBranch = g.CreateNode("flow/branch"); + var onTick2 = g.CreateNode("event/onTick"); + onTick2.AddFlow(doneBranch); + doneBranch.AddConnectedValue(ConstStrings.CONDITION, donege); + + donege.AddConnectedValue(ConstStrings.A, onTick2, ConstStrings.TIME_SINCE_START); + donege.AddValue(ConstStrings.B, TEST_DURATION); + + var outSet = g.CreateNode("variable/set"); + var outGet = g.CreateNode("variable/get"); + var outVar = g.AddVariable("outFlowActivated", false); + var outVarIndex = g.IndexOfVariable(outVar); + var outBranch = g.CreateNode("flow/branch"); + var outFail = CreateFailSubGraph(g, "Out flow did not trigger during this test."); + + outSet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outGet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outSet.AddValue(ConstStrings.VALUE, true); + + outBranch.AddConnectedValue(ConstStrings.CONDITION, outGet); + outBranch.AddFlow(complete, ConstStrings.TRUE); + outBranch.AddFlow(outFail, ConstStrings.FALSE); + + stop.AddFlow(failErr, ConstStrings.ERR); + stop.AddFlow(outSet, ConstStrings.OUT); + + doneBranch.AddFlow(outBranch, ConstStrings.TRUE); + + return g; + } + + private static Graph CreateAnimationStopAtGraph() + { + const float STOP_TIME = 0.4f; + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var animation = g.CreateNode("animation/start"); + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + onStartNode.AddFlow(animation); + + var values = GetDefaultAnimationStartValues(); + + foreach (var value in values) + { + animation.AddValue(value.Key, value.Value); + } + + var failErr = CreateFailSubGraph(g, "Err flow should not activate on stop node."); + var failDone = CreateFailSubGraph(g, "Done flow should not activate on start node in this test."); + animation.AddFlow(failDone, ConstStrings.DONE); + + var stop = g.CreateNode("animation/stopAt"); + stop.AddValue(ConstStrings.STOP_TIME, STOP_TIME); + stop.AddValue(ConstStrings.ANIMATION, values[ConstStrings.ANIMATION]); + + var outSet = g.CreateNode("variable/set"); + var outGet = g.CreateNode("variable/get"); + var outVar = g.AddVariable("outFlowActivated", false); + var outVarIndex = g.IndexOfVariable(outVar); + var outBranch = g.CreateNode("flow/branch"); + var outFail = CreateFailSubGraph(g, "Out flow did not trigger during this test."); + + outSet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outGet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outSet.AddValue(ConstStrings.VALUE, true); + + outBranch.AddConnectedValue(ConstStrings.CONDITION, outGet); + outBranch.AddFlow(complete, ConstStrings.TRUE); + outBranch.AddFlow(outFail, ConstStrings.FALSE); + + animation.AddFlow(stop); + stop.AddFlow(failErr, ConstStrings.ERR); + stop.AddFlow(outSet, ConstStrings.OUT); + stop.AddFlow(outBranch, ConstStrings.DONE); + return g; + } + + private static Graph GenerateAnimationInvalidInputTestGraph(string nodeName, params Dictionary[] inputs) + { + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var success = g.CreateNode("event/send"); + var subGraphs = new Node[inputs.Length]; + + success.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + for (int i = 0; i < subGraphs.Length; i++) + { + subGraphs[i] = CreateAnimationInvalidInputSubGraph(g, nodeName, inputs[i]); + } + + start.AddFlow(subGraphs[0]); + + for (int i = 0; i < subGraphs.Length - 1; i++) + { + subGraphs[i].AddFlow(subGraphs[i + 1], ConstStrings.ERR); + } + + subGraphs[subGraphs.Length - 1].AddFlow(success, ConstStrings.ERR); + + return g; + } + + private static Node CreateAnimationInvalidInputSubGraph(Graph g, string nodeName, Dictionary inputs) + { + var node = g.CreateNode(nodeName); + + var sb = new ValueStringBuilder(512); + + foreach (var input in inputs) + { + sb.Append(input.Key); + sb.Append(':'); + sb.Append(' '); + sb.Append(input.Value.ToString()); + sb.Append(','); + sb.Append(' '); + + node.AddValue(input.Key, input.Value); + } + + var inputString = sb.ToString(); + var logFailOut = CreateFailSubGraph(g, $"Out flow was activated despite inputs containing an invalid value: {inputString}."); + var logFailDone = CreateFailSubGraph(g, $"Done flow was activated despite inputs containing an invalid value: {inputString}."); + + node.AddFlow(logFailOut, ConstStrings.OUT); + node.AddFlow(logFailDone, ConstStrings.DONE); + return node; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs.meta new file mode 100644 index 000000000..3eb0eb82f --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Animation/AnimationNodeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d13d8e5bfeb442540b608e16b7f1929e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef b/Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef new file mode 100644 index 000000000..27439386e --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef @@ -0,0 +1,27 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Animation", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common", + "UnityGLTF.Interactivity.Playback.Tests.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef.meta new file mode 100644 index 000000000..e15c9faba --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Animation/UnityGLTF.Interactivity.Playback.Tests.Animation.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ec6919da3d2d7464f98356f30621e5b2 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Common.meta b/Tests/Runtime/Interactivity/Nodes/Common.meta new file mode 100644 index 000000000..0d34a41a9 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3a70e5a942af0264f8b681746d36ed9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs b/Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs new file mode 100644 index 000000000..6434d96f0 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public static class Extensions + { + public static void AddValue(this Dictionary inputs, string id, T value) + { + inputs.Add(id, new Value() + { + id = id, + property = new Property(value) + }); + } + + public static void AddValue(this Dictionary ouputs, string id, T value) + { + ouputs.Add(id, new Property(value)); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs.meta b/Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs.meta new file mode 100644 index 000000000..2f287dedd --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/Extensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bb2d2c083eda8949bc2d28e1ec75a84 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs new file mode 100644 index 000000000..79da5078f --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -0,0 +1,560 @@ +#define GENERATE_TEST_FILES +#define WRITE_TO_TEST_MANIFEST + +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.TestTools; +using UnityGLTF.Interactivity.Playback.Extensions; +using UnityGLTF.Loader; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public abstract class NodeTestHelpers + { + public struct TestValues + { + public ReadOnlyDictionary values; + public ReadOnlyDictionary expectedResults; + public Dictionary config; + } + + protected struct ManifestEntry + { + public string nodeName; + public string fileName; + public string title; + public string description; + public string dependencies; + } + + protected struct TestData + { + public string nodeName; + public string fileName; + public Graph graph; + public float executionTimeoutDuration; + public GLTFSceneImporter importer; + public string title; + public string description; + public TestValues values; + + public ManifestEntry CreateManifestEntry() + { + var typeIndexByType = TypesSerializer.GetSystemTypeByIndexDictionary(graph); + var declarations = DeclarationsSerializer.GetDeclarations(graph.nodes, typeIndexByType); + + var dependencies = new ValueStringBuilder(); + + foreach (var declaration in declarations) + { + dependencies.Append(declaration.Key); + dependencies.Append(','); + dependencies.Append(' '); + } + + return new ManifestEntry() + { + nodeName = nodeName, + fileName = fileName, + title = title, + description = description, + dependencies = dependencies.ToString() + }; + } + } + + protected const int FAIL_EVENT_INDEX = 0; + protected const int COMPLETED_EVENT_INDEX = 1; + protected const float DEFAULT_MULTI_FRAME_EXECUTION_TIMEOUT = 10f; + protected const string TEST_GRAPH_SAVE_DIRECTORY = "TestGraphJson"; + + protected bool _testSuccessful = false; + private readonly GraphSerializer _serializer = new(Newtonsoft.Json.Formatting.Indented); + + protected string _testGraphDirectory; + protected abstract string _subDirectory { get; } + + protected static readonly List _testQueue = new(); + + protected string _manifestPath; + + public static readonly ISubGraph[] subGraphs = new ISubGraph[] + { + new ApproximatelySubGraph(0.001f), + new EqualSubGraph(), + new IsNaNSubGraph(), + new IsInfSubGraph() + }; + + protected void OnCustomEventFired(int eventIndex, Dictionary outValues) + { + switch (eventIndex) + { + case 0: + Assert.Fail("Failure case was triggered by this test."); + break; + case 1: + Util.Log("Test was successful."); + _testSuccessful = true; + break; + } + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + var dir = Path.GetFullPath(Path.Combine(Application.dataPath, @"..\")); + if (!string.IsNullOrWhiteSpace(_subDirectory)) + _testGraphDirectory = Path.Combine(dir, TEST_GRAPH_SAVE_DIRECTORY, _subDirectory); + else + _testGraphDirectory = Path.Combine(dir, TEST_GRAPH_SAVE_DIRECTORY); + + Directory.CreateDirectory(_testGraphDirectory); + + _manifestPath = $"{_testGraphDirectory}/manifest.md"; + + if (!File.Exists(_manifestPath)) + File.WriteAllText(_manifestPath, "|Filename|Node Type|Title|Description|Dependencies|\n|---|---|---|---|---|\n"); + } + + [SetUp] + public void SetUp() + { + _testQueue.Clear(); + } + + [UnityTearDown] + public IEnumerator TearDown() + { + foreach (var test in _testQueue) + { + var endTime = Time.time + test.executionTimeoutDuration; + _testSuccessful = false; +#if GENERATE_TEST_FILES +#if WRITE_TO_TEST_MANIFEST + var e = test.CreateManifestEntry(); + + File.AppendAllText(_manifestPath, $"|{e.fileName}|{e.nodeName}|{e.title}|{e.description}|{e.dependencies}|\n"); +#endif + Task.Run(() => + { + // Record graph to json file. + if (!string.IsNullOrWhiteSpace(test.fileName)) + { + var extension = new KHR_interactivity() + { + graphs = new List() { test.graph }, + defaultGraphIndex = 0 + }; + File.WriteAllText($"{_testGraphDirectory}/{test.fileName}.json", _serializer.Serialize(extension)); + } + }); +#endif + + // Actually run test. + var eng = CreateBehaviourEngineForGraph(test.graph, OnCustomEventFired, test.importer, startPlayback: true); + + while (!_testSuccessful && Time.time < endTime) + { + eng.Tick(); + yield return null; + } + + Assert.IsTrue(_testSuccessful); + } + } + + protected static void TestNodeWithAllFloatNInputVariants(string fileName, string testName, string testDescription, string nodeName, float4 a, float4 expected, ComparisonType subGraphType = ComparisonType.Approximately) + { + QueueTest(nodeName, $"{fileName}-float-x", $"{testName} (float-x)", $"{testDescription} Uses the x-component of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.x), Out(expected.x), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-y", $"{testName} (float-y)", $"{testDescription}Uses the y-component of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.y), Out(expected.y), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-z", $"{testName} (float-z)", $"{testDescription}Uses the z-component of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.z), Out(expected.z), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-w", $"{testName} (float-w)", $"{testDescription}Uses the w-component of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.w), Out(expected.w), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float2-xy", $"{testName} (float2-xy)", $"{testDescription}Uses the xy-components of the float4 version of this test to test this node's float2 operation.", CreateSelfContainedTestGraph(nodeName, In(new float2(a.x, a.y)), Out(expected.xy), subGraphType)); + QueueTest(nodeName, $"{fileName}-float2-zw", $"{testName} (float2-zw)", $"{testDescription}Uses the zw-components of the float4 version of this test to test this node's float2 operation.", CreateSelfContainedTestGraph(nodeName, In(new float2(a.z, a.w)), Out(expected.zw), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float3-xyz", $"{testName} (float3-xyz)", $"{testDescription}Uses the xyz-components of the float4 version of this test to test this node's float3 operation.", CreateSelfContainedTestGraph(nodeName, In(new float3(a.x, a.y, a.z)), Out(expected.xyz), subGraphType)); + QueueTest(nodeName, $"{fileName}-float3-yzw", $"{testName} (float3-yzw)", $"{testDescription}Uses the yzw-components of the float4 version of this test to test this node's float3 operation.", CreateSelfContainedTestGraph(nodeName, In(new float3(a.y, a.z, a.w)), Out(expected.yzw), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float4", $"{testName} (float4)", $"{testDescription}Tests this node's float4 operation.", CreateSelfContainedTestGraph(nodeName, In(a), Out(expected), subGraphType)); + } + + protected static void TestNodeWithAllFloatNInputVariants(string fileName, string testName, string testDescription, string nodeName, float4 a, float4 b, float4 expected, ComparisonType subGraphType = ComparisonType.Approximately) + { + QueueTest(nodeName, $"{fileName}-float-x", $"{testName} (float-x)", $"{testDescription}Uses the x-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.x, b.x), Out(expected.x), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-y", $"{testName} (float-y)", $"{testDescription}Uses the y-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.y, b.y), Out(expected.y), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-z", $"{testName} (float-z)", $"{testDescription}Uses the z-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.z, b.z), Out(expected.z), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-w", $"{testName} (float-w)", $"{testDescription}Uses the w-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.w, b.w), Out(expected.w), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float2-xy", $"{testName} (float2-xy)", $"{testDescription}Uses the xy-components of the float4 version of this test to test this node's float2 operation.", CreateSelfContainedTestGraph(nodeName, In(new float2(a.x, a.y), new float2(b.x, b.y)), Out(expected.xy), subGraphType)); + QueueTest(nodeName, $"{fileName}-float2-zw", $"{testName} (float2-zw)", $"{testDescription}Uses the zw-components of the float4 version of this test to test this node's float2 operation.", CreateSelfContainedTestGraph(nodeName, In(new float2(a.z, a.w), new float2(b.z, b.w)), Out(expected.zw), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float3-xyz", $"{testName} (float3-xyz)", $"{testDescription}Uses the xyz-components of the float4 version of this test to test this node's float3 operation.", CreateSelfContainedTestGraph(nodeName, In(new float3(a.x, a.y, a.z), new float3(b.x, b.y, b.z)), Out(expected.xyz), subGraphType)); + QueueTest(nodeName, $"{fileName}-float3-yzw", $"{testName} (float3-yzw)", $"{testDescription}Uses the yzw-components of the float4 version of this test to test this node's float3 operation.", CreateSelfContainedTestGraph(nodeName, In(new float3(a.y, a.z, a.w), new float3(b.y, b.z, b.w)), Out(expected.yzw), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float4", $"{testName} (float4)", $"{testDescription}Tests this node's float4 operation.", CreateSelfContainedTestGraph(nodeName, In(a, b), Out(expected), subGraphType)); + } + + + protected static void TestNodeWithAllFloatNInputVariants(string fileName, string testName, string testDescription, string nodeName, float4 a, float4 b, float4 c, float4 expected, ComparisonType subGraphType = ComparisonType.Approximately) + { + QueueTest(nodeName, $"{fileName}-float-x", $"{testName} (float-x)", $"{testDescription}Uses the x-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.x, b.x, c.x), Out(expected.x), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-y", $"{testName} (float-y)", $"{testDescription}Uses the y-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.y, b.y, c.y), Out(expected.y), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-z", $"{testName} (float-z)", $"{testDescription}Uses the z-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.z, b.z, c.z), Out(expected.z), subGraphType)); + QueueTest(nodeName, $"{fileName}-float-w", $"{testName} (float-w)", $"{testDescription}Uses the w-components of the float4 version of this test to test this node's float operation.", CreateSelfContainedTestGraph(nodeName, In(a.w, b.w, c.w), Out(expected.w), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float2-xy", $"{testName} (float2-xy)", $"{testDescription}Uses the xy-components of the float4 version of this test to test this node's float2 operation.", CreateSelfContainedTestGraph(nodeName, In(new float2(a.x, a.y), new float2(b.x, b.y), new float2(c.x, c.y)), Out(expected.xy), subGraphType)); + QueueTest(nodeName, $"{fileName}-float2-zw", $"{testName} (float2-zw)", $"{testDescription}Uses the zw-components of the float4 version of this test to test this node's float2 operation.", CreateSelfContainedTestGraph(nodeName, In(new float2(a.z, a.w), new float2(b.z, b.w), new float2(c.z, c.w)), Out(expected.zw), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float3-xyz", $"{testName} (float3-xyz)", $"{testDescription}Uses the xyz-components of the float4 version of this test to test this node's float3 operation.", CreateSelfContainedTestGraph(nodeName, In(new float3(a.x, a.y, a.z), new float3(b.x, b.y, b.z), new float3(c.x, c.y, c.z)), Out(expected.xyz), subGraphType)); + QueueTest(nodeName, $"{fileName}-float3-yzw", $"{testName} (float3-yzw)", $"{testDescription}Uses the yzw-components of the float4 version of this test to test this node's float3 operation.", CreateSelfContainedTestGraph(nodeName, In(new float3(a.y, a.z, a.w), new float3(b.y, b.z, b.w), new float3(c.y, c.z, c.w)), Out(expected.yzw), subGraphType)); + + QueueTest(nodeName, $"{fileName}-float4", $"{testName} (float4)", $"{testDescription}Tests this node's float4 operation.", CreateSelfContainedTestGraph(nodeName, In(a, b, c), Out(expected), subGraphType)); + } + + + protected static (Graph, TestValues) CreateSelfContainedTestGraph(string nodeStr, Dictionary values, Dictionary expectedResults, ComparisonType subGraphType) + { + Graph g = CreateGraphForTest(); + + var opNode = g.CreateNode(nodeStr); + var subGraph = subGraphs[(int)subGraphType]; + + foreach (var value in values) + { + opNode.AddValue(value.Key, value.Value); + } + + GenerateGraphByExpectedValueType(expectedResults, g, opNode, subGraph); + + var testValues = new TestValues() + { + values = new(values), + expectedResults = new(expectedResults), + }; + + return (g, testValues); + } + + private static void GenerateGraphByExpectedValueType(Dictionary expectedResults, Graph g, Node opNode, ISubGraph subGraph) + { + Value value; + Node node; + + foreach (var expected in expectedResults) + { + if (subGraph is EqualSubGraph) + { + CreateSingleValueTestSubGraph(g, opNode, expected.Key, expected.Value, subGraph); + continue; + } + + switch (expected.Value) + { + default: + CreateSingleValueTestSubGraph(g, opNode, expected.Key, expected.Value, subGraph); + break; + case Property: + node = CreateExtractNode(g, "math/extract2", opNode, out value, out node, expected); + var f2Val = ((Property)expected.Value).value; + for (int i = 0; i < 2; i++) + { + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[i], f2Val[i], subGraph); + } + break; + case Property: + node = CreateExtractNode(g, "math/extract3", opNode, out value, out node, expected); + var f3Val = ((Property)expected.Value).value; + for (int i = 0; i < 3; i++) + { + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[i], f3Val[i], subGraph); + } + break; + case Property: + node = CreateExtractNode(g, "math/extract4", opNode, out value, out node, expected); + var f4Val = ((Property)expected.Value).value; + for (int i = 0; i < 4; i++) + { + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[i], f4Val[i], subGraph); + } + break; + case Property: + node = CreateExtractNode(g, "math/extract2x2", opNode, out value, out node, expected); + var f2x2Val = ((Property)expected.Value).value; + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[0], f2x2Val.c0.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[1], f2x2Val.c0.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[2], f2x2Val.c1.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[3], f2x2Val.c1.y, subGraph); + break; + + case Property: + node = CreateExtractNode(g, "math/extract3x3", opNode, out value, out node, expected); + var f3x3Val = ((Property)expected.Value).value; + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[0], f3x3Val.c0.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[1], f3x3Val.c0.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[2], f3x3Val.c0.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[3], f3x3Val.c1.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[4], f3x3Val.c1.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[5], f3x3Val.c1.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[6], f3x3Val.c2.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[7], f3x3Val.c2.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[8], f3x3Val.c2.z, subGraph); + break; + + case Property: + node = CreateExtractNode(g, "math/extract4x4", opNode, out value, out node, expected); + var f4x4Val = ((Property)expected.Value).value; + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[0], f4x4Val.c0.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[1], f4x4Val.c0.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[2], f4x4Val.c0.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[3], f4x4Val.c0.w, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[4], f4x4Val.c1.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[5], f4x4Val.c1.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[6], f4x4Val.c1.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[7], f4x4Val.c1.w, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[8], f4x4Val.c2.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[9], f4x4Val.c2.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[10], f4x4Val.c2.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[11], f4x4Val.c2.w, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[12], f4x4Val.c3.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[13], f4x4Val.c3.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[14], f4x4Val.c3.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[15], f4x4Val.c3.w, subGraph); + break; + + } + } + + static Node CreateExtractNode(Graph g, string nodeName, Node opNode, out Value value, out Node node, KeyValuePair expected) + { + node = g.CreateNode(nodeName); + value = node.AddValue(ConstStrings.A, 0); + value.TryConnectToSocket(opNode, expected.Key); + + return node; + } + } + + private static void CreateSingleValueTestSubGraph(Graph g, Node outNode, string outSocket, T expected, ISubGraph subGraph) + { + CreateSingleValueTestSubGraph(g, outNode, outSocket, (IProperty)new Property(expected), subGraph); + } + + private static void CreateSingleValueTestSubGraph(Graph g, Node outNode, string outSocket, IProperty expected, ISubGraph subGraph) + { + var onStart = g.CreateNode("event/onStart"); + var pass = g.CreateNode("event/send"); + var fail = g.CreateNode("event/send"); + var failLog = g.CreateNode("debug/log"); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + pass.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + failLog.AddConfiguration(ConstStrings.MESSAGE, $"Output {outSocket}" + ", Expected: {expected}, Actual: {actual}"); + (var subIn, var subOut) = subGraph.CreateSubGraph(g); + + var subAValue = subIn.AddValue(ConstStrings.A, 0); + subAValue.TryConnectToSocket(outNode, outSocket); + + if (subGraph.hasBValue) subIn.AddValue(ConstStrings.B, expected); + + onStart.AddFlow(subOut, ConstStrings.OUT, ConstStrings.IN); + + subOut.AddFlow(pass, ConstStrings.TRUE, ConstStrings.IN); + subOut.AddFlow(failLog, ConstStrings.FALSE, ConstStrings.IN); + + failLog.AddFlow(fail, ConstStrings.OUT, ConstStrings.IN); + failLog.AddValue(ConstStrings.EXPECTED, expected); + var failLogActualValue = failLog.AddValue(ConstStrings.ACTUAL, 0); + failLogActualValue.TryConnectToSocket(outNode, outSocket); + } + + protected static BehaviourEngine CreateBehaviourEngineForGraph(Graph g, Action> onEventFired, GLTFSceneImporter importer, bool startPlayback) + { + BehaviourEngine eng = new BehaviourEngine(g, importer); + + AddAnimationSupportIfApplicable(eng, importer); + + if (onEventFired != null) + eng.onCustomEventFired += onEventFired; + + if (startPlayback) + eng.StartPlayback(); + + return eng; + } + + private static void AddAnimationSupportIfApplicable(BehaviourEngine eng, GLTFSceneImporter importer) + { + if (importer == null || importer.AnimationCache.IsNullOrEmpty()) + return; + + var animationWrapper = importer.SceneParent.gameObject.AddComponent(); + eng.SetAnimationWrapper(animationWrapper, importer.LastLoadedScene.GetComponents()[0]); + } + + protected static async Task LoadTestModel(string modelName, Action onLoadComplete = null) + { + ImporterFactory _importerFactory = ScriptableObject.CreateInstance(); + ImportOptions _importOptions = new ImportOptions() + { + ImportNormals = GLTFImporterNormals.Import, + ImportTangents = GLTFImporterNormals.Import, + }; + + _importOptions.DataLoader = new ResourcesLoader(); + + var importer = _importerFactory.CreateSceneImporter( + modelName, + _importOptions + ); + + var sceneParent = new GameObject(modelName).transform; + + importer.SceneParent = sceneParent; + importer.Collider = GLTFSceneImporter.ColliderType.Box; + importer.MaximumLod = 300; + importer.Timeout = 8; + importer.IsMultithreaded = true; + importer.CustomShaderName = null; + + // for logging progress + await importer.LoadSceneAsync( + showSceneObj: true, + onLoadComplete: (go, e) => onLoadComplete?.Invoke(go, e, importer) + ); + + return importer; + } + + protected static Graph CreateGraphForTest() + { + var graph = new Graph(); + graph.AddDefaultTypes(); + graph.AddEvent("Failed"); + graph.AddEvent("Completed"); + return graph; + } + + protected static string GetCallerName([CallerMemberName] string caller = null) + { + return caller; + } + + protected static void QueueTest(string nodeName, string fileName, string testName, string testDescription, Graph graph, GLTFSceneImporter importer) + { + _testQueue.Add(new TestData() + { + nodeName = nodeName, + fileName = fileName, + graph = graph, + executionTimeoutDuration = DEFAULT_MULTI_FRAME_EXECUTION_TIMEOUT, + importer = importer, + title = testName, + description = testDescription + }); + } + + protected static void QueueTest(string nodeName, string fileName, string testName, string testDescription, Graph graph) + { + _testQueue.Add(new TestData() + { + nodeName = nodeName, + fileName = fileName, + graph = graph, + executionTimeoutDuration = DEFAULT_MULTI_FRAME_EXECUTION_TIMEOUT, + title = testName, + description = testDescription + }); + } + + protected static void QueueTest(string nodeName, string fileName, string testName, string testDescription, (Graph graph, TestValues values) graphTuple) + { + _testQueue.Add(new TestData() + { + nodeName = nodeName, + fileName = fileName, + graph = graphTuple.graph, + executionTimeoutDuration = DEFAULT_MULTI_FRAME_EXECUTION_TIMEOUT, + title = testName, + description = testDescription, + values = graphTuple.values + }); + } + + protected static Dictionary In(params T[] inputValues) + { + Dictionary inputs = new(); + + for (int i = 0; i < inputValues.Length; i++) + { + inputs.Add(ConstStrings.Letters[i], new Value() + { + id = ConstStrings.Letters[i], + property = new Property(inputValues[i]) + }); + } + + return inputs; + } + + protected static Dictionary Out(T expected) + { + Dictionary outputs = new(); + + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + return outputs; + } + + protected static Dictionary In(params KeyValuePair[] inputValues) + { + Dictionary inputs = new(); + + for (int i = 0; i < inputValues.Length; i++) + { + inputs.Add(inputValues[i].Key, new Value() + { + id = inputValues[i].Key, + property = new Property(inputValues[i].Value) + }); + } + + return inputs; + } + + protected static Dictionary Out(params KeyValuePair[] expectedOutputs) + { + Dictionary outputs = new(); + + for (int i = 0; i < expectedOutputs.Length; i++) + { + outputs.Add(expectedOutputs[i].Key, new Property(expectedOutputs[i].Value)); + } + + return outputs; + } + + protected static Node CreateFailSubGraph(Graph g, string failMessage) + { + var fail = g.CreateNode("event/send"); + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + var log = g.CreateNode("debug/log"); + log.AddConfiguration(ConstStrings.MESSAGE, failMessage); + log.AddFlow(fail); + + return log; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs.meta b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs.meta new file mode 100644 index 000000000..09e054cca --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62b7deb0bbcaf1149960cf34b79d82b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs b/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs new file mode 100644 index 000000000..286300819 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs @@ -0,0 +1,96 @@ +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public enum ComparisonType + { + Approximately = 0, + Equals = 1, + IsNaN = 2, + IsInfinity = 3 + } + public interface ISubGraph + { + bool hasBValue { get; } + + public (Node inputNode, Node outputNode) CreateSubGraph(Graph g); + } + + public class ApproximatelySubGraph : ISubGraph + { + private readonly float _tolerance; + public bool hasBValue => true; + + public ApproximatelySubGraph(float tolerance) + { + _tolerance = tolerance; + } + + public (Node inputNode, Node outputNode) CreateSubGraph(Graph g) + { + var sub = g.CreateNode("math/sub"); + var abs = g.CreateNode("math/abs"); + var ge = g.CreateNode("math/ge"); + var branch = g.CreateNode("flow/branch"); + + var branchConditionValue = branch.AddValue(ConstStrings.CONDITION, true); + branchConditionValue.TryConnectToSocket(ge, ConstStrings.VALUE); + + ge.AddValue(ConstStrings.A, _tolerance); + var geBValue = ge.AddValue(ConstStrings.B, 0); + geBValue.TryConnectToSocket(abs, ConstStrings.VALUE); + + var absAValue = abs.AddValue(ConstStrings.A, 0); + absAValue.TryConnectToSocket(sub, ConstStrings.VALUE); + + return (sub, branch); + } + } + + public class EqualSubGraph : ISubGraph + { + public bool hasBValue => true; + + public (Node inputNode, Node outputNode) CreateSubGraph(Graph g) + { + var eq = g.CreateNode("math/eq"); + var branch = g.CreateNode("flow/branch"); + + var branchConditionValue = branch.AddValue(ConstStrings.CONDITION, true); + branchConditionValue.TryConnectToSocket(eq, ConstStrings.VALUE); + + return (eq, branch); + } + } + + public class IsNaNSubGraph : ISubGraph + { + public bool hasBValue => false; + + public (Node inputNode, Node outputNode) CreateSubGraph(Graph g) + { + var eq = g.CreateNode("math/isnan"); + var branch = g.CreateNode("flow/branch"); + + var branchConditionValue = branch.AddValue(ConstStrings.CONDITION, true); + branchConditionValue.TryConnectToSocket(eq, ConstStrings.VALUE); + + return (eq, branch); + } + } + + public class IsInfSubGraph : ISubGraph + { + public bool hasBValue => false; + + + public (Node inputNode, Node outputNode) CreateSubGraph(Graph g) + { + var eq = g.CreateNode("math/isinf"); + var branch = g.CreateNode("flow/branch"); + + var branchConditionValue = branch.AddValue(ConstStrings.CONDITION, true); + branchConditionValue.TryConnectToSocket(eq, ConstStrings.VALUE); + + return (eq, branch); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs.meta b/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs.meta new file mode 100644 index 000000000..a9bc9a5bd --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1fa2363e3084cc44973e33fa0a0e9b1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef b/Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef new file mode 100644 index 000000000..bc0f49e5c --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef @@ -0,0 +1,26 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Common", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef.meta new file mode 100644 index 000000000..7c54dcae2 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Common/UnityGLTF.Interactivity.Playback.Tests.Common.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a5d87504a210aaf409e4e3a0b62492aa +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Event.meta b/Tests/Runtime/Interactivity/Nodes/Event.meta new file mode 100644 index 000000000..771b1e1d2 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Event.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eed993c050608114493dede410ec0b26 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs new file mode 100644 index 000000000..8593ca9e1 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs @@ -0,0 +1,400 @@ +using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.TestTools; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public class EventNodeTests : NodeTestHelpers + { + protected override string _subDirectory => "Event"; + + [Test] + public void OnStart_OutFlowActivatedOnEnginePlaybackStart() + { + QueueTest("event/onStart", GetCallerName(), "OnStart", "Test fails if the out flow never triggers when the test begins.", CreateOnStartTestGraph()); + } + + [Test] + public void Receive_EventIsReceivedAndOutFlowActivates() + { + QueueTest("event/receive", GetCallerName(), "Receive Basic", "Fails if trigger event is never received by a receive node.", CreateEventReceiveTestGraph()); + } + + [Test] + public void Receive_SendWithValues_ValuesAreReceived() + { + QueueTest("event/receive", GetCallerName(), "Receive With Values", "Sends an event with every event value type included. Test fails if they are not received on the other end or are incorrect.", CreateEventReceiveWithValuesTestGraph()); + } + + [Test] + public void Receive_CheckOutputValuesBeforeSendOccurs_ValuesAreEventValueDefaults() + { + QueueTest("event/receive", GetCallerName(), "Receive Access Values w/o Event Send", "Grabs every type of value from an event/receive node without triggering send first. Test fails if the values retrieved are not the defaults set in the event itself.", CreateEventReceiveWithNoSendCheckOutputValuesTestGraph()); + } + + [Test] + public void OnTick_OccursAfterOnStartAndOutputValuesAreCorrectByFrame() + { + QueueTest("event/onTick", GetCallerName(), "OnTick", "Fails if: onStart does not activate its out flow before onTick; timeSinceStart and timeSinceLastTick are not NaN before first out flow from onTick node; timeSinceStart is not zero or timeSinceLastTick is not NaN on first activation of out flow from onTick; timeSinceStart and timeSinceLastTick are not valid and greater than 0 on second activation of out flow from onTick ", CreateOnTickTestGraph()); + } + + private static Graph CreateEventReceiveTestGraph() + { + var g = CreateGraphForTest(); + + g.AddEvent("trigger"); + + var start = g.CreateNode("event/onStart"); + var receiveTrigger = g.CreateNode("event/receive"); + var sendTrigger = g.CreateNode("event/send"); + + var complete = g.CreateNode("event/send"); + + start.AddFlow(sendTrigger); + + receiveTrigger.AddFlow(complete); + + sendTrigger.AddConfiguration(ConstStrings.EVENT, 2); + receiveTrigger.AddConfiguration(ConstStrings.EVENT, 2); + + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + return g; + } + + private static Graph CreateOnStartTestGraph() + { + var g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var complete = g.CreateNode("event/send"); + + start.AddFlow(complete); + + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + return g; + } + + private static Graph CreateEventReceiveWithValuesTestGraph() + { + var g = CreateGraphForTest(); + + var initial = new IProperty[] + { + P(false), + P(1), + P(1f), + P(new float2(1f, 1f)), + P(new float3(1f, 1f, 1f)), + P(new float4(1f, 1f, 1f, 1f)), + P(new float2x2(new float2(1f, 1f), new float2(1f, 1f))), + P(new float3x3(new float3(1f, 1f, 1f), new float3(1f, 1f, 1f), new float3(1f, 1f, 1f))), + P(new float4x4(new float4(1f, 1f, 1f, 1f), new float4(1f, 1f, 1f, 1f), new float4(1f, 1f, 1f, 1f), new float4(1f, 1f, 1f, 1f))) + }; + + var expected = new IProperty[] + { + P(true), + P(42), + P(3.14f), + P(new float2(0.75f, -2.5f)), + P(new float3(1.23f, -0.98f, 4.56f)), + P(new float4(-1.1f, 0.0f, 2.2f, 3.3f)), + P(new float2x2( + new float2(7.77f, -8.88f), + new float2(9.99f, -0.12f)) + ), + P(new float3x3( + new float3(0.1f, 0.2f, 0.3f), + new float3(4.4f, 5.5f, 6.6f), + new float3(7.7f, 8.8f, 9.9f)) + ), + P(new float4x4( + new float4(1.1f, 2.2f, 3.3f, 4.4f), + new float4(5.5f, 6.6f, 7.7f, 8.8f), + new float4(9.9f, -1.2f, -3.4f, -5.6f), + new float4(0.0f, 0.1f, 0.2f, 0.3f)) + ) + }; + + var values = new List(); + + for (int i = 0; i < initial.Length; i++) + { + values.Add(new EventValue(initial[i].GetTypeSignature(), initial[i])); + } + + g.AddEvent("trigger", values); + + var start = g.CreateNode("event/onStart"); + var receiveTrigger = g.CreateNode("event/receive"); + var sendTrigger = g.CreateNode("event/send"); + + var complete = g.CreateNode("event/send"); + + start.AddFlow(sendTrigger); + + sendTrigger.AddConfiguration(ConstStrings.EVENT, 2); + + for (int i = 0; i < expected.Length; i++) + { + sendTrigger.AddValue(values[i].id, expected[i]); + } + + receiveTrigger.AddConfiguration(ConstStrings.EVENT, 2); + + var sequence = g.CreateNode("flow/sequence"); + receiveTrigger.AddFlow(sequence); + + for (int i = 0; i < values.Count; i++) + { + var eq = g.CreateNode("math/eq"); + var branch = g.CreateNode("flow/branch"); + var fail = g.CreateNode("event/send"); + var failLog = g.CreateNode("debug/log"); + + eq.AddConnectedValue(ConstStrings.A, receiveTrigger, values[i].id); + eq.AddValue(ConstStrings.B, expected[i]); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + branch.AddFlow(failLog, ConstStrings.FALSE); + + failLog.AddConfiguration(ConstStrings.MESSAGE, $"{values[i].id} " + "Expected: {expected}, Actual: {actual}"); + failLog.AddConnectedValue(ConstStrings.ACTUAL, receiveTrigger, values[i].id); + failLog.AddValue(ConstStrings.EXPECTED, expected[i]); + failLog.AddFlow(fail); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + sequence.AddFlow(branch, ConstStrings.Numbers[i]); + } + + sequence.AddFlow(complete, ConstStrings.Numbers[values.Count]); + + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + return g; + + IProperty P(T value) + { + return new Property(value); + } + } + + private static Graph CreateEventReceiveWithNoSendCheckOutputValuesTestGraph() + { + var g = CreateGraphForTest(); + + var initial = new IProperty[] + { + P(true), + P(42), + P(3.14f), + P(new float2(0.75f, -2.5f)), + P(new float3(1.23f, -0.98f, 4.56f)), + P(new float4(-1.1f, 0.0f, 2.2f, 3.3f)), + P(new float2x2( + new float2(7.77f, -8.88f), + new float2(9.99f, -0.12f)) + ), + P(new float3x3( + new float3(0.1f, 0.2f, 0.3f), + new float3(4.4f, 5.5f, 6.6f), + new float3(7.7f, 8.8f, 9.9f)) + ), + P(new float4x4( + new float4(1.1f, 2.2f, 3.3f, 4.4f), + new float4(5.5f, 6.6f, 7.7f, 8.8f), + new float4(9.9f, -1.2f, -3.4f, -5.6f), + new float4(0.0f, 0.1f, 0.2f, 0.3f)) + ) + }; + + var values = new List(); + + for (int i = 0; i < initial.Length; i++) + { + values.Add(new EventValue(initial[i].GetTypeSignature(), initial[i])); + } + + g.AddEvent("trigger", values); + + var start = g.CreateNode("event/onStart"); + var receiveTrigger = g.CreateNode("event/receive"); + + var complete = g.CreateNode("event/send"); + + receiveTrigger.AddConfiguration(ConstStrings.EVENT, 2); + + var sequence = g.CreateNode("flow/sequence"); + start.AddFlow(sequence); + + for (int i = 0; i < values.Count; i++) + { + var eq = g.CreateNode("math/eq"); + var branch = g.CreateNode("flow/branch"); + var fail = g.CreateNode("event/send"); + var failLog = g.CreateNode("debug/log"); + + eq.AddConnectedValue(ConstStrings.A, receiveTrigger, values[i].id); + eq.AddValue(ConstStrings.B, initial[i]); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + branch.AddFlow(failLog, ConstStrings.FALSE); + + failLog.AddConfiguration(ConstStrings.MESSAGE, $"{values[i].id} " + "Expected: {expected}, Actual: {actual}"); + failLog.AddConnectedValue(ConstStrings.ACTUAL, receiveTrigger, values[i].id); + failLog.AddValue(ConstStrings.EXPECTED, initial[i]); + failLog.AddFlow(fail); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + sequence.AddFlow(branch, ConstStrings.Numbers[i]); + } + + sequence.AddFlow(complete, ConstStrings.Numbers[values.Count]); + + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + return g; + + IProperty P(T value) + { + return new Property(value); + } + } + + private static Graph CreateOnTickTestGraph() + { + var g = CreateGraphForTest(); + + var onStartNodeTriggeredVariable = g.AddVariable("onStartNodeTriggered", false); + var onStartNodeTriggeredVariableIndex = g.IndexOfVariable(onStartNodeTriggeredVariable); + + var start = g.CreateNode("event/onStart"); + var hasStartedSet = g.CreateNode("variable/set"); + var branch = g.CreateNode("flow/branch"); + var failStart = g.CreateNode("event/send"); + var failLog = g.CreateNode("debug/log"); + var onTick = g.CreateNode("event/onTick"); + var complete = g.CreateNode("event/send"); + var startAnd = g.CreateNode("math/and"); + var isTimeSinceStartNaN = g.CreateNode("math/isnan"); + var isTimeSinceLastTickNaN = g.CreateNode("math/isnan"); + + failStart.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + failLog.AddConfiguration(ConstStrings.MESSAGE, "Both timeSinceStart and timeSinceLastTick should be NaN before onTick activates its first out flow. timeSinceStart: {timeSinceStart}, timeSinceLastTick: {timeSinceLastTick}"); + failLog.AddConnectedValue(ConstStrings.TIME_SINCE_START, onTick, ConstStrings.TIME_SINCE_START); + failLog.AddConnectedValue(ConstStrings.TIME_SINCE_LAST_TICK, onTick, ConstStrings.TIME_SINCE_LAST_TICK); + + hasStartedSet.AddConfiguration(ConstStrings.VARIABLE, onStartNodeTriggeredVariableIndex); + hasStartedSet.AddValue(ConstStrings.VALUE, true); + + start.AddFlow(hasStartedSet); + hasStartedSet.AddFlow(branch); + + branch.AddFlow(failLog, ConstStrings.FALSE); + branch.AddConnectedValue(ConstStrings.CONDITION, startAnd); + failLog.AddFlow(failStart); + + startAnd.AddConnectedValue(ConstStrings.A, isTimeSinceStartNaN); + startAnd.AddConnectedValue(ConstStrings.B, isTimeSinceLastTickNaN); + + isTimeSinceStartNaN.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + isTimeSinceLastTickNaN.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_LAST_TICK); + + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var hasStartedGet = g.CreateNode("variable/get"); + var hasStartedBranch = g.CreateNode("flow/branch"); + var onStartNotTriggeredBeforeOnTickLog = g.CreateNode("debug/log"); + var onStartNotTriggeredBeforeOnTickFail = g.CreateNode("event/send"); + + onStartNotTriggeredBeforeOnTickFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + onStartNotTriggeredBeforeOnTickLog.AddConfiguration(ConstStrings.MESSAGE, "onTick activated its out flow before onStart did."); + + onTick.AddFlow(hasStartedBranch); + hasStartedBranch.AddConnectedValue(ConstStrings.CONDITION, hasStartedGet); + hasStartedBranch.AddFlow(onStartNotTriggeredBeforeOnTickLog, ConstStrings.FALSE); + onStartNotTriggeredBeforeOnTickLog.AddFlow(onStartNotTriggeredBeforeOnTickFail); + + hasStartedGet.AddConfiguration(ConstStrings.VARIABLE, onStartNodeTriggeredVariableIndex); + + var isFirstTickVariable = g.AddVariable("isFirstTick", true); + var isFirstTickVariableIndex = g.IndexOfVariable(isFirstTickVariable); + + var isFirstTickBranch = g.CreateNode("flow/branch"); + var isFirstTickGet = g.CreateNode("variable/get"); + var isFirstTickSet = g.CreateNode("variable/set"); + isFirstTickGet.AddConfiguration(ConstStrings.VARIABLE, isFirstTickVariableIndex); + isFirstTickSet.AddConfiguration(ConstStrings.VARIABLE, isFirstTickVariableIndex); + + hasStartedBranch.AddFlow(isFirstTickBranch, ConstStrings.TRUE); + isFirstTickBranch.AddConnectedValue(ConstStrings.CONDITION, isFirstTickGet); + + var firstTickBranch = g.CreateNode("flow/branch"); + var firstTickAnd = g.CreateNode("math/and"); + var firstTickIsNaN = g.CreateNode("math/isnan"); + var firstTickEq = g.CreateNode("math/eq"); + + isFirstTickBranch.AddFlow(firstTickBranch, ConstStrings.TRUE); + firstTickBranch.AddConnectedValue(ConstStrings.CONDITION, firstTickAnd); + firstTickAnd.AddConnectedValue(ConstStrings.A, firstTickIsNaN); + firstTickAnd.AddConnectedValue(ConstStrings.B, firstTickEq); + + firstTickEq.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + firstTickEq.AddValue(ConstStrings.B, 0f); + firstTickIsNaN.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_LAST_TICK); + + var firstTickFailLog = g.CreateNode("debug/log"); + var firstTickFail = g.CreateNode("event/send"); + + firstTickFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + firstTickFailLog.AddConfiguration(ConstStrings.MESSAGE, "On the first tick timeSinceStart should be 0 and timeSinceLastTick should be NaN. timeSinceStart: {timeSinceStart}, timeSinceLastTick: {timeSinceLastTick}"); + firstTickFailLog.AddConnectedValue(ConstStrings.TIME_SINCE_START, onTick, ConstStrings.TIME_SINCE_START); + firstTickFailLog.AddConnectedValue(ConstStrings.TIME_SINCE_LAST_TICK, onTick, ConstStrings.TIME_SINCE_LAST_TICK); + + firstTickFailLog.AddFlow(firstTickFail); + + isFirstTickSet.AddValue(ConstStrings.VALUE, false); + firstTickBranch.AddFlow(isFirstTickSet, ConstStrings.TRUE); + firstTickBranch.AddFlow(firstTickFailLog, ConstStrings.FALSE); + + var secondTickBranch = g.CreateNode("flow/branch"); + var secondTickAnd = g.CreateNode("math/and"); + var secondTickGtB = g.CreateNode("math/gt"); + var secondTickGtA = g.CreateNode("math/gt"); + + isFirstTickBranch.AddFlow(secondTickBranch, ConstStrings.FALSE); + + secondTickBranch.AddConnectedValue(ConstStrings.CONDITION, secondTickAnd); + + secondTickAnd.AddConnectedValue(ConstStrings.A, secondTickGtA); + secondTickAnd.AddConnectedValue(ConstStrings.B, secondTickGtB); + + secondTickGtA.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + secondTickGtA.AddValue(ConstStrings.B, 0f); + secondTickGtB.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_LAST_TICK); + secondTickGtB.AddValue(ConstStrings.B, 0f); + + secondTickBranch.AddFlow(complete, ConstStrings.TRUE); + + var secondTickFailLog = g.CreateNode("debug/log"); + var secondTickFail = g.CreateNode("event/send"); + + secondTickFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + secondTickFailLog.AddConfiguration(ConstStrings.MESSAGE, "On ticks after the first both timeSinceStart should be valid and above 0. timeSinceStart: {timeSinceStart}, timeSinceLastTick: {timeSinceLastTick}"); + secondTickFailLog.AddConnectedValue(ConstStrings.TIME_SINCE_START, onTick, ConstStrings.TIME_SINCE_START); + secondTickFailLog.AddConnectedValue(ConstStrings.TIME_SINCE_LAST_TICK, onTick, ConstStrings.TIME_SINCE_LAST_TICK); + secondTickFailLog.AddFlow(secondTickFail); + + secondTickBranch.AddFlow(secondTickFailLog, ConstStrings.FALSE); + return g; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs.meta new file mode 100644 index 000000000..5dc7653e7 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da97217f68cac3d4bb68533a92a5b1f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef b/Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef new file mode 100644 index 000000000..0902d6588 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef @@ -0,0 +1,27 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Event", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common", + "UnityGLTF.Interactivity.Playback.Tests.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef.meta new file mode 100644 index 000000000..6d07939ea --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Event/UnityGLTF.Interactivity.Playback.Tests.Event.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8fb5e306244887744bdd3bc20a736179 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow.meta b/Tests/Runtime/Interactivity/Nodes/Flow.meta new file mode 100644 index 000000000..67682b9dc --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 81d16036e2b7ac648931ca4f0bef6627 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs new file mode 100644 index 000000000..4b18d4073 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs @@ -0,0 +1,197 @@ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using UnityEngine.Pool; +using UnityEngine.TestTools; +using UnityGLTF.Interactivity; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + partial class FlowNodesTests + { + [Test] + public void DoN() + { + QueueTest("flow/doN", GetCallerName(), "DoN Basic", "A for node is used to run the doN node (n+m) times where n is the \"n\" input value of the doN node and m is the additional number of times the for loop will try to run the doN node. Test can fail if the doN node does not correctly increment its currentCount output value or if it triggers more than \"n\" out flows. Does not test reset input flow.", CreateDoNGraph(5, 3)); + } + + [Test] + public void DoN_Reset() + { + QueueTest("flow/doN", GetCallerName(), "DoN Reset", "doN is triggered m times before its reset input flow is triggered. Afterwards it is triggered n times. Test fails if the value of current count is ever incorrect or if the total number of \"out\" flow executions is incorrect.", CreateDoNResetGraph(5, 3)); + } + + private Graph CreateDoNGraph(int n, int extraTimesToTriggerInFlow) + { + const int INITIAL_INDEX = 0; + const int START_INDEX = 0; + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var forNode = g.CreateNode("flow/for"); + var currentCountNodeEq = g.CreateNode("math/eq"); + var currentCountVarEq = g.CreateNode("math/eq"); + var doNNode = g.CreateNode("flow/doN"); + var branch = g.CreateNode("flow/branch"); + var failSend = g.CreateNode("event/send"); + var completeSend = g.CreateNode("event/send"); + var failLog = g.CreateNode("debug/log"); + var and = g.CreateNode("math/and"); + var currentCountVar = g.CreateNode("variable/get"); + + failLog.AddConfiguration(ConstStrings.MESSAGE, "Current count output value from the node was incorrect or the out flow was not triggered exactly n times as intended."); + + start.AddFlow(forNode); + + var endIndex = n + extraTimesToTriggerInFlow; + + forNode.AddConfiguration(ConstStrings.INITIAL_INDEX, INITIAL_INDEX); + forNode.AddFlow(doNNode, ConstStrings.LOOP_BODY); + forNode.AddFlow(branch, ConstStrings.COMPLETED); + forNode.AddValue(ConstStrings.START_INDEX, START_INDEX); + forNode.AddValue(ConstStrings.END_INDEX, endIndex); + + doNNode.AddValue(ConstStrings.N, n); + + var countVariable = g.AddVariable(ConstStrings.CURRENT_COUNT, 0); + + Node varSet = CreateVariableIncrementSubgraph(g, countVariable); + currentCountVar.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(ConstStrings.CURRENT_COUNT)); + + doNNode.AddFlow(varSet); + + branch.AddFlow(failLog, ConstStrings.FALSE); + branch.AddFlow(completeSend, ConstStrings.TRUE); + + branch.AddConnectedValue(ConstStrings.CONDITION, and); + + and.AddConnectedValue(ConstStrings.A, currentCountNodeEq); + and.AddConnectedValue(ConstStrings.B, currentCountVarEq); + + currentCountVarEq.AddConnectedValue(ConstStrings.A, currentCountVar); + currentCountVarEq.AddValue(ConstStrings.B, n); + + failLog.AddFlow(failSend); + + failSend.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + completeSend.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + return g; + } + + private Graph CreateDoNResetGraph(int n, int resetAfter) + { + const int COUNTER_VARIABLE_INITIAL_VALUE = 0; + const string TOTAL_COUNT = "totalCount"; + var expectedTotalCount = 2 * resetAfter; + Graph g = CreateGraphForTest(); + + var resetVariable = g.AddVariable("hasResetOnce", false); + + var start = g.CreateNode("event/onStart"); + var startTrigger = g.CreateNode("event/send"); + var triggerReceive = g.CreateNode("event/receive"); + var resetReceive = g.CreateNode("event/receive"); + var doNNode = g.CreateNode("flow/doN"); + var triggerSend = g.CreateNode("event/send"); + var resetSend = g.CreateNode("event/send"); + var failSend = g.CreateNode("event/send"); + var failTotalSend = g.CreateNode("event/send"); + var completeSend = g.CreateNode("event/send"); + var eq = g.CreateNode("math/eq"); + var currentCountGet = g.CreateNode("variable/get"); + var loopBodyBranch = g.CreateNode("flow/branch"); + var resetBranch = g.CreateNode("flow/branch"); + var resetSet = g.CreateNode("variable/set"); + var loopEq = g.CreateNode("math/eq"); + var resetGet = g.CreateNode("variable/get"); + var resetOrFinishBranch = g.CreateNode("flow/branch"); + var resetTrigger = g.CreateNode("event/send"); + var resetCounterVariable = g.CreateNode("variable/set"); + var totalCountGet = g.CreateNode("variable/get"); + var totalCountBranch = g.CreateNode("flow/branch"); + var totalCountEq = g.CreateNode("math/eq"); + var completedFailLog = g.CreateNode("debug/log"); + + completedFailLog.AddConfiguration(ConstStrings.MESSAGE, "Completed with Total Iterations Expected: {expected}, Actual: {actual}"); + completedFailLog.AddValue(ConstStrings.EXPECTED, expectedTotalCount); + + completedFailLog.AddConnectedValue(ConstStrings.ACTUAL, totalCountGet); + + resetSet.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(resetVariable)); + resetGet.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(resetVariable)); + + var currentCountVariable = g.AddVariable(ConstStrings.CURRENT_COUNT, COUNTER_VARIABLE_INITIAL_VALUE); + var totalCountVariable = g.AddVariable(TOTAL_COUNT, COUNTER_VARIABLE_INITIAL_VALUE); + + Node varSet = CreateVariableIncrementSubgraph(g, currentCountVariable); + Node totalCountSet = CreateVariableIncrementSubgraph(g, totalCountVariable); + + currentCountGet.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(ConstStrings.CURRENT_COUNT)); + resetCounterVariable.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(ConstStrings.CURRENT_COUNT)); + totalCountGet.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(TOTAL_COUNT)); + + triggerReceive.AddFlow(doNNode); + resetReceive.AddFlow(doNNode, ConstStrings.OUT, ConstStrings.RESET); + doNNode.AddFlow(totalCountSet); + totalCountSet.AddFlow(varSet); + + varSet.AddFlow(loopBodyBranch); + + doNNode.AddValue(ConstStrings.N, n); + eq.AddConnectedValue(ConstStrings.A, doNNode, ConstStrings.CURRENT_COUNT); + eq.AddConnectedValue(ConstStrings.B, currentCountGet); + + loopBodyBranch.AddConnectedValue(ConstStrings.CONDITION, eq); + + loopBodyBranch.AddFlow(resetBranch, ConstStrings.TRUE); + loopBodyBranch.AddFlow(failSend, ConstStrings.FALSE); + resetBranch.AddFlow(resetOrFinishBranch, ConstStrings.TRUE); + resetBranch.AddFlow(triggerSend, ConstStrings.FALSE); + + resetBranch.AddConnectedValue(ConstStrings.CONDITION, loopEq); + + loopEq.AddConnectedValue(ConstStrings.A, doNNode, ConstStrings.CURRENT_COUNT); + loopEq.AddValue(ConstStrings.B, resetAfter); + + resetOrFinishBranch.AddConnectedValue(ConstStrings.CONDITION, resetGet); + + resetOrFinishBranch.AddFlow(totalCountBranch, ConstStrings.TRUE); + resetOrFinishBranch.AddFlow(resetSet, ConstStrings.FALSE); + + totalCountBranch.AddConnectedValue(ConstStrings.CONDITION, totalCountEq); + totalCountBranch.AddFlow(completeSend, ConstStrings.TRUE); + totalCountBranch.AddFlow(completedFailLog, ConstStrings.FALSE); + completedFailLog.AddFlow(failTotalSend); + resetSet.AddFlow(resetCounterVariable); + resetSet.AddValue(ConstStrings.VALUE, true); + + resetCounterVariable.AddValue(ConstStrings.VALUE, COUNTER_VARIABLE_INITIAL_VALUE); + resetCounterVariable.AddFlow(resetSend); + resetSend.AddFlow(resetTrigger); + + totalCountEq.AddConnectedValue(ConstStrings.A, totalCountGet); + totalCountEq.AddValue(ConstStrings.B, expectedTotalCount); + + g.AddEvent("TriggerDoN"); + g.AddEvent("Reset"); + + start.AddFlow(startTrigger); + + startTrigger.AddConfiguration(ConstStrings.EVENT, 2); + failSend.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + failTotalSend.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + completeSend.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + triggerSend.AddConfiguration(ConstStrings.EVENT, 2); + triggerReceive.AddConfiguration(ConstStrings.EVENT, 2); + resetTrigger.AddConfiguration(ConstStrings.EVENT, 2); + resetReceive.AddConfiguration(ConstStrings.EVENT, 3); + resetSend.AddConfiguration(ConstStrings.EVENT, 3); + + return g; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs.meta new file mode 100644 index 000000000..0f4b6f1ef --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/DoNTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 842d18257a5296749986203c228f2ade +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs new file mode 100644 index 000000000..f23619984 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs @@ -0,0 +1,386 @@ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using UnityEngine.Pool; +using UnityEngine.TestTools; +using UnityGLTF.Interactivity; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public partial class FlowNodesTests : NodeTestHelpers + { + protected override string _subDirectory => "Flow"; + + [Test] + public void Branch_TrueCondition_TrueFlow() + { + QueueTest("flow/branch", GetCallerName(), "Branch", "Fails if the false flow is triggered or the true flow fails to trigger.", CreateFlowBranchGraph(true)); + } + + [Test] + public void Branch_FalseCondition_FalseFlow() + { + QueueTest("flow/branch", GetCallerName(), "Branch", "Fails if the true flow is triggered or the false flow fails to trigger.", CreateFlowBranchGraph(false)); + } + + [Test] + public void For_Starti0Endi6Initiali3_CorrectNumberOfIterationsAndCompletedFlowActivates() + { + QueueTest("flow/for", GetCallerName(), "For Test 1", "Test fails if the loopBody flow is triggered an incorrect number of times or the completed flow never triggers.", CreateForTestGraph(0, 6, 3)); + } + + [Test] + public void For_Starti0Endi4Initiali0_CorrectNumberOfIterationsAndCompletedFlowActivates() + { + QueueTest("flow/for", GetCallerName(), "For Test 2", "Test fails if the loopBody flow is triggered an incorrect number of times or the completed flow never triggers.", CreateForTestGraph(0, 4, 0)); + } + + [Test] + public void Sequence_AllFlowsActivateAndInOrder() + { + QueueTest("flow/sequence", GetCallerName(), "Sequence", "Test fails if any of the output flows does not trigger or if they occur out of order.", CreateFlowSequenceGraph(5)); + } + + [Test] + public void FlowSwitch_AllCasesActivatedOutOfOrderThenDefaultCase_AllFlowsActivated() + { + QueueTest("flow/switch", GetCallerName(), "Switch", "Runs through 4 case flows out of order and then feeds in a case value that is not present to trigger the default flow. Test fails if it's detected that not all flows have been triggered by the time the default flow is triggered.", CreateSwitchGraph()); + } + + [Test] + public void While_LoopBodyActivatesCorrectNumberOfTimesAndCompletedActivatesAtTheEnd() + { + QueueTest("flow/while", GetCallerName(), "While", "Test fails if loopBody does not activate the correct number of times or if completed flow never activates.", GenerateWhileTestGraph(5)); + } + + private Graph CreateFlowBranchGraph(bool outputSocketFromBranchNode) + { + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var branchNode = g.CreateNode("flow/branch"); + var failNode = g.CreateNode("event/send"); + var successNode = g.CreateNode("event/send"); + + failNode.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + successNode.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + onStartNode.AddFlow(branchNode); + + branchNode.AddFlow(outputSocketFromBranchNode ? successNode : failNode, ConstStrings.TRUE); + branchNode.AddFlow(outputSocketFromBranchNode ? failNode : successNode, ConstStrings.FALSE); + + branchNode.AddValue(ConstStrings.CONDITION, outputSocketFromBranchNode); + + return g; + } + + private static Graph CreateForTestGraph(int startIndex, int endIndex, int initialIndex) + { + Graph g = CreateGraphForTest(); + + var onStartnode = g.CreateNode("event/onStart"); + var forNode = g.CreateNode("flow/for"); + var loopBodyBranch = g.CreateNode("flow/branch"); + var completedBranch = g.CreateNode("flow/branch"); + var loopBodySend = g.CreateNode("event/send"); + var completedSend = g.CreateNode("event/send"); + var successSend = g.CreateNode("event/send"); + var loopBodyGet = g.CreateNode("variable/get"); + var loopBodyEq = g.CreateNode("math/eq"); + var completedEq = g.CreateNode("math/eq"); + var loopFailLog = g.CreateNode("debug/log"); + var completedFailLog = g.CreateNode("debug/log"); + + var variable = g.AddVariable(ConstStrings.INDEX, startIndex - 1); + Node varSet = CreateVariableIncrementSubgraph(g, variable); + + loopFailLog.AddConfiguration(ConstStrings.MESSAGE, "Loop Body with Index Expected: {expected}, Actual: {actual}"); + completedFailLog.AddConfiguration(ConstStrings.MESSAGE, "Completed with Index Expected: {expected}, Actual: {actual}"); + + loopBodyGet.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(variable)); + + loopBodySend.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + completedSend.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + successSend.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + onStartnode.AddFlow(forNode); + forNode.AddFlow(varSet, ConstStrings.LOOP_BODY); + forNode.AddFlow(completedBranch, ConstStrings.COMPLETED); + + forNode.AddConfiguration(ConstStrings.INITIAL_INDEX, initialIndex); + forNode.AddValue(ConstStrings.START_INDEX, startIndex); + forNode.AddValue(ConstStrings.END_INDEX, endIndex); + + varSet.AddFlow(loopBodyBranch); + loopBodyBranch.AddConnectedValue(ConstStrings.CONDITION, loopBodyEq); + + loopBodyEq.AddConnectedValue(ConstStrings.A, forNode, ConstStrings.INDEX); + loopBodyEq.AddConnectedValue(ConstStrings.B, loopBodyGet); + + loopBodyBranch.AddFlow(loopFailLog, ConstStrings.FALSE); + completedBranch.AddFlow(completedFailLog, ConstStrings.FALSE); + completedBranch.AddFlow(successSend, ConstStrings.TRUE); + + loopFailLog.AddFlow(loopBodySend); + completedFailLog.AddFlow(completedSend); + + loopFailLog.AddConnectedValue(ConstStrings.EXPECTED, loopBodyGet); + loopFailLog.AddConnectedValue(ConstStrings.ACTUAL, forNode, ConstStrings.INDEX); + + completedFailLog.AddValue(ConstStrings.EXPECTED, endIndex); + completedFailLog.AddConnectedValue(ConstStrings.ACTUAL, forNode, ConstStrings.INDEX); + + completedBranch.AddConnectedValue(ConstStrings.CONDITION, completedEq); + + completedEq.AddValue(ConstStrings.A, endIndex); + completedEq.AddConnectedValue(ConstStrings.B, forNode, ConstStrings.INDEX); + + return g; + } + + private static Node CreateVariableIncrementSubgraph(Graph g, Variable variable) + { + var variableIndex = g.IndexOfVariable(variable); + + var varSet = g.CreateNode("variable/set"); + var varSetGet = g.CreateNode("variable/get"); + var add = g.CreateNode("math/add"); + + varSet.AddConfiguration(ConstStrings.VARIABLE, variableIndex); + varSetGet.AddConfiguration(ConstStrings.VARIABLE, variableIndex); + + varSet.AddConnectedValue(ConstStrings.VALUE, add); + + add.AddValue(ConstStrings.B, 1); + add.AddConnectedValue(ConstStrings.A, varSetGet); + return varSet; + } + + private static Graph CreateFlowSequenceGraph(int numOutFlows) + { + const string EXPECTED_OUTPUT_FLOW_INDEX = "expectedOutputFlowIndex"; + + Graph g = CreateGraphForTest(); + + var indexVariable = g.AddVariable(EXPECTED_OUTPUT_FLOW_INDEX, 0); + + var onStartnode = g.CreateNode("event/onStart"); + var sequenceNode = g.CreateNode("flow/sequence"); + + onStartnode.AddFlow(sequenceNode); + + for (int i = 0; i < numOutFlows; i++) + { + var branch = g.CreateNode("flow/branch"); + var eq = g.CreateNode("math/eq"); + var varGet = g.CreateNode("variable/get"); + var completedFailLog = g.CreateNode("debug/log"); + var fail = g.CreateNode("event/send"); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + completedFailLog.AddConfiguration(ConstStrings.MESSAGE, "Completed with Total Iterations Expected: {expected}, Actual: {actual}"); + completedFailLog.AddValue(ConstStrings.EXPECTED, i); + completedFailLog.AddConnectedValue(ConstStrings.ACTUAL, varGet); + + varGet.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(indexVariable)); + + eq.AddConnectedValue(ConstStrings.A, varGet); + eq.AddValue(ConstStrings.B, i); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + branch.AddFlow(completedFailLog, ConstStrings.FALSE); + + completedFailLog.AddFlow(fail); + + if (i < numOutFlows - 1) + { + Node varSet = CreateVariableIncrementSubgraph(g, indexVariable); + + branch.AddFlow(varSet, ConstStrings.TRUE); + } + else + { + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + branch.AddFlow(complete, ConstStrings.TRUE); + } + + sequenceNode.AddFlow(branch, ConstStrings.Numbers[i]); + } + + return g; + } + + private static Graph GenerateWhileTestGraph(int iterations) + { + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var whileNode = g.CreateNode("flow/while"); + var get = g.CreateNode("variable/get"); + var lt = g.CreateNode("math/lt"); + var eq = g.CreateNode("math/eq"); + var branch = g.CreateNode("flow/branch"); + var failLog = g.CreateNode("debug/log"); + var fail = g.CreateNode("event/send"); + var completed = g.CreateNode("event/send"); + + failLog.AddConfiguration(ConstStrings.MESSAGE, "Completed with iterations Expected: {expected}, Actual: {actual}"); + failLog.AddValue(ConstStrings.EXPECTED, iterations); + failLog.AddConnectedValue(ConstStrings.ACTUAL, get); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + completed.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var indexVariable = g.AddVariable("count", 0); + + Node varSet = CreateVariableIncrementSubgraph(g, indexVariable); + + get.AddConfiguration(ConstStrings.VARIABLE, g.IndexOfVariable(indexVariable)); + + start.AddFlow(whileNode); + whileNode.AddFlow(varSet, ConstStrings.LOOP_BODY); + whileNode.AddFlow(branch, ConstStrings.COMPLETED); + whileNode.AddConnectedValue(ConstStrings.CONDITION, lt); + + lt.AddConnectedValue(ConstStrings.A, get); + lt.AddValue(ConstStrings.B, iterations); + + eq.AddConnectedValue(ConstStrings.A, get); + eq.AddValue(ConstStrings.B, iterations); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + branch.AddFlow(completed, ConstStrings.TRUE); + branch.AddFlow(failLog, ConstStrings.FALSE); + + failLog.AddFlow(fail); + + return g; + } + + private static Node CreateNaNCheckSubGraph(Graph g) + { + var start = g.CreateNode("event/onStart"); + var branch = g.CreateNode("flow/branch"); + var isNaN = g.CreateNode("math/isnan"); + var failLog = g.CreateNode("debug/log"); + var fail = g.CreateNode("event/send"); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + start.AddFlow(branch); + branch.AddConnectedValue(ConstStrings.CONDITION, isNaN); + branch.AddFlow(failLog, ConstStrings.FALSE); + + failLog.AddFlow(fail); + failLog.AddConfiguration(ConstStrings.MESSAGE, "lastRemainingTime should be NaN before the first activation of throttle node's out flow."); + + return isNaN; + } + + private static Graph CreateSwitchGraph() + { + Graph g = CreateGraphForTest(); + var outputFlowOrder = new int[] { 3, 2, -9, 1 }; + + g.AddEvent("trigger"); // 2 + + var flowIndexVariable = g.AddVariable("outputFlowIndex", 0); + var bitmaskVariable = g.AddVariable("bitmask", 0); + + var flowIndexVariableIndex = g.IndexOfVariable(flowIndexVariable); + var bitmaskVariableIndex = g.IndexOfVariable(bitmaskVariable); + + var onStartnode = g.CreateNode("event/onStart"); + var triggerSend = g.CreateNode("event/send"); + + triggerSend.AddConfiguration(ConstStrings.EVENT, 2); + onStartnode.AddFlow(triggerSend); + + var receiveTrigger = g.CreateNode("event/receive"); + var switchNode = g.CreateNode("flow/switch"); + var getOutputFlow = g.CreateNode("variable/get"); + + switchNode.AddConfiguration(ConstStrings.CASES, new int[] { 0, 1, 2, 3 }); + + receiveTrigger.AddConfiguration(ConstStrings.EVENT, 2); + receiveTrigger.AddFlow(switchNode); + + getOutputFlow.AddConfiguration(ConstStrings.VARIABLE, flowIndexVariableIndex); + + switchNode.AddConnectedValue(ConstStrings.SELECTION, getOutputFlow); + + var expected = 0; + + for (int i = 0; i < outputFlowOrder.Length; i++) + { + var subGraphEntry = CreateSwitchFlowSubGraph(g, outputFlowOrder[i], 1 << i, flowIndexVariableIndex, bitmaskVariableIndex); + switchNode.AddFlow(subGraphEntry, ConstStrings.Numbers[i]); + + expected |= 1 << i; + } + + var branch = g.CreateNode("flow/branch"); + var bitmaskGet = g.CreateNode("variable/get"); + var eq = g.CreateNode("math/eq"); + + bitmaskGet.AddConfiguration(ConstStrings.VARIABLE, bitmaskVariableIndex); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + + eq.AddConnectedValue(ConstStrings.A, bitmaskGet); + eq.AddValue(ConstStrings.B, expected); + + switchNode.AddFlow(branch, ConstStrings.DEFAULT); + + var fail = g.CreateNode("event/send"); + var completed = g.CreateNode("event/send"); + var logFail = g.CreateNode("debug/log"); + + fail.AddConfiguration(ConstStrings.EVENT, 0); + completed.AddConfiguration(ConstStrings.EVENT, 1); + + branch.AddFlow(completed, ConstStrings.TRUE); + branch.AddFlow(logFail, ConstStrings.FALSE); + + logFail.AddConfiguration(ConstStrings.MESSAGE, "Completed with Bitmask Expected: {expected}, Actual: {actual}"); + logFail.AddValue(ConstStrings.EXPECTED, expected); + logFail.AddConnectedValue(ConstStrings.ACTUAL, bitmaskGet); + + logFail.AddFlow(fail); + + return g; + } + + private static Node CreateSwitchFlowSubGraph(Graph g, int nextFlowIndex, int bitmask, int flowIndexVariableIndex, int bitmaskVariableIndex) + { + var setOutputFlow = g.CreateNode("variable/set"); + setOutputFlow.AddConfiguration(ConstStrings.VARIABLE, flowIndexVariableIndex); + setOutputFlow.AddValue(ConstStrings.VALUE, nextFlowIndex); + + var bitmaskSet = g.CreateNode("variable/set"); + var add = g.CreateNode("math/add"); + var bitmaskGet = g.CreateNode("variable/get"); + var send = g.CreateNode("event/send"); + + send.AddConfiguration(ConstStrings.EVENT, 2); + + setOutputFlow.AddFlow(bitmaskSet); + + bitmaskSet.AddConfiguration(ConstStrings.VARIABLE, bitmaskVariableIndex); + bitmaskGet.AddConfiguration(ConstStrings.VARIABLE, bitmaskVariableIndex); + + bitmaskSet.AddConnectedValue(ConstStrings.VALUE, add); + add.AddValue(ConstStrings.A, bitmask); + add.AddConnectedValue(ConstStrings.B, bitmaskGet); + bitmaskSet.AddFlow(send); + + return setOutputFlow; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs.meta new file mode 100644 index 000000000..23d5496c7 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f4405f955d6ab845bea554de4ed3b08 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs new file mode 100644 index 000000000..4efca8c3e --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs @@ -0,0 +1,279 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + partial class FlowNodesTests + { + [Test] + public void MultiGate_RunAllOutputsOnce_AllOutputsRun() + { + QueueTest("flow/multiGate", GetCallerName(), "MultiGate Basic", "Runs all output flows once. Test fails if all output flows do not occur.", GenerateMultiGateTestGraph(5)); + } + + [Test] + public void MultiGate_IsLoop_RunsAllOutputFlowsInALoop() + { + QueueTest("flow/multiGate", GetCallerName(), "MultiGate IsLoop", "Triggers the in flow x times. Test fails if x output flow activations in total do not occur for x in flows or if lastIndex is incorrect after an output flow is activated.", GenerateMultiGateLoopTestGraph(5, 35)); + } + + [Test] + public void MultiGate_ResetAfterMoreActivationsThanOutputs_CorrectNumberOfOutputFlowsOccur() + { + QueueTest("flow/multiGate", GetCallerName(), "MultiGate Reset", "Triggers the in flow 9x with reset triggered after the 8th in flow. Test fails if an incorrect number of output flow activations occur.", GenerateMultiGateResetTestGraph(5, 8, 10)); + } + + private static Graph GenerateMultiGateTestGraph(int outputs) + { + Graph g = CreateGraphForTest(); + + g.AddEvent("trigger"); // 2 + + var start = g.CreateNode("event/onStart"); + var send = g.CreateNode("event/send"); + + send.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + start.AddFlow(send); + + var receive = g.CreateNode("event/receive"); + var multiGate = g.CreateNode("flow/multiGate"); + + multiGate.AddConfiguration(ConstStrings.IS_RANDOM, false); + multiGate.AddConfiguration(ConstStrings.IS_LOOP, false); + receive.AddConfiguration(ConstStrings.EVENT, 2); + receive.AddFlow(multiGate); + + var flowIndexVariable = g.AddVariable("outputFlowIndex", 0); + var bitmaskVariable = g.AddVariable("bitmask", 0); + + var flowIndexVariableIndex = g.IndexOfVariable(flowIndexVariable); + var bitmaskVariableIndex = g.IndexOfVariable(bitmaskVariable); + + var expected = 0; + + for (int i = 0; i < outputs - 1; i++) + { + var subGraphEntry = CreateMultiGateFlowSubGraph(g, 1 << i, flowIndexVariableIndex, bitmaskVariableIndex); + multiGate.AddFlow(subGraphEntry, ConstStrings.Numbers[i]); + + expected |= 1 << i; + } + + var branch = g.CreateNode("flow/branch"); + var bitmaskGet = g.CreateNode("variable/get"); + var eq = g.CreateNode("math/eq"); + + bitmaskGet.AddConfiguration(ConstStrings.VARIABLE, bitmaskVariableIndex); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + + eq.AddConnectedValue(ConstStrings.A, bitmaskGet); + eq.AddValue(ConstStrings.B, expected); + + multiGate.AddFlow(branch, ConstStrings.Numbers[outputs - 1]); + + var fail = g.CreateNode("event/send"); + var completed = g.CreateNode("event/send"); + var logFail = g.CreateNode("debug/log"); + + fail.AddConfiguration(ConstStrings.EVENT, 0); + completed.AddConfiguration(ConstStrings.EVENT, 1); + + branch.AddFlow(completed, ConstStrings.TRUE); + branch.AddFlow(logFail, ConstStrings.FALSE); + + logFail.AddConfiguration(ConstStrings.MESSAGE, "Completed with Bitmask Expected: {expected}, Actual: {actual}"); + logFail.AddValue(ConstStrings.EXPECTED, expected); + logFail.AddConnectedValue(ConstStrings.ACTUAL, bitmaskGet); + + logFail.AddFlow(fail); + + return g; + } + + private static Node CreateMultiGateFlowSubGraph(Graph g, int bitmask, int flowIndexVariableIndex, int bitmaskVariableIndex) + { + var bitmaskSet = g.CreateNode("variable/set"); + var add = g.CreateNode("math/add"); + var bitmaskGet = g.CreateNode("variable/get"); + var send = g.CreateNode("event/send"); + + send.AddConfiguration(ConstStrings.EVENT, 2); + + bitmaskSet.AddConfiguration(ConstStrings.VARIABLE, bitmaskVariableIndex); + bitmaskGet.AddConfiguration(ConstStrings.VARIABLE, bitmaskVariableIndex); + + bitmaskSet.AddConnectedValue(ConstStrings.VALUE, add); + add.AddValue(ConstStrings.A, bitmask); + add.AddConnectedValue(ConstStrings.B, bitmaskGet); + bitmaskSet.AddFlow(send); + + return bitmaskSet; + } + + private static Graph GenerateMultiGateLoopTestGraph(int outputs, int iterations) + { + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var forNode = g.CreateNode("flow/for"); + + forNode.AddConfiguration(ConstStrings.INITIAL_INDEX, 0); + forNode.AddValue(ConstStrings.START_INDEX, 0); + forNode.AddValue(ConstStrings.END_INDEX, iterations); + + start.AddFlow(forNode); + + var multiGate = g.CreateNode("flow/multiGate"); + + multiGate.AddConfiguration(ConstStrings.IS_RANDOM, false); + multiGate.AddConfiguration(ConstStrings.IS_LOOP, true); + + forNode.AddFlow(multiGate, ConstStrings.LOOP_BODY); + + var outFlowActivationsVariable = g.AddVariable("outFlowActivations", 0); + var outFlowActivationsVariableIndex = g.IndexOfVariable(outFlowActivationsVariable); + + for (int i = 0; i < outputs; i++) + { + Node outFlowCounter = CreateVariableIncrementSubgraph(g, outFlowActivationsVariable); + multiGate.AddFlow(outFlowCounter, ConstStrings.Numbers[i]); + + var lastIndexBranch = g.CreateNode("flow/branch"); + var lastIndexEq = g.CreateNode("math/eq"); + var lastIndexFail = g.CreateNode("event/send"); + var lastIndexLog = g.CreateNode("debug/log"); + lastIndexFail.AddConfiguration(ConstStrings.EVENT, 0); + + outFlowCounter.AddFlow(lastIndexBranch); + + lastIndexEq.AddValue(ConstStrings.A, i); + lastIndexEq.AddConnectedValue(ConstStrings.B, multiGate, ConstStrings.LAST_INDEX); + + lastIndexBranch.AddConnectedValue(ConstStrings.CONDITION, lastIndexEq); + + lastIndexLog.AddConfiguration(ConstStrings.MESSAGE, "lastIndex Expected: {expected}, Actual: {actual}"); + lastIndexLog.AddValue(ConstStrings.EXPECTED, i); + lastIndexLog.AddConnectedValue(ConstStrings.ACTUAL, multiGate, ConstStrings.LAST_INDEX); + + lastIndexLog.AddFlow(lastIndexFail); + lastIndexBranch.AddFlow(lastIndexLog, ConstStrings.FALSE); + } + + var branch = g.CreateNode("flow/branch"); + var fail = g.CreateNode("event/send"); + var completed = g.CreateNode("event/send"); + var logFail = g.CreateNode("debug/log"); + var eq = g.CreateNode("math/eq"); + var get = g.CreateNode("variable/get"); + + get.AddConfiguration(ConstStrings.VARIABLE, outFlowActivationsVariableIndex); + + fail.AddConfiguration(ConstStrings.EVENT, 0); + completed.AddConfiguration(ConstStrings.EVENT, 1); + + branch.AddFlow(completed, ConstStrings.TRUE); + branch.AddFlow(logFail, ConstStrings.FALSE); + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + + eq.AddValue(ConstStrings.A, iterations); + eq.AddConnectedValue(ConstStrings.B, get); + + logFail.AddConfiguration(ConstStrings.MESSAGE, "Completed with output activations Expected: {expected}, Actual: {actual}"); + logFail.AddValue(ConstStrings.EXPECTED, iterations); + logFail.AddConnectedValue(ConstStrings.ACTUAL, get); + + logFail.AddFlow(fail); + + forNode.AddFlow(branch, ConstStrings.COMPLETED); + + return g; + } + + private static Graph GenerateMultiGateResetTestGraph(int outputs, int resetIteration, int totalIterations) + { + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var forNode = g.CreateNode("flow/for"); + + forNode.AddConfiguration(ConstStrings.INITIAL_INDEX, 0); + forNode.AddValue(ConstStrings.START_INDEX, 0); + forNode.AddValue(ConstStrings.END_INDEX, totalIterations); + + start.AddFlow(forNode); + + var eq = g.CreateNode("math/eq"); + eq.AddValue(ConstStrings.A, resetIteration); + eq.AddConnectedValue(ConstStrings.B, forNode, ConstStrings.INDEX); + + var multiGate = g.CreateNode("flow/multiGate"); + + multiGate.AddConfiguration(ConstStrings.IS_RANDOM, false); + multiGate.AddConfiguration(ConstStrings.IS_LOOP, false); + + var branch = g.CreateNode("flow/branch"); + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + + forNode.AddFlow(branch, ConstStrings.LOOP_BODY); + branch.AddFlow(multiGate, ConstStrings.FALSE, ConstStrings.IN); + branch.AddFlow(multiGate, ConstStrings.TRUE, ConstStrings.RESET); + + var outFlowActivationsVariable = g.AddVariable("outFlowActivations", 0); + var outFlowActivationsVariableIndex = g.IndexOfVariable(outFlowActivationsVariable); + + for (int i = 0; i < outputs; i++) + { + Node outFlowCounter = CreateVariableIncrementSubgraph(g, outFlowActivationsVariable); + multiGate.AddFlow(outFlowCounter, ConstStrings.Numbers[i]); + } + + var completeBranch = g.CreateNode("flow/branch"); + var fail = g.CreateNode("event/send"); + var completed = g.CreateNode("event/send"); + var logFail = g.CreateNode("debug/log"); + var completeEq = g.CreateNode("math/eq"); + var get = g.CreateNode("variable/get"); + + get.AddConfiguration(ConstStrings.VARIABLE, outFlowActivationsVariableIndex); + + fail.AddConfiguration(ConstStrings.EVENT, 0); + completed.AddConfiguration(ConstStrings.EVENT, 1); + + completeBranch.AddFlow(completed, ConstStrings.TRUE); + completeBranch.AddFlow(logFail, ConstStrings.FALSE); + completeBranch.AddConnectedValue(ConstStrings.CONDITION, completeEq); + + var expected = 0; + var counter = 0; + for (int i = 0; i < totalIterations; i++) + { + if (counter < outputs) + { + expected++; + counter++; + } + + if (i == resetIteration) + { + counter = 0; + } + } + + completeEq.AddValue(ConstStrings.A, expected); + completeEq.AddConnectedValue(ConstStrings.B, get); + + logFail.AddConfiguration(ConstStrings.MESSAGE, "Completed with output activations Expected: {expected}, Actual: {actual}"); + logFail.AddValue(ConstStrings.EXPECTED, expected); + logFail.AddConnectedValue(ConstStrings.ACTUAL, get); + + logFail.AddFlow(fail); + + forNode.AddFlow(completeBranch, ConstStrings.COMPLETED); + + return g; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs.meta new file mode 100644 index 000000000..6c003037b --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee3c297146a376f459d5d9c80e874024 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs new file mode 100644 index 000000000..c32ef954e --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs @@ -0,0 +1,239 @@ +using NUnit.Framework; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public partial class FlowNodesTests : NodeTestHelpers + { + [Test] + public void SetDelay_OutFlowTriggers_DoneFlowTriggersAfterDuration_ErrFlowDoesNotTrigger() + { + const float DURATION = 0.75f; + const float EXTRA_EXECUTION_TIME = 0.25f; + QueueTest("flow/setDelay", GetCallerName(), "SetDelay Basic", "Test fails if: err flow triggers, out flow does not trigger, done flow does not trigger, done flow triggers at the wrong time.", CreateSetDelayGraph(DURATION, EXTRA_EXECUTION_TIME)); + } + + [Test] + public void SetDelay_InfNaNNegativeDuration_ActivatesErrFlow() + { + QueueTest("flow/setDelay", GetCallerName(), "SetDelay Invalid Duration", "Activates setDelay nodes with negative, infinite, and NaN durations. Test fails if out or done flow is activated for any of the three duration inputs or if the err flow is not activated for all three inputs.", GenerateSetDelayInvalidDurationTestGraph()); + } + + [Test] + public void SetDelay_CancelInputMidDelay_DoneFlowDoesNotTrigger() + { + const float DURATION = 0.75f; + const float CANCEL_TIME = 0.5f; + const float EXTRA_EXECUTION_TIME = 0.15f; + QueueTest("flow/setDelay", GetCallerName(), "SetDelay Cancel Input", "Cancel input flow on the setDelay is activated during the delay. Test fails if the done output flow triggers during a 1s long test.", CreateSetDelayCancelGraph(DURATION, CANCEL_TIME, EXTRA_EXECUTION_TIME)); + } + + [Test] + public void CancelDelay_SetDelayActivatedCancelDelayActivatedAfterwards_SetDelayDoneFlowDoesNotTrigger() + { + const float DURATION = 0.75f; + const float CANCEL_TIME = 0.5f; + const float EXTRA_EXECUTION_TIME = 0.15f; + QueueTest("flow/cancelDelay", GetCallerName(), "CancelDelay", "Delay is activated and then cancelDelay node activates with delayIndex = lastDelayIndex from the setDelay node after a short delay. Test fails if the setDelay node done output flow triggers during a 1s long test.", CreateCancelDelayGraph(DURATION, CANCEL_TIME, EXTRA_EXECUTION_TIME)); + } + + private Graph CreateSetDelayGraph(float duration, float extraExecutionTime) + { + Graph g = CreateGraphForTest(); + var outVar = g.AddVariable("outTriggered", false); + g.AddVariable("doneTriggered", false); + g.AddVariable("testDuration", duration + extraExecutionTime); + + var onStartnode = g.CreateNode("event/onStart"); + var setDelayNode = g.CreateNode("flow/setDelay"); + var outVarSet = g.CreateNode("variable/set"); + var outVarGet = g.CreateNode("variable/get"); + var errSendNode = g.CreateNode("event/send"); + var doneSendNode = g.CreateNode("event/send"); + var doneBranch = g.CreateNode("flow/branch"); + var errFlowLog = g.CreateNode("debug/log"); + var missedOutFlowLog = g.CreateNode("debug/log"); + var failMissedOutFlow = g.CreateNode("event/send"); + + errFlowLog.AddConfiguration(ConstStrings.MESSAGE, "Err flow triggered with valid duration value."); + missedOutFlowLog.AddConfiguration(ConstStrings.MESSAGE, "Done flow triggered but out flow was never triggered."); + + var varIndex = g.IndexOfVariable(outVar); + outVarSet.AddConfiguration(ConstStrings.VARIABLE, varIndex); + outVarGet.AddConfiguration(ConstStrings.VARIABLE, varIndex); + outVarSet.AddValue(ConstStrings.VALUE, true); + + errSendNode.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + failMissedOutFlow.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + doneSendNode.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + onStartnode.AddFlow(setDelayNode); + + setDelayNode.AddFlow(outVarSet); + setDelayNode.AddFlow(errFlowLog, ConstStrings.ERR); + setDelayNode.AddFlow(doneBranch, ConstStrings.DONE); + errFlowLog.AddFlow(errSendNode); + + setDelayNode.AddValue(ConstStrings.DURATION, duration); + + doneBranch.AddFlow(doneSendNode, ConstStrings.TRUE); + doneBranch.AddFlow(missedOutFlowLog, ConstStrings.FALSE); + doneBranch.AddConnectedValue(ConstStrings.CONDITION, outVarGet); + missedOutFlowLog.AddFlow(failMissedOutFlow); + + return g; + } + + private Graph CreateSetDelayCancelGraph(float duration, float cancelTime, float extraExecutionTime) + { + Graph g = CreateGraphForTest(); + var outVar = g.AddVariable("cancelActivated", false); + var outVarIndex = g.IndexOfVariable(outVar); + + var onStart = g.CreateNode("event/onStart"); + var setDelay = g.CreateNode("flow/setDelay"); + var doneFailLog = g.CreateNode("debug/log"); + var doneFail = g.CreateNode("event/send"); + + doneFailLog.AddConfiguration(ConstStrings.MESSAGE, "Done flow was activated even though it should have been canceled."); + doneFailLog.AddFlow(doneFail); + doneFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + setDelay.AddValue(ConstStrings.DURATION, duration); + onStart.AddFlow(setDelay); + setDelay.AddFlow(doneFailLog, ConstStrings.DONE); + + var onTick = g.CreateNode("event/onTick"); + var timeBranch = g.CreateNode("flow/branch"); + var ge = g.CreateNode("math/ge"); + + ge.AddValue(ConstStrings.B, cancelTime); + ge.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + + var branch = g.CreateNode("flow/branch"); + var get = g.CreateNode("variable/get"); + + get.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + branch.AddConnectedValue(ConstStrings.CONDITION, get); + + onTick.AddFlow(branch); + branch.AddFlow(timeBranch, ConstStrings.FALSE); + var set = g.CreateNode("variable/set"); + set.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + set.AddValue(ConstStrings.VALUE, true); + + timeBranch.AddConnectedValue(ConstStrings.CONDITION, ge); + timeBranch.AddFlow(set, ConstStrings.TRUE); + + set.AddFlow(setDelay, ConstStrings.OUT, ConstStrings.CANCEL); + + var completedBranch = g.CreateNode("flow/branch"); + var completedge = g.CreateNode("math/ge"); + + completedge.AddValue(ConstStrings.B, duration + extraExecutionTime); + completedge.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + completedBranch.AddConnectedValue(ConstStrings.CONDITION, completedge); + completedBranch.AddFlow(complete, ConstStrings.TRUE); + branch.AddFlow(completedBranch, ConstStrings.TRUE); + + return g; + } + + private Graph CreateCancelDelayGraph(float duration, float cancelTime, float extraExecutionTime) + { + Graph g = CreateGraphForTest(); + var outVar = g.AddVariable("cancelActivated", false); + var outVarIndex = g.IndexOfVariable(outVar); + + var onStart = g.CreateNode("event/onStart"); + var setDelay = g.CreateNode("flow/setDelay"); + var doneFailLog = g.CreateNode("debug/log"); + var doneFail = g.CreateNode("event/send"); + + doneFailLog.AddConfiguration(ConstStrings.MESSAGE, "Done flow was activated even though it should have been canceled."); + doneFailLog.AddFlow(doneFail); + doneFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + setDelay.AddValue(ConstStrings.DURATION, duration); + onStart.AddFlow(setDelay); + setDelay.AddFlow(doneFailLog, ConstStrings.DONE); + + var onTick = g.CreateNode("event/onTick"); + var cancelDelay = g.CreateNode("flow/cancelDelay"); + var timeBranch = g.CreateNode("flow/branch"); + var ge = g.CreateNode("math/ge"); + + ge.AddValue(ConstStrings.B, cancelTime); + ge.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + + timeBranch.AddConnectedValue(ConstStrings.CONDITION, ge); + timeBranch.AddFlow(cancelDelay, ConstStrings.TRUE); + + var branch = g.CreateNode("flow/branch"); + var get = g.CreateNode("variable/get"); + + get.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + branch.AddConnectedValue(ConstStrings.CONDITION, get); + + onTick.AddFlow(branch); + branch.AddFlow(timeBranch, ConstStrings.FALSE); + var set = g.CreateNode("variable/set"); + set.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + set.AddValue(ConstStrings.VALUE, true); + + cancelDelay.AddConnectedValue(ConstStrings.DELAY_INDEX, setDelay, ConstStrings.LAST_DELAY_INDEX); + cancelDelay.AddFlow(set); + + var completedBranch = g.CreateNode("flow/branch"); + var completedge = g.CreateNode("math/ge"); + + completedge.AddValue(ConstStrings.B, duration + extraExecutionTime); + completedge.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + completedBranch.AddConnectedValue(ConstStrings.CONDITION, completedge); + completedBranch.AddFlow(complete, ConstStrings.TRUE); + branch.AddFlow(completedBranch, ConstStrings.TRUE); + + return g; + } + + private static Graph GenerateSetDelayInvalidDurationTestGraph() + { + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var success = g.CreateNode("event/send"); + success.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var setDelayNeg = CreateSetDelayInvalidDurationSubGraph(g, -1f); + var setDelayInf = CreateSetDelayInvalidDurationSubGraph(g, float.PositiveInfinity); + var setDelayNaN = CreateSetDelayInvalidDurationSubGraph(g, float.NaN); + + start.AddFlow(setDelayNeg); + setDelayNeg.AddFlow(setDelayInf, ConstStrings.ERR); + setDelayInf.AddFlow(setDelayNaN, ConstStrings.ERR); + setDelayNaN.AddFlow(success, ConstStrings.ERR); + + return g; + } + + private static Node CreateSetDelayInvalidDurationSubGraph(Graph g, float invalidDuration) + { + var setDelay = g.CreateNode("flow/setDelay"); + setDelay.AddValue(ConstStrings.DURATION, invalidDuration); + + var logFailOut = CreateFailSubGraph(g, $"Out flow was activated despite using {invalidDuration} duration."); + var logFailDone = CreateFailSubGraph(g, $"Done flow was activated despite using {invalidDuration} duration."); + + setDelay.AddFlow(logFailOut); + setDelay.AddFlow(logFailDone, ConstStrings.DONE); + return setDelay; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs.meta new file mode 100644 index 000000000..2793e40d5 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/SetDelayTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 499c2131796a12e45a6f90391aaaad0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs new file mode 100644 index 000000000..a327adec9 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs @@ -0,0 +1,300 @@ +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using UnityEngine.Pool; +using UnityEngine.TestTools; +using UnityGLTF.Interactivity; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + partial class FlowNodesTests + { + [Test] + public void Throttle_Basic() + { + var THROTTLE_DURATION = 0.5f; + var TEST_DURATION = 0.75f; + QueueTest("flow/throttle", GetCallerName(), "Throttle Basic", "Test fails if err flow is activated, if the out flow is activated more than twice, or lastRemainingTime is not NaN before the in flow is activated. Does not test reset input flow.", GenerateThrottleTestGraph(THROTTLE_DURATION, TEST_DURATION)); + } + + [Test] + public void Throttle_InfNaNNegativeDuration_ActivatesErrFlow() + { + QueueTest("flow/throttle", GetCallerName(), "Throttle Invalid Duration", "Test fails if out flow is activated for any of the three duration inputs or if the err flow is not activated for all three inputs.", GenerateThrottleInvalidDurationTestGraph()); + } + + [Test] + public void Throttle_ActivateNormally_LastRemainingTimeCorrect() + { + var THROTTLE_DURATION = 0.5f; + var TEST_DURATION = 0.4f; + QueueTest("flow/throttle", GetCallerName(), "Throttle LastRemainingTime Valid", "Test fails if lastRemainingTime is non-zero after the first activation of throttle out flow or if lastRemainingTime is incorrect for the next 0.4s.", GenerateThrottleLastRemainingTimeTestGraph(THROTTLE_DURATION, TEST_DURATION)); + } + + [Test] + public void Throttle_Reset_LastRemainingTimeIsNaN() + { + var THROTTLE_DURATION = 1f; + var RESET_TIME = 0.5f; + QueueTest("flow/throttle", GetCallerName(), "Throttle Reset", "Test fails if lastRemainingTime is not NaN after activating reset input.", GenerateThrottleResetTestGraph(THROTTLE_DURATION, RESET_TIME)); + } + + private static Graph GenerateThrottleTestGraph(float duration, float testDuration) + { + Graph g = CreateGraphForTest(); + + var isNaN = CreateNaNCheckSubGraph(g); + var throttle = g.CreateNode("flow/throttle"); + throttle.AddValue(ConstStrings.DURATION, duration); + isNaN.AddConnectedValue(ConstStrings.A, throttle, ConstStrings.LAST_REMAINING_TIME); + + var onTick = g.CreateNode("event/onTick"); + onTick.AddFlow(throttle); + + var errLog = g.CreateNode("debug/log"); + var err = g.CreateNode("event/send"); + + err.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + errLog.AddFlow(err); + errLog.AddConfiguration(ConstStrings.MESSAGE, "Err flow was activated which should not occur in this test."); + throttle.AddFlow(errLog, ConstStrings.ERR); + + var outFlowActivationsVariable = g.AddVariable("outFlowActivations", 0); + var outFlowActivationsVariableIndex = g.IndexOfVariable(outFlowActivationsVariable); + Node outFlowCounter = CreateVariableIncrementSubgraph(g, outFlowActivationsVariable); + + throttle.AddFlow(outFlowCounter); + + var setDelayStart = g.CreateNode("event/onStart"); + var setDelay = g.CreateNode("flow/setDelay"); + + setDelayStart.AddFlow(setDelay); + setDelay.AddValue(ConstStrings.DURATION, testDuration); + + var completionBranch = g.CreateNode("flow/branch"); + var eq = g.CreateNode("math/eq"); + completionBranch.AddConnectedValue(ConstStrings.CONDITION, eq); + setDelay.AddFlow(completionBranch, ConstStrings.DONE); + + var get = g.CreateNode("variable/get"); + get.AddConfiguration(ConstStrings.VARIABLE, outFlowActivationsVariableIndex); + + var expectedIterations = Mathf.FloorToInt(testDuration / duration) + 1; + + eq.AddConnectedValue(ConstStrings.A, get); + eq.AddValue(ConstStrings.B, expectedIterations); + + var incorrectIterationsLog = g.CreateNode("debug/log"); + var fail = g.CreateNode("event/send"); + var completed = g.CreateNode("event/send"); + + incorrectIterationsLog.AddFlow(fail); + incorrectIterationsLog.AddConfiguration(ConstStrings.MESSAGE, "Number of iterations, Expected: {expected}, Actual: {actual}"); + incorrectIterationsLog.AddValue(ConstStrings.EXPECTED, expectedIterations); + incorrectIterationsLog.AddConnectedValue(ConstStrings.ACTUAL, get); + + completionBranch.AddFlow(completed, ConstStrings.TRUE); + completionBranch.AddFlow(incorrectIterationsLog, ConstStrings.FALSE); + + completed.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + return g; + } + + private static Graph GenerateThrottleLastRemainingTimeTestGraph(float duration, float testDuration) + { + Graph g = CreateGraphForTest(); + + var throttleTick = g.CreateNode("event/onTick"); + var throttle = g.CreateNode("flow/throttle"); + + throttle.AddValue(ConstStrings.DURATION, duration); + throttleTick.AddFlow(throttle); + + var onTick = g.CreateNode("event/onTick"); + var isFirstFrame = g.CreateNode("flow/branch"); + + var frameCountVariable = g.AddVariable("frameCount", 0); + var frameCountVariableIndex = g.IndexOfVariable(frameCountVariable); + Node frameCounter = CreateVariableIncrementSubgraph(g, frameCountVariable); + + onTick.AddFlow(frameCounter); + frameCounter.AddFlow(isFirstFrame); + + var frameGet = g.CreateNode("variable/get"); + frameGet.AddConfiguration(ConstStrings.VARIABLE, frameCountVariableIndex); + + var eq = g.CreateNode("math/eq"); + eq.AddValue(ConstStrings.A, 1); + eq.AddConnectedValue(ConstStrings.B, frameGet); + + isFirstFrame.AddConnectedValue(ConstStrings.CONDITION, eq); + + var firstFrameBranch = g.CreateNode("flow/branch"); + var firstFrameEq = g.CreateNode("math/eq"); + firstFrameEq.AddValue(ConstStrings.A, 0f); + firstFrameEq.AddConnectedValue(ConstStrings.B, throttle, ConstStrings.LAST_REMAINING_TIME); + + firstFrameBranch.AddConnectedValue(ConstStrings.CONDITION, firstFrameEq); + + isFirstFrame.AddFlow(firstFrameBranch, ConstStrings.TRUE); + + var badFirstFrameLog = g.CreateNode("debug/log"); + var badFirstFrameFail = g.CreateNode("event/send"); + + badFirstFrameLog.AddFlow(badFirstFrameFail); + badFirstFrameLog.AddConfiguration(ConstStrings.MESSAGE, "Last Remaining Time on first activation should be 0, Actual: {actual}"); + badFirstFrameLog.AddConnectedValue(ConstStrings.ACTUAL, throttle, ConstStrings.LAST_REMAINING_TIME); + badFirstFrameFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + firstFrameBranch.AddFlow(badFirstFrameLog, ConstStrings.FALSE); + + var approxSubGraph = new ApproximatelySubGraph(0.001f); + (var approxIn, var approxOut) = approxSubGraph.CreateSubGraph(g); + + var sub = g.CreateNode("math/sub"); + var rem = g.CreateNode("math/rem"); + + sub.AddValue(ConstStrings.A, duration); + sub.AddConnectedValue(ConstStrings.B, onTick, ConstStrings.TIME_SINCE_START); + + rem.AddConnectedValue(ConstStrings.A, sub); + rem.AddValue(ConstStrings.B, duration); + + approxIn.AddConnectedValue(ConstStrings.A, throttle, ConstStrings.LAST_REMAINING_TIME); + approxIn.AddConnectedValue(ConstStrings.B, rem); + + var badTimeLog = g.CreateNode("debug/log"); + var badTimeFail = g.CreateNode("event/send"); + var timeLog = g.CreateNode("debug/log"); + timeLog.AddConfiguration(ConstStrings.MESSAGE, "Last Remaining Time Expected {expected}, Actual: {actual}"); + timeLog.AddConnectedValue(ConstStrings.EXPECTED, rem); + timeLog.AddConnectedValue(ConstStrings.ACTUAL, throttle, ConstStrings.LAST_REMAINING_TIME); + badTimeLog.AddFlow(badTimeFail); + badTimeLog.AddConfiguration(ConstStrings.MESSAGE, "Last Remaining Time Expected {expected}, Actual: {actual}"); + badTimeLog.AddConnectedValue(ConstStrings.EXPECTED, rem); + badTimeLog.AddConnectedValue(ConstStrings.ACTUAL, throttle, ConstStrings.LAST_REMAINING_TIME); + badTimeFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + isFirstFrame.AddFlow(approxOut, ConstStrings.FALSE); + approxOut.AddFlow(timeLog, ConstStrings.TRUE); + + approxOut.AddFlow(badTimeLog, ConstStrings.FALSE); + + var start = g.CreateNode("event/onStart"); + var setDelay = g.CreateNode("flow/setDelay"); + setDelay.AddValue(ConstStrings.DURATION, testDuration); + start.AddFlow(setDelay); + + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + setDelay.AddFlow(complete, ConstStrings.DONE); + + return g; + } + + private static Graph GenerateThrottleInvalidDurationTestGraph() + { + Graph g = CreateGraphForTest(); + + var startNeg = g.CreateNode("event/onStart"); + var throttleNeg = g.CreateNode("flow/throttle"); + startNeg.AddFlow(throttleNeg); + throttleNeg.AddValue(ConstStrings.DURATION, -1f); + + var success = g.CreateNode("event/send"); + success.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var failOutNeg = g.CreateNode("event/send"); + + var logFailOutNeg = g.CreateNode("debug/log"); + + logFailOutNeg.AddConfiguration(ConstStrings.MESSAGE, "Out flow was activated despite using a negative duration."); + logFailOutNeg.AddFlow(failOutNeg); + + throttleNeg.AddFlow(logFailOutNeg); + + var throttleInf = g.CreateNode("flow/throttle"); + throttleNeg.AddFlow(throttleInf, ConstStrings.ERR); + throttleInf.AddValue(ConstStrings.DURATION, float.PositiveInfinity); + + var failOutInf = g.CreateNode("event/send"); + var logFailOutInf = g.CreateNode("debug/log"); + + logFailOutInf.AddConfiguration(ConstStrings.MESSAGE, "Out flow was activated despite using an infinite duration."); + logFailOutInf.AddFlow(failOutInf); + + throttleInf.AddFlow(logFailOutInf); + + var throttleNaN = g.CreateNode("flow/throttle"); + throttleInf.AddFlow(throttleNaN, ConstStrings.ERR); + throttleNaN.AddValue(ConstStrings.DURATION, float.NaN); + + var failOutNaN = g.CreateNode("event/send"); + var logFailOutNaN = g.CreateNode("debug/log"); + + logFailOutNaN.AddConfiguration(ConstStrings.MESSAGE, "Out flow was activated despite using NaN as a duration."); + logFailOutNaN.AddFlow(failOutNaN); + + failOutInf.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + failOutNeg.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + failOutNaN.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + throttleNaN.AddFlow(logFailOutNaN); + throttleNaN.AddFlow(success, ConstStrings.ERR); + + return g; + } + + private static Graph GenerateThrottleResetTestGraph(float duration, float resetTime) + { + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var throttle = g.CreateNode("flow/throttle"); + + throttle.AddValue(ConstStrings.DURATION, duration); + + start.AddFlow(throttle); + + var startDelay = g.CreateNode("event/onStart"); + var setDelay = g.CreateNode("flow/setDelay"); + var sequence = g.CreateNode("flow/sequence"); + var branch = g.CreateNode("flow/branch"); + var isNaN = g.CreateNode("math/isnan"); + + setDelay.AddValue(ConstStrings.DURATION, resetTime); + + startDelay.AddFlow(setDelay); + setDelay.AddFlow(sequence, ConstStrings.DONE); + + sequence.AddFlow(throttle, ConstStrings.Numbers[0], ConstStrings.RESET); + sequence.AddFlow(branch, ConstStrings.Numbers[1]); + + branch.AddConnectedValue(ConstStrings.CONDITION, isNaN); + isNaN.AddConnectedValue(ConstStrings.A, throttle, ConstStrings.LAST_REMAINING_TIME); + + var success = g.CreateNode("event/send"); + success.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var badTimeLog = g.CreateNode("debug/log"); + var badTimeFail = g.CreateNode("event/send"); + + badTimeLog.AddFlow(badTimeFail); + badTimeLog.AddConfiguration(ConstStrings.MESSAGE, "Last Remaining Time should be NaN after reset, Actual: {actual}"); + badTimeLog.AddConnectedValue(ConstStrings.ACTUAL, throttle, ConstStrings.LAST_REMAINING_TIME); + badTimeFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + branch.AddFlow(success, ConstStrings.TRUE); + branch.AddFlow(badTimeLog, ConstStrings.FALSE); + + return g; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs.meta new file mode 100644 index 000000000..533d324e9 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1bdb2bd1cab85c34aafaa8f7da5a36c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef b/Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef new file mode 100644 index 000000000..97c858ae5 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef @@ -0,0 +1,27 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Flow", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common", + "UnityGLTF.Interactivity.Playback.Tests.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef.meta new file mode 100644 index 000000000..f3fd94837 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/UnityGLTF.Interactivity.Playback.Tests.Flow.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 559b61cc5bd68404380697f4a0d307b1 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs new file mode 100644 index 000000000..9352b0284 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs @@ -0,0 +1,121 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + partial class FlowNodesTests + { + [Test] + public void WaitAll_ActivateAllInputs_CompletedFlowActivates() + { + QueueTest("flow/waitAll", GetCallerName(), "WaitAll Basic", "Triggers all 5 input flows. Test fails if completed flow is not activated.", GenerateWaitAllTestGraph(5)); + } + + [Test] + public void WaitAll_ResetWhileActivatingInputs_CompletedFlowDoesNotActivateAndRemainingInputsIsCorrect() + { + QueueTest("flow/waitAll", GetCallerName(), "WaitAll Reset", "Triggers 3 input flows, then resets and triggers the last 2. Test fails if the completed flow is activated or if remainingInputs has an incorrect value at any time after an input flow is activated.", GenerateWaitAllResetTestGraph()); + } + + private static Graph GenerateWaitAllTestGraph(int inputs) + { + Graph g = CreateGraphForTest(); + + var starts = new Node[inputs]; + var waitAll = g.CreateNode("flow/waitAll"); + waitAll.AddConfiguration(ConstStrings.INPUT_FLOWS, inputs); + + for (int i = 0; i < inputs; i++) + { + starts[i] = g.CreateNode("event/onStart"); + starts[i].AddFlow(waitAll, ConstStrings.OUT, ConstStrings.Numbers[i]); + } + + var completed = g.CreateNode("event/send"); + completed.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + waitAll.AddFlow(completed, ConstStrings.COMPLETED); + + return g; + } + + private static Graph GenerateWaitAllResetTestGraph() + { + const int INPUTS = 5; + Graph g = CreateGraphForTest(); + + var start = g.CreateNode("event/onStart"); + var waitAll = g.CreateNode("flow/waitAll"); + var sequence = g.CreateNode("flow/sequence"); + waitAll.AddConfiguration(ConstStrings.INPUT_FLOWS, INPUTS); + + start.AddFlow(sequence); + + var branch1 = g.CreateNode("flow/branch"); + var eq1 = g.CreateNode("math/eq"); + + branch1.AddConnectedValue(ConstStrings.CONDITION, eq1); + + eq1.AddConnectedValue(ConstStrings.A, waitAll, ConstStrings.REMAINING_INPUTS); + eq1.AddValue(ConstStrings.B, 2); + + var inputs1Log = g.CreateNode("debug/log"); + var fail1 = g.CreateNode("event/send"); + + fail1.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + inputs1Log.AddFlow(fail1); + inputs1Log.AddConfiguration(ConstStrings.MESSAGE, "remainingInputs, Expected: 2, Actual: {actual}"); + inputs1Log.AddConnectedValue(ConstStrings.ACTUAL, waitAll, ConstStrings.REMAINING_INPUTS); + + branch1.AddFlow(inputs1Log, ConstStrings.FALSE); + + var branch2 = g.CreateNode("flow/branch"); + var eq2 = g.CreateNode("math/eq"); + + branch2.AddConnectedValue(ConstStrings.CONDITION, eq2); + + eq2.AddConnectedValue(ConstStrings.A, waitAll, ConstStrings.REMAINING_INPUTS); + eq2.AddValue(ConstStrings.B, 3); + + var inputs2Log = g.CreateNode("debug/log"); + var fail2 = g.CreateNode("event/send"); + + fail2.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + inputs2Log.AddFlow(fail2); + inputs2Log.AddConfiguration(ConstStrings.MESSAGE, "remainingInputs, Expected: 3, Actual: {actual}"); + inputs2Log.AddConnectedValue(ConstStrings.ACTUAL, waitAll, ConstStrings.REMAINING_INPUTS); + + branch2.AddFlow(inputs2Log, ConstStrings.FALSE); + + sequence.AddFlow(waitAll, ConstStrings.Numbers[0], ConstStrings.Numbers[0]); + sequence.AddFlow(waitAll, ConstStrings.Numbers[1], ConstStrings.Numbers[1]); + sequence.AddFlow(waitAll, ConstStrings.Numbers[2], ConstStrings.Numbers[2]); + sequence.AddFlow(branch1, ConstStrings.Numbers[3]); + sequence.AddFlow(waitAll, ConstStrings.Numbers[4], ConstStrings.RESET); + sequence.AddFlow(waitAll, ConstStrings.Numbers[5], ConstStrings.Numbers[3]); + sequence.AddFlow(waitAll, ConstStrings.Numbers[6], ConstStrings.Numbers[4]); + sequence.AddFlow(branch2, ConstStrings.Numbers[7]); + + var completed = g.CreateNode("event/send"); + completed.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + branch2.AddFlow(completed, ConstStrings.TRUE); + + var completedFlowFailLog = g.CreateNode("debug/log"); + var completedFlowFail = g.CreateNode("event/send"); + + completedFlowFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + + completedFlowFailLog.AddFlow(completedFlowFail); + completedFlowFailLog.AddConfiguration(ConstStrings.MESSAGE, "Completed flow should never activate in this test."); + + waitAll.AddFlow(completedFlowFailLog, ConstStrings.COMPLETED); + + return g; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs.meta new file mode 100644 index 000000000..57e7ac5c3 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90f3be03d120f664dbd8cb480ba803de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Math.meta b/Tests/Runtime/Interactivity/Nodes/Math.meta new file mode 100644 index 000000000..2c00bf088 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Math.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5e4e09ceacdfd564f8df0fd9526a7106 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs new file mode 100644 index 000000000..7ffb1d86c --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -0,0 +1,994 @@ +using NUnit.Framework; +using System.Collections.Generic; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public class MathNodesTests : NodeTestHelpers + { + protected override string _subDirectory => "Math"; + + [Test] + public void TestAsr() + { + QueueTest("math/asr", "Asr_Positive", "Asr Positive Value", "Tests math/asr with a positive value.", CreateSelfContainedTestGraph("math/asr", In(17, 1), Out(8), ComparisonType.Equals)); + QueueTest("math/asr", "Asr_Negative", "Asr Negative Value", "Tests math/asr with a negative value.", CreateSelfContainedTestGraph("math/asr", In(-19, 2), Out(-5), ComparisonType.Equals)); + QueueTest("math/asr", "Asr_B_Out_Of_Range", "Asr w/ B Out Of Range", "Attempts to right shift by over 31 bits. This should truncate 'b' to 5 bits.", CreateSelfContainedTestGraph("math/asr", In(0b111111111111111111, 0b10000010001), Out(1), ComparisonType.Equals)); + } + + [Test] + public void TestLsl() + { + QueueTest("math/lsl", "Lsl_Positive", "Lsl Positive Value", "Left shifts a positive value.", CreateSelfContainedTestGraph("math/lsl", In(25, 2), Out(100), ComparisonType.Equals)); + QueueTest("math/lsl", "Lsl_Negative", "Lsl Negative Value", "Left shifts a negative value.", CreateSelfContainedTestGraph("math/lsl", In(-23, 2), Out(-92), ComparisonType.Equals)); + QueueTest("math/lsl", "Lsl_B_Out_Of_Range", "Lsl w/ B Out Of Range", "Attempts to left shift by over 31 bits. This should truncate 'b' to 5 bits.", CreateSelfContainedTestGraph("math/lsl", In(1, 0b1000111001), Out(33554432), ComparisonType.Equals)); + } + + [Test] + public void TestClz() + { + QueueTest("math/clz", "Clz_32", "Clz 32", "Counts leading zeros of 0b100000.", CreateSelfContainedTestGraph("math/clz", In(0b100000), Out(26), ComparisonType.Equals)); + QueueTest("math/clz", "Clz_1", "Clz 1", "Counts leading zeros of 0b1.", CreateSelfContainedTestGraph("math/clz", In(0b1), Out(31), ComparisonType.Equals)); + QueueTest("math/clz", "Clz_0", "Clz 0", "Counts leading zeros of 0.", CreateSelfContainedTestGraph("math/clz", In(0), Out(32), ComparisonType.Equals)); + QueueTest("math/clz", "Clz_0x0fff0000", "0x0fff0000", "Counts leading zeros of 0x0fff0000.", CreateSelfContainedTestGraph("math/clz", In(0x0fff0000), Out(4), ComparisonType.Equals)); + } + + [Test] + public void TestCtz() + { + QueueTest("math/ctz", "Ctz_0", "Ctz 0", "Counts trailing zeros of 0.", CreateSelfContainedTestGraph("math/ctz", In(0), Out(32), ComparisonType.Equals)); + QueueTest("math/ctz", "Ctz_1", "Ctz 1", "Counts trailing zeros of 0b1.", CreateSelfContainedTestGraph("math/ctz", In(1), Out(0), ComparisonType.Equals)); + QueueTest("math/ctz", "Ctz_16", "Ctz 16", "Counts trailing zeros of 0b10000.", CreateSelfContainedTestGraph("math/ctz", In(16), Out(4), ComparisonType.Equals)); + QueueTest("math/ctz", "Ctz_0x0f000000", "Ctz 0x0f000000", "Counts trailing zeros of 0x0f000000.", CreateSelfContainedTestGraph("math/ctz", In(0x0f000000), Out(24), ComparisonType.Equals)); + } + + [Test] + public void TestPopcnt() + { + QueueTest("math/popcnt", "Popcnt_Binary", "Count Set Bits", "Tests Popcnt/Binary operation.", CreateSelfContainedTestGraph("math/popcnt", In(0b0000001000100000), Out(2), ComparisonType.Equals)); + } + + [Test] + public void TestE() + { + QueueTest("math/e", "E_Constant", "Constant E", "Retrieves the value from math/e and checks that it is e.", CreateSelfContainedTestGraph("math/e", new(), Out(math.E), ComparisonType.Equals)); + } + + [Test] + public void TestPI() + { + QueueTest("math/pi", "PI_Constant", "Constant PI", "Retrieves the value from math/pi and checks that it is pi.", CreateSelfContainedTestGraph("math/pi", new(), Out(math.PI), ComparisonType.Equals)); + } + + [Test] + public void TestInf() + { + QueueTest("math/inf", "Infinity_Constant", "Constant Inf", "Retrieves the value from math/ing and checks that it is infinity.", CreateSelfContainedTestGraph("math/inf", new(), Out(math.INFINITY), ComparisonType.IsInfinity)); + } + + [Test] + public void TestNAN() + { + QueueTest("math/nan", "NaN_Constant", "Constant NaN", "Retrieves the value from math/nan and checks that it is nan.", CreateSelfContainedTestGraph("math/nan", new(), Out(math.NAN), ComparisonType.IsNaN)); + } + + + [Test] + public void TestAbs() + { + QueueTest("math/abs", "Abs_Negative", "Absolute Negative", "Tests getting the absolute value of a negative number.", CreateSelfContainedTestGraph("math/abs", In(-2), Out(2), ComparisonType.Equals)); + QueueTest("math/abs", "Abs_Positive", "Absolute Positive", "Tests getting the absolute value of a positive number.", CreateSelfContainedTestGraph("math/abs", In(9), Out(9), ComparisonType.Equals)); + + TestNodeWithAllFloatNInputVariants("Abs", "Absolute", "Tests math/abs node with standard values.", "math/abs", new float4(-2f, 2f, -9.15f, 0f), new float4(2f, 2f, 9.15f, 0f)); + } + + [Test] + public void TestSign() + { + QueueTest("math/sign", "Sign_Float_Positive", "Float Positive", "Tests Sign/Float/Positive operation.", CreateSelfContainedTestGraph("math/sign", In(32.0f), Out(1.0f), ComparisonType.Equals)); + QueueTest("math/sign", "Sign_Float_Negative", "Float Negative", "Tests Sign/Float/Negative operation.", CreateSelfContainedTestGraph("math/sign", In(-12.0f), Out(-1.0f), ComparisonType.Equals)); + QueueTest("math/sign", "Sign_Float_Zero", "Float Zero", "Tests Sign/Float/Zero operation.", CreateSelfContainedTestGraph("math/sign", In(0.0f), Out(0.0f), ComparisonType.Equals)); + + QueueTest("math/sign", "Sign_Int_Positive", "Int Positive", "Tests Sign/Int/Positive operation.", CreateSelfContainedTestGraph("math/sign", In(32), Out(1), ComparisonType.Equals)); + QueueTest("math/sign", "Sign_Int_Negative", "Int Negative", "Tests Sign/Int/Negative operation.", CreateSelfContainedTestGraph("math/sign", In(-12), Out(-1), ComparisonType.Equals)); + QueueTest("math/sign", "Sign_Int_Zero", "Int Zero", "Tests Sign/Int/Zero operation.", CreateSelfContainedTestGraph("math/sign", In(0), Out(0), ComparisonType.Equals)); + + TestNodeWithAllFloatNInputVariants("Sign", "Sign", "Tests math/sign node with standard values.", "math/sign", new float4(32.0f, -12.0f, 0.0f, 5.0f), new float4(1.0f, -1.0f, 0.0f, 1.0f)); + } + + [Test] + public void TestFloor() + { + TestNodeWithAllFloatNInputVariants("Floor", "Floor", "Tests math/floor node with standard values.", "math/floor", new float4(3.87f, 3.14f, -3.14f, -3.87f), new float4(3f, 3f, -4f, -4f)); + } + + [Test] + public void TestTrunc() + { + TestNodeWithAllFloatNInputVariants("Trunc", "Truncate", "Tests math/trunc node with standard values.", "math/trunc", new float4(3.87f, 3.14f, -3.14f, -3.87f), new float4(3f, 3f, -3f, -3f)); + } + + [Test] + public void TestCeil() + { + TestNodeWithAllFloatNInputVariants("Ceil", "Ceiling", "Tests math/ceil node with standard values.", "math/ceil", new float4(3.87f, 3.14f, -3.14f, -3.87f), new float4(4f, 4f, -3f, -3f)); + } + + [Test] + public void TestAdd() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(-34.0f, 22f, -11f, 70.0f); + + var expected = a + b; + + TestNodeWithAllFloatNInputVariants("Add", "Add", "Tests math/add node with standard values.", "math/add", a, b, expected); + QueueTest("math/add", "Add_Positive", "Add Positive", "Tests adding with positive values.", CreateSelfContainedTestGraph("math/add", In(5, 15), Out(20), ComparisonType.Equals)); + QueueTest("math/add", "Add_Negative", "Add Negative", "Tests adding a negative value.", CreateSelfContainedTestGraph("math/add", In(5, -15), Out(-10), ComparisonType.Equals)); + QueueTest("math/add", "Add_ZeroOperand", "Add B Zero", "Tests adding 0.", CreateSelfContainedTestGraph("math/add", In(5, 0), Out(5), ComparisonType.Equals)); + QueueTest("math/add", "Add_BothZero", "Add Both Zero", "Tests adding 0 to 0.", CreateSelfContainedTestGraph("math/add", In(0, 0), Out(0), ComparisonType.Equals)); + } + + [Test] + public void TestSub() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(-34.0f, 22f, -11f, 70.0f); + + var expected = a - b; + + TestNodeWithAllFloatNInputVariants("Sub", "Subtract", "Tests math/sub node with standard values.", "math/sub", a, b, expected); + QueueTest("math/sub", "Sub_Positive", "Subtract Positive", "Tests subtracting a positive number.", CreateSelfContainedTestGraph("math/sub", In(5, 15), Out(-10), ComparisonType.Equals)); + QueueTest("math/sub", "Sub_Negative", "Subtract Negative", "Tests subtracting a negative number.", CreateSelfContainedTestGraph("math/sub", In(5, -15), Out(20), ComparisonType.Equals)); + QueueTest("math/sub", "Sub_ZeroOperand", "Subtract B Zerio", "Tests subtracting 0.", CreateSelfContainedTestGraph("math/sub", In(5, 0), Out(5), ComparisonType.Equals)); + QueueTest("math/sub", "Sub_BothZero", "Subtract Both Zero", "Testss subtracting 0 from 0.", CreateSelfContainedTestGraph("math/sub", In(0, 0), Out(0), ComparisonType.Equals)); + } + + [Test] + public void TestMul() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(-34.0f, 22f, -11f, 70.0f); + + var expected = a * b; + + TestNodeWithAllFloatNInputVariants("Mul", "Multiply", "Tests math/mul node with standard values.", "math/mul", a, b, expected); + QueueTest("math/mul", "Mul_Positive", "Multiply Positive", "Tests Mul/Positive operation.", CreateSelfContainedTestGraph("math/mul", In(1, 15), Out(15), ComparisonType.Equals)); + QueueTest("math/mul", "Mul_ZeroOperand_Negative", "Multiply 0 By A Number", "Multiplies zero by a number.", CreateSelfContainedTestGraph("math/mul", In(0, -15), Out(0), ComparisonType.Equals)); + QueueTest("math/mul", "Mul_ZeroOperand_Positive", "Multiply A Number By 0", "Multiplies a number by zero.", CreateSelfContainedTestGraph("math/mul", In(5, 0), Out(0), ComparisonType.Equals)); + } + + [Test] + public void TestDiv() + { + var a = new float4(5f, 5f, 12.4f, -12.4f); + var b = new float4(1f, 12f, -55f, -12.4f); + var expected = a / b; + TestNodeWithAllFloatNInputVariants("Div", "Divide", "Tests math/div node with standard values.", "math/div", a, b, expected); + QueueTest("math/div", "Div_ByZero", "Divide By Zero", "Divides a number by zero.", CreateSelfContainedTestGraph("math/div", In(5f, 0f), Out(float.PositiveInfinity), ComparisonType.IsInfinity)); + QueueTest("math/div", "Div_ByPositiveInfinity", "Divide By Infinity", "Divides a number by infinity.", CreateSelfContainedTestGraph("math/div", In(5f, float.PositiveInfinity), Out(0f), ComparisonType.Equals)); + } + + [Test] + public void TestRem() + { + var a = new float4(5f, 5f, 12.4f, -12.4f); + var b = new float4(1f, 12f, -55f, -12.4f); + var expected = a % b; + + QueueTest("math/rem", "Rem_DivideByZero", "Remainder", "Tests that the remainder of a number divided by zero is NaN.", CreateSelfContainedTestGraph("math/rem", In(5.0f, 0.0f), Out(math.NAN), ComparisonType.IsNaN)); + QueueTest("math/rem", "Rem_DivideByInfinity", "Remainder", "Tests that the remainder of a number divided by infinity is the original number.", CreateSelfContainedTestGraph("math/rem", In(5.0f, float.PositiveInfinity), Out(5.0f), ComparisonType.Equals)); + TestNodeWithAllFloatNInputVariants("Rem", "Remainder", "Tests math/rem node with standard values.", "math/rem", a, b, expected); + QueueTest("math/rem", "Rem_Int_Positive", "Remainder Int Positive", "Tests the remainder operation with an integer.", CreateSelfContainedTestGraph("math/rem", In(5, 4), Out(1), ComparisonType.Equals)); + QueueTest("math/rem", "Rem_Int_Equal", "Remainder Int Equal", "Tests that the remainder of a number divided by itself is 0.", CreateSelfContainedTestGraph("math/rem", In(5, 5), Out(0), ComparisonType.Equals)); + } + + [Test] + public void TestMin() + { + TestNodeWithAllFloatNInputVariants("Min", "Minimum", "Tests math/min node with standard values.", "math/min", new float4(12.0f, 16.0f, -32.0f, 7.0f), new float4(3.0f, 0.0f, 14.0f, 14.0f), new float4(3.0f, 0.0f, -32.0f, 7.0f)); + + QueueTest("math/min", "Min_Int_Positive", "Minimum Integer", "Tests math/min with an integer.", CreateSelfContainedTestGraph("math/min", In(100, 10), Out(10), ComparisonType.Equals)); + } + + [Test] + public void TestMax() + { + TestNodeWithAllFloatNInputVariants("Max", "Maximum", "Tests math/max with standard values.", "math/max", new float4(12.0f, 16.0f, -32.0f, 7.0f), new float4(3.0f, 0.0f, 14.0f, 14.0f), new float4(12.0f, 16.0f, 14.0f, 14.0f)); + + QueueTest("math/max", "Max_Int_Positive", "Maximum", "Tests math/max with an integer", CreateSelfContainedTestGraph("math/max", In(100, 10), Out(100), ComparisonType.Equals)); + } + + [Test] + public void TestClamp() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(30.0f, 40.0f, 50.0f, 60.0f); + var c = new float4(92.0f, 43.0f, 59.0f, 90.0f); + + var expected = math.clamp(a, b, c); + + TestNodeWithAllFloatNInputVariants("Clamp", "Clamp Values", "Tests math/clamp with standard values.", "math/clamp", a, b, c, expected); + } + + [Test] + public void TestSaturate() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + TestNodeWithAllFloatNInputVariants("Saturate", "Saturate", "Tests math/saturate with standard values.", "math/saturate", a, math.saturate(a)); + } + + [Test] + public void TestMix() + { + TestNodeWithAllFloatNInputVariants("Mix", "Mix", "Tests math/mix with standard values.", "math/mix", new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(9.0f, 10.0f, 11.0f, 12.0f), new float4(1.0f, 0.25f, 0.5f, 0.0f), new float4(9.0f, 4.0f, 7.0f, 4.0f)); + } + + [Test] + public void TestEq() + { + QueueTest("math/eq", "Eq_X_Comparison_Infinity", "Eq Inf == Inf", "Tests that infinity == infinity in this implementation.", CreateSelfContainedTestGraph("math/eq", In(float.PositiveInfinity, float.PositiveInfinity), Out(true), ComparisonType.Equals)); + + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(30.0f, 40.0f, 50.0f, 60.0f); + + QueueTest("math/eq", "Eq_X_Comparison_False", "Eq Unequal Float", "Tests two unequal float values.", CreateSelfContainedTestGraph("math/eq", In(a.x, b.x), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_XY_Comparison_False", "Eq Unequal Float2", "Tests two unequal float2 values.", CreateSelfContainedTestGraph("math/eq", In(a.xy, b.xy), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_XYZ_Comparison_False", "Eq Unequal Float3", "Tests two unequal float3 values.", CreateSelfContainedTestGraph("math/eq", In(a.xyz, b.xyz), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_Full_Comparison_False", "Eq Unequal Float4", "Tests two unequal float4 values.", CreateSelfContainedTestGraph("math/eq", In(a, b), Out(false), ComparisonType.Equals)); + + QueueTest("math/eq", "Eq_X_Comparison_True", "Eq Equal Float", "Tests two equal float values.", CreateSelfContainedTestGraph("math/eq", In(a.x, a.x), Out(true), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_XY_Comparison_True", "Eq Equal Float2", "Tests two equal float2 values.", CreateSelfContainedTestGraph("math/eq", In(a.xy, a.xy), Out(true), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_XYZ_Comparison_True", "Eq Equal Float3", "Tests two equal float3 values.", CreateSelfContainedTestGraph("math/eq", In(a.xyz, a.xyz), Out(true), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_Full_Comparison_True", "Eq Equal Float4", "Tests two equal float4 values.", CreateSelfContainedTestGraph("math/eq", In(a, a), Out(true), ComparisonType.Equals)); + + QueueTest("math/eq", "Eq_Int_Comparison_False", "Eq Unequal Int", "Tests two unequal int values.", CreateSelfContainedTestGraph("math/eq", In(1, 2), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_Int_Comparison_True", "Eq Equal Int", "Tests two equal int values.", CreateSelfContainedTestGraph("math/eq", In(1, 1), Out(true), ComparisonType.Equals)); + + QueueTest("math/eq", "Eq_NegativeComparison_False", "Eq Unequal Positive/Negative", "Tests that a number and its negative value are not equal.", CreateSelfContainedTestGraph("math/eq", In(-2, 2), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_NegativeComparison_True", "NegativeComparison True", "Tests that two negative numbers are equal.", CreateSelfContainedTestGraph("math/eq", In(-2, -2), Out(true), ComparisonType.Equals)); + + var f2x2a = new float2x2(2.3f, -4.1f, 12.4f, 11.5f); + var f3x3a = new float3x3(2.3f, -4.1f, 12.3f, 12.4f, 11.5f, 17.1f, 1.3f, -5.0f, 19.5f); + var f4x4a = new float4x4(2.3f, -4.1f, 12.3f, 8.3f, 12.4f, 11.5f, 17.1f, 83.0f, 1.3f, -5.0f, 19.5f, 14.1f, 4.4f, 19.1f, 72.3f, 18.2f); + + var f2x2b = new float2x2(3.7f, -2.9f, 7.8f, 0.6f); + var f3x3b = new float3x3(6.1f, 9.2f, -3.3f, 15.7f, -7.4f, 10.5f, 21.9f, -11.8f, 2.6f); + var f4x4b = new float4x4( + 5.9f, -8.7f, 13.6f, 9.1f, + 22.2f, -6.9f, 3.3f, 0.4f, + 16.8f, 7.1f, -1.6f, 25.0f, + -12.3f, 6.6f, -4.8f, 11.2f + ); + + QueueTest("math/eq", "Eq_Float2x2_Comparison_False", "Eq Unequal Float2x2", "Tests two unequal float2x2 values.", CreateSelfContainedTestGraph("math/eq", In(f2x2a, f2x2b), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_Float2x2_Comparison_True", "Eq equal Float2x2", "Tests two equal float2x2 values.", CreateSelfContainedTestGraph("math/eq", In(f2x2a, f2x2a), Out(true), ComparisonType.Equals)); + + QueueTest("math/eq", "Eq_Float3x3_Comparison_False", "Eq Unequal Float3x3", "Tests two unequal float3x3 values.", CreateSelfContainedTestGraph("math/eq", In(f3x3a, f3x3b), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_Float3x3_Comparison_True", "Eq Equal Float3x3", "Tests two equal float3x3 values.", CreateSelfContainedTestGraph("math/eq", In(f3x3a, f3x3a), Out(true), ComparisonType.Equals)); + + QueueTest("math/eq", "Eq_Float4x4_Comparison_False", "Eq Unequal Float4x4", "Tests two unequal float4x4 values.", CreateSelfContainedTestGraph("math/eq", In(f4x4a, f4x4b), Out(false), ComparisonType.Equals)); + QueueTest("math/eq", "Eq_Float4x4_Comparison_True", "Eq Equal Float4x4", "Tests two equal float4x4 values.", CreateSelfContainedTestGraph("math/eq", In(f4x4a, f4x4a), Out(true), ComparisonType.Equals)); + + } + + [Test] + public void TestLT() + { + QueueTest("math/lt", "LT_Float_Less", "Less Than Float True", "Tests that math/lt outputs true when float value A is less than B.", CreateSelfContainedTestGraph("math/lt", In(10.0f, 20.0f), Out(true), ComparisonType.Equals)); + QueueTest("math/lt", "LT_Float_Equal", "Less Than Float Equal", "Tests that math/lt outputs false when float value A is equal to B.", CreateSelfContainedTestGraph("math/lt", In(10.0f, 10.0f), Out(false), ComparisonType.Equals)); + QueueTest("math/lt", "LT_Float_Greater", "Less Than Float False", "Tests that math/lt outputs false when float value A is greater than B.", CreateSelfContainedTestGraph("math/lt", In(40.0f, 20.0f), Out(false), ComparisonType.Equals)); + + QueueTest("math/lt", "LT_Int_Less", "Less Than Int True", "Tests that math/lt outputs true when int value A is less than B.", CreateSelfContainedTestGraph("math/lt", In(10, 20), Out(true), ComparisonType.Equals)); + QueueTest("math/lt", "LT_Int_Equal", "Less Than Int Equal", "Tests that math/lt outputs false when int value A is equal to B.", CreateSelfContainedTestGraph("math/lt", In(10, 10), Out(false), ComparisonType.Equals)); + QueueTest("math/lt", "LT_Int_Greater", "Less Than Int False", "Tests that math/lt outputs false when int value A is greater than B.", CreateSelfContainedTestGraph("math/lt", In(40, 20), Out(false), ComparisonType.Equals)); + } + + [Test] + public void TestLE() + { + QueueTest("math/le", "LE_Float_Less", "LessEqual Float True (Less)", "Tests that math/le outputs true when float value A is less than B.", CreateSelfContainedTestGraph("math/le", In(10.0f, 20.0f), Out(true), ComparisonType.Equals)); + QueueTest("math/le", "LE_Float_Equal", "LessEqual Float True (Equal)", "Tests that math/le outputs true when float value A is equal to B.", CreateSelfContainedTestGraph("math/le", In(10.0f, 10.0f), Out(true), ComparisonType.Equals)); + QueueTest("math/le", "LE_Float_Greater", "LessEqual Float False", "Tests that math/le outputs false when float value A is greater than B.", CreateSelfContainedTestGraph("math/le", In(40.0f, 20.0f), Out(false), ComparisonType.Equals)); + + QueueTest("math/le", "LE_Int_Less", "LessEqual Int True (Less)", "Tests that math/le outputs true when int value A is less than B.", CreateSelfContainedTestGraph("math/le", In(10, 20), Out(true), ComparisonType.Equals)); + QueueTest("math/le", "LE_Int_Equal", "LessEqual Int True (Equal)", "Tests that math/le outputs true when int value A is equal to B.", CreateSelfContainedTestGraph("math/le", In(10, 10), Out(true), ComparisonType.Equals)); + QueueTest("math/le", "LE_Int_Greater", "LessEqual Int False", "Tests that math/le outputs false when int value A is greater than B.", CreateSelfContainedTestGraph("math/le", In(40, 20), Out(false), ComparisonType.Equals)); + } + + [Test] + public void TestGT() + { + QueueTest("math/gt", "GT_Float_Less", "GreaterThan Float False", "Tests that math/gt outputs false when float value A is less than B.", CreateSelfContainedTestGraph("math/gt", In(10.0f, 20.0f), Out(false), ComparisonType.Equals)); + QueueTest("math/gt", "GT_Float_Equal", "GreaterThan Float False (Equal)", "Tests that math/gt outputs false when float value A is equal to B.", CreateSelfContainedTestGraph("math/gt", In(10.0f, 10.0f), Out(false), ComparisonType.Equals)); + QueueTest("math/gt", "GT_Float_Greater", "GreaterThan Float True", "Tests that math/gt outputs true when float value A is greater than B.", CreateSelfContainedTestGraph("math/gt", In(40.0f, 20.0f), Out(true), ComparisonType.Equals)); + + QueueTest("math/gt", "GT_Int_Less", "GreaterThan Int False", "Tests that math/gt outputs false when int value A is less than B.", CreateSelfContainedTestGraph("math/gt", In(10, 20), Out(false), ComparisonType.Equals)); + QueueTest("math/gt", "GT_Int_Equal", "GreaterThan Int False (Equal)", "Tests that math/gt outputs false when int value A is equal to B.", CreateSelfContainedTestGraph("math/gt", In(10, 10), Out(false), ComparisonType.Equals)); + QueueTest("math/gt", "GT_Int_Greater", "GreaterThan Int True", "Tests that math/gt outputs true when int value A is greater than B.", CreateSelfContainedTestGraph("math/gt", In(40, 20), Out(true), ComparisonType.Equals)); + } + + [Test] + public void TestGE() + { + QueueTest("math/ge", "GE_Float_Less", "GreaterEqual Float False", "Tests that math/ge outputs false when float value A is less than B.", CreateSelfContainedTestGraph("math/ge", In(10.0f, 20.0f), Out(false), ComparisonType.Equals)); + QueueTest("math/ge", "GE_Float_Equal", "GreaterEqual Float True (Equal)", "Tests that math/ge outputs true when float value A is equal to B.", CreateSelfContainedTestGraph("math/ge", In(10.0f, 10.0f), Out(true), ComparisonType.Equals)); + QueueTest("math/ge", "GE_Float_Greater", "GreaterEqual Float True", "Tests that math/ge outputs true when float value A is greater than B.", CreateSelfContainedTestGraph("math/ge", In(40.0f, 20.0f), Out(true), ComparisonType.Equals)); + + QueueTest("math/ge", "GE_Int_Less", "GreaterEqual Int False", "Tests that math/ge outputs false when int value A is less than B.", CreateSelfContainedTestGraph("math/ge", In(10, 20), Out(false), ComparisonType.Equals)); + QueueTest("math/ge", "GE_Int_Equal", "GreaterEqual Int True (Equal)", "Tests that math/ge outputs true when int value A is equal to B.", CreateSelfContainedTestGraph("math/ge", In(10, 10), Out(true), ComparisonType.Equals)); + QueueTest("math/ge", "GE_Int_Greater", "GreaterEqual Int True", "Tests that math/ge outputs true when int value A is greater than B.", CreateSelfContainedTestGraph("math/ge", In(40, 20), Out(true), ComparisonType.Equals)); + } + + [Test] + public void TestIsNan() + { + QueueTest("math/isnan", "IsNan_True", "IsNaN w/ NaN Value", "Tests isNaN returns true for a nan input.", CreateSelfContainedTestGraph("math/isnan", In((float)math.acos(-2.0)), Out(true), ComparisonType.Equals)); + QueueTest("math/isnan", "IsNan_False", "IsNaN w/ Valid Float", "Tests that isNaN is false when the input is a number.", CreateSelfContainedTestGraph("math/isnan", In(10.0f), Out(false), ComparisonType.Equals)); + } + + [Test] + public void TestIsInf() + { + QueueTest("math/isinf", "IsInf_True", "IsInf w/ Inf Value", "Tests isInf returns true for an infinite value.", CreateSelfContainedTestGraph("math/isinf", In(10.0f / 0.0f), Out(true), ComparisonType.Equals)); + QueueTest("math/isinf", "IsInf_False", "IsInf w/ Non-Inf Value", "Tests isInf returns false for a non-infinite value.", CreateSelfContainedTestGraph("math/isinf", In(10.0f), Out(false), ComparisonType.Equals)); + } + + + [Test] + public void TestSelect() + { + QueueTest("math/select", "Select_True", "Select True Condition", "Tests that input A is returned when condition is true.", MathSelectTest(10.0f, 20.0f, true, 10.0f)); + QueueTest("math/select", "Select_True", "Select True Condition", "Tests that input B is returned when condition is false.", MathSelectTest(10.0f, 20.0f, false, 20.0f)); + } + + private static (Graph, TestValues) MathSelectTest(T a, T b, bool condition, T expected) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.CONDITION, new Value() { id = ConstStrings.CONDITION, property = new Property(condition) }); + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(a) }); + inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + return CreateSelfContainedTestGraph("math/select", inputs, outputs, ComparisonType.Equals); + } + + [Test] + public void TestSin() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.sin(a); + + TestNodeWithAllFloatNInputVariants("Sin", "Sin", "Tests math/sin with standard values.", "math/sin", a, expected); + QueueTest("math/sin", "Sin_Zero", "Sin Zero", "Tests that sin(0) = 0.", CreateSelfContainedTestGraph("math/sin", In(0.0f), Out(0.0f), ComparisonType.Approximately)); + QueueTest("math/sin", "Sin_PIOver2", "Sin PIOver2", "Tests that sin(pi/2) = 1.", CreateSelfContainedTestGraph("math/sin", In((float)(math.PI / 2.0)), Out(1.0f), ComparisonType.Approximately)); + } + + [Test] + public void TestCos() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.cos(a); + + TestNodeWithAllFloatNInputVariants("Cos", "Cos", "Tests math/cos with standard values.", "math/cos", a, expected); + QueueTest("math/cos", "Cos_Zero", "Zero", "Tests that cos(0) = 1.", CreateSelfContainedTestGraph("math/cos", In(0.0f), Out(1.0f), ComparisonType.Approximately)); + QueueTest("math/cos", "Cos_PIOver2", "PIOver2", "Tests that cos(pi/2) = 0.", CreateSelfContainedTestGraph("math/cos", In((float)(math.PI / 2.0)), Out(0.0f), ComparisonType.Approximately)); + } + + [Test] + public void TestTan() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.tan(a); + + TestNodeWithAllFloatNInputVariants("Tan", "Tan", "Tests math/tan with standard values.", "math/tan", a, expected); + // TestNode("math/tan", math.PI * 0.5f, math.INFINITY); // it fails because of PI not precise + } + + [Test] + public void TestAsin() + { + var a = new float4(-1f, 1f, 0f, 0.4f); + var expected = new float4(-math.PI / 2f, math.PI / 2f, 0f, 0.411516f); + + TestNodeWithAllFloatNInputVariants("Asin", "Asin", "Tests asin with standard values.", "math/asin", a, expected); + + QueueTest("math/asin", "Asin_OutOfRange", "OutOfRange", "Tests Asin/OutOfRange operation.", CreateSelfContainedTestGraph("math/asin", In(1000.0f), Out(math.NAN), ComparisonType.IsNaN)); + } + + [Test] + public void TestAcos() + { + var a = new float4(0f, 0.999f, -0.9999f, 0.1f); + var expected = math.acos(a); + + TestNodeWithAllFloatNInputVariants("Acos", "Acos", "Tests acos with standard values.", "math/acos", a, expected); + + //TestNode("math/acos", 1000.0f, math.NAN, ComparisonType.IsNaN); + //TestNode("math/acos", 0.0f, math.PI * 0.5f, ComparisonType.Approximately); + } + + [Test] + public void TestAtan() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.atan(a); + + TestNodeWithAllFloatNInputVariants("Atan", "Atan", "Tests atan with standard values.", "math/atan", a, expected); + + QueueTest("math/atan", "Atan_1", "Atan 1", "Tests that atan(1) = pi/4.", CreateSelfContainedTestGraph("math/atan", In(1.0f), Out(math.PI / 4.0f), ComparisonType.Equals)); + QueueTest("math/atan", "Atan_Zero", "Atan 0", "Tests atan(0) = 0.", CreateSelfContainedTestGraph("math/atan", In(0.0f), Out(0.0f), ComparisonType.Equals)); + } + + [Test] + public void TestAtan2() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(30.0f, 40.0f, 50.0f, 60.0f); + + var expected = math.atan2(a, b); + + TestNodeWithAllFloatNInputVariants("Atan2", "Atan2", "Tests atan2 with standard values.", "math/atan2", a, b, expected); + + QueueTest("math/atan2", "Atan2_1_1", "Atan2(1,1)", "Tests that atan2(1,1) = pi/4", CreateSelfContainedTestGraph("math/atan2", In(1.0f, 1.0f), Out(math.PI / 4.0f), ComparisonType.Equals)); + QueueTest("math/atan2", "Atan2_1_0", "SecondQuadrant", "Tests that atan2(1,0) = pi/2.", CreateSelfContainedTestGraph("math/atan2", In(1.0f, 0.0f), Out(math.PI / 2.0f), ComparisonType.Equals)); + } + + [Test] + public void TestSinH() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.sinh(a); + + TestNodeWithAllFloatNInputVariants("Sinh", "Sinh", "Tests sinh with standard values.", "math/sinh", a, expected); + QueueTest("math/sinh", "Sinh_Zero", "Sinh 0", "Tests sinh(0) = 0.", CreateSelfContainedTestGraph("math/sinh", In(0.0f), Out(0.0f), ComparisonType.Equals)); + } + + [Test] + public void TestCosH() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.cosh(a); + + TestNodeWithAllFloatNInputVariants("Cosh", "Cosh", "Tests cose with standard values.", "math/cosh", a, expected); + QueueTest("math/cosh", "Cosh_Zero", "Costh 0", "Tests cosh(0) = 1.", CreateSelfContainedTestGraph("math/cosh", In(0.0f), Out(1.0f), ComparisonType.Equals)); + } + + [Test] + public void TestTanH() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.tanh(a); + + TestNodeWithAllFloatNInputVariants("Tanh", "Tanh", "Tests tanh with standard values.", "math/tanh", a, expected); + QueueTest("math/tanh", "Tanh_PositiveInfinity", "Tanh(inf)", "Tests tanh(inf) = 1.", CreateSelfContainedTestGraph("math/tanh", In(math.INFINITY), Out(1.0f), ComparisonType.Equals)); + QueueTest("math/tanh", "Tanh_NegativeInfinity", "Tranf(-inf)", "Tests tanh(-inf) = -1.", CreateSelfContainedTestGraph("math/tanh", In(-math.INFINITY), Out(-1.0f), ComparisonType.Equals)); + } + + private float4 asinh(float4 x) + { + return math.log(x + math.sqrt(x * x + 1)); + } + + private float4 acosh(float4 x) + { + return math.log(x + math.sqrt(x * x - 1)); + } + + private float4 atanh(float4 x) + { + return 0.5f * math.log((1 + x) / (1 - x)); + } + + [Test] + public void TestASinH() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = asinh(a); + + TestNodeWithAllFloatNInputVariants("Asinh", "Asinh", "Tests asinh with standard values.", "math/asinh", a, expected); + } + + [Test] + public void TestACosH() + { + float4 val = new float4(1, 10.0f, 100.0f, 1000.0f); + TestNodeWithAllFloatNInputVariants("Acosh", "Acosh", "Tests acosh with standard values.", "math/acosh", val, acosh(val), ComparisonType.Approximately); + QueueTest("math/acosh", "Acosh_One", "Acosh 1", "Tests acosh(1) = 0.", CreateSelfContainedTestGraph("math/acosh", In(1.0f), Out(0.0f), ComparisonType.Equals)); + QueueTest("math/acosh", "Acosh_LessThanOne", "Acosh < 1", "Tests that acos returns NaN below 1.", CreateSelfContainedTestGraph("math/acosh", In(0.5f), Out(float.NaN), ComparisonType.IsNaN)); + } + + [Test] + public void TestATanH() + { + float4 val = new float4(-0.99f, -0.3f, 0.3f, 0.99f); + TestNodeWithAllFloatNInputVariants("Atanh", "Atanh", "Tests atanh with standard values.", "math/atanh", val, atanh(val)); + QueueTest("math/atanh", "Atanh_PositiveOne", "Atanh 1", "Tests atanh(1) = inf.", CreateSelfContainedTestGraph("math/atanh", In(1.0f), Out(math.INFINITY), ComparisonType.IsInfinity)); + QueueTest("math/atanh", "Atanh_NegativeOne", "Atanh -1", "Tests atanh(-1) = -inf.", CreateSelfContainedTestGraph("math/atanh", In(-1.0f), Out(-math.INFINITY), ComparisonType.IsInfinity)); + QueueTest("math/atanh", "Atanh_PositiveGreaterThanOne", "Atanh > 1", "Tests atanh > 1 is NaN.", CreateSelfContainedTestGraph("math/atanh", In(1.1f), Out(math.NAN), ComparisonType.IsNaN)); + QueueTest("math/atanh", "Atanh_NegativeGreaterThanOne", "Atanh < 1", "Tests atanh < 1 is NaN.", CreateSelfContainedTestGraph("math/atanh", In(-1.1f), Out(-math.NAN), ComparisonType.IsNaN)); + } + + [Test] + public void TestExp() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.exp(a); + + TestNodeWithAllFloatNInputVariants("Exp", "Exp", "Tests exp with standard values.", "math/exp", a, expected); + } + + [Test] + public void TestLog() + { + float4 val = new float4(30.0f, 0.3f, 1f, 15.0f); + QueueTest("math/log", "Log_PositiveValue", "Log Positive", "Tests log with a positive value.", CreateSelfContainedTestGraph("math/log", In(val), Out(math.log(val)), ComparisonType.Approximately)); + QueueTest("math/log", "Log_NegativeValue", "Log Negative", "Tests log < 0 is NaN.", CreateSelfContainedTestGraph("math/log", In(-1.0f), Out(math.NAN), ComparisonType.IsNaN)); + QueueTest("math/log", "Log_Zero", "Log Zero", "Tests log(0) = -inf.", CreateSelfContainedTestGraph("math/log", In(0.0f), Out(-math.INFINITY), ComparisonType.IsInfinity)); + + TestNodeWithAllFloatNInputVariants("Log", "Log", "Tests log with standard values.", "math/log", val, math.log(val)); + } + + [Test] + public void TestLog2() + { + float val = 30.0f; + + QueueTest("math/log2", "Log2_PositiveValue", "Log2 Positive", "Tests log2 with a positive value.", CreateSelfContainedTestGraph("math/log2", In(val), Out(math.log2(val)), ComparisonType.Approximately)); + QueueTest("math/log2", "Log2_NegativeValue", "Log2 Negative", "Tests log2 < 0 is NaN.", CreateSelfContainedTestGraph("math/log2", In(-1.0f), Out(math.NAN), ComparisonType.IsNaN)); + QueueTest("math/log2", "Log2_Zero", "Log2 Zero", "Tests log2(0) = -inf.", CreateSelfContainedTestGraph("math/log2", In(0.0f), Out(-math.INFINITY), ComparisonType.IsInfinity)); + + float4 vecVal = new float4(30.0f, 0.3f, 1f, 15.0f); + TestNodeWithAllFloatNInputVariants("Log2", "Log2", "Tests log2 with standard values.", "math/log2", vecVal, math.log2(vecVal)); + } + + + [Test] + public void TestLog10() + { + float val = 30.0f; + + QueueTest("math/log10", "Log10_PositiveValue", "Log10 Positive", "Tests log10 with a positive value.", CreateSelfContainedTestGraph("math/log10", In(val), Out(math.log10(val)), ComparisonType.Approximately)); + QueueTest("math/log10", "Log10_NegativeValue", "Log10 Negative", "Tests log10 < 0 is NaN.", CreateSelfContainedTestGraph("math/log10", In(-1.0f), Out(math.NAN), ComparisonType.IsNaN)); + QueueTest("math/log10", "Log10_Zero", "Log10 Zero", "Tests log10(0) = -inf.", CreateSelfContainedTestGraph("math/log10", In(0.0f), Out(-math.INFINITY), ComparisonType.IsInfinity)); + + float4 vecVal = new float4(30.0f, 0.3f, 1f, 15.0f); + TestNodeWithAllFloatNInputVariants("Log10", "Log10", "Tests log10 with standard values.", "math/log10", vecVal, math.log10(vecVal)); + } + + + [Test] + public void TestSqrt() + { + float4 val = new float4(30.0f, 0.3f, 0.2f, 15.0f); + TestNodeWithAllFloatNInputVariants("Sqrt", "Sqrt", "Tests sqrt with standard values.", "math/sqrt", val, math.sqrt(val)); + QueueTest("math/sqrt", "Sqrt_NegativeValue", "Sqrt Negative", "Tests that sqrt of a negative number is NaN.", CreateSelfContainedTestGraph("math/sqrt", In(-1.0f), Out(math.NAN), ComparisonType.IsNaN)); + QueueTest("math/sqrt", "Sqrt_Zero", "Sqrt Zero", "Tests sqrt(0) = 0.", CreateSelfContainedTestGraph("math/sqrt", In(0.0f), Out(0.0f), ComparisonType.Equals)); + } + + [Test] + public void TestPow() + { + float4 val = new float4(30.0f, 0.3f, -2f, 15.0f); + float4 e = new float4(1.0f, 1.3f, -2f, 5.0f); + var expected = math.pow(val, e); + Util.Log(expected.ToString()); + + TestNodeWithAllFloatNInputVariants("Pow", "Pow", "Tests pow with standard values.", "math/pow", val, e, expected); + QueueTest("math/pow", "Pow_NegativeBase_PositiveExponent", "Pow NegativeBase PositiveExponent", "Tests pow with a negative base and positive exponent.", CreateSelfContainedTestGraph("math/pow", In(-1.0f, 2.0f), Out(1.0f), ComparisonType.Approximately)); + QueueTest("math/pow", "Pow_Zero_PositiveExponent", "Pow Zero PositiveExponent", "Tests pow by raising zero to a power.", CreateSelfContainedTestGraph("math/pow", In(0.0f, 1000.0f), Out(0.0f), ComparisonType.Approximately)); + QueueTest("math/pow", "Pow_PositiveBase_ZeroExponent", "Pow PositiveBase ZeroExponent", "Tests that raising a number to 0 equals 1.", CreateSelfContainedTestGraph("math/pow", In(1000.0f, 0.0f), Out(1.0f), ComparisonType.Approximately)); + QueueTest("math/pow", "Pow_NegativeBase_NonIntegerExponent", "Pow NegativeBase NonIntegerExponent", "Tests that raising a negative value to a negative exponent is NaN.", CreateSelfContainedTestGraph("math/pow", In(-0.2f, -1.2f), Out(float.NaN), ComparisonType.IsNaN)); + } + + [Test] + public void TestLength() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + QueueTest("math/length", "Length_XY", "Length float2", "Tests length with a float2 input.", CreateSelfContainedTestGraph("math/length", In(a.xy), Out(math.length(a.xy)), ComparisonType.Equals)); + QueueTest("math/length", "Length_XYZ", "Length float3", "Tests length with a float3 input.", CreateSelfContainedTestGraph("math/length", In(a.xyz), Out(math.length(a.xyz)), ComparisonType.Equals)); + QueueTest("math/length", "Length_XYZW", "Length float4", "Tests length with a float4 input.", CreateSelfContainedTestGraph("math/length", In(a), Out(math.length(a)), ComparisonType.Equals)); + } + + [Test] + public void TestNormalize() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + QueueTest("math/normalize", "Normalize_XY", "Normalize float2", "Tests normalize with a float2 input.", CreateSelfContainedTestGraph("math/normalize", In(a.xy), Out(math.normalize(a.xy)), ComparisonType.Equals)); + QueueTest("math/normalize", "Normalize_XYZ", "Normalize float3", "Tests normalize with a float3 input.", CreateSelfContainedTestGraph("math/normalize", In(a.xyz), Out(math.normalize(a.xyz)), ComparisonType.Equals)); + QueueTest("math/normalize", "Normalize_XYZW", "Normalize float4", "Tests normalize with a float4 input.", CreateSelfContainedTestGraph("math/normalize", In(a), Out(math.normalize(a)), ComparisonType.Equals)); + } + + [Test] + public void TestDot() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(30.0f, 40.0f, 50.0f, 60.0f); + + QueueTest("math/dot", "Dot_XY", "Dot float2", "Tests dot product with a float2 input.", CreateSelfContainedTestGraph("math/dot", In(a.xy, b.xy), Out(math.dot(a.xy, b.xy)), ComparisonType.Equals)); + QueueTest("math/dot", "Dot_XYZ", "Dot float3", "Tests dot product with a float3 input.", CreateSelfContainedTestGraph("math/dot", In(a.xyz, b.xyz), Out(math.dot(a.xyz, b.xyz)), ComparisonType.Equals)); + QueueTest("math/dot", "Dot_XYZW", "Dot float4", "Tests dot product with a float4 input.", CreateSelfContainedTestGraph("math/dot", In(a, b), Out(math.dot(a, b)), ComparisonType.Equals)); + } + + [Test] + public void TestCross() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var b = new float4(30.0f, 40.0f, 50.0f, 60.0f); + + QueueTest("math/cross", "Cross_XYZ", "Cross Product", "Tests cross product.", CreateSelfContainedTestGraph("math/cross", In(a.xyz, b.xyz), Out(math.cross(a.xyz, b.xyz)), ComparisonType.Equals)); + QueueTest("math/cross", "Cross_XYZ_Self", "Cross Product When A = B", "Tests that the cross product of a vector with itself is a zero vector.", CreateSelfContainedTestGraph("math/cross", In(a.xyz, a.xyz), Out(new float3(0.0f, 0.0f, 0.0f)), ComparisonType.Equals)); + } + + [Test] + public void TestCombine2() + { + QueueTest("math/combine2", "Combine2", "Combine2", "Tests Combine2 operation.", CreateSelfContainedTestGraph("math/combine2", In(1.0f, 2.0f), Out(new float2(1.0f, 2.0f)), ComparisonType.Equals)); + } + + [Test] + public void TestCombine3() + { + QueueTest("math/combine3", "Combine3", "Combine3", "Tests Combine3 operation.", CreateSelfContainedTestGraph("math/combine3", In(1.0f, 2.0f, 3.0f), Out(new float3(1.0f, 2.0f, 3.0f)), ComparisonType.Equals)); + } + + [Test] + public void TestCombine4() + { + QueueTest("math/combine4", "Combine4", "Combine4", "Tests Combine4 operation.", CreateSelfContainedTestGraph("math/combine4", In(1.0f, 2.0f, 3.0f, 4.0f), Out(new float4(1.0f, 2.0f, 3.0f, 4.0f)), ComparisonType.Equals)); + } + + [Test] + public void TestCombine2x2() + { + CombineTest("Combine2x2", "Combine2x2", "Tests Combine2x2 operation.", "math/combine2x2", new float[] { 1.0f, 2.0f, 3.0f, 4.0f }, new float2x2(new float2(1.0f, 2.0f), new float2(3.0f, 4.0f))); + } + + [Test] + public void TestCombine3x3() + { + CombineTest("Combine3x3", "Combine3x3", "Tests Combine3x3 operation.", "math/combine3x3", new float[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f }, new float3x3(new float3(1.0f, 2.0f, 3.0f), new float3(4.0f, 5.0f, 6.0f), new float3(7.0f, 8.0f, 9.0f))); + } + + [Test] + public void TestCombine4x4() + { + CombineTest("Combine4x4", "Combine4x4", "Tests Combine4x4 operation.", "math/combine4x4", new float[] { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f }, new float4x4(new float4(1.0f, 2.0f, 3.0f, 4.0f), new float4(5.0f, 6.0f, 7.0f, 8.0f), new float4(9.0f, 10.0f, 11.0f, 12.0f), new float4(13.0f, 14.0f, 15.0f, 16.0f))); + } + + private static void CombineTest(string fileName, string testName, string testDescription, string nodeName, float[] inputValues, T expected) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + for (int i = 0; i < inputValues.Length; i++) + { + inputs.Add(ConstStrings.Letters[i], new Value() { id = ConstStrings.Letters[i], property = new Property(inputValues[i]) }); + } + + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + QueueTest(nodeName, fileName, testName, testDescription, CreateSelfContainedTestGraph(nodeName, inputs, outputs, ComparisonType.Equals)); + } + + [Test] + public void TestExtract2() + { + var v = new float2(10.3f, -10.4f); + + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); + for (int i = 0; i < 2; i++) + { + outputs.Add(ConstStrings.Numbers[i], new Property(v[i])); + } + + QueueTest("math/extract2", "Extract2", "Extract2", "Tests Extract2 operation.", CreateSelfContainedTestGraph("math/extract2", inputs, outputs, ComparisonType.Equals)); + } + + [Test] + public void TestExtract3() + { + var v = new float3(10.3f, -10.4f, 32.3f); + + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); + for (int i = 0; i < 3; i++) + { + outputs.Add(ConstStrings.Numbers[i], new Property(v[i])); + } + + QueueTest("math/extract3", "Extract3", "Extract3", "Tests Extract3 operation.", CreateSelfContainedTestGraph("math/extract3", inputs, outputs, ComparisonType.Equals)); + } + + [Test] + public void TestExtract4() + { + var v = new float4(10.3f, -10.4f, 32.3f, 11.5f); + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); + for (int i = 0; i < 4; i++) + { + outputs.Add(ConstStrings.Numbers[i], new Property(v[i])); + } + + QueueTest("math/extract4", "Extract4", "Extract4", "Tests Extract4 operation.", CreateSelfContainedTestGraph("math/extract4", inputs, outputs, ComparisonType.Equals)); + } + + [Test] + public void TestExtract2x2() + { + var v = new float2x2(1.0f, 2.0f, 3.0f, 4.0f); + + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); + for (int i = 0; i < 4; i++) + { + outputs.Add(ConstStrings.Numbers[i], new Property(v[i / 2][i % 2])); + } + + QueueTest("math/extract2x2", "Extract2x2", "Extract2x2", "Tests Extract2x2 operation.", CreateSelfContainedTestGraph("math/extract2x2", inputs, outputs, ComparisonType.Equals)); + } + + [Test] + public void TestExtract3x3() + { + var v = new float3x3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); + + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); + for (int i = 0; i < 9; i++) + { + outputs.Add(ConstStrings.Numbers[i], new Property(v[i / 3][i % 3])); + } + + QueueTest("math/extract3x3", "Extract3x3", "Extract3x3", "Tests Extract3x3 operation.", CreateSelfContainedTestGraph("math/extract3x3", inputs, outputs, ComparisonType.Equals)); + } + + [Test] + public void TestExtract4x4() + { + var v = new float4x4(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f); + + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); + for (int i = 0; i < 16; i++) + { + outputs.Add(ConstStrings.Numbers[i], new Property(v[i / 4][i % 4])); + } + + QueueTest("math/extract4x4", "Extract4x4", "Extract4x4", "Tests Extract4x4 operation.", CreateSelfContainedTestGraph("math/extract4x4", inputs, outputs, ComparisonType.Equals)); + } + + [Test] + public void TestTranspose() + { + { + var mat4 = new float4x4(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f); + var tmat4 = new float4x4(); + for (int i = 0; i < 16; i++) + { + tmat4[i / 4][i % 4] = mat4[i % 4][i / 4]; + } + QueueTest("math/transpose", "Transpose_4x4", "Transpose 4x4", "Tests Transpose/4x4 operation.", CreateSelfContainedTestGraph("math/transpose", In(mat4), Out(tmat4), ComparisonType.Equals)); + } + + { + var mat3 = new float3x3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); + var tmat3 = new float3x3(); + for (int i = 0; i < 9; i++) + { + tmat3[i / 3][i % 3] = mat3[i % 3][i / 3]; + } + QueueTest("math/transpose", "Transpose_3x3", "Transpose 3x3", "Tests Transpose/3x3 operation.", CreateSelfContainedTestGraph("math/transpose", In(mat3), Out(tmat3), ComparisonType.Equals)); + } + + { + var mat2 = new float2x2(1.0f, 2.0f, 3.0f, 4.0f); + var tmat2 = new float2x2(); + for (int i = 0; i < 4; i++) + { + tmat2[i / 2][i % 2] = mat2[i % 2][i / 2]; + } + QueueTest("math/transpose", "Transpose_2x2", "Transpose 2x2", "Tests Transpose/2x2 operation.", CreateSelfContainedTestGraph("math/transpose", In(mat2), Out(tmat2), ComparisonType.Equals)); + } + } + + [Test] + public void TestDeterminant() + { + // 4x4 Matrix Determinant + var mat4 = new float4x4(1.0f, 2.0f, 3.0f, 41.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 151.0f, 16.0f); + QueueTest("math/determinant", "Determinant_4x4", "Determinant 4x4", "Tests Determinant/4x4 operation.", CreateSelfContainedTestGraph("math/determinant", In(mat4), Out(20128.0f), ComparisonType.Equals)); + + // 3x3 Matrix Determinant + var mat3 = new float3x3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 91.0f); + QueueTest("math/determinant", "Determinant_3x3", "Determinant 3x3", "Tests Determinant/3x3 operation.", CreateSelfContainedTestGraph("math/determinant", In(mat3), Out(-246.0f), ComparisonType.Equals)); + + // 2x2 Matrix Determinant + var mat2 = new float2x2(1.0f, 2.0f, 3.0f, 41.0f); + QueueTest("math/determinant", "Determinant_2x2", "Determinant 2x2", "Tests Determinant/2x2 operation.", CreateSelfContainedTestGraph("math/determinant", In(mat2), Out(35.0f), ComparisonType.Equals)); + } + + [Test] + public void TestInverse() + { + // 4x4 Matrix Inverse + var mat4 = new float4x4(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 121.0f, 13.0f, 14.0f, 151.0f, 161.0f); + var imat4 = new float4x4(-1.47672f, 0.4608f, 0.008567f, 0.007352f, 1.21262f, -0.18996f, -0.00796f, -0.0147f, 0.00492f, -0.0025f, -0.009781f, 0.007352f, 0.00917f, -0.018348f, 0.009174f, 0.0f); + QueueTest("math/inverse", "Inverse_4x4", "Inverse 4x4", "Tests Inverse/4x4 operation.", CreateSelfContainedTestGraph("math/inverse", In(mat4), Out(imat4), ComparisonType.Approximately)); + + // 3x3 Matrix Inverse + var mat3 = new float3x3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 91.0f); + var imat3 = new float3x3(-1.65447f, 0.64227f, 0.0122f, 1.30894f, -0.28455f, -0.0244f, 0.0122f, -0.0244f, 0.0122f); + QueueTest("math/inverse", "Inverse_3x3", "Inverse 3x3", "Tests Inverse/3x3 operation.", CreateSelfContainedTestGraph("math/inverse", In(mat3), Out(imat3), ComparisonType.Approximately)); + + // 2x2 Matrix Inverse + var mat2 = new float2x2(1.0f, 2.0f, 3.0f, 41.0f); + var imat2 = new float2x2(1.17142f, -0.05714f, -0.08571f, 0.02857f); + QueueTest("math/inverse", "Inverse_2x2", "Inverse 2x2", "Tests Inverse/2x2 operation.", CreateSelfContainedTestGraph("math/inverse", In(mat2), Out(imat2), ComparisonType.Approximately)); + } + + [Test] + public void TestMatMul() + { + // 4x4 Matrix Multiplication + var mat41 = new float4x4(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f); + var mat42 = new float4x4(1.0f, 2.0f, 3.0f, 4.0f, 4.0f, 3.0f, 2.0f, 1.0f, 5.0f, 6.0f, 7.0f, 8.0f, 8.0f, 7.0f, 6.0f, 5.0f); + var matres = new float4x4(56.0f, 54.0f, 52.0f, 50.0f, 128.0f, 126.0f, 124.0f, 122.0f, 200.0f, 198.0f, 196.0f, 194.0f, 272.0f, 270.0f, 268.0f, 266.0f); + QueueTest("math/matmul", "MatMul_4x4", "MatMul 4x4", "Tests MatMul/4x4 operation.", CreateSelfContainedTestGraph("math/matmul", In(mat41, mat42), Out(matres), ComparisonType.Approximately)); + + // 3x3 Matrix Multiplication + var mat31 = new float3x3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); + var mat32 = new float3x3(1.0f, 2.0f, 3.0f, 3.0f, 2.0f, 1.0f, 6.0f, 4.0f, 5.0f); + var matres2 = new float3x3(25.0f, 18.0f, 20.0f, 55.0f, 42.0f, 47.0f, 85.0f, 66.0f, 74.0f); + QueueTest("math/matmul", "MatMul_3x3", "MatMul 3x3", "Tests MatMul/3x3 operation.", CreateSelfContainedTestGraph("math/matmul", In(mat31, mat32), Out(matres2), ComparisonType.Approximately)); + + // 2x2 Matrix Multiplication + var mat21 = new float2x2(1.0f, 2.0f, 3.0f, 4.0f); + var mat22 = new float2x2(1.0f, 2.0f, 2.0f, 1.0f); + var matres3 = new float2x2(5.0f, 4.0f, 11.0f, 10.0f); + QueueTest("math/matmul", "MatMul_2x2", "MatMul 2x2", "Tests MatMul/2x2 operation.", CreateSelfContainedTestGraph("math/matmul", In(mat21, mat22), Out(matres3), ComparisonType.Approximately)); + } + + [Test] + public void TestAnd() + { + QueueTest("math/and", "And_Boolean_False", "And Boolean True, False", "Tests true & false = false.", CreateSelfContainedTestGraph("math/and", In(true, false), Out(false), ComparisonType.Equals)); + QueueTest("math/and", "And_Boolean_False", "And Boolean False, False", "Tests false & true = false.", CreateSelfContainedTestGraph("math/and", In(false, false), Out(false), ComparisonType.Equals)); + QueueTest("math/and", "And_Boolean_True", "And Boolean True, True", "Tests true & true = true.", CreateSelfContainedTestGraph("math/and", In(true, true), Out(true), ComparisonType.Equals)); + + QueueTest("math/and", "And_Integer", "And Integer", "Tests int & operation.", CreateSelfContainedTestGraph("math/and", In(3, 8), Out(3 & 8), ComparisonType.Equals)); + } + + [Test] + public void TestOr() + { + QueueTest("math/or", "Or_Boolean_True", "Or Boolean True, False", "Tests true | false = true.", CreateSelfContainedTestGraph("math/or", In(true, false), Out(true), ComparisonType.Equals)); + QueueTest("math/or", "Or_Boolean_False", "Or Boolean False, False", "Tests false | false = false.", CreateSelfContainedTestGraph("math/or", In(false, false), Out(false), ComparisonType.Equals)); + QueueTest("math/or", "Or_Boolean_True", "Or Boolean True, True", "Tests true | true = true.", CreateSelfContainedTestGraph("math/or", In(true, true), Out(true), ComparisonType.Equals)); + + QueueTest("math/or", "Or_Integer", "Or Integer", "Tests int | operation.", CreateSelfContainedTestGraph("math/or", In(3, 8), Out(3 | 8), ComparisonType.Equals)); + } + + [Test] + public void TestXor() + { + QueueTest("math/xor", "Xor_Boolean_True", "Xor Boolean True", "Tests true ^ false = true.", CreateSelfContainedTestGraph("math/xor", In(true, false), Out(true), ComparisonType.Equals)); + QueueTest("math/xor", "Xor_Boolean_False", "Xor Boolean False", "Tests false ^ false = false.", CreateSelfContainedTestGraph("math/xor", In(false, false), Out(false), ComparisonType.Equals)); + QueueTest("math/xor", "Xor_Boolean_False", "Xor Boolean False", "Tests true ^ true = false.", CreateSelfContainedTestGraph("math/xor", In(true, true), Out(false), ComparisonType.Equals)); + + QueueTest("math/xor", "Xor_Integer", "XorInteger", "Tests int ^ operation.", CreateSelfContainedTestGraph("math/xor", In(3, 8), Out(3 ^ 8), ComparisonType.Equals)); + } + + [Test] + public void TestRotate3d() + { + RotateTest3D("Rotate3D_NegativeZ_AroundY_ByPiOver2", "Rotate3D Test 1", "Tests a rotation in 3D.", "math/rotate3d", new float3(0.0f, 0.0f, -1.0f), new float3(0.0f, 1.0f, 0.0f), math.PI * 0.5f, new float3(-1.0f, 0.0f, 0.0f)); + RotateTest3D("Rotate3D_X_AroundY_ByPi", "Rotate3D Test 2", "Tests a rotation in 3D.", "math/rotate3d", new float3(1.0f, 0.0f, 0.0f), new float3(0.0f, 1.0f, 0.0f), math.PI, new float3(-1.0f, 0.0f, 0.0f)); + } + + private static void RotateTest3D(string fileName, string testName, string testDescription, string nodeName, T a, T b, V c, T expected) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(a) }); + inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); + inputs.Add(ConstStrings.C, new Value() { id = ConstStrings.C, property = new Property(c) }); + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + QueueTest(nodeName, fileName, testName, testDescription, CreateSelfContainedTestGraph(nodeName, inputs, outputs, ComparisonType.Approximately)); + } + + [Test] + public void TestRotate2d() + { + RotateTest2D("Rotate2D_Y_ByPiOver2", "Rotate2D Test 1", "Tests a rotation in 2D.", "math/rotate2d", new float2(0.0f, 1.0f), math.PI * 0.5f, new float2(-1.0f, 0.0f)); + RotateTest2D("Rotate2D_NegativeX_ByPiOver2", "Rotate2D Test 2", "Tests a rotation in 2D.", "math/rotate2d", new float2(-1.0f, 0.0f), math.PI * 0.5f, new float2(0.0f, -1.0f)); + } + + private static void RotateTest2D(string fileName, string testName, string testDescription, string nodeName, T a, V b, T expected) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(a) }); + inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + QueueTest(nodeName, fileName, testName, testDescription, CreateSelfContainedTestGraph(nodeName, inputs, outputs, ComparisonType.Approximately)); + } + + [Test] + public void TestTransform() + { + TransformTest("Transform_2x2", "Transform 2x2", "Tests a matrix transformation with float2x2.", new float2(10.2f, 12.1f), new float2x2(2.3f, -4.1f, 12.4f, 11.5f), new float2(-26.15f, 265.63f)); + TransformTest("Transform_3x3", "Transform 3x3", "Tests a matrix transformation with float3x3.", new float3(10.2f, 12.1f, 16.4f), new float3x3(2.3f, -4.1f, 12.3f, 12.4f, 11.5f, 17.1f, 1.3f, -5.0f, 19.5f), new float3(175.57f, 546.07f, 272.56f)); + TransformTest("Transform_4x4", "Transform 4x4", "Tests a matrix transformation with float4x4.", new float4(10.2f, 12.1f, 16.4f, 6.4f), new float4x4(2.3f, -4.1f, 12.3f, 8.3f, 12.4f, 11.5f, 17.1f, 83.0f, 1.3f, -5.0f, 19.5f, 14.1f, 4.4f, 19.1f, 72.3f, 18.2f), new float4(228.69f, 1077.27f, 362.8f, 1578.19f)); + } + + private static void TransformTest(string fileName, string testName, string testDescription, T a, V b, T expected) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(a) }); + inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + QueueTest("math/transform", fileName, testName, testDescription, CreateSelfContainedTestGraph("math/transform", inputs, outputs, ComparisonType.Approximately)); + } + + [Test] + public void TestCbrt() + { + TestNodeWithAllFloatNInputVariants("Cbrt", "Cbrt", "Tests math/cbrt with standard values.", "math/cbrt", new float4(11.3f, -50.3f, 33.3f, 100.1f), new float4(2.24401703f, -3.69138487f, 3.21722482f, 4.64313551f)); + } + + [Test] + public void TestFract() + { + + TestNodeWithAllFloatNInputVariants("Fract", "Fract", "Tests math/fract with standard values.", "math/fract", new float4(15.4f, -10.1f, 12.39f, -32.33f), new float4(0.4f, 0.9f, 0.39f, 0.67f)); + } + + [Test] + public void TestNeg() + { + TestNodeWithAllFloatNInputVariants("Neg", "Neg", "Tests math/neg with standard values.", "math/neg", new float4(15.4f, -10.1f, 12.39f, -32.33f), new float4(-15.4f, 10.1f, -12.39f, 32.33f)); + } + + [Test] + public void TestRad() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.radians(a); + + TestNodeWithAllFloatNInputVariants("Rad", "Rad", "Tests math/rad with standard values.", "math/rad", a, expected); + } + + [Test] + public void TestDeg() + { + var a = new float4(34.0f, 41.0f, 30.0f, 70.0f); + var expected = math.degrees(a); + + TestNodeWithAllFloatNInputVariants("Deg", "Deg", "Tests math/deg with standard values.", "math/deg", a, expected); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs.meta new file mode 100644 index 000000000..750ff1c40 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec3b946b09b8244cfba21c5c61607aac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef b/Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef new file mode 100644 index 000000000..ce7a7b749 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef @@ -0,0 +1,27 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Math", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common", + "UnityGLTF.Interactivity.Playback.Tests.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef.meta new file mode 100644 index 000000000..022a1569a --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Math/UnityGLTF.Interactivity.Playback.Tests.Math.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7be37d5232e2ffd4e8fbb1486b4d66f0 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer.meta b/Tests/Runtime/Interactivity/Nodes/Pointer.meta new file mode 100644 index 000000000..7e0c787a5 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Pointer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8132311f40d6c4e4fa7d10fd840e39e7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs b/Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs new file mode 100644 index 000000000..ec3f07cc4 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs @@ -0,0 +1,105 @@ +namespace UnityGLTF.Interactivity.Playback.Tests +{ + partial class PointerNodesTests + { + + private static readonly (string pointer, string type)[] MATERIAL_POINTERS = new (string, string)[] + { + ("/materials/{nodeIndex}/alphaCutoff", "float"), + ("/materials/{nodeIndex}/emissiveFactor", "float3"), + ("/materials/{nodeIndex}/normalTexture/scale", "float"), + ("/materials/{nodeIndex}/occlusionTexture/strength", "float"), + ("/materials/{nodeIndex}/pbrMetallicRoughness/baseColorFactor", "float4"), + ("/materials/{nodeIndex}/pbrMetallicRoughness/metallicFactor", "float"), + ("/materials/{nodeIndex}/pbrMetallicRoughness/roughnessFactor", "float"), + + ("/materials/{nodeIndex}/normalTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/normalTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/normalTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/occlusionTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/occlusionTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/occlusionTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/emissiveTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/emissiveTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/emissiveTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/pbrMetallicRoughness/baseColorTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/pbrMetallicRoughness/baseColorTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/pbrMetallicRoughness/baseColorTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/pbrMetallicRoughness/metallicRoughnessTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/pbrMetallicRoughness/metallicRoughnessTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/pbrMetallicRoughness/metallicRoughnessTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatFactor", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatRoughnessFactor", "float"), + //("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatNormalTexture/scale", "float"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatRoughnessTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatRoughnessTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatRoughnessTexture/extensions/KHR_texture_transform/scale", "float2"), + + //("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatNormalTexture/extensions/KHR_texture_transform/offset", "float2"), + //("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatNormalTexture/extensions/KHR_texture_transform/rotation", "float"), + //("/materials/{nodeIndex}/extensions/KHR_materials_clearcoat/clearcoatNormalTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_dispersion/dispersion", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_ior/ior", "float"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceFactor", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceIor", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceThicknessMinimum", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceThicknessMaximum", "float"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceThicknessTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceThicknessTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_iridescence/iridescenceThicknessTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenColorFactor", "float3"), + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenRoughnessFactor", "float"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenColorTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenColorTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenColorTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenRoughnessTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenRoughnessTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_sheen/sheenRoughnessTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularFactor", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularColorFactor", "float3"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularColorTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularColorTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_specular/specularColorTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_transmission/transmissionFactor", "float"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_transmission/transmissionTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_transmission/transmissionTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_transmission/transmissionTexture/extensions/KHR_texture_transform/scale", "float2"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_volume/thicknessFactor", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_volume/attenuationDistance", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_volume/attenuationColor", "float3"), + + ("/materials/{nodeIndex}/extensions/KHR_materials_volume/thicknessTexture/extensions/KHR_texture_transform/offset", "float2"), + ("/materials/{nodeIndex}/extensions/KHR_materials_volume/thicknessTexture/extensions/KHR_texture_transform/rotation", "float"), + ("/materials/{nodeIndex}/extensions/KHR_materials_volume/thicknessTexture/extensions/KHR_texture_transform/scale", "float2"), + }; + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs.meta b/Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs.meta new file mode 100644 index 000000000..d98e0de87 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Pointer/MaterialPointers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d36d915584d38647931b0d31b652750 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs new file mode 100644 index 000000000..1224eacce --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs @@ -0,0 +1,552 @@ +using System; +using System.Collections; +using Unity.Mathematics; +using UnityEngine.TestTools; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public partial class PointerNodesTests : NodeTestHelpers + { + private const string TEST_GLB = "pointer_test"; + private const float INTERPOLATION_DURATION = 0.5f; + private static readonly float2 P1 = new float2(0.42f, 0f); + private static readonly float2 P2 = new float2(0.52f, 1f); + protected override string _subDirectory => "Pointer"; + + [UnityTest] + public IEnumerator PointerInterpolateGet_ValuesInGetAreWhatWereInterpolatedTo() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + for (int i = 0; i < MATERIAL_POINTERS.Length; i++) + { + var pointer = MATERIAL_POINTERS[i].pointer.Replace('/', '_'); + QueueTest("pointer/interpolate", $"InterpolateAndGetPointer{pointer}", $"Pointer Interpolate {pointer}", $"Tests that pointer/interpolate and pointer/get work for {pointer}.", CreatePointerInterpolateGraph(MATERIAL_POINTERS[i].pointer, MATERIAL_POINTERS[i].type), importer.Result); + } + } + + [UnityTest] + public IEnumerator PointerInterpolate_InvalidParameter() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateInterpolateErrGraph( + pointer: "/nodes/{nodeIndex}/translation", + nodeIndex: -1, + duration: 0.5f, + p1: new float2(0.42f, 0f), + p2: new float2(0.52f, 1f), + value: new float3(1f, 2f, 3f), + type: "float3"); + + QueueTest("pointer/interpolate", GetCallerName(), "Interpolate Pointer w/ Negative Parameter", "Tests that a pointer with a {parameter} in it triggers the err output flow when that value is negative. Test fails if out or done flows are activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerInterpolate_TypeMismatch() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateInterpolateErrGraph( + pointer: "/nodes/{nodeIndex}/translation", + nodeIndex: 0, + duration: 0.5f, + p1: new float2(0.42f, 0f), + p2: new float2(0.52f, 1f), + value: 4, + type: "int"); + + QueueTest("pointer/interpolate", GetCallerName(), "Interpolate Pointer w/ Type Mismatch", "Tests that a pointer with a value/config type that does not match the object model type activates the err flow. Test fails if out or done flows are activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerInterpolate_UnsupportedPointer() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateInterpolateErrGraph( + pointer: "/nodes/{nodeIndex}/unsupported", + nodeIndex: 0, + duration: 0.5f, + p1: new float2(0.42f, 0f), + p2: new float2(0.52f, 1f), + value: 4, + type: "int"); + QueueTest("pointer/interpolate", GetCallerName(), "Interpolate Pointer w/ Unsupported Pointer", "Tests that an interpolate node activates the err output flow when an unsupported pointer is used. Test fails if out or done flows are activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerInterpolate_InvalidDuration() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g1 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: -1f, p1: new float2(0.42f, 0f), p2: new float2(0.52f, 1f), value: new float3(1f, 1f, 1f), type: "float3"); + var g2 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: float.PositiveInfinity, p1: new float2(0.42f, 0f), p2: new float2(0.52f, 1f), value: new float3(1f, 1f, 1f), type: "float3"); + var g3 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: float.NaN, p1: new float2(0.42f, 0f), p2: new float2(0.52f, 1f), value: new float3(1f, 1f, 1f), type: "float3"); + + QueueTest("pointer/interpolate", "PointerInterpolate_NegativeDuration", "Interpolate Pointer w/ Negative Duration", "Tests that an interpolate node activates the err output flow when a negative duration is used. Test fails if out or done flows are activated or err flow is not activated.", g1, importer.Result); + QueueTest("pointer/interpolate", "PointerInterpolate_InfDuration", "Interpolate Pointer w/ Inf Duration", "Tests that an interpolate node activates the err output flow when an infinite duration is used. Test fails if out or done flows are activated or err flow is not activated.", g2, importer.Result); + QueueTest("pointer/interpolate", "PointerInterpolate_NaNDuration", "Interpolate Pointer w/ NaN Duration", "Tests that an interpolate node activates the err output flow when duration is NaN. Test fails if out or done flows are activated or err flow is not activated.", g3, importer.Result); + } + + [UnityTest] + public IEnumerator PointerInterpolate_InvalidP1() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + var g1 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: 0.5f, p1: new float2(-1f, 0f), p2: new float2(0.52f, 1f), value: new float3(1f, 1f, 1f), type: "float3"); + var g2 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: 0.5f, p1: new float2(0f, float.PositiveInfinity), p2: new float2(0.52f, 1f), value: new float3(1f, 1f, 1f), type: "float3"); + var g3 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: 0.5f, p1: new float2(0.5f, float.NaN), p2: new float2(0.52f, 1f), value: new float3(1f, 1f, 1f), type: "float3"); + + QueueTest("pointer/interpolate", "PointerInterpolate_NegativeP1", "Interpolate Pointer w/ Negative P1", "Tests that an interpolate node activates the err output flow when a negative value is used for P1. Test fails if out or done flows are activated or err flow is not activated.", g1, importer.Result); + QueueTest("pointer/interpolate", "PointerInterpolate_InfP1", "Interpolate Pointer w/ Inf P1", "Tests that an interpolate node activates the err output flow when an infinite value is used for P1. Test fails if out or done flows are activated or err flow is not activated.", g2, importer.Result); + QueueTest("pointer/interpolate", "PointerInterpolate_NaNP1", "Interpolate Pointer w/ NaN P1", "Tests that an interpolate node activates the err output flow when P1 contains a NaN. Test fails if out or done flows are activated or err flow is not activated.", g3, importer.Result); + } + + [UnityTest] + public IEnumerator PointerInterpolate_InvalidP2() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + var g1 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: -1f, p1: new float2(0.42f, 0f), p2: new float2(-1f, 0f), value: new float3(1f, 1f, 1f), type: "float3"); + var g2 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: float.PositiveInfinity, p1: new float2(0.42f, 0f), p2: new float2(0f, float.PositiveInfinity), value: new float3(1f, 1f, 1f), type: "float3"); + var g3 = CreateInterpolateErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, duration: float.NaN, p1: new float2(0.42f, 0f), p2: new float2(0.5f, float.NaN), value: new float3(1f, 1f, 1f), type: "float3"); + + QueueTest("pointer/interpolate", "PointerInterpolate_NegativeP1", "Interpolate Pointer w/ Negative P1", "Tests that an interpolate node activates the err output flow when a negative value is used for P1. Test fails if out or done flows are activated or err flow is not activated.", g1, importer.Result); + QueueTest("pointer/interpolate", "PointerInterpolate_InfP1", "Interpolate Pointer w/ Inf P1", "Tests that an interpolate node activates the err output flow when an infinite value is used for P1. Test fails if out or done flows are activated or err flow is not activated.", g2, importer.Result); + QueueTest("pointer/interpolate", "PointerInterpolate_NaNP1", "Interpolate Pointer w/ NaN P1", "Tests that an interpolate node activates the err output flow when P1 contains a NaN. Test fails if out or done flows are activated or err flow is not activated.", g3, importer.Result); + } + + [UnityTest] + public IEnumerator PointerInterpolate_ReadOnlyPointer() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + var g = CreateInterpolateErrGraph( + pointer: "/nodes/{nodeIndex}/weights.length", + nodeIndex: 0, + duration: 0.5f, + p1: new float2(0.42f, 0f), + p2: new float2(0.52f, 1f), + value: 4, + type: "int"); + + QueueTest("pointer/interpolate", GetCallerName(), "Interpolate Pointer w/ ReadOnly Pointer", "Tests that an interpolate node activates the err output flow when a readonly pointer is used. Test fails if out or done flows are activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerSetGet_ValuesInGetAreWhatWereSet() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + for (int i = 0; i < MATERIAL_POINTERS.Length; i++) + { + var pointer = MATERIAL_POINTERS[i].pointer.Replace('/', '_'); + QueueTest("pointer/set", $"SetAndGetPointer{pointer}", $"Pointer Set/Get {pointer}", $"Tests that pointer/set and pointer/get work for {pointer}.", CreatePointerSetGraph(MATERIAL_POINTERS[i].pointer, MATERIAL_POINTERS[i].type), importer.Result); + } + } + + [UnityTest] + public IEnumerator PointerSet_InvalidParameter() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateSetErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: -1, value: new float3(1f, 2f, 3f), type: "float3"); + + QueueTest("pointer/set", GetCallerName(), "Set Pointer w/ Negative Parameter", "Tests that a pointer with a {parameter} in it triggers the err output flow when that value is negative. Test fails if out flow is activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerSet_TypeMismatch() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateSetErrGraph(pointer: "/nodes/{nodeIndex}/translation", nodeIndex: 0, value: 4, type: "int"); + + QueueTest("pointer/set", GetCallerName(), "Set Pointer w/ Type Mismatch", "Tests that a pointer with a value/config type that does not match the object model type activates the err flow. Test fails if out flow is activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerSet_UnsupportedPointer() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateSetErrGraph(pointer: "/nodes/{nodeIndex}/unsupported", nodeIndex: 0, value: 4, type: "int"); + + QueueTest("pointer/set", GetCallerName(), "Set Pointer w/ Unsupported Pointer", "Tests that a set node activates the err output flow when an unsupported pointer is used. Test fails if out flow is activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerSet_ReadOnlyPointer() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateSetErrGraph(pointer: "/nodes/{nodeIndex}/weights.length", nodeIndex: 0, value: 4, type: "int"); + + QueueTest("pointer/set", GetCallerName(), "Set Pointer w/ ReadOnly Pointer", "Tests that a set node activates the err output flow when a readonly pointer is used. Test fails if out flow is activated or err flow is not activated.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerGet_InvalidParameter() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateGetErrGraph(pointer: "/materials/{nodeIndex}/alphaCutoff", nodeIndex: -1, type: "float"); + + QueueTest("pointer/get", GetCallerName(), "Get Pointer w/ Invalid Parameter", "Tests a pointer/get node with an invalid node index parameter. Test fails if isValid output value is true or the value output is not the default for the given type.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerGet_UnsupportedPointer() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateGetErrGraph(pointer: "/materials/{nodeIndex}/unsupported", nodeIndex: 0, type: "float"); + + QueueTest("pointer/get", GetCallerName(), "Get Pointer w/ Unsupported Pointer", "Tests a pointer/get node with an unsupported pointer. Test fails if isValid output value is true or the value output is not the default for the given type.", g, importer.Result); + } + + [UnityTest] + public IEnumerator PointerGet_IsValid() + { + var importer = LoadTestModel(TEST_GLB); + while (!importer.IsCompleted) + { + yield return null; + } + + var g = CreateGetValidGraph(pointer: "/materials/{nodeIndex}/alphaCutoff", nodeIndex: 0, type: "float"); + + QueueTest("pointer/get", GetCallerName(), "Get Pointer, Check IsValid", "Tests that a pointer/get node with a supported pointer has IsValid == true. Test fails if isValid output value is false.", g, importer.Result); + } + + private Graph CreatePointerInterpolateGraph(string pointer, string type) + { + var g = CreateGraphForTest(); + + IProperty value = type switch + { + "float" => new Property(0.7f), + "float2" => new Property(new float2(0.7f, 0.5f)), + "float3" => new Property(new float3(0.7f, 0.5f, 0.3f)), + "float4" => new Property(new float4(0.7f, 0.5f, 0.3f, 0.2f)), + _ => throw new InvalidOperationException(), + }; + + Util.Log($"{pointer} with type {type}"); + + var typeIndex = g.IndexOfType(type); + var onStartNode = g.CreateNode("event/onStart"); + var pointerInterpolateNode = g.CreateNode("pointer/interpolate"); + + pointerInterpolateNode.AddValue(ConstStrings.NODE_INDEX, 0); + pointerInterpolateNode.AddConfiguration(ConstStrings.TYPE, typeIndex); + pointerInterpolateNode.AddConfiguration(ConstStrings.POINTER, pointer); + pointerInterpolateNode.AddValue(ConstStrings.VALUE, value); + pointerInterpolateNode.AddValue(ConstStrings.DURATION, INTERPOLATION_DURATION); + pointerInterpolateNode.AddValue(ConstStrings.P1, P1); + pointerInterpolateNode.AddValue(ConstStrings.P2, P2); + + var get = g.CreateNode("pointer/get"); + get.AddConfiguration(ConstStrings.POINTER, pointer); + get.AddConfiguration(ConstStrings.TYPE, typeIndex); + get.AddValue(ConstStrings.NODE_INDEX, 0); + + var branch = g.CreateNode("flow/branch"); + var eq = g.CreateNode("math/eq"); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + + eq.AddConnectedValue(ConstStrings.A, get); + eq.AddValue(ConstStrings.B, value); + + var complete = g.CreateNode("event/send"); + var failLog = CreateFailSubGraph(g, $"Get did not return the correct value for pointer {pointer}." + " Expected: {expected}, Actual: {actual}"); + failLog.AddConnectedValue(ConstStrings.ACTUAL, get); + failLog.AddValue(ConstStrings.EXPECTED, value); + failLog.AddValue(ConstStrings.NODE_INDEX, 0); + + onStartNode.AddFlow(pointerInterpolateNode); + branch.AddFlow(complete, ConstStrings.TRUE); + branch.AddFlow(failLog, ConstStrings.FALSE); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var outSet = g.CreateNode("variable/set"); + var outGet = g.CreateNode("variable/get"); + var outVar = g.AddVariable("outFlowActivated", false); + var outVarIndex = g.IndexOfVariable(outVar); + var outBranch = g.CreateNode("flow/branch"); + var outFail = CreateFailSubGraph(g, "Out flow did not trigger during this test."); + + outSet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outGet.AddConfiguration(ConstStrings.VARIABLE, outVarIndex); + outSet.AddValue(ConstStrings.VALUE, true); + + outBranch.AddConnectedValue(ConstStrings.CONDITION, outGet); + outBranch.AddFlow(branch, ConstStrings.TRUE); + outBranch.AddFlow(outFail, ConstStrings.FALSE); + + var fail = CreateFailSubGraph(g, "Err flow should not trigger during this test."); + pointerInterpolateNode.AddFlow(fail, ConstStrings.ERR); + pointerInterpolateNode.AddFlow(outSet); + pointerInterpolateNode.AddFlow(outBranch, ConstStrings.DONE); + + return g; + } + + private static Graph CreatePointerSetGraph(string pointer, string type) + { + var g = CreateGraphForTest(); + + IProperty value = type switch + { + "float" => new Property(0.7f), + "float2" => new Property(new float2(0.7f, 0.5f)), + "float3" => new Property(new float3(0.7f, 0.5f, 0.3f)), + "float4" => new Property(new float4(0.7f, 0.5f, 0.3f, 0.2f)), + _ => throw new InvalidOperationException(), + }; + + Util.Log($"{pointer} with type {type}"); + + var typeIndex = g.IndexOfType(type); + var onStartNode = g.CreateNode("event/onStart"); + var pointerSetNode = g.CreateNode("pointer/set"); + + pointerSetNode.AddValue(ConstStrings.NODE_INDEX, 0); + pointerSetNode.AddConfiguration(ConstStrings.TYPE, typeIndex); + pointerSetNode.AddConfiguration(ConstStrings.POINTER, pointer); + pointerSetNode.AddValue(ConstStrings.VALUE, value); + + var get = g.CreateNode("pointer/get"); + get.AddConfiguration(ConstStrings.POINTER, pointer); + get.AddConfiguration(ConstStrings.TYPE, typeIndex); + get.AddValue(ConstStrings.NODE_INDEX, 0); + + var branch = g.CreateNode("flow/branch"); + var eq = g.CreateNode("math/eq"); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + + eq.AddConnectedValue(ConstStrings.A, get); + eq.AddValue(ConstStrings.B, value); + + var complete = g.CreateNode("event/send"); + var failLog = CreateFailSubGraph(g, $"Get did not return the correct value for pointer {pointer}." + " Expected: {expected}, Actual: {actual}"); + failLog.AddConnectedValue(ConstStrings.ACTUAL, get); + failLog.AddValue(ConstStrings.EXPECTED, value); + failLog.AddValue(ConstStrings.NODE_INDEX, 0); + + onStartNode.AddFlow(pointerSetNode); + pointerSetNode.AddFlow(branch); + branch.AddFlow(complete, ConstStrings.TRUE); + branch.AddFlow(failLog, ConstStrings.FALSE); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var fail = CreateFailSubGraph(g, "Err flow should not trigger during this test."); + pointerSetNode.AddFlow(fail, ConstStrings.ERR); + return g; + } + + private static Graph CreateInterpolateErrGraph(string pointer, int nodeIndex, float duration, float2 p1, float2 p2, T value, string type) + { + const float TEST_DURATION = 1f; + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var interp = g.CreateNode("pointer/interpolate"); + + interp.AddConfiguration(ConstStrings.TYPE, g.IndexOfType(type)); + interp.AddConfiguration(ConstStrings.POINTER, pointer); + interp.AddValue(ConstStrings.NODE_INDEX, nodeIndex); + interp.AddValue(ConstStrings.VALUE, value); + interp.AddValue(ConstStrings.P1, p1); + interp.AddValue(ConstStrings.P2, p2); + interp.AddValue(ConstStrings.DURATION, duration); + + onStartNode.AddFlow(interp); + + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var failOut = CreateFailSubGraph(g, "Out flow should not activate during this test."); + var failDone = CreateFailSubGraph(g, "Done flow should not activate during this test."); + var failErr = CreateFailSubGraph(g, "Err flow did not activate during this test."); + + var errFlowVar = g.AddVariable("errFlowActivated", false); + var errFlowVarIndex = g.IndexOfVariable(errFlowVar); + var errFlowSet = g.CreateNode("variable/set"); + var errFlowGet = g.CreateNode("variable/get"); + var onTick = g.CreateNode("event/onTick"); + var ge = g.CreateNode("math/ge"); + var timeBranch = g.CreateNode("flow/branch"); + var doneBranch = g.CreateNode("flow/branch"); + + errFlowSet.AddValue(ConstStrings.VALUE, true); + errFlowSet.AddConfiguration(ConstStrings.VARIABLE, errFlowVarIndex); + errFlowGet.AddConfiguration(ConstStrings.VARIABLE, errFlowVarIndex); + + onTick.AddFlow(timeBranch); + + ge.AddConnectedValue(ConstStrings.A, onTick, ConstStrings.TIME_SINCE_START); + ge.AddValue(ConstStrings.B, TEST_DURATION); + + timeBranch.AddConnectedValue(ConstStrings.CONDITION, ge); + timeBranch.AddFlow(doneBranch, ConstStrings.TRUE); + + doneBranch.AddConnectedValue(ConstStrings.CONDITION, errFlowGet); + doneBranch.AddFlow(complete, ConstStrings.TRUE); + doneBranch.AddFlow(failErr, ConstStrings.FALSE); + + interp.AddFlow(errFlowSet, ConstStrings.ERR); + interp.AddFlow(failOut, ConstStrings.OUT); + interp.AddFlow(failDone, ConstStrings.DONE); + return g; + } + + private static Graph CreateSetErrGraph(string pointer, int nodeIndex, T value, string type) + { + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var pointerSetNode = g.CreateNode("pointer/set"); + + pointerSetNode.AddConfiguration(ConstStrings.TYPE, g.IndexOfType(type)); + pointerSetNode.AddConfiguration(ConstStrings.POINTER, pointer); + pointerSetNode.AddValue(ConstStrings.NODE_INDEX, nodeIndex); + pointerSetNode.AddValue(ConstStrings.VALUE, value); + + onStartNode.AddFlow(pointerSetNode); + + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var failOut = CreateFailSubGraph(g, "Out flow should not activate during this test."); + pointerSetNode.AddFlow(complete, ConstStrings.ERR); + pointerSetNode.AddFlow(failOut); + + return g; + } + + private static Graph CreateGetErrGraph(string pointer, int nodeIndex, string type) + { + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var branch = g.CreateNode("flow/branch"); + var get = g.CreateNode("pointer/get"); + var typeIndex = g.IndexOfType(type); + + get.AddConfiguration(ConstStrings.TYPE, typeIndex); + get.AddConfiguration(ConstStrings.POINTER, pointer); + get.AddValue(ConstStrings.NODE_INDEX, nodeIndex); + + onStartNode.AddFlow(branch); + + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var failOut = CreateFailSubGraph(g, "isValid Should be false for this test."); + + var isnan = g.CreateNode("math/isnan"); + var defaultBranch = g.CreateNode("flow/branch"); + var failDefault = CreateFailSubGraph(g, $"Output value should be the default for type {type}."); + + isnan.AddConnectedValue(ConstStrings.A, get); + + defaultBranch.AddConnectedValue(ConstStrings.CONDITION, isnan); + defaultBranch.AddFlow(failDefault, ConstStrings.FALSE); + defaultBranch.AddFlow(complete, ConstStrings.TRUE); + + branch.AddFlow(defaultBranch, ConstStrings.FALSE); + branch.AddFlow(failOut, ConstStrings.TRUE); + branch.AddConnectedValue(ConstStrings.CONDITION, get, ConstStrings.IS_VALID); + + return g; + } + + private static Graph CreateGetValidGraph(string pointer, int nodeIndex, string type) + { + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var branch = g.CreateNode("flow/branch"); + var get = g.CreateNode("pointer/get"); + var typeIndex = g.IndexOfType(type); + + get.AddConfiguration(ConstStrings.TYPE, typeIndex); + get.AddConfiguration(ConstStrings.POINTER, pointer); + get.AddValue(ConstStrings.NODE_INDEX, nodeIndex); + + onStartNode.AddFlow(branch); + + var complete = g.CreateNode("event/send"); + complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var failOut = CreateFailSubGraph(g, "isValid Should be true for this test."); + + branch.AddConnectedValue(ConstStrings.CONDITION, get, ConstStrings.IS_VALID); + branch.AddFlow(complete, ConstStrings.TRUE); + branch.AddFlow(failOut, ConstStrings.FALSE); + + return g; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs.meta new file mode 100644 index 000000000..9c03bbd31 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f5908544ecfb6ac48a07cf76e5ac528c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef b/Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef new file mode 100644 index 000000000..a8ba483a3 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef @@ -0,0 +1,27 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Pointer", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common", + "UnityGLTF.Interactivity.Playback.Tests.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef.meta new file mode 100644 index 000000000..01dc82acf --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Pointer/UnityGLTF.Interactivity.Playback.Tests.Pointer.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6150265ddcd93d042adb9d6402968ece +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Type.meta b/Tests/Runtime/Interactivity/Nodes/Type.meta new file mode 100644 index 000000000..0226ce896 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Type.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d04bfb0c629046847b3c19d85c85697b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs new file mode 100644 index 000000000..ba0f8167b --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs @@ -0,0 +1,60 @@ +using NUnit.Framework; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public class TypeNodesTests : NodeTestHelpers + { + protected override string _subDirectory => "Type"; + + [Test] + public void IntToFloat() + { + QueueTest("type/intToFloat", "IntToFloat_PositiveValue", "IntToFloat Positive Value", "Tests math/intToFloat with a positive value.", CreateSelfContainedTestGraph("type/intToFloat", In(10), Out(10f), ComparisonType.Equals)); + QueueTest("type/intToFloat", "IntToFloat_NegativeValue", "IntToFloat Negative Value", "Tests math/intToFloat with a negative value.", CreateSelfContainedTestGraph("type/intToFloat", In(-10), Out(-10f), ComparisonType.Equals)); + } + + [Test] + public void TestFloatToInt() + { + QueueTest("type/floatToInt", "FloatToInt_Negative", "FloatToInt Positive Value", "Tests math/floatToInt with a positive value.", CreateSelfContainedTestGraph("type/floatToInt", In(-10f), Out(-10), ComparisonType.Equals)); + QueueTest("type/floatToInt", "FloatToInt_Positive", "FloatToInt Negative Value", "Tests math/floatToInt with a negative value.", CreateSelfContainedTestGraph("type/floatToInt", In(10f), Out(10), ComparisonType.Equals)); + QueueTest("type/floatToInt", "FloatToInt_Truncate_10.32", "FloatToInt Truncate 1", "Tests math/floatToInt truncates properly.", CreateSelfContainedTestGraph("type/floatToInt", In(10.32f), Out(10), ComparisonType.Equals)); + QueueTest("type/floatToInt", "FloatToInt_Truncate_10.81", "FloatToInt Truncate 2", "Tests math/floatToInt truncates properly.", CreateSelfContainedTestGraph("type/floatToInt", In(10.81f), Out(10), ComparisonType.Equals)); + QueueTest("type/floatToInt", "FloatToInt_Truncate_Neg_10.81", "FloatToInt Truncate Negative 1", "Tests math/floatToInt truncates properly.", CreateSelfContainedTestGraph("type/floatToInt", In(-10.81f), Out(-10), ComparisonType.Equals)); + QueueTest("type/floatToInt", "FloatToInt_Truncate_Neg_10.32", "FloatToInt Truncate Negative 2", "Tests math/floatToInt truncates properly.", CreateSelfContainedTestGraph("type/floatToInt", In(-10.32f), Out(-10), ComparisonType.Equals)); + } + + [Test] + public void TestBoolToInt() + { + QueueTest("type/boolToInt", "BoolToInt_True", "BoolToInt True", "Tests that a true value results in 1", CreateSelfContainedTestGraph("type/boolToInt", In(true), Out(1), ComparisonType.Equals)); + QueueTest("type/boolToInt", "BoolToInt_False", "BoolToInt False", "Tests that a false value results in 0", CreateSelfContainedTestGraph("type/boolToInt", In(false), Out(0), ComparisonType.Equals)); + } + + [Test] + public void TestBoolToFloat() + { + QueueTest("type/boolToFloat", "BoolToFloat_True", "BoolToFloat True", "Tests that a true value results in 1", CreateSelfContainedTestGraph("type/boolToFloat", In(true), Out(1f), ComparisonType.Equals)); + QueueTest("type/boolToFloat", "BoolToFloat_False", "BoolToFloat False", "Tests that a false value results in 0", CreateSelfContainedTestGraph("type/boolToFloat", In(false), Out(0f), ComparisonType.Equals)); + } + + [Test] + public void TestIntToBool() + { + QueueTest("type/intToBool", "IntToBool_One", "IntToBool 1", "Tests that value 1 results in true.", CreateSelfContainedTestGraph("type/intToBool", In(1), Out(true), ComparisonType.Equals)); + QueueTest("type/intToBool", "IntToBool_Zero", "IntToBool 0", "Tests that value 0 results in false.", CreateSelfContainedTestGraph("type/intToBool", In(0), Out(false), ComparisonType.Equals)); + QueueTest("type/intToBool", "IntToBool_Negative", "IntToBool Negative", "Tests that a negative value results in true.", CreateSelfContainedTestGraph("type/intToBool", In(-155), Out(true), ComparisonType.Equals)); + } + + [Test] + public void TestFloatToBool() + { + QueueTest("type/floatToBool", "FloatToBool_One", "FloatToBool 1", "Tests that value 1 results in true.", CreateSelfContainedTestGraph("type/floatToBool", In(1f), Out(true), ComparisonType.Equals)); + QueueTest("type/floatToBool", "FloatToBool_Zero", "FloatToBool 0", "Tests that value 0 results in false.", CreateSelfContainedTestGraph("type/floatToBool", In(0f), Out(false), ComparisonType.Equals)); + QueueTest("type/floatToBool", "FloatToBool_Negative", "FloatToBool Negative", "Tests that a negative value results in true.", CreateSelfContainedTestGraph("type/floatToBool", In(-155f), Out(true), ComparisonType.Equals)); + QueueTest("type/floatToBool", "FloatToBool_PositiveInfinity", "FloatToBool Inf", "Tests that inf results in true.", CreateSelfContainedTestGraph("type/floatToBool", In(float.PositiveInfinity), Out(true), ComparisonType.Equals)); + QueueTest("type/floatToBool", "FloatToBool_NaN", "FloatToBool NaN", "Tests that NaN results in true.", CreateSelfContainedTestGraph("type/floatToBool", In(float.NaN), Out(true), ComparisonType.Equals)); + } + + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs.meta new file mode 100644 index 000000000..c527ea8eb --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Type/TypeNodesTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3e77c0da8a1f0d49ad2720203c9135d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef b/Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef new file mode 100644 index 000000000..786812a0e --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef @@ -0,0 +1,27 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Type", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common", + "UnityGLTF.Interactivity.Playback.Tests.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef.meta new file mode 100644 index 000000000..7ab6556d1 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Type/UnityGLTF.Interactivity.Playback.Tests.Type.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 618305c283701e44ba15a22227125a6c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Variable.meta b/Tests/Runtime/Interactivity/Nodes/Variable.meta new file mode 100644 index 000000000..b0472cdb5 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Variable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7b7961d7869572147b97baf127718351 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef b/Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef new file mode 100644 index 000000000..28ad18e8e --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef @@ -0,0 +1,27 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Tests.Variable", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mathematics", + "UnityGLTFScripts", + "UnityGLTF.Interactivity.Playback", + "UnityGLTF.Interactivity.Playback.Common", + "UnityGLTF.Interactivity.Playback.Tests.Common" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef.meta b/Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef.meta new file mode 100644 index 000000000..93a7488a3 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Variable/UnityGLTF.Interactivity.Playback.Tests.Variable.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4252edcc425237b4b8b6e3577d7e1b9a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs b/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs new file mode 100644 index 000000000..1d1e6298c --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs @@ -0,0 +1,326 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback.Tests +{ + public class VariableNodeTests : NodeTestHelpers + { + private const float INTERPOLATE_DURATION = 0.5f; + private static readonly float2 P1 = new float2(0.42f, 0f); + private static readonly float2 P2 = new float2(0.52f, 1f); + protected override string _subDirectory => "Variable"; + + [Test] + public void VariableGetAndSet_VariablesAllChangeToSetValuesAndGetReturnsThemCorrectly() + { + QueueTest("variable/set", GetCallerName(), "Variable Set & Get", "Creates a bool, int, float, float2, float3, float4, float2x2, float3x3, and float4x4 variable with an arbitrary default value, sets each to a different value, and uses a get node to check that they changed correctly.", CreateVariableGetAndSetGraph(new VariableSetSubGraph())); + } + + [Test] + public void VariableInterpolate_VariablesAllChangeToSetValuesAndGetReturnsThemCorrectly() + { + QueueTest("variable/interpolate", GetCallerName(), "Variable Interpolate Basic", "Creates variables for all floatN and floatNxN types with an arbitrary default value, and interpolates each to a different value. Test fails if err flow is activated, if the out flow does not activate before done flow, or if any of the values are incorrect after the done flow activates.", CreateVariableGetAndSetGraph(new VariableInterpolateSubGraph())); + } + + [Test] + public void VariableInterpolate_InvalidDuration_ActivatesErrFlow() + { + QueueTest("variable/interpolate", GetCallerName(), "Variable Interpolate Invalid Duration", "Test fails if out or done flow is activated for any of the three duration inputs or if the err flow is not activated for all three inputs.", GenerateInterpolateInvalidInputTestGraph(ConstStrings.DURATION, -1f, float.PositiveInfinity, float.NaN)); + } + + [Test] + public void VariableInterpolate_InvalidCP1_ActivatesErrFlow() + { + QueueTest("variable/interpolate", GetCallerName(), "Variable Interpolate Invalid P1", "Test fails if out or done flow is activated for any of the three duration inputs or if the err flow is not activated for all three inputs.", GenerateInterpolateInvalidInputTestGraph(ConstStrings.P1, new float2(-1f, 0f), new float2(0f, float.PositiveInfinity), new float2(0.5f, float.NaN))); + } + + [Test] + public void VariableInterpolate_InvalidCP2_ActivatesErrFlow() + { + QueueTest("variable/interpolate", GetCallerName(), "Variable Interpolate Invalid P2", "Test fails if out or done flow is activated for any of the three duration inputs or if the err flow is not activated for all three inputs.", GenerateInterpolateInvalidInputTestGraph(ConstStrings.P2, new float2(-1f, 0f), new float2(0f, float.PositiveInfinity), new float2(0.5f, float.NaN))); + } + + [Test] + public void VariableSetMultiple_VariablesAllChangeToSetValuesAndGetReturnsThemCorrectly() + { + QueueTest("variable/setMultiple", GetCallerName(), "Variable Set Multiple", "Creates a bool, int, float, float2, float3, float4, float2x2, float3x3, and float4x4 variable with an arbitrary default value, sets each to a different value, and uses a get node to check that they changed correctly. Sequence used for simplifying graph.", CreateVariableSetMultipleGraph()); + } + + private static Graph CreateVariableGetAndSetGraph(IVariableSubGraph subGraphGenerator) + { + var g = CreateGraphForTest(); + + if(subGraphGenerator is VariableSetSubGraph) + { + // Can't interpolate from false to true so only do that in the "variable/set" test. + subGraphGenerator.AppendVariableTestSubGraph(g, false, true); + subGraphGenerator.AppendVariableTestSubGraph(g, 0, 5); + } + + subGraphGenerator.AppendVariableTestSubGraph(g, 0f, 10f); + subGraphGenerator.AppendVariableTestSubGraph(g, new float2(0f, 1f), new float2(2f, 3f)); + subGraphGenerator.AppendVariableTestSubGraph(g, new float3(0f, 1f, 2f), new float3(3f, 4f, 5f)); + subGraphGenerator.AppendVariableTestSubGraph(g, new float4(0f, 1f, 2f, 3f), new float4(4f, 5f, 6f, 7f)); + var float2x2a = new float2x2(new float2(0f, 1f), new float2(2f, 3f)); + var float2x2b = new float2x2(new float2(-1f, -2f), new float2(-3f, -4f)); + subGraphGenerator.AppendVariableTestSubGraph(g, float2x2a, float2x2b); + var float3x3a = new float3x3( + new float3(0f, 1f, 2f), + new float3(3f, 4f, 5f), + new float3(6f, 7f, 8f) + ); + var float3x3b = new float3x3( + new float3(-1f, -2f, -3f), + new float3(-4f, -5f, -6f), + new float3(-7f, -8f, -9f) + ); + subGraphGenerator.AppendVariableTestSubGraph(g, float3x3a, float3x3b); + + var float4x4a = new float4x4( + new float4(0f, 1f, 2f, 3f), + new float4(4f, 5f, 6f, 7f), + new float4(8f, 9f, 10f, 11f), + new float4(12f, 13f, 14f, 15f) + ); + var float4x4b = new float4x4( + new float4(-1f, -2f, -3f, -4f), + new float4(-5f, -6f, -7f, -8f), + new float4(-9f, -10f, -11f, -12f), + new float4(-13f, -14f, -15f, -16f) + ); + subGraphGenerator.AppendVariableTestSubGraph(g, float4x4a, float4x4b); + + return g; + } + + private static (Node branch, int variableIndex) CreateCheckEqualitySubGraph(Graph g, T initialValue, T setValue) + { + var variableName = $"{Helpers.GetSignatureBySystemType(typeof(T))}Variable"; + var variable = g.AddVariable(variableName, initialValue); + var variableIndex = g.IndexOfVariable(variable); + + var get = g.CreateNode("variable/get"); + var fail = g.CreateNode("event/send"); + var success = g.CreateNode("event/send"); + var branch = g.CreateNode("flow/branch"); + var eq = g.CreateNode("math/eq"); + var failLog = g.CreateNode("debug/log"); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + success.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + get.AddConfiguration(ConstStrings.VARIABLE, variableIndex); + + branch.AddConnectedValue(ConstStrings.CONDITION, eq); + eq.AddConnectedValue(ConstStrings.A, get); + eq.AddValue(ConstStrings.B, setValue); + + branch.AddFlow(success, ConstStrings.TRUE); + branch.AddFlow(failLog, ConstStrings.FALSE); + + failLog.AddConfiguration(ConstStrings.MESSAGE, $"Variable \"{variableName}\" " + "Expected: {expected}, Actual: {actual}"); + failLog.AddValue(ConstStrings.EXPECTED, setValue); + failLog.AddConnectedValue(ConstStrings.ACTUAL, get); + + failLog.AddFlow(fail); + + return (branch, variableIndex); + } + + private static Graph CreateVariableSetMultipleGraph() + { + var g = CreateGraphForTest(); + + var onStartNode = g.CreateNode("event/onStart"); + var set = g.CreateNode("variable/setMultiple"); + var sequence = g.CreateNode("flow/sequence"); + + onStartNode.AddFlow(set); + set.AddFlow(sequence); + + var expectedf = 10f; + var expectedi = 5; + var expectedb = true; + var expected2 = new float2(9f, 8f); + var expected3 = new float3(5f, 4f, 3f); + var expected4 = new float4(9f, 8f, 7f, 6f); + var expected2x2 = new float2x2(new float2(4f, 3f), new float2(2f, 1f)); + var expected3x3 = new float3x3(new float3(13f, 12f, 11f), new float3(10f, 9f, 8f), new float3(7f, 6f, 5f)); + var expected4x4 = new float4x4(new float4(29f, 28f, 27f, 26f), new float4(25f, 24f, 23f, 22f), new float4(21f, 20f, 19f, 18f), new float4(17f, 16f, 15f, 14f)); + + (var boolBranch, var boolIndex) = CreateCheckEqualitySubGraph(g, false, expectedb); + (var intBranch, var intIndex) = CreateCheckEqualitySubGraph(g, 0, expectedi); + (var floatBranch, var floatIndex) = CreateCheckEqualitySubGraph(g, 0f, expectedf); + (var float2Branch, var float2Index) = CreateCheckEqualitySubGraph(g, new float2(1f, 2f), expected2); + (var float3Branch, var float3Index) = CreateCheckEqualitySubGraph(g, new float3(3f, 4f, 5f), expected3); + (var float4Branch, var float4Index) = CreateCheckEqualitySubGraph(g, new float4(6f, 7f, 8f, 9f), expected4); + (var float2x2Branch, var float2x2Index) = CreateCheckEqualitySubGraph(g, new float2x2(new float2(1f, 2f), new float2(3f, 4f)), expected2x2); + (var float3x3Branch, var float3x3Index) = CreateCheckEqualitySubGraph(g, new float3x3(new float3(5f, 6f, 7f), new float3(8f, 9f, 10f), new float3(11f, 12f, 13f)), expected3x3); + (var float4x4Branch, var float4x4Index) = CreateCheckEqualitySubGraph(g, + new float4x4( + new float4(14f, 15f, 16f, 17f), + new float4(18f, 19f, 20f, 21f), + new float4(22f, 23f, 24f, 25f), + new float4(26f, 27f, 28f, 29f) + ), expected4x4 + ); + + sequence.AddFlow(boolBranch, ConstStrings.Numbers[0]); + sequence.AddFlow(intBranch, ConstStrings.Numbers[1]); + sequence.AddFlow(floatBranch, ConstStrings.Numbers[2]); + sequence.AddFlow(float2Branch, ConstStrings.Numbers[3]); + sequence.AddFlow(float3Branch, ConstStrings.Numbers[4]); + sequence.AddFlow(float4Branch, ConstStrings.Numbers[5]); + sequence.AddFlow(float2x2Branch, ConstStrings.Numbers[6]); + sequence.AddFlow(float3x3Branch, ConstStrings.Numbers[7]); + sequence.AddFlow(float4x4Branch, ConstStrings.Numbers[8]); + + set.AddValue(ConstStrings.Numbers[0], expectedb); + set.AddValue(ConstStrings.Numbers[1], expectedi); + set.AddValue(ConstStrings.Numbers[2], expectedf); + set.AddValue(ConstStrings.Numbers[3], expected2); + set.AddValue(ConstStrings.Numbers[4], expected3); + set.AddValue(ConstStrings.Numbers[5], expected4); + set.AddValue(ConstStrings.Numbers[6], expected2x2); + set.AddValue(ConstStrings.Numbers[7], expected3x3); + set.AddValue(ConstStrings.Numbers[8], expected4x4); + + set.AddConfiguration(ConstStrings.VARIABLES, new int[] { + boolIndex, + intIndex, + floatIndex, + float2Index, + float3Index, + float4Index, + float2x2Index, + float3x3Index, + float4x4Index + }); + + return g; + } + + private static Graph GenerateInterpolateInvalidInputTestGraph(string inputName, T invalid1, T invalid2, T invalid3) + { + Graph g = CreateGraphForTest(); + + g.AddVariable("a", 0f); // 0 + + var start = g.CreateNode("event/onStart"); + var success = g.CreateNode("event/send"); + success.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); + + var setDelayNeg = CreateInterpolateInvalidInputSubGraph(g, inputName, invalid1); + var setDelayInf = CreateInterpolateInvalidInputSubGraph(g, inputName, invalid2); + var setDelayNaN = CreateInterpolateInvalidInputSubGraph(g, inputName, invalid3); + + start.AddFlow(setDelayNeg); + setDelayNeg.AddFlow(setDelayInf, ConstStrings.ERR); + setDelayInf.AddFlow(setDelayNaN, ConstStrings.ERR); + setDelayNaN.AddFlow(success, ConstStrings.ERR); + + return g; + } + + private static Node CreateInterpolateInvalidInputSubGraph(Graph g, string inputName, T inputValue) + { + var interpolate = g.CreateNode("variable/interpolate"); + + interpolate.AddConfiguration(ConstStrings.USE_SLERP, false); + interpolate.AddValue(ConstStrings.VALUE, 100f); + interpolate.AddValue(ConstStrings.P1, P1); + interpolate.AddValue(ConstStrings.P2, P2); + interpolate.AddValue(ConstStrings.DURATION, INTERPOLATE_DURATION); + + interpolate.AddConfiguration(ConstStrings.VARIABLE, 0); + interpolate.AddValue(inputName, inputValue); + + var logFailOut = CreateFailSubGraph(g, $"Out flow was activated despite using {inputValue} duration."); + var logFailDone = CreateFailSubGraph(g, $"Done flow was activated despite using {inputValue} duration."); + + interpolate.AddFlow(logFailOut); + interpolate.AddFlow(logFailDone, ConstStrings.DONE); + return interpolate; + } + + private interface IVariableSubGraph + { + public void AppendVariableTestSubGraph(Graph g, T initialValue, T setValue); + } + + private class VariableSetSubGraph : IVariableSubGraph + { + public void AppendVariableTestSubGraph(Graph g, T initialValue, T setValue) + { + (var branch, var variableIndex) = CreateCheckEqualitySubGraph(g, initialValue, setValue); + + var onStartNode = g.CreateNode("event/onStart"); + + var set = g.CreateNode("variable/set"); + + set.AddConfiguration(ConstStrings.VARIABLE, variableIndex); + set.AddValue(ConstStrings.VALUE, setValue); + + onStartNode.AddFlow(set); + set.AddFlow(branch); + } + } + + private class VariableInterpolateSubGraph : IVariableSubGraph + { + public void AppendVariableTestSubGraph(Graph g, T initialValue, T setValue) + { + var interpolateType = Helpers.GetSignatureBySystemType(typeof(T)); + + (var branch, var variableIndex) = CreateCheckEqualitySubGraph(g, initialValue, setValue); + + var onStartNode = g.CreateNode("event/onStart"); + var interpolate = g.CreateNode("variable/interpolate"); + + var fail = g.CreateNode("event/send"); + var failLog = g.CreateNode("debug/log"); + + fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + failLog.AddConfiguration(ConstStrings.MESSAGE, $"Err flow activated on interpolate node for type {interpolateType}."); + failLog.AddFlow(fail); + + interpolate.AddConfiguration(ConstStrings.USE_SLERP, false); + interpolate.AddConfiguration(ConstStrings.VARIABLE, variableIndex); + interpolate.AddValue(ConstStrings.VALUE, setValue); + interpolate.AddValue(ConstStrings.DURATION, INTERPOLATE_DURATION); + interpolate.AddValue(ConstStrings.P1, P1); + interpolate.AddValue(ConstStrings.P2, P2); + + onStartNode.AddFlow(interpolate); + + var outTriggeredVar = g.AddVariable($"{interpolateType}InterpOutTriggered", false); + var outTriggeredVarIndex = g.IndexOfVariable(outTriggeredVar); + var outSet = g.CreateNode("variable/set"); + var outGet = g.CreateNode("variable/get"); + var outBranch = g.CreateNode("flow/branch"); + var outFail = g.CreateNode("event/send"); + var outFailLog = g.CreateNode("debug/log"); + + outFail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); + outFailLog.AddConfiguration(ConstStrings.MESSAGE, $"Out flow did not activate on interpolate node for type {interpolateType} before done flow."); + outFailLog.AddFlow(outFail); + + outSet.AddValue(ConstStrings.VALUE, true); + outSet.AddConfiguration(ConstStrings.VARIABLE, outTriggeredVarIndex); + outGet.AddConfiguration(ConstStrings.VARIABLE, outTriggeredVarIndex); + + outBranch.AddConnectedValue(ConstStrings.CONDITION, outGet); + + interpolate.AddFlow(outBranch, ConstStrings.DONE); + interpolate.AddFlow(outSet); + interpolate.AddFlow(failLog, ConstStrings.ERR); + + outBranch.AddFlow(branch, ConstStrings.TRUE); + outBranch.AddFlow(outFailLog, ConstStrings.FALSE); + } + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs.meta b/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs.meta new file mode 100644 index 000000000..422282159 --- /dev/null +++ b/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b355c8ed3b95a543a1e082a9127b46d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 0328bad764d4b026821576e672bc120c49d13bfe Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Mon, 12 May 2025 16:09:59 -0400 Subject: [PATCH 02/28] Removed some leftovers --- .../Playback/Data/Helpers/Interpolation.cs | 43 ------------------- .../Interactivity/Playback/ICancelToken.cs | 38 ---------------- .../Playback/ICancelToken.cs.meta | 11 ----- .../Interactivity/Playback/NodeRegistry.cs | 2 - .../NodeSpecs/Debugging/DebugAssert.cs | 20 --------- .../NodeSpecs/Debugging/DebugAssert.cs.meta | 11 ----- 6 files changed, 125 deletions(-) delete mode 100644 Runtime/Scripts/Interactivity/Playback/ICancelToken.cs delete mode 100644 Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta delete mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs delete mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs index 2584f6fec..a6a607f21 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Interpolation.cs @@ -7,51 +7,8 @@ namespace UnityGLTF.Interactivity.Playback { - public struct BezierInterpolateData - { - public IPointer pointer; - public float duration; - public float2 cp0; - public float2 cp1; - public NodeEngineCancelToken cancellationToken; - } - public static partial class Helpers { - public static async Task InterpolateAsync(T from, T to, Action setter, Func evaluator, float duration, V cancellationToken) where V : struct, ICancelToken - { - for (float t = 0f; t < 1f; t += Time.deltaTime / duration) - { - if (cancellationToken.isCancelled) - return false; - - setter(evaluator(from, to, t)); - await Task.Yield(); - } - - return true; - } - - public static async Task LinearInterpolateAsync(T to, Pointer pointer, float duration, V cancellationToken) where V : struct, ICancelToken - { - return await InterpolateAsync(pointer.getter(), to, pointer.setter, pointer.evaluator, duration, cancellationToken); - } - - public static async Task InterpolateBezierAsync(Property to, BezierInterpolateData d) - { - var v = to.value; - return await InterpolateBezierAsync(v, d); - } - - public static async Task InterpolateBezierAsync(T to, BezierInterpolateData d) - { - var p = (Pointer)d.pointer; - - var evaluator = new Func((a, b, t) => p.evaluator(a, b, CubicBezier(t, d.cp0, d.cp1).y)); - - return await InterpolateAsync(p.getter(), to, p.setter, evaluator, d.duration, d.cancellationToken); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float2 CubicBezier(float t, float2 cp0, float2 cp1) { diff --git a/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs b/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs deleted file mode 100644 index ea4cc521c..000000000 --- a/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading; - -namespace UnityGLTF.Interactivity.Playback -{ - public interface ICancelToken - { - public bool isCancelled { get; } - } - - public struct NodeEngineCancelToken : ICancelToken - { - public CancellationToken engineToken; - public CancellationToken nodeToken; - - public NodeEngineCancelToken(CancellationToken engineToken, CancellationToken nodeToken) - { - this.engineToken = engineToken; - this.nodeToken = nodeToken; - } - - public bool isCancelled => engineToken.IsCancellationRequested || nodeToken.IsCancellationRequested; - } - - public struct EngineCancelToken : ICancelToken - { - public CancellationToken engineToken; - - public EngineCancelToken(CancellationToken engineToken) - { - this.engineToken = engineToken; - } - - public bool isCancelled => engineToken.IsCancellationRequested; - - public static implicit operator CancellationToken(EngineCancelToken d) => d.engineToken; - public static implicit operator EngineCancelToken(CancellationToken d) => new EngineCancelToken(d); - } -} diff --git a/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta b/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta deleted file mode 100644 index 4a051385f..000000000 --- a/Runtime/Scripts/Interactivity/Playback/ICancelToken.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 39477dd4b73bae340a7144d4ee556f9a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs index 3e3cf1500..506638c8c 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs @@ -24,7 +24,6 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["animation/start"] = (engine, node) => new AnimationStart(engine, node), ["animation/stop"] = (engine, node) => new AnimationStop(engine, node), ["animation/stopAt"] = (engine, node) => new AnimationStopAt(engine, node), - ["ADBE/output_console_node"] = (engine, node) => new DebugLog(engine, node), ["debug/log"] = (engine, node) => new DebugLog(engine, node), ["event/onHoverIn"] = (engine, node) => new EventOnHoverIn(engine, node), ["event/onHoverOut"] = (engine, node) => new EventOnHoverOut(engine, node), @@ -154,7 +153,6 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["animation/stop"] = new AnimationStopSpec(), ["animation/stopAt"] = new AnimationStopAtSpec(), ["debug/log"] = new DebugLogSpec(), - ["debug/assert"] = new DebugAssertSpec(), // ["event/onHoverIn"] = new EventOnHoverInSpec(), // ["event/onHoverOut"] = new EventOnHoverOutSpec(), ["event/onStart"] = new EventOnStartSpec(), diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs deleted file mode 100644 index 1133dd93a..000000000 --- a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Unity.Mathematics; - -namespace UnityGLTF.Interactivity.Playback -{ - public class DebugAssertSpec : NodeSpecifications - { - protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() - { - var values = new NodeValue[] - { - new NodeValue(ConstStrings.A, "Condition", new Type[] { typeof(bool) }), - new NodeValue(ConstStrings.B, "Parameter 1", new Type[] { typeof(bool), typeof(int), typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }), - new NodeValue(ConstStrings.C, "Parameter 2", new Type[] { typeof(bool), typeof(int), typeof(float), typeof(int), typeof(float2), typeof(float3), typeof(float4) }) - }; - - return (null, values); - } - } -} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta deleted file mode 100644 index 3cdf0e5ee..000000000 --- a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Debugging/DebugAssert.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 264d8e380a9fa4eef90436c8702950ed -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From ce39293e6401a2d1f26b696760d56facb4aa1874 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Tue, 13 May 2025 11:02:34 -0400 Subject: [PATCH 03/28] Creation of BehaviourEngine and event/animation wrappers is now handled in the InteractivityImporterContext automatically. Unit tests updated to create their own engine and wrappers as the tests are using generated graphs and non-interactive glbs. Added import events to GltfImportPlugin for mesh, camera, vertex colored material, and load complete. --- Runtime/Scripts/GLTFSceneImporter.cs | 7 +- .../Interactivity/Playback/BehaviourEngine.cs | 6 +- .../Import/InteractivityImportContext.cs | 74 ++++++++- .../Import/InteractivityImportPlugin.cs | 4 +- .../Playback/Pointers/CameraPointers.cs | 3 +- .../Pointers/Materials/MaterialPointers.cs | 3 +- .../Playback/Pointers/MeshPointers.cs | 3 +- .../Playback/Pointers/NodePointers.cs | 4 +- .../Playback/Pointers/PointerResolver.cs | 151 ++++++++++++++---- .../Playback/Pointers/ScenePointers.cs | 14 +- .../Scripts/Plugins/Core/GltfImportPlugin.cs | 21 ++- .../Scripts/SceneImporter/ImporterCameras.cs | 6 + .../SceneImporter/ImporterMaterials.cs | 2 + .../Scripts/SceneImporter/ImporterMeshes.cs | 16 +- .../Nodes/Common/NodeTestHelpers.cs | 47 +++++- ...Interactivity.Playback.Tests.Common.asmdef | 3 +- 16 files changed, 309 insertions(+), 55 deletions(-) diff --git a/Runtime/Scripts/GLTFSceneImporter.cs b/Runtime/Scripts/GLTFSceneImporter.cs index 73465972d..6d8f1ae2a 100644 --- a/Runtime/Scripts/GLTFSceneImporter.cs +++ b/Runtime/Scripts/GLTFSceneImporter.cs @@ -519,7 +519,12 @@ public void Dispose() if (this.progress != null) await Task.Yield(); - + + foreach (var plugin in Context.Plugins) + { + plugin.OnLoadComplete(LastLoadedScene); + } + onLoadComplete?.Invoke(LastLoadedScene, null); } diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs index 137ec73e2..c175bd352 100644 --- a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs @@ -25,10 +25,10 @@ public class BehaviourEngine public readonly PointerResolver pointerResolver; - public BehaviourEngine(Graph graph, GLTFSceneImporter importer) + public BehaviourEngine(Graph graph, PointerResolver pointerResolver) { this.graph = graph; - pointerResolver = importer != null ? new PointerResolver(importer) : null; + this.pointerResolver = pointerResolver; for (int i = 0; i < graph.nodes.Count; i++) { @@ -118,7 +118,7 @@ public void SetAnimationWrapper(AnimationWrapper wrapper, Animation animation) { animationWrapper = wrapper; wrapper.SetData(this, animation); - pointerResolver.RegisterAnimations(wrapper); + pointerResolver.CreateAnimationPointers(wrapper); } public void PlayAnimation(in AnimationPlayData data) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs index 47a310066..5f06680ce 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -1,4 +1,5 @@ using GLTF.Schema; +using System; using UnityEngine; using UnityGLTF.Plugins; @@ -7,10 +8,13 @@ namespace UnityGLTF.Interactivity.Playback public class InteractivityImportContext : GLTFImportPluginContext { internal readonly InteractivityImportPlugin settings; + private PointerResolver _pointerResolver; + private GLTFImportContext _context; - public InteractivityImportContext(InteractivityImportPlugin interactivityLoader) + public InteractivityImportContext(InteractivityImportPlugin interactivityLoader, GLTFImportContext context) { settings = interactivityLoader; + _context = context; } /// @@ -18,6 +22,7 @@ public InteractivityImportContext(InteractivityImportPlugin interactivityLoader) /// public override void OnBeforeImport() { + _pointerResolver = new(); Util.Log($"InteractivityImportContext::OnBeforeImport Complete"); } @@ -32,6 +37,7 @@ public override void OnBeforeImportRoot() public override void OnAfterImportRoot(GLTFRoot gltfRoot) { Util.Log($"InteractivityImportContext::OnAfterImportRoot Complete: {gltfRoot.ToString()}"); + _pointerResolver.CreateScenePointers(gltfRoot); } public override void OnBeforeImportScene(GLTFScene scene) @@ -42,11 +48,25 @@ public override void OnBeforeImportScene(GLTFScene scene) public override void OnAfterImportNode(GLTF.Schema.Node node, int nodeIndex, GameObject nodeObject) { Util.Log($"InteractivityImportContext::OnAfterImportNode Complete: {node.ToString()}"); + _pointerResolver.RegisterNode(node, nodeIndex, nodeObject); } - public override void OnAfterImportMaterial(GLTFMaterial material, int materialIndex, Material materialObject) + public override void OnAfterImportMesh(GLTFMesh mesh, int meshIndex, Mesh meshObject) + { + Util.Log($"InteractivityImportContext::OnAfterImportMesh Complete: {mesh.ToString()}"); + _pointerResolver.RegisterMesh(mesh, meshIndex, meshObject); + } + + public override void OnAfterImportMaterialWithVertexColors(GLTFMaterial material, int materialIndex, Material materialObject) { Util.Log($"InteractivityImportContext::OnAfterImportMaterial Complete: {material.ToString()}"); + _pointerResolver.RegisterMaterial(material, materialIndex, materialObject); + } + + public override void OnAfterImportCamera(GLTFCamera camera, int cameraIndex, Camera cameraObject) + { + Util.Log($"InteractivityImportContext::OnAfterImportCamera Complete: {camera.ToString()}"); + _pointerResolver.RegisterCamera(camera, cameraIndex, cameraObject); } public override void OnAfterImportTexture(GLTFTexture texture, int textureIndex, Texture textureObject) @@ -59,9 +79,57 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj Util.Log($"InteractivityImportContext::OnAfterImportScene Complete: {scene.Extensions}"); } - public override void OnAfterImport() + public override void OnLoadComplete(GameObject sceneObject) { Util.Log($"InteractivityImportContext::OnAfterImport Complete"); + + var extensions = _context.SceneImporter.Root?.Extensions; + + if (extensions == null) + { + Util.Log("Extensions are null."); + return; + } + + if (!extensions.TryGetValue(InteractivityGraphExtension.EXTENSION_NAME, out IExtension extensionValue)) + { + Util.Log("Extensions does not contain interactivity."); + return; + } + + if (extensionValue is not InteractivityGraphExtension interactivityGraph) + { + Util.Log("Extensions does not contain a graph."); + return; + } + + try + { + _pointerResolver.CreatePointers(); + + var defaultGraphIndex = interactivityGraph.extensionData.defaultGraphIndex; + // Can be used to inject a graph created from code in a hacky way for testing. + //interactivityGraph.extensionData.graphs[defaultGraphIndex] = TestGraph.CreateTestGraph(); + var defaultGraph = interactivityGraph.extensionData.graphs[defaultGraphIndex]; + var eng = new BehaviourEngine(defaultGraph, _pointerResolver); + + AnimationWrapper animationWrapper = null; + var animationComponents = sceneObject.GetComponents(); + if (animationComponents != null && animationComponents.Length > 0) + { + animationWrapper = sceneObject.AddComponent(); + eng.SetAnimationWrapper(animationWrapper, animationComponents[0]); + } + + var eventWrapper = sceneObject.AddComponent(); + + eventWrapper.SetData(eng, interactivityGraph.extensionData); + } + catch (Exception e) + { + Debug.LogException(e); + return; + } } } diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs index a99dff11e..1bde889e8 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs @@ -6,14 +6,14 @@ namespace UnityGLTF.Interactivity.Playback public class InteractivityImportPlugin : GLTFImportPlugin { public override string DisplayName => "KHR_interactivity_Importer"; - public override string Description => "Imports KHR compliant interactivity graphs"; + public override string Description => "Imports KHR compliant interactivity graphs for runtime playback."; private InteractivityImportContext _context; public override GLTFImportPluginContext CreateInstance(GLTFImportContext context) { - _context = new InteractivityImportContext(this); GLTFProperty.RegisterExtension(new InteractivityGraphFactory()); + _context = new InteractivityImportContext(this, context); return _context; } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs index 2b1fe9e9c..cb8d4e807 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs @@ -17,8 +17,9 @@ public struct CameraPointers public Pointer zFar; public Pointer zNear; - public CameraPointers(Camera cam) + public CameraPointers(in CameraData data) { + var cam = data.unityCamera; // Unity does not allow you to set the width of the orthographic window directly. // cam.orthographicSize is the YMag and the width is then that value multiplied by your aspect ratio. orthographicXMag = new Pointer() diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs index 034bbe84f..cf912aff2 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs @@ -46,8 +46,9 @@ public struct MaterialPointers public Material material { get; private set; } - public MaterialPointers(Material mat) + public MaterialPointers(in MaterialData data) { + var mat = data.unityMaterial; material = mat; alphaCutoff = PointerHelpers.CreateFloatPointer(mat, alphaCutoffHash); diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs index 107e54b03..1aacef480 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs @@ -12,8 +12,9 @@ public struct MeshPointers public ReadOnlyPointer weightsLength; public Pointer[] weights; - public MeshPointers(GLTFMesh mesh) + public MeshPointers(in MeshData data) { + var mesh = data.mesh; if(mesh.Weights == null || mesh.Weights.Count == 0) { weightsLength = new ReadOnlyPointer(() => 0); diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs index 74600ac54..2710842d6 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs @@ -20,8 +20,10 @@ public struct NodePointers public Pointer[] weights; public GameObject gameObject; - public NodePointers(GameObject go, GLTF.Schema.Node schema) + public NodePointers(in NodeData data) { + var go = data.unityObject; + var schema = data.node; gameObject = go; // Unity coordinate system differs from the GLTF one. diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs index 1764e520d..e497055d2 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs @@ -1,3 +1,4 @@ +using GLTF.Schema; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -7,6 +8,62 @@ namespace UnityGLTF.Interactivity.Playback { + public struct MeshData + { + public GLTF.Schema.GLTFMesh mesh; + public int meshIndex; + public Mesh unityMesh; + + public MeshData(GLTFMesh mesh, int meshIndex, Mesh unityMesh) + { + this.mesh = mesh; + this.meshIndex = meshIndex; + this.unityMesh = unityMesh; + } + } + + public struct MaterialData + { + public GLTF.Schema.GLTFMaterial material; + public int materialIndex; + public Material unityMaterial; + + public MaterialData(GLTFMaterial material, int materialIndex, Material unityMaterial) + { + this.material = material; + this.materialIndex = materialIndex; + this.unityMaterial = unityMaterial; + } + } + + public struct CameraData + { + public GLTF.Schema.GLTFCamera camera; + public int cameraIndex; + public Camera unityCamera; + + public CameraData(GLTFCamera camera, int cameraIndex, Camera unityCamera) + { + this.camera = camera; + this.cameraIndex = cameraIndex; + this.unityCamera = unityCamera; + } + } + + public struct NodeData + { + public GLTF.Schema.Node node; + public int nodeIndex; + public GameObject unityObject; + + public NodeData(GLTF.Schema.Node node, int nodeIndex, GameObject unityObject) + { + this.node = node; + this.nodeIndex = nodeIndex; + this.unityObject = unityObject; + } + } + public class PointerResolver { private readonly List _nodePointers = new(); @@ -14,29 +71,65 @@ public class PointerResolver private readonly List _cameraPointers = new(); private readonly List _animationPointers = new(); private readonly List _meshPointers = new(); - private readonly ScenePointers _scenePointers; + private ScenePointers _scenePointers; private readonly ActiveCameraPointers _activeCameraPointers = ActiveCameraPointers.CreatePointers(); + private readonly List _meshes = new(); + private readonly List _materials = new(); + private readonly List _cameras = new(); + private readonly List _nodes = new(); + public ReadOnlyCollection nodePointers { get; private set; } - public PointerResolver(GLTFSceneImporter importer) + public void RegisterMesh(GLTF.Schema.GLTFMesh mesh, int meshIndex, Mesh unityMesh) { - RegisterNodes(importer); - RegisterMaterials(importer); - RegisterMeshes(importer); + _meshes.Add(new MeshData(mesh, meshIndex, unityMesh)); + } - _scenePointers = new ScenePointers(importer); + public void RegisterMaterial(GLTFMaterial material, int materialIndex, Material unityMaterial) + { + _materials.Add(new MaterialData(material, materialIndex, unityMaterial)); + } + + public void RegisterCamera(GLTFCamera camera, int cameraIndex, Camera unityCamera) + { + _cameras.Add(new CameraData(camera, cameraIndex, unityCamera)); + } + + public void RegisterNode(GLTF.Schema.Node node, int nodeIndex, GameObject unityObject) + { + _nodes.Add(new NodeData(node, nodeIndex, unityObject)); + } + + public void CreateScenePointers(GLTF.Schema.GLTFRoot root) + { + _scenePointers = new ScenePointers(root); + } + + public void CreatePointers() + { + _meshes.Sort((a, b) => a.meshIndex.CompareTo(b.meshIndex)); + _materials.Sort((a, b) => a.materialIndex.CompareTo(b.materialIndex)); + _cameras.Sort((a, b) => a.cameraIndex.CompareTo(b.cameraIndex)); + _nodes.Sort((a, b) => a.nodeIndex.CompareTo(b.nodeIndex)); + + CreateMeshPointers(); + CreateNodePointers(); + CreateCameraPointers(); + CreateMaterialPointers(); } - private void RegisterMeshes(GLTFSceneImporter importer) + private void CreateMeshPointers() { - for (int i = 0; i < importer.Root.Meshes.Count; i++) + for (int i = 0; i < _meshes.Count; i++) { - _meshPointers.Add(new MeshPointers(importer.Root.Meshes[i])); + _meshPointers.Add(new MeshPointers(_meshes[i])); } + + _meshes.Clear(); } - public void RegisterAnimations(AnimationWrapper wrapper) + public void CreateAnimationPointers(AnimationWrapper wrapper) { for (int i = 0; i < wrapper.animationComponent.GetClipCount(); i++) { @@ -44,33 +137,37 @@ public void RegisterAnimations(AnimationWrapper wrapper) } } - private void RegisterNodes(GLTFSceneImporter importer) + private void CreateNodePointers() { - var nodeSchemas = importer.Root.Nodes; - var nodeGameObjects = importer.NodeCache; - - for (int i = 0; i < nodeGameObjects.Length; i++) + for (int i = 0; i < _nodes.Count; i++) { - Util.Log($"Registered Node Pointer {i}", nodeGameObjects[i]); - _nodePointers.Add(new NodePointers(nodeGameObjects[i], nodeSchemas[i])); - - if (nodeGameObjects[i].TryGetComponent(out Camera cam)) - { - Util.Log($"Registered Camera", nodeGameObjects[i]); - _cameraPointers.Add(new CameraPointers(cam)); - } + Util.Log($"Registered Node Pointer {_nodes[i].nodeIndex}", _nodes[i].unityObject); + _nodePointers.Add(new NodePointers(_nodes[i])); } nodePointers = new(_nodePointers); + _nodes.Clear(); + } + + private void CreateCameraPointers() + { + for (int i = 0; i < _cameras.Count; i++) + { + Util.Log($"Registered Camera {_cameras[i].cameraIndex}", _cameras[i].unityCamera.gameObject); + _cameraPointers.Add(new CameraPointers(_cameras[i])); + } + + _cameras.Clear(); } - private void RegisterMaterials(GLTFSceneImporter importer) + private void CreateMaterialPointers() { - var materials = importer.MaterialCache; - for (int i = 0; i < materials.Length; i++) + for (int i = 0; i < _materials.Count; i++) { - _materialPointers.Add(new MaterialPointers(materials[i].UnityMaterialWithVertexColor)); + _materialPointers.Add(new MaterialPointers(_materials[i])); } + + _materials.Clear(); } public bool TryGetPointersOf(GameObject go, out NodePointers pointers) diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs index c4e2bafc6..fb6df6fa5 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs @@ -11,14 +11,14 @@ public struct ScenePointers public ReadOnlyPointer nodesLength; public ReadOnlyPointer scenesLength; - public ScenePointers(GLTFSceneImporter importer) + public ScenePointers(GLTF.Schema.GLTFRoot root) { - animationsLength = new ReadOnlyPointer(() => importer.Root.Animations.Count); - camerasLength = new ReadOnlyPointer(() => importer.Root.Cameras.Count); - materialsLength = new ReadOnlyPointer(() => importer.Root.Materials.Count); - meshesLength = new ReadOnlyPointer(() => importer.Root.Meshes.Count); - nodesLength = new ReadOnlyPointer(() => importer.Root.Nodes.Count); - scenesLength = new ReadOnlyPointer(() => importer.Root.Scenes.Count); + animationsLength = new ReadOnlyPointer(() => root.Animations.Count); + camerasLength = new ReadOnlyPointer(() => root.Cameras.Count); + materialsLength = new ReadOnlyPointer(() => root.Materials.Count); + meshesLength = new ReadOnlyPointer(() => root.Meshes.Count); + nodesLength = new ReadOnlyPointer(() => root.Nodes.Count); + scenesLength = new ReadOnlyPointer(() => root.Scenes.Count); } } } \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs b/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs index 54e86b8b5..fcf9deece 100644 --- a/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs +++ b/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using GLTF.Schema; using UnityEngine; @@ -49,6 +50,18 @@ public virtual void OnAfterImportMaterial(GLTFMaterial material, int materialInd { } + public virtual void OnAfterImportMaterialWithVertexColors(GLTFMaterial material, int materialIndex, Material materialObject) + { + } + + public virtual void OnAfterImportMesh(GLTFMesh mesh, int meshIndex, Mesh meshObject) + { + } + + public virtual void OnAfterImportCamera(GLTFCamera camera, int cameraIndex, Camera cameraObject) + { + } + public virtual void OnAfterImportTexture(GLTFTexture texture, int textureIndex, Texture textureObject) { } @@ -61,5 +74,9 @@ public virtual void OnAfterImport() { } - } + + public virtual void OnLoadComplete(GameObject sceneObject) + { + } + } } diff --git a/Runtime/Scripts/SceneImporter/ImporterCameras.cs b/Runtime/Scripts/SceneImporter/ImporterCameras.cs index 9884c1e99..2a7fecb09 100644 --- a/Runtime/Scripts/SceneImporter/ImporterCameras.cs +++ b/Runtime/Scripts/SceneImporter/ImporterCameras.cs @@ -40,6 +40,12 @@ private bool ConstructCamera(GameObject nodeObj, Node node) unityCamera.enabled = false; nodeObj.transform.localRotation *= SchemaExtensions.InvertDirection; + + foreach (var plugin in Context.Plugins) + { + plugin.OnAfterImportCamera(camera, node.Camera.Id, unityCamera); + } + return true; } } diff --git a/Runtime/Scripts/SceneImporter/ImporterMaterials.cs b/Runtime/Scripts/SceneImporter/ImporterMaterials.cs index e7f7a88bd..80c66166b 100644 --- a/Runtime/Scripts/SceneImporter/ImporterMaterials.cs +++ b/Runtime/Scripts/SceneImporter/ImporterMaterials.cs @@ -828,6 +828,8 @@ void SetTransformKeyword() foreach (var plugin in Context.Plugins) { plugin.OnAfterImportMaterial(def, materialIndex, mapper.Material); + // TODO: Make a breaking change by adding the vert color material as an arg to OnAfterImportMaterial? + plugin.OnAfterImportMaterialWithVertexColors(def, materialIndex, vertColorMapper.Material); } } diff --git a/Runtime/Scripts/SceneImporter/ImporterMeshes.cs b/Runtime/Scripts/SceneImporter/ImporterMeshes.cs index fbd040f24..d693a1b32 100644 --- a/Runtime/Scripts/SceneImporter/ImporterMeshes.cs +++ b/Runtime/Scripts/SceneImporter/ImporterMeshes.cs @@ -124,7 +124,12 @@ protected virtual async Task ConstructMesh(GLTFMesh mesh, int meshIndex, Cancell if (unityData.Vertices != null) Statistics.VertexCount += unityData.Vertices.Length; - await ConstructUnityMesh(unityData, meshIndex, mesh.Name); + var unityMesh = await ConstructUnityMesh(unityData, meshIndex, mesh.Name); + + foreach (var plugin in Context.Plugins) + { + plugin.OnAfterImportMesh(mesh, meshIndex, unityMesh); + } } private async Task CreateMeshMaterials(GLTFMesh mesh) @@ -670,10 +675,11 @@ private UnityMeshData CreateUnityMeshData(GLTFMesh gltfMesh, int meshIndex, bool /// /// /// - protected async Task ConstructUnityMesh(UnityMeshData unityMeshData, int meshIndex, string meshName) + protected async Task ConstructUnityMesh(UnityMeshData unityMeshData, int meshIndex, string meshName) { - if (_assetCache.MeshCache[meshIndex].LoadedMesh != null) - return; + var cache = _assetCache.MeshCache[meshIndex]; + if (cache.LoadedMesh != null) + return cache.LoadedMesh; await YieldOnTimeoutAndThrowOnLowMemory(); Mesh mesh = new Mesh @@ -729,6 +735,8 @@ protected async Task ConstructUnityMesh(UnityMeshData unityMeshData, int meshInd // Free up some memory unityMeshData.Clear(); + + return mesh; } private void AddBlendShapesToMesh(UnityMeshData unityMeshData, int meshIndex, Mesh mesh) diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs index 79da5078f..7e3010b90 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -381,8 +381,12 @@ private static void CreateSingleValueTestSubGraph(Graph g, Node outNode, string protected static BehaviourEngine CreateBehaviourEngineForGraph(Graph g, Action> onEventFired, GLTFSceneImporter importer, bool startPlayback) { - BehaviourEngine eng = new BehaviourEngine(g, importer); + // Because we are adding interactivity graphs to non-interactive glbs in the tests for animations/pointers we need to build the pointer resolver manually. + var pointerResolver = CreatePointerResolver(importer); + BehaviourEngine eng = new BehaviourEngine(g, pointerResolver); + + // Animation wrapper/pointers need to be manually built as well, usually happens in the InteractivityImporterContext. AddAnimationSupportIfApplicable(eng, importer); if (onEventFired != null) @@ -394,6 +398,47 @@ protected static BehaviourEngine CreateBehaviourEngineForGraph(Graph g, Action Date: Wed, 14 May 2025 06:10:02 -0400 Subject: [PATCH 04/28] Fix for debug log node getting eaten by the gitignore due to being in a folder named Debug. --- .../Playback/Data/Helpers/Constants.cs | 2 +- .../Nodes/{Debug.meta => Debugging.meta} | 2 +- .../Playback/Nodes/Debugging/Log.cs | 58 +++++++++++++++++++ .../Playback/Nodes/Debugging/Log.cs.meta | 11 ++++ 4 files changed, 71 insertions(+), 2 deletions(-) rename Runtime/Scripts/Interactivity/Playback/Nodes/{Debug.meta => Debugging.meta} (77%) create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs.meta diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs index cb19ef978..36c48455d 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs @@ -38,7 +38,7 @@ public static class ConstStrings public const string TIME_SINCE_LAST_TICK = "timeSinceLastTick"; public const string EXPECTED = "expected"; public const string ACTUAL = "actual"; - + public const string SEVERITY = "severity"; public const string MESSAGE = "message"; public const string NODE_INDEX = "nodeIndex"; public const string DEFAULT = "default"; diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Debug.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging.meta similarity index 77% rename from Runtime/Scripts/Interactivity/Playback/Nodes/Debug.meta rename to Runtime/Scripts/Interactivity/Playback/Nodes/Debugging.meta index 8b882cb48..9db9bfeee 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Debug.meta +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0def57d03d35eff4ca98cbef090f6ceb +guid: d8a02d5d9c55bba4db4961d43aed234b folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs new file mode 100644 index 000000000..17c5cdbdf --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs @@ -0,0 +1,58 @@ +using System.Text.RegularExpressions; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class DebugLog : BehaviourEngineNode + { + private readonly int _severity; + private readonly string _message; + private static readonly Regex _variableRegex = new("{(.*?)}"); + + public DebugLog(BehaviourEngine engine, Node node) : base(engine, node) + { + if (!TryGetConfig(ConstStrings.MESSAGE, out _message)) + _message = ""; + + if (!TryGetConfig(ConstStrings.SEVERITY, out _severity)) + _severity = 0; + } + + protected override void Execute(string socket, ValidationResult validationResult) + { + var formatted = FormatString(_message); + + switch (_severity) + { + case 0: + Debug.LogWarning(formatted); + break; + + case 1: + Debug.LogError(formatted); + break; + + default: + Debug.Log(formatted); + break; + } + + TryExecuteFlow(ConstStrings.OUT); + } + + private string FormatString(string str) + { + var matches = _variableRegex.Matches(str); + + foreach (Match match in matches) + { + if (!TryEvaluateValue(match.Groups[1].Value, out IProperty value)) + continue; + + str = str.Replace(match.Value, value.ToString()); + } + + return str; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs.meta new file mode 100644 index 000000000..4693f1819 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Debugging/Log.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afe2129a82866334bba1c17fd5b0062c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 1b7c39279b504a76deada4cb38d7b64451da6ffc Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 14 May 2025 06:13:59 -0400 Subject: [PATCH 05/28] Changed plugin names. --- .../Playback/Context/Export/InteractivityExportPlugin.cs | 4 ++-- .../Playback/Context/Import/InteractivityImportPlugin.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs index c8debd263..5391e177a 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs @@ -4,8 +4,8 @@ namespace UnityGLTF.Interactivity.Playback { public class InteractivityExportPlugin : GLTFExportPlugin { - public override string DisplayName => "KHR_interactivity_Exporter"; - public override string Description => "Exports KHR compliant interactivity graphs"; + public override string DisplayName => "KHR_interactivity Playback Exporter"; + public override string Description => "Allows the export of KHR compliant interactivity graphs at runtime."; private InteractivityExportContext _context; public readonly KHR_interactivity extensionData; diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs index 1bde889e8..73e3c26af 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs @@ -5,7 +5,7 @@ namespace UnityGLTF.Interactivity.Playback { public class InteractivityImportPlugin : GLTFImportPlugin { - public override string DisplayName => "KHR_interactivity_Importer"; + public override string DisplayName => "KHR_interactivity Playback Importer"; public override string Description => "Imports KHR compliant interactivity graphs for runtime playback."; private InteractivityImportContext _context; From b4c2c518cfc5b8efa7834a972a3ecdb413cb4171 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 14 May 2025 07:56:47 -0400 Subject: [PATCH 06/28] Removed unnecessary OnLoadComplete callback that was added. --- Runtime/Scripts/GLTFSceneImporter.cs | 5 ----- .../Playback/Context/Import/InteractivityImportContext.cs | 6 ------ Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs | 4 ---- 3 files changed, 15 deletions(-) diff --git a/Runtime/Scripts/GLTFSceneImporter.cs b/Runtime/Scripts/GLTFSceneImporter.cs index 6d8f1ae2a..573972959 100644 --- a/Runtime/Scripts/GLTFSceneImporter.cs +++ b/Runtime/Scripts/GLTFSceneImporter.cs @@ -520,11 +520,6 @@ public void Dispose() if (this.progress != null) await Task.Yield(); - foreach (var plugin in Context.Plugins) - { - plugin.OnLoadComplete(LastLoadedScene); - } - onLoadComplete?.Invoke(LastLoadedScene, null); } diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs index 5f06680ce..42e5a861b 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -77,11 +77,6 @@ public override void OnAfterImportTexture(GLTFTexture texture, int textureIndex, public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObject sceneObject) { Util.Log($"InteractivityImportContext::OnAfterImportScene Complete: {scene.Extensions}"); - } - - public override void OnLoadComplete(GameObject sceneObject) - { - Util.Log($"InteractivityImportContext::OnAfterImport Complete"); var extensions = _context.SceneImporter.Root?.Extensions; @@ -132,5 +127,4 @@ public override void OnLoadComplete(GameObject sceneObject) } } } - } \ No newline at end of file diff --git a/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs b/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs index fcf9deece..ebd78a8e5 100644 --- a/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs +++ b/Runtime/Scripts/Plugins/Core/GltfImportPlugin.cs @@ -74,9 +74,5 @@ public virtual void OnAfterImport() { } - - public virtual void OnLoadComplete(GameObject sceneObject) - { - } } } From c457b921b27644730674825489c6535214b3f98c Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Thu, 22 May 2025 11:43:42 -0400 Subject: [PATCH 07/28] Playback now supported for GLTF assets that are dragged into a scene in the editor and run via playmode. Previously only interactive GLTFs imported at runtime could play back an interaction. --- Editor/Scripts/Interactivity/Playback.meta | 8 ++ ...InteractivityAnimationWrapper_Inspector.cs | 18 +++ ...activityAnimationWrapper_Inspector.cs.meta | 2 + .../GLTFInteractivityData_Inspector.cs | 23 ++++ .../GLTFInteractivityData_Inspector.cs.meta | 2 + .../GLTFInteractivityPlayback_Inspector.cs | 18 +++ ...LTFInteractivityPlayback_Inspector.cs.meta | 2 + ...yGLTF.Interactivity.Playback.Editor.asmdef | 19 +++ ....Interactivity.Playback.Editor.asmdef.meta | 7 ++ .../Interactivity/Playback/BehaviourEngine.cs | 8 +- .../Export/InteractivityExportContext.cs | 2 +- .../Import/InteractivityImportContext.cs | 16 ++- .../InteractivityGraphExtension.cs | 4 +- ...s => GLTFInteractivityAnimationWrapper.cs} | 2 +- ...GLTFInteractivityAnimationWrapper.cs.meta} | 0 .../Playback/GLTFInteractivityData.cs | 12 ++ .../Playback/GLTFInteractivityData.cs.meta | 11 ++ ...rapper.cs => GLTFInteractivityPlayback.cs} | 32 ++++- ...meta => GLTFInteractivityPlayback.cs.meta} | 0 .../Playback/Pointers/AnimationPointers.cs | 2 +- .../Playback/Pointers/MeshPointers.cs | 41 ++++-- .../Playback/Pointers/NodePointers.cs | 79 +++--------- .../Playback/Pointers/PointerData.cs | 83 +++++++++++++ .../Playback/Pointers/PointerData.cs.meta | 11 ++ .../Playback/Pointers/PointerResolver.cs | 117 +++++++----------- .../Playback/Pointers/ScenePointers.cs | 14 +-- .../Nodes/Common/NodeTestHelpers.cs | 4 +- 27 files changed, 373 insertions(+), 164 deletions(-) create mode 100644 Editor/Scripts/Interactivity/Playback.meta create mode 100644 Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs create mode 100644 Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs.meta create mode 100644 Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs create mode 100644 Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs.meta create mode 100644 Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs create mode 100644 Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs.meta create mode 100644 Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef create mode 100644 Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef.meta rename Runtime/Scripts/Interactivity/Playback/{AnimationWrapper.cs => GLTFInteractivityAnimationWrapper.cs} (98%) rename Runtime/Scripts/Interactivity/Playback/{AnimationWrapper.cs.meta => GLTFInteractivityAnimationWrapper.cs.meta} (100%) create mode 100644 Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs.meta rename Runtime/Scripts/Interactivity/Playback/{EventWrapper.cs => GLTFInteractivityPlayback.cs} (78%) rename Runtime/Scripts/Interactivity/Playback/{EventWrapper.cs.meta => GLTFInteractivityPlayback.cs.meta} (100%) create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs.meta diff --git a/Editor/Scripts/Interactivity/Playback.meta b/Editor/Scripts/Interactivity/Playback.meta new file mode 100644 index 000000000..38477b02d --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7e657261bf9908349b1ce8caf80a1928 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs new file mode 100644 index 000000000..7a60a1cee --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs @@ -0,0 +1,18 @@ +using UnityEngine; + +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace UnityGLTF.Interactivity.Playback +{ + [CustomEditor(typeof(GLTFInteractivityAnimationWrapper))] + public class GLTFInteractivityAnimationWrapper_Inspector : Editor + { + public override void OnInspectorGUI() + { + EditorGUILayout.LabelField("Required to support animations during interactivity playback."); + EditorGUILayout.LabelField("Only works when this asset is imported with Legacy animation mode."); + } + } +} \ No newline at end of file diff --git a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs.meta b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs.meta new file mode 100644 index 000000000..b2408819a --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper_Inspector.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2c721ed6b4208fd48a4096c479584663 diff --git a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs new file mode 100644 index 000000000..7794acce7 --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs @@ -0,0 +1,23 @@ +using UnityEngine; + +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace UnityGLTF.Interactivity.Playback +{ + [CustomEditor(typeof(GLTFInteractivityData))] + public class GLTFInteractivityData_Inspector : Editor + { + public override void OnInspectorGUI() + { + EditorGUILayout.LabelField("Contains serialized data representing this GLTF's interactivity graph."); + EditorGUILayout.Space(); + var data = (GLTFInteractivityData)target; + data.showData = EditorGUILayout.Toggle("Show Data", data.showData); + + if (data.showData) + base.OnInspectorGUI(); + } + } +} \ No newline at end of file diff --git a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs.meta b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs.meta new file mode 100644 index 000000000..caed2c3ce --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityData_Inspector.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b0250a7f90a8d2d41bfe66af200a7542 \ No newline at end of file diff --git a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs new file mode 100644 index 000000000..f74b627c6 --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs @@ -0,0 +1,18 @@ +using UnityEngine; + +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace UnityGLTF.Interactivity.Playback +{ + [CustomEditor(typeof(GLTFInteractivityPlayback))] + public class GLTFInteractivityPlayback_Inspector : Editor + { + public override void OnInspectorGUI() + { + EditorGUILayout.LabelField("Handles all Unity event hooks for interactive GLTF playback."); + EditorGUILayout.LabelField("Make sure colliders are enabled in the importer for hover/selection events."); + } + } +} \ No newline at end of file diff --git a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs.meta b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs.meta new file mode 100644 index 000000000..829d247a7 --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1caf26521fdc5254a85463080ce8a412 diff --git a/Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef b/Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef new file mode 100644 index 000000000..9b58fea99 --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef @@ -0,0 +1,19 @@ +{ + "name": "UnityGLTF.Interactivity.Playback.Editor", + "rootNamespace": "", + "references": [ + "GUID:044607fd7a2ec4483a9d4a82c8ab78a7", + "GUID:b5e009300cbc0584fa5c0fa797ef4c5f" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef.meta b/Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef.meta new file mode 100644 index 000000000..a808c30b9 --- /dev/null +++ b/Editor/Scripts/Interactivity/Playback/UnityGLTF.Interactivity.Playback.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f3a18a69c208bb942b8df1dfdaa93f08 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs index c175bd352..08cd41008 100644 --- a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs @@ -7,9 +7,9 @@ namespace UnityGLTF.Interactivity.Playback { public class BehaviourEngine { - public readonly Graph graph; + public Graph graph { get; private set; } public readonly Dictionary engineNodes = new(); - public AnimationWrapper animationWrapper { get; private set; } + public GLTFInteractivityAnimationWrapper animationWrapper { get; private set; } public readonly PointerInterpolationManager pointerInterpolationManager = new(); public readonly VariableInterpolationManager variableInterpolationManager = new(); public readonly NodeDelayManager nodeDelayManager = new(); @@ -23,7 +23,7 @@ public class BehaviourEngine public event Action> onCustomEventFired; public event Action onFlowTriggered; - public readonly PointerResolver pointerResolver; + public PointerResolver pointerResolver { get; private set; } public BehaviourEngine(Graph graph, PointerResolver pointerResolver) { @@ -114,7 +114,7 @@ public bool TryGetPointer(string pointerString, BehaviourEngineNode engineNode, } } - public void SetAnimationWrapper(AnimationWrapper wrapper, Animation animation) + public void SetAnimationWrapper(GLTFInteractivityAnimationWrapper wrapper, Animation animation) { animationWrapper = wrapper; wrapper.SetData(this, animation); diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs index 94df388c6..d25e5efad 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs @@ -29,7 +29,7 @@ public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfR Util.Log($"InteractivityExportContext::AfterSceneExport "); if (exporter.RootTransforms == null) return; - EventWrapper wrapper = null; + GLTFInteractivityPlayback wrapper = null; Transform t; // This assumes that EventWrapper exists on one of the root transforms which I think must be true due to how we import. diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs index 42e5a861b..4c63304f4 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -37,7 +37,6 @@ public override void OnBeforeImportRoot() public override void OnAfterImportRoot(GLTFRoot gltfRoot) { Util.Log($"InteractivityImportContext::OnAfterImportRoot Complete: {gltfRoot.ToString()}"); - _pointerResolver.CreateScenePointers(gltfRoot); } public override void OnBeforeImportScene(GLTFScene scene) @@ -100,6 +99,7 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj try { + _pointerResolver.RegisterSceneData(_context.SceneImporter.Root); _pointerResolver.CreatePointers(); var defaultGraphIndex = interactivityGraph.extensionData.defaultGraphIndex; @@ -108,17 +108,25 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj var defaultGraph = interactivityGraph.extensionData.graphs[defaultGraphIndex]; var eng = new BehaviourEngine(defaultGraph, _pointerResolver); - AnimationWrapper animationWrapper = null; + GLTFInteractivityAnimationWrapper animationWrapper = null; var animationComponents = sceneObject.GetComponents(); if (animationComponents != null && animationComponents.Length > 0) { - animationWrapper = sceneObject.AddComponent(); + animationWrapper = sceneObject.AddComponent(); eng.SetAnimationWrapper(animationWrapper, animationComponents[0]); } - var eventWrapper = sceneObject.AddComponent(); + var eventWrapper = sceneObject.AddComponent(); eventWrapper.SetData(eng, interactivityGraph.extensionData); + + if (!Application.isPlaying) + { + var data = sceneObject.AddComponent(); + data.interactivityJson = interactivityGraph.json; + data.animationWrapper = animationWrapper; + data.pointerReferences = _pointerResolver; + } } catch (Exception e) { diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs index f0094eaf6..f6dda6d13 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Serialization/InteractivityGraphExtension.cs @@ -10,6 +10,7 @@ public class InteractivityGraphExtension : IExtension public const string EXTENSION_NAME = "KHR_interactivity"; public KHR_interactivity extensionData { get; private set; } + public string json { get; private set; } private readonly GraphSerializer _serializer = new(); @@ -28,7 +29,8 @@ public void Deserialize(JProperty extensionToken) if (!extensionToken.Name.Equals(EXTENSION_NAME)) return; - extensionData = _serializer.Deserialize(extensionToken.Value.ToString()); + json = extensionToken.Value.ToString(); + extensionData = _serializer.Deserialize(json); } public JProperty Serialize() diff --git a/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper.cs similarity index 98% rename from Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs rename to Runtime/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper.cs index 525a8cb83..7f2a0ab13 100644 --- a/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs +++ b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper.cs @@ -29,7 +29,7 @@ public AnimationData(AnimationState anim) } } - public class AnimationWrapper : MonoBehaviour + public class GLTFInteractivityAnimationWrapper : MonoBehaviour { public Animation animationComponent { get; private set; } diff --git a/Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs.meta b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper.cs.meta similarity index 100% rename from Runtime/Scripts/Interactivity/Playback/AnimationWrapper.cs.meta rename to Runtime/Scripts/Interactivity/Playback/GLTFInteractivityAnimationWrapper.cs.meta diff --git a/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs new file mode 100644 index 000000000..648b4fcbd --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class GLTFInteractivityData : MonoBehaviour + { + public string interactivityJson; + public PointerResolver pointerReferences; + public GLTFInteractivityAnimationWrapper animationWrapper; + [HideInInspector] public bool showData = false; + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs.meta b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs.meta new file mode 100644 index 000000000..1817be280 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bea4f6a14ae27a149b1d90064ac9a96a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs similarity index 78% rename from Runtime/Scripts/Interactivity/Playback/EventWrapper.cs rename to Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs index 76e3b0573..2f8098c93 100644 --- a/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs +++ b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs @@ -1,8 +1,9 @@ +using System; using UnityEngine; namespace UnityGLTF.Interactivity.Playback { - public class EventWrapper : MonoBehaviour + public class GLTFInteractivityPlayback : MonoBehaviour { private const int MAX_RAYCAST_HITS = 32; @@ -23,8 +24,37 @@ public void SetData(BehaviourEngine engine, KHR_interactivity extensionData) this.engine = engine; } + private void Awake() + { + if (!TryGetComponent(out GLTFInteractivityData data)) + return; + + data.pointerReferences.CreatePointers(); + + var serializer = new GraphSerializer(); + var interactivityExtension = serializer.Deserialize(data.interactivityJson); + + var defaultGraphIndex = interactivityExtension.defaultGraphIndex; + var defaultGraph = interactivityExtension.graphs[defaultGraphIndex]; + var eng = new BehaviourEngine(defaultGraph, data.pointerReferences); + + var animationComponents = GetComponents(); + if (animationComponents != null && animationComponents.Length > 0) + { + if(!TryGetComponent(out GLTFInteractivityAnimationWrapper animationWrapper)) + animationWrapper = gameObject.AddComponent(); + + eng.SetAnimationWrapper(animationWrapper, animationComponents[0]); + } + + SetData(eng, interactivityExtension); + } + private void Start() { + if (engine == null) + throw new InvalidOperationException($"No valid BehaviourEngine to play back for {name}!"); + engine.StartPlayback(); } diff --git a/Runtime/Scripts/Interactivity/Playback/EventWrapper.cs.meta b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs.meta similarity index 100% rename from Runtime/Scripts/Interactivity/Playback/EventWrapper.cs.meta rename to Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs.meta diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs index 010261cc2..2fad65304 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs @@ -12,7 +12,7 @@ public struct AnimationPointers public ReadOnlyPointer minTime; public ReadOnlyPointer maxTime; - public AnimationPointers(AnimationWrapper wrapper, int animationIndex) + public AnimationPointers(GLTFInteractivityAnimationWrapper wrapper, int animationIndex) { isPlaying = new ReadOnlyPointer(() => wrapper.IsAnimationPlaying(animationIndex)); playhead = new ReadOnlyPointer(() => wrapper.GetPlayhead(animationIndex)); diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs index 1aacef480..f19fb3eeb 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs @@ -12,28 +12,55 @@ public struct MeshPointers public ReadOnlyPointer weightsLength; public Pointer[] weights; - public MeshPointers(in MeshData data) + public MeshPointers(in MeshData data, IReadOnlyList nodes) { - var mesh = data.mesh; - if(mesh.Weights == null || mesh.Weights.Count == 0) + var skinnedMeshRenderers = new List(); + SkinnedMeshRenderer smr; + + for (int i = 0; i < nodes.Count; i++) + { + smr = nodes[i].skinnedMeshRenderer; + if (smr == null) + continue; + + if (smr.sharedMesh != data.unityMesh) + continue; + + skinnedMeshRenderers.Add(smr); + } + + if(skinnedMeshRenderers.Count <= 0) { weightsLength = new ReadOnlyPointer(() => 0); weights = new Pointer[0]; return; } - weightsLength = new ReadOnlyPointer(() => mesh.Weights.Count); - weights = new Pointer[mesh.Weights.Count]; + smr = skinnedMeshRenderers[0]; + var blendShapeCount = data.unityMesh.blendShapeCount; + weightsLength = new ReadOnlyPointer(() => blendShapeCount); + weights = new Pointer[blendShapeCount]; + + // TODO: Figure out how this should play with node.weight modifications. + // Right now this will overwrite those completely. for (int i = 0; i < weights.Length; i++) { weights[i] = new Pointer() { - setter = (v) => { }, // TODO: Figure this out, Unity does not handle blend shapes like GLTF does so setting it directly on a mesh is difficult. - getter = () => (float)mesh.Weights[i], + setter = (v) => SetAllBlendShapeWeights(i,v), + getter = () => smr.GetBlendShapeWeight(i), evaluator = (a, b, t) => math.lerp(a, b, t) }; } + + void SetAllBlendShapeWeights(int index, float value) + { + for (int i = 0; i < skinnedMeshRenderers.Count; i++) + { + skinnedMeshRenderers[i].SetBlendShapeWeight(index, value); + } + } } public static IPointer ProcessPointer(StringSpanReader reader, BehaviourEngineNode engineNode, List pointers) diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs index 2710842d6..369be2bf7 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs @@ -23,7 +23,6 @@ public struct NodePointers public NodePointers(in NodeData data) { var go = data.unityObject; - var schema = data.node; gameObject = go; // Unity coordinate system differs from the GLTF one. @@ -74,11 +73,27 @@ public NodePointers(in NodeData data) evaluator = null }; - selectability = GetSelectabilityPointers(schema); - hoverability = GetHoverabilityPointers(schema); + var isSelectable = data.isSelectable; - if(go.TryGetComponent(out SkinnedMeshRenderer smr)) + selectability = new Pointer() { + setter = (v) => isSelectable = v, + getter = () => isSelectable, + evaluator = null + }; + + var isHoverable = data.isHoverable; + + hoverability = new Pointer() + { + setter = (v) => isHoverable = v, + getter = () => isHoverable, + evaluator = null + }; + + if (data.skinnedMeshRenderer != null) + { + var smr = data.skinnedMeshRenderer; weightsLength = new ReadOnlyPointer(() => smr.sharedMesh.blendShapeCount); weights = new Pointer[smr.sharedMesh.blendShapeCount]; @@ -99,62 +114,6 @@ public NodePointers(in NodeData data) } } - private static Pointer GetSelectabilityPointers(GLTF.Schema.Node schema) - { - Pointer selectability; - - if (schema.Extensions != null && schema.Extensions.TryGetValue(GLTF.Schema.KHR_node_selectability_Factory.EXTENSION_NAME, out var extension)) - { - var selectabilityExtension = extension as GLTF.Schema.KHR_node_selectability; - - selectability = new Pointer() - { - setter = (v) => selectabilityExtension.selectable = v, - getter = () => selectabilityExtension.selectable, - evaluator = null - }; - } - else - { - selectability = new Pointer() - { - setter = (v) => { }, - getter = () => true, - evaluator = null - }; - } - - return selectability; - } - - private static Pointer GetHoverabilityPointers(GLTF.Schema.Node schema) - { - Pointer hoverability; - - if (schema.Extensions != null && schema.Extensions.TryGetValue(GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME, out var extension)) - { - var hoverabilityExtension = extension as GLTF.Schema.KHR_node_hoverability; - - hoverability = new Pointer() - { - setter = (v) => hoverabilityExtension.hoverable = v, - getter = () => hoverabilityExtension.hoverable, - evaluator = null - }; - } - else - { - hoverability = new Pointer() - { - setter = (v) => { }, - getter = () => true, - evaluator = null - }; - } - - return hoverability; - } - public static IPointer ProcessNodePointer(StringSpanReader reader, BehaviourEngineNode engineNode, List pointers) { reader.AdvanceToNextToken('/'); diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs new file mode 100644 index 000000000..1d5d11b13 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + [Serializable] + public struct MeshData + { + public GLTF.Schema.GLTFMesh mesh; + public int meshIndex; + public Mesh unityMesh; + + public MeshData(GLTF.Schema.GLTFMesh mesh, int meshIndex, Mesh unityMesh) + { + this.mesh = mesh; + this.meshIndex = meshIndex; + this.unityMesh = unityMesh; + } + } + + [Serializable] + public struct MaterialData + { + public GLTF.Schema.GLTFMaterial material; + public int materialIndex; + public Material unityMaterial; + + public MaterialData(GLTF.Schema.GLTFMaterial material, int materialIndex, Material unityMaterial) + { + this.material = material; + this.materialIndex = materialIndex; + this.unityMaterial = unityMaterial; + } + } + + [Serializable] + public struct CameraData + { + public GLTF.Schema.GLTFCamera camera; + public int cameraIndex; + public Camera unityCamera; + + public CameraData(GLTF.Schema.GLTFCamera camera, int cameraIndex, Camera unityCamera) + { + this.camera = camera; + this.cameraIndex = cameraIndex; + this.unityCamera = unityCamera; + } + } + + [Serializable] + public struct NodeData + { + public GLTF.Schema.Node node; + public int nodeIndex; + public GameObject unityObject; + public SkinnedMeshRenderer skinnedMeshRenderer; + public bool isSelectable; + public bool isHoverable; + + public NodeData(GLTF.Schema.Node node, int nodeIndex, GameObject unityObject, SkinnedMeshRenderer skinnedMeshRenderer, bool isSelectable, bool isHoverable) + { + this.node = node; + this.nodeIndex = nodeIndex; + this.unityObject = unityObject; + this.skinnedMeshRenderer = skinnedMeshRenderer; + this.isSelectable = isSelectable; + this.isHoverable = isHoverable; + } + } + + [Serializable] + public struct SceneData + { + public int animationCount; + public int cameraCount; + public int materialCount; + public int meshCount; + public int nodeCount; + public int sceneCount; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs.meta b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs.meta new file mode 100644 index 000000000..7bfc654aa --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b30c646bbb2d6d488f92090fd3e7679 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs index e497055d2..75fe1e0cc 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs @@ -1,4 +1,3 @@ -using GLTF.Schema; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -8,62 +7,7 @@ namespace UnityGLTF.Interactivity.Playback { - public struct MeshData - { - public GLTF.Schema.GLTFMesh mesh; - public int meshIndex; - public Mesh unityMesh; - - public MeshData(GLTFMesh mesh, int meshIndex, Mesh unityMesh) - { - this.mesh = mesh; - this.meshIndex = meshIndex; - this.unityMesh = unityMesh; - } - } - - public struct MaterialData - { - public GLTF.Schema.GLTFMaterial material; - public int materialIndex; - public Material unityMaterial; - - public MaterialData(GLTFMaterial material, int materialIndex, Material unityMaterial) - { - this.material = material; - this.materialIndex = materialIndex; - this.unityMaterial = unityMaterial; - } - } - - public struct CameraData - { - public GLTF.Schema.GLTFCamera camera; - public int cameraIndex; - public Camera unityCamera; - - public CameraData(GLTFCamera camera, int cameraIndex, Camera unityCamera) - { - this.camera = camera; - this.cameraIndex = cameraIndex; - this.unityCamera = unityCamera; - } - } - - public struct NodeData - { - public GLTF.Schema.Node node; - public int nodeIndex; - public GameObject unityObject; - - public NodeData(GLTF.Schema.Node node, int nodeIndex, GameObject unityObject) - { - this.node = node; - this.nodeIndex = nodeIndex; - this.unityObject = unityObject; - } - } - + [Serializable] public class PointerResolver { private readonly List _nodePointers = new(); @@ -74,10 +18,11 @@ public class PointerResolver private ScenePointers _scenePointers; private readonly ActiveCameraPointers _activeCameraPointers = ActiveCameraPointers.CreatePointers(); - private readonly List _meshes = new(); - private readonly List _materials = new(); - private readonly List _cameras = new(); - private readonly List _nodes = new(); + [SerializeField] private List _meshes = new(); + [SerializeField] private List _materials = new(); + [SerializeField] private List _cameras = new(); + [SerializeField] private List _nodes = new(); + [SerializeField] private SceneData _sceneData; public ReadOnlyCollection nodePointers { get; private set; } @@ -86,28 +31,56 @@ public void RegisterMesh(GLTF.Schema.GLTFMesh mesh, int meshIndex, Mesh unityMes _meshes.Add(new MeshData(mesh, meshIndex, unityMesh)); } - public void RegisterMaterial(GLTFMaterial material, int materialIndex, Material unityMaterial) + public void RegisterMaterial(GLTF.Schema.GLTFMaterial material, int materialIndex, Material unityMaterial) { _materials.Add(new MaterialData(material, materialIndex, unityMaterial)); } - public void RegisterCamera(GLTFCamera camera, int cameraIndex, Camera unityCamera) + public void RegisterCamera(GLTF.Schema.GLTFCamera camera, int cameraIndex, Camera unityCamera) { _cameras.Add(new CameraData(camera, cameraIndex, unityCamera)); } public void RegisterNode(GLTF.Schema.Node node, int nodeIndex, GameObject unityObject) { - _nodes.Add(new NodeData(node, nodeIndex, unityObject)); + var selectable = true; + var hoverable = true; + + if (node.Extensions != null) + { + if (node.Extensions.TryGetValue(GLTF.Schema.KHR_node_selectability_Factory.EXTENSION_NAME, out var extension)) + { + var selectabilityExtension = extension as GLTF.Schema.KHR_node_selectability; + selectable = selectabilityExtension.selectable; + } + + if (node.Extensions.TryGetValue(GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME, out extension)) + { + var hoverabilityExtension = extension as GLTF.Schema.KHR_node_hoverability; + hoverable = hoverabilityExtension.hoverable; + } + } + + _nodes.Add(new NodeData(node, nodeIndex, unityObject, unityObject.GetComponent(), selectable, hoverable)); } - public void CreateScenePointers(GLTF.Schema.GLTFRoot root) + public void RegisterSceneData(GLTF.Schema.GLTFRoot root) { - _scenePointers = new ScenePointers(root); + _sceneData = new() + { + animationCount = (root.Animations == null) ? 0 : root.Animations.Count, + cameraCount = (root.Cameras == null) ? 0 : root.Cameras.Count, + materialCount = (root.Materials == null) ? 0 : root.Materials.Count, + meshCount = (root.Meshes == null) ? 0 : root.Meshes.Count, + nodeCount = (root.Nodes == null) ? 0 : root.Nodes.Count, + sceneCount = (root.Scenes == null) ? 0 : root.Scenes.Count + }; } public void CreatePointers() { + Util.Log("Creating all Pointers for GLTF."); + _meshes.Sort((a, b) => a.meshIndex.CompareTo(b.meshIndex)); _materials.Sort((a, b) => a.materialIndex.CompareTo(b.materialIndex)); _cameras.Sort((a, b) => a.cameraIndex.CompareTo(b.cameraIndex)); @@ -117,19 +90,18 @@ public void CreatePointers() CreateNodePointers(); CreateCameraPointers(); CreateMaterialPointers(); + _scenePointers = new(_sceneData); } private void CreateMeshPointers() { for (int i = 0; i < _meshes.Count; i++) { - _meshPointers.Add(new MeshPointers(_meshes[i])); + _meshPointers.Add(new MeshPointers(_meshes[i], _nodes)); } - - _meshes.Clear(); } - public void CreateAnimationPointers(AnimationWrapper wrapper) + public void CreateAnimationPointers(GLTFInteractivityAnimationWrapper wrapper) { for (int i = 0; i < wrapper.animationComponent.GetClipCount(); i++) { @@ -146,7 +118,6 @@ private void CreateNodePointers() } nodePointers = new(_nodePointers); - _nodes.Clear(); } private void CreateCameraPointers() @@ -156,8 +127,6 @@ private void CreateCameraPointers() Util.Log($"Registered Camera {_cameras[i].cameraIndex}", _cameras[i].unityCamera.gameObject); _cameraPointers.Add(new CameraPointers(_cameras[i])); } - - _cameras.Clear(); } private void CreateMaterialPointers() @@ -166,8 +135,6 @@ private void CreateMaterialPointers() { _materialPointers.Add(new MaterialPointers(_materials[i])); } - - _materials.Clear(); } public bool TryGetPointersOf(GameObject go, out NodePointers pointers) diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs index fb6df6fa5..356cf000f 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/ScenePointers.cs @@ -11,14 +11,14 @@ public struct ScenePointers public ReadOnlyPointer nodesLength; public ReadOnlyPointer scenesLength; - public ScenePointers(GLTF.Schema.GLTFRoot root) + public ScenePointers(SceneData data) { - animationsLength = new ReadOnlyPointer(() => root.Animations.Count); - camerasLength = new ReadOnlyPointer(() => root.Cameras.Count); - materialsLength = new ReadOnlyPointer(() => root.Materials.Count); - meshesLength = new ReadOnlyPointer(() => root.Meshes.Count); - nodesLength = new ReadOnlyPointer(() => root.Nodes.Count); - scenesLength = new ReadOnlyPointer(() => root.Scenes.Count); + animationsLength = new ReadOnlyPointer(() => data.animationCount); + camerasLength = new ReadOnlyPointer(() => data.cameraCount); + materialsLength = new ReadOnlyPointer(() => data.materialCount); + meshesLength = new ReadOnlyPointer(() => data.meshCount); + nodesLength = new ReadOnlyPointer(() => data.nodeCount); + scenesLength = new ReadOnlyPointer(() => data.sceneCount); } } } \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs index 7e3010b90..76083a81c 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -405,7 +405,7 @@ private static PointerResolver CreatePointerResolver(GLTFSceneImporter importer) var pointerResolver = new PointerResolver(); - pointerResolver.CreateScenePointers(importer.Root); + pointerResolver.RegisterSceneData(importer.Root); var meshes = importer.MeshCache; var materials = importer.MaterialCache; @@ -444,7 +444,7 @@ private static void AddAnimationSupportIfApplicable(BehaviourEngine eng, GLTFSce if (importer == null || importer.AnimationCache.IsNullOrEmpty()) return; - var animationWrapper = importer.SceneParent.gameObject.AddComponent(); + var animationWrapper = importer.SceneParent.gameObject.AddComponent(); eng.SetAnimationWrapper(animationWrapper, importer.LastLoadedScene.GetComponents()[0]); } From c106c13db085c8d7c2c41e9972dd6da4029654ee Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Thu, 22 May 2025 11:50:07 -0400 Subject: [PATCH 08/28] Fixed issue with tests failing from the new debug/log schema. --- .../Playback/BehaviourEngineNode.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs index 0b31a07b7..26d8590a7 100644 --- a/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; -using Unity.Properties; using UnityEngine; namespace UnityGLTF.Interactivity.Playback @@ -103,17 +101,23 @@ public bool TryEvaluateValue(string valueId, out IProperty value) public bool TryGetConfig(string id, out T value) { - try + if (configuration.TryGetValue(id, out var config)) { - value = ((Property)Helpers.CreateProperty(typeof(T), configuration[id].value)).value; - return true; - } - catch (Exception ex) - { - Debug.LogException(ex); - value = default; - return false; + try + { + value = ((Property)Helpers.CreateProperty(typeof(T), config.value)).value; + return true; + } + catch (Exception e) + { + Debug.LogException(e); + value = default; + return false; + } } + + value = default; + return false; } } } \ No newline at end of file From 22a0b2e3727845ff0673acd1d967b3271544daf5 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Tue, 27 May 2025 09:45:20 -0400 Subject: [PATCH 09/28] Changed onSelect, onHoverIn, and onHoverOut to use the event system. --- .../GLTFInteractivityPlayback_Inspector.cs | 2 +- .../Import/InteractivityImportContext.cs | 12 +- .../Playback/GLTFInteractivityEventWrapper.cs | 43 +++++++ .../GLTFInteractivityEventWrapper.cs.meta | 11 ++ .../Playback/GLTFInteractivityPlayback.cs | 120 ------------------ .../Playback/Nodes/Event/OnHoverIn.cs | 6 +- .../Playback/Nodes/Event/OnHoverOut.cs | 2 +- .../Playback/Nodes/Event/OnSelect.cs | 4 +- 8 files changed, 72 insertions(+), 128 deletions(-) create mode 100644 Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs.meta diff --git a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs index f74b627c6..b879e9bf1 100644 --- a/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs +++ b/Editor/Scripts/Interactivity/Playback/GLTFInteractivityPlayback_Inspector.cs @@ -11,7 +11,7 @@ public class GLTFInteractivityPlayback_Inspector : Editor { public override void OnInspectorGUI() { - EditorGUILayout.LabelField("Handles all Unity event hooks for interactive GLTF playback."); + EditorGUILayout.LabelField("Handles the onStart and onTick events for this interactive GLTF."); EditorGUILayout.LabelField("Make sure colliders are enabled in the importer for hover/selection events."); } } diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs index 4c63304f4..f1b176144 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -116,9 +116,17 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj eng.SetAnimationWrapper(animationWrapper, animationComponents[0]); } - var eventWrapper = sceneObject.AddComponent(); + var playback = sceneObject.AddComponent(); - eventWrapper.SetData(eng, interactivityGraph.extensionData); + playback.SetData(eng, interactivityGraph.extensionData); + + var colliders = sceneObject.GetComponentsInChildren(true); + + for (int i = 0; i < colliders.Length; i++) + { + var wrapper = colliders[i].gameObject.AddComponent(); + wrapper.playback = playback; + } if (!Application.isPlaying) { diff --git a/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs new file mode 100644 index 000000000..0a89d3cfc --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs @@ -0,0 +1,43 @@ +using System; +using UnityEngine; +using UnityEngine.EventSystems; + +namespace UnityGLTF.Interactivity.Playback +{ + public class GLTFInteractivityEventWrapper : MonoBehaviour, IPointerClickHandler, IPointerEnterHandler, IPointerExitHandler + { + [field: SerializeField] public GLTFInteractivityPlayback playback { get; set; } + + public void OnPointerClick(PointerEventData eventData) + { + var args = CreateRayArgs(gameObject, eventData); + playback.engine.Select(args); + } + + public void OnPointerEnter(PointerEventData eventData) + { + var args = CreateRayArgs(gameObject, eventData); + playback.engine.HoverIn(args); + } + + public void OnPointerExit(PointerEventData eventData) + { + var args = CreateRayArgs(gameObject, eventData); + playback.engine.HoverOut(args); + } + + private static RayArgs CreateRayArgs(GameObject go, PointerEventData eventData) + { + var origin = Camera.main.ScreenToWorldPoint(eventData.pointerCurrentRaycast.screenPosition); + var dir = eventData.pointerCurrentRaycast.worldPosition - origin; + + return new RayArgs() + { + ray = new Ray(origin, dir), + result = eventData.pointerCurrentRaycast, + go = go, + controllerIndex = eventData.pointerId + }; + } + } +} diff --git a/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs.meta b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs.meta new file mode 100644 index 000000000..e34aa5213 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityEventWrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85d74b89abcc65d45aa1472823694932 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs index 2f8098c93..9dedd91ab 100644 --- a/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs +++ b/Runtime/Scripts/Interactivity/Playback/GLTFInteractivityPlayback.cs @@ -5,18 +5,9 @@ namespace UnityGLTF.Interactivity.Playback { public class GLTFInteractivityPlayback : MonoBehaviour { - private const int MAX_RAYCAST_HITS = 32; - public KHR_interactivity extensionData { get; private set; } public BehaviourEngine engine { get; private set; } - private static readonly RaycastHit[] _raycastHits = new RaycastHit[MAX_RAYCAST_HITS]; - private static readonly RaycastHit[] _selectableHits = new RaycastHit[MAX_RAYCAST_HITS]; - private static readonly RaycastHit[] _hoverableHits = new RaycastHit[MAX_RAYCAST_HITS]; - - private RayArgs _currentHover; - private bool _isHovering; - // TODO: Make this wrapper accept an array of BehaviourEngine objects so we can switch which graphs are being executed. public void SetData(BehaviourEngine engine, KHR_interactivity extensionData) { @@ -60,118 +51,7 @@ private void Start() private void Update() { - CheckForObjectHoverOrSelect(); engine.Tick(); } - - public void Select(in RayArgs args) - { - engine.Select(args); - } - - private void HoverIn(in RayArgs args) - { - _isHovering = true; - _currentHover = args; - engine.HoverIn(args); - } - - private void HoverOut(in RayArgs args) - { - _isHovering = false; - engine.HoverOut(args); - } - - private void CheckForObjectHoverOrSelect() - { - var ray = Camera.main.ScreenPointToRay(Input.mousePosition); - - var hitCount = Physics.RaycastNonAlloc(ray, _raycastHits); - - if (hitCount <= 0) - { - if (_isHovering) - HoverOut(_currentHover); - - return; - } - - NodePointers pointers; - GameObject go; - - var selectableCount = 0; - RaycastHit closestSelectableHit = default; - float closestSelectableHitDistance = float.MaxValue; - - var hoverableCount = 0; - RaycastHit closestHoverableHit = default; - float closestHoverableHitDistance = float.MaxValue; - - for (int i = 0; i < hitCount; i++) - { - go = _raycastHits[i].transform.gameObject; - // If this is an app with both gltf and non-gltf content things may not have pointers. - if (!engine.pointerResolver.TryGetPointersOf(go, out pointers)) - continue; - - if (pointers.selectability.getter()) - { - _selectableHits[selectableCount++] = _raycastHits[i]; - - if (_raycastHits[i].distance < closestSelectableHitDistance) - { - closestSelectableHit = _raycastHits[i]; - closestSelectableHitDistance = _raycastHits[i].distance; - } - } - - if (pointers.hoverability.getter()) - { - _hoverableHits[hoverableCount++] = _raycastHits[i]; - - if (_raycastHits[i].distance < closestHoverableHitDistance) - { - closestHoverableHit = _raycastHits[i]; - closestHoverableHitDistance = _raycastHits[i].distance; - } - } - } - - // Select - if (Input.GetMouseButtonDown(0) && TryGetValidHit(selectableCount, closestSelectableHit, ray, out var selectedArgs)) - Select(selectedArgs); - - // Hover - if (TryGetValidHit(hoverableCount, closestHoverableHit, ray, out var hoveredArgs)) - { - if (_isHovering) - { - if (_currentHover.hit.transform != closestHoverableHit.transform) - HoverOut(_currentHover); - } - else - HoverIn(hoveredArgs); - } - else if (_isHovering) - HoverOut(_currentHover); - } - - private static bool TryGetValidHit(int count, RaycastHit closestHit, in Ray ray, out RayArgs args) - { - args = default; - - if (count <= 0) - return false; - - // TODO: Controller Index is 0 for now, need to extend this for different use-cases. - args = new RayArgs() - { - controllerIndex = 0, - ray = ray, - hit = closestHit - }; - - return true; - } } } diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs index e4488ac30..94e6fb946 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs @@ -1,12 +1,14 @@ using System; using UnityEngine; +using UnityEngine.EventSystems; namespace UnityGLTF.Interactivity.Playback { public struct RayArgs { public Ray ray; - public RaycastHit hit; + public RaycastResult result; + public GameObject go; public int controllerIndex; } @@ -47,7 +49,7 @@ private void OnHoverIn(RayArgs args) { // TODO: Add support for stopPropagation once we understand what it actually does. // I've read that part of the spec a handful of times and still am not sure. - var t = args.hit.transform; + var t = args.go.transform; var go = t.gameObject; var nodeIndex = engine.pointerResolver.IndexOf(go); diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs index aa59327ef..453bef770 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs @@ -40,7 +40,7 @@ private void OnHoverOut(RayArgs args) { // TODO: Add support for stopPropagation once we understand what it actually does. // I've read that part of the spec a handful of times and still am not sure. - var t = args.hit.transform; + var t = args.go.transform; var go = t.gameObject; var nodeIndex = engine.pointerResolver.IndexOf(go); diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs index fb1f53e17..8fd21d13e 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs @@ -45,7 +45,7 @@ private void OnSelect(RayArgs args) { // TODO: Add support for stopPropagation once we understand what it actually does. // I've read that part of the spec a handful of times and still am not sure. - var t = args.hit.transform; + var t = args.go.transform; var go = t.gameObject; var nodeIndex = engine.pointerResolver.IndexOf(go); @@ -60,7 +60,7 @@ private void OnSelect(RayArgs args) return; _selectedNodeIndex = nodeIndex; - _selectionPoint = args.hit.point; + _selectionPoint = args.result.worldPosition; _selectionRayOrigin = args.ray.origin; _controllerIndex = args.controllerIndex; From 83ff69bbbc9710c73e997f50610629c3f8663eb8 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 28 May 2025 11:16:01 -0400 Subject: [PATCH 10/28] Dragging in an interactive GLTF asset at runtime now works and immediately starts playback. Colliders now get added to any GLTF node object in Unity that if hover/select events are present and the object does not have a collider (if selectable/hoverable extension is present it only adds the collider if it is marked as hoverable/selectable). --- .../Import/InteractivityImportContext.cs | 110 ++++++++++++++---- 1 file changed, 87 insertions(+), 23 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs index f1b176144..63b17b41e 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -1,5 +1,6 @@ using GLTF.Schema; using System; +using System.Collections.Generic; using UnityEngine; using UnityGLTF.Plugins; @@ -10,6 +11,8 @@ public class InteractivityImportContext : GLTFImportPluginContext internal readonly InteractivityImportPlugin settings; private PointerResolver _pointerResolver; private GLTFImportContext _context; + private InteractivityGraphExtension _interactivityGraph; + private bool _hasSelectOrHoverNode; public InteractivityImportContext(InteractivityImportPlugin interactivityLoader, GLTFImportContext context) { @@ -36,6 +39,49 @@ public override void OnBeforeImportRoot() /// public override void OnAfterImportRoot(GLTFRoot gltfRoot) { + var extensions = _context.SceneImporter.Root?.Extensions; + + if (extensions == null) + { + Util.Log("Extensions are null."); + return; + } + + if (!extensions.TryGetValue(InteractivityGraphExtension.EXTENSION_NAME, out IExtension extensionValue)) + { + Util.Log("Extensions does not contain interactivity."); + return; + } + + if (extensionValue is not InteractivityGraphExtension interactivityGraph) + { + Util.Log("Extensions does not contain a graph."); + return; + } + + Util.Log("Extensions contains interactivity."); + + _interactivityGraph = interactivityGraph; + + var graph = interactivityGraph.extensionData.graphs[interactivityGraph.extensionData.defaultGraphIndex]; + + for (int i = 0; i < graph.declarations.Count; i++) + { + switch(graph.declarations[i].op) + { + case "event/onSelect": + case "event/onHoverIn": + case "event/onHoverOut": + _hasSelectOrHoverNode = true; + break; + } + } + + if(!_hasSelectOrHoverNode) + return; + + Util.Log("Select or hover node present."); + Util.Log($"InteractivityImportContext::OnAfterImportRoot Complete: {gltfRoot.ToString()}"); } @@ -46,10 +92,45 @@ public override void OnBeforeImportScene(GLTFScene scene) public override void OnAfterImportNode(GLTF.Schema.Node node, int nodeIndex, GameObject nodeObject) { + AddColliderIfNecessary(node, nodeIndex, nodeObject); + Util.Log($"InteractivityImportContext::OnAfterImportNode Complete: {node.ToString()}"); _pointerResolver.RegisterNode(node, nodeIndex, nodeObject); } + private void AddColliderIfNecessary(GLTF.Schema.Node node, int nodeIndex, GameObject nodeObject) + { + if (!_hasSelectOrHoverNode || nodeObject.TryGetComponent(out Collider collider)) + return; + + Mesh mesh = null; + + if (nodeObject.TryGetComponent(out MeshFilter mf)) + mesh = mf.sharedMesh; + else if (nodeObject.TryGetComponent(out SkinnedMeshRenderer smr)) + mesh = smr.sharedMesh; + + if (mesh == null) + return; + + var selectable = true; + var hoverable = true; + + if (node.Extensions != null) + { + if (node.Extensions.TryGetValue(GLTF.Schema.KHR_node_selectability_Factory.EXTENSION_NAME, out var selectableExtension)) + selectable = (selectableExtension as GLTF.Schema.KHR_node_selectability).selectable; + + if (node.Extensions.TryGetValue(GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME, out var hoverableExtension)) + hoverable = (hoverableExtension as GLTF.Schema.KHR_node_hoverability).hoverable; + } + + if (!selectable && !hoverable) + return; + + nodeObject.AddComponent(); + } + public override void OnAfterImportMesh(GLTFMesh mesh, int meshIndex, Mesh meshObject) { Util.Log($"InteractivityImportContext::OnAfterImportMesh Complete: {mesh.ToString()}"); @@ -77,35 +158,18 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj { Util.Log($"InteractivityImportContext::OnAfterImportScene Complete: {scene.Extensions}"); - var extensions = _context.SceneImporter.Root?.Extensions; - - if (extensions == null) - { - Util.Log("Extensions are null."); - return; - } - - if (!extensions.TryGetValue(InteractivityGraphExtension.EXTENSION_NAME, out IExtension extensionValue)) - { - Util.Log("Extensions does not contain interactivity."); - return; - } - - if (extensionValue is not InteractivityGraphExtension interactivityGraph) - { - Util.Log("Extensions does not contain a graph."); + if (_interactivityGraph == null) return; - } try { _pointerResolver.RegisterSceneData(_context.SceneImporter.Root); _pointerResolver.CreatePointers(); - var defaultGraphIndex = interactivityGraph.extensionData.defaultGraphIndex; + var defaultGraphIndex = _interactivityGraph.extensionData.defaultGraphIndex; // Can be used to inject a graph created from code in a hacky way for testing. //interactivityGraph.extensionData.graphs[defaultGraphIndex] = TestGraph.CreateTestGraph(); - var defaultGraph = interactivityGraph.extensionData.graphs[defaultGraphIndex]; + var defaultGraph = _interactivityGraph.extensionData.graphs[defaultGraphIndex]; var eng = new BehaviourEngine(defaultGraph, _pointerResolver); GLTFInteractivityAnimationWrapper animationWrapper = null; @@ -118,7 +182,7 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj var playback = sceneObject.AddComponent(); - playback.SetData(eng, interactivityGraph.extensionData); + playback.SetData(eng, _interactivityGraph.extensionData); var colliders = sceneObject.GetComponentsInChildren(true); @@ -128,10 +192,10 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj wrapper.playback = playback; } - if (!Application.isPlaying) + if (_context.AssetContext != null) { var data = sceneObject.AddComponent(); - data.interactivityJson = interactivityGraph.json; + data.interactivityJson = _interactivityGraph.json; data.animationWrapper = animationWrapper; data.pointerReferences = _pointerResolver; } From ec39abb9c9d0a694468d6f9d60febbf6efcac2b8 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 28 May 2025 15:37:44 -0400 Subject: [PATCH 11/28] Added safety for grabbing a number string in a node that goes above 64 --- .../Export/InteractivityExportContext.cs | 4 ++ .../Playback/Data/Helpers/Constants.cs | 10 ++- .../Playback/Nodes/Variable/SetMultiple.cs | 2 +- .../Nodes/Common/NodeTestHelpers.cs | 64 +++++++++---------- .../Nodes/Event/EventNodeTests.cs | 8 +-- .../Nodes/Flow/FlowNodesTests.cs | 4 +- .../Nodes/Flow/MultiGateTests.cs | 8 +-- .../Interactivity/Nodes/Flow/ThrottleTests.cs | 4 +- .../Interactivity/Nodes/Flow/WaitAllTests.cs | 18 +++--- .../Nodes/Math/MathNodesTests.cs | 12 ++-- .../Nodes/Variable/VariableNodeTests.cs | 38 +++++------ 11 files changed, 92 insertions(+), 80 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs index d25e5efad..fd06e70c6 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs @@ -36,6 +36,10 @@ public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfR foreach (var transform in exporter.RootTransforms) { t = transform; + + if (t.TryGetComponent(out wrapper)) + break; + while (t.parent != null) { if (t.parent.TryGetComponent(out wrapper)) diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs index 36c48455d..108533ee0 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs @@ -121,7 +121,15 @@ public static class ConstStrings A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P }; - public static readonly string[] Numbers = new string[] + public static string GetNumberString(int i) + { + if (i > 0 && i < _numbers.Length) + return _numbers[i]; + + return i.ToString(); + } + + private static readonly string[] _numbers = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs index 74191517d..b36533edb 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Variable/SetMultiple.cs @@ -25,7 +25,7 @@ protected override void Execute(string socket, ValidationResult validationResult { index = _variableIndices[i]; - if (!TryEvaluateValue(ConstStrings.Numbers[index], out IProperty value)) + if (!TryEvaluateValue(ConstStrings.GetNumberString(i), out IProperty value)) continue; variable = engine.graph.variables[index]; diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs index 76083a81c..e8b8a7eef 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -269,7 +269,7 @@ private static void GenerateGraphByExpectedValueType(Dictionary)expected.Value).value; for (int i = 0; i < 2; i++) { - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[i], f2Val[i], subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(i), f2Val[i], subGraph); } break; case Property: @@ -277,7 +277,7 @@ private static void GenerateGraphByExpectedValueType(Dictionary)expected.Value).value; for (int i = 0; i < 3; i++) { - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[i], f3Val[i], subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(i), f3Val[i], subGraph); } break; case Property: @@ -285,51 +285,51 @@ private static void GenerateGraphByExpectedValueType(Dictionary)expected.Value).value; for (int i = 0; i < 4; i++) { - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[i], f4Val[i], subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(i), f4Val[i], subGraph); } break; case Property: node = CreateExtractNode(g, "math/extract2x2", opNode, out value, out node, expected); var f2x2Val = ((Property)expected.Value).value; - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[0], f2x2Val.c0.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[1], f2x2Val.c0.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[2], f2x2Val.c1.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[3], f2x2Val.c1.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2x2Val.c0.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2x2Val.c0.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f2x2Val.c1.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f2x2Val.c1.y, subGraph); break; case Property: node = CreateExtractNode(g, "math/extract3x3", opNode, out value, out node, expected); var f3x3Val = ((Property)expected.Value).value; - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[0], f3x3Val.c0.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[1], f3x3Val.c0.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[2], f3x3Val.c0.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[3], f3x3Val.c1.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[4], f3x3Val.c1.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[5], f3x3Val.c1.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[6], f3x3Val.c2.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[7], f3x3Val.c2.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[8], f3x3Val.c2.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3x3Val.c0.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3x3Val.c0.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3x3Val.c0.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f3x3Val.c1.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f3x3Val.c1.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f3x3Val.c1.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f3x3Val.c2.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f3x3Val.c2.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f3x3Val.c2.z, subGraph); break; case Property: node = CreateExtractNode(g, "math/extract4x4", opNode, out value, out node, expected); var f4x4Val = ((Property)expected.Value).value; - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[0], f4x4Val.c0.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[1], f4x4Val.c0.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[2], f4x4Val.c0.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[3], f4x4Val.c0.w, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[4], f4x4Val.c1.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[5], f4x4Val.c1.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[6], f4x4Val.c1.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[7], f4x4Val.c1.w, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[8], f4x4Val.c2.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[9], f4x4Val.c2.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[10], f4x4Val.c2.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[11], f4x4Val.c2.w, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[12], f4x4Val.c3.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[13], f4x4Val.c3.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[14], f4x4Val.c3.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.Numbers[15], f4x4Val.c3.w, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4x4Val.c0.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4x4Val.c0.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4x4Val.c0.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4x4Val.c0.w, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f4x4Val.c1.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f4x4Val.c1.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f4x4Val.c1.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f4x4Val.c1.w, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f4x4Val.c2.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(9), f4x4Val.c2.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(10), f4x4Val.c2.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(11), f4x4Val.c2.w, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(12), f4x4Val.c3.x, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(13), f4x4Val.c3.y, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(14), f4x4Val.c3.z, subGraph); + CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(15), f4x4Val.c3.w, subGraph); break; } diff --git a/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs index 8593ca9e1..043fc67a7 100644 --- a/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs @@ -169,10 +169,10 @@ private static Graph CreateEventReceiveWithValuesTestGraph() fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); - sequence.AddFlow(branch, ConstStrings.Numbers[i]); + sequence.AddFlow(branch, ConstStrings.GetNumberString(i)); } - sequence.AddFlow(complete, ConstStrings.Numbers[values.Count]); + sequence.AddFlow(complete, ConstStrings.GetNumberString(values.Count)); complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); @@ -252,10 +252,10 @@ private static Graph CreateEventReceiveWithNoSendCheckOutputValuesTestGraph() fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); - sequence.AddFlow(branch, ConstStrings.Numbers[i]); + sequence.AddFlow(branch, ConstStrings.GetNumberString(i)); } - sequence.AddFlow(complete, ConstStrings.Numbers[values.Count]); + sequence.AddFlow(complete, ConstStrings.GetNumberString(values.Count)); complete.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs index f23619984..9320ea82d 100644 --- a/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs @@ -210,7 +210,7 @@ private static Graph CreateFlowSequenceGraph(int numOutFlows) branch.AddFlow(complete, ConstStrings.TRUE); } - sequenceNode.AddFlow(branch, ConstStrings.Numbers[i]); + sequenceNode.AddFlow(branch, ConstStrings.GetNumberString(i)); } return g; @@ -320,7 +320,7 @@ private static Graph CreateSwitchGraph() for (int i = 0; i < outputFlowOrder.Length; i++) { var subGraphEntry = CreateSwitchFlowSubGraph(g, outputFlowOrder[i], 1 << i, flowIndexVariableIndex, bitmaskVariableIndex); - switchNode.AddFlow(subGraphEntry, ConstStrings.Numbers[i]); + switchNode.AddFlow(subGraphEntry, ConstStrings.GetNumberString(i)); expected |= 1 << i; } diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs index 4efca8c3e..1e7f24243 100644 --- a/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Flow/MultiGateTests.cs @@ -56,7 +56,7 @@ private static Graph GenerateMultiGateTestGraph(int outputs) for (int i = 0; i < outputs - 1; i++) { var subGraphEntry = CreateMultiGateFlowSubGraph(g, 1 << i, flowIndexVariableIndex, bitmaskVariableIndex); - multiGate.AddFlow(subGraphEntry, ConstStrings.Numbers[i]); + multiGate.AddFlow(subGraphEntry, ConstStrings.GetNumberString(i)); expected |= 1 << i; } @@ -72,7 +72,7 @@ private static Graph GenerateMultiGateTestGraph(int outputs) eq.AddConnectedValue(ConstStrings.A, bitmaskGet); eq.AddValue(ConstStrings.B, expected); - multiGate.AddFlow(branch, ConstStrings.Numbers[outputs - 1]); + multiGate.AddFlow(branch, ConstStrings.GetNumberString(outputs - 1)); var fail = g.CreateNode("event/send"); var completed = g.CreateNode("event/send"); @@ -139,7 +139,7 @@ private static Graph GenerateMultiGateLoopTestGraph(int outputs, int iterations) for (int i = 0; i < outputs; i++) { Node outFlowCounter = CreateVariableIncrementSubgraph(g, outFlowActivationsVariable); - multiGate.AddFlow(outFlowCounter, ConstStrings.Numbers[i]); + multiGate.AddFlow(outFlowCounter, ConstStrings.GetNumberString(i)); var lastIndexBranch = g.CreateNode("flow/branch"); var lastIndexEq = g.CreateNode("math/eq"); @@ -227,7 +227,7 @@ private static Graph GenerateMultiGateResetTestGraph(int outputs, int resetItera for (int i = 0; i < outputs; i++) { Node outFlowCounter = CreateVariableIncrementSubgraph(g, outFlowActivationsVariable); - multiGate.AddFlow(outFlowCounter, ConstStrings.Numbers[i]); + multiGate.AddFlow(outFlowCounter, ConstStrings.GetNumberString(i)); } var completeBranch = g.CreateNode("flow/branch"); diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs index a327adec9..7379d7a48 100644 --- a/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs @@ -274,8 +274,8 @@ private static Graph GenerateThrottleResetTestGraph(float duration, float resetT startDelay.AddFlow(setDelay); setDelay.AddFlow(sequence, ConstStrings.DONE); - sequence.AddFlow(throttle, ConstStrings.Numbers[0], ConstStrings.RESET); - sequence.AddFlow(branch, ConstStrings.Numbers[1]); + sequence.AddFlow(throttle, ConstStrings.GetNumberString(0), ConstStrings.RESET); + sequence.AddFlow(branch, ConstStrings.GetNumberString(1)); branch.AddConnectedValue(ConstStrings.CONDITION, isNaN); isNaN.AddConnectedValue(ConstStrings.A, throttle, ConstStrings.LAST_REMAINING_TIME); diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs index 9352b0284..d9a41113e 100644 --- a/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Flow/WaitAllTests.cs @@ -30,7 +30,7 @@ private static Graph GenerateWaitAllTestGraph(int inputs) for (int i = 0; i < inputs; i++) { starts[i] = g.CreateNode("event/onStart"); - starts[i].AddFlow(waitAll, ConstStrings.OUT, ConstStrings.Numbers[i]); + starts[i].AddFlow(waitAll, ConstStrings.OUT, ConstStrings.GetNumberString(i)); } var completed = g.CreateNode("event/send"); @@ -91,14 +91,14 @@ private static Graph GenerateWaitAllResetTestGraph() branch2.AddFlow(inputs2Log, ConstStrings.FALSE); - sequence.AddFlow(waitAll, ConstStrings.Numbers[0], ConstStrings.Numbers[0]); - sequence.AddFlow(waitAll, ConstStrings.Numbers[1], ConstStrings.Numbers[1]); - sequence.AddFlow(waitAll, ConstStrings.Numbers[2], ConstStrings.Numbers[2]); - sequence.AddFlow(branch1, ConstStrings.Numbers[3]); - sequence.AddFlow(waitAll, ConstStrings.Numbers[4], ConstStrings.RESET); - sequence.AddFlow(waitAll, ConstStrings.Numbers[5], ConstStrings.Numbers[3]); - sequence.AddFlow(waitAll, ConstStrings.Numbers[6], ConstStrings.Numbers[4]); - sequence.AddFlow(branch2, ConstStrings.Numbers[7]); + sequence.AddFlow(waitAll, ConstStrings.GetNumberString(0), ConstStrings.GetNumberString(0)); + sequence.AddFlow(waitAll, ConstStrings.GetNumberString(1), ConstStrings.GetNumberString(1)); + sequence.AddFlow(waitAll, ConstStrings.GetNumberString(2), ConstStrings.GetNumberString(2)); + sequence.AddFlow(branch1, ConstStrings.GetNumberString(3)); + sequence.AddFlow(waitAll, ConstStrings.GetNumberString(4), ConstStrings.RESET); + sequence.AddFlow(waitAll, ConstStrings.GetNumberString(5), ConstStrings.GetNumberString(3)); + sequence.AddFlow(waitAll, ConstStrings.GetNumberString(6), ConstStrings.GetNumberString(4)); + sequence.AddFlow(branch2, ConstStrings.GetNumberString(7)); var completed = g.CreateNode("event/send"); completed.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 7ffb1d86c..7147abbac 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -684,7 +684,7 @@ public void TestExtract2() inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); for (int i = 0; i < 2; i++) { - outputs.Add(ConstStrings.Numbers[i], new Property(v[i])); + outputs.Add(ConstStrings.GetNumberString(i), new Property(v[i])); } QueueTest("math/extract2", "Extract2", "Extract2", "Tests Extract2 operation.", CreateSelfContainedTestGraph("math/extract2", inputs, outputs, ComparisonType.Equals)); @@ -701,7 +701,7 @@ public void TestExtract3() inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); for (int i = 0; i < 3; i++) { - outputs.Add(ConstStrings.Numbers[i], new Property(v[i])); + outputs.Add(ConstStrings.GetNumberString(i), new Property(v[i])); } QueueTest("math/extract3", "Extract3", "Extract3", "Tests Extract3 operation.", CreateSelfContainedTestGraph("math/extract3", inputs, outputs, ComparisonType.Equals)); @@ -717,7 +717,7 @@ public void TestExtract4() inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); for (int i = 0; i < 4; i++) { - outputs.Add(ConstStrings.Numbers[i], new Property(v[i])); + outputs.Add(ConstStrings.GetNumberString(i), new Property(v[i])); } QueueTest("math/extract4", "Extract4", "Extract4", "Tests Extract4 operation.", CreateSelfContainedTestGraph("math/extract4", inputs, outputs, ComparisonType.Equals)); @@ -734,7 +734,7 @@ public void TestExtract2x2() inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); for (int i = 0; i < 4; i++) { - outputs.Add(ConstStrings.Numbers[i], new Property(v[i / 2][i % 2])); + outputs.Add(ConstStrings.GetNumberString(i), new Property(v[i / 2][i % 2])); } QueueTest("math/extract2x2", "Extract2x2", "Extract2x2", "Tests Extract2x2 operation.", CreateSelfContainedTestGraph("math/extract2x2", inputs, outputs, ComparisonType.Equals)); @@ -751,7 +751,7 @@ public void TestExtract3x3() inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); for (int i = 0; i < 9; i++) { - outputs.Add(ConstStrings.Numbers[i], new Property(v[i / 3][i % 3])); + outputs.Add(ConstStrings.GetNumberString(i), new Property(v[i / 3][i % 3])); } QueueTest("math/extract3x3", "Extract3x3", "Extract3x3", "Tests Extract3x3 operation.", CreateSelfContainedTestGraph("math/extract3x3", inputs, outputs, ComparisonType.Equals)); @@ -768,7 +768,7 @@ public void TestExtract4x4() inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(v) }); for (int i = 0; i < 16; i++) { - outputs.Add(ConstStrings.Numbers[i], new Property(v[i / 4][i % 4])); + outputs.Add(ConstStrings.GetNumberString(i), new Property(v[i / 4][i % 4])); } QueueTest("math/extract4x4", "Extract4x4", "Extract4x4", "Tests Extract4x4 operation.", CreateSelfContainedTestGraph("math/extract4x4", inputs, outputs, ComparisonType.Equals)); diff --git a/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs b/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs index 1d1e6298c..fee008347 100644 --- a/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Variable/VariableNodeTests.cs @@ -168,25 +168,25 @@ private static Graph CreateVariableSetMultipleGraph() ), expected4x4 ); - sequence.AddFlow(boolBranch, ConstStrings.Numbers[0]); - sequence.AddFlow(intBranch, ConstStrings.Numbers[1]); - sequence.AddFlow(floatBranch, ConstStrings.Numbers[2]); - sequence.AddFlow(float2Branch, ConstStrings.Numbers[3]); - sequence.AddFlow(float3Branch, ConstStrings.Numbers[4]); - sequence.AddFlow(float4Branch, ConstStrings.Numbers[5]); - sequence.AddFlow(float2x2Branch, ConstStrings.Numbers[6]); - sequence.AddFlow(float3x3Branch, ConstStrings.Numbers[7]); - sequence.AddFlow(float4x4Branch, ConstStrings.Numbers[8]); - - set.AddValue(ConstStrings.Numbers[0], expectedb); - set.AddValue(ConstStrings.Numbers[1], expectedi); - set.AddValue(ConstStrings.Numbers[2], expectedf); - set.AddValue(ConstStrings.Numbers[3], expected2); - set.AddValue(ConstStrings.Numbers[4], expected3); - set.AddValue(ConstStrings.Numbers[5], expected4); - set.AddValue(ConstStrings.Numbers[6], expected2x2); - set.AddValue(ConstStrings.Numbers[7], expected3x3); - set.AddValue(ConstStrings.Numbers[8], expected4x4); + sequence.AddFlow(boolBranch, ConstStrings.GetNumberString(0)); + sequence.AddFlow(intBranch, ConstStrings.GetNumberString(1)); + sequence.AddFlow(floatBranch, ConstStrings.GetNumberString(2)); + sequence.AddFlow(float2Branch, ConstStrings.GetNumberString(3)); + sequence.AddFlow(float3Branch, ConstStrings.GetNumberString(4)); + sequence.AddFlow(float4Branch, ConstStrings.GetNumberString(5)); + sequence.AddFlow(float2x2Branch, ConstStrings.GetNumberString(6)); + sequence.AddFlow(float3x3Branch, ConstStrings.GetNumberString(7)); + sequence.AddFlow(float4x4Branch, ConstStrings.GetNumberString(8)); + + set.AddValue(ConstStrings.GetNumberString(0), expectedb); + set.AddValue(ConstStrings.GetNumberString(1), expectedi); + set.AddValue(ConstStrings.GetNumberString(2), expectedf); + set.AddValue(ConstStrings.GetNumberString(3), expected2); + set.AddValue(ConstStrings.GetNumberString(4), expected3); + set.AddValue(ConstStrings.GetNumberString(5), expected4); + set.AddValue(ConstStrings.GetNumberString(6), expected2x2); + set.AddValue(ConstStrings.GetNumberString(7), expected3x3); + set.AddValue(ConstStrings.GetNumberString(8), expected4x4); set.AddConfiguration(ConstStrings.VARIABLES, new int[] { boolIndex, From 1c8da45e2ca8f6d551f965751726d5db69442479 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Thu, 29 May 2025 06:16:21 -0400 Subject: [PATCH 12/28] Colliders only get added to nodes/children of nodes that contain a selectability or hoverability extension if a collider is not already present. --- .../Export/InteractivityExportContext.cs | 10 ++-- .../Import/InteractivityImportContext.cs | 50 ++++++++++++------- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs index fd06e70c6..d920c0bff 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs @@ -29,7 +29,7 @@ public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfR Util.Log($"InteractivityExportContext::AfterSceneExport "); if (exporter.RootTransforms == null) return; - GLTFInteractivityPlayback wrapper = null; + GLTFInteractivityPlayback playback = null; Transform t; // This assumes that EventWrapper exists on one of the root transforms which I think must be true due to how we import. @@ -37,23 +37,23 @@ public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfR { t = transform; - if (t.TryGetComponent(out wrapper)) + if (t.TryGetComponent(out playback)) break; while (t.parent != null) { - if (t.parent.TryGetComponent(out wrapper)) + if (t.parent.TryGetComponent(out playback)) break; t = t.parent; } } - if (wrapper == null) + if (playback == null) return; exporter.DeclareExtensionUsage(InteractivityGraphExtension.EXTENSION_NAME, true); - gltfRoot.AddExtension(InteractivityGraphExtension.EXTENSION_NAME, new InteractivityGraphExtension(wrapper.extensionData)); + gltfRoot.AddExtension(InteractivityGraphExtension.EXTENSION_NAME, new InteractivityGraphExtension(playback.extensionData)); } public override void AfterTextureExport(GLTFSceneExporter exporter, GLTFSceneExporter.UniqueTexture texture, int index, GLTFTexture tex) { diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs index 63b17b41e..cab883e27 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -13,6 +13,7 @@ public class InteractivityImportContext : GLTFImportPluginContext private GLTFImportContext _context; private InteractivityGraphExtension _interactivityGraph; private bool _hasSelectOrHoverNode; + private List _selectableOrHoverableObjects; public InteractivityImportContext(InteractivityImportPlugin interactivityLoader, GLTFImportContext context) { @@ -25,6 +26,8 @@ public InteractivityImportContext(InteractivityImportPlugin interactivityLoader, /// public override void OnBeforeImport() { + _hasSelectOrHoverNode = false; + _selectableOrHoverableObjects = new(); _pointerResolver = new(); Util.Log($"InteractivityImportContext::OnBeforeImport Complete"); } @@ -92,29 +95,19 @@ public override void OnBeforeImportScene(GLTFScene scene) public override void OnAfterImportNode(GLTF.Schema.Node node, int nodeIndex, GameObject nodeObject) { - AddColliderIfNecessary(node, nodeIndex, nodeObject); + CheckIfNodeIsInteractable(node, nodeIndex, nodeObject); Util.Log($"InteractivityImportContext::OnAfterImportNode Complete: {node.ToString()}"); _pointerResolver.RegisterNode(node, nodeIndex, nodeObject); } - private void AddColliderIfNecessary(GLTF.Schema.Node node, int nodeIndex, GameObject nodeObject) + private void CheckIfNodeIsInteractable(GLTF.Schema.Node node, int nodeIndex, GameObject nodeObject) { - if (!_hasSelectOrHoverNode || nodeObject.TryGetComponent(out Collider collider)) + if (!_hasSelectOrHoverNode) return; - Mesh mesh = null; - - if (nodeObject.TryGetComponent(out MeshFilter mf)) - mesh = mf.sharedMesh; - else if (nodeObject.TryGetComponent(out SkinnedMeshRenderer smr)) - mesh = smr.sharedMesh; - - if (mesh == null) - return; - - var selectable = true; - var hoverable = true; + var selectable = false; + var hoverable = false; if (node.Extensions != null) { @@ -127,8 +120,8 @@ private void AddColliderIfNecessary(GLTF.Schema.Node node, int nodeIndex, GameOb if (!selectable && !hoverable) return; - - nodeObject.AddComponent(); + + _selectableOrHoverableObjects.Add(nodeObject); } public override void OnAfterImportMesh(GLTFMesh mesh, int meshIndex, Mesh meshObject) @@ -161,6 +154,11 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj if (_interactivityGraph == null) return; + for (int i = 0; i < _selectableOrHoverableObjects.Count; i++) + { + AddCollidersToChildrenOfInteractableNode(_selectableOrHoverableObjects[i]); + } + try { _pointerResolver.RegisterSceneData(_context.SceneImporter.Root); @@ -206,5 +204,23 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj return; } } + + private void AddCollidersToChildrenOfInteractableNode(GameObject nodeObject) + { + var meshFilters = nodeObject.GetComponentsInChildren(); + + if (meshFilters.Length <= 0) + return; + + GameObject go; + + for (int i = 0; i < meshFilters.Length; i++) + { + go = meshFilters[i].gameObject; + + if (!go.TryGetComponent(out Collider collider)) + go.AddComponent(); + } + } } } \ No newline at end of file From 5d274faa8dcabeb27e716413c0f8b2db9320824f Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Fri, 30 May 2025 07:10:19 -0400 Subject: [PATCH 13/28] Use MeshColliders instead of BoxColliders for selectable/hoverable nodes that don't have a collider during import --- .../Import/InteractivityImportContext.cs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs index cab883e27..1821bbd0a 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportContext.cs @@ -156,7 +156,8 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj for (int i = 0; i < _selectableOrHoverableObjects.Count; i++) { - AddCollidersToChildrenOfInteractableNode(_selectableOrHoverableObjects[i]); + AddCollidersToChildSkinnedMeshRenderers(_selectableOrHoverableObjects[i]); + AddCollidersToChildMeshRenderers(_selectableOrHoverableObjects[i]); } try @@ -205,7 +206,28 @@ public override void OnAfterImportScene(GLTFScene scene, int sceneIndex, GameObj } } - private void AddCollidersToChildrenOfInteractableNode(GameObject nodeObject) + private void AddCollidersToChildSkinnedMeshRenderers(GameObject nodeObject) + { + var smrs = nodeObject.GetComponentsInChildren(); + + if (smrs.Length <= 0) + return; + + GameObject go; + + for (int i = 0; i < smrs.Length; i++) + { + go = smrs[i].gameObject; + + if (!go.TryGetComponent(out Collider collider)) + { + var mc = go.AddComponent(); + mc.sharedMesh = smrs[i].sharedMesh; + } + } + } + + private void AddCollidersToChildMeshRenderers(GameObject nodeObject) { var meshFilters = nodeObject.GetComponentsInChildren(); @@ -219,7 +241,10 @@ private void AddCollidersToChildrenOfInteractableNode(GameObject nodeObject) go = meshFilters[i].gameObject; if (!go.TryGetComponent(out Collider collider)) - go.AddComponent(); + { + var mc = go.AddComponent(); + mc.sharedMesh = meshFilters[i].sharedMesh; + } } } } From 814120955183c7b321a27dbfe005e8524ffff483 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Fri, 30 May 2025 11:04:08 -0400 Subject: [PATCH 14/28] Fix for editor-time exporting --- .../Context/Export/InteractivityExportContext.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs index d920c0bff..057e96f0f 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs @@ -53,7 +53,16 @@ public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfR return; exporter.DeclareExtensionUsage(InteractivityGraphExtension.EXTENSION_NAME, true); - gltfRoot.AddExtension(InteractivityGraphExtension.EXTENSION_NAME, new InteractivityGraphExtension(playback.extensionData)); + + var extensionData = playback.extensionData; + + if(extensionData == null && playback.gameObject.TryGetComponent(out GLTFInteractivityData data)) + { + var serializer = new GraphSerializer(); + extensionData = serializer.Deserialize(data.interactivityJson); + } + + gltfRoot.AddExtension(InteractivityGraphExtension.EXTENSION_NAME, new InteractivityGraphExtension(extensionData)); } public override void AfterTextureExport(GLTFSceneExporter exporter, GLTFSceneExporter.UniqueTexture texture, int index, GLTFTexture tex) { From 4e1a0f64782ae2b8b7a2b85f3157113e2626ba65 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Fri, 30 May 2025 13:27:25 -0400 Subject: [PATCH 15/28] Fix for hoverability/selectability not working on export from playback --- .../Export/InteractivityExportContext.cs | 116 ++++++++++++++---- .../Playback/Nodes/Event/OnHoverIn.cs | 19 +-- .../Playback/Nodes/Event/OnHoverOut.cs | 23 ++-- .../Playback/Nodes/Event/OnSelect.cs | 21 ++-- .../Playback/Pointers/PointerResolver.cs | 5 +- 5 files changed, 130 insertions(+), 54 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs index 057e96f0f..80e0a77fe 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportContext.cs @@ -1,5 +1,6 @@ using GLTF.Schema; -using System.Linq; +using System; +using System.Collections.Generic; using UnityEngine; using UnityGLTF.Plugins; @@ -8,6 +9,11 @@ namespace UnityGLTF.Interactivity.Playback public class InteractivityExportContext : GLTFExportPluginContext { + private HashSet _hoverable = new(); + private HashSet _selectable = new(); + private GLTFInteractivityData _interactivityData; + private GLTFInteractivityPlayback _playback; + public override void AfterMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) { Util.Log($"InteractivityExportContext::AfterMaterialExport "); @@ -27,9 +33,42 @@ public override void AfterPrimitiveExport(GLTFSceneExporter exporter, Mesh mesh, public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) { Util.Log($"InteractivityExportContext::AfterSceneExport "); + } + public override void AfterTextureExport(GLTFSceneExporter exporter, GLTFSceneExporter.UniqueTexture texture, int index, GLTFTexture tex) + { + Util.Log($"InteractivityExportContext::AfterTextureExport "); + } + public override bool BeforeMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) + { + Util.Log($"InteractivityExportContext::BeforeMaterialExport "); + return false; + } + public override void BeforeNodeExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, GLTF.Schema.Node node) + { + if(_playback.engine != null && _playback.engine.pointerResolver.TryGetPointersOf(transform.gameObject, out var pointers)) + { + if(pointers.selectability.getter()) + AddSelectabilityExtensionToNode(exporter, node); + + if (pointers.hoverability.getter()) + AddHoverabilityExtensionToNode(exporter, node); + } + else if (_interactivityData != null) + { + if(_selectable.Contains(transform)) + AddSelectabilityExtensionToNode(exporter, node); + + if (_hoverable.Contains(transform)) + AddHoverabilityExtensionToNode(exporter, node); + } + Util.Log($"InteractivityExportContext::BeforeNodeExport "); + } + public override void BeforeSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) + { if (exporter.RootTransforms == null) return; - GLTFInteractivityPlayback playback = null; + _playback = null; + _interactivityData = null; Transform t; // This assumes that EventWrapper exists on one of the root transforms which I think must be true due to how we import. @@ -37,54 +76,83 @@ public override void AfterSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfR { t = transform; - if (t.TryGetComponent(out playback)) + if (t.TryGetComponent(out _playback)) break; while (t.parent != null) { - if (t.parent.TryGetComponent(out playback)) + if (t.parent.TryGetComponent(out _playback)) break; t = t.parent; } } - if (playback == null) + if (_playback == null) return; - exporter.DeclareExtensionUsage(InteractivityGraphExtension.EXTENSION_NAME, true); + var extensionData = _playback.extensionData; - var extensionData = playback.extensionData; + var hasData = _playback.gameObject.TryGetComponent(out _interactivityData); - if(extensionData == null && playback.gameObject.TryGetComponent(out GLTFInteractivityData data)) + if (extensionData == null) { + if (!hasData) + throw new InvalidOperationException("No valid extension data source found for interactive glb. Did you delete the data component before exporting?"); + var serializer = new GraphSerializer(); - extensionData = serializer.Deserialize(data.interactivityJson); + extensionData = serializer.Deserialize(_interactivityData.interactivityJson); + + for (int i = 0; i < _interactivityData.pointerReferences.nodes.Count; i++) + { + var node = _interactivityData.pointerReferences.nodes[i]; + if (node.isHoverable) + _hoverable.Add(node.unityObject.transform); + + if (node.isSelectable) + _selectable.Add(node.unityObject.transform); + } } + exporter.DeclareExtensionUsage(InteractivityGraphExtension.EXTENSION_NAME, true); gltfRoot.AddExtension(InteractivityGraphExtension.EXTENSION_NAME, new InteractivityGraphExtension(extensionData)); + Util.Log($"InteractivityExportContext::BeforeSceneExport "); } - public override void AfterTextureExport(GLTFSceneExporter exporter, GLTFSceneExporter.UniqueTexture texture, int index, GLTFTexture tex) - { - Util.Log($"InteractivityExportContext::AfterTextureExport "); - } - public override bool BeforeMaterialExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Material material, GLTFMaterial materialNode) - { - Util.Log($"InteractivityExportContext::BeforeMaterialExport "); - return false; - } - public override void BeforeNodeExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot, Transform transform, GLTF.Schema.Node node) + public override void BeforeTextureExport(GLTFSceneExporter exporter, ref GLTFSceneExporter.UniqueTexture texture, string textureSlot) { - Util.Log($"InteractivityExportContext::BeforeNodeExport "); + Util.Log($"InteractivityExportContext::BeforeTextureExport "); } - public override void BeforeSceneExport(GLTFSceneExporter exporter, GLTFRoot gltfRoot) + + public static void AddHoverabilityExtensionToNode(GLTFSceneExporter exporter, GLTF.Schema.Node node) { - Util.Log($"InteractivityExportContext::BeforeSceneExport "); + var nodeExtensions = node.Extensions; + if (nodeExtensions == null) + { + nodeExtensions = new Dictionary(); + node.Extensions = nodeExtensions; + } + if (!nodeExtensions.ContainsKey(KHR_node_hoverability_Factory.EXTENSION_NAME)) + { + nodeExtensions.Add(KHR_node_hoverability_Factory.EXTENSION_NAME, new KHR_node_hoverability()); + } + exporter.DeclareExtensionUsage(KHR_node_hoverability_Factory.EXTENSION_NAME, false); } - public override void BeforeTextureExport(GLTFSceneExporter exporter, ref GLTFSceneExporter.UniqueTexture texture, string textureSlot) + + public void AddSelectabilityExtensionToNode(GLTFSceneExporter exporter, GLTF.Schema.Node node) { - Util.Log($"InteractivityExportContext::BeforeTextureExport "); + var nodeExtensions = node.Extensions; + if (nodeExtensions == null) + { + nodeExtensions = new Dictionary(); + node.Extensions = nodeExtensions; + } + if (!nodeExtensions.ContainsKey(KHR_node_selectability_Factory.EXTENSION_NAME)) + { + nodeExtensions.Add(KHR_node_selectability_Factory.EXTENSION_NAME, new KHR_node_selectability()); + } + exporter.DeclareExtensionUsage(KHR_node_selectability_Factory.EXTENSION_NAME, false); } + } } \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs index 94e6fb946..b44c2676a 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs @@ -50,19 +50,22 @@ private void OnHoverIn(RayArgs args) // TODO: Add support for stopPropagation once we understand what it actually does. // I've read that part of the spec a handful of times and still am not sure. var t = args.go.transform; - var go = t.gameObject; - var nodeIndex = engine.pointerResolver.IndexOf(go); - - var shouldExecute = true; // If there's a parent node provided in the config we need to check if what we hit was a child of it (or that specific object itself) if (_parentNode != null) - shouldExecute = t.IsChildOf(_parentNode); + { + if (!engine.pointerResolver.TryGetPointersOf(_parentNode.gameObject, out var pointers)) + return; - // Node was not a child of the nodeIndex from the config, so we shouldn't execute our flow or set any values. - if (!shouldExecute) - return; + if (!pointers.hoverability.getter()) + return; + + if (!t.IsChildOf(_parentNode)) + return; + } + var go = t.gameObject; + var nodeIndex = engine.pointerResolver.IndexOf(go); _hoverNodeIndex = nodeIndex; _controllerIndex = args.controllerIndex; diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs index 453bef770..bb3f5e7e7 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs @@ -41,23 +41,24 @@ private void OnHoverOut(RayArgs args) // TODO: Add support for stopPropagation once we understand what it actually does. // I've read that part of the spec a handful of times and still am not sure. var t = args.go.transform; - var go = t.gameObject; - var nodeIndex = engine.pointerResolver.IndexOf(go); - - var shouldExecute = true; // If there's a parent node provided in the config we need to check if what we hit was a child of it (or that specific object itself) if (_parentNode != null) - shouldExecute = t.IsChildOf(_parentNode); + { + if (!engine.pointerResolver.TryGetPointersOf(_parentNode.gameObject, out var pointers)) + return; - // Node was not a child of the nodeIndex from the config, so we shouldn't execute our flow or set any values. - if (!shouldExecute) - return; + if (!pointers.hoverability.getter()) + return; + + if (!t.IsChildOf(_parentNode)) + return; + } - _hoverNodeIndex = nodeIndex; - _controllerIndex = args.controllerIndex; + var go = t.gameObject; + var nodeIndex = engine.pointerResolver.IndexOf(go); - Util.Log($"OnHoverIn node {nodeIndex} corresponding to GO {go.name}", go); + Util.Log($"OnHoverOut node {nodeIndex} corresponding to GO {go.name}", go); TryExecuteFlow(ConstStrings.OUT); } diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs index 8fd21d13e..42aad7f81 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs @@ -46,18 +46,21 @@ private void OnSelect(RayArgs args) // TODO: Add support for stopPropagation once we understand what it actually does. // I've read that part of the spec a handful of times and still am not sure. var t = args.go.transform; - var go = t.gameObject; - var nodeIndex = engine.pointerResolver.IndexOf(go); - - var shouldExecute = true; - // If there's a parent node provided in the config we need to check if what we hit was a child of it (or that specific object itself) if (_parentNode != null) - shouldExecute = t.IsChildOf(_parentNode); + { + if (!engine.pointerResolver.TryGetPointersOf(_parentNode.gameObject, out var pointers)) + return; - // Node was not a child of the nodeIndex from the config, so we shouldn't execute our flow or set any values. - if (!shouldExecute) - return; + if (!pointers.selectability.getter()) + return; + + if (!t.IsChildOf(_parentNode)) + return; + } + + var go = t.gameObject; + var nodeIndex = engine.pointerResolver.IndexOf(go); _selectedNodeIndex = nodeIndex; _selectionPoint = args.result.worldPosition; diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs index 75fe1e0cc..86098415b 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs @@ -24,6 +24,7 @@ public class PointerResolver [SerializeField] private List _nodes = new(); [SerializeField] private SceneData _sceneData; + public IReadOnlyList nodes => _nodes; public ReadOnlyCollection nodePointers { get; private set; } public void RegisterMesh(GLTF.Schema.GLTFMesh mesh, int meshIndex, Mesh unityMesh) @@ -43,8 +44,8 @@ public void RegisterCamera(GLTF.Schema.GLTFCamera camera, int cameraIndex, Camer public void RegisterNode(GLTF.Schema.Node node, int nodeIndex, GameObject unityObject) { - var selectable = true; - var hoverable = true; + var selectable = false; + var hoverable = false; if (node.Extensions != null) { From 0d730bf845447b5392aad3c17ff7a2bc48214761 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 4 Jun 2025 13:49:15 -0400 Subject: [PATCH 16/28] Updated math/rotate3D to match new spec changes. Updated name of math/rotate2D to capitalize the D. --- .../Context/Export/InteractivityExportPlugin.cs | 1 + .../Context/Import/InteractivityImportPlugin.cs | 1 + .../Interactivity/Playback/NodeRegistry.cs | 8 ++++---- .../Playback/NodeSpecs/Math/Rotate3D.cs | 7 +++---- .../Playback/Nodes/Math/Rotate3D.cs | 13 ++++++++----- .../Interactivity/Nodes/Math/MathNodesTests.cs | 17 ++++++++++------- 6 files changed, 27 insertions(+), 20 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs index 5391e177a..e392f9f07 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Export/InteractivityExportPlugin.cs @@ -2,6 +2,7 @@ namespace UnityGLTF.Interactivity.Playback { + [NonRatifiedPlugin] public class InteractivityExportPlugin : GLTFExportPlugin { public override string DisplayName => "KHR_interactivity Playback Exporter"; diff --git a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs index 73e3c26af..129e9d12d 100644 --- a/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs +++ b/Runtime/Scripts/Interactivity/Playback/Context/Import/InteractivityImportPlugin.cs @@ -3,6 +3,7 @@ namespace UnityGLTF.Interactivity.Playback { + [NonRatifiedPlugin] public class InteractivityImportPlugin : GLTFImportPlugin { public override string DisplayName => "KHR_interactivity Playback Importer"; diff --git a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs index 506638c8c..98bef9141 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs @@ -133,8 +133,8 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/normalize"] = (engine, node) => new MathNormalize(engine, node), ["math/or"] = (engine, node) => new MathOr(engine, node), ["math/pi"] = (engine, node) => new MathPi(engine, node), - ["math/rotate2d"] = (engine, node) => new MathRotate2D(engine, node), - ["math/rotate3d"] = (engine, node) => new MathRotate3D(engine, node), + ["math/rotate2D"] = (engine, node) => new MathRotate2D(engine, node), + ["math/rotate3D"] = (engine, node) => new MathRotate3D(engine, node), ["math/round"] = (engine, node) => new MathRound(engine, node), ["math/sign"] = (engine, node) => new MathSign(engine, node), ["math/sinh"] = (engine, node) => new MathSinH(engine, node), @@ -259,8 +259,8 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/normalize"] = new MathOneOperandSpec(), ["math/or"] = new MathTwoOperandsSpec(), ["math/pi"] = new MathConstantSpec(), - ["math/rotate2d"] = new MathTwoOperandsRetSpec(), - ["math/rotate3d"] = new MathRotate3DSpec(), + ["math/rotate2D"] = new MathTwoOperandsRetSpec(), + ["math/rotate3D"] = new MathRotate3DSpec(), ["math/round"] = new MathOneOperandFloatSpec(), ["math/sign"] = new MathOneOperandSpec(), ["math/sinh"] = new MathOneOperandFloatSpec(), diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs index dfe5b14b3..c48b50763 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs @@ -10,9 +10,8 @@ protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() { var values = new NodeValue[] { - new NodeValue(ConstStrings.A, "Vector", new Type[] { typeof(float3)}), - new NodeValue(ConstStrings.B, "Axis", new Type[] { typeof(float3)}), - new NodeValue(ConstStrings.C, "Angle", new Type[] { typeof(float)}), + new NodeValue(ConstStrings.A, "Vector to Rotate", new Type[] { typeof(float3)}), + new NodeValue(ConstStrings.B, "Rotation Quaternion", new Type[] { typeof(float4)}), }; return (null, values); @@ -22,7 +21,7 @@ protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() { var values = new NodeValue[] { - new NodeValue(ConstStrings.VALUE, "Value", new Type[] { typeof(float3) }), + new NodeValue(ConstStrings.VALUE, "Rotated Vector", new Type[] { typeof(float3) }), }; return (null, values); diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs index bd8fd13c6..f3ee6563e 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs @@ -1,6 +1,7 @@ using System; using Unity.Mathematics; using UnityEngine; +using UnityGLTF.Interactivity.Playback.Extensions; namespace UnityGLTF.Interactivity.Playback { @@ -14,19 +15,21 @@ public override IProperty GetOutputValue(string id) { TryEvaluateValue(ConstStrings.A, out IProperty a); TryEvaluateValue(ConstStrings.B, out IProperty b); - TryEvaluateValue(ConstStrings.C, out IProperty c); return a switch { - Property aProp when b is Property bProp && c is Property cProp => new Property(rotate(aProp.value, bProp.value, cProp.value)), + Property aProp when b is Property bProp => new Property(rotate(aProp.value, bProp.value)), _ => throw new InvalidOperationException("No supported type found."), }; } - private static float3 rotate(float3 vec, float3 axis, float rad) + private static float3 rotate(float3 a, float4 b) { - // TODO: Test rotation direction to make sure it matches the spec (counter-clockwise). - return math.mul(quaternion.AxisAngle(axis, rad), vec); + var b3 = b.xyz; + var b3xa = math.cross(b3, a); + // Is this equivalent to math.mul(b.ToQuaternion(), a)? + // I have no idea so let's be safe and use the eq from the spec. + return a + 2f * (math.cross(b3, b3xa) + b.w * b3xa); } } } \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 7147abbac..89a747c68 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using System.Collections.Generic; using Unity.Mathematics; +using UnityGLTF.Interactivity.Playback.Extensions; namespace UnityGLTF.Interactivity.Playback.Tests { @@ -898,18 +899,20 @@ public void TestXor() [Test] public void TestRotate3d() { - RotateTest3D("Rotate3D_NegativeZ_AroundY_ByPiOver2", "Rotate3D Test 1", "Tests a rotation in 3D.", "math/rotate3d", new float3(0.0f, 0.0f, -1.0f), new float3(0.0f, 1.0f, 0.0f), math.PI * 0.5f, new float3(-1.0f, 0.0f, 0.0f)); - RotateTest3D("Rotate3D_X_AroundY_ByPi", "Rotate3D Test 2", "Tests a rotation in 3D.", "math/rotate3d", new float3(1.0f, 0.0f, 0.0f), new float3(0.0f, 1.0f, 0.0f), math.PI, new float3(-1.0f, 0.0f, 0.0f)); + var q1 = quaternion.Euler(0f, math.PI, 0f).ToFloat4(); + var q2 = quaternion.Euler(0f, math.PI/2, 0f).ToFloat4(); + + RotateTest3D("Rotate3D_NegativeZ_AroundY_ByPiOver2", "Rotate3D Test 1", "Tests a rotation in 3D.", "math/rotate3D", new float3(0.0f, 0.0f, -1.0f), q1, new float3(0.0f, 0.0f, 1.0f)); + RotateTest3D("Rotate3D_X_AroundY_ByPi", "Rotate3D Test 2", "Tests a rotation in 3D.", "math/rotate3D", new float3(1.0f, 0.0f, 0.0f), q2, new float3(0.0f, 0.0f, -1.0f)); } - private static void RotateTest3D(string fileName, string testName, string testDescription, string nodeName, T a, T b, V c, T expected) + private static void RotateTest3D(string fileName, string testName, string testDescription, string nodeName, T a, V b, T expected) { var inputs = new Dictionary(); var outputs = new Dictionary(); inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(a) }); - inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); - inputs.Add(ConstStrings.C, new Value() { id = ConstStrings.C, property = new Property(c) }); + inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); outputs.Add(ConstStrings.VALUE, new Property(expected)); QueueTest(nodeName, fileName, testName, testDescription, CreateSelfContainedTestGraph(nodeName, inputs, outputs, ComparisonType.Approximately)); @@ -918,8 +921,8 @@ private static void RotateTest3D(string fileName, string testName, string [Test] public void TestRotate2d() { - RotateTest2D("Rotate2D_Y_ByPiOver2", "Rotate2D Test 1", "Tests a rotation in 2D.", "math/rotate2d", new float2(0.0f, 1.0f), math.PI * 0.5f, new float2(-1.0f, 0.0f)); - RotateTest2D("Rotate2D_NegativeX_ByPiOver2", "Rotate2D Test 2", "Tests a rotation in 2D.", "math/rotate2d", new float2(-1.0f, 0.0f), math.PI * 0.5f, new float2(0.0f, -1.0f)); + RotateTest2D("Rotate2D_Y_ByPiOver2", "Rotate2D Test 1", "Tests a rotation in 2D.", "math/rotate2D", new float2(0.0f, 1.0f), math.PI * 0.5f, new float2(-1.0f, 0.0f)); + RotateTest2D("Rotate2D_NegativeX_ByPiOver2", "Rotate2D Test 2", "Tests a rotation in 2D.", "math/rotate2D", new float2(-1.0f, 0.0f), math.PI * 0.5f, new float2(0.0f, -1.0f)); } private static void RotateTest2D(string fileName, string testName, string testDescription, string nodeName, T a, V b, T expected) From 106e7652f534c4489b6a3bfad8b07c28b082990c Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Mon, 9 Jun 2025 17:24:05 -0400 Subject: [PATCH 17/28] Added new quaternion nodes from spec update. --- .../Playback/Data/Helpers/Constants.cs | 2 + .../Interactivity/Playback/NodeRegistry.cs | 12 ++ .../Playback/NodeSpecs/Math/Quaternions.cs | 148 ++++++++++++++++++ .../NodeSpecs/Math/Quaternions.cs.meta | 2 + .../Playback/Nodes/Math/QuatAngleBetween.cs | 32 ++++ .../Nodes/Math/QuatAngleBetween.cs.meta | 2 + .../Playback/Nodes/Math/QuatConjugate.cs | 31 ++++ .../Playback/Nodes/Math/QuatConjugate.cs.meta | 11 ++ .../Playback/Nodes/Math/QuatFromAxisAngle.cs | 36 +++++ .../Nodes/Math/QuatFromAxisAngle.cs.meta | 2 + .../Playback/Nodes/Math/QuatFromDirections.cs | 60 +++++++ .../Nodes/Math/QuatFromDirections.cs.meta | 2 + .../Playback/Nodes/Math/QuatMul.cs | 37 +++++ .../Playback/Nodes/Math/QuatMul.cs.meta | 2 + .../Playback/Nodes/Math/QuatToAxisAngle.cs | 46 ++++++ .../Nodes/Math/QuatToAxisAngle.cs.meta | 2 + 16 files changed, 427 insertions(+) create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs.meta create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs.meta diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs index 108533ee0..5a7446c3b 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Constants.cs @@ -31,6 +31,8 @@ public static class Pointers public static class ConstStrings { + public const string AXIS = "axis"; + public const string ANGLE = "angle"; public const string REMAINING_INPUTS = "remainingInputs"; public const string LAST_INDEX = "lastIndex"; public const string LAST_REMAINING_TIME = "lastRemainingTime"; diff --git a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs index 98bef9141..97049f863 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs @@ -82,6 +82,12 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/not"] = (engine, node) => new MathNot(engine, node), ["math/popcnt"] = (engine, node) => new MathPopcnt(engine, node), ["math/pow"] = (engine, node) => new MathPow(engine, node), + ["math/quatConjugate"] = (engine, node) => new MathQuatConjugate(engine, node), + ["math/quatMul"] = (engine, node) => new MathQuatMul(engine, node), + ["math/quatAngleBetween"] = (engine, node) => new MathQuatAngleBetween(engine, node), + ["math/quatFromAxisAngle"] = (engine, node) => new MathQuatFromAxisAngle(engine, node), + ["math/quatToAxisAngle"] = (engine, node) => new MathQuatToAxisAngle(engine, node), + ["math/quatFromDirections"] = (engine, node) => new MathQuatFromDirections(engine, node), ["math/rad"] = (engine, node) => new MathRad(engine, node), ["math/random"] = (engine, node) => new MathRandom(engine, node), ["math/rem"] = (engine, node) => new MathRem(engine, node), @@ -215,6 +221,12 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/not"] = new MathOneOperandSpec(), ["math/popcnt"] = new MathOneOperandSpec(), ["math/pow"] = new MathTwoOperandsFloatSpec(), + ["math/quatConjugate"] = new MathQuatConjugateSpec(), + ["math/quatMul"] = new MathQuatMulSpec(), + ["math/quatAngleBetween"] = new MathQuatAngleBetweenSpec(), + ["math/quatFromAxisAngle"] = new MathQuatFromAxisAngleSpec(), + ["math/quatToAxisAngle"] = new MathQuatToAxisAngleSpec(), + ["math/quatFromDirections"] = new MathQuatFromDirectionsSpec(), ["math/rad"] = new MathOneOperandFloatSpec(), ["math/random"] = new MathRandomSpec(), ["math/rem"] = new MathTwoOperandsSpec(), diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs new file mode 100644 index 000000000..aca925bb8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs @@ -0,0 +1,148 @@ +using System; +using Unity.Mathematics; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathQuatConjugateSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Input quaternion", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Conjugated quaternion.", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + } + + public class MathQuatMulSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "First quaternion", new Type[] { typeof(float4) }), + new NodeValue(ConstStrings.B, "Second quaternion", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Quaternion product.", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + } + + public class MathQuatAngleBetweenSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "First quaternion", new Type[] { typeof(float4) }), + new NodeValue(ConstStrings.B, "Second quaternion", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Angle in radians.", new Type[] { typeof(float) }), + }; + + return (null, values); + } + } + + public class MathQuatFromAxisAngleSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.AXIS, "Rotation axis.", new Type[] { typeof(float3) }), + new NodeValue(ConstStrings.ANGLE, "Angle in Radians.", new Type[] { typeof(float) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Rotation quaternion.", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + } + + public class MathQuatToAxisAngleSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Rotation quaternion.", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.AXIS, "Rotation axis.", new Type[] { typeof(float3) }), + new NodeValue(ConstStrings.ANGLE, "Angle in Radians.", new Type[] { typeof(float) }), + }; + + return (null, values); + } + } + + public class MathQuatFromDirectionsSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "First direction.", new Type[] { typeof(float3) }), + new NodeValue(ConstStrings.B, "Second direction.", new Type[] { typeof(float3) }), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Rotation quaternion.", new Type[] { typeof(float4) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs.meta new file mode 100644 index 000000000..cbc5f7333 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Quaternions.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8f45e99c2bf56954cbc0fdeaab4dcb4a \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs new file mode 100644 index 000000000..38fa23304 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathQuatAngleBetween : BehaviourEngineNode + { + public MathQuatAngleBetween(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(AngleBetween(aProp.value, bProp.value)), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float AngleBetween(float4 a, float4 b) + { + return 2f * math.acos(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs.meta new file mode 100644 index 000000000..0433db095 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatAngleBetween.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7c8afeeb8da0f344c82c63d35cb8b8c8 \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs new file mode 100644 index 000000000..762946667 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathQuatConjugate : BehaviourEngineNode + { + public MathQuatConjugate(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + + return a switch + { + Property aProp => new Property(Conjugate(aProp.value)), + _ => throw new InvalidOperationException($"Input A is a {a.GetTypeSignature()} and not a float4!"), + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float4 Conjugate(float4 a) + { + return new float4(-a.x, -a.y, -a.z, a.w); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs.meta new file mode 100644 index 000000000..ee19f8cf8 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatConjugate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 634102360427cac45b45f7f79753e0ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs new file mode 100644 index 000000000..b645b1688 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs @@ -0,0 +1,36 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathQuatFromAxisAngle : BehaviourEngineNode + { + public MathQuatFromAxisAngle(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.AXIS, out IProperty axis); + TryEvaluateValue(ConstStrings.ANGLE, out IProperty angle); + + return axis switch + { + Property axisProp when angle is Property angleProp => new Property(AxisAngle(axisProp.value, angleProp.value)), + _ => throw new InvalidOperationException($"Axis is a {axis.GetTypeSignature()}, expected float3. Angle is a {angle.GetTypeSignature()}, expected float."), + }; + } + + private static float4 AxisAngle(float3 axis, float angle) + { + var sin = math.sin(0.5f * angle); + var x = axis.x * sin; + var y = axis.y * sin; + var z = axis.z * sin; + var w = math.cos(0.5f * angle); + + return new float4(x, y, z, w); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs.meta new file mode 100644 index 000000000..c633c37c1 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromAxisAngle.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e22b9a8d4f5ac8c40825df8d49e8ee3d \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs new file mode 100644 index 000000000..1a4addc84 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs @@ -0,0 +1,60 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathQuatFromDirections : BehaviourEngineNode + { + private static readonly float4 IDENTITY = new float4(0f, 0f, 0f, 1f); + + public MathQuatFromDirections(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(FromDirections(aProp.value, bProp.value)), + _ => throw new InvalidOperationException($"Input A is a {a.GetTypeSignature()} and not a float4!"), + }; + } + + private static float4 FromDirections(float3 a, float3 b) + { + var c = math.dot(a, b); + + if (Mathf.Approximately(c, 1f)) + return IDENTITY; + + if (Mathf.Approximately(c, -1f)) + return GeneratePerpendicularUnitVector(a); + + var halfc = 0.5f * c; + var r = math.normalize(math.cross(a, b)); + r *= math.sqrt(0.5f - halfc); + + return new float4(r.x, r.y, r.z, math.sqrt(0.5f + halfc)); + } + + private static float4 GeneratePerpendicularUnitVector(float3 a) + { + var x = CopySign(a.z, a.x); + var y = CopySign(a.z, a.y); + var z = -CopySign(a.x, a.z) - CopySign(a.y, a.z); + + return new float4(x, y, z, 0f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float CopySign(float a, float b) + { + return (b >= 0 ? 1f : -1f) * math.abs(a); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs.meta new file mode 100644 index 000000000..7bd9fed77 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatFromDirections.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3748adaef9811c04abf6f59c7ff0a24a \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs new file mode 100644 index 000000000..5faea8bea --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathQuatMul : BehaviourEngineNode + { + public MathQuatMul(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out IProperty a); + TryEvaluateValue(ConstStrings.B, out IProperty b); + + return a switch + { + Property aProp when b is Property bProp => new Property(Multiply(aProp.value, bProp.value)), + _ => throw new InvalidOperationException($"No supported type found for input A: {a.GetTypeSignature()} or input type did not match B: {b.GetTypeSignature()}."), + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float4 Multiply(float4 a, float4 b) + { + var x = a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y; + var y = a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z; + var z = a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x; + var w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z; + + return new float4(x, y, z, w); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs.meta new file mode 100644 index 000000000..e6620007f --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatMul.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a8aeb69542d52a6479e0be9c1ae7ce81 \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs new file mode 100644 index 000000000..36139f248 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs @@ -0,0 +1,46 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathQuatToAxisAngle : BehaviourEngineNode + { + private static readonly float3 UP = new float3(0f, 1f, 0f); + + public MathQuatToAxisAngle(BehaviourEngine engine, Node node) : base(engine, node) + { + } + + public override IProperty GetOutputValue(string id) + { + TryEvaluateValue(ConstStrings.A, out float4 a); + + return id switch + { + ConstStrings.AXIS => new Property(Axis(a)), + ConstStrings.ANGLE => new Property(Angle(a)), + _ => throw new InvalidOperationException($"Requested output {id} is not part of the spec for this node."), + }; + } + + private float3 Axis(float4 a) + { + if (Mathf.Approximately(math.abs(a.w), 1f)) + return UP; + + var d = math.sqrt(1 - a.w * a.w); + a /= d; + + return a.xyz; + } + + private static float Angle(float4 a) + { + if (Mathf.Approximately(math.abs(a.w), 1f)) + return 0f; + + return 2f * math.acos(a.w); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs.meta b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs.meta new file mode 100644 index 000000000..fdd1a4da6 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/QuatToAxisAngle.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8a8e5993209c0654abc4ff41006c1785 \ No newline at end of file From 30bfcd344514cde951ca5a672dcd9dc7981e586d Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 11 Jun 2025 09:31:00 -0400 Subject: [PATCH 18/28] Added tests for quaternion conjugate, multiply, angle between, and to axis angle. --- .../Nodes/Math/MathNodesTests.cs | 101 +++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 89a747c68..b9e1dca64 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -900,7 +900,7 @@ public void TestXor() public void TestRotate3d() { var q1 = quaternion.Euler(0f, math.PI, 0f).ToFloat4(); - var q2 = quaternion.Euler(0f, math.PI/2, 0f).ToFloat4(); + var q2 = quaternion.Euler(0f, math.PI / 2, 0f).ToFloat4(); RotateTest3D("Rotate3D_NegativeZ_AroundY_ByPiOver2", "Rotate3D Test 1", "Tests a rotation in 3D.", "math/rotate3D", new float3(0.0f, 0.0f, -1.0f), q1, new float3(0.0f, 0.0f, 1.0f)); RotateTest3D("Rotate3D_X_AroundY_ByPi", "Rotate3D Test 2", "Tests a rotation in 3D.", "math/rotate3D", new float3(1.0f, 0.0f, 0.0f), q2, new float3(0.0f, 0.0f, -1.0f)); @@ -993,5 +993,104 @@ public void TestDeg() TestNodeWithAllFloatNInputVariants("Deg", "Deg", "Tests math/deg with standard values.", "math/deg", a, expected); } + + [Test] + public void TestQuatConjugate() + { + var a = new float4(1f, -2f, 3f, 4f); + var e = new float4(-1f, 2f, -3f, 4f); + + QueueTest("math/quatConjugate", "Quat_Conjugate", "Quaternion Conjugate", "Tests quaternion conjugate operation.", CreateSelfContainedTestGraph("math/quatConjugate", In(a), Out(e), ComparisonType.Equals)); + + var a2 = new float4(1f, 2f, float.PositiveInfinity, 4f); + var e2 = new float4(-1f, -2f, float.NegativeInfinity, 4f); + QueueTest("math/quatConjugate", "Quat_Conjugate_Inf", "Quaternion Conjugate w/ Infinity", "Tests quaternion conjugate operation with infinity.", CreateSelfContainedTestGraph("math/quatConjugate", In(a2), Out(e2), ComparisonType.Equals)); + + var a3 = new float4(float.NaN, float.NaN, float.NaN, float.NaN); + var e3 = new float4(float.NaN, float.NaN, float.NaN, float.NaN); + QueueTest("math/quatConjugate", "Quat_Conjugate_NaN", "Quaternion Conjugate w/ NaN", "Tests quaternion conjugate operation with NaN.", CreateSelfContainedTestGraph("math/quatConjugate", In(a3), Out(e3), ComparisonType.IsNaN)); + } + + [Test] + public void TestQuatMul() + { + QueueTest("math/quatMul", "Quat_Mul_Identity", "Quaternion Multiplication (Identity)", "Tests that identity quaternions multiplied together produce an identity quaternion.", QuatMulTest(new float4(0, 0, 0, 1), + new float4(0, 0, 0, 1), + new float4(0, 0, 0, 1))); + + QueueTest("math/quatMul", "Quat_Mul_xy_z", "Quaternion Multiplication (x*y=z)", "Tests that x*y=z.", QuatMulTest(new float4(1, 0, 0, 0), + new float4(0, 1, 0, 0), + new float4(0, 0, 1, 0))); + + QueueTest("math/quatMul", "Quat_Mul_yz_x", "Quaternion Multiplication (y*z=x)", "Tests that y*z=x.", QuatMulTest(new float4(0, 1, 0, 0), + new float4(0, 0, 1, 0), + new float4(1, 0, 0, 0))); + + QueueTest("math/quatMul", "Quat_Mul_zx_y", "Quaternion Multiplication (z*x=y)", "Tests that z*x=y.", QuatMulTest(new float4(0, 0, 1, 0), + new float4(1, 0, 0, 0), + new float4(0, 1, 0, 0))); + + QueueTest("math/quatMul", "Quat_Mul_xx_negx", "Quaternion Multiplication (x*x=-x)", "Tests that x*x=-x.", QuatMulTest(new float4(1, 0, 0, 0), + new float4(1, 0, 0, 0), + new float4(0, 0, 0, -1))); + } + + private static (Graph, TestValues) QuatMulTest(float4 a, float4 b, float4 e) + { + return CreateSelfContainedTestGraph("math/quatMul", In(a, b), Out(e), ComparisonType.Equals); + } + + [Test] + public void TestQuatAngleBetween() + { + var a = quaternion.Euler(math.PI * 0.5f, 0f, 0f).ToFloat4(); + var b = quaternion.Euler(-math.PI * 0.5f, 0f, 0f).ToFloat4(); + + QueueTest("math/quatAngleBetween", "Quat_Angle_Between", "Quaternion Angle Between", "Tests quaternion angle between operation on the x axis only.", CreateSelfContainedTestGraph("math/quatAngleBetween", In(a, b), Out(math.PI), ComparisonType.Equals)); + + var a2 = quaternion.Euler(math.PI * 0.5f, 0f, 0f).ToFloat4(); + var b2 = quaternion.Euler(-math.PI * 0.5f, 0f, math.PI * 0.5f).ToFloat4(); + + QueueTest("math/quatAngleBetween", "Quat_Angle_Between_xz", "Quaternion Angle Between (x/z rotations)", "Tests quaternion angle between operation with the xz axes.", CreateSelfContainedTestGraph("math/quatAngleBetween", In(a2, b2), Out(math.PI), ComparisonType.Equals)); + + var a3 = quaternion.Euler(math.PI * 0.5f, math.PI * 0.5f, 0f).ToFloat4(); + var b3 = quaternion.Euler(-math.PI * 0.5f, 0f, 0f).ToFloat4(); + + QueueTest("math/quatAngleBetween", "Quat_Angle_Between_xy", "Quaternion Angle Between (x/y rotations)", "Tests quaternion angle between operation with the xy axes.", CreateSelfContainedTestGraph("math/quatAngleBetween", In(a3, b3), Out(math.PI), ComparisonType.Equals)); + } + + [Test] + public void TestQuatFromAxisAngle() + { + var axis = new float3(1f, 0f, 0f); + var angle = math.PI * 0.5f; + var expected = quaternion.Euler(math.PI * 0.5f, 0f, 0f).ToFloat4(); + + QueueTest("math/quatFromAxisAngle", "Quat_From_Axis_Angle_X", "Quaternion From Axis Angle X", "Tests quaternion from axis angle.", QuatFromAxisAngle(axis, angle, expected)); + + var axis2 = new float3(0f, 1f, 0f); + var angle2 = math.PI * 0.5f; + var expected2 = quaternion.Euler(0f, math.PI * 0.5f, 0f).ToFloat4(); + + QueueTest("math/quatFromAxisAngle", "Quat_From_Axis_Angle_Y", "Quaternion From Axis Angle Y", "Tests quaternion from axis angle.", QuatFromAxisAngle(axis2, angle2, expected2)); + + var axis3 = new float3(0f, 0f, 1f); + var angle3 = math.PI * 0.5f; + var expected3 = quaternion.Euler(0f, 0f, math.PI * 0.5f).ToFloat4(); + + QueueTest("math/quatFromAxisAngle", "Quat_From_Axis_Angle_Z", "Quaternion From Axis Angle Z", "Tests quaternion from axis angle.", QuatFromAxisAngle(axis3, angle3, expected3)); + } + + private static (Graph, TestValues) QuatFromAxisAngle(float3 axis, float angle, float4 expected) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.AXIS, new Value() { id = ConstStrings.A, property = new Property(axis) }); + inputs.Add(ConstStrings.ANGLE, new Value() { id = ConstStrings.B, property = new Property(angle) }); + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + return CreateSelfContainedTestGraph("math/quatFromAxisAngle", inputs, outputs, ComparisonType.Equals); + } } } \ No newline at end of file From 282680a5c3910c9be489fe9ae05eebe1aa69943a Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 11 Jun 2025 11:20:07 -0400 Subject: [PATCH 19/28] Added tests for remaining quaternion nodes. --- .../Nodes/Math/MathNodesTests.cs | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index b9e1dca64..7ea54d6b0 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -1086,11 +1086,75 @@ private static (Graph, TestValues) QuatFromAxisAngle(float3 axis, float angle, f var inputs = new Dictionary(); var outputs = new Dictionary(); - inputs.Add(ConstStrings.AXIS, new Value() { id = ConstStrings.A, property = new Property(axis) }); - inputs.Add(ConstStrings.ANGLE, new Value() { id = ConstStrings.B, property = new Property(angle) }); + inputs.Add(ConstStrings.AXIS, new Value() { id = ConstStrings.AXIS, property = new Property(axis) }); + inputs.Add(ConstStrings.ANGLE, new Value() { id = ConstStrings.ANGLE, property = new Property(angle) }); outputs.Add(ConstStrings.VALUE, new Property(expected)); return CreateSelfContainedTestGraph("math/quatFromAxisAngle", inputs, outputs, ComparisonType.Equals); } + + [Test] + public void TestQuatToAxisAngle() + { + var axis = new float3(1f, 0f, 0f); + var angle = math.PI * 0.5f; + var q = quaternion.Euler(math.PI * 0.5f, 0f, 0f).ToFloat4(); + + QueueTest("math/quatToAxisAngle", "Quat_To_Axis_Angle_X", "Quaternion To Axis Angle X", "Tests quaternion to axis angle.", QuatToAxisAngle(q, axis, angle)); + + var axis2 = new float3(0f, 1f, 0f); + var angle2 = math.PI * 0.5f; + var q2 = quaternion.Euler(0f, math.PI * 0.5f, 0f).ToFloat4(); + + QueueTest("math/quatToAxisAngle", "Quat_To_Axis_Angle_Y", "Quaternion To Axis Angle Y", "Tests quaternion to axis angle.", QuatToAxisAngle(q2, axis2, angle2)); + + var axis3 = new float3(0f, 0f, 1f); + var angle3 = math.PI * 0.5f; + var q3 = quaternion.Euler(0f, 0f, math.PI * 0.5f).ToFloat4(); + + QueueTest("math/quatToAxisAngle", "Quat_To_Axis_Angle_Z", "Quaternion To Axis Angle Z", "Tests quaternion to axis angle.", QuatToAxisAngle(q3, axis3, angle3)); + } + + private static (Graph, TestValues) QuatToAxisAngle(float4 quaternion, float3 axis, float angle) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(quaternion) }); + outputs.Add(ConstStrings.AXIS, new Property(axis)); + outputs.Add(ConstStrings.ANGLE, new Property(angle)); + + return CreateSelfContainedTestGraph("math/quatToAxisAngle", inputs, outputs, ComparisonType.Equals); + } + + [Test] + public void TestQuatFromDirections() + { + var a = new float3(1f, 0f, 0f); + var b = new float3(0f, 1f, 0f); + + var q = quaternion.Euler(0f, 0f, math.PI * 0.5f).ToFloat4(); + + QueueTest("math/quatFromDirections", "Quat_From_Directions_XY", "Quaternion From Directions (XY)", "Tests quaternion from directions.", QuatFromDirections(a, b, q)); + + var a2 = new float3(0f, 1f, 0f); + var b2 = new float3(0f, 0f, 1f); + + var q2 = quaternion.Euler(math.PI * 0.5f, 0f, 0f).ToFloat4(); + + QueueTest("math/quatFromDirections", "Quat_From_Directions_YZ", "Quaternion From Directions (YZ)", "Tests quaternion from directions.", QuatFromDirections(a2, b2, q2)); + + var a3 = new float3(0f, 0f, 1f); + var b3 = new float3(1f, 0f, 0f); + + var q3 = quaternion.Euler(0f, math.PI * 0.5f, 0f).ToFloat4(); + + QueueTest("math/quatFromDirections", "Quat_From_Directions_ZX", "Quaternion From Directions (ZX)", "Tests quaternion from directions.", QuatFromDirections(a3, b3, q3)); + } + + private static (Graph, TestValues) QuatFromDirections(float3 a, float3 b, float4 expected) + { + return CreateSelfContainedTestGraph("math/quatFromDirections", In(a, b), Out(expected), ComparisonType.Equals); + } } } \ No newline at end of file From 3781bf557f6819762edd15bea44d80235489e2d0 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 11 Jun 2025 11:29:37 -0400 Subject: [PATCH 20/28] Fixed description of a quat mul test. --- Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 7ea54d6b0..1ebbe3cc8 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -1030,7 +1030,7 @@ public void TestQuatMul() new float4(1, 0, 0, 0), new float4(0, 1, 0, 0))); - QueueTest("math/quatMul", "Quat_Mul_xx_negx", "Quaternion Multiplication (x*x=-x)", "Tests that x*x=-x.", QuatMulTest(new float4(1, 0, 0, 0), + QueueTest("math/quatMul", "Quat_Mul_xx_negw", "Quaternion Multiplication (x*x=-w)", "Tests that x*x results in -w.", QuatMulTest(new float4(1, 0, 0, 0), new float4(1, 0, 0, 0), new float4(0, 0, 0, -1))); } From 9deb5c1f1dddf6224a3889c95eceb1f950a122e6 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Mon, 23 Jun 2025 13:07:55 -0400 Subject: [PATCH 21/28] Renamed "b" values in math/rotate2D and math/rotate3D to match newest spec change. --- .../Interactivity/Playback/NodeRegistry.cs | 2 +- .../Playback/NodeSpecs/Math/Rotate2D.cs | 30 +++++++++++++++++++ .../Playback/NodeSpecs/Math/Rotate2D.cs.meta | 2 ++ .../Playback/NodeSpecs/Math/Rotate3D.cs | 2 +- .../Playback/Nodes/Math/Rotate2D.cs | 2 +- .../Playback/Nodes/Math/Rotate3D.cs | 2 +- .../Nodes/Math/MathNodesTests.cs | 4 +-- 7 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs create mode 100644 Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs.meta diff --git a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs index 97049f863..12efb631f 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs @@ -271,7 +271,7 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/normalize"] = new MathOneOperandSpec(), ["math/or"] = new MathTwoOperandsSpec(), ["math/pi"] = new MathConstantSpec(), - ["math/rotate2D"] = new MathTwoOperandsRetSpec(), + ["math/rotate2D"] = new MathRotate2DSpec(), ["math/rotate3D"] = new MathRotate3DSpec(), ["math/round"] = new MathOneOperandFloatSpec(), ["math/sign"] = new MathOneOperandSpec(), diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs new file mode 100644 index 000000000..7fa19b504 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs @@ -0,0 +1,30 @@ +using System; +using Unity.Mathematics; +using UnityEngine; + +namespace UnityGLTF.Interactivity.Playback +{ + public class MathRotate2DSpec : NodeSpecifications + { + protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.A, "Vector to Rotate", new Type[] { typeof(float2)}), + new NodeValue(ConstStrings.ANGLE, "Angle in Radians", new Type[] { typeof(float)}), + }; + + return (null, values); + } + + protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() + { + var values = new NodeValue[] + { + new NodeValue(ConstStrings.VALUE, "Rotated Vector", new Type[] { typeof(float2) }), + }; + + return (null, values); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs.meta b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs.meta new file mode 100644 index 000000000..272ab7241 --- /dev/null +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate2D.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6263f9b04f1d5d448843de04a0f7bc9b \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs index c48b50763..f2a859987 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/Rotate3D.cs @@ -11,7 +11,7 @@ protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() var values = new NodeValue[] { new NodeValue(ConstStrings.A, "Vector to Rotate", new Type[] { typeof(float3)}), - new NodeValue(ConstStrings.B, "Rotation Quaternion", new Type[] { typeof(float4)}), + new NodeValue(ConstStrings.ROTATION, "Rotation Quaternion", new Type[] { typeof(float4)}), }; return (null, values); diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs index d148c25a9..4b4225095 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate2D.cs @@ -13,7 +13,7 @@ public MathRotate2D(BehaviourEngine engine, Node node) : base(engine, node) public override IProperty GetOutputValue(string id) { TryEvaluateValue(ConstStrings.A, out IProperty a); - TryEvaluateValue(ConstStrings.B, out IProperty b); + TryEvaluateValue(ConstStrings.ANGLE, out IProperty b); return a switch { diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs index f3ee6563e..ed1e3b457 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/Rotate3D.cs @@ -14,7 +14,7 @@ public MathRotate3D(BehaviourEngine engine, Node node) : base(engine, node) public override IProperty GetOutputValue(string id) { TryEvaluateValue(ConstStrings.A, out IProperty a); - TryEvaluateValue(ConstStrings.B, out IProperty b); + TryEvaluateValue(ConstStrings.ROTATION, out IProperty b); return a switch { diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 1ebbe3cc8..56eb7d3af 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -912,7 +912,7 @@ private static void RotateTest3D(string fileName, string testName, string var outputs = new Dictionary(); inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(a) }); - inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); + inputs.Add(ConstStrings.ROTATION, new Value() { id = ConstStrings.ROTATION, property = new Property(b) }); outputs.Add(ConstStrings.VALUE, new Property(expected)); QueueTest(nodeName, fileName, testName, testDescription, CreateSelfContainedTestGraph(nodeName, inputs, outputs, ComparisonType.Approximately)); @@ -931,7 +931,7 @@ private static void RotateTest2D(string fileName, string testName, string var outputs = new Dictionary(); inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(a) }); - inputs.Add(ConstStrings.B, new Value() { id = ConstStrings.B, property = new Property(b) }); + inputs.Add(ConstStrings.ANGLE, new Value() { id = ConstStrings.ANGLE, property = new Property(b) }); outputs.Add(ConstStrings.VALUE, new Property(expected)); QueueTest(nodeName, fileName, testName, testDescription, CreateSelfContainedTestGraph(nodeName, inputs, outputs, ComparisonType.Approximately)); From 9fc86bdf78205d0ea36f47f3cdc0884c3d5649a0 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Mon, 23 Jun 2025 15:54:23 -0400 Subject: [PATCH 22/28] Invalid pointers no longer throw. --- .../Interactivity/Playback/BehaviourEngine.cs | 7 +++---- .../Playback/Data/Helpers/Pointers.cs | 10 +++++++++- .../Playback/Pointers/ActiveCameraPointers.cs | 2 +- .../Playback/Pointers/AnimationPointers.cs | 5 +++-- .../Playback/Pointers/CameraPointers.cs | 9 +++++---- .../Playback/Pointers/Materials/BaseColor.cs | 2 +- .../Playback/Pointers/Materials/Clearcoat.cs | 4 ++-- .../Pointers/Materials/ClearcoatRoughness.cs | 2 +- .../Playback/Pointers/Materials/Emissive.cs | 2 +- .../Pointers/Materials/Iridescence.cs | 4 ++-- .../Materials/IridescenceThickness.cs | 2 +- .../Pointers/Materials/MaterialPointers.cs | 15 +++++++------- .../Pointers/Materials/MetallicRoughness.cs | 2 +- .../Playback/Pointers/Materials/Normal.cs | 2 +- .../Playback/Pointers/Materials/Occlusion.cs | 2 +- .../Playback/Pointers/Materials/Sheen.cs | 4 ++-- .../Pointers/Materials/SheenRoughness.cs | 2 +- .../Playback/Pointers/Materials/Specular.cs | 4 ++-- .../Pointers/Materials/SpecularColor.cs | 2 +- .../Playback/Pointers/Materials/Thickness.cs | 4 ++-- .../Pointers/Materials/Transmission.cs | 4 ++-- .../Playback/Pointers/MeshPointers.cs | 5 +++-- .../Playback/Pointers/NodePointers.cs | 7 ++++--- .../Playback/Pointers/PointerResolver.cs | 20 ++++++++++++++++++- .../Playback/Pointers/Pointers.cs | 4 ++++ 25 files changed, 80 insertions(+), 46 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs index 08cd41008..ae9bb67c3 100644 --- a/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngine.cs @@ -103,15 +103,14 @@ public bool TryGetPointer(string pointerString, BehaviourEngineNode engineNode, try { pointer = pointerResolver.GetPointer(pointerString, engineNode); - return true; } catch (Exception ex) { - Debug.LogWarning(ex); + Debug.LogException(ex); // Feeding in a really malformed pointerString could throw maybe? - pointer = default; - return false; + pointer = PointerHelpers.InvalidPointer(); } + return !pointer.invalid; } public void SetAnimationWrapper(GLTFInteractivityAnimationWrapper wrapper, Animation animation) diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs index 3e7587bd1..a79d66cd8 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Pointers.cs @@ -4,10 +4,18 @@ using Unity.Mathematics; using UnityEngine; -namespace UnityGLTF.Interactivity.Playback.Materials +namespace UnityGLTF.Interactivity.Playback { public static class PointerHelpers { + public static Pointer InvalidPointer() + { + return new Pointer() + { + invalid = true + }; + } + public static Pointer CreatePointer(Action setter, Func getter, Func evaluator) { return new Pointer() diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs index efa4a71ba..89429e5e8 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/ActiveCameraPointers.cs @@ -45,7 +45,7 @@ public IPointer ProcessActiveCameraPointer(StringSpanReader reader) { var a when a.Is("translation") => translation, var a when a.Is("rotation") => rotation, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs index 2fad65304..0de8ca2db 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/AnimationPointers.cs @@ -25,7 +25,8 @@ public static IPointer ProcessPointer(StringSpanReader reader, BehaviourEngineNo { reader.AdvanceToNextToken('/'); - var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + if (!PointerResolver.TryGetIndexFromArgument(reader, engineNode, pointers, out int nodeIndex)) + return PointerHelpers.InvalidPointer(); var pointer = pointers[nodeIndex]; @@ -41,7 +42,7 @@ var a when a.Is(Pointers.MIN_TIME) => pointer.minTime, var a when a.Is(Pointers.MAX_TIME) => pointer.maxTime, var a when a.Is(Pointers.PLAYHEAD) => pointer.playhead, var a when a.Is(Pointers.VIRTUAL_PLAYHEAD) => pointer.virtualPlayhead, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs index cb8d4e807..444c51f2e 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/CameraPointers.cs @@ -69,7 +69,8 @@ public static IPointer ProcessCameraPointer(StringSpanReader reader, BehaviourEn { reader.AdvanceToNextToken('/'); - var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + if (!PointerResolver.TryGetIndexFromArgument(reader, engineNode, pointers, out int nodeIndex)) + return PointerHelpers.InvalidPointer(); var pointer = pointers[nodeIndex]; @@ -80,7 +81,7 @@ public static IPointer ProcessCameraPointer(StringSpanReader reader, BehaviourEn { var a when a.Is("orthographic") => ProcessOrthographicPointer(reader, pointer), var a when a.Is("perspective") => ProcessPerspectivePointer(reader, pointer), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -95,7 +96,7 @@ var a when a.Is("aspectRatio") => pointer.perspectiveAspectRatio, var a when a.Is("yfov") => pointer.perspectiveYFov, var a when a.Is("zfar") => pointer.zFar, var a when a.Is("znear") => pointer.zNear, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -110,7 +111,7 @@ var a when a.Is("xmag") => pointer.orthographicXMag, var a when a.Is("ymag") => pointer.orthographicYMag, var a when a.Is("zfar") => pointer.zFar, var a when a.Is("znear") => pointer.zNear, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs index 7f9c7f610..3fc0f7923 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/BaseColor.cs @@ -41,7 +41,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, BaseCol var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs index 75cf57051..0ed092432 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Clearcoat.cs @@ -42,7 +42,7 @@ var a when a.Is("clearcoatRoughnessTexture") => ClearcoatRoughnessPointers.Proce // TODO: This property is not mentioned anywhere in the PBRGraph UnityGLTF shader so I didn't include it. //var a when a.Is("clearcoatNormalTexture") => , - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -58,7 +58,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Clearco var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs index ceb86d8a7..be6e2c80e 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/ClearcoatRoughness.cs @@ -39,7 +39,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Clearco var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs index 4aae7ca9c..0c159bbbb 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Emissive.cs @@ -39,7 +39,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Emissiv var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs index b8100ad68..8ada4b761 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Iridescence.cs @@ -44,7 +44,7 @@ var a when a.Is("iridescenceThicknessMaximum") => matPointer.iridescenceThicknes var a when a.Is("iridescenceTexture") => ProcessExtensionsPointer(reader, matPointer.iridescencePointers), var a when a.Is("iridescenceThicknessTexture") => IridescenceThicknessPointers.ProcessExtensionsPointer(reader, matPointer.iridescenceThicknessPointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -60,7 +60,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Iridesc var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs index a0eeaea78..b3e3e540b 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/IridescenceThickness.cs @@ -42,7 +42,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Iridesc var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs index cf912aff2..8aab7a144 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MaterialPointers.cs @@ -78,7 +78,8 @@ public static IPointer ProcessMaterialPointer(StringSpanReader reader, Behaviour { reader.AdvanceToNextToken('/'); - var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + if (!PointerResolver.TryGetIndexFromArgument(reader, engineNode, pointers, out int nodeIndex)) + return PointerHelpers.InvalidPointer(); var pointer = pointers[nodeIndex]; @@ -94,7 +95,7 @@ var a when a.Is("occlusionTexture") => ProcessOcclusionMapPointer(reader, pointe var a when a.Is("emissiveTexture") => ProcessEmissiveMapPointer(reader, pointer), var a when a.Is("pbrMetallicRoughness") => ProcessPBRMetallicRoughnessPointer(reader, pointer), var a when a.Is("extensions") => ProcessExtensionPointer(reader, pointer), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -110,7 +111,7 @@ var a when a.Is("baseColorTexture") => BaseColorPointers.ProcessExtensionsPointe var a when a.Is("metallicRoughnessTexture") => MetallicRoughnessPointers.ProcessExtensionsPointer(reader, matPointer.metallicRoughnessPointers), var a when a.Is("metallicFactor") => matPointer.metallicRoughnessPointers.metallicFactor, var a when a.Is("roughnessFactor") => matPointer.metallicRoughnessPointers.roughnessFactor, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -123,7 +124,7 @@ private static IPointer ProcessOcclusionMapPointer(StringSpanReader reader, Mate { var a when a.Is("strength") => matPointer.occlusionPointers.occlusionStrength, var a when a.Is("extensions") => OcclusionPointers.ProcessExtensionsPointer(reader, matPointer.occlusionPointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -136,7 +137,7 @@ private static IPointer ProcessNormalMapPointer(StringSpanReader reader, Materia { var a when a.Is("scale") => matPointer.normalPointers.normalScale, var a when a.Is("extensions") => NormalPointers.ProcessExtensionsPointer(reader, matPointer.normalPointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -148,7 +149,7 @@ private static IPointer ProcessEmissiveMapPointer(StringSpanReader reader, Mater return reader.AsReadOnlySpan() switch { var a when a.Is("extensions") => EmissivePointers.ProcessExtensionsPointer(reader, matPointer.emissivePointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -168,7 +169,7 @@ var a when a.Is("KHR_materials_specular") => SpecularPointers.ProcessPointer(rea var a when a.Is("KHR_materials_transmission") => TransmissionPointers.ProcessPointer(reader, matPointer), var a when a.Is("KHR_materials_volume") => ThicknessPointers.ProcessPointer(reader, matPointer), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs index ba39309c0..2a0a79405 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/MetallicRoughness.cs @@ -44,7 +44,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Metalli var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs index f4bdfc487..5a6dc82f6 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Normal.cs @@ -41,7 +41,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, NormalP var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs index f849bad54..37dfd80ae 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Occlusion.cs @@ -39,7 +39,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Occlusi var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs index 15e2f53b5..7d68051db 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Sheen.cs @@ -39,7 +39,7 @@ var a when a.Is("sheenRoughnessFactor") => matPointer.sheenRoughnessPointers.fac var a when a.Is("sheenColorTexture") => ProcessExtensionsPointer(reader, matPointer.sheenPointers), var a when a.Is("sheenRoughnessTexture") => SheenRoughnessPointers.ProcessExtensionsPointer(reader, matPointer.sheenRoughnessPointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -55,7 +55,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, SheenPo var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs index afa73ca2f..941375fa2 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SheenRoughness.cs @@ -39,7 +39,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, SheenRo var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs index 889e17cd1..916a19235 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Specular.cs @@ -39,7 +39,7 @@ var a when a.Is("specularColorFactor") => matPointer.specularColorPointers.specu var a when a.Is("specularTexture") => ProcessExtensionsPointer(reader, matPointer.specularPointers), var a when a.Is("specularColorTexture") => SpecularColorPointers.ProcessExtensionsPointer(reader, matPointer.specularColorPointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -55,7 +55,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Specula var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs index 2e0fad757..b5ae7d109 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/SpecularColor.cs @@ -39,7 +39,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Specula var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs index 2f8515f86..c4614c5a9 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Thickness.cs @@ -39,7 +39,7 @@ var a when a.Is("attenuationDistance") => matPointer.attenuationDistance, var a when a.Is("attenuationColor") => matPointer.attenuationColor, var a when a.Is("thicknessTexture") => ProcessExtensionsPointer(reader, matPointer.thicknessPointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -55,7 +55,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Thickne var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs index 578a0f62b..fdcba2f55 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Materials/Transmission.cs @@ -37,7 +37,7 @@ public static IPointer ProcessPointer(StringSpanReader reader, MaterialPointers var a when a.Is("transmissionFactor") => matPointer.transmissionPointers.transmissionFactor, var a when a.Is("transmissionTexture") => ProcessExtensionsPointer(reader, matPointer.transmissionPointers), - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -53,7 +53,7 @@ public static IPointer ProcessExtensionsPointer(StringSpanReader reader, Transmi var a when a.Is("offset") => pointers.transformPointers.offset, var a when a.Is("rotation") => pointers.transformPointers.rotation, var a when a.Is("scale") => pointers.transformPointers.scale, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs index f19fb3eeb..37b1ccc2c 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/MeshPointers.cs @@ -67,7 +67,8 @@ public static IPointer ProcessPointer(StringSpanReader reader, BehaviourEngineNo { reader.AdvanceToNextToken('/'); - var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + if (!PointerResolver.TryGetIndexFromArgument(reader, engineNode, pointers, out int nodeIndex)) + return PointerHelpers.InvalidPointer(); var pointer = pointers[nodeIndex]; @@ -78,7 +79,7 @@ public static IPointer ProcessPointer(StringSpanReader reader, BehaviourEngineNo { var a when a.Is(Pointers.WEIGHTS) => ProcessWeightsPointer(reader, engineNode, pointer), var a when a.Is(Pointers.WEIGHTS_LENGTH) => pointer.weightsLength, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs index 369be2bf7..56a1a3efe 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/NodePointers.cs @@ -118,7 +118,8 @@ public static IPointer ProcessNodePointer(StringSpanReader reader, BehaviourEngi { reader.AdvanceToNextToken('/'); - var nodeIndex = PointerResolver.GetIndexFromArgument(reader, engineNode); + if (!PointerResolver.TryGetIndexFromArgument(reader, engineNode, pointers, out int nodeIndex)) + return PointerHelpers.InvalidPointer(); var nodePointer = pointers[nodeIndex]; @@ -135,7 +136,7 @@ var a when a.Is(Pointers.WEIGHTS_LENGTH) => nodePointer.weightsLength, var a when a.Is(Pointers.EXTENSIONS) => ProcessExtensionPointer(reader, nodePointer), var a when a.Is(Pointers.MATRIX) => nodePointer.matrix, var a when a.Is(Pointers.GLOBAL_MATRIX) => nodePointer.globalMatrix, - _ => throw new InvalidOperationException($"Property {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -150,7 +151,7 @@ private static IPointer ProcessExtensionPointer(StringSpanReader reader, NodePoi var a when a.Is(GLTF.Schema.KHR_node_selectability_Factory.EXTENSION_NAME) => nodePointer.selectability, var a when a.Is(GLTF.Schema.KHR_node_visibility_Factory.EXTENSION_NAME) => nodePointer.visibility, var a when a.Is(GLTF.Schema.KHR_node_hoverability_Factory.EXTENSION_NAME) => nodePointer.hoverability, - _ => throw new InvalidOperationException($"Extension {reader.ToString()} is unsupported at this time!"), + _ => PointerHelpers.InvalidPointer(), }; } diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs index 86098415b..4f4715bd8 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/PointerResolver.cs @@ -27,6 +27,8 @@ public class PointerResolver public IReadOnlyList nodes => _nodes; public ReadOnlyCollection nodePointers { get; private set; } + private Dictionary _pointerCache = new(); + public void RegisterMesh(GLTF.Schema.GLTFMesh mesh, int meshIndex, Mesh unityMesh) { _meshes.Add(new MeshData(mesh, meshIndex, unityMesh)); @@ -186,7 +188,7 @@ var a when a.Is(Pointers.ANIMATIONS_LENGTH) => _scenePointers.animationsLength, var a when a.Is(Pointers.MATERIALS_LENGTH) => _scenePointers.materialsLength, var a when a.Is(Pointers.MESHES_LENGTH) => _scenePointers.meshesLength, var a when a.Is(Pointers.NODES_LENGTH) => _scenePointers.nodesLength, - _ => throw new InvalidOperationException($"No valid pointer found with name {reader.ToString()}"), + _ => PointerHelpers.InvalidPointer(), }; } @@ -208,5 +210,21 @@ public static int GetIndexFromArgument(StringSpanReader reader, BehaviourEngineN return nodeIndex; } + + public static bool TryGetIndexFromArgument(StringSpanReader reader, BehaviourEngineNode engineNode, IReadOnlyList list, out int index) + { + index = -1; + try + { + index = GetIndexFromArgument(reader, engineNode); + } + catch + { + Debug.LogWarning("Could not resolve index parameter in this pointer."); + return false; + } + + return index >= 0 && index < list.Count; + } } } \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs b/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs index b3ad6bd60..2838f255f 100644 --- a/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs +++ b/Runtime/Scripts/Interactivity/Playback/Pointers/Pointers.cs @@ -4,6 +4,7 @@ namespace UnityGLTF.Interactivity.Playback { public interface IPointer { + public bool invalid { get; } public Type GetSystemType(); public string GetTypeSignature(); } @@ -22,10 +23,12 @@ public interface IReadOnlyPointer : IReadOnlyPointer public struct ReadOnlyPointer : IReadOnlyPointer { + public bool invalid { get; set; } public Func getter; public ReadOnlyPointer(Func getter) { + invalid = false; this.getter = getter; } @@ -55,6 +58,7 @@ public struct Pointer : IPointer public Action setter; public Func getter; public Func evaluator; + public bool invalid { get; set; } public T GetValue() { From d007cef2de796e2d12658a3df88e150fcc44e187 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Tue, 24 Jun 2025 11:55:15 -0400 Subject: [PATCH 23/28] Optimized node configuration to parse into its final type during the deserialization stage rather than each time the node with that configuration runs. Reduces memory alllocations for graphs with heavy use of pointers. --- .../Playback/BehaviourEngineNode.cs | 2 +- .../Playback/Data/Deserializers/Nodes.cs | 38 ++++++++++++++++++- .../Helpers/Extensions/BehaviourEngineNode.cs | 4 +- .../Interactivity/Playback/Data/Model/Node.cs | 9 +---- .../Playback/Data/Model/Various.cs | 3 +- .../Playback/Data/Serializers/Nodes.cs | 35 ++++++++++++++--- .../Playback/Nodes/Event/OnHoverIn.cs | 2 +- .../Playback/Nodes/Event/OnHoverOut.cs | 2 +- .../Playback/Nodes/Event/OnSelect.cs | 2 +- .../Interactivity/Playback/Nodes/Flow/For.cs | 5 ++- 10 files changed, 81 insertions(+), 21 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs index 26d8590a7..ed23fb64c 100644 --- a/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs +++ b/Runtime/Scripts/Interactivity/Playback/BehaviourEngineNode.cs @@ -105,7 +105,7 @@ public bool TryGetConfig(string id, out T value) { try { - value = ((Property)Helpers.CreateProperty(typeof(T), config.value)).value; + value = ((Property)config.property).value; return true; } catch (Exception e) diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs index 8bc5f99c7..3ab5eed9c 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Deserializers/Nodes.cs @@ -148,10 +148,12 @@ private static List GetConfiguration(JToken jToken) foreach (var v in jConfiguration) { + var parsedSuccessfully = TryGetPropertyFromConfigEntry(v.Key, v.Value[ConstStrings.VALUE] as JArray, out IProperty property); configuration.Add(new Configuration() { id = v.Key, - value = v.Value[ConstStrings.VALUE] as JArray + property = property, + parsedSuccessfully = parsedSuccessfully }); } @@ -169,5 +171,39 @@ private static Metadata GetMetadata(JToken jToken) positionY = double.Parse(jToken["positionY"].Value()), }; } + + private static bool TryGetPropertyFromConfigEntry(string id, JArray value, out IProperty property) + { + try + { + property = id switch + { + ConstStrings.POINTER => Helpers.CreateProperty(typeof(string), value), + ConstStrings.MESSAGE => Helpers.CreateProperty(typeof(string), value), + ConstStrings.VARIABLE => Helpers.CreateProperty(typeof(int), value), + ConstStrings.USE_SLERP => Helpers.CreateProperty(typeof(bool), value), + ConstStrings.IS_LOOP => Helpers.CreateProperty(typeof(bool), value), + ConstStrings.IS_RANDOM => Helpers.CreateProperty(typeof(bool), value), + ConstStrings.STOP_PROPAGATION => Helpers.CreateProperty(typeof(bool), value), + ConstStrings.CASES => Helpers.CreateProperty(typeof(int[]), value), + ConstStrings.VARIABLES => Helpers.CreateProperty(typeof(int[]), value), + ConstStrings.INPUT_FLOWS => Helpers.CreateProperty(typeof(int), value), + ConstStrings.INITIAL_INDEX => Helpers.CreateProperty(typeof(int), value), + ConstStrings.TYPE => Helpers.CreateProperty(typeof(int), value), + ConstStrings.NODE_INDEX => Helpers.CreateProperty(typeof(int), value), + ConstStrings.EVENT => Helpers.CreateProperty(typeof(int), value), + ConstStrings.SEVERITY => Helpers.CreateProperty(typeof(int), value), + _ => throw new InvalidOperationException($"Config {id} is not supported!"), + }; + } + catch (Exception ex) + { + Debug.LogWarning(ex.Message); + property = default; + return false; + } + + return true; + } } } \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs index 1a3b1e7c1..7e34d4533 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Helpers/Extensions/BehaviourEngineNode.cs @@ -33,7 +33,7 @@ public bool TryGetPointerFromConfiguration(out IPointer pointer) if (!configuration.TryGetValue(ConstStrings.POINTER, out Configuration config)) return false; - var pointerPath = Parser.ToString(config.value); + var pointerPath = ((Property)config.property).value; return TryGetPointer(pointerPath, out pointer); } @@ -92,7 +92,7 @@ public bool TryGetVariableFromConfiguration(out Variable variable, out int index try { - index = Parser.ToInt(config.value); + index = ((Property)config.property).value; variable = engine.graph.variables[index]; } catch (Exception e) diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs index 9af9d5574..100c73746 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Node.cs @@ -160,25 +160,20 @@ public bool RemoveFlow(Flow flow) } public Configuration AddConfiguration(string id, T value) - { - return AddConfiguration(id, new JArray(value)); - } - - public Configuration AddConfiguration(string id, JArray value) { for (int i = 0; i < configuration.Count; i++) { if (!configuration[i].id.Equals(id)) continue; - configuration[i].value = value; + configuration[i].property = new Property(value); return configuration[i]; } var config = new Configuration() { id = id, - value = value, + property = new Property(value) }; configuration.Add(config); diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs index c162b5a61..fcc4f896d 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Various.cs @@ -47,7 +47,8 @@ public class Variable public class Configuration { public string id { get; set; } - public JArray value { get; set; } + public IProperty property { get; set; } + public bool parsedSuccessfully { get; set; } } public class InteractivityType diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs index 40d8f8416..dd78e0f1a 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs @@ -104,12 +104,8 @@ private static void WriteConfigurationEntry(JsonWriter writer, Configuration con writer.WritePropertyName(configuration.id); writer.WriteStartObject(); - writer.WritePropertyName(ConstStrings.VALUE); - if (configuration.value is JArray array) - array.WriteTo(writer); - else - writer.WriteValue(configuration.value); + WriteConfigLiteral(writer, configuration.property); writer.WriteEndObject(); } @@ -242,5 +238,34 @@ public static void WriteValueLiteral(JsonWriter writer, IProperty property, Dict writer.WritePropertyName(ConstStrings.TYPE); writer.WriteValue(type); } + + public static void WriteConfigLiteral(JsonWriter writer, IProperty property) + { + writer.WritePropertyName(ConstStrings.VALUE); + writer.WriteStartArray(); + + switch (property) + { + case Property iProp: + writer.WriteValue(iProp.value); + break; + case Property bProp: + writer.WriteValue(bProp.value); + break; + case Property p: + for (int i = 0; i < p.value.Length; i++) + { + writer.WriteValue(p.value[i]); + } + break; + case Property p: + writer.WriteValue(p.value); + break; + default: + throw new NotImplementedException(); + } + + writer.WriteEndArray(); + } } } \ No newline at end of file diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs index b44c2676a..dc1574078 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverIn.cs @@ -30,7 +30,7 @@ public EventOnHoverIn(BehaviourEngine engine, Node node) : base(engine, node) if (!configuration.TryGetValue(ConstStrings.NODE_INDEX, out Configuration config)) return; - var parentIndex = Parser.ToInt(config.value); + var parentIndex = ((Property)config.property).value; _parentNode = engine.pointerResolver.nodePointers[parentIndex].gameObject.transform; } diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs index bb3f5e7e7..812ac1642 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnHoverOut.cs @@ -21,7 +21,7 @@ public EventOnHoverOut(BehaviourEngine engine, Node node) : base(engine, node) if (!configuration.TryGetValue(ConstStrings.NODE_INDEX, out Configuration config)) return; - var parentIndex = Parser.ToInt(config.value); + var parentIndex = ((Property)config.property).value; _parentNode = engine.pointerResolver.nodePointers[parentIndex].gameObject.transform; } diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs index 42aad7f81..f9ad1f696 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Event/OnSelect.cs @@ -24,7 +24,7 @@ public EventOnSelect(BehaviourEngine engine, Node node) : base(engine, node) if (!configuration.TryGetValue(ConstStrings.NODE_INDEX, out Configuration config)) return; - var parentIndex = Parser.ToInt(config.value); + var parentIndex = ((Property)config.property).value; _parentNode = engine.pointerResolver.nodePointers[parentIndex].gameObject.transform; } diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs index 0b558d3c5..3f24c67ce 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Flow/For.cs @@ -12,7 +12,10 @@ public class FlowFor : BehaviourEngineNode public FlowFor(BehaviourEngine engine, Node node) : base(engine, node) { - _index = Parser.ToInt(configuration[ConstStrings.INITIAL_INDEX].value); + if (!configuration.TryGetValue(ConstStrings.INITIAL_INDEX, out Configuration config)) + return; + + _index = ((Property)config.property).value; } public override IProperty GetOutputValue(string socket) From 1532a9bd6a05b39a5732c9ed7deadf04215fe37a Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Tue, 24 Jun 2025 16:24:28 -0400 Subject: [PATCH 24/28] Removed string from default types. --- Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs index d37285141..de8579ff8 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Graph.cs @@ -144,7 +144,6 @@ public void AddDefaultTypes() typeof(float2x2), typeof(float3x3), typeof(float4x4), - typeof(string), }; @@ -159,7 +158,6 @@ public void AddDefaultTypes() new InteractivityType() { signature = "float2x2" }, new InteractivityType() { signature = "float3x3" }, new InteractivityType() { signature = "float4x4" }, - new InteractivityType() { signature = "string" }, }; } From b3079fba04c165247c156879e64c68db4e781d7d Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Mon, 14 Jul 2025 14:26:32 -0400 Subject: [PATCH 25/28] Tests now generate with Unix line endings. Fixed math tests to only have a single completed event node. --- .../Nodes/Common/NodeTestHelpers.cs | 171 ++++++++++++------ 1 file changed, 117 insertions(+), 54 deletions(-) diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs index e8b8a7eef..1c7e6b951 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -155,7 +155,13 @@ public IEnumerator TearDown() graphs = new List() { test.graph }, defaultGraphIndex = 0 }; - File.WriteAllText($"{_testGraphDirectory}/{test.fileName}.json", _serializer.Serialize(extension)); + using (var stream = File.OpenWrite($"{_testGraphDirectory}/{test.fileName}.json")) + using (var writer = new StreamWriter(stream)) + { + writer.NewLine = "\n"; // Use Unix line endings + writer.Write(_serializer.Serialize(extension)); + writer.WriteLine(); // Adds newline at the end + } } }); #endif @@ -251,90 +257,151 @@ private static void GenerateGraphByExpectedValueType(Dictionary: node = CreateExtractNode(g, "math/extract2", opNode, out value, out node, expected); var f2Val = ((Property)expected.Value).value; - for (int i = 0; i < 2; i++) - { - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(i), f2Val[i], subGraph); - } + + firstBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2Val[0], subGraph); + lastBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2Val[1], subGraph); + + firstBranch.AddFlow(lastBranch, ConstStrings.TRUE); + break; case Property: node = CreateExtractNode(g, "math/extract3", opNode, out value, out node, expected); var f3Val = ((Property)expected.Value).value; - for (int i = 0; i < 3; i++) - { - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(i), f3Val[i], subGraph); - } + + firstBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3Val[0], subGraph); + var b1 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3Val[1], subGraph); + lastBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3Val[2], subGraph); + + firstBranch.AddFlow(b1, ConstStrings.TRUE); + b1.AddFlow(lastBranch, ConstStrings.TRUE); + break; case Property: node = CreateExtractNode(g, "math/extract4", opNode, out value, out node, expected); var f4Val = ((Property)expected.Value).value; - for (int i = 0; i < 4; i++) - { - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(i), f4Val[i], subGraph); - } + firstBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4Val[0], subGraph); + var f4b1 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4Val[1], subGraph); + var f4b2 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4Val[2], subGraph); + + lastBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4Val[3], subGraph); + + firstBranch.AddFlow(f4b1, ConstStrings.TRUE); + f4b1.AddFlow(f4b2, ConstStrings.TRUE); + f4b2.AddFlow(lastBranch, ConstStrings.TRUE); + break; case Property: node = CreateExtractNode(g, "math/extract2x2", opNode, out value, out node, expected); var f2x2Val = ((Property)expected.Value).value; - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2x2Val.c0.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2x2Val.c0.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f2x2Val.c1.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f2x2Val.c1.y, subGraph); + + var branches = new Node[4]; + + branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2x2Val.c0.x, subGraph); + branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2x2Val.c0.y, subGraph); + branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f2x2Val.c1.x, subGraph); + branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f2x2Val.c1.y, subGraph); + + branches[0].AddFlow(branches[1], ConstStrings.TRUE); + branches[1].AddFlow(branches[2], ConstStrings.TRUE); + branches[2].AddFlow(branches[3], ConstStrings.TRUE); + + firstBranch = branches[0]; + lastBranch = branches[3]; break; case Property: node = CreateExtractNode(g, "math/extract3x3", opNode, out value, out node, expected); var f3x3Val = ((Property)expected.Value).value; - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3x3Val.c0.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3x3Val.c0.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3x3Val.c0.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f3x3Val.c1.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f3x3Val.c1.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f3x3Val.c1.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f3x3Val.c2.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f3x3Val.c2.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f3x3Val.c2.z, subGraph); + + var f3x3branches = new Node[9]; + + f3x3branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3x3Val.c0.x, subGraph); + f3x3branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3x3Val.c0.y, subGraph); + f3x3branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3x3Val.c0.z, subGraph); + + f3x3branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f3x3Val.c1.x, subGraph); + f3x3branches[4] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f3x3Val.c1.y, subGraph); + f3x3branches[5] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f3x3Val.c1.z, subGraph); + + f3x3branches[6] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f3x3Val.c2.x, subGraph); + f3x3branches[7] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f3x3Val.c2.y, subGraph); + f3x3branches[8] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f3x3Val.c2.z, subGraph); + + for (int i = 0; i < f3x3branches.Length - 1; i++) + { + f3x3branches[i].AddFlow(f3x3branches[i + 1], ConstStrings.TRUE); + } + + firstBranch = f3x3branches[0]; + lastBranch = f3x3branches[8]; break; + case Property: node = CreateExtractNode(g, "math/extract4x4", opNode, out value, out node, expected); var f4x4Val = ((Property)expected.Value).value; - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4x4Val.c0.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4x4Val.c0.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4x4Val.c0.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4x4Val.c0.w, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f4x4Val.c1.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f4x4Val.c1.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f4x4Val.c1.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f4x4Val.c1.w, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f4x4Val.c2.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(9), f4x4Val.c2.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(10), f4x4Val.c2.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(11), f4x4Val.c2.w, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(12), f4x4Val.c3.x, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(13), f4x4Val.c3.y, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(14), f4x4Val.c3.z, subGraph); - CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(15), f4x4Val.c3.w, subGraph); - break; + var f4x4branches = new Node[16]; + + f4x4branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4x4Val.c0.x, subGraph); + f4x4branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4x4Val.c0.y, subGraph); + f4x4branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4x4Val.c0.z, subGraph); + f4x4branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4x4Val.c0.w, subGraph); + + f4x4branches[4] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f4x4Val.c1.x, subGraph); + f4x4branches[5] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f4x4Val.c1.y, subGraph); + f4x4branches[6] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f4x4Val.c1.z, subGraph); + f4x4branches[7] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f4x4Val.c1.w, subGraph); + + f4x4branches[8] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f4x4Val.c2.x, subGraph); + f4x4branches[9] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(9), f4x4Val.c2.y, subGraph); + f4x4branches[10] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(10), f4x4Val.c2.z, subGraph); + f4x4branches[11] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(11), f4x4Val.c2.w, subGraph); + + f4x4branches[12] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(12), f4x4Val.c3.x, subGraph); + f4x4branches[13] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(13), f4x4Val.c3.y, subGraph); + f4x4branches[14] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(14), f4x4Val.c3.z, subGraph); + f4x4branches[15] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(15), f4x4Val.c3.w, subGraph); + + for (int i = 0; i < f4x4branches.Length - 1; i++) + { + f4x4branches[i].AddFlow(f4x4branches[i + 1], ConstStrings.TRUE); + } + + firstBranch = f4x4branches[0]; + lastBranch = f4x4branches[15]; + break; } } + onStart.AddFlow(firstBranch); + lastBranch.AddFlow(pass, ConstStrings.TRUE); + static Node CreateExtractNode(Graph g, string nodeName, Node opNode, out Value value, out Node node, KeyValuePair expected) { node = g.CreateNode(nodeName); @@ -345,20 +412,17 @@ static Node CreateExtractNode(Graph g, string nodeName, Node opNode, out Value v } } - private static void CreateSingleValueTestSubGraph(Graph g, Node outNode, string outSocket, T expected, ISubGraph subGraph) + private static Node CreateSingleValueTestSubGraph(Graph g, Node outNode, string outSocket, T expected, ISubGraph subGraph) { - CreateSingleValueTestSubGraph(g, outNode, outSocket, (IProperty)new Property(expected), subGraph); + return CreateSingleValueTestSubGraph(g, outNode, outSocket, (IProperty)new Property(expected), subGraph); } - private static void CreateSingleValueTestSubGraph(Graph g, Node outNode, string outSocket, IProperty expected, ISubGraph subGraph) + private static Node CreateSingleValueTestSubGraph(Graph g, Node outNode, string outSocket, IProperty expected, ISubGraph subGraph) { - var onStart = g.CreateNode("event/onStart"); - var pass = g.CreateNode("event/send"); var fail = g.CreateNode("event/send"); var failLog = g.CreateNode("debug/log"); fail.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); - pass.AddConfiguration(ConstStrings.EVENT, COMPLETED_EVENT_INDEX); failLog.AddConfiguration(ConstStrings.MESSAGE, $"Output {outSocket}" + ", Expected: {expected}, Actual: {actual}"); (var subIn, var subOut) = subGraph.CreateSubGraph(g); @@ -368,15 +432,14 @@ private static void CreateSingleValueTestSubGraph(Graph g, Node outNode, string if (subGraph.hasBValue) subIn.AddValue(ConstStrings.B, expected); - onStart.AddFlow(subOut, ConstStrings.OUT, ConstStrings.IN); - - subOut.AddFlow(pass, ConstStrings.TRUE, ConstStrings.IN); subOut.AddFlow(failLog, ConstStrings.FALSE, ConstStrings.IN); failLog.AddFlow(fail, ConstStrings.OUT, ConstStrings.IN); failLog.AddValue(ConstStrings.EXPECTED, expected); var failLogActualValue = failLog.AddValue(ConstStrings.ACTUAL, 0); failLogActualValue.TryConnectToSocket(outNode, outSocket); + + return subOut; } protected static BehaviourEngine CreateBehaviourEngineForGraph(Graph g, Action> onEventFired, GLTFSceneImporter importer, bool startPlayback) From 42573b1f5af10e7031cc545387f977c56b9e4b15 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 20 Aug 2025 16:34:07 -0400 Subject: [PATCH 26/28] Fix for any graphs exported with float2x2 values. Updated naming for various math nodes to match current spec. Removed indentation from generated test json. --- .../Playback/Data/Serializers/Nodes.cs | 2 +- .../Interactivity/Playback/NodeRegistry.cs | 28 +++++++++---------- .../Schema/Nodes/Math/MathNodes.cs | 14 +++++----- .../Nodes/Common/NodeTestHelpers.cs | 2 +- .../Interactivity/Nodes/Common/SubGraphs.cs | 4 +-- .../Nodes/Event/EventNodeTests.cs | 6 ++-- .../Nodes/Flow/FlowNodesTests.cs | 2 +- .../Interactivity/Nodes/Flow/ThrottleTests.cs | 2 +- .../Nodes/Math/MathNodesTests.cs | 22 +++++++-------- .../Nodes/Pointer/PointerNodesTests.cs | 2 +- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs index dd78e0f1a..1cd8e9487 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Serializers/Nodes.cs @@ -191,7 +191,7 @@ public static void WriteValueLiteral(JsonWriter writer, IProperty property, Dict writer.WriteValue(p.value.c0.x); writer.WriteValue(p.value.c0.y); writer.WriteValue(p.value.c1.x); - writer.WriteValue(p.value.c0.y); + writer.WriteValue(p.value.c1.y); type = typeIndexByType[typeof(float2x2)]; break; case Property p: diff --git a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs index 12efb631f..c1d8cee8c 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeRegistry.cs @@ -60,7 +60,7 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/cos"] = (engine, node) => new MathCos(engine, node), ["math/deg"] = (engine, node) => new MathDeg(engine, node), ["math/div"] = (engine, node) => new MathDiv(engine, node), - ["math/e"] = (engine, node) => new MathE(engine, node), + ["math/E"] = (engine, node) => new MathE(engine, node), ["math/eq"] = (engine, node) => new MathEq(engine, node), ["math/extract2"] = (engine, node) => new MathExtract2(engine, node), ["math/extract3"] = (engine, node) => new MathExtract3(engine, node), @@ -71,7 +71,7 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/floor"] = (engine, node) => new MathFloor(engine, node), ["math/ge"] = (engine, node) => new MathGe(engine, node), ["math/gt"] = (engine, node) => new MathGt(engine, node), - ["math/isnan"] = (engine, node) => new MathIsNaN(engine, node), + ["math/isNaN"] = (engine, node) => new MathIsNaN(engine, node), ["math/le"] = (engine, node) => new MathLe(engine, node), ["math/length"] = (engine, node) => new MathLength(engine, node), ["math/lsl"] = (engine, node) => new MathLsl(engine, node), @@ -124,21 +124,21 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/dot"] = (engine, node) => new MathDot(engine, node), ["math/exp"] = (engine, node) => new MathExp(engine, node), ["math/fract"] = (engine, node) => new MathFract(engine, node), - ["math/inf"] = (engine, node) => new MathInf(engine, node), + ["math/Inf"] = (engine, node) => new MathInf(engine, node), ["math/inverse"] = (engine, node) => new MathInverse(engine, node), - ["math/isinf"] = (engine, node) => new MathIsInf(engine, node), + ["math/isInf"] = (engine, node) => new MathIsInf(engine, node), ["math/log"] = (engine, node) => new MathLog(engine, node), ["math/log10"] = (engine, node) => new MathLog10(engine, node), ["math/log2"] = (engine, node) => new MathLog2(engine, node), ["math/matCompose"] = (engine, node) => new MathMatCompose(engine, node), ["math/matDecompose"] = (engine, node) => new MathMatDecompose(engine, node), - ["math/matmul"] = (engine, node) => new MathMatMul(engine, node), + ["math/matMul"] = (engine, node) => new MathMatMul(engine, node), ["math/min"] = (engine, node) => new MathMin(engine, node), ["math/max"] = (engine, node) => new MathMax(engine, node), - ["math/nan"] = (engine, node) => new MathNaN(engine, node), + ["math/NaN"] = (engine, node) => new MathNaN(engine, node), ["math/normalize"] = (engine, node) => new MathNormalize(engine, node), ["math/or"] = (engine, node) => new MathOr(engine, node), - ["math/pi"] = (engine, node) => new MathPi(engine, node), + ["math/Pi"] = (engine, node) => new MathPi(engine, node), ["math/rotate2D"] = (engine, node) => new MathRotate2D(engine, node), ["math/rotate3D"] = (engine, node) => new MathRotate3D(engine, node), ["math/round"] = (engine, node) => new MathRound(engine, node), @@ -197,7 +197,7 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/cos"] = new MathOneOperandFloatSpec(), ["math/deg"] = new MathOneOperandFloatSpec(), ["math/div"] = new MathTwoOperandsSpec(), - ["math/e"] = new MathConstantSpec(), + ["math/E"] = new MathConstantSpec(), ["math/eq"] = new MathTwoOperandsRetSpec(), ["math/supereq"] = new MathTwoOperandsRetSpec(), ["math/approxeq"] = new MathTwoOperandsRetSpec(), @@ -210,7 +210,7 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/floor"] = new MathOneOperandFloatSpec(), ["math/ge"] = new MathTwoOperandsRetSpec(), ["math/gt"] = new MathTwoOperandsRetSpec(), - ["math/isnan"] = new MathOneOperandRetSpec(), + ["math/isNaN"] = new MathOneOperandRetSpec(), ["math/le"] = new MathTwoOperandsRetSpec(), ["math/length"] = new MathOneOperandSpec(), ["math/lsl"] = new MathTwoOperandsSpec(), @@ -256,21 +256,21 @@ public static BehaviourEngineNode CreateBehaviourEngineNode(BehaviourEngine engi ["math/dot"] = new MathTwoOperandsRetSpec(), ["math/exp"] = new MathOneOperandFloatSpec(), ["math/fract"] = new MathOneOperandFloatSpec(), - ["math/inf"] = new MathConstantSpec(), + ["math/Inf"] = new MathConstantSpec(), ["math/inverse"] = new MathOneOperandSpec(), - ["math/isinf"] = new MathOneOperandRetSpec(), + ["math/isInf"] = new MathOneOperandRetSpec(), ["math/log"] = new MathOneOperandFloatSpec(), ["math/log10"] = new MathOneOperandFloatSpec(), ["math/log2"] = new MathOneOperandFloatSpec(), ["math/matCompose"] = new MathMatComposeSpec(), ["math/matDecompose"] = new MathMatDecomposeSpec(), - ["math/matmul"] = new MathTwoOperandsSpec(), + ["math/matMul"] = new MathTwoOperandsSpec(), ["math/min"] = new MathTwoOperandsSpec(), ["math/max"] = new MathTwoOperandsSpec(), - ["math/nan"] = new MathConstantSpec(), + ["math/NaN"] = new MathConstantSpec(), ["math/normalize"] = new MathOneOperandSpec(), ["math/or"] = new MathTwoOperandsSpec(), - ["math/pi"] = new MathConstantSpec(), + ["math/Pi"] = new MathConstantSpec(), ["math/rotate2D"] = new MathRotate2DSpec(), ["math/rotate3D"] = new MathRotate3DSpec(), ["math/round"] = new MathOneOperandFloatSpec(), diff --git a/Runtime/Scripts/Interactivity/Schema/Nodes/Math/MathNodes.cs b/Runtime/Scripts/Interactivity/Schema/Nodes/Math/MathNodes.cs index 6ad78310a..53d01bb02 100644 --- a/Runtime/Scripts/Interactivity/Schema/Nodes/Math/MathNodes.cs +++ b/Runtime/Scripts/Interactivity/Schema/Nodes/Math/MathNodes.cs @@ -20,7 +20,7 @@ public class Math_SwitchNode : GltfInteractivityNodeSchema public class Math_MatMulNode : GltfInteractivityNodeSchema { - public override string Op { get; set; } = "math/matmul"; + public override string Op { get; set; } = "math/matMul"; [OutputSocketDescriptionWithTypeDependencyFromInput(IdValueA)] public const string IdOut = "value"; @@ -697,7 +697,7 @@ public class Math_LengthNode : GltfInteractivityNodeSchema public class Math_IsNaNNode : GltfInteractivityNodeSchema { - public override string Op { get; set; } = "math/isnan"; + public override string Op { get; set; } = "math/isNaN"; [OutputSocketDescription(GltfTypes.Bool)] public const string IdOut = "value"; @@ -763,7 +763,7 @@ public class Math_CbrtNode : AbstractSameOneInOneOutNode public class Math_PiNode : GltfInteractivityNodeSchema { - public override string Op { get; set; } = "math/pi"; + public override string Op { get; set; } = "math/Pi"; [OutputSocketDescription(GltfTypes.Float)] public const string IdOut = "value"; @@ -772,7 +772,7 @@ public class Math_PiNode : GltfInteractivityNodeSchema public class Math_ENode : GltfInteractivityNodeSchema { - public override string Op { get; set; } = "math/e"; + public override string Op { get; set; } = "math/E"; [OutputSocketDescription(GltfTypes.Float)] public const string IdOut = "value"; @@ -781,7 +781,7 @@ public class Math_ENode : GltfInteractivityNodeSchema public class Math_InfNode : GltfInteractivityNodeSchema { - public override string Op { get; set; } = "math/inf"; + public override string Op { get; set; } = "math/Inf"; [OutputSocketDescription(GltfTypes.Float)] public const string IdOut = "value"; @@ -790,7 +790,7 @@ public class Math_InfNode : GltfInteractivityNodeSchema public class Math_NaNNode : GltfInteractivityNodeSchema { - public override string Op { get; set; } = "math/nan"; + public override string Op { get; set; } = "math/NaN"; [OutputSocketDescription(GltfTypes.Float)] public const string IdOut = "value"; @@ -900,7 +900,7 @@ public class Math_GtNode : GltfInteractivityNodeSchema public class Math_IsInfNode: GltfInteractivityNodeSchema { - public override string Op { get; set; } = "math/isinf"; + public override string Op { get; set; } = "math/isInf"; [InputSocketDescription(GltfTypes.Float)] public const string IdInputA = "a"; diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs index 1c7e6b951..1dfad38e3 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -78,7 +78,7 @@ public ManifestEntry CreateManifestEntry() protected const string TEST_GRAPH_SAVE_DIRECTORY = "TestGraphJson"; protected bool _testSuccessful = false; - private readonly GraphSerializer _serializer = new(Newtonsoft.Json.Formatting.Indented); + private readonly GraphSerializer _serializer = new(Newtonsoft.Json.Formatting.None); protected string _testGraphDirectory; protected abstract string _subDirectory { get; } diff --git a/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs b/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs index 286300819..65fb5ad0f 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/SubGraphs.cs @@ -67,7 +67,7 @@ public class IsNaNSubGraph : ISubGraph public (Node inputNode, Node outputNode) CreateSubGraph(Graph g) { - var eq = g.CreateNode("math/isnan"); + var eq = g.CreateNode("math/isNaN"); var branch = g.CreateNode("flow/branch"); var branchConditionValue = branch.AddValue(ConstStrings.CONDITION, true); @@ -84,7 +84,7 @@ public class IsInfSubGraph : ISubGraph public (Node inputNode, Node outputNode) CreateSubGraph(Graph g) { - var eq = g.CreateNode("math/isinf"); + var eq = g.CreateNode("math/isInf"); var branch = g.CreateNode("flow/branch"); var branchConditionValue = branch.AddValue(ConstStrings.CONDITION, true); diff --git a/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs index 043fc67a7..ee6ba3f74 100644 --- a/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Event/EventNodeTests.cs @@ -282,8 +282,8 @@ private static Graph CreateOnTickTestGraph() var onTick = g.CreateNode("event/onTick"); var complete = g.CreateNode("event/send"); var startAnd = g.CreateNode("math/and"); - var isTimeSinceStartNaN = g.CreateNode("math/isnan"); - var isTimeSinceLastTickNaN = g.CreateNode("math/isnan"); + var isTimeSinceStartNaN = g.CreateNode("math/isNaN"); + var isTimeSinceLastTickNaN = g.CreateNode("math/isNaN"); failStart.AddConfiguration(ConstStrings.EVENT, FAIL_EVENT_INDEX); failLog.AddConfiguration(ConstStrings.MESSAGE, "Both timeSinceStart and timeSinceLastTick should be NaN before onTick activates its first out flow. timeSinceStart: {timeSinceStart}, timeSinceLastTick: {timeSinceLastTick}"); @@ -337,7 +337,7 @@ private static Graph CreateOnTickTestGraph() var firstTickBranch = g.CreateNode("flow/branch"); var firstTickAnd = g.CreateNode("math/and"); - var firstTickIsNaN = g.CreateNode("math/isnan"); + var firstTickIsNaN = g.CreateNode("math/isNaN"); var firstTickEq = g.CreateNode("math/eq"); isFirstTickBranch.AddFlow(firstTickBranch, ConstStrings.TRUE); diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs index 9320ea82d..760c9b3a2 100644 --- a/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Flow/FlowNodesTests.cs @@ -267,7 +267,7 @@ private static Node CreateNaNCheckSubGraph(Graph g) { var start = g.CreateNode("event/onStart"); var branch = g.CreateNode("flow/branch"); - var isNaN = g.CreateNode("math/isnan"); + var isNaN = g.CreateNode("math/isNaN"); var failLog = g.CreateNode("debug/log"); var fail = g.CreateNode("event/send"); diff --git a/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs index 7379d7a48..2e8c5a5ab 100644 --- a/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Flow/ThrottleTests.cs @@ -267,7 +267,7 @@ private static Graph GenerateThrottleResetTestGraph(float duration, float resetT var setDelay = g.CreateNode("flow/setDelay"); var sequence = g.CreateNode("flow/sequence"); var branch = g.CreateNode("flow/branch"); - var isNaN = g.CreateNode("math/isnan"); + var isNaN = g.CreateNode("math/isNaN"); setDelay.AddValue(ConstStrings.DURATION, resetTime); diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 56eb7d3af..8fac9e5d9 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -52,25 +52,25 @@ public void TestPopcnt() [Test] public void TestE() { - QueueTest("math/e", "E_Constant", "Constant E", "Retrieves the value from math/e and checks that it is e.", CreateSelfContainedTestGraph("math/e", new(), Out(math.E), ComparisonType.Equals)); + QueueTest("math/E", "E_Constant", "Constant E", "Retrieves the value from math/e and checks that it is e.", CreateSelfContainedTestGraph("math/E", new(), Out(math.E), ComparisonType.Equals)); } [Test] public void TestPI() { - QueueTest("math/pi", "PI_Constant", "Constant PI", "Retrieves the value from math/pi and checks that it is pi.", CreateSelfContainedTestGraph("math/pi", new(), Out(math.PI), ComparisonType.Equals)); + QueueTest("math/Pi", "PI_Constant", "Constant PI", "Retrieves the value from math/pi and checks that it is pi.", CreateSelfContainedTestGraph("math/Pi", new(), Out(math.PI), ComparisonType.Equals)); } [Test] public void TestInf() { - QueueTest("math/inf", "Infinity_Constant", "Constant Inf", "Retrieves the value from math/ing and checks that it is infinity.", CreateSelfContainedTestGraph("math/inf", new(), Out(math.INFINITY), ComparisonType.IsInfinity)); + QueueTest("math/Inf", "Infinity_Constant", "Constant Inf", "Retrieves the value from math/ing and checks that it is infinity.", CreateSelfContainedTestGraph("math/Inf", new(), Out(math.INFINITY), ComparisonType.IsInfinity)); } [Test] public void TestNAN() { - QueueTest("math/nan", "NaN_Constant", "Constant NaN", "Retrieves the value from math/nan and checks that it is nan.", CreateSelfContainedTestGraph("math/nan", new(), Out(math.NAN), ComparisonType.IsNaN)); + QueueTest("math/NaN", "NaN_Constant", "Constant NaN", "Retrieves the value from math/nan and checks that it is nan.", CreateSelfContainedTestGraph("math/NaN", new(), Out(math.NAN), ComparisonType.IsNaN)); } @@ -324,15 +324,15 @@ public void TestGE() [Test] public void TestIsNan() { - QueueTest("math/isnan", "IsNan_True", "IsNaN w/ NaN Value", "Tests isNaN returns true for a nan input.", CreateSelfContainedTestGraph("math/isnan", In((float)math.acos(-2.0)), Out(true), ComparisonType.Equals)); - QueueTest("math/isnan", "IsNan_False", "IsNaN w/ Valid Float", "Tests that isNaN is false when the input is a number.", CreateSelfContainedTestGraph("math/isnan", In(10.0f), Out(false), ComparisonType.Equals)); + QueueTest("math/isNaN", "IsNan_True", "IsNaN w/ NaN Value", "Tests isNaN returns true for a nan input.", CreateSelfContainedTestGraph("math/isNaN", In((float)math.acos(-2.0)), Out(true), ComparisonType.Equals)); + QueueTest("math/isNaN", "IsNan_False", "IsNaN w/ Valid Float", "Tests that isNaN is false when the input is a number.", CreateSelfContainedTestGraph("math/isNaN", In(10.0f), Out(false), ComparisonType.Equals)); } [Test] public void TestIsInf() { - QueueTest("math/isinf", "IsInf_True", "IsInf w/ Inf Value", "Tests isInf returns true for an infinite value.", CreateSelfContainedTestGraph("math/isinf", In(10.0f / 0.0f), Out(true), ComparisonType.Equals)); - QueueTest("math/isinf", "IsInf_False", "IsInf w/ Non-Inf Value", "Tests isInf returns false for a non-infinite value.", CreateSelfContainedTestGraph("math/isinf", In(10.0f), Out(false), ComparisonType.Equals)); + QueueTest("math/isInf", "IsInf_True", "IsInf w/ Inf Value", "Tests isInf returns true for an infinite value.", CreateSelfContainedTestGraph("math/isInf", In(10.0f / 0.0f), Out(true), ComparisonType.Equals)); + QueueTest("math/isInf", "IsInf_False", "IsInf w/ Non-Inf Value", "Tests isInf returns false for a non-infinite value.", CreateSelfContainedTestGraph("math/isInf", In(10.0f), Out(false), ComparisonType.Equals)); } @@ -851,19 +851,19 @@ public void TestMatMul() var mat41 = new float4x4(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f); var mat42 = new float4x4(1.0f, 2.0f, 3.0f, 4.0f, 4.0f, 3.0f, 2.0f, 1.0f, 5.0f, 6.0f, 7.0f, 8.0f, 8.0f, 7.0f, 6.0f, 5.0f); var matres = new float4x4(56.0f, 54.0f, 52.0f, 50.0f, 128.0f, 126.0f, 124.0f, 122.0f, 200.0f, 198.0f, 196.0f, 194.0f, 272.0f, 270.0f, 268.0f, 266.0f); - QueueTest("math/matmul", "MatMul_4x4", "MatMul 4x4", "Tests MatMul/4x4 operation.", CreateSelfContainedTestGraph("math/matmul", In(mat41, mat42), Out(matres), ComparisonType.Approximately)); + QueueTest("math/matMul", "MatMul_4x4", "MatMul 4x4", "Tests MatMul/4x4 operation.", CreateSelfContainedTestGraph("math/matMul", In(mat41, mat42), Out(matres), ComparisonType.Approximately)); // 3x3 Matrix Multiplication var mat31 = new float3x3(1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f); var mat32 = new float3x3(1.0f, 2.0f, 3.0f, 3.0f, 2.0f, 1.0f, 6.0f, 4.0f, 5.0f); var matres2 = new float3x3(25.0f, 18.0f, 20.0f, 55.0f, 42.0f, 47.0f, 85.0f, 66.0f, 74.0f); - QueueTest("math/matmul", "MatMul_3x3", "MatMul 3x3", "Tests MatMul/3x3 operation.", CreateSelfContainedTestGraph("math/matmul", In(mat31, mat32), Out(matres2), ComparisonType.Approximately)); + QueueTest("math/matMul", "MatMul_3x3", "MatMul 3x3", "Tests MatMul/3x3 operation.", CreateSelfContainedTestGraph("math/matMul", In(mat31, mat32), Out(matres2), ComparisonType.Approximately)); // 2x2 Matrix Multiplication var mat21 = new float2x2(1.0f, 2.0f, 3.0f, 4.0f); var mat22 = new float2x2(1.0f, 2.0f, 2.0f, 1.0f); var matres3 = new float2x2(5.0f, 4.0f, 11.0f, 10.0f); - QueueTest("math/matmul", "MatMul_2x2", "MatMul 2x2", "Tests MatMul/2x2 operation.", CreateSelfContainedTestGraph("math/matmul", In(mat21, mat22), Out(matres3), ComparisonType.Approximately)); + QueueTest("math/matMul", "MatMul_2x2", "MatMul 2x2", "Tests MatMul/2x2 operation.", CreateSelfContainedTestGraph("math/matMul", In(mat21, mat22), Out(matres3), ComparisonType.Approximately)); } [Test] diff --git a/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs index 1224eacce..3bb74b3b8 100644 --- a/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Pointer/PointerNodesTests.cs @@ -505,7 +505,7 @@ private static Graph CreateGetErrGraph(string pointer, int nodeIndex, string typ var failOut = CreateFailSubGraph(g, "isValid Should be false for this test."); - var isnan = g.CreateNode("math/isnan"); + var isnan = g.CreateNode("math/isNaN"); var defaultBranch = g.CreateNode("flow/branch"); var failDefault = CreateFailSubGraph(g, $"Output value should be the default for type {type}."); From 388a464c93b56df50791697b274a9cf65286b757 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Wed, 3 Sep 2025 15:09:33 -0400 Subject: [PATCH 27/28] Added tests for matCompose and matDecompose. Fixed test graphs for math nodes with multiple output values. Added isValid output value to matDecompose. --- .../Interactivity/Playback/Data/Model/Flow.cs | 1 - .../Playback/NodeSpecs/Math/MatDecompose.cs | 1 + .../Playback/Nodes/Math/MatDecompose.cs | 61 ++++- .../Nodes/Common/NodeTestHelpers.cs | 250 ++++++++++-------- .../Nodes/Math/MathNodesTests.cs | 148 +++++++++++ 5 files changed, 342 insertions(+), 119 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs b/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs index 1c50d8d1b..0b1db08ff 100644 --- a/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs +++ b/Runtime/Scripts/Interactivity/Playback/Data/Model/Flow.cs @@ -16,7 +16,6 @@ public Flow(Node fromNode, string fromSocket, Node toNode, string toSocket) fromNode.onRemovedFromGraph += OnFromNodeRemovedFromGraph; toNode.onRemovedFromGraph += OnToNodeRemovedFromGraph; - } private void OnFromNodeRemovedFromGraph() diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs index 0e063d654..5198f9526 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs @@ -23,6 +23,7 @@ protected override (NodeFlow[] flows, NodeValue[] values) GenerateOutputs() new NodeValue(ConstStrings.TRANSLATION, "Translation", new Type[] { typeof(float3) }), new NodeValue(ConstStrings.ROTATION, "Rotation", new Type[] { typeof(float4) }), new NodeValue(ConstStrings.SCALE, "Scale", new Type[] { typeof(float3) }), + new NodeValue(ConstStrings.IS_VALID, "Is Valid", new Type[] { typeof(bool) }), }; return (null, values); diff --git a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs index ea4dff8e1..c6836dc91 100644 --- a/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs +++ b/Runtime/Scripts/Interactivity/Playback/Nodes/Math/MatDecompose.cs @@ -18,15 +18,74 @@ public override IProperty GetOutputValue(string id) if (a is not Property mProp) throw new InvalidOperationException($"Type of value a must be Matrix4x4 but a {a.GetTypeSignature()} was passed in!"); - mProp.value.Decompose(out var translation, out var rotation, out var scale); + var translation = float3.zero; + var rotation = new float4(0f, 0f, 0f, 1f); + var scale = new float3(1f, 1f, 1f); + var isValid = IsMatrixDecomposable(mProp.value); + + if(isValid) + mProp.value.Decompose(out translation, out rotation, out scale); return id switch { ConstStrings.TRANSLATION => new Property(translation), ConstStrings.ROTATION => new Property(rotation), ConstStrings.SCALE => new Property(scale), + ConstStrings.IS_VALID => new Property(isValid), _ => throw new InvalidOperationException($"Requested output {id} is not part of the spec for this node."), }; } + + private static bool IsMatrixDecomposable(in float4x4 m) + { + if (!LastRowIsValid(m)) + return false; + + if (!ScaleIsFinite(m, out var s)) + return false; + + if (!ScaledDeterminateIsOne(m, s)) + return false; + + return true; + } + + private static bool LastRowIsValid(in float4x4 m) + { + return m.c0.w == 0f && m.c1.w == 0f && m.c2.w == 0f && m.c3.w == 1f; + } + + private static bool ScaleIsFinite(in float4x4 m, out float3 s) + { + s = float3.zero; + s.x = math.sqrt(m.c0.x * m.c0.x + m.c0.y * m.c0.y + m.c0.z * m.c0.z); + + if (InfiniteZeroOrNaN(s.x)) + return false; + + s.y = math.sqrt(m.c1.x * m.c1.x + m.c1.y * m.c1.y + m.c1.z * m.c1.z); + + if (InfiniteZeroOrNaN(s.y)) + return false; + + s.z = math.sqrt(m.c2.x * m.c2.x + m.c2.y * m.c2.y + m.c2.z * m.c2.z); + + if (InfiniteZeroOrNaN(s.z)) + return false; + + return true; + } + + private static bool InfiniteZeroOrNaN(float v) + { + return v == 0 || math.isinf(v) || math.isnan(v); + } + + private static bool ScaledDeterminateIsOne(in float4x4 m, in float3 s) + { + var b = new float3x3(new float4x4(m.c0 / s.x, m.c1 / s.y, m.c2 / s.z, m.c3)); + + return Mathf.Approximately(math.abs(math.determinant(b)), 1f); + } } } \ No newline at end of file diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs index 1dfad38e3..9b8c4f8d8 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -263,140 +263,156 @@ private static void GenerateGraphByExpectedValueType(Dictionary: - node = CreateExtractNode(g, "math/extract2", opNode, out value, out node, expected); - var f2Val = ((Property)expected.Value).value; - - firstBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2Val[0], subGraph); - lastBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2Val[1], subGraph); - - firstBranch.AddFlow(lastBranch, ConstStrings.TRUE); - - break; - case Property: - node = CreateExtractNode(g, "math/extract3", opNode, out value, out node, expected); - var f3Val = ((Property)expected.Value).value; - - firstBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3Val[0], subGraph); - var b1 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3Val[1], subGraph); - lastBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3Val[2], subGraph); - - firstBranch.AddFlow(b1, ConstStrings.TRUE); - b1.AddFlow(lastBranch, ConstStrings.TRUE); - - break; - case Property: - node = CreateExtractNode(g, "math/extract4", opNode, out value, out node, expected); - var f4Val = ((Property)expected.Value).value; - firstBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4Val[0], subGraph); - var f4b1 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4Val[1], subGraph); - var f4b2 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4Val[2], subGraph); - - lastBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4Val[3], subGraph); - - firstBranch.AddFlow(f4b1, ConstStrings.TRUE); - f4b1.AddFlow(f4b2, ConstStrings.TRUE); - f4b2.AddFlow(lastBranch, ConstStrings.TRUE); - - break; - case Property: - node = CreateExtractNode(g, "math/extract2x2", opNode, out value, out node, expected); - var f2x2Val = ((Property)expected.Value).value; - - var branches = new Node[4]; - - branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2x2Val.c0.x, subGraph); - branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2x2Val.c0.y, subGraph); - branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f2x2Val.c1.x, subGraph); - branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f2x2Val.c1.y, subGraph); - - branches[0].AddFlow(branches[1], ConstStrings.TRUE); - branches[1].AddFlow(branches[2], ConstStrings.TRUE); - branches[2].AddFlow(branches[3], ConstStrings.TRUE); - - firstBranch = branches[0]; - lastBranch = branches[3]; - break; - - case Property: - node = CreateExtractNode(g, "math/extract3x3", opNode, out value, out node, expected); - var f3x3Val = ((Property)expected.Value).value; - - var f3x3branches = new Node[9]; - - f3x3branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3x3Val.c0.x, subGraph); - f3x3branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3x3Val.c0.y, subGraph); - f3x3branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3x3Val.c0.z, subGraph); - - f3x3branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f3x3Val.c1.x, subGraph); - f3x3branches[4] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f3x3Val.c1.y, subGraph); - f3x3branches[5] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f3x3Val.c1.z, subGraph); - - f3x3branches[6] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f3x3Val.c2.x, subGraph); - f3x3branches[7] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f3x3Val.c2.y, subGraph); - f3x3branches[8] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f3x3Val.c2.z, subGraph); - - for (int i = 0; i < f3x3branches.Length - 1; i++) - { - f3x3branches[i].AddFlow(f3x3branches[i + 1], ConstStrings.TRUE); - } - - firstBranch = f3x3branches[0]; - lastBranch = f3x3branches[8]; - break; + switch (expected.Value) + { + default: + iteratorStartBranch = CreateSingleValueTestSubGraph(g, opNode, expected.Key, expected.Value, subGraph); + iteratorEndBranch = iteratorStartBranch; + break; + case Property: + node = CreateExtractNode(g, "math/extract2", opNode, out value, out node, expected); + var f2Val = ((Property)expected.Value).value; + iteratorStartBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2Val[0], subGraph); + iteratorEndBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2Val[1], subGraph); - case Property: - node = CreateExtractNode(g, "math/extract4x4", opNode, out value, out node, expected); - var f4x4Val = ((Property)expected.Value).value; + iteratorStartBranch.AddFlow(iteratorEndBranch, ConstStrings.TRUE); - var f4x4branches = new Node[16]; + break; + case Property: + node = CreateExtractNode(g, "math/extract3", opNode, out value, out node, expected); + var f3Val = ((Property)expected.Value).value; - f4x4branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4x4Val.c0.x, subGraph); - f4x4branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4x4Val.c0.y, subGraph); - f4x4branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4x4Val.c0.z, subGraph); - f4x4branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4x4Val.c0.w, subGraph); + iteratorStartBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3Val[0], subGraph); + var b1 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3Val[1], subGraph); + iteratorEndBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3Val[2], subGraph); + + iteratorStartBranch.AddFlow(b1, ConstStrings.TRUE); + b1.AddFlow(iteratorEndBranch, ConstStrings.TRUE); + + break; + case Property: + node = CreateExtractNode(g, "math/extract4", opNode, out value, out node, expected); + var f4Val = ((Property)expected.Value).value; + iteratorStartBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4Val[0], subGraph); + var f4b1 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4Val[1], subGraph); + var f4b2 = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4Val[2], subGraph); + + iteratorEndBranch = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4Val[3], subGraph); + + iteratorStartBranch.AddFlow(f4b1, ConstStrings.TRUE); + f4b1.AddFlow(f4b2, ConstStrings.TRUE); + f4b2.AddFlow(iteratorEndBranch, ConstStrings.TRUE); + + break; + case Property: + node = CreateExtractNode(g, "math/extract2x2", opNode, out value, out node, expected); + var f2x2Val = ((Property)expected.Value).value; + + var branches = new Node[4]; + + branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f2x2Val.c0.x, subGraph); + branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f2x2Val.c0.y, subGraph); + branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f2x2Val.c1.x, subGraph); + branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f2x2Val.c1.y, subGraph); + + branches[0].AddFlow(branches[1], ConstStrings.TRUE); + branches[1].AddFlow(branches[2], ConstStrings.TRUE); + branches[2].AddFlow(branches[3], ConstStrings.TRUE); + + iteratorStartBranch = branches[0]; + iteratorEndBranch = branches[3]; + break; + + case Property: + node = CreateExtractNode(g, "math/extract3x3", opNode, out value, out node, expected); + var f3x3Val = ((Property)expected.Value).value; + + var f3x3branches = new Node[9]; + + f3x3branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f3x3Val.c0.x, subGraph); + f3x3branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f3x3Val.c0.y, subGraph); + f3x3branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f3x3Val.c0.z, subGraph); + + f3x3branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f3x3Val.c1.x, subGraph); + f3x3branches[4] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f3x3Val.c1.y, subGraph); + f3x3branches[5] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f3x3Val.c1.z, subGraph); + + f3x3branches[6] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f3x3Val.c2.x, subGraph); + f3x3branches[7] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f3x3Val.c2.y, subGraph); + f3x3branches[8] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f3x3Val.c2.z, subGraph); + + for (int i = 0; i < f3x3branches.Length - 1; i++) + { + f3x3branches[i].AddFlow(f3x3branches[i + 1], ConstStrings.TRUE); + } + + iteratorStartBranch = f3x3branches[0]; + iteratorEndBranch = f3x3branches[8]; + break; + + + case Property: + node = CreateExtractNode(g, "math/extract4x4", opNode, out value, out node, expected); + var f4x4Val = ((Property)expected.Value).value; + + var f4x4branches = new Node[16]; + + f4x4branches[0] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(0), f4x4Val.c0.x, subGraph); + f4x4branches[1] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(1), f4x4Val.c0.y, subGraph); + f4x4branches[2] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(2), f4x4Val.c0.z, subGraph); + f4x4branches[3] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(3), f4x4Val.c0.w, subGraph); + + f4x4branches[4] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f4x4Val.c1.x, subGraph); + f4x4branches[5] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f4x4Val.c1.y, subGraph); + f4x4branches[6] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f4x4Val.c1.z, subGraph); + f4x4branches[7] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f4x4Val.c1.w, subGraph); - f4x4branches[4] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(4), f4x4Val.c1.x, subGraph); - f4x4branches[5] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(5), f4x4Val.c1.y, subGraph); - f4x4branches[6] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(6), f4x4Val.c1.z, subGraph); - f4x4branches[7] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(7), f4x4Val.c1.w, subGraph); + f4x4branches[8] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f4x4Val.c2.x, subGraph); + f4x4branches[9] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(9), f4x4Val.c2.y, subGraph); + f4x4branches[10] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(10), f4x4Val.c2.z, subGraph); + f4x4branches[11] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(11), f4x4Val.c2.w, subGraph); - f4x4branches[8] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(8), f4x4Val.c2.x, subGraph); - f4x4branches[9] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(9), f4x4Val.c2.y, subGraph); - f4x4branches[10] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(10), f4x4Val.c2.z, subGraph); - f4x4branches[11] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(11), f4x4Val.c2.w, subGraph); + f4x4branches[12] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(12), f4x4Val.c3.x, subGraph); + f4x4branches[13] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(13), f4x4Val.c3.y, subGraph); + f4x4branches[14] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(14), f4x4Val.c3.z, subGraph); + f4x4branches[15] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(15), f4x4Val.c3.w, subGraph); - f4x4branches[12] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(12), f4x4Val.c3.x, subGraph); - f4x4branches[13] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(13), f4x4Val.c3.y, subGraph); - f4x4branches[14] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(14), f4x4Val.c3.z, subGraph); - f4x4branches[15] = CreateSingleValueTestSubGraph(g, node, ConstStrings.GetNumberString(15), f4x4Val.c3.w, subGraph); + for (int i = 0; i < f4x4branches.Length - 1; i++) + { + f4x4branches[i].AddFlow(f4x4branches[i + 1], ConstStrings.TRUE); + } - for (int i = 0; i < f4x4branches.Length - 1; i++) - { - f4x4branches[i].AddFlow(f4x4branches[i + 1], ConstStrings.TRUE); - } + iteratorStartBranch = f4x4branches[0]; + iteratorEndBranch = f4x4branches[15]; + break; + } + } - firstBranch = f4x4branches[0]; - lastBranch = f4x4branches[15]; - break; + if (count == 0) + { + firstBranch = iteratorStartBranch; + } + else + { + lastBranch.AddFlow(iteratorStartBranch, ConstStrings.TRUE); } + lastBranch = iteratorEndBranch; + count++; } onStart.AddFlow(firstBranch); diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 8fac9e5d9..9de4d3531 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -1156,5 +1156,153 @@ private static (Graph, TestValues) QuatFromDirections(float3 a, float3 b, float4 { return CreateSelfContainedTestGraph("math/quatFromDirections", In(a, b), Out(expected), ComparisonType.Equals); } + + [Test] + public void TestMatCompose() + { + var translation = new float3(1f, 2f, 3f); + var identity_rotation = new float4(0f, 0f, 0f, 1f); + var identity_scale = new float3(1f, 1f, 1f); + var expected_translation_only = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(translation.x, translation.y, translation.z, 1f)); + QueueTest("math/matCompose", "MatCompose_Translation_Only", "MatCompose Translation Only", "Tests matCompose with a translation. Rotation and scale are identity vectors.", MatComposeTest(translation, identity_rotation, identity_scale, expected_translation_only)); + + var euler = new float3(47.3f, 27.2f, 14f); + var rotation = quaternion.Euler(euler).ToFloat4(); + var expected_rotation_only = SpecTRSMatrix(float3.zero, rotation, identity_scale); + QueueTest("math/matCompose", "MatCompose_Rotation_Only", "MatCompose Rotation Only", "Tests matCompose with a rotation. Translation is zero and scale is one.", MatComposeTest(float3.zero, rotation, identity_scale, expected_rotation_only)); + + var scale = new float3(2f, 3f, 4f); + var expected_scale_only = new float4x4(new float4(scale.x, 0f, 0f, 0f), new float4(0f, scale.y, 0f, 0f), new float4(0f, 0f, scale.z, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matCompose", "MatCompose_Scale_Only", "MatCompose Scale Only", "Tests matCompose with a scale. Translation is zero and rotation is identity.", MatComposeTest(float3.zero, identity_rotation, scale, expected_scale_only)); + + var expected_tr = SpecTRSMatrix(translation, rotation, identity_scale); + QueueTest("math/matCompose", "MatCompose_Translation_Rotation", "MatCompose Translation Rotation", "Tests matCompose with a translation and rotation. Scale is one.", MatComposeTest(translation, rotation, identity_scale, expected_tr)); + + var expected_rs = SpecTRSMatrix(float3.zero, rotation, scale); + QueueTest("math/matCompose", "MatCompose_Rotation_Scale", "MatCompose Rotation Scale", "Tests matCompose with a rotation and scale. Translation is zero.", MatComposeTest(float3.zero, rotation, scale, expected_rs)); + + var expected_ts = SpecTRSMatrix(translation, identity_rotation, scale); + QueueTest("math/matCompose", "MatCompose_Translation_Scale", "MatCompose Translation Scale", "Tests matCompose with a translation and scale. Rotation is identity vector.", MatComposeTest(translation, identity_rotation, scale, expected_ts)); + + var expected_trs = SpecTRSMatrix(translation, rotation, scale); + QueueTest("math/matCompose", "MatCompose_Full_TRS", "MatCompose Full TRS", "Tests matCompose with a translation, rotation, and scale.", MatComposeTest(translation, rotation, scale, expected_trs)); + } + + private static (Graph, TestValues) MatComposeTest(float3 translation, float4 rotation, float3 scale, float4x4 expected) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.TRANSLATION, new Value() { id = ConstStrings.TRANSLATION, property = new Property(translation) }); + inputs.Add(ConstStrings.ROTATION, new Value() { id = ConstStrings.ROTATION, property = new Property(rotation) }); + inputs.Add(ConstStrings.SCALE, new Value() { id = ConstStrings.SCALE, property = new Property(scale) }); + + outputs.Add(ConstStrings.VALUE, new Property(expected)); + + return CreateSelfContainedTestGraph("math/matCompose", inputs, outputs, ComparisonType.Equals); + } + + private static float4x4 SpecTRSMatrix(float3 t, float4 r, float3 s) + { + var c00 = s.x * (1 - 2f * (r.y * r.y + r.z * r.z)); + var c01 = s.x * (2f * (r.x * r.y + r.z * r.w)); + var c02 = s.x * (2f * (r.x * r.z - r.y * r.w)); + var c03 = 0f; + + var c10 = s.y * (2f * (r.x * r.y - r.z * r.w)); + var c11 = s.y * (1 - 2f * (r.x * r.x + r.z * r.z)); + var c12 = s.y * (2f * (r.y * r.z + r.x * r.w)); + var c13 = 0f; + + var c20 = s.z * (2f * (r.x * r.z + r.y * r.w)); + var c21 = s.z * (2f * (r.y * r.z - r.x * r.w)); + var c22 = s.z * (1 - 2f * (r.x * r.x + r.y * r.y)); + var c23 = 0f; + + var c3 = new float4(t.x, t.y, t.z, 1f); + + return new float4x4(new float4(c00, c01, c02, c03), new float4(c10, c11, c12, c13), new float4(c20, c21, c22, c23), c3); + } + + [Test] + public void TestMatDecompose() + { + var translation = new float3(1f, 2f, 3f); + var identity_rotation = new float4(0f, 0f, 0f, 1f); + var identity_scale = new float3(1f, 1f, 1f); + var input_translation_only = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(translation.x, translation.y, translation.z, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Translation_Only", "MatDecompose Translation Only", "Tests matDecompose with a translation. Rotation and scale are identity vectors.", MatDecomposeTest(translation, identity_rotation, identity_scale, input_translation_only)); + + var euler = new float3(47.3f, 27.2f, 14f); + var rotation = quaternion.Euler(euler).ToFloat4(); + var input_rotation_only = SpecTRSMatrix(float3.zero, rotation, identity_scale); + QueueTest("math/matDecompose", "MatDecompose_Rotation_Only", "MatDecompose Rotation Only", "Tests matDecompose with a rotation. Translation is zero and scale is one.", MatDecomposeTest(float3.zero, rotation, identity_scale, input_rotation_only)); + + var scale = new float3(2f, 3f, 4f); + var input_scale_only = new float4x4(new float4(scale.x, 0f, 0f, 0f), new float4(0f, scale.y, 0f, 0f), new float4(0f, 0f, scale.z, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Scale_Only", "MatDecompose Scale Only", "Tests matDecompose with a scale. Translation is zero and rotation is identity.", MatDecomposeTest(float3.zero, identity_rotation, scale, input_scale_only)); + + var input_tr = SpecTRSMatrix(translation, rotation, identity_scale); + QueueTest("math/matDecompose", "MatDecompose_Translation_Rotation", "MatDecompose Translation Rotation", "Tests matDecompose with a translation and rotation. Scale is one.", MatDecomposeTest(translation, rotation, identity_scale, input_tr)); + + var input_rs = SpecTRSMatrix(float3.zero, rotation, scale); + QueueTest("math/matDecompose", "MatDecompose_Rotation_Scale", "MatDecompose Rotation Scale", "Tests matDecompose with a rotation and scale. Translation is zero.", MatDecomposeTest(float3.zero, rotation, scale, input_rs)); + + var input_ts = SpecTRSMatrix(translation, identity_rotation, scale); + QueueTest("math/matDecompose", "MatDecompose_Translation_Scale", "MatDecompose Translation Scale", "Tests matDecompose with a translation and scale. Rotation is identity vector.", MatDecomposeTest(translation, identity_rotation, scale, input_ts)); + + var input_trs = SpecTRSMatrix(translation, rotation, scale); + QueueTest("math/matDecompose", "MatDecompose_Full_TRS", "MatDecompose Full TRS", "Tests matDecompose with a translation, rotation, and scale.", MatDecomposeTest(translation, rotation, scale, input_trs)); + + // Invalid matrix tests + var invalid_4th_row = new float4x4(new float4(1f, 0f, 0f, 1f), new float4(0f, 1f, 0f, 1f), new float4(0f, 0f, 1f, 1f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Invalid_4th_Row", "MatDecompose Invalid 4th Row", "Tests matDecompose with an invalid 4th row.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_4th_row, false)); + + var invalid_zero_scale_x = new float4x4(new float4(0f, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Zero_Scale_x", "MatDecompose Sx = 0", "Tests matDecompose with a zero x scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_zero_scale_x, false)); + + var invalid_zero_scale_y = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, 0f, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Zero_Scale_y", "MatDecompose Sy = 0", "Tests matDecompose with a zero y scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_zero_scale_y, false)); + + var invalid_zero_scale_z = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, 0f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Zero_Scale_z", "MatDecompose Sz = 0", "Tests matDecompose with a zero z scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_zero_scale_z, false)); + + var invalid_inf_scale_x = new float4x4(new float4(float.PositiveInfinity, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Inf_Scale_x", "MatDecompose Sx = Inf", "Tests matDecompose with an infinite x scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_inf_scale_x, false)); + + var invalid_inf_scale_y = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, float.PositiveInfinity, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Inf_Scale_y", "MatDecompose Sy = Inf", "Tests matDecompose with an infinite y scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_inf_scale_y, false)); + + var invalid_inf_scale_z = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, float.PositiveInfinity, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Inf_Scale_z", "MatDecompose Sz = Inf", "Tests matDecompose with an infinite z scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_inf_scale_z, false)); + + var invalid_inf_scale_NaN_x = new float4x4(new float4(float.NaN, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Inf_Scale_x", "MatDecompose Sx = NaN", "Tests matDecompose with a NaN x scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_inf_scale_NaN_x, false)); + + var invalid_inf_scale_NaN_y = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, float.NaN, 0f, 0f), new float4(0f, 0f, 1f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Inf_Scale_y", "MatDecompose Sy = NaN", "Tests matDecompose with a NaN y scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_inf_scale_NaN_y, false)); + + var invalid_inf_scale_NaN_z = new float4x4(new float4(1f, 0f, 0f, 0f), new float4(0f, 1f, 0f, 0f), new float4(0f, 0f, float.NaN, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Inf_Scale_z", "MatDecompose Sz = NaN", "Tests matDecompose with a NaN z scale.", MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_inf_scale_NaN_z, false)); + + var invalid_scaled_det = new float4x4(new float4(3f, 5f, 1f, 0f), new float4(2f, 3f, 11f, 0f), new float4(0f, 0f, 1f, 0f), new float4(0f, 0f, 0f, 1f)); + QueueTest("math/matDecompose", "MatDecompose_Invalid_Scaled_Det", "MatDecompose Invalid Scaled Determinant", "Tests matDecompose with an invalid TRS that fails the scaled determinant portion of the test.", + MatDecomposeTest(float3.zero, identity_rotation, identity_scale, invalid_scaled_det, false)); + } + + private static (Graph, TestValues) MatDecomposeTest(float3 translation, float4 rotation, float3 scale, float4x4 trs, bool isValid = true) + { + var inputs = new Dictionary(); + var outputs = new Dictionary(); + + inputs.Add(ConstStrings.A, new Value() { id = ConstStrings.A, property = new Property(trs) }); + + outputs.Add(ConstStrings.TRANSLATION, new Property(translation)); + outputs.Add(ConstStrings.ROTATION, new Property(rotation)); + outputs.Add(ConstStrings.SCALE, new Property(scale)); + outputs.Add(ConstStrings.IS_VALID, new Property(isValid)); + + return CreateSelfContainedTestGraph("math/matDecompose", inputs, outputs, ComparisonType.Equals); + } } } \ No newline at end of file From 1e4d262250ccdc427f311c68a6f307dd453ede59 Mon Sep 17 00:00:00 2001 From: Jordan Robinson Date: Mon, 22 Sep 2025 12:11:48 -0400 Subject: [PATCH 28/28] Fixed bug in node spec for matDecompose. Made matCompose and matDecompose tests use approximation subgraphs to avoid floating point errors in some engines. --- .../Playback/NodeSpecs/Math/MatDecompose.cs | 2 +- .../Interactivity/Nodes/Common/NodeTestHelpers.cs | 10 +++++++++- .../Runtime/Interactivity/Nodes/Math/MathNodesTests.cs | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs index 5198f9526..8cb00339e 100644 --- a/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs +++ b/Runtime/Scripts/Interactivity/Playback/NodeSpecs/Math/MatDecompose.cs @@ -10,7 +10,7 @@ protected override (NodeFlow[] flows, NodeValue[] values) GenerateInputs() { var values = new NodeValue[] { - new NodeValue(ConstStrings.VALUE, "Matrix", new Type[] { typeof(float4x4) }), + new NodeValue(ConstStrings.A, "Matrix", new Type[] { typeof(float4x4) }), }; return (null, values); diff --git a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs index 9b8c4f8d8..985b4fa71 100644 --- a/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs +++ b/Tests/Runtime/Interactivity/Nodes/Common/NodeTestHelpers.cs @@ -252,7 +252,7 @@ protected static (Graph, TestValues) CreateSelfContainedTestGraph(string nodeStr return (g, testValues); } - private static void GenerateGraphByExpectedValueType(Dictionary expectedResults, Graph g, Node opNode, ISubGraph subGraph) + private static void GenerateGraphByExpectedValueType(Dictionary expectedResults, Graph g, Node opNode, ISubGraph requestedSubGraph) { Value value; Node node; @@ -268,8 +268,16 @@ private static void GenerateGraphByExpectedValueType(Dictionary or Property => subGraphs[(int)ComparisonType.Equals], + _ => requestedSubGraph, + }; + if (subGraph is EqualSubGraph) { iteratorStartBranch = CreateSingleValueTestSubGraph(g, opNode, expected.Key, expected.Value, subGraph); diff --git a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs index 9de4d3531..ff4e9ce7c 100644 --- a/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs +++ b/Tests/Runtime/Interactivity/Nodes/Math/MathNodesTests.cs @@ -1199,7 +1199,7 @@ private static (Graph, TestValues) MatComposeTest(float3 translation, float4 rot outputs.Add(ConstStrings.VALUE, new Property(expected)); - return CreateSelfContainedTestGraph("math/matCompose", inputs, outputs, ComparisonType.Equals); + return CreateSelfContainedTestGraph("math/matCompose", inputs, outputs, ComparisonType.Approximately); } private static float4x4 SpecTRSMatrix(float3 t, float4 r, float3 s) @@ -1302,7 +1302,7 @@ private static (Graph, TestValues) MatDecomposeTest(float3 translation, float4 r outputs.Add(ConstStrings.SCALE, new Property(scale)); outputs.Add(ConstStrings.IS_VALID, new Property(isValid)); - return CreateSelfContainedTestGraph("math/matDecompose", inputs, outputs, ComparisonType.Equals); + return CreateSelfContainedTestGraph("math/matDecompose", inputs, outputs, ComparisonType.Approximately); } } } \ No newline at end of file