using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
namespace NavisworksTransport.UnitTests.Integration
{
///
/// Free 自由路径集成测试(覆盖计划 T05)。
///
/// Free 路径语义:任意多点,每个路径点即物体包围盒中心(轨迹即物体中心,无偏移)。
/// 验证点:
/// - 虚拟物体沿 Free 路径完整动画/碰撞流程
/// - 逐帧位置精确匹配路径点(XYZ 全匹配,区别于 Ground/Hoisting 的中心高度偏移)
///
[TestClass]
[TestCategory("NavisworksIntegration")]
public class FreePathAutomationTests
{
private const double Tolerance = 1e-2;
private const int TestFrameRate = 15;
private const double TestDurationSeconds = 5.0;
[TestMethod]
[Timeout(360000)]
public async Task FreeVirtualObject_Animation_Completes()
{
using (var client = new NavisworksTestAutomationClient())
{
await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);
JObject response = await client.RunCollisionTestAsync(
"Free",
240,
frameRate: TestFrameRate,
durationSeconds: TestDurationSeconds).ConfigureAwait(false);
Assert.IsTrue(response.Value("ok"), "Free 路径碰撞测试失败: " + (string)response["error"]);
JObject data = (JObject)response["data"];
JObject route = (JObject)data["route"];
Assert.IsNotNull(route, "缺少 route");
Assert.AreEqual("Free", (string)route["pathType"], "返回的路径类型不匹配");
Assert.AreEqual("自动测试_Free", (string)route["name"], "应使用标准 Free 测试路径");
JObject animation = (JObject)data["animation"];
Assert.AreEqual("Finished", (string)animation["currentState"], "动画未完成");
Assert.IsTrue((int)animation["totalFrames"] > 0, "动画总帧数应大于 0");
CollisionReportAssertions.AssertReportStructure(data, route);
}
}
[TestMethod]
[Timeout(240000)]
public async Task FreeVirtualObject_FramesMatchPathPoints()
{
using (var client = new NavisworksTestAutomationClient())
{
await client.EnsureServiceReadyAsync(TimeSpan.FromSeconds(90)).ConfigureAwait(false);
JObject response = await client.ProbeAnimationFramesAsync(
"Free",
120,
"0,0.5,1.0",
TestFrameRate,
TestDurationSeconds).ConfigureAwait(false);
Assert.IsTrue(response.Value("ok"), "帧探针失败: " + (string)response["error"]);
JObject data = (JObject)response["data"];
Assert.AreEqual("自动测试_Free", (string)data["route"]["name"], "探针路径不匹配");
JObject pathStart = (JObject)data["pathStart"];
JObject pathEnd = (JObject)data["pathEnd"];
JArray probes = (JArray)data["probes"];
Assert.IsNotNull(probes, "缺少 probes");
Assert.AreEqual(3, probes.Count, "探针点数应为 3(0/0.5/1.0)");
var p0 = (JObject)probes[0]["position"];
var pm = (JObject)probes[1]["position"];
var p1 = (JObject)probes[2]["position"];
// Free 路径:物体中心 = 路径点(XYZ 全匹配)
AssertNear((double)pathStart["x"], (double)p0["x"], "起点 X");
AssertNear((double)pathStart["y"], (double)p0["y"], "起点 Y");
AssertNear((double)pathStart["z"], (double)p0["z"], "起点 Z");
AssertNear((double)pathEnd["x"], (double)p1["x"], "终点 X");
AssertNear((double)pathEnd["y"], (double)p1["y"], "终点 Y");
AssertNear((double)pathEnd["z"], (double)p1["z"], "终点 Z");
// 中间帧在起终点之间(3D 距离)
double startToMid = Distance3D(p0, pm);
double startToEnd = Distance3D(p0, p1);
Assert.IsTrue(startToMid > Tolerance, "中间帧不应等于起点");
Assert.IsTrue(startToMid < startToEnd - Tolerance, "中间帧应在起终点之间");
// yaw 应为有限值(Free 路径保持物体姿态)
foreach (var probe in probes)
{
Assert.IsFalse(double.IsNaN((double)probe["yawDegrees"]), "yaw 不应为 NaN");
}
}
}
private static double Distance3D(JObject a, JObject b)
{
double dx = (double)a["x"] - (double)b["x"];
double dy = (double)a["y"] - (double)b["y"];
double dz = (double)a["z"] - (double)b["z"];
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
private static void AssertNear(double expected, double actual, string message)
{
Assert.IsTrue(
Math.Abs(expected - actual) <= Tolerance,
$"{message}:期望 {expected:F3},实际 {actual:F3},容差 {Tolerance}");
}
}
}