Files
AR-Menu/Library/PackageCache/org.khronos.unitygltf@6b55d14e19c1/Runtime/Scripts/Interactivity/Schema/GltfInteractivityNode.cs
2025-11-30 08:35:03 +02:00

355 lines
12 KiB
C#

using UnityEngine;
namespace UnityGLTF.Interactivity.Schema
{
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
public class GltfInteractivityNode
{
public int Index;
public int OpDeclaration = -1;
public virtual GltfInteractivityNodeSchema Schema { get; protected set; }
// Data to be serialized into Gltf
public Dictionary<string, ConfigData> Configuration =
new Dictionary<string, ConfigData>();
public Dictionary<string, FlowSocketData> FlowConnections =
new Dictionary<string, FlowSocketData>();
public Dictionary<string, ValueSocketData> ValueInConnection =
new Dictionary<string, ValueSocketData>();
public Dictionary<string, string> MetaData = new Dictionary<string, string>();
public void RemoveUnconnectedFlows()
{
var keys = FlowConnections.Keys.ToList();
foreach (var key in keys)
{
if (FlowConnections[key].Node == null || FlowConnections[key].Node == -1)
FlowConnections.Remove(key);
}
}
public void SetFlowOut(string socketId, GltfInteractivityNode targetNode, string targetSocketId)
{
if (!FlowConnections.TryGetValue(socketId, out var socket))
{
socket = new FlowSocketData();
FlowConnections.Add(socketId, socket);
}
socket.Node = targetNode.Index;
socket.Socket = targetSocketId;
}
public virtual void SetSchema(GltfInteractivityNodeSchema schema, bool applySocketDescriptors, bool clearExistingSocketData = true)
{
this.Schema = schema;
if (applySocketDescriptors)
{
Schema = schema;
if (clearExistingSocketData)
{
Configuration.Clear();
FlowConnections.Clear();
ValueInConnection.Clear();
MetaData.Clear();
}
foreach (var descriptor in Schema.Configuration)
{
Configuration.Add(descriptor.Key, new ConfigData());
}
foreach (var descriptor in Schema.InputValueSockets)
{
ValueInConnection.Add(descriptor.Key, new ValueSocketData()
{
Type = GltfTypes.TypeIndexByGltfSignature(descriptor.Value.SupportedTypes[0]),
typeRestriction = descriptor.Value.typeRestriction
});
}
foreach (var descriptor in Schema.OutputFlowSockets)
{
FlowConnections.Add(descriptor.Key, new FlowSocketData());
}
foreach (GltfInteractivityNodeSchema.MetaDataEntry descriptor in Schema.MetaDatas)
{
MetaData.Add(descriptor.key, descriptor.value);
}
}
}
public void SetValueInSocketSource(string socketId, GltfInteractivityNode sourceNode, string sourceSocketId, TypeRestriction typeRestriction = null)
{
if (!ValueInConnection.TryGetValue(socketId, out var socket))
{
socket = new ValueSocketData();
ValueInConnection.Add(socketId, socket);
}
socket.Node = sourceNode.Index;
socket.Socket = sourceSocketId;
socket.Value = null;
socket.Type = -1;
if (typeRestriction != null)
socket.typeRestriction = typeRestriction;
}
public void SetValueInSocket(string socketId, object value, TypeRestriction typeRestriction = null)
{
if (!ValueInConnection.TryGetValue(socketId, out var socket))
{
socket = new ValueSocketData();
ValueInConnection.Add(socketId, socket);
}
socket.Node = null;
socket.Socket = null;
socket.Value = value;
if (value != null)
socket.Type = GltfTypes.TypeIndex(value.GetType());
if (typeRestriction != null)
socket.typeRestriction = typeRestriction;
}
public GltfInteractivityNode(GltfInteractivityNodeSchema schema)
{
SetSchema(schema, true);
}
public virtual JObject SerializeObject()
{
var configs = new JObject();
foreach (var config in Configuration)
configs.Add(config.Key, config.Value.SerializeObject());
var values = new JObject();
foreach (var value in ValueInConnection)
values.Add(value.Key, value.Value.SerializeObject());
var flows = new JObject();
foreach (var flow in FlowConnections)
if (flow.Value.Node != null)
flows.Add(flow.Key, flow.Value.SerializeObject());
JObject jo = new JObject
{
new JProperty("declaration", OpDeclaration),
new JProperty("configuration",configs),
new JProperty("values", values),
new JProperty("flows", flows),
};
// Remove all empty arrays in the first level of the JSON Object
jo.SelectTokens("$.*")
.OfType<JArray>()
.Where(x => x.Type == JTokenType.Array && !x.HasValues)
.Select(a => a.Parent)
.ToList()
.ForEach(a => a.Remove());
return jo;
}
public class ConfigData
{
// data field holds index in list of types supported in the extension
public object Value = null;
public JObject SerializeObject()
{
if (Value == null)
return null;
var jObject = new JObject
{
};
ValueSerializer.Serialize(Value, jObject);
return jObject;
}
}
/// <summary>
/// Describes a socket connection's data.
///
/// Only outgoing connections from this node to the next are required to be serialized.
/// </summary>
public abstract class SocketData
{
public string Socket = null;
public int? Node = null;
public override string ToString()
{
return $"Node: {(Node.HasValue ? Node.Value.ToString() : "null")}, Socket: \"{Socket}\"";
}
}
public class EventValues
{
public int Type = -1;
public object Value = null;
public JObject SerializeObject()
{
JObject valueObject = new JObject()
{
new JProperty("type", Type),
};
ValueSerializer.Serialize(Value, valueObject);
return valueObject;
}
public override string ToString()
{
return $"{base.ToString()}, Type: {Type}";
}
}
/// <summary>
/// Describes Flow data for the node.
///
/// Only outgoing connections from this node to the next are required to be serialized.
/// </summary>
public class FlowSocketData : SocketData
{
public JObject SerializeObject()
{
return new JObject
{
new JProperty("node", Node),
new JProperty("socket", Socket)
};
}
}
public static class ValueSerializer
{
public static void Serialize(object value, JObject valueObject)
{
if (value == null)
return;
if (value is Color color)
{
valueObject.Add(new JProperty("value", new JArray(color.r, color.g, color.b, color.a)));
}
else if (value is Color32 color32)
{
Color col = color32;
valueObject.Add(new JProperty("value", new JArray(col.r, col.g, col.b, col.a)));
}
else if (value is Matrix4x4 m4)
{
// TODO check if this is the correct row-column order
valueObject.Add(new JProperty("value", new JArray(
m4.m00, m4.m01, m4.m02, m4.m03,
m4.m10, m4.m11, m4.m12, m4.m13,
m4.m20, m4.m21, m4.m22, m4.m23,
m4.m30, m4.m31, m4.m32, m4.m33)));
}
else if (value is Vector4 v4)
{
valueObject.Add(new JProperty("value", new JArray(v4.x, v4.y, v4.z, v4.w)));
}
else if (value is Vector3 v3)
{
valueObject.Add(new JProperty("value", new JArray(v3.x, v3.y, v3.z)));
}
else if (value is Vector2 v2)
{
valueObject.Add(new JProperty("value", new JArray(v2.x, v2.y)));
}
else if (value is Quaternion q)
{
valueObject.Add(new JProperty("value", new JArray(q.x, q.y, q.z, q.w)));
}
else if (value is bool b)
{
valueObject.Add(new JProperty("value", new JArray(b)));
}
else if (value is string s)
{
valueObject.Add(new JProperty("value", new JArray(s)));
}
else if (value is int i)
{
valueObject.Add(new JProperty("value", new JArray(i)));
}
else if (value is float f)
{
valueObject.Add(new JProperty("value", new JArray(f)));
}
else
{
valueObject.Add(new JProperty("value", new JArray(value)));
}
}
}
public class OutputValueSocketData
{
public ExpectedType expectedType;
}
/// <summary>
/// Describes value data for the node.
///
/// Either the Value field will be used when the socket is defined by a literal in-line
/// value or the Node and Socket fields will be used when the socket gets/sets the value
/// through a connection to another Node's value socket.
/// </summary>
public class ValueSocketData : SocketData
{
public int Type = -1;
public object Value = null;
public TypeRestriction typeRestriction = null;
public JObject SerializeObject()
{
JObject valueObject = new JObject()
{
};
// Optional fields are only added if non-null
if (Node != null)
{
valueObject.Add(new JProperty("node", Node));
}
if (Socket != null)
{
valueObject.Add(new JProperty("socket", Socket));
}
if (Value != null)
{
valueObject.Add(new JProperty("type", Type));
ValueSerializer.Serialize(Value, valueObject);
}
return valueObject;
}
public override string ToString()
{
return $"{base.ToString()}, Value: {Value}";
}
}
}
}