438 lines
17 KiB
C#
438 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using MCPForUnity.Editor.Helpers;
|
|
using MCPForUnity.Runtime.Helpers;
|
|
|
|
namespace MCPForUnity.Editor.Tools.Physics
|
|
{
|
|
internal static class PhysicsRigidbodyOps
|
|
{
|
|
private static readonly HashSet<string> Valid3DKeys = new HashSet<string>
|
|
{
|
|
"mass", "drag", "lineardamping", "angulardrag", "angulardamping",
|
|
"usegravity", "iskinematic", "interpolation", "collisiondetectionmode", "constraints"
|
|
};
|
|
|
|
private static readonly HashSet<string> Valid2DKeys = new HashSet<string>
|
|
{
|
|
"mass", "gravityscale", "drag", "lineardamping", "angulardrag", "angulardamping",
|
|
"bodytype", "simulated", "collisiondetectionmode", "constraints"
|
|
};
|
|
|
|
public static object GetRigidbody(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
|
|
var targetToken = p.GetRaw("target");
|
|
if (targetToken == null || string.IsNullOrEmpty(targetToken.ToString()))
|
|
return new ErrorResponse("'target' parameter is required.");
|
|
|
|
string searchMethod = p.Get("search_method");
|
|
string dimensionParam = p.Get("dimension")?.ToLowerInvariant();
|
|
|
|
var go = GameObjectLookup.FindByTarget(targetToken, searchMethod ?? "by_name");
|
|
if (go == null)
|
|
return new ErrorResponse($"GameObject not found: '{targetToken}'.");
|
|
|
|
bool has3D = go.GetComponent<Rigidbody>() != null;
|
|
bool has2D = go.GetComponent<Rigidbody2D>() != null;
|
|
|
|
if (!has3D && !has2D)
|
|
return new ErrorResponse($"No Rigidbody or Rigidbody2D found on '{go.name}'.");
|
|
|
|
bool is2D;
|
|
if (dimensionParam == "2d")
|
|
is2D = true;
|
|
else if (dimensionParam == "3d")
|
|
is2D = false;
|
|
else
|
|
is2D = has2D && !has3D;
|
|
|
|
if (is2D)
|
|
return GetRigidbody2D(go);
|
|
|
|
return GetRigidbody3D(go);
|
|
}
|
|
|
|
private static object GetRigidbody3D(GameObject go)
|
|
{
|
|
var rb = go.GetComponent<Rigidbody>();
|
|
if (rb == null)
|
|
return new ErrorResponse($"No Rigidbody found on '{go.name}'.");
|
|
|
|
var data = new Dictionary<string, object>
|
|
{
|
|
["target"] = go.name,
|
|
["instanceID"] = go.GetInstanceIDCompat(),
|
|
["dimension"] = "3d",
|
|
["mass"] = rb.mass,
|
|
#if UNITY_6000_0_OR_NEWER
|
|
["linearDamping"] = rb.linearDamping,
|
|
["angularDamping"] = rb.angularDamping,
|
|
#else
|
|
["linearDamping"] = rb.drag,
|
|
["angularDamping"] = rb.angularDrag,
|
|
#endif
|
|
["useGravity"] = rb.useGravity,
|
|
["isKinematic"] = rb.isKinematic,
|
|
["position"] = new[] { rb.position.x, rb.position.y, rb.position.z },
|
|
["rotation"] = new[] { rb.rotation.x, rb.rotation.y, rb.rotation.z, rb.rotation.w },
|
|
#if UNITY_6000_0_OR_NEWER
|
|
["velocity"] = new[] { rb.linearVelocity.x, rb.linearVelocity.y, rb.linearVelocity.z },
|
|
#else
|
|
["velocity"] = new[] { rb.velocity.x, rb.velocity.y, rb.velocity.z },
|
|
#endif
|
|
["angularVelocity"] = new[] { rb.angularVelocity.x, rb.angularVelocity.y, rb.angularVelocity.z },
|
|
["interpolation"] = rb.interpolation.ToString(),
|
|
["collisionDetectionMode"] = rb.collisionDetectionMode.ToString(),
|
|
["constraints"] = rb.constraints.ToString(),
|
|
["isSleeping"] = rb.IsSleeping(),
|
|
["centerOfMass"] = new[] { rb.centerOfMass.x, rb.centerOfMass.y, rb.centerOfMass.z },
|
|
["maxAngularVelocity"] = rb.maxAngularVelocity,
|
|
#if UNITY_6000_0_OR_NEWER
|
|
["maxLinearVelocity"] = rb.maxLinearVelocity,
|
|
#endif
|
|
};
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Retrieved Rigidbody state for '{go.name}'.",
|
|
data
|
|
};
|
|
}
|
|
|
|
private static object GetRigidbody2D(GameObject go)
|
|
{
|
|
var rb2d = go.GetComponent<Rigidbody2D>();
|
|
if (rb2d == null)
|
|
return new ErrorResponse($"No Rigidbody2D found on '{go.name}'.");
|
|
|
|
var data = new Dictionary<string, object>
|
|
{
|
|
["target"] = go.name,
|
|
["instanceID"] = go.GetInstanceIDCompat(),
|
|
["dimension"] = "2d",
|
|
["mass"] = rb2d.mass,
|
|
["gravityScale"] = rb2d.gravityScale,
|
|
#if UNITY_6000_0_OR_NEWER
|
|
["linearDamping"] = rb2d.linearDamping,
|
|
["angularDamping"] = rb2d.angularDamping,
|
|
#else
|
|
["linearDamping"] = rb2d.drag,
|
|
["angularDamping"] = rb2d.angularDrag,
|
|
#endif
|
|
["bodyType"] = rb2d.bodyType.ToString(),
|
|
["simulated"] = rb2d.simulated,
|
|
["position"] = new[] { rb2d.position.x, rb2d.position.y },
|
|
["rotation"] = rb2d.rotation,
|
|
#if UNITY_6000_0_OR_NEWER
|
|
["velocity"] = new[] { rb2d.linearVelocity.x, rb2d.linearVelocity.y },
|
|
#else
|
|
["velocity"] = new[] { rb2d.velocity.x, rb2d.velocity.y },
|
|
#endif
|
|
["angularVelocity"] = rb2d.angularVelocity,
|
|
["collisionDetectionMode"] = rb2d.collisionDetectionMode.ToString(),
|
|
["constraints"] = rb2d.constraints.ToString(),
|
|
["isSleeping"] = rb2d.IsSleeping(),
|
|
["isAwake"] = rb2d.IsAwake(),
|
|
["centerOfMass"] = new[] { rb2d.centerOfMass.x, rb2d.centerOfMass.y },
|
|
};
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Retrieved Rigidbody2D state for '{go.name}'.",
|
|
data
|
|
};
|
|
}
|
|
|
|
public static object ConfigureRigidbody(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
|
|
var targetToken = p.GetRaw("target");
|
|
if (targetToken == null || string.IsNullOrEmpty(targetToken.ToString()))
|
|
return new ErrorResponse("'target' parameter is required.");
|
|
|
|
var properties = p.GetRaw("properties") as JObject;
|
|
if (properties == null || properties.Count == 0)
|
|
return new ErrorResponse("'properties' parameter is required and must be a non-empty object.");
|
|
|
|
string searchMethod = p.Get("search_method");
|
|
string dimensionParam = p.Get("dimension")?.ToLowerInvariant();
|
|
|
|
var go = GameObjectLookup.FindByTarget(targetToken, searchMethod ?? "by_name");
|
|
if (go == null)
|
|
return new ErrorResponse($"GameObject not found: '{targetToken}'.");
|
|
|
|
bool has3D = go.GetComponent<Rigidbody>() != null;
|
|
bool has2D = go.GetComponent<Rigidbody2D>() != null;
|
|
bool is2D;
|
|
|
|
if (dimensionParam == "2d")
|
|
is2D = true;
|
|
else if (dimensionParam == "3d")
|
|
is2D = false;
|
|
else
|
|
is2D = has2D && !has3D;
|
|
|
|
if (is2D)
|
|
return ConfigureRigidbody2D(go, properties);
|
|
|
|
return ConfigureRigidbody3D(go, properties);
|
|
}
|
|
|
|
private static object ConfigureRigidbody3D(GameObject go, JObject properties)
|
|
{
|
|
var rb = go.GetComponent<Rigidbody>();
|
|
if (rb == null)
|
|
return new ErrorResponse($"No Rigidbody found on '{go.name}'.");
|
|
|
|
// Validate all keys before applying any changes
|
|
var unknown = new List<string>();
|
|
foreach (var prop in properties.Properties())
|
|
{
|
|
string key = prop.Name.ToLowerInvariant().Replace("_", "");
|
|
if (!Valid3DKeys.Contains(key))
|
|
unknown.Add(prop.Name);
|
|
}
|
|
if (unknown.Count > 0)
|
|
return new ErrorResponse(
|
|
$"Unknown Rigidbody property(ies): {string.Join(", ", unknown)}.");
|
|
|
|
Undo.RecordObject(rb, "Configure Rigidbody");
|
|
|
|
var changed = new List<string>();
|
|
|
|
foreach (var prop in properties.Properties())
|
|
{
|
|
string key = prop.Name.ToLowerInvariant().Replace("_", "");
|
|
switch (key)
|
|
{
|
|
case "mass":
|
|
rb.mass = prop.Value.Value<float>();
|
|
changed.Add("mass");
|
|
break;
|
|
case "drag":
|
|
case "lineardamping":
|
|
#if UNITY_6000_0_OR_NEWER
|
|
rb.linearDamping = prop.Value.Value<float>();
|
|
#else
|
|
rb.drag = prop.Value.Value<float>();
|
|
#endif
|
|
changed.Add("linearDamping");
|
|
break;
|
|
case "angulardrag":
|
|
case "angulardamping":
|
|
#if UNITY_6000_0_OR_NEWER
|
|
rb.angularDamping = prop.Value.Value<float>();
|
|
#else
|
|
rb.angularDrag = prop.Value.Value<float>();
|
|
#endif
|
|
changed.Add("angularDamping");
|
|
break;
|
|
case "usegravity":
|
|
rb.useGravity = prop.Value.Value<bool>();
|
|
changed.Add("useGravity");
|
|
break;
|
|
case "iskinematic":
|
|
rb.isKinematic = prop.Value.Value<bool>();
|
|
changed.Add("isKinematic");
|
|
break;
|
|
case "interpolation":
|
|
{
|
|
string val = prop.Value.ToString();
|
|
if (Enum.TryParse<RigidbodyInterpolation>(val, true, out var interp))
|
|
{
|
|
rb.interpolation = interp;
|
|
changed.Add("interpolation");
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(
|
|
$"Invalid interpolation value: '{val}'. Valid: None, Interpolate, Extrapolate.");
|
|
}
|
|
break;
|
|
}
|
|
case "collisiondetectionmode":
|
|
{
|
|
string val = prop.Value.ToString();
|
|
if (Enum.TryParse<CollisionDetectionMode>(val, true, out var mode))
|
|
{
|
|
rb.collisionDetectionMode = mode;
|
|
changed.Add("collisionDetectionMode");
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(
|
|
$"Invalid collisionDetectionMode: '{val}'. Valid: Discrete, Continuous, ContinuousDynamic, ContinuousSpeculative.");
|
|
}
|
|
break;
|
|
}
|
|
case "constraints":
|
|
{
|
|
var token = prop.Value;
|
|
if (token.Type == JTokenType.Integer)
|
|
{
|
|
rb.constraints = (RigidbodyConstraints)token.Value<int>();
|
|
changed.Add("constraints");
|
|
}
|
|
else
|
|
{
|
|
string val = token.ToString();
|
|
if (Enum.TryParse<RigidbodyConstraints>(val, true, out var c))
|
|
{
|
|
rb.constraints = c;
|
|
changed.Add("constraints");
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(
|
|
$"Invalid constraints value: '{val}'. Use enum name (e.g. 'FreezePositionX, FreezeRotationY') or int flags.");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorUtility.SetDirty(rb);
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Configured Rigidbody on '{go.name}': {string.Join(", ", changed)}.",
|
|
data = new { target = go.name, dimension = "3d", changed }
|
|
};
|
|
}
|
|
|
|
private static object ConfigureRigidbody2D(GameObject go, JObject properties)
|
|
{
|
|
var rb2d = go.GetComponent<Rigidbody2D>();
|
|
if (rb2d == null)
|
|
return new ErrorResponse($"No Rigidbody2D found on '{go.name}'.");
|
|
|
|
// Validate all keys before applying any changes
|
|
var unknown = new List<string>();
|
|
foreach (var prop in properties.Properties())
|
|
{
|
|
string key = prop.Name.ToLowerInvariant().Replace("_", "");
|
|
if (!Valid2DKeys.Contains(key))
|
|
unknown.Add(prop.Name);
|
|
}
|
|
if (unknown.Count > 0)
|
|
return new ErrorResponse(
|
|
$"Unknown Rigidbody2D property(ies): {string.Join(", ", unknown)}.");
|
|
|
|
Undo.RecordObject(rb2d, "Configure Rigidbody2D");
|
|
|
|
var changed = new List<string>();
|
|
|
|
foreach (var prop in properties.Properties())
|
|
{
|
|
string key = prop.Name.ToLowerInvariant().Replace("_", "");
|
|
switch (key)
|
|
{
|
|
case "mass":
|
|
rb2d.mass = prop.Value.Value<float>();
|
|
changed.Add("mass");
|
|
break;
|
|
case "gravityscale":
|
|
rb2d.gravityScale = prop.Value.Value<float>();
|
|
changed.Add("gravityScale");
|
|
break;
|
|
case "drag":
|
|
case "lineardamping":
|
|
#if UNITY_6000_0_OR_NEWER
|
|
rb2d.linearDamping = prop.Value.Value<float>();
|
|
#else
|
|
rb2d.drag = prop.Value.Value<float>();
|
|
#endif
|
|
changed.Add("linearDamping");
|
|
break;
|
|
case "angulardrag":
|
|
case "angulardamping":
|
|
#if UNITY_6000_0_OR_NEWER
|
|
rb2d.angularDamping = prop.Value.Value<float>();
|
|
#else
|
|
rb2d.angularDrag = prop.Value.Value<float>();
|
|
#endif
|
|
changed.Add("angularDamping");
|
|
break;
|
|
case "bodytype":
|
|
{
|
|
string val = prop.Value.ToString();
|
|
if (Enum.TryParse<RigidbodyType2D>(val, true, out var bt))
|
|
{
|
|
rb2d.bodyType = bt;
|
|
changed.Add("bodyType");
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(
|
|
$"Invalid bodyType: '{val}'. Valid: Dynamic, Kinematic, Static.");
|
|
}
|
|
break;
|
|
}
|
|
case "simulated":
|
|
rb2d.simulated = prop.Value.Value<bool>();
|
|
changed.Add("simulated");
|
|
break;
|
|
case "collisiondetectionmode":
|
|
{
|
|
string val = prop.Value.ToString();
|
|
if (Enum.TryParse<CollisionDetectionMode2D>(val, true, out var mode))
|
|
{
|
|
rb2d.collisionDetectionMode = mode;
|
|
changed.Add("collisionDetectionMode");
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(
|
|
$"Invalid collisionDetectionMode: '{val}'. Valid: Discrete, Continuous.");
|
|
}
|
|
break;
|
|
}
|
|
case "constraints":
|
|
{
|
|
var token = prop.Value;
|
|
if (token.Type == JTokenType.Integer)
|
|
{
|
|
rb2d.constraints = (RigidbodyConstraints2D)token.Value<int>();
|
|
changed.Add("constraints");
|
|
}
|
|
else
|
|
{
|
|
string val = token.ToString();
|
|
if (Enum.TryParse<RigidbodyConstraints2D>(val, true, out var c))
|
|
{
|
|
rb2d.constraints = c;
|
|
changed.Add("constraints");
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse(
|
|
$"Invalid constraints value: '{val}'. Use enum name (e.g. 'FreezePositionX, FreezeRotation') or int flags.");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EditorUtility.SetDirty(rb2d);
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Configured Rigidbody2D on '{go.name}': {string.Join(", ", changed)}.",
|
|
data = new { target = go.name, dimension = "2d", changed }
|
|
};
|
|
}
|
|
}
|
|
}
|