using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; using UnityEngine; namespace MCPForUnity.Editor.Helpers { /// /// Utility class for parsing JSON tokens into Unity vector, math, and animation types. /// Supports both array format [x, y, z] and object format {x: 1, y: 2, z: 3}. /// public static class VectorParsing { /// /// Parses a JToken (array or object) into a Vector3. /// /// The JSON token to parse /// The parsed Vector3 or null if parsing fails public static Vector3? ParseVector3(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { // Array format: [x, y, z] if (token is JArray array && array.Count >= 3) { return new Vector3( array[0].ToObject(), array[1].ToObject(), array[2].ToObject() ); } // Object format: {x: 1, y: 2, z: 3} if (token is JObject obj && obj.ContainsKey("x") && obj.ContainsKey("y") && obj.ContainsKey("z")) { return new Vector3( obj["x"].ToObject(), obj["y"].ToObject(), obj["z"].ToObject() ); } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Vector3 from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken into a Vector3, returning a default value if parsing fails. /// public static Vector3 ParseVector3OrDefault(JToken token, Vector3 defaultValue = default) { return ParseVector3(token) ?? defaultValue; } /// /// Parses a JToken (array or object) into a Vector2. /// /// The JSON token to parse /// The parsed Vector2 or null if parsing fails public static Vector2? ParseVector2(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { // Array format: [x, y] if (token is JArray array && array.Count >= 2) { return new Vector2( array[0].ToObject(), array[1].ToObject() ); } // Object format: {x: 1, y: 2} if (token is JObject obj && obj.ContainsKey("x") && obj.ContainsKey("y")) { return new Vector2( obj["x"].ToObject(), obj["y"].ToObject() ); } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Vector2 from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken (array or object) into a Quaternion. /// Supports both euler angles [x, y, z] and quaternion components [x, y, z, w]. /// Note: Raw quaternion components are NOT normalized. Callers should normalize if needed /// for operations like interpolation where non-unit quaternions cause issues. /// /// The JSON token to parse /// If true, treats 3-element arrays as euler angles /// The parsed Quaternion or null if parsing fails public static Quaternion? ParseQuaternion(JToken token, bool asEulerAngles = true) { if (token == null || token.Type == JTokenType.Null) return null; try { if (token is JArray array) { // Quaternion components: [x, y, z, w] if (array.Count >= 4) { return new Quaternion( array[0].ToObject(), array[1].ToObject(), array[2].ToObject(), array[3].ToObject() ); } // Euler angles: [x, y, z] if (array.Count >= 3 && asEulerAngles) { return Quaternion.Euler( array[0].ToObject(), array[1].ToObject(), array[2].ToObject() ); } } // Object format: {x: 0, y: 0, z: 0, w: 1} if (token is JObject obj) { if (obj.ContainsKey("x") && obj.ContainsKey("y") && obj.ContainsKey("z") && obj.ContainsKey("w")) { return new Quaternion( obj["x"].ToObject(), obj["y"].ToObject(), obj["z"].ToObject(), obj["w"].ToObject() ); } // Euler format in object: {x: 45, y: 90, z: 0} (as euler angles) if (obj.ContainsKey("x") && obj.ContainsKey("y") && obj.ContainsKey("z") && asEulerAngles) { return Quaternion.Euler( obj["x"].ToObject(), obj["y"].ToObject(), obj["z"].ToObject() ); } } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Quaternion from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken (array or object) into a Color. /// Supports both [r, g, b, a] and {r: 1, g: 1, b: 1, a: 1} formats. /// /// The JSON token to parse /// The parsed Color or null if parsing fails public static Color? ParseColor(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { // Array format: [r, g, b, a] or [r, g, b] if (token is JArray array) { if (array.Count >= 4) { return new Color( array[0].ToObject(), array[1].ToObject(), array[2].ToObject(), array[3].ToObject() ); } if (array.Count >= 3) { return new Color( array[0].ToObject(), array[1].ToObject(), array[2].ToObject(), 1f // Default alpha ); } } // Object format: {r: 1, g: 1, b: 1, a: 1} if (token is JObject obj && obj.ContainsKey("r") && obj.ContainsKey("g") && obj.ContainsKey("b")) { float a = obj.ContainsKey("a") ? obj["a"].ToObject() : 1f; return new Color( obj["r"].ToObject(), obj["g"].ToObject(), obj["b"].ToObject(), a ); } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Color from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken into a Color, returning a default value if parsing fails. /// Added for ManageVFX refactoring. /// public static Color ParseColorOrDefault(JToken token, Color defaultValue = default) { if (defaultValue == default) defaultValue = Color.black; return ParseColor(token) ?? defaultValue; } /// /// Parses a JToken (array or object) into a Vector4. /// Added for ManageVFX refactoring. /// /// The JSON token to parse /// The parsed Vector4 or null if parsing fails public static Vector4? ParseVector4(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { // Array format: [x, y, z, w] if (token is JArray array && array.Count >= 4) { return new Vector4( array[0].ToObject(), array[1].ToObject(), array[2].ToObject(), array[3].ToObject() ); } // Object format: {x: 1, y: 2, z: 3, w: 4} if (token is JObject obj && obj.ContainsKey("x") && obj.ContainsKey("y") && obj.ContainsKey("z") && obj.ContainsKey("w")) { return new Vector4( obj["x"].ToObject(), obj["y"].ToObject(), obj["z"].ToObject(), obj["w"].ToObject() ); } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Vector4 from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken into a Vector4, returning a default value if parsing fails. /// Added for ManageVFX refactoring. /// public static Vector4 ParseVector4OrDefault(JToken token, Vector4 defaultValue = default) { return ParseVector4(token) ?? defaultValue; } /// /// Parses a JToken into a Gradient. /// Supports formats: /// - Simple: {startColor: [r,g,b,a], endColor: [r,g,b,a]} /// - Full: {colorKeys: [{color: [r,g,b,a], time: 0.0}, ...], alphaKeys: [{alpha: 1.0, time: 0.0}, ...]} /// Added for ManageVFX refactoring. /// /// The JSON token to parse /// The parsed Gradient or null if parsing fails public static Gradient ParseGradient(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { Gradient gradient = new Gradient(); if (token is JObject obj) { // Simple format: {startColor: ..., endColor: ...} if (obj.ContainsKey("startColor")) { Color startColor = ParseColorOrDefault(obj["startColor"]); Color endColor = ParseColorOrDefault(obj["endColor"] ?? obj["startColor"]); float startAlpha = obj["startAlpha"]?.ToObject() ?? startColor.a; float endAlpha = obj["endAlpha"]?.ToObject() ?? endColor.a; gradient.SetKeys( new GradientColorKey[] { new GradientColorKey(startColor, 0f), new GradientColorKey(endColor, 1f) }, new GradientAlphaKey[] { new GradientAlphaKey(startAlpha, 0f), new GradientAlphaKey(endAlpha, 1f) } ); return gradient; } // Full format: {colorKeys: [...], alphaKeys: [...]} var colorKeys = new List(); var alphaKeys = new List(); if (obj["colorKeys"] is JArray colorKeysArr) { foreach (var key in colorKeysArr) { Color color = ParseColorOrDefault(key["color"]); float time = key["time"]?.ToObject() ?? 0f; colorKeys.Add(new GradientColorKey(color, time)); } } if (obj["alphaKeys"] is JArray alphaKeysArr) { foreach (var key in alphaKeysArr) { float alpha = key["alpha"]?.ToObject() ?? 1f; float time = key["time"]?.ToObject() ?? 0f; alphaKeys.Add(new GradientAlphaKey(alpha, time)); } } // Ensure at least 2 keys if (colorKeys.Count == 0) { colorKeys.Add(new GradientColorKey(Color.white, 0f)); colorKeys.Add(new GradientColorKey(Color.white, 1f)); } if (alphaKeys.Count == 0) { alphaKeys.Add(new GradientAlphaKey(1f, 0f)); alphaKeys.Add(new GradientAlphaKey(1f, 1f)); } gradient.SetKeys(colorKeys.ToArray(), alphaKeys.ToArray()); return gradient; } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Gradient from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken into a Gradient, returning a default gradient if parsing fails. /// Added for ManageVFX refactoring. /// public static Gradient ParseGradientOrDefault(JToken token) { var result = ParseGradient(token); if (result != null) return result; // Return default white gradient var gradient = new Gradient(); gradient.SetKeys( new GradientColorKey[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) }, new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) } ); return gradient; } /// /// Parses a JToken into an AnimationCurve. /// Supports formats: /// - Constant: 1.0 (number) /// - Simple: {start: 0.0, end: 1.0} /// - Full: {keys: [{time: 0.0, value: 1.0, inTangent: 0.0, outTangent: 0.0}, ...]} /// Added for ManageVFX refactoring. /// /// The JSON token to parse /// The parsed AnimationCurve or null if parsing fails public static AnimationCurve ParseAnimationCurve(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { // Constant value: just a number if (token.Type == JTokenType.Float || token.Type == JTokenType.Integer) { return AnimationCurve.Constant(0f, 1f, token.ToObject()); } if (token is JObject obj) { // Full format: {keys: [...]} if (obj["keys"] is JArray keys) { AnimationCurve curve = new AnimationCurve(); foreach (var key in keys) { float time = key["time"]?.ToObject() ?? 0f; float value = key["value"]?.ToObject() ?? 1f; float inTangent = key["inTangent"]?.ToObject() ?? 0f; float outTangent = key["outTangent"]?.ToObject() ?? 0f; curve.AddKey(new Keyframe(time, value, inTangent, outTangent)); } return curve; } // Simple format: {start: 0.0, end: 1.0} or {startValue: 0.0, endValue: 1.0} if (obj.ContainsKey("start") || obj.ContainsKey("startValue") || obj.ContainsKey("end") || obj.ContainsKey("endValue")) { float startValue = obj["start"]?.ToObject() ?? obj["startValue"]?.ToObject() ?? 1f; float endValue = obj["end"]?.ToObject() ?? obj["endValue"]?.ToObject() ?? 1f; AnimationCurve curve = new AnimationCurve(); curve.AddKey(0f, startValue); curve.AddKey(1f, endValue); return curve; } } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse AnimationCurve from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken into an AnimationCurve, returning a constant curve if parsing fails. /// Added for ManageVFX refactoring. /// /// The JSON token to parse /// The constant value for the default curve public static AnimationCurve ParseAnimationCurveOrDefault(JToken token, float defaultValue = 1f) { return ParseAnimationCurve(token) ?? AnimationCurve.Constant(0f, 1f, defaultValue); } /// /// Parses a JToken into a Rect. /// Supports {x, y, width, height} format. /// public static Rect? ParseRect(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { if (token is JObject obj && obj.ContainsKey("x") && obj.ContainsKey("y") && obj.ContainsKey("width") && obj.ContainsKey("height")) { return new Rect( obj["x"].ToObject(), obj["y"].ToObject(), obj["width"].ToObject(), obj["height"].ToObject() ); } // Array format: [x, y, width, height] if (token is JArray array && array.Count >= 4) { return new Rect( array[0].ToObject(), array[1].ToObject(), array[2].ToObject(), array[3].ToObject() ); } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Rect from '{token}': {ex.Message}"); } return null; } /// /// Parses a JToken into a Bounds. /// Supports {center: {x,y,z}, size: {x,y,z}} format. /// public static Bounds? ParseBounds(JToken token) { if (token == null || token.Type == JTokenType.Null) return null; try { if (token is JObject obj && obj.ContainsKey("center") && obj.ContainsKey("size")) { var center = ParseVector3(obj["center"]) ?? Vector3.zero; var size = ParseVector3(obj["size"]) ?? Vector3.zero; return new Bounds(center, size); } } catch (Exception ex) { McpLog.Warn($"[VectorParsing] Failed to parse Bounds from '{token}': {ex.Message}"); } return null; } } }