deea7d2a56
Addresses review feedback on the Unity 6.5 GetInstanceID migration: - UnityObjectIdCompatExtensions: drop the reflective method lookup in favor of a simple #if UNITY_6000_5_OR_NEWER / #else split calling GetEntityId() or GetInstanceID() directly. Also wrap the class in the MCPForUnity.Runtime.Helpers namespace. - UnityTypeConverters: remove the reflective EntityIdToObject probe and call EditorUtility.EntityIdToObject(EntityId) directly under the same version gate. Serialize entityID as EntityId.ToULong() rather than ToString(), since Unity's docs explicitly warn that the textual form is not a stable serialization contract. - Drop #pragma warning disable 0619 from 22 files that no longer make any direct calls to obsolete APIs. The remaining 7 files still need it (FindObjectsOfType, InstanceIDToObject fallback) and are left as-is — those deprecations are out of scope for this PR. - Add the MCPForUnity.Runtime.Helpers using to every file that calls GetInstanceIDCompat() now that the extension method lives in a namespace.
808 lines
33 KiB
C#
808 lines
33 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEngine;
|
|
using MCPForUnity.Editor.Helpers;
|
|
using MCPForUnity.Runtime.Helpers;
|
|
|
|
namespace MCPForUnity.Editor.Tools.Physics
|
|
{
|
|
internal static class PhysicsQueryOps
|
|
{
|
|
public static object Raycast(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant();
|
|
|
|
var originArr = p.GetRaw("origin") as JArray;
|
|
if (originArr == null)
|
|
return new ErrorResponse("'origin' parameter is required (array of floats).");
|
|
|
|
var dirArr = p.GetRaw("direction") as JArray;
|
|
if (dirArr == null)
|
|
return new ErrorResponse("'direction' parameter is required (array of floats).");
|
|
|
|
float maxDistance = p.GetFloat("max_distance") ?? Mathf.Infinity;
|
|
|
|
if (dimension == "2d")
|
|
return Raycast2D(originArr, dirArr, maxDistance, p);
|
|
|
|
if (dimension != "3d")
|
|
return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'.");
|
|
|
|
return Raycast3D(originArr, dirArr, maxDistance, p);
|
|
}
|
|
|
|
private static object Raycast3D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p)
|
|
{
|
|
if (originArr.Count < 3)
|
|
return new ErrorResponse("3D raycast 'origin' requires [x, y, z].");
|
|
if (dirArr.Count < 3)
|
|
return new ErrorResponse("3D raycast 'direction' requires [x, y, z].");
|
|
|
|
var origin = new Vector3(
|
|
originArr[0].Value<float>(), originArr[1].Value<float>(), originArr[2].Value<float>());
|
|
var direction = new Vector3(
|
|
dirArr[0].Value<float>(), dirArr[1].Value<float>(), dirArr[2].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
QueryTriggerInteraction triggerInteraction = QueryTriggerInteraction.UseGlobal;
|
|
string qti = p.Get("query_trigger_interaction");
|
|
if (!string.IsNullOrEmpty(qti))
|
|
{
|
|
if (!System.Enum.TryParse(qti, true, out triggerInteraction))
|
|
return new ErrorResponse($"Invalid query_trigger_interaction: '{qti}'. Valid: UseGlobal, Ignore, Collide.");
|
|
}
|
|
|
|
UnityEngine.Physics.SyncTransforms();
|
|
|
|
bool hit = UnityEngine.Physics.Raycast(origin, direction, out RaycastHit hitInfo, maxDistance, layerMask, triggerInteraction);
|
|
|
|
if (!hit)
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "Raycast did not hit anything.",
|
|
data = new { hit = false }
|
|
};
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Raycast hit '{hitInfo.collider.gameObject.name}'.",
|
|
data = new
|
|
{
|
|
hit = true,
|
|
point = new[] { hitInfo.point.x, hitInfo.point.y, hitInfo.point.z },
|
|
normal = new[] { hitInfo.normal.x, hitInfo.normal.y, hitInfo.normal.z },
|
|
distance = hitInfo.distance,
|
|
gameObject = hitInfo.collider.gameObject.name,
|
|
instanceID = hitInfo.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = hitInfo.collider.GetType().Name
|
|
}
|
|
};
|
|
}
|
|
|
|
private static object Raycast2D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p)
|
|
{
|
|
if (originArr.Count < 2)
|
|
return new ErrorResponse("2D raycast 'origin' requires [x, y].");
|
|
if (dirArr.Count < 2)
|
|
return new ErrorResponse("2D raycast 'direction' requires [x, y].");
|
|
|
|
var origin = new Vector2(originArr[0].Value<float>(), originArr[1].Value<float>());
|
|
var direction = new Vector2(dirArr[0].Value<float>(), dirArr[1].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
Physics2D.SyncTransforms();
|
|
|
|
var hit = Physics2D.Raycast(origin, direction, maxDistance, layerMask);
|
|
|
|
if (hit.collider == null)
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "2D Raycast did not hit anything.",
|
|
data = new { hit = false }
|
|
};
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"2D Raycast hit '{hit.collider.gameObject.name}'.",
|
|
data = new
|
|
{
|
|
hit = true,
|
|
point = new[] { hit.point.x, hit.point.y },
|
|
normal = new[] { hit.normal.x, hit.normal.y },
|
|
distance = hit.distance,
|
|
gameObject = hit.collider.gameObject.name,
|
|
instanceID = hit.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = hit.collider.GetType().Name
|
|
}
|
|
};
|
|
}
|
|
|
|
public static object Overlap(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant();
|
|
string shape = p.Get("shape");
|
|
if (string.IsNullOrEmpty(shape))
|
|
return new ErrorResponse("'shape' parameter is required (sphere, box, capsule for 3D; circle, box, capsule for 2D).");
|
|
|
|
var posArr = p.GetRaw("position") as JArray;
|
|
if (posArr == null)
|
|
return new ErrorResponse("'position' parameter is required (array of floats).");
|
|
|
|
var sizeToken = p.GetRaw("size");
|
|
if (sizeToken == null)
|
|
return new ErrorResponse("'size' parameter is required.");
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
if (dimension == "2d")
|
|
return Overlap2D(shape, posArr, sizeToken, layerMask);
|
|
|
|
if (dimension != "3d")
|
|
return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'.");
|
|
|
|
return Overlap3D(shape, posArr, sizeToken, layerMask);
|
|
}
|
|
|
|
private static object Overlap3D(string shape, JArray posArr, JToken sizeToken, int layerMask)
|
|
{
|
|
if (posArr.Count < 3)
|
|
return new ErrorResponse("3D overlap 'position' requires [x, y, z].");
|
|
|
|
var position = new Vector3(
|
|
posArr[0].Value<float>(), posArr[1].Value<float>(), posArr[2].Value<float>());
|
|
|
|
UnityEngine.Physics.SyncTransforms();
|
|
|
|
Collider[] results;
|
|
|
|
switch (shape.ToLowerInvariant())
|
|
{
|
|
case "sphere":
|
|
{
|
|
float radius = sizeToken.Value<float>();
|
|
results = UnityEngine.Physics.OverlapSphere(position, radius, layerMask);
|
|
break;
|
|
}
|
|
case "box":
|
|
{
|
|
Vector3 halfExtents;
|
|
if (sizeToken is JArray sizeArr && sizeArr.Count >= 3)
|
|
halfExtents = new Vector3(
|
|
sizeArr[0].Value<float>(), sizeArr[1].Value<float>(), sizeArr[2].Value<float>());
|
|
else
|
|
return new ErrorResponse("3D box overlap 'size' requires [halfX, halfY, halfZ].");
|
|
results = UnityEngine.Physics.OverlapBox(position, halfExtents, Quaternion.identity, layerMask);
|
|
break;
|
|
}
|
|
case "capsule":
|
|
{
|
|
if (sizeToken is JObject capsuleObj)
|
|
{
|
|
float radius = capsuleObj["radius"]?.Value<float>() ?? 0.5f;
|
|
float height = capsuleObj["height"]?.Value<float>() ?? 2f;
|
|
int direction = capsuleObj["direction"]?.Value<int>() ?? 1;
|
|
|
|
Vector3 point0, point1;
|
|
float halfHeight = Mathf.Max(0, height / 2f - radius);
|
|
switch (direction)
|
|
{
|
|
case 0: // X
|
|
point0 = position + Vector3.left * halfHeight;
|
|
point1 = position + Vector3.right * halfHeight;
|
|
break;
|
|
case 2: // Z
|
|
point0 = position + Vector3.back * halfHeight;
|
|
point1 = position + Vector3.forward * halfHeight;
|
|
break;
|
|
default: // Y (1)
|
|
point0 = position + Vector3.down * halfHeight;
|
|
point1 = position + Vector3.up * halfHeight;
|
|
break;
|
|
}
|
|
|
|
results = UnityEngine.Physics.OverlapCapsule(point0, point1, radius, layerMask);
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse("3D capsule overlap 'size' requires {radius, height, direction}.");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return new ErrorResponse($"Unknown 3D shape: '{shape}'. Valid: sphere, box, capsule.");
|
|
}
|
|
|
|
return FormatOverlapResults(results, "3D");
|
|
}
|
|
|
|
private static object Overlap2D(string shape, JArray posArr, JToken sizeToken, int layerMask)
|
|
{
|
|
if (posArr.Count < 2)
|
|
return new ErrorResponse("2D overlap 'position' requires [x, y].");
|
|
|
|
var position = new Vector2(posArr[0].Value<float>(), posArr[1].Value<float>());
|
|
|
|
Physics2D.SyncTransforms();
|
|
|
|
Collider2D[] results;
|
|
|
|
switch (shape.ToLowerInvariant())
|
|
{
|
|
case "circle":
|
|
{
|
|
float radius = sizeToken.Value<float>();
|
|
results = Physics2D.OverlapCircleAll(position, radius, layerMask);
|
|
break;
|
|
}
|
|
case "box":
|
|
{
|
|
Vector2 size;
|
|
if (sizeToken is JArray sizeArr && sizeArr.Count >= 2)
|
|
size = new Vector2(sizeArr[0].Value<float>(), sizeArr[1].Value<float>());
|
|
else
|
|
return new ErrorResponse("2D box overlap 'size' requires [width, height].");
|
|
results = Physics2D.OverlapBoxAll(position, size, 0f, layerMask);
|
|
break;
|
|
}
|
|
case "capsule":
|
|
{
|
|
if (sizeToken is JObject capsuleObj)
|
|
{
|
|
float sizeX = capsuleObj["width"]?.Value<float>() ?? 1f;
|
|
float sizeY = capsuleObj["height"]?.Value<float>() ?? 2f;
|
|
var dir = CapsuleDirection2D.Vertical;
|
|
string dirStr = capsuleObj["direction"]?.ToString();
|
|
if (dirStr != null && dirStr.ToLowerInvariant() == "horizontal")
|
|
dir = CapsuleDirection2D.Horizontal;
|
|
results = Physics2D.OverlapCapsuleAll(position, new Vector2(sizeX, sizeY), dir, 0f, layerMask);
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse("2D capsule overlap 'size' requires {width, height, direction}.");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return new ErrorResponse($"Unknown 2D shape: '{shape}'. Valid: circle, box, capsule.");
|
|
}
|
|
|
|
return FormatOverlapResults2D(results, "2D");
|
|
}
|
|
|
|
private static object FormatOverlapResults(Collider[] results, string dimension)
|
|
{
|
|
var colliders = new List<object>();
|
|
foreach (var col in results)
|
|
{
|
|
colliders.Add(new
|
|
{
|
|
gameObject = col.gameObject.name,
|
|
instanceID = col.gameObject.GetInstanceIDCompat(),
|
|
collider_type = col.GetType().Name
|
|
});
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Overlap query found {colliders.Count} collider(s) ({dimension}).",
|
|
data = new { colliders }
|
|
};
|
|
}
|
|
|
|
private static object FormatOverlapResults2D(Collider2D[] results, string dimension)
|
|
{
|
|
var colliders = new List<object>();
|
|
foreach (var col in results)
|
|
{
|
|
colliders.Add(new
|
|
{
|
|
gameObject = col.gameObject.name,
|
|
instanceID = col.gameObject.GetInstanceIDCompat(),
|
|
collider_type = col.GetType().Name
|
|
});
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Overlap query found {colliders.Count} collider(s) ({dimension}).",
|
|
data = new { colliders }
|
|
};
|
|
}
|
|
|
|
public static object Shapecast(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant();
|
|
string shape = p.Get("shape");
|
|
if (string.IsNullOrEmpty(shape))
|
|
return new ErrorResponse("'shape' parameter is required (sphere, box, capsule for 3D; circle, box, capsule for 2D).");
|
|
|
|
var originArr = p.GetRaw("origin") as JArray;
|
|
if (originArr == null)
|
|
return new ErrorResponse("'origin' parameter is required (array of floats).");
|
|
|
|
var dirArr = p.GetRaw("direction") as JArray;
|
|
if (dirArr == null)
|
|
return new ErrorResponse("'direction' parameter is required (array of floats).");
|
|
|
|
float maxDistance = p.GetFloat("max_distance") ?? Mathf.Infinity;
|
|
|
|
if (dimension == "2d")
|
|
return Shapecast2D(shape, originArr, dirArr, maxDistance, p);
|
|
|
|
if (dimension != "3d")
|
|
return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'.");
|
|
|
|
return Shapecast3D(shape, originArr, dirArr, maxDistance, p);
|
|
}
|
|
|
|
private static object Shapecast3D(string shape, JArray originArr, JArray dirArr, float maxDistance, ToolParams p)
|
|
{
|
|
if (originArr.Count < 3)
|
|
return new ErrorResponse("3D shapecast 'origin' requires [x, y, z].");
|
|
if (dirArr.Count < 3)
|
|
return new ErrorResponse("3D shapecast 'direction' requires [x, y, z].");
|
|
|
|
var origin = new Vector3(
|
|
originArr[0].Value<float>(), originArr[1].Value<float>(), originArr[2].Value<float>());
|
|
var direction = new Vector3(
|
|
dirArr[0].Value<float>(), dirArr[1].Value<float>(), dirArr[2].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
QueryTriggerInteraction triggerInteraction = QueryTriggerInteraction.UseGlobal;
|
|
string qti = p.Get("query_trigger_interaction");
|
|
if (!string.IsNullOrEmpty(qti))
|
|
{
|
|
if (!System.Enum.TryParse(qti, true, out triggerInteraction))
|
|
return new ErrorResponse($"Invalid query_trigger_interaction: '{qti}'. Valid: UseGlobal, Ignore, Collide.");
|
|
}
|
|
|
|
var sizeToken = p.GetRaw("size");
|
|
if (sizeToken == null)
|
|
return new ErrorResponse("'size' parameter is required.");
|
|
|
|
UnityEngine.Physics.SyncTransforms();
|
|
|
|
bool hit;
|
|
RaycastHit hitInfo;
|
|
|
|
switch (shape.ToLowerInvariant())
|
|
{
|
|
case "sphere":
|
|
{
|
|
float radius = sizeToken.Value<float>();
|
|
hit = UnityEngine.Physics.SphereCast(origin, radius, direction, out hitInfo, maxDistance, layerMask, triggerInteraction);
|
|
break;
|
|
}
|
|
case "box":
|
|
{
|
|
Vector3 halfExtents;
|
|
if (sizeToken is JArray sizeArr && sizeArr.Count >= 3)
|
|
halfExtents = new Vector3(
|
|
sizeArr[0].Value<float>(), sizeArr[1].Value<float>(), sizeArr[2].Value<float>());
|
|
else
|
|
return new ErrorResponse("3D box shapecast 'size' requires [halfX, halfY, halfZ].");
|
|
hit = UnityEngine.Physics.BoxCast(origin, halfExtents, direction, out hitInfo, Quaternion.identity, maxDistance, layerMask, triggerInteraction);
|
|
break;
|
|
}
|
|
case "capsule":
|
|
{
|
|
float radius = sizeToken.Value<float>();
|
|
Vector3 pt1, pt2;
|
|
|
|
var pt1Arr = p.GetRaw("point1") as JArray;
|
|
var pt2Arr = p.GetRaw("point2") as JArray;
|
|
|
|
if (pt1Arr != null && pt1Arr.Count >= 3 && pt2Arr != null && pt2Arr.Count >= 3)
|
|
{
|
|
pt1 = new Vector3(pt1Arr[0].Value<float>(), pt1Arr[1].Value<float>(), pt1Arr[2].Value<float>());
|
|
pt2 = new Vector3(pt2Arr[0].Value<float>(), pt2Arr[1].Value<float>(), pt2Arr[2].Value<float>());
|
|
}
|
|
else
|
|
{
|
|
float height = p.GetFloat("height") ?? 2f;
|
|
int capsuleDirection = p.GetInt("capsule_direction") ?? 1;
|
|
float halfHeight = Mathf.Max(0, height / 2f - radius);
|
|
|
|
switch (capsuleDirection)
|
|
{
|
|
case 0: // X
|
|
pt1 = origin + Vector3.left * halfHeight;
|
|
pt2 = origin + Vector3.right * halfHeight;
|
|
break;
|
|
case 2: // Z
|
|
pt1 = origin + Vector3.back * halfHeight;
|
|
pt2 = origin + Vector3.forward * halfHeight;
|
|
break;
|
|
default: // Y (1)
|
|
pt1 = origin + Vector3.down * halfHeight;
|
|
pt2 = origin + Vector3.up * halfHeight;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hit = UnityEngine.Physics.CapsuleCast(pt1, pt2, radius, direction, out hitInfo, maxDistance, layerMask, triggerInteraction);
|
|
break;
|
|
}
|
|
default:
|
|
return new ErrorResponse($"Unknown 3D shape: '{shape}'. Valid: sphere, box, capsule.");
|
|
}
|
|
|
|
if (!hit)
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "Shapecast did not hit anything.",
|
|
data = new { hit = false }
|
|
};
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Shapecast hit '{hitInfo.collider.gameObject.name}'.",
|
|
data = new
|
|
{
|
|
hit = true,
|
|
point = new[] { hitInfo.point.x, hitInfo.point.y, hitInfo.point.z },
|
|
normal = new[] { hitInfo.normal.x, hitInfo.normal.y, hitInfo.normal.z },
|
|
distance = hitInfo.distance,
|
|
gameObject = hitInfo.collider.gameObject.name,
|
|
instanceID = hitInfo.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = hitInfo.collider.GetType().Name
|
|
}
|
|
};
|
|
}
|
|
|
|
private static object Shapecast2D(string shape, JArray originArr, JArray dirArr, float maxDistance, ToolParams p)
|
|
{
|
|
if (originArr.Count < 2)
|
|
return new ErrorResponse("2D shapecast 'origin' requires [x, y].");
|
|
if (dirArr.Count < 2)
|
|
return new ErrorResponse("2D shapecast 'direction' requires [x, y].");
|
|
|
|
var origin = new Vector2(originArr[0].Value<float>(), originArr[1].Value<float>());
|
|
var direction = new Vector2(dirArr[0].Value<float>(), dirArr[1].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
float angle = p.GetFloat("angle") ?? 0f;
|
|
|
|
var sizeToken = p.GetRaw("size");
|
|
if (sizeToken == null)
|
|
return new ErrorResponse("'size' parameter is required.");
|
|
|
|
Physics2D.SyncTransforms();
|
|
|
|
RaycastHit2D hit;
|
|
|
|
switch (shape.ToLowerInvariant())
|
|
{
|
|
case "circle":
|
|
{
|
|
float radius = sizeToken.Value<float>();
|
|
hit = Physics2D.CircleCast(origin, radius, direction, maxDistance, layerMask);
|
|
break;
|
|
}
|
|
case "box":
|
|
{
|
|
Vector2 size;
|
|
if (sizeToken is JArray sizeArr && sizeArr.Count >= 2)
|
|
size = new Vector2(sizeArr[0].Value<float>(), sizeArr[1].Value<float>());
|
|
else
|
|
return new ErrorResponse("2D box shapecast 'size' requires [width, height].");
|
|
hit = Physics2D.BoxCast(origin, size, angle, direction, maxDistance, layerMask);
|
|
break;
|
|
}
|
|
case "capsule":
|
|
{
|
|
if (sizeToken is JObject sizeObj)
|
|
{
|
|
var capsuleSize = new Vector2(
|
|
sizeObj["width"]?.Value<float>() ?? 1f,
|
|
sizeObj["height"]?.Value<float>() ?? 2f);
|
|
var capsuleDir = CapsuleDirection2D.Vertical;
|
|
string dirStr = sizeObj["direction"]?.ToString();
|
|
if (dirStr != null && dirStr.ToLowerInvariant() == "horizontal")
|
|
capsuleDir = CapsuleDirection2D.Horizontal;
|
|
hit = Physics2D.CapsuleCast(origin, capsuleSize, capsuleDir, angle, direction, maxDistance, layerMask);
|
|
}
|
|
else
|
|
{
|
|
return new ErrorResponse("2D capsule shapecast 'size' requires {width, height, direction}.");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return new ErrorResponse($"Unknown 2D shape: '{shape}'. Valid: circle, box, capsule.");
|
|
}
|
|
|
|
if (hit.collider == null)
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "2D Shapecast did not hit anything.",
|
|
data = new { hit = false }
|
|
};
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"2D Shapecast hit '{hit.collider.gameObject.name}'.",
|
|
data = new
|
|
{
|
|
hit = true,
|
|
point = new[] { hit.point.x, hit.point.y },
|
|
normal = new[] { hit.normal.x, hit.normal.y },
|
|
distance = hit.distance,
|
|
gameObject = hit.collider.gameObject.name,
|
|
instanceID = hit.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = hit.collider.GetType().Name
|
|
}
|
|
};
|
|
}
|
|
|
|
public static object RaycastAll(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant();
|
|
|
|
var originArr = p.GetRaw("origin") as JArray;
|
|
if (originArr == null)
|
|
return new ErrorResponse("'origin' parameter is required (array of floats).");
|
|
|
|
var dirArr = p.GetRaw("direction") as JArray;
|
|
if (dirArr == null)
|
|
return new ErrorResponse("'direction' parameter is required (array of floats).");
|
|
|
|
float maxDistance = p.GetFloat("max_distance") ?? Mathf.Infinity;
|
|
|
|
if (dimension == "2d")
|
|
return RaycastAll2D(originArr, dirArr, maxDistance, p);
|
|
|
|
if (dimension != "3d")
|
|
return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'.");
|
|
|
|
return RaycastAll3D(originArr, dirArr, maxDistance, p);
|
|
}
|
|
|
|
private static object RaycastAll3D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p)
|
|
{
|
|
if (originArr.Count < 3)
|
|
return new ErrorResponse("3D RaycastAll 'origin' requires [x, y, z].");
|
|
if (dirArr.Count < 3)
|
|
return new ErrorResponse("3D RaycastAll 'direction' requires [x, y, z].");
|
|
|
|
var origin = new Vector3(
|
|
originArr[0].Value<float>(), originArr[1].Value<float>(), originArr[2].Value<float>());
|
|
var direction = new Vector3(
|
|
dirArr[0].Value<float>(), dirArr[1].Value<float>(), dirArr[2].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
QueryTriggerInteraction triggerInteraction = QueryTriggerInteraction.UseGlobal;
|
|
string qti = p.Get("query_trigger_interaction");
|
|
if (!string.IsNullOrEmpty(qti))
|
|
{
|
|
if (!System.Enum.TryParse(qti, true, out triggerInteraction))
|
|
return new ErrorResponse($"Invalid query_trigger_interaction: '{qti}'. Valid: UseGlobal, Ignore, Collide.");
|
|
}
|
|
|
|
UnityEngine.Physics.SyncTransforms();
|
|
|
|
RaycastHit[] hits = UnityEngine.Physics.RaycastAll(origin, direction, maxDistance, layerMask, triggerInteraction);
|
|
System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
|
|
|
|
var hitsArray = new List<object>();
|
|
foreach (var h in hits)
|
|
{
|
|
hitsArray.Add(new
|
|
{
|
|
point = new[] { h.point.x, h.point.y, h.point.z },
|
|
normal = new[] { h.normal.x, h.normal.y, h.normal.z },
|
|
distance = h.distance,
|
|
gameObject = h.collider.gameObject.name,
|
|
instanceID = h.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = h.collider.GetType().Name
|
|
});
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"RaycastAll found {hits.Length} hit(s).",
|
|
data = new { hit_count = hits.Length, hits = hitsArray }
|
|
};
|
|
}
|
|
|
|
private static object RaycastAll2D(JArray originArr, JArray dirArr, float maxDistance, ToolParams p)
|
|
{
|
|
if (originArr.Count < 2)
|
|
return new ErrorResponse("2D RaycastAll 'origin' requires [x, y].");
|
|
if (dirArr.Count < 2)
|
|
return new ErrorResponse("2D RaycastAll 'direction' requires [x, y].");
|
|
|
|
var origin = new Vector2(originArr[0].Value<float>(), originArr[1].Value<float>());
|
|
var direction = new Vector2(dirArr[0].Value<float>(), dirArr[1].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
Physics2D.SyncTransforms();
|
|
|
|
RaycastHit2D[] hits = Physics2D.RaycastAll(origin, direction, maxDistance, layerMask);
|
|
|
|
var hitsArray = new List<object>();
|
|
foreach (var h in hits)
|
|
{
|
|
hitsArray.Add(new
|
|
{
|
|
point = new[] { h.point.x, h.point.y },
|
|
normal = new[] { h.normal.x, h.normal.y },
|
|
distance = h.distance,
|
|
gameObject = h.collider.gameObject.name,
|
|
instanceID = h.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = h.collider.GetType().Name
|
|
});
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"RaycastAll found {hits.Length} hit(s).",
|
|
data = new { hit_count = hits.Length, hits = hitsArray }
|
|
};
|
|
}
|
|
|
|
public static object Linecast(JObject @params)
|
|
{
|
|
var p = new ToolParams(@params);
|
|
string dimension = (p.Get("dimension") ?? "3d").ToLowerInvariant();
|
|
|
|
var startArr = p.GetRaw("start") as JArray;
|
|
if (startArr == null)
|
|
return new ErrorResponse("'start' parameter is required (array of floats).");
|
|
|
|
var endArr = p.GetRaw("end") as JArray;
|
|
if (endArr == null)
|
|
return new ErrorResponse("'end' parameter is required (array of floats).");
|
|
|
|
if (dimension == "2d")
|
|
return Linecast2D(startArr, endArr, p);
|
|
|
|
if (dimension != "3d")
|
|
return new ErrorResponse($"Invalid dimension: '{dimension}'. Use '3d' or '2d'.");
|
|
|
|
return Linecast3D(startArr, endArr, p);
|
|
}
|
|
|
|
private static object Linecast3D(JArray startArr, JArray endArr, ToolParams p)
|
|
{
|
|
if (startArr.Count < 3)
|
|
return new ErrorResponse("3D linecast 'start' requires [x, y, z].");
|
|
if (endArr.Count < 3)
|
|
return new ErrorResponse("3D linecast 'end' requires [x, y, z].");
|
|
|
|
var start = new Vector3(
|
|
startArr[0].Value<float>(), startArr[1].Value<float>(), startArr[2].Value<float>());
|
|
var end = new Vector3(
|
|
endArr[0].Value<float>(), endArr[1].Value<float>(), endArr[2].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
QueryTriggerInteraction triggerInteraction = QueryTriggerInteraction.UseGlobal;
|
|
string qti = p.Get("query_trigger_interaction");
|
|
if (!string.IsNullOrEmpty(qti))
|
|
{
|
|
if (!System.Enum.TryParse(qti, true, out triggerInteraction))
|
|
return new ErrorResponse($"Invalid query_trigger_interaction: '{qti}'. Valid: UseGlobal, Ignore, Collide.");
|
|
}
|
|
|
|
UnityEngine.Physics.SyncTransforms();
|
|
|
|
bool hit = UnityEngine.Physics.Linecast(start, end, out RaycastHit hitInfo, layerMask, triggerInteraction);
|
|
|
|
if (!hit)
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "Linecast did not hit anything.",
|
|
data = new { hit = false }
|
|
};
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"Linecast hit '{hitInfo.collider.gameObject.name}'.",
|
|
data = new
|
|
{
|
|
hit = true,
|
|
point = new[] { hitInfo.point.x, hitInfo.point.y, hitInfo.point.z },
|
|
normal = new[] { hitInfo.normal.x, hitInfo.normal.y, hitInfo.normal.z },
|
|
distance = hitInfo.distance,
|
|
gameObject = hitInfo.collider.gameObject.name,
|
|
instanceID = hitInfo.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = hitInfo.collider.GetType().Name
|
|
}
|
|
};
|
|
}
|
|
|
|
private static object Linecast2D(JArray startArr, JArray endArr, ToolParams p)
|
|
{
|
|
if (startArr.Count < 2)
|
|
return new ErrorResponse("2D linecast 'start' requires [x, y].");
|
|
if (endArr.Count < 2)
|
|
return new ErrorResponse("2D linecast 'end' requires [x, y].");
|
|
|
|
var start = new Vector2(startArr[0].Value<float>(), startArr[1].Value<float>());
|
|
var end = new Vector2(endArr[0].Value<float>(), endArr[1].Value<float>());
|
|
|
|
int layerMask = ResolveLayerMask(p.Get("layer_mask"));
|
|
|
|
Physics2D.SyncTransforms();
|
|
|
|
var hit = Physics2D.Linecast(start, end, layerMask);
|
|
|
|
if (hit.collider == null)
|
|
{
|
|
return new
|
|
{
|
|
success = true,
|
|
message = "2D Linecast did not hit anything.",
|
|
data = new { hit = false }
|
|
};
|
|
}
|
|
|
|
return new
|
|
{
|
|
success = true,
|
|
message = $"2D Linecast hit '{hit.collider.gameObject.name}'.",
|
|
data = new
|
|
{
|
|
hit = true,
|
|
point = new[] { hit.point.x, hit.point.y },
|
|
normal = new[] { hit.normal.x, hit.normal.y },
|
|
distance = hit.distance,
|
|
gameObject = hit.collider.gameObject.name,
|
|
instanceID = hit.collider.gameObject.GetInstanceIDCompat(),
|
|
collider_type = hit.collider.GetType().Name
|
|
}
|
|
};
|
|
}
|
|
|
|
private static int ResolveLayerMask(string layerMaskStr)
|
|
{
|
|
if (string.IsNullOrEmpty(layerMaskStr))
|
|
return ~0; // All layers
|
|
|
|
if (int.TryParse(layerMaskStr, out int mask))
|
|
return mask;
|
|
|
|
int layer = LayerMask.NameToLayer(layerMaskStr);
|
|
if (layer >= 0)
|
|
return 1 << layer;
|
|
|
|
throw new ArgumentException($"Unknown layer name: '{layerMaskStr}'. Use a valid layer name or integer mask.");
|
|
}
|
|
}
|
|
}
|