Add RVO2 benchmark adapter

This commit is contained in:
JSD\13999
2026-06-13 14:41:55 +08:00
parent ab1d376b45
commit 47a3936c13
4 changed files with 536 additions and 1 deletions

View File

@@ -9,9 +9,10 @@ namespace FishROV.AvoidanceBenchmark
case AvoidanceFrameworkKind.NoAvoidance: case AvoidanceFrameworkKind.NoAvoidance:
return new NoAvoidanceAdapter(); return new NoAvoidanceAdapter();
case AvoidanceFrameworkKind.AstarRvo: case AvoidanceFrameworkKind.AstarRvo:
case AvoidanceFrameworkKind.Rvo2:
case AvoidanceFrameworkKind.UnityNavMesh: case AvoidanceFrameworkKind.UnityNavMesh:
return new UnavailableAvoidanceAdapter(kind); return new UnavailableAvoidanceAdapter(kind);
case AvoidanceFrameworkKind.Rvo2:
return new Rvo2Adapter();
default: default:
return new UnavailableAvoidanceAdapter(kind); return new UnavailableAvoidanceAdapter(kind);
} }

View File

@@ -0,0 +1,513 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace FishROV.AvoidanceBenchmark
{
public sealed class Rvo2Adapter : IAvoidanceAdapter
{
private readonly Dictionary<BenchmarkAgent, int> agentIds = new Dictionary<BenchmarkAgent, int>();
private Rvo2ReflectionBridge bridge;
private AvoidanceBenchmarkConfig config;
private bool initialized;
public string Name => "RVO2 / RVO2-3D";
public bool IsAvailable => bridge != null && bridge.IsAvailable;
public string Status => bridge != null
? bridge.Status
: Rvo2ReflectionBridge.MissingPackageStatus;
public void Initialize(AvoidanceBenchmarkConfig config)
{
this.config = config.Clone();
agentIds.Clear();
bridge = Rvo2ReflectionBridge.Create(this.config);
initialized = true;
}
public BenchmarkAgent CreateAgent(BenchmarkAgentSpawnInfo spawnInfo, Transform parent)
{
var agent = BenchmarkAgent.CreatePrimitive(spawnInfo, parent);
if (!initialized || bridge == null || !bridge.IsAvailable)
{
return agent;
}
var id = bridge.AddAgent(spawnInfo);
if (id >= 0)
{
agentIds[agent] = id;
}
return agent;
}
public void SetPreferredVelocity(BenchmarkAgent agent, Vector3 preferredVelocity)
{
if (bridge == null || !bridge.IsAvailable || !agentIds.TryGetValue(agent, out var id))
{
agent.ApplyVelocity(preferredVelocity);
return;
}
bridge.SetPreferredVelocity(id, preferredVelocity);
}
public void SetTarget(BenchmarkAgent agent, Vector3 target)
{
agent.Target = target;
}
public void Tick(float deltaTime)
{
if (bridge == null || !bridge.IsAvailable)
{
return;
}
bridge.Step(deltaTime);
foreach (var pair in agentIds)
{
pair.Key.ApplyVelocity(bridge.GetVelocity(pair.Value, pair.Key.MaxSpeed));
}
}
public void Dispose()
{
agentIds.Clear();
bridge?.Dispose();
bridge = null;
initialized = false;
}
private sealed class Rvo2ReflectionBridge
{
public const string MissingPackageStatus =
"RVO2 package/source not found. Place the RVO2 or RVO2-3D C# source/package in Assets or Packages with a Simulator type such as RVO.Simulator; this adapter will bind by reflection.";
private static readonly string[] SimulatorTypeNames =
{
"RVO.Simulator",
"RVO2.Simulator",
"RVO3D.Simulator",
"RVO.Simulator3D"
};
private readonly object simulator;
private readonly Type simulatorType;
private readonly Type vector2Type;
private readonly Type vector3Type;
private readonly bool use3D;
private readonly string status;
private readonly MethodInfo addAgent;
private readonly MethodInfo setAgentPrefVelocity;
private readonly MethodInfo doStep;
private readonly MethodInfo getAgentVelocity;
private readonly MethodInfo setTimeStep;
private readonly MethodInfo setAgentDefaults;
private readonly MethodInfo clear;
private Rvo2ReflectionBridge(
object simulator,
Type simulatorType,
Type vector2Type,
Type vector3Type,
bool use3D,
string status,
MethodInfo addAgent,
MethodInfo setAgentPrefVelocity,
MethodInfo doStep,
MethodInfo getAgentVelocity,
MethodInfo setTimeStep,
MethodInfo setAgentDefaults,
MethodInfo clear)
{
this.simulator = simulator;
this.simulatorType = simulatorType;
this.vector2Type = vector2Type;
this.vector3Type = vector3Type;
this.use3D = use3D;
this.status = status;
this.addAgent = addAgent;
this.setAgentPrefVelocity = setAgentPrefVelocity;
this.doStep = doStep;
this.getAgentVelocity = getAgentVelocity;
this.setTimeStep = setTimeStep;
this.setAgentDefaults = setAgentDefaults;
this.clear = clear;
}
public bool IsAvailable => simulator != null;
public string Status => status;
public static Rvo2ReflectionBridge Create(AvoidanceBenchmarkConfig config)
{
var simulatorType = FindType(SimulatorTypeNames);
if (simulatorType == null)
{
return Unavailable();
}
var simulator = CreateOrGetSimulator(simulatorType);
if (simulator == null)
{
return Unavailable("RVO2 Simulator type was found, but no singleton instance or public constructor could be used.");
}
var vector2Type = FindType(simulatorType, "RVO.Vector2", "RVO2.Vector2");
var vector3Type = FindType(simulatorType, "RVO.Vector3", "RVO3D.Vector3", "RVO2.Vector3");
var use3D = config.DimensionMode == AvoidanceDimensionMode.XYZ3D && vector3Type != null;
var agentVectorType = use3D ? vector3Type : vector2Type;
if (agentVectorType == null)
{
return Unavailable("RVO2 Simulator type was found, but no supported RVO.Vector2 or RVO.Vector3 type was found.");
}
var addAgent = FindMethod(simulatorType, "addAgent", "AddAgent", new[] { agentVectorType });
var setAgentPrefVelocity = FindMethod(simulatorType, "setAgentPrefVelocity", "SetAgentPrefVelocity", new[] { typeof(int), agentVectorType });
var getAgentVelocity = FindMethod(simulatorType, "getAgentVelocity", "GetAgentVelocity", new[] { typeof(int) });
var doStep = FindMethod(simulatorType, "doStep", "DoStep", Type.EmptyTypes);
var setTimeStep = FindMethod(simulatorType, "setTimeStep", "SetTimeStep", new[] { typeof(float) });
var setAgentDefaults = FindMethodByName(simulatorType, "setAgentDefaults", "SetAgentDefaults");
var clear = FindMethodByName(simulatorType, "clear", "Clear", "reset", "Reset");
if (addAgent == null || setAgentPrefVelocity == null || getAgentVelocity == null || doStep == null)
{
return Unavailable("RVO2 Simulator type was found, but required methods addAgent, setAgentPrefVelocity, getAgentVelocity, and doStep were not all available.");
}
var bridge = new Rvo2ReflectionBridge(
simulator,
simulatorType,
vector2Type,
vector3Type,
use3D,
use3D ? "RVO2-3D reflection adapter active" : "RVO2 reflection adapter active",
addAgent,
setAgentPrefVelocity,
doStep,
getAgentVelocity,
setTimeStep,
setAgentDefaults,
clear);
bridge.Configure(config);
return bridge;
}
public int AddAgent(BenchmarkAgentSpawnInfo spawnInfo)
{
try
{
var result = addAgent.Invoke(simulator, new[] { ToRvoVector(spawnInfo.Position) });
return Convert.ToInt32(result);
}
catch (Exception exception)
{
Debug.LogWarning($"RVO2 addAgent failed: {exception.Message}");
return -1;
}
}
public void SetPreferredVelocity(int id, Vector3 preferredVelocity)
{
try
{
setAgentPrefVelocity.Invoke(simulator, new[] { id, ToRvoVector(preferredVelocity) });
}
catch (Exception exception)
{
Debug.LogWarning($"RVO2 setAgentPrefVelocity failed: {exception.Message}");
}
}
public void Step(float deltaTime)
{
try
{
InvokeSetTimeStep(deltaTime);
doStep.Invoke(simulator, null);
}
catch (Exception exception)
{
Debug.LogWarning($"RVO2 doStep failed: {exception.Message}");
}
}
public Vector3 GetVelocity(int id, float maxSpeed)
{
try
{
var value = getAgentVelocity.Invoke(simulator, new object[] { id });
return Vector3.ClampMagnitude(FromRvoVector(value), maxSpeed);
}
catch (Exception exception)
{
Debug.LogWarning($"RVO2 getAgentVelocity failed: {exception.Message}");
return Vector3.zero;
}
}
public void Dispose()
{
try
{
clear?.Invoke(simulator, null);
}
catch (Exception exception)
{
Debug.LogWarning($"RVO2 clear/reset failed: {exception.Message}");
}
}
private void Configure(AvoidanceBenchmarkConfig config)
{
InvokeSetTimeStep(Time.fixedDeltaTime);
InvokeAgentDefaults(config);
}
private void InvokeSetTimeStep(float deltaTime)
{
if (setTimeStep == null)
{
return;
}
setTimeStep.Invoke(simulator, new object[] { Mathf.Max(0.001f, deltaTime) });
}
private void InvokeAgentDefaults(AvoidanceBenchmarkConfig config)
{
if (setAgentDefaults == null)
{
return;
}
var parameters = setAgentDefaults.GetParameters();
var values = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
var parameterType = parameters[i].ParameterType;
var name = parameters[i].Name ?? string.Empty;
if (parameterType == typeof(int))
{
values[i] = config.MaxNeighbours;
}
else if (parameterType == typeof(float))
{
values[i] = GuessDefaultFloat(name, config);
}
else if (parameterType == vector2Type)
{
values[i] = CreateVector(vector2Type, 0f, 0f, 0f);
}
else if (parameterType == vector3Type)
{
values[i] = CreateVector(vector3Type, 0f, 0f, 0f);
}
else
{
values[i] = parameterType.IsValueType ? Activator.CreateInstance(parameterType) : null;
}
}
setAgentDefaults.Invoke(simulator, values);
}
private static float GuessDefaultFloat(string parameterName, AvoidanceBenchmarkConfig config)
{
var lower = parameterName.ToLowerInvariant();
if (lower.Contains("radius"))
{
return config.FishRadius;
}
if (lower.Contains("maxspeed") || lower.Contains("max_speed"))
{
return config.FishSpeed;
}
if (lower.Contains("neigh") || lower.Contains("dist"))
{
return config.FishRadius * 8f;
}
if (lower.Contains("horizon"))
{
return 2.5f;
}
return 0f;
}
private object ToRvoVector(Vector3 value)
{
if (use3D && vector3Type != null)
{
return CreateVector(vector3Type, value.x, value.y, value.z);
}
return CreateVector(vector2Type, value.x, value.z, 0f);
}
private Vector3 FromRvoVector(object value)
{
if (value == null)
{
return Vector3.zero;
}
var type = value.GetType();
if (use3D && type == vector3Type)
{
return new Vector3(GetFloatMember(value, "x", "X"), GetFloatMember(value, "y", "Y"), GetFloatMember(value, "z", "Z"));
}
return new Vector3(GetFloatMember(value, "x", "X"), 0f, GetFloatMember(value, "y", "Y"));
}
private static object CreateVector(Type type, float x, float y, float z)
{
var constructor = type.GetConstructor(new[] { typeof(float), typeof(float), typeof(float) });
if (constructor != null)
{
return constructor.Invoke(new object[] { x, y, z });
}
constructor = type.GetConstructor(new[] { typeof(float), typeof(float) });
if (constructor != null)
{
return constructor.Invoke(new object[] { x, y });
}
var value = Activator.CreateInstance(type);
SetFloatMember(value, x, "x", "X");
SetFloatMember(value, y, "y", "Y");
SetFloatMember(value, z, "z", "Z");
return value;
}
private static float GetFloatMember(object value, params string[] names)
{
var type = value.GetType();
foreach (var name in names)
{
var field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return Convert.ToSingle(field.GetValue(value));
}
var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
return Convert.ToSingle(property.GetValue(value, null));
}
}
return 0f;
}
private static void SetFloatMember(object value, float memberValue, params string[] names)
{
var type = value.GetType();
foreach (var name in names)
{
var field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
field.SetValue(value, memberValue);
return;
}
var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.CanWrite)
{
property.SetValue(value, memberValue, null);
return;
}
}
}
private static Rvo2ReflectionBridge Unavailable(string status = MissingPackageStatus)
{
return new Rvo2ReflectionBridge(null, null, null, null, false, status, null, null, null, null, null, null, null);
}
private static object CreateOrGetSimulator(Type type)
{
var instanceProperty = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static);
if (instanceProperty != null)
{
return instanceProperty.GetValue(null, null);
}
var instanceField = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static);
if (instanceField != null)
{
return instanceField.GetValue(null);
}
var constructor = type.GetConstructor(Type.EmptyTypes);
return constructor != null ? constructor.Invoke(null) : null;
}
private static Type FindType(params string[] typeNames)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var typeName in typeNames)
{
var type = assembly.GetType(typeName, false);
if (type != null)
{
return type;
}
}
}
return null;
}
private static Type FindType(Type anchorType, params string[] typeNames)
{
foreach (var typeName in typeNames)
{
var type = anchorType.Assembly.GetType(typeName, false);
if (type != null)
{
return type;
}
}
return FindType(typeNames);
}
private static MethodInfo FindMethod(Type type, string camelName, string pascalName, Type[] parameterTypes)
{
return type.GetMethod(camelName, BindingFlags.Instance | BindingFlags.Public, null, parameterTypes, null)
?? type.GetMethod(pascalName, BindingFlags.Instance | BindingFlags.Public, null, parameterTypes, null);
}
private static MethodInfo FindMethodByName(Type type, params string[] names)
{
foreach (var name in names)
{
foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
if (method.Name == name)
{
return method;
}
}
}
return null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ab9415f1c774b36a29d07f9f462c911
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -46,3 +46,13 @@ Adapter implementations may add package-specific components to created agents, b
`NoAvoidanceAdapter` is the control group. It moves agents directly by preferred velocity and does not perform avoidance. `NoAvoidanceAdapter` is the control group. It moves agents directly by preferred velocity and does not perform avoidance.
The A* RVO, RVO2, and Unity NavMesh adapters currently start as placeholders so their worktree diffs stay easy to review. The A* RVO, RVO2, and Unity NavMesh adapters currently start as placeholders so their worktree diffs stay easy to review.
## RVO2 / RVO2-3D Adapter
`Rvo2Adapter` is intentionally a reflection adapter. The benchmark must compile even when no RVO2 package or source is present.
- Without RVO2 source/package, selecting `Rvo2` creates the same benchmark primitives and reports the missing package in the Game view `Status` line.
- To enable the real framework, place a C# RVO2 or RVO2-3D implementation under `Assets/` or add it as a package under `Packages/`.
- The adapter looks for a simulator type such as `RVO.Simulator`, `RVO2.Simulator`, `RVO3D.Simulator`, or `RVO.Simulator3D`.
- The required simulator methods are `addAgent`/`AddAgent`, `setAgentPrefVelocity`/`SetAgentPrefVelocity`, `getAgentVelocity`/`GetAgentVelocity`, and `doStep`/`DoStep`.
- 2D and 2.5D modes bind to an RVO `Vector2` style type and map Unity `x/z` to RVO `x/y`. 3D mode binds to an RVO `Vector3` style type when available.