1351 lines
42 KiB
C++
1351 lines
42 KiB
C++
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
|
|
|
#include "VisjectGraph.h"
|
|
#include "GraphUtilities.h"
|
|
#include "Engine/Core/Random.h"
|
|
#include "Engine/Core/Collections/Sorting.h"
|
|
#include "Engine/Core/Math/Vector4.h"
|
|
#include "Engine/Core/Math/Transform.h"
|
|
#include "Engine/Engine/GameplayGlobals.h"
|
|
#include "Engine/Scripting/Scripting.h"
|
|
#include "Engine/Level/Actor.h"
|
|
#include "Engine/Scripting/ManagedCLR/MType.h"
|
|
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
|
#include "Engine/Scripting/ManagedCLR/MField.h"
|
|
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
|
#include "Engine/Serialization/MemoryReadStream.h"
|
|
#include "Engine/Utilities/StringConverter.h"
|
|
|
|
#define RAND Random::Rand()
|
|
#define ENSURE(condition, errorMsg) if (!(condition)) { OnError(node, box, errorMsg); return; }
|
|
|
|
VisjectExecutor::VisjectExecutor()
|
|
{
|
|
// Register per group type processing events
|
|
// Note: index must match group id
|
|
#if BUILD_DEBUG
|
|
Platform::MemoryClear(&_perGroupProcessCall, sizeof(_perGroupProcessCall));
|
|
#endif
|
|
_perGroupProcessCall[2] = &VisjectExecutor::ProcessGroupConstants;
|
|
_perGroupProcessCall[3] = &VisjectExecutor::ProcessGroupMath;
|
|
_perGroupProcessCall[4] = &VisjectExecutor::ProcessGroupPacking;
|
|
_perGroupProcessCall[7] = &VisjectExecutor::ProcessGroupTools;
|
|
_perGroupProcessCall[10] = &VisjectExecutor::ProcessGroupBoolean;
|
|
_perGroupProcessCall[11] = &VisjectExecutor::ProcessGroupBitwise;
|
|
_perGroupProcessCall[12] = &VisjectExecutor::ProcessGroupComparisons;
|
|
_perGroupProcessCall[14] = &VisjectExecutor::ProcessGroupParticles;
|
|
_perGroupProcessCall[18] = &VisjectExecutor::ProcessGroupCollections;
|
|
}
|
|
|
|
VisjectExecutor::~VisjectExecutor()
|
|
{
|
|
}
|
|
|
|
void VisjectExecutor::OnError(Node* node, Box* box, const StringView& message)
|
|
{
|
|
Error(node, box, message);
|
|
LOG_STR(Error, message);
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupConstants(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Constant value
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 12:
|
|
value = node->Values[0];
|
|
break;
|
|
case 4:
|
|
{
|
|
const Value& cv = node->Values[0];
|
|
if (box->ID == 0)
|
|
value = cv;
|
|
else if (box->ID == 1)
|
|
value = cv.AsVector2().X;
|
|
else if (box->ID == 2)
|
|
value = cv.AsVector2().Y;
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
const Value& cv = node->Values[0];
|
|
if (box->ID == 0)
|
|
value = cv;
|
|
else if (box->ID == 1)
|
|
value = cv.AsVector3().X;
|
|
else if (box->ID == 2)
|
|
value = cv.AsVector3().Y;
|
|
else if (box->ID == 3)
|
|
value = cv.AsVector3().Z;
|
|
break;
|
|
}
|
|
case 6:
|
|
case 7:
|
|
{
|
|
const Value& cv = node->Values[0];
|
|
if (box->ID == 0)
|
|
value = cv;
|
|
else if (box->ID == 1)
|
|
value = cv.AsVector4().X;
|
|
else if (box->ID == 2)
|
|
value = cv.AsVector4().Y;
|
|
else if (box->ID == 3)
|
|
value = cv.AsVector4().Z;
|
|
else if (box->ID == 4)
|
|
value = cv.AsVector4().W;
|
|
break;
|
|
}
|
|
case 8:
|
|
{
|
|
const float pitch = (float)node->Values[0];
|
|
const float yaw = (float)node->Values[1];
|
|
const float roll = (float)node->Values[2];
|
|
value = Quaternion::Euler(pitch, yaw, roll);
|
|
break;
|
|
}
|
|
case 9:
|
|
value = node->Values[0];
|
|
break;
|
|
// PI
|
|
case 10:
|
|
value = PI;
|
|
break;
|
|
// Enum
|
|
case 11:
|
|
value = node->Values[0];
|
|
break;
|
|
// Array
|
|
case 13:
|
|
value = node->Values[0];
|
|
if (value.Type.Type == VariantType::Array)
|
|
{
|
|
auto& array = value.AsArray();
|
|
const int32 count = Math::Min(array.Count(), node->Boxes.Count() - 1);
|
|
const VariantType elementType = value.Type.GetElementType();
|
|
for (int32 i = 0; i < count; i++)
|
|
{
|
|
auto b = &node->Boxes[i + 1];
|
|
if (b && b->HasConnection())
|
|
array[i] = eatBox(node, b->FirstConnection()).Cast(elementType);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupMath(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Add, Subtract, Multiply, Divide, Modulo, Max, Min, Pow, Fmod, Atan2
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 21:
|
|
case 22:
|
|
case 23:
|
|
case 40:
|
|
case 41:
|
|
{
|
|
Value v1 = tryGetValue(node->GetBox(0), 0, Value::Zero);
|
|
Value v2 = tryGetValue(node->GetBox(1), 1, Value::Zero).Cast(v1.Type);
|
|
GraphUtilities::ApplySomeMathHere(node->TypeID, value, v1, v2);
|
|
break;
|
|
}
|
|
// Absolute Value, Ceil, Cosine, Floor, Round, Saturate, Sine, Sqrt, Tangent, Negate, 1 - Value, Asine, Acosine, Atan, Trunc, Frac, Degrees, Radians
|
|
case 7:
|
|
case 8:
|
|
case 9:
|
|
case 10:
|
|
case 13:
|
|
case 14:
|
|
case 15:
|
|
case 16:
|
|
case 17:
|
|
case 27:
|
|
case 28:
|
|
case 33:
|
|
case 34:
|
|
case 35:
|
|
case 38:
|
|
case 39:
|
|
case 43:
|
|
case 44:
|
|
{
|
|
Value v1 = tryGetValue(node->GetBox(0), Value::Zero);
|
|
GraphUtilities::ApplySomeMathHere(node->TypeID, value, v1);
|
|
break;
|
|
}
|
|
// Length, Normalize
|
|
case 11:
|
|
case 12:
|
|
{
|
|
Value v1 = tryGetValue(node->GetBox(0), Value::Zero);
|
|
switch (node->TypeID)
|
|
{
|
|
case 11:
|
|
{
|
|
switch (v1.Type.Type)
|
|
{
|
|
case VariantType::Vector2:
|
|
value = v1.AsVector2().Length();
|
|
break;
|
|
case VariantType::Vector3:
|
|
value = v1.AsVector3().Length();
|
|
break;
|
|
case VariantType::Vector4:
|
|
value = Vector3(v1.AsVector4()).Length();
|
|
break;
|
|
default: CRASH;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 12:
|
|
switch (v1.Type.Type)
|
|
{
|
|
case VariantType::Int:
|
|
value = Math::Saturate(v1.AsInt);
|
|
break;
|
|
case VariantType::Uint:
|
|
value = Math::Saturate(v1.AsUint);
|
|
break;
|
|
case VariantType::Float:
|
|
value = Math::Saturate(v1.AsFloat);
|
|
break;
|
|
case VariantType::Vector2:
|
|
value = Vector2::Normalize(v1.AsVector2());
|
|
break;
|
|
case VariantType::Vector3:
|
|
value = Vector3::Normalize(v1.AsVector3());
|
|
break;
|
|
case VariantType::Vector4:
|
|
value = Vector4(Vector3::Normalize(Vector3(v1.AsVector4())), 0.0f);
|
|
break;
|
|
default: CRASH;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
// Cross, Distance, Dot
|
|
case 18:
|
|
case 19:
|
|
case 20:
|
|
{
|
|
Value v1 = tryGetValue(node->GetBox(0), Value::Zero);
|
|
Value v2 = tryGetValue(node->GetBox(1), Value::Zero).Cast(v1.Type);
|
|
switch (node->TypeID)
|
|
{
|
|
case 18:
|
|
switch (v1.Type.Type)
|
|
{
|
|
case VariantType::Vector3:
|
|
value = Vector3::Cross(v1.AsVector3(), v2.AsVector3());
|
|
break;
|
|
default: CRASH;
|
|
break;
|
|
}
|
|
break;
|
|
case 19:
|
|
switch (v1.Type.Type)
|
|
{
|
|
case VariantType::Vector2:
|
|
value = Vector2::Distance(v1.AsVector2(), v2.AsVector2());
|
|
break;
|
|
case VariantType::Vector3:
|
|
value = Vector3::Distance(v1.AsVector3(), v2.AsVector3());
|
|
break;
|
|
case VariantType::Vector4:
|
|
case VariantType::Color:
|
|
value = Vector3::Distance((Vector3)v1, (Vector3)v2);
|
|
break;
|
|
default: CRASH;
|
|
break;
|
|
}
|
|
break;
|
|
case 20:
|
|
switch (v1.Type.Type)
|
|
{
|
|
case VariantType::Vector2:
|
|
value = Vector2::Dot(v1.AsVector2(), v2.AsVector2());
|
|
break;
|
|
case VariantType::Vector3:
|
|
value = Vector3::Dot(v1.AsVector3(), v2.AsVector3());
|
|
break;
|
|
case VariantType::Vector4:
|
|
case VariantType::Color:
|
|
value = Vector3::Dot((Vector3)v1, (Vector3)v2);
|
|
break;
|
|
default: CRASH;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
// Clamp
|
|
case 24:
|
|
{
|
|
Value v1 = tryGetValue(node->GetBox(0), Value::Zero);
|
|
Value v2 = tryGetValue(node->GetBox(1), 0, Value::Zero).Cast(v1.Type);
|
|
Value v3 = tryGetValue(node->GetBox(2), 1, Value::One).Cast(v1.Type);
|
|
GraphUtilities::ApplySomeMathHere(value, v1, v2, v3, [](float a, float b, float c)
|
|
{
|
|
return Math::Clamp(a, b, c);
|
|
});
|
|
break;
|
|
}
|
|
// Lerp
|
|
case 25:
|
|
{
|
|
Value a = tryGetValue(node->GetBox(0), 0, Value::Zero);
|
|
Value b = tryGetValue(node->GetBox(1), 1, Value::One).Cast(a.Type);
|
|
Value alpha = tryGetValue(node->GetBox(2), 2, Value::Zero).Cast(ValueType(ValueType::Float));
|
|
value = Value::Lerp(a, b, alpha.AsFloat);
|
|
break;
|
|
}
|
|
// Reflect
|
|
case 26:
|
|
{
|
|
Value v1 = tryGetValue(node->GetBox(0), Value::Zero);
|
|
Value v2 = tryGetValue(node->GetBox(1), Value::Zero).Cast(v1.Type);
|
|
switch (v1.Type.Type)
|
|
{
|
|
case VariantType::Vector2:
|
|
value = v1.AsVector2() - 2 * v2.AsVector2() * Vector2::Dot(v1.AsVector2(), v2.AsVector2());
|
|
break;
|
|
case VariantType::Vector3:
|
|
value = v1.AsVector3() - 2 * v2.AsVector3() * Vector3::Dot(v1.AsVector3(), v2.AsVector3());
|
|
break;
|
|
case VariantType::Vector4:
|
|
value = Vector4(v1.AsVector4() - 2 * v2.AsVector4() * Vector3::Dot((Vector3)v1, (Vector3)v2));
|
|
break;
|
|
default: CRASH;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
// Mad
|
|
case 31:
|
|
{
|
|
Value v1 = tryGetValue(node->GetBox(0), Value::Zero);
|
|
Value v2 = tryGetValue(node->GetBox(1), 0, Value::One).Cast(v1.Type);
|
|
Value v3 = tryGetValue(node->GetBox(2), 1, Value::Zero).Cast(v1.Type);
|
|
GraphUtilities::ApplySomeMathHere(value, v1, v2, v3, [](float a, float b, float c)
|
|
{
|
|
return (a * b) + c;
|
|
});
|
|
break;
|
|
}
|
|
// Extract Largest Component
|
|
case 32:
|
|
{
|
|
const auto v1 = (Vector3)tryGetValue(node->GetBox(0), Value::Zero);
|
|
value = Math::ExtractLargestComponent(v1);
|
|
break;
|
|
}
|
|
// Bias and Scale
|
|
case 36:
|
|
{
|
|
ASSERT(node->Values.Count() == 2 && node->Values[0].Type == VariantType::Float && node->Values[1].Type == VariantType::Float);
|
|
const auto bias = node->Values[0].AsFloat;
|
|
const auto scale = node->Values[1].AsFloat;
|
|
const auto input = (Vector3)tryGetValue(node->GetBox(0), Value::Zero);
|
|
value = (input + bias) * scale;
|
|
break;
|
|
}
|
|
// Rotate About Axis
|
|
case 37:
|
|
{
|
|
const auto normalizedRotationAxis = (Vector3)tryGetValue(node->GetBox(0), Value::Zero);
|
|
const auto rotationAngle = (float)tryGetValue(node->GetBox(1), Value::Zero);
|
|
const auto pivotPoint = (Vector3)tryGetValue(node->GetBox(2), Value::Zero);
|
|
const auto position = (Vector3)tryGetValue(node->GetBox(3), Value::Zero);
|
|
value = Math::RotateAboutAxis(normalizedRotationAxis, rotationAngle, pivotPoint, position);
|
|
break;
|
|
}
|
|
// Near Equal
|
|
case 42:
|
|
{
|
|
const Value a = tryGetValue(node->GetBox(0), node->Values[0]);
|
|
const Value b = tryGetValue(node->GetBox(1), node->Values[1]).Cast(a.Type);
|
|
const float epsilon = (float)tryGetValue(node->GetBox(2), node->Values[2]);
|
|
value = Value::NearEqual(a, b, epsilon);
|
|
break;
|
|
}
|
|
// Enum Value
|
|
case 45:
|
|
value = (uint64)tryGetValue(node->GetBox(0), Value::Zero);
|
|
break;
|
|
// Enum AND
|
|
case 46:
|
|
value = tryGetValue(node->GetBox(0), Value::Zero);
|
|
if (value.Type.Type == VariantType::Enum)
|
|
value.AsUint64 = value.AsUint64 & (uint64)tryGetValue(node->GetBox(1), Value::Zero);
|
|
break;
|
|
// Enum OR
|
|
case 47:
|
|
value = tryGetValue(node->GetBox(0), Value::Zero);
|
|
if (value.Type.Type == VariantType::Enum)
|
|
value.AsUint64 = value.AsUint64 | (uint64)tryGetValue(node->GetBox(1), Value::Zero);
|
|
break;
|
|
// Remap
|
|
case 48:
|
|
{
|
|
const float inVal = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat;
|
|
const Vector2 rangeA = tryGetValue(node->GetBox(1), node->Values[1]).AsVector2();
|
|
const Vector2 rangeB = tryGetValue(node->GetBox(2), node->Values[2]).AsVector2();
|
|
const bool clamp = tryGetValue(node->GetBox(3), node->Values[3]).AsBool;
|
|
auto mapFunc = Math::Remap(inVal, rangeA.X, rangeA.Y, rangeB.X, rangeB.Y);
|
|
value = clamp ? Math::Clamp(mapFunc, rangeB.X, rangeB.Y) : mapFunc;
|
|
break;
|
|
}
|
|
// Rotate Vector
|
|
case 49:
|
|
{
|
|
const Quaternion quaternion = (Quaternion)tryGetValue(node->GetBox(0), Quaternion::Identity);
|
|
const Vector3 vector = (Vector3)tryGetValue(node->GetBox(1), Vector3::Forward);
|
|
value = quaternion * vector;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Pack
|
|
case 20:
|
|
{
|
|
float vX = (float)tryGetValue(node->GetBox(1), node->Values[0]);
|
|
float vY = (float)tryGetValue(node->GetBox(2), node->Values[1]);
|
|
value = Vector2(vX, vY);
|
|
break;
|
|
}
|
|
case 21:
|
|
{
|
|
float vX = (float)tryGetValue(node->GetBox(1), node->Values[0]);
|
|
float vY = (float)tryGetValue(node->GetBox(2), node->Values[1]);
|
|
float vZ = (float)tryGetValue(node->GetBox(3), node->Values[2]);
|
|
value = Vector3(vX, vY, vZ);
|
|
break;
|
|
}
|
|
case 22:
|
|
{
|
|
float vX = (float)tryGetValue(node->GetBox(1), node->Values[0]);
|
|
float vY = (float)tryGetValue(node->GetBox(2), node->Values[1]);
|
|
float vZ = (float)tryGetValue(node->GetBox(3), node->Values[2]);
|
|
float vW = (float)tryGetValue(node->GetBox(4), node->Values[3]);
|
|
value = Vector4(vX, vY, vZ, vW);
|
|
break;
|
|
}
|
|
case 23:
|
|
{
|
|
float vX = (float)tryGetValue(node->GetBox(1), node->Values[0]);
|
|
float vY = (float)tryGetValue(node->GetBox(2), node->Values[1]);
|
|
float vZ = (float)tryGetValue(node->GetBox(3), node->Values[2]);
|
|
value = Quaternion::Euler(vX, vY, vZ);
|
|
break;
|
|
}
|
|
case 24:
|
|
{
|
|
const Vector3 vX = (Vector3)tryGetValue(node->GetBox(1), Vector3::Zero);
|
|
const Quaternion vY = (Quaternion)tryGetValue(node->GetBox(2), Quaternion::Identity);
|
|
const Vector3 vZ = (Vector3)tryGetValue(node->GetBox(3), Vector3::One);
|
|
value = Variant(Transform(vX, vY, vZ));
|
|
break;
|
|
}
|
|
case 25:
|
|
{
|
|
const Vector3 vX = (Vector3)tryGetValue(node->GetBox(1), Vector3::Zero);
|
|
const Vector3 vY = (Vector3)tryGetValue(node->GetBox(2), Vector3::Zero);
|
|
value = Variant(BoundingBox(vX, vY));
|
|
break;
|
|
}
|
|
// Unpack
|
|
case 30:
|
|
{
|
|
Vector2 v = (Vector2)tryGetValue(node->GetBox(0), Vector2::Zero);
|
|
int32 subIndex = box->ID - 1;
|
|
ASSERT(subIndex >= 0 && subIndex < 2);
|
|
value = v.Raw[subIndex];
|
|
break;
|
|
}
|
|
case 31:
|
|
{
|
|
Vector3 v = (Vector3)tryGetValue(node->GetBox(0), Vector3::Zero);
|
|
int32 subIndex = box->ID - 1;
|
|
ASSERT(subIndex >= 0 && subIndex < 3);
|
|
value = v.Raw[subIndex];
|
|
break;
|
|
}
|
|
case 32:
|
|
{
|
|
Vector4 v = (Vector4)tryGetValue(node->GetBox(0), Vector4::Zero);
|
|
int32 subIndex = box->ID - 1;
|
|
ASSERT(subIndex >= 0 && subIndex < 4);
|
|
value = v.Raw[subIndex];
|
|
break;
|
|
}
|
|
case 33:
|
|
{
|
|
const Vector3 v = ((Quaternion)tryGetValue(node->GetBox(0), Quaternion::Identity)).GetEuler();
|
|
const int32 subIndex = box->ID - 1;
|
|
ASSERT(subIndex >= 0 && subIndex < 3);
|
|
value = v.Raw[subIndex];
|
|
break;
|
|
}
|
|
case 34:
|
|
{
|
|
const Transform v = (Transform)tryGetValue(node->GetBox(0), Variant::Zero);
|
|
switch (box->ID)
|
|
{
|
|
case 1:
|
|
value = v.Translation;
|
|
break;
|
|
case 2:
|
|
value = v.Orientation;
|
|
break;
|
|
case 3:
|
|
value = v.Scale;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case 35:
|
|
{
|
|
const BoundingBox v = (BoundingBox)tryGetValue(node->GetBox(0), Variant::Zero);
|
|
switch (box->ID)
|
|
{
|
|
case 1:
|
|
value = v.Minimum;
|
|
break;
|
|
case 2:
|
|
value = v.Maximum;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
// Pack Structure
|
|
case 26:
|
|
{
|
|
// Find type
|
|
const StringView typeName(node->Values[0]);
|
|
const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
|
|
const StringAnsiView typeNameAnsiView(typeNameAnsi.Get(), typeName.Length());
|
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeNameAnsiView);
|
|
if (!typeHandle)
|
|
{
|
|
#if !COMPILE_WITHOUT_CSHARP
|
|
const auto mclass = Scripting::FindClass(typeNameAnsiView);
|
|
if (mclass)
|
|
{
|
|
// Fallback to C#-only types
|
|
bool failed = false;
|
|
auto instance = mclass->CreateInstance();
|
|
value = instance;
|
|
auto& layoutCache = node->Values[1];
|
|
CHECK(layoutCache.Type.Type == VariantType::Blob);
|
|
MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
|
|
const byte version = stream.ReadByte();
|
|
if (version == 1)
|
|
{
|
|
int32 fieldsCount;
|
|
stream.ReadInt32(&fieldsCount);
|
|
for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
|
|
{
|
|
box = &node->Boxes[boxId];
|
|
String fieldName;
|
|
stream.ReadString(&fieldName, 11);
|
|
VariantType fieldType;
|
|
stream.ReadVariantType(&fieldType);
|
|
if (box && box->HasConnection())
|
|
{
|
|
StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length());
|
|
auto field = mclass->GetField(fieldNameAnsi.Get());
|
|
if (field)
|
|
{
|
|
Variant fieldValue = eatBox(node, box->FirstConnection());
|
|
field->SetValue(instance, MUtils::VariantToManagedArgPtr(fieldValue, field->GetType(), failed));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (typeName.HasChars())
|
|
#endif
|
|
{
|
|
OnError(node, box, String::Format(TEXT("Missing type '{0}'"), typeName));
|
|
}
|
|
return;
|
|
}
|
|
const ScriptingType& type = typeHandle.GetType();
|
|
|
|
// Allocate structure data and initialize it with native constructor
|
|
value.SetType(VariantType(VariantType::Structure, typeNameAnsiView));
|
|
|
|
// Setup structure fields
|
|
auto& layoutCache = node->Values[1];
|
|
CHECK(layoutCache.Type.Type == VariantType::Blob);
|
|
MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
|
|
const byte version = stream.ReadByte();
|
|
if (version == 1)
|
|
{
|
|
int32 fieldsCount;
|
|
stream.ReadInt32(&fieldsCount);
|
|
for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
|
|
{
|
|
box = &node->Boxes[boxId];
|
|
String fieldName;
|
|
stream.ReadString(&fieldName, 11);
|
|
VariantType fieldType;
|
|
stream.ReadVariantType(&fieldType);
|
|
if (box && box->HasConnection())
|
|
{
|
|
const Variant fieldValue = eatBox(node, box->FirstConnection());
|
|
type.Struct.SetField(value.AsBlob.Data, fieldName, fieldValue);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// Unpack Structure
|
|
case 36:
|
|
{
|
|
// Get value with structure data
|
|
const Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
|
|
if (!node->GetBox(0)->HasConnection())
|
|
return;
|
|
|
|
// Find type
|
|
const StringView typeName(node->Values[0]);
|
|
const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
|
|
const StringAnsiView typeNameAnsiView(typeNameAnsi.Get(), typeName.Length());
|
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeNameAnsiView);
|
|
if (!typeHandle)
|
|
{
|
|
#if !COMPILE_WITHOUT_CSHARP
|
|
const auto mclass = Scripting::FindClass(typeNameAnsiView);
|
|
if (mclass)
|
|
{
|
|
// Fallback to C#-only types
|
|
auto instance = (MonoObject*)structureValue;
|
|
CHECK(instance);
|
|
if (structureValue.Type.Type != VariantType::ManagedObject || mono_object_get_class(instance) != mclass->GetNative())
|
|
{
|
|
OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), String(MUtils::GetClassFullname(instance)), typeName));
|
|
return;
|
|
}
|
|
auto& layoutCache = node->Values[1];
|
|
CHECK(layoutCache.Type.Type == VariantType::Blob);
|
|
MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
|
|
const byte version = stream.ReadByte();
|
|
if (version == 1)
|
|
{
|
|
int32 fieldsCount;
|
|
stream.ReadInt32(&fieldsCount);
|
|
for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
|
|
{
|
|
String fieldName;
|
|
stream.ReadString(&fieldName, 11);
|
|
VariantType fieldType;
|
|
stream.ReadVariantType(&fieldType);
|
|
if (box->ID == boxId)
|
|
{
|
|
StringAsANSI<40> fieldNameAnsi(*fieldName, fieldName.Length());
|
|
auto field = mclass->GetField(fieldNameAnsi.Get());
|
|
if (field)
|
|
value = MUtils::UnboxVariant(field->GetValueBoxed(instance));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (typeName.HasChars())
|
|
#endif
|
|
{
|
|
OnError(node, box, String::Format(TEXT("Missing type '{0}'"), typeName));
|
|
}
|
|
return;
|
|
}
|
|
const ScriptingType& type = typeHandle.GetType();
|
|
if (structureValue.Type.Type != VariantType::Structure || StringUtils::Compare(typeNameAnsi.Get(), structureValue.Type.TypeName) != 0)
|
|
{
|
|
OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName));
|
|
return;
|
|
}
|
|
|
|
// Read structure field
|
|
auto& layoutCache = node->Values[1];
|
|
CHECK(layoutCache.Type.Type == VariantType::Blob);
|
|
MemoryReadStream stream((byte*)layoutCache.AsBlob.Data, layoutCache.AsBlob.Length);
|
|
const byte version = stream.ReadByte();
|
|
if (version == 1)
|
|
{
|
|
int32 fieldsCount;
|
|
stream.ReadInt32(&fieldsCount);
|
|
for (int32 boxId = 1; boxId < node->Boxes.Count(); boxId++)
|
|
{
|
|
String fieldName;
|
|
stream.ReadString(&fieldName, 11);
|
|
VariantType fieldType;
|
|
stream.ReadVariantType(&fieldType);
|
|
if (box->ID == boxId)
|
|
{
|
|
type.Struct.GetField(structureValue.AsBlob.Data, fieldName, value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// Mask X, Y, Z, W
|
|
case 40:
|
|
case 41:
|
|
case 42:
|
|
case 43:
|
|
{
|
|
const Vector4 v = (Vector4)tryGetValue(node->GetBox(0), Vector4::Zero);
|
|
value = v.Raw[node->TypeID - 40];
|
|
break;
|
|
}
|
|
// Mask XY, YZ, XZ,...
|
|
case 44:
|
|
{
|
|
value = (Vector2)tryGetValue(node->GetBox(0), Vector2::Zero);
|
|
break;
|
|
}
|
|
case 45:
|
|
{
|
|
const Vector4 v = (Vector4)tryGetValue(node->GetBox(0), Vector4::Zero);
|
|
value = Vector2(v.X, v.Z);
|
|
break;
|
|
}
|
|
case 46:
|
|
{
|
|
const Vector4 v = (Vector4)tryGetValue(node->GetBox(0), Vector4::Zero);
|
|
value = Vector2(v.Y, v.Z);
|
|
break;
|
|
}
|
|
case 47:
|
|
{
|
|
const Vector4 v = (Vector4)tryGetValue(node->GetBox(0), Vector4::Zero);
|
|
value = Vector2(v.Z, v.W);
|
|
break;
|
|
}
|
|
// Mask XYZ
|
|
case 70:
|
|
{
|
|
value = (Vector3)tryGetValue(node->GetBox(0), Vector3::Zero);
|
|
break;
|
|
}
|
|
// Append
|
|
case 100:
|
|
{
|
|
auto in0 = node->GetBox(0);
|
|
auto in1 = node->GetBox(1);
|
|
if (!in0->HasConnection() || !in1->HasConnection())
|
|
{
|
|
value = Value::Zero;
|
|
break;
|
|
}
|
|
|
|
auto value0 = eatBox(in0->GetParent<Node>(), in0->FirstConnection());
|
|
auto value1 = eatBox(in1->GetParent<Node>(), in1->FirstConnection());
|
|
|
|
auto count0 = GraphUtilities::CountComponents(value0.Type.Type);
|
|
auto count1 = GraphUtilities::CountComponents(value1.Type.Type);
|
|
|
|
auto count = count0 + count1;
|
|
switch (count)
|
|
{
|
|
case 1:
|
|
value = count0 ? value0 : value1;
|
|
break;
|
|
case 2:
|
|
value = Vector2((float)value0, (float)value1);
|
|
break;
|
|
case 3:
|
|
if (count0 == 1)
|
|
value = Vector3((float)value0, value1.AsVector2().X, value1.AsVector2().Y);
|
|
else
|
|
value = Vector3((Vector2)value0, (float)value1);
|
|
break;
|
|
case 4:
|
|
if (count0 == 1)
|
|
value = Vector4((float)value0, value1.AsVector3().X, value1.AsVector3().Y, value1.AsVector3().Z);
|
|
else if (count0 == 2)
|
|
value = Vector4(value0.AsVector2().X, value0.AsVector2().Y, value1.AsVector2().X, value1.AsVector2().Y);
|
|
else
|
|
value = Vector4((Vector3)value0, (float)value1);
|
|
break;
|
|
default:
|
|
value = Value::Zero;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Color Gradient
|
|
case 10:
|
|
{
|
|
float time, prevTime, curTime;
|
|
Color prevColor, curColor;
|
|
const int32 count = (int32)node->Values[0];
|
|
switch (count)
|
|
{
|
|
case 0:
|
|
// No sample
|
|
value = Value::Zero;
|
|
break;
|
|
case 1:
|
|
// Single sample
|
|
value = (Color)node->Values[2];
|
|
break;
|
|
case 2:
|
|
// Linear blend between 2 samples
|
|
time = (float)tryGetValue(node->GetBox(0), Value::Zero);
|
|
prevTime = (float)node->Values[1];
|
|
prevColor = (Color)node->Values[2];
|
|
curTime = (float)node->Values[3];
|
|
curColor = (Color)node->Values[4];
|
|
value = Color::Lerp(prevColor, curColor, Math::Saturate((time - prevTime) / (curTime - prevTime)));
|
|
break;
|
|
default:
|
|
time = (float)tryGetValue(node->GetBox(0), Value::Zero);
|
|
if (time >= node->Values[1 + count * 2 - 2].AsFloat)
|
|
{
|
|
// Outside the range
|
|
value = (Color)node->Values[1 + count * 2 - 2 + 1];
|
|
}
|
|
else
|
|
{
|
|
// Find 2 samples to blend between them
|
|
prevTime = (float)node->Values[1];
|
|
prevColor = (Color)node->Values[2];
|
|
for (int32 i = 1; i < count; i++)
|
|
{
|
|
curTime = (float)node->Values[i * 2 + 1];
|
|
curColor = (Color)node->Values[i * 2 + 2];
|
|
|
|
if (time <= curTime)
|
|
{
|
|
value = Color::Lerp(prevColor, curColor, Math::Saturate((time - prevTime) / (curTime - prevTime)));
|
|
break;
|
|
}
|
|
|
|
prevTime = curTime;
|
|
prevColor = curColor;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
// Curve
|
|
#define SAMPLE_CURVE(id, curves, type, graphType) \
|
|
case id: \
|
|
{ \
|
|
const auto& curve = GetCurrentGraph()->curves[node->Data.Curve.CurveIndex]; \
|
|
const float time = (float)tryGetValue(node->GetBox(0), Value::Zero); \
|
|
value.Type = VariantType(VariantType::graphType); \
|
|
curve.Evaluate(*(type*)value.AsData, time, false); \
|
|
break; \
|
|
}
|
|
SAMPLE_CURVE(12, FloatCurves, float, Float)
|
|
SAMPLE_CURVE(13, Vector2Curves, Vector2, Vector2)
|
|
SAMPLE_CURVE(14, Vector3Curves, Vector3, Vector3)
|
|
SAMPLE_CURVE(15, Vector4Curves, Vector4, Vector4)
|
|
#undef SETUP_CURVE
|
|
// Get Gameplay Global
|
|
case 16:
|
|
if (const auto asset = node->Assets[0].As<GameplayGlobals>())
|
|
{
|
|
const StringView& name = (StringView)node->Values[1];
|
|
const auto e = asset->Variables.Find(name);
|
|
value = e != asset->Variables.End() ? e->Value.Value : Value::Zero;
|
|
}
|
|
else
|
|
{
|
|
value = Value::Zero;
|
|
}
|
|
break;
|
|
// Platform Switch
|
|
case 17:
|
|
{
|
|
int32 boxId = 1;
|
|
switch (PLATFORM_TYPE)
|
|
{
|
|
case PlatformType::Windows:
|
|
boxId = 2;
|
|
break;
|
|
case PlatformType::XboxOne:
|
|
boxId = 3;
|
|
break;
|
|
case PlatformType::UWP:
|
|
boxId = 4;
|
|
break;
|
|
case PlatformType::Linux:
|
|
boxId = 5;
|
|
break;
|
|
case PlatformType::PS4:
|
|
boxId = 6;
|
|
break;
|
|
case PlatformType::XboxScarlett:
|
|
boxId = 7;
|
|
break;
|
|
case PlatformType::Android:
|
|
boxId = 8;
|
|
break;
|
|
case PlatformType::Switch:
|
|
boxId = 9;
|
|
break;
|
|
case PlatformType::PS5:
|
|
boxId = 10;
|
|
break;
|
|
case PlatformType::Mac:
|
|
boxId = 11;
|
|
break;
|
|
default: ;
|
|
}
|
|
value = tryGetValue(node->GetBox(node->GetBox(boxId)->HasConnection() ? boxId : 1), Value::Zero);
|
|
break;
|
|
}
|
|
// Asset Reference
|
|
case 18:
|
|
value = ::LoadAsset((Guid)node->Values[0], Asset::TypeInitializer);
|
|
break;
|
|
// To String
|
|
case 20:
|
|
value.SetString(tryGetValue(node->GetBox(1), Value(StringView::Empty)).ToString());
|
|
break;
|
|
// Actor Reference
|
|
case 21:
|
|
value = Scripting::FindObject<Actor>((Guid)node->Values[0]);
|
|
break;
|
|
// As
|
|
case 22:
|
|
{
|
|
value = Value::Null;
|
|
const auto obj = (ScriptingObject*)tryGetValue(node->GetBox(1), Value::Null);
|
|
if (obj)
|
|
{
|
|
const StringView typeName(node->Values[0]);
|
|
const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
|
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
|
|
const auto objClass = obj->GetClass();
|
|
if (typeHandle && objClass && objClass->IsSubClassOf(typeHandle.GetType().ManagedClass))
|
|
value = obj;
|
|
}
|
|
break;
|
|
}
|
|
// Type Reference node
|
|
case 23:
|
|
{
|
|
const StringView typeName(node->Values[0]);
|
|
if (box->ID == 0)
|
|
value.SetTypename(typeName);
|
|
else
|
|
value = typeName;
|
|
break;
|
|
}
|
|
// Is
|
|
case 24:
|
|
{
|
|
value = Value::False;
|
|
const auto obj = (ScriptingObject*)tryGetValue(node->GetBox(1), Value::Null);
|
|
if (obj)
|
|
{
|
|
const StringView typeName(node->Values[0]);
|
|
const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
|
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
|
|
const auto objClass = obj->GetClass();
|
|
value.AsBool = typeHandle && objClass && objClass->IsSubClassOf(typeHandle.GetType().ManagedClass);
|
|
}
|
|
break;
|
|
}
|
|
// Is Null
|
|
case 27:
|
|
value = (void*)tryGetValue(node->GetBox(1), Value::Null) == nullptr;
|
|
break;
|
|
// Is Valid
|
|
case 28:
|
|
value = (void*)tryGetValue(node->GetBox(1), Value::Null) != nullptr;
|
|
break;
|
|
// Reroute
|
|
case 29:
|
|
value = tryGetValue(node->GetBox(0), Value::Zero);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupBoolean(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// NOT
|
|
case 1:
|
|
{
|
|
const bool a = (bool)tryGetValue(node->GetBox(0), Value::False);
|
|
value = !a;
|
|
break;
|
|
}
|
|
// AND, OR, XOR, NOR, NAND
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
{
|
|
const bool a = (bool)tryGetValue(node->GetBox(0), 0, node->Values[0]);
|
|
const bool b = (bool)tryGetValue(node->GetBox(1), 1, node->Values[1]);
|
|
bool result = false;
|
|
switch (node->TypeID)
|
|
{
|
|
// AND
|
|
case 2:
|
|
result = a && b;
|
|
break;
|
|
// OR
|
|
case 3:
|
|
result = a || b;
|
|
break;
|
|
// XOR
|
|
case 4:
|
|
result = !a != !b;
|
|
break;
|
|
// NOR
|
|
case 5:
|
|
result = !(a || b);
|
|
break;
|
|
// NAND
|
|
case 6:
|
|
result = !(a && b);
|
|
break;
|
|
}
|
|
value = result;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupBitwise(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// NOT
|
|
case 1:
|
|
{
|
|
const int32 a = (int32)tryGetValue(node->GetBox(0), Value(0));
|
|
value = !a;
|
|
break;
|
|
}
|
|
// AND, OR, XOR
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
{
|
|
const int32 a = (int32)tryGetValue(node->GetBox(0), 0, node->Values[0]);
|
|
const int32 b = (int32)tryGetValue(node->GetBox(1), 1, node->Values[1]);
|
|
int32 result = 0;
|
|
switch (node->TypeID)
|
|
{
|
|
// AND
|
|
case 2:
|
|
result = a & b;
|
|
break;
|
|
// OR
|
|
case 3:
|
|
result = a | b;
|
|
break;
|
|
// XOR
|
|
case 4:
|
|
result = a ^ b;
|
|
break;
|
|
}
|
|
value = result;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupComparisons(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// ==, !=, >, <=, >=
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
{
|
|
const Value a = tryGetValue(node->GetBox(0), 0, node->Values[0]);
|
|
const Value b = tryGetValue(node->GetBox(1), 1, node->Values[1]).Cast(a.Type);
|
|
bool result = false;
|
|
switch (node->TypeID)
|
|
{
|
|
case 1:
|
|
result = a == b;
|
|
break;
|
|
case 2:
|
|
result = a != b;
|
|
break;
|
|
case 3:
|
|
result = a > b;
|
|
break;
|
|
case 4:
|
|
result = a < b;
|
|
break;
|
|
case 5:
|
|
result = a <= b;
|
|
break;
|
|
case 6:
|
|
result = a >= b;
|
|
break;
|
|
}
|
|
value = result;
|
|
break;
|
|
}
|
|
// Switch On Bool
|
|
case 7:
|
|
{
|
|
const Value condition = tryGetValue(node->GetBox(0), Value::False);
|
|
if (condition)
|
|
value = tryGetValue(node->GetBox(2), 1, Value::Zero);
|
|
else
|
|
value = tryGetValue(node->GetBox(1), 0, Value::Zero);
|
|
break;
|
|
}
|
|
// Switch On Enum
|
|
case 8:
|
|
{
|
|
const Value v = tryGetValue(node->GetBox(0), Value::Null);
|
|
if (v.Type.Type == VariantType::Enum && node->Values.Count() == 1 && node->Values[0].Type.Type == VariantType::Blob)
|
|
{
|
|
int32* dataValues = (int32*)node->Values[0].AsBlob.Data;
|
|
int32 dataValuesCount = node->Values[0].AsBlob.Length / 4;
|
|
int32 vAsInt = (int32)v;
|
|
for (int32 i = 0; i < dataValuesCount; i++)
|
|
{
|
|
if (dataValues[i] == vAsInt)
|
|
{
|
|
value = tryGetValue(node->GetBox(i + 2), Value::Null);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
value = Value::Null;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
|
{
|
|
switch (node->TypeID)
|
|
{
|
|
// Random Float
|
|
case 208:
|
|
{
|
|
value = RAND;
|
|
break;
|
|
}
|
|
// Random Vector2
|
|
case 209:
|
|
{
|
|
value = Vector2(RAND, RAND);
|
|
break;
|
|
}
|
|
// Random Vector3
|
|
case 210:
|
|
{
|
|
value = Vector3(RAND, RAND, RAND);
|
|
break;
|
|
}
|
|
// Random Vector4
|
|
case 211:
|
|
{
|
|
value = Vector4(RAND, RAND, RAND, RAND);
|
|
break;
|
|
}
|
|
// Random Float Range
|
|
case 213:
|
|
{
|
|
auto& a = node->Values[0].AsFloat;
|
|
auto& b = node->Values[1].AsFloat;
|
|
value = Math::Lerp(a, b, RAND);
|
|
break;
|
|
}
|
|
// Random Vector2 Range
|
|
case 214:
|
|
{
|
|
auto a = (Vector2)node->Values[0];
|
|
auto b = (Vector2)node->Values[1];
|
|
value = Vector2(
|
|
Math::Lerp(a.X, b.X, RAND),
|
|
Math::Lerp(a.Y, b.Y, RAND)
|
|
);
|
|
break;
|
|
}
|
|
// Random Vector3 Range
|
|
case 215:
|
|
{
|
|
auto a = (Vector3)node->Values[0];
|
|
auto b = (Vector3)node->Values[1];
|
|
value = Vector3(
|
|
Math::Lerp(a.X, b.X, RAND),
|
|
Math::Lerp(a.Y, b.Y, RAND),
|
|
Math::Lerp(a.Z, b.Z, RAND)
|
|
);
|
|
break;
|
|
}
|
|
// Random Vector4 Range
|
|
case 216:
|
|
{
|
|
auto a = (Vector4)node->Values[0];
|
|
auto b = (Vector4)node->Values[1];
|
|
value = Vector4(
|
|
Math::Lerp(a.X, b.X, RAND),
|
|
Math::Lerp(a.Y, b.Y, RAND),
|
|
Math::Lerp(a.Z, b.Z, RAND),
|
|
Math::Lerp(a.W, b.W, RAND)
|
|
);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VisjectExecutor::ProcessGroupCollections(Box* box, Node* node, Value& value)
|
|
{
|
|
if (node->TypeID < 100)
|
|
{
|
|
// Array
|
|
Variant v = tryGetValue(node->GetBox(0), Value::Null);
|
|
ENSURE(v.Type.Type == VariantType::Array, String::Format(TEXT("Input value {0} is not an array."), v));
|
|
auto& array = v.AsArray();
|
|
Box* b;
|
|
switch (node->TypeID)
|
|
{
|
|
// Count
|
|
case 1:
|
|
value = array.Count();
|
|
break;
|
|
// Contains
|
|
case 2:
|
|
value = array.Contains(tryGetValue(node->GetBox(1), Value::Null));
|
|
break;
|
|
// Find
|
|
case 3:
|
|
b = node->GetBox(1);
|
|
ENSURE(b->HasConnection(), TEXT("Missing value to find."));
|
|
value = array.Find(eatBox(b->GetParent<Node>(), b->FirstConnection()));
|
|
break;
|
|
// Find Last
|
|
case 4:
|
|
b = node->GetBox(1);
|
|
ENSURE(b->HasConnection(), TEXT("Missing value to find."));
|
|
value = array.FindLast(eatBox(b->GetParent<Node>(), b->FirstConnection()));
|
|
break;
|
|
// Clear
|
|
case 5:
|
|
array.Clear();
|
|
value = MoveTemp(v);
|
|
break;
|
|
// Remove
|
|
case 6:
|
|
b = node->GetBox(1);
|
|
ENSURE(b->HasConnection(), TEXT("Missing value to remove."));
|
|
array.Remove(eatBox(b->GetParent<Node>(), b->FirstConnection()));
|
|
value = MoveTemp(v);
|
|
break;
|
|
// Remove At
|
|
case 7:
|
|
{
|
|
const int32 index = (int32)tryGetValue(node->GetBox(1), 0, Value::Null);
|
|
ENSURE(index >= 0 && index < array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count() - 1));
|
|
array.RemoveAt(index);
|
|
value = MoveTemp(v);
|
|
break;
|
|
}
|
|
// Add
|
|
case 8:
|
|
b = node->GetBox(1);
|
|
ENSURE(b->HasConnection(), TEXT("Missing value to add."));
|
|
array.Add(eatBox(b->GetParent<Node>(), b->FirstConnection()));
|
|
value = MoveTemp(v);
|
|
break;
|
|
// Insert
|
|
case 9:
|
|
{
|
|
b = node->GetBox(1);
|
|
ENSURE(b->HasConnection(), TEXT("Missing value to add."));
|
|
const int32 index = (int32)tryGetValue(node->GetBox(2), 0, Value::Null);
|
|
ENSURE(index >= 0 && index <= array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count()));
|
|
array.Insert(index, eatBox(b->GetParent<Node>(), b->FirstConnection()));
|
|
value = MoveTemp(v);
|
|
break;
|
|
}
|
|
// Get
|
|
case 10:
|
|
{
|
|
const int32 index = (int32)tryGetValue(node->GetBox(1), 0, Value::Null);
|
|
ENSURE(index >= 0 && index < array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count() - 1));
|
|
value = MoveTemp(array[index]);
|
|
break;
|
|
}
|
|
// Set
|
|
case 11:
|
|
{
|
|
b = node->GetBox(2);
|
|
ENSURE(b->HasConnection(), TEXT("Missing value to set."));
|
|
const int32 index = (int32)tryGetValue(node->GetBox(1), 0, Value::Null);
|
|
ENSURE(index >= 0 && index < array.Count(), String::Format(TEXT("Array index {0} is out of range [0;{1}]."), index, array.Count() - 1));
|
|
array[index] = MoveTemp(eatBox(b->GetParent<Node>(), b->FirstConnection()));
|
|
value = MoveTemp(v);
|
|
break;
|
|
}
|
|
// Sort
|
|
case 12:
|
|
Sorting::QuickSort(array.Get(), array.Count());
|
|
value = MoveTemp(v);
|
|
break;
|
|
// Reverse
|
|
case 13:
|
|
array.Reverse();
|
|
value = MoveTemp(v);
|
|
break;
|
|
}
|
|
}
|
|
}
|