Import RVO2 CS source

This commit is contained in:
JSD\13999
2026-06-13 14:57:30 +08:00
parent 47142e749d
commit 6af887adee
30 changed files with 3904 additions and 11 deletions

View File

@@ -12,7 +12,7 @@ namespace FishROV.AvoidanceBenchmark
private AvoidanceBenchmarkConfig config; private AvoidanceBenchmarkConfig config;
private bool initialized; private bool initialized;
public string Name => "RVO2 / RVO2-3D 本地避障"; public string Name => "RVO2 本地避障";
public bool IsAvailable => bridge != null && bridge.IsAvailable; public bool IsAvailable => bridge != null && bridge.IsAvailable;
public string Status => bridge != null public string Status => bridge != null
? bridge.Status ? bridge.Status
@@ -52,6 +52,7 @@ namespace FishROV.AvoidanceBenchmark
return; return;
} }
bridge.SetAgentPosition(id, agent.transform.position);
bridge.SetPreferredVelocity(id, preferredVelocity); bridge.SetPreferredVelocity(id, preferredVelocity);
} }
@@ -104,6 +105,7 @@ namespace FishROV.AvoidanceBenchmark
private readonly string status; private readonly string status;
private readonly MethodInfo addAgent; private readonly MethodInfo addAgent;
private readonly MethodInfo setAgentPrefVelocity; private readonly MethodInfo setAgentPrefVelocity;
private readonly MethodInfo setAgentPosition;
private readonly MethodInfo doStep; private readonly MethodInfo doStep;
private readonly MethodInfo getAgentVelocity; private readonly MethodInfo getAgentVelocity;
private readonly MethodInfo setTimeStep; private readonly MethodInfo setTimeStep;
@@ -119,6 +121,7 @@ namespace FishROV.AvoidanceBenchmark
string status, string status,
MethodInfo addAgent, MethodInfo addAgent,
MethodInfo setAgentPrefVelocity, MethodInfo setAgentPrefVelocity,
MethodInfo setAgentPosition,
MethodInfo doStep, MethodInfo doStep,
MethodInfo getAgentVelocity, MethodInfo getAgentVelocity,
MethodInfo setTimeStep, MethodInfo setTimeStep,
@@ -133,6 +136,7 @@ namespace FishROV.AvoidanceBenchmark
this.status = status; this.status = status;
this.addAgent = addAgent; this.addAgent = addAgent;
this.setAgentPrefVelocity = setAgentPrefVelocity; this.setAgentPrefVelocity = setAgentPrefVelocity;
this.setAgentPosition = setAgentPosition;
this.doStep = doStep; this.doStep = doStep;
this.getAgentVelocity = getAgentVelocity; this.getAgentVelocity = getAgentVelocity;
this.setTimeStep = setTimeStep; this.setTimeStep = setTimeStep;
@@ -168,6 +172,7 @@ namespace FishROV.AvoidanceBenchmark
var addAgent = FindMethod(simulatorType, "addAgent", "AddAgent", new[] { agentVectorType }); var addAgent = FindMethod(simulatorType, "addAgent", "AddAgent", new[] { agentVectorType });
var setAgentPrefVelocity = FindMethod(simulatorType, "setAgentPrefVelocity", "SetAgentPrefVelocity", new[] { typeof(int), agentVectorType }); var setAgentPrefVelocity = FindMethod(simulatorType, "setAgentPrefVelocity", "SetAgentPrefVelocity", new[] { typeof(int), agentVectorType });
var setAgentPosition = FindMethod(simulatorType, "setAgentPosition", "SetAgentPosition", new[] { typeof(int), agentVectorType });
var getAgentVelocity = FindMethod(simulatorType, "getAgentVelocity", "GetAgentVelocity", new[] { typeof(int) }); var getAgentVelocity = FindMethod(simulatorType, "getAgentVelocity", "GetAgentVelocity", new[] { typeof(int) });
var doStep = FindMethod(simulatorType, "doStep", "DoStep", Type.EmptyTypes); var doStep = FindMethod(simulatorType, "doStep", "DoStep", Type.EmptyTypes);
var setTimeStep = FindMethod(simulatorType, "setTimeStep", "SetTimeStep", new[] { typeof(float) }); var setTimeStep = FindMethod(simulatorType, "setTimeStep", "SetTimeStep", new[] { typeof(float) });
@@ -188,6 +193,7 @@ namespace FishROV.AvoidanceBenchmark
use3D ? "RVO2-3D 已通过反射接入。" : "RVO2 已通过反射接入。", use3D ? "RVO2-3D 已通过反射接入。" : "RVO2 已通过反射接入。",
addAgent, addAgent,
setAgentPrefVelocity, setAgentPrefVelocity,
setAgentPosition,
doStep, doStep,
getAgentVelocity, getAgentVelocity,
setTimeStep, setTimeStep,
@@ -212,6 +218,23 @@ namespace FishROV.AvoidanceBenchmark
} }
} }
public void SetAgentPosition(int id, Vector3 position)
{
if (setAgentPosition == null)
{
return;
}
try
{
setAgentPosition.Invoke(simulator, new[] { id, ToRvoVector(position) });
}
catch (Exception exception)
{
Debug.LogWarning($"RVO2 同步 Agent 位置失败:{exception.Message}");
}
}
public void SetPreferredVelocity(int id, Vector3 preferredVelocity) public void SetPreferredVelocity(int id, Vector3 preferredVelocity)
{ {
try try
@@ -435,7 +458,7 @@ namespace FishROV.AvoidanceBenchmark
private static Rvo2ReflectionBridge Unavailable(string status = MissingPackageStatus) private static Rvo2ReflectionBridge Unavailable(string status = MissingPackageStatus)
{ {
return new Rvo2ReflectionBridge(null, null, null, null, false, status, null, null, null, null, null, null, null); return new Rvo2ReflectionBridge(null, null, null, null, false, status, null, null, null, null, null, null, null, null);
} }
private static object CreateOrGetSimulator(Type type) private static object CreateOrGetSimulator(Type type)

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 08562b8a001a1ee459ff33a1ad143d23
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3d0866b55235ff948be97fbeb160eb12
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2e6d028a17551e240ae4528dcafd0158
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cb86a7b56767e3b4e9c9b2614f8dfaf0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the
copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other
entities that control, are controlled by, or are under common control with
that entity. For the purposes of this definition, "control" means (i) the
power, direct or indirect, to cause the direction or management of such
entity, whether by contract or otherwise, or (ii) ownership of fifty percent
(50%) or more of the outstanding shares, or (iii) beneficial ownership of
such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.
"Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled
object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form,
made available under the License, as indicated by a copyright notice that is
included in or attached to the work (an example is provided in the Appendix
below).
"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial
revisions, annotations, elaborations, or other modifications represent, as a
whole, an original work of authorship. For the purposes of this License,
Derivative Works shall not include works that remain separable from, or
merely link (or bind by name) to the interfaces of, the Work and Derivative
Works thereof.
"Contribution" shall mean any work of authorship, including the original
version of the Work and any modifications or additions to that Work or
Derivative Works thereof, that is intentionally submitted to Licensor for
inclusion in the Work by the copyright owner or by an individual or Legal
Entity authorized to submit on behalf of the copyright owner. For the
purposes of this definition, "submitted" means any form of electronic,
verbal, or written communication sent to the Licensor or its
representatives, including but not limited to communication on electronic
mailing lists, source code control systems, and issue tracking systems that
are managed by, or on behalf of, the Licensor for the purpose of discussing
and improving the Work, but excluding communication that is conspicuously
marked or otherwise designated in writing by the copyright owner as "Not a
Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on
behalf of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
reproduce, prepare Derivative Works of, publicly display, publicly perform,
sublicense, and distribute the Work and such Derivative Works in Source or
Object form.
3. Grant of Patent License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
this section) patent license to make, have made, use, offer to sell, sell,
import, and otherwise transfer the Work, where such license applies only to
those patent claims licensable by such Contributor that are necessarily
infringed by their Contribution(s) alone or by combination of their
Contribution(s) with the Work to which such Contribution(s) was submitted.
If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this
License for that Work shall terminate as of the date such litigation is
filed.
4. Redistribution. You may reproduce and distribute copies of the Work or
Derivative Works thereof in any medium, with or without modifications, and
in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a
copy of this License; and
(b) You must cause any modified files to carry prominent notices stating
that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You
distribute, all copyright, patent, trademark, and attribution notices
from the Source form of the Work, excluding those notices that do not
pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable
copy of the attribution notices contained within such NOTICE file,
excluding those notices that do not pertain to any part of the
Derivative Works, in at least one of the following places: within a
NOTICE text file distributed as part of the Derivative Works; within the
Source form or documentation, if provided along with the Derivative
Works; or, within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents of the
NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative
Works that You distribute, alongside or as an addendum to the NOTICE
text from the Work, provided that such additional attribution notices
cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may
provide additional or different license terms and conditions for use,
reproduction, or distribution of Your modifications, or for any such
Derivative Works as a whole, provided Your use, reproduction, and
distribution of the Work otherwise complies with the conditions stated in
this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any
Contribution intentionally submitted for inclusion in the Work by You to the
Licensor shall be under the terms and conditions of this License, without
any additional terms or conditions. Notwithstanding the above, nothing
herein shall supersede or modify the terms of any separate license agreement
you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as
required for reasonable and customary use in describing the origin of the
Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied, including, without limitation, any
warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or
FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining
the appropriateness of using or redistributing the Work and assume any risks
associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in
tort (including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed to
in writing, shall any Contributor be liable to You for damages, including
any direct, indirect, special, incidental, or consequential damages of any
character arising as a result of this License or out of the use or inability
to use the Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all other
commercial damages or losses), even if such Contributor has been advised of
the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or
Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with this License. However, in accepting such
obligations, You may act only on Your own behalf and on Your sole
responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any
liability incurred by, or claims asserted against, such Contributor by
reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included
on the same "printed page" as the copyright notice for easier identification
within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a6d8b6920c5e24c40ac8c3b79ef24675
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,111 @@
<!--
README.md
RVO2 Library C#
SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
SPDX-License-Identifier: CC-BY-SA-4.0
Creative Commons Attribution-ShareAlike 4.0 International Public License
You are free to:
* Share -- copy and redistribute the material in any medium or format
* ShareAlike -- If you remix, transform, or build upon the material, you must
distribute your contributions under the same license as the original
* Adapt -- remix, transform, and build upon the material for any purpose, even
commercially.
The licensor cannot revoke these freedoms as long as you follow the license
terms.
Under the following terms:
* Attribution -- You must give appropriate credit, provide a link to the
license, and indicate if changes were made. You may do so in any reasonable
manner, but not in any way that suggests the licensor endorses you or your
use.
* No additional restrictions -- You may not apply legal terms or technological
measures that legally restrict others from doing anything the license
permits.
Notices:
* You do not have to comply with the license for elements of the material in
the public domain or where your use is permitted by an applicable exception
or limitation.
* No warranties are given. The license may not give you all of the permissions
necessary for your intended use. For example, other rights such as publicity,
privacy, or moral rights may limit how you use the material.
Please send all bug reports to <geom@cs.unc.edu>.
The authors may be contacted via:
Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
Dept. of Computer Science
201 S. Columbia St.
Frederick P. Brooks, Jr. Computer Science Bldg.
Chapel Hill, N.C. 27599-3175
United States of America
<https://gamma.cs.unc.edu/RVO2/>
-->
Optimal Reciprocal Collision Avoidance for C#
=============================================
<https://gamma.cs.unc.edu/RVO2/>
[![DOI](https://zenodo.org/badge/45011155.svg)](https://zenodo.org/badge/latestdoi/45011155)
We present a formal approach to reciprocal collision avoidance, where multiple
independent mobile robots or agents need to avoid collisions with each other
without communication among agents while moving in a common workspace. Our
formulation, optimal reciprocal collision avoidance (ORCA), provides sufficient
conditions for collision-free motion by letting each agent take half of the
responsibility of avoiding pairwise collisions. Selecting the optimal action for
each agent is reduced to solving a low-dimensional linear program, and we prove
that the resulting motions are smooth. We test our optimal reciprocal collision
avoidance approach on several dense and complex simulation scenarios workspaces
involving thousands of agents, and compute collision-free actions for all of
them in only a few milliseconds.
RVO2 Library C# is an open-source C# .NET 10 implementation of our algorithm in
two dimensions. It has a simple API for third-party applications. The user
specifies static obstacles, agents, and the preferred velocities of the agents.
The simulation is performed step-by-step via a simple call to the library. The
simulation is fully accessible and manipulable during runtime.
![Build Status](https://github.com/snape/RVO2-CS/actions/workflows/ci.yml/badge.svg?branch=main)
<!-- REUSE-IgnoreStart -->
SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
&nbsp;&nbsp;<https://www.apache.org/licenses/LICENSE-2.0>
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Please send all bug reports to [geom@cs.unc.edu](mailto:geom@cs.unc.edu).
The authors may be contacted via:
Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
Dept. of Computer Science
201 S. Columbia St.
Frederick P. Brooks, Jr. Computer Science Bldg.
Chapel Hill, N.C. 27599-3175
United States of America
<!-- REUSE-IgnoreEnd -->

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d4db5e3fc23bec64cb3ea454f330224a
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6af893cb0842767489c05c232f3c840c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,719 @@
/*
* Agent.cs
* RVO2 Library C#
*
* SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Please send all bug reports to <geom@cs.unc.edu>.
*
* The authors may be contacted via:
*
* Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
* Dept. of Computer Science
* 201 S. Columbia St.
* Frederick P. Brooks, Jr. Computer Science Bldg.
* Chapel Hill, N.C. 27599-3175
* United States of America
*
* <http://gamma.cs.unc.edu/RVO2/>
*/
using System;
using System.Collections.Generic;
namespace RVO
{
/// <summary>Defines an agent in the simulation.</summary>
internal class Agent
{
internal IList<KeyValuePair<float, Agent>> _agentNeighbors = new List<KeyValuePair<float, Agent>>();
internal IList<KeyValuePair<float, Obstacle>> _obstacleNeighbors = new List<KeyValuePair<float, Obstacle>>();
internal IList<Line> _orcaLines = new List<Line>();
internal Vector2 _position;
internal Vector2 _prefVelocity;
internal Vector2 _velocity;
internal int _id = 0;
internal int _maxNeighbors = 0;
internal float _maxSpeed = 0.0f;
internal float _neighborDist = 0.0f;
internal float _radius = 0.0f;
internal float _timeHorizon = 0.0f;
internal float _timeHorizonObst = 0.0f;
private Vector2 _newVelocity;
/// <summary>Computes the neighbors of this agent.</summary>
internal void ComputeNeighbors()
{
_obstacleNeighbors.Clear();
float range = _timeHorizonObst * _maxSpeed + _radius;
float rangeSq = range * range;
Simulator.Instance._kdTree.ComputeObstacleNeighbors(this, rangeSq);
_agentNeighbors.Clear();
if (_maxNeighbors > 0)
{
rangeSq = _neighborDist * _neighborDist;
Simulator.Instance._kdTree.ComputeAgentNeighbors(this, ref rangeSq);
}
}
/// <summary>Computes the new velocity of this agent.</summary>
internal void ComputeNewVelocity()
{
_orcaLines.Clear();
float invTimeHorizonObst = 1.0f / _timeHorizonObst;
/* Create obstacle ORCA lines. */
for (int i = 0; i < _obstacleNeighbors.Count; ++i)
{
Obstacle obstacle1 = _obstacleNeighbors[i].Value;
Obstacle obstacle2 = obstacle1._next;
Vector2 relativePosition1 = obstacle1._point - _position;
Vector2 relativePosition2 = obstacle2._point - _position;
/*
* Check if velocity obstacle of obstacle is already taken care
* of by previously constructed obstacle ORCA lines.
*/
bool alreadyCovered = false;
for (int j = 0; j < _orcaLines.Count; ++j)
{
if (RVOMath.Det(invTimeHorizonObst * relativePosition1 - _orcaLines[j].Point, _orcaLines[j].Direction) - invTimeHorizonObst * _radius >= -RVOMath.RVO_EPSILON && RVOMath.Det(invTimeHorizonObst * relativePosition2 - _orcaLines[j].Point, _orcaLines[j].Direction) - invTimeHorizonObst * _radius >= -RVOMath.RVO_EPSILON)
{
alreadyCovered = true;
break;
}
}
if (alreadyCovered)
{
continue;
}
/* Not yet covered. Check for collisions. */
float distSq1 = RVOMath.AbsSq(relativePosition1);
float distSq2 = RVOMath.AbsSq(relativePosition2);
float radiusSq = _radius * _radius;
Vector2 obstacleVector = obstacle2._point - obstacle1._point;
float s = (-relativePosition1 * obstacleVector) / RVOMath.AbsSq(obstacleVector);
float distSqLine = RVOMath.AbsSq(-relativePosition1 - s * obstacleVector);
Line line;
if (s < 0.0f && distSq1 <= radiusSq)
{
/* Collision with left vertex. Ignore if non-convex. */
if (obstacle1._convex)
{
line.Point = new Vector2(0.0f, 0.0f);
line.Direction = RVOMath.Normalize(new Vector2(-relativePosition1.Y, relativePosition1.X));
_orcaLines.Add(line);
}
continue;
}
else if (s > 1.0f && distSq2 <= radiusSq)
{
/*
* Collision with right vertex. Ignore if non-convex or if
* it will be taken care of by neighboring obstacle.
*/
if (obstacle2._convex && RVOMath.Det(relativePosition2, obstacle2._direction) >= 0.0f)
{
line.Point = new Vector2(0.0f, 0.0f);
line.Direction = RVOMath.Normalize(new Vector2(-relativePosition2.Y, relativePosition2.X));
_orcaLines.Add(line);
}
continue;
}
else if (s >= 0.0f && s <= 1.0f && distSqLine <= radiusSq)
{
/* Collision with obstacle segment. */
line.Point = new Vector2(0.0f, 0.0f);
line.Direction = -obstacle1._direction;
_orcaLines.Add(line);
continue;
}
/*
* No collision. Compute legs. When obliquely viewed, both legs
* can come from a single vertex. Legs extend cut-off line when
* non-convex vertex.
*/
Vector2 leftLegDirection, rightLegDirection;
if (s < 0.0f && distSqLine <= radiusSq)
{
/*
* Obstacle viewed obliquely so that left vertex
* defines velocity obstacle.
*/
if (!obstacle1._convex)
{
/* Ignore obstacle. */
continue;
}
obstacle2 = obstacle1;
float leg1 = MathF.Sqrt(distSq1 - radiusSq);
leftLegDirection = new Vector2(relativePosition1.X * leg1 - relativePosition1.Y * _radius, relativePosition1.X * _radius + relativePosition1.Y * leg1) / distSq1;
rightLegDirection = new Vector2(relativePosition1.X * leg1 + relativePosition1.Y * _radius, -relativePosition1.X * _radius + relativePosition1.Y * leg1) / distSq1;
}
else if (s > 1.0f && distSqLine <= radiusSq)
{
/*
* Obstacle viewed obliquely so that
* right vertex defines velocity obstacle.
*/
if (!obstacle2._convex)
{
/* Ignore obstacle. */
continue;
}
obstacle1 = obstacle2;
float leg2 = MathF.Sqrt(distSq2 - radiusSq);
leftLegDirection = new Vector2(relativePosition2.X * leg2 - relativePosition2.Y * _radius, relativePosition2.X * _radius + relativePosition2.Y * leg2) / distSq2;
rightLegDirection = new Vector2(relativePosition2.X * leg2 + relativePosition2.Y * _radius, -relativePosition2.X * _radius + relativePosition2.Y * leg2) / distSq2;
}
else
{
/* Usual situation. */
if (obstacle1._convex)
{
float leg1 = MathF.Sqrt(distSq1 - radiusSq);
leftLegDirection = new Vector2(relativePosition1.X * leg1 - relativePosition1.Y * _radius, relativePosition1.X * _radius + relativePosition1.Y * leg1) / distSq1;
}
else
{
/* Left vertex non-convex; left leg extends cut-off line. */
leftLegDirection = -obstacle1._direction;
}
if (obstacle2._convex)
{
float leg2 = MathF.Sqrt(distSq2 - radiusSq);
rightLegDirection = new Vector2(relativePosition2.X * leg2 + relativePosition2.Y * _radius, -relativePosition2.X * _radius + relativePosition2.Y * leg2) / distSq2;
}
else
{
/* Right vertex non-convex; right leg extends cut-off line. */
rightLegDirection = obstacle1._direction;
}
}
/*
* Legs can never point into neighboring edge when convex
* vertex, take cutoff-line of neighboring edge instead. If
* velocity projected on "foreign" leg, no constraint is added.
*/
Obstacle leftNeighbor = obstacle1._previous;
bool isLeftLegForeign = false;
bool isRightLegForeign = false;
if (obstacle1._convex && RVOMath.Det(leftLegDirection, -leftNeighbor._direction) >= 0.0f)
{
/* Left leg points into obstacle. */
leftLegDirection = -leftNeighbor._direction;
isLeftLegForeign = true;
}
if (obstacle2._convex && RVOMath.Det(rightLegDirection, obstacle2._direction) <= 0.0f)
{
/* Right leg points into obstacle. */
rightLegDirection = obstacle2._direction;
isRightLegForeign = true;
}
/* Compute cut-off centers. */
Vector2 leftCutOff = invTimeHorizonObst * (obstacle1._point - _position);
Vector2 rightCutOff = invTimeHorizonObst * (obstacle2._point - _position);
Vector2 cutOffVector = rightCutOff - leftCutOff;
/* Project current velocity on velocity obstacle. */
/* Check if current velocity is projected on cutoff circles. */
float t = obstacle1 == obstacle2 ? 0.5f : ((_velocity - leftCutOff) * cutOffVector) / RVOMath.AbsSq(cutOffVector);
float tLeft = (_velocity - leftCutOff) * leftLegDirection;
float tRight = (_velocity - rightCutOff) * rightLegDirection;
if ((t < 0.0f && tLeft < 0.0f) || (obstacle1 == obstacle2 && tLeft < 0.0f && tRight < 0.0f))
{
/* Project on left cut-off circle. */
Vector2 unitW = RVOMath.Normalize(_velocity - leftCutOff);
line.Direction = new Vector2(unitW.Y, -unitW.X);
line.Point = leftCutOff + _radius * invTimeHorizonObst * unitW;
_orcaLines.Add(line);
continue;
}
else if (t > 1.0f && tRight < 0.0f)
{
/* Project on right cut-off circle. */
Vector2 unitW = RVOMath.Normalize(_velocity - rightCutOff);
line.Direction = new Vector2(unitW.Y, -unitW.X);
line.Point = rightCutOff + _radius * invTimeHorizonObst * unitW;
_orcaLines.Add(line);
continue;
}
/*
* Project on left leg, right leg, or cut-off line, whichever is
* closest to velocity.
*/
float distSqCutoff = (t < 0.0f || t > 1.0f || obstacle1 == obstacle2) ? float.PositiveInfinity : RVOMath.AbsSq(_velocity - (leftCutOff + t * cutOffVector));
float distSqLeft = tLeft < 0.0f ? float.PositiveInfinity : RVOMath.AbsSq(_velocity - (leftCutOff + tLeft * leftLegDirection));
float distSqRight = tRight < 0.0f ? float.PositiveInfinity : RVOMath.AbsSq(_velocity - (rightCutOff + tRight * rightLegDirection));
if (distSqCutoff <= distSqLeft && distSqCutoff <= distSqRight)
{
/* Project on cut-off line. */
line.Direction = -obstacle1._direction;
line.Point = leftCutOff + _radius * invTimeHorizonObst * new Vector2(-line.Direction.Y, line.Direction.X);
_orcaLines.Add(line);
continue;
}
if (distSqLeft <= distSqRight)
{
/* Project on left leg. */
if (isLeftLegForeign)
{
continue;
}
line.Direction = leftLegDirection;
line.Point = leftCutOff + _radius * invTimeHorizonObst * new Vector2(-line.Direction.Y, line.Direction.X);
_orcaLines.Add(line);
continue;
}
/* Project on right leg. */
if (isRightLegForeign)
{
continue;
}
line.Direction = -rightLegDirection;
line.Point = rightCutOff + _radius * invTimeHorizonObst * new Vector2(-line.Direction.Y, line.Direction.X);
_orcaLines.Add(line);
}
int numObstLines = _orcaLines.Count;
float invTimeHorizon = 1.0f / _timeHorizon;
/* Create agent ORCA lines. */
for (int i = 0; i < _agentNeighbors.Count; ++i)
{
Agent other = _agentNeighbors[i].Value;
Vector2 relativePosition = other._position - _position;
Vector2 relativeVelocity = _velocity - other._velocity;
float distSq = RVOMath.AbsSq(relativePosition);
float combinedRadius = _radius + other._radius;
float combinedRadiusSq = combinedRadius * combinedRadius;
Line line;
Vector2 u;
if (distSq > combinedRadiusSq)
{
/* No collision. */
Vector2 w = relativeVelocity - invTimeHorizon * relativePosition;
/* Vector from cutoff center to relative velocity. */
float wLengthSq = RVOMath.AbsSq(w);
float dotProduct1 = w * relativePosition;
if (dotProduct1 < 0.0f && dotProduct1 * dotProduct1 > combinedRadiusSq * wLengthSq)
{
/* Project on cut-off circle. */
float wLength = MathF.Sqrt(wLengthSq);
Vector2 unitW = w / wLength;
line.Direction = new Vector2(unitW.Y, -unitW.X);
u = (combinedRadius * invTimeHorizon - wLength) * unitW;
}
else
{
/* Project on legs. */
float leg = MathF.Sqrt(distSq - combinedRadiusSq);
if (RVOMath.Det(relativePosition, w) > 0.0f)
{
/* Project on left leg. */
line.Direction = new Vector2(relativePosition.X * leg - relativePosition.Y * combinedRadius, relativePosition.X * combinedRadius + relativePosition.Y * leg) / distSq;
}
else
{
/* Project on right leg. */
line.Direction = -new Vector2(relativePosition.X * leg + relativePosition.Y * combinedRadius, -relativePosition.X * combinedRadius + relativePosition.Y * leg) / distSq;
}
float dotProduct2 = relativeVelocity * line.Direction;
u = dotProduct2 * line.Direction - relativeVelocity;
}
}
else
{
/* Collision. Project on cut-off circle of time timeStep. */
float invTimeStep = 1.0f / Simulator.Instance.TimeStep;
/* Vector from cutoff center to relative velocity. */
Vector2 w = relativeVelocity - invTimeStep * relativePosition;
float wLength = RVOMath.Abs(w);
Vector2 unitW = w / wLength;
line.Direction = new Vector2(unitW.Y, -unitW.X);
u = (combinedRadius * invTimeStep - wLength) * unitW;
}
line.Point = _velocity + 0.5f * u;
_orcaLines.Add(line);
}
int lineFail = LinearProgram2(_orcaLines, _maxSpeed, _prefVelocity, false, out _newVelocity);
if (lineFail < _orcaLines.Count)
{
LinearProgram3(_orcaLines, numObstLines, lineFail, _maxSpeed, ref _newVelocity);
}
}
/// <summary>Inserts an agent neighbor into the set of neighbors of this
/// agent.</summary>
///
/// <param name="agent">A pointer to the agent to be inserted.</param>
/// <param name="rangeSq">The squared range around this agent.</param>
internal void InsertAgentNeighbor(Agent agent, ref float rangeSq)
{
if (this != agent)
{
float distSq = RVOMath.AbsSq(_position - agent._position);
if (distSq < rangeSq)
{
if (_agentNeighbors.Count < _maxNeighbors)
{
_agentNeighbors.Add(new KeyValuePair<float, Agent>(distSq, agent));
}
int i = _agentNeighbors.Count - 1;
while (i != 0 && distSq < _agentNeighbors[i - 1].Key)
{
_agentNeighbors[i] = _agentNeighbors[i - 1];
--i;
}
_agentNeighbors[i] = new KeyValuePair<float, Agent>(distSq, agent);
if (_agentNeighbors.Count == _maxNeighbors)
{
rangeSq = _agentNeighbors[_agentNeighbors.Count - 1].Key;
}
}
}
}
/// <summary>Inserts a static obstacle neighbor into the set of neighbors
/// of this agent.</summary>
///
/// <param name="obstacle">The number of the static obstacle to be
/// inserted.</param>
/// <param name="rangeSq">The squared range around this agent.</param>
internal void InsertObstacleNeighbor(Obstacle obstacle, float rangeSq)
{
Obstacle nextObstacle = obstacle._next;
float distSq = RVOMath.DistSqPointLineSegment(obstacle._point, nextObstacle._point, _position);
if (distSq < rangeSq)
{
_obstacleNeighbors.Add(new KeyValuePair<float, Obstacle>(distSq, obstacle));
int i = _obstacleNeighbors.Count - 1;
while (i != 0 && distSq < _obstacleNeighbors[i - 1].Key)
{
_obstacleNeighbors[i] = _obstacleNeighbors[i - 1];
--i;
}
_obstacleNeighbors[i] = new KeyValuePair<float, Obstacle>(distSq, obstacle);
}
}
/// <summary>Updates the two-dimensional position and two-dimensional
/// velocity of this agent.</summary>
internal void Update()
{
_velocity = _newVelocity;
if (RVOMath.AbsSq(_velocity) > _maxSpeed * _maxSpeed)
{
_velocity = RVOMath.Normalize(_velocity) * _maxSpeed;
}
_position += _velocity * Simulator.Instance.TimeStep;
}
/// <summary>Solves a one-dimensional linear program on a specified line
/// subject to linear constraints defined by lines and a circular
/// constraint.</summary>
///
/// <returns>True if successful.</returns>
///
/// <param name="lines">Lines defining the linear constraints.</param>
/// <param name="lineNo">The specified line constraint.</param>
/// <param name="radius">The radius of the circular constraint.</param>
/// <param name="optVelocity">The optimization velocity.</param>
/// <param name="directionOpt">True if the direction should be optimized.
/// </param>
/// <param name="result">A reference to the result of the linear program.
/// </param>
private bool LinearProgram1(IList<Line> lines, int lineNo, float radius, Vector2 optVelocity, bool directionOpt, ref Vector2 result)
{
float dotProduct = lines[lineNo].Point * lines[lineNo].Direction;
float discriminant = dotProduct * dotProduct + radius * radius - RVOMath.AbsSq(lines[lineNo].Point);
if (discriminant < 0.0f)
{
/* Max speed circle fully invalidates line lineNo. */
return false;
}
float sqrtDiscriminant = MathF.Sqrt(discriminant);
float tLeft = -dotProduct - sqrtDiscriminant;
float tRight = -dotProduct + sqrtDiscriminant;
for (int i = 0; i < lineNo; ++i)
{
float denominator = RVOMath.Det(lines[lineNo].Direction, lines[i].Direction);
float numerator = RVOMath.Det(lines[i].Direction, lines[lineNo].Point - lines[i].Point);
if (MathF.Abs(denominator) <= RVOMath.RVO_EPSILON)
{
/* Lines lineNo and i are (almost) parallel. */
if (numerator < 0.0f)
{
return false;
}
continue;
}
float t = numerator / denominator;
if (denominator >= 0.0f)
{
/* Line i bounds line lineNo on the right. */
tRight = Math.Min(tRight, t);
}
else
{
/* Line i bounds line lineNo on the left. */
tLeft = Math.Max(tLeft, t);
}
if (tLeft > tRight)
{
return false;
}
}
if (directionOpt)
{
/* Optimize direction. */
if (optVelocity * lines[lineNo].Direction > 0.0f)
{
/* Take right extreme. */
result = lines[lineNo].Point + tRight * lines[lineNo].Direction;
}
else
{
/* Take left extreme. */
result = lines[lineNo].Point + tLeft * lines[lineNo].Direction;
}
}
else
{
/* Optimize closest point. */
float t = lines[lineNo].Direction * (optVelocity - lines[lineNo].Point);
if (t < tLeft)
{
result = lines[lineNo].Point + tLeft * lines[lineNo].Direction;
}
else if (t > tRight)
{
result = lines[lineNo].Point + tRight * lines[lineNo].Direction;
}
else
{
result = lines[lineNo].Point + t * lines[lineNo].Direction;
}
}
return true;
}
/// <summary>Solves a two-dimensional linear program subject to linear
/// constraints defined by lines and a circular constraint.</summary>
///
/// <returns>The number of the line it fails on, and the number of lines
/// if successful.</returns>
///
/// <param name="lines">Lines defining the linear constraints.</param>
/// <param name="radius">The radius of the circular constraint.</param>
/// <param name="optVelocity">The optimization velocity.</param>
/// <param name="directionOpt">True if the direction should be optimized.
/// </param>
/// <param name="result">A reference to the result of the linear program.
/// </param>
private int LinearProgram2(IList<Line> lines, float radius, Vector2 optVelocity, bool directionOpt, out Vector2 result)
{
if (directionOpt)
{
/*
* Optimize direction. Note that the optimization velocity is of
* unit length in this case.
*/
result = optVelocity * radius;
}
else if (RVOMath.AbsSq(optVelocity) > radius * radius)
{
/* Optimize closest point and outside circle. */
result = RVOMath.Normalize(optVelocity) * radius;
}
else
{
/* Optimize closest point and inside circle. */
result = optVelocity;
}
for (int i = 0; i < lines.Count; ++i)
{
if (RVOMath.Det(lines[i].Direction, lines[i].Point - result) > 0.0f)
{
/* Result does not satisfy constraint i. Compute new optimal result. */
Vector2 tempResult = result;
if (!LinearProgram1(lines, i, radius, optVelocity, directionOpt, ref result))
{
result = tempResult;
return i;
}
}
}
return lines.Count;
}
/// <summary>Solves a two-dimensional linear program subject to linear
/// constraints defined by lines and a circular constraint.</summary>
///
/// <param name="lines">Lines defining the linear constraints.</param>
/// <param name="numObstLines">Count of obstacle lines.</param>
/// <param name="beginLine">The line on which the 2-d linear program
/// failed.</param>
/// <param name="radius">The radius of the circular constraint.</param>
/// <param name="result">A reference to the result of the linear program.
/// </param>
private void LinearProgram3(IList<Line> lines, int numObstLines, int beginLine, float radius, ref Vector2 result)
{
float distance = 0.0f;
for (int i = beginLine; i < lines.Count; ++i)
{
if (RVOMath.Det(lines[i].Direction, lines[i].Point - result) > distance)
{
/* Result does not satisfy constraint of line i. */
IList<Line> projLines = new List<Line>();
for (int ii = 0; ii < numObstLines; ++ii)
{
projLines.Add(lines[ii]);
}
for (int j = numObstLines; j < i; ++j)
{
Line line;
float determinant = RVOMath.Det(lines[i].Direction, lines[j].Direction);
if (MathF.Abs(determinant) <= RVOMath.RVO_EPSILON)
{
/* Line i and line j are parallel. */
if (lines[i].Direction * lines[j].Direction > 0.0f)
{
/* Line i and line j point in the same direction. */
continue;
}
else
{
/* Line i and line j point in opposite direction. */
line.Point = 0.5f * (lines[i].Point + lines[j].Point);
}
}
else
{
line.Point = lines[i].Point + (RVOMath.Det(lines[j].Direction, lines[i].Point - lines[j].Point) / determinant) * lines[i].Direction;
}
line.Direction = RVOMath.Normalize(lines[j].Direction - lines[i].Direction);
projLines.Add(line);
}
Vector2 tempResult = result;
if (LinearProgram2(projLines, radius, new Vector2(-lines[i].Direction.Y, lines[i].Direction.X), true, out result) < projLines.Count)
{
/*
* This should in principle not happen. The result is by
* definition already in the feasible region of this
* linear program. If it fails, it is due to small
* floating point error, and the current result is kept.
*/
result = tempResult;
}
distance = RVOMath.Det(lines[i].Direction, lines[i].Point - result);
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,590 @@
/*
* KdTree.cs
* RVO2 Library C#
*
* SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Please send all bug reports to <geom@cs.unc.edu>.
*
* The authors may be contacted via:
*
* Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
* Dept. of Computer Science
* 201 S. Columbia St.
* Frederick P. Brooks, Jr. Computer Science Bldg.
* Chapel Hill, N.C. 27599-3175
* United States of America
*
* <http://gamma.cs.unc.edu/RVO2/>
*/
using System;
using System.Collections.Generic;
namespace RVO
{
/// <summary>Defines k-D trees for agents and static obstacles in the
/// simulation.</summary>
internal class KdTree
{
/// <summary>Defines a node of an agent k-D tree.</summary>
private struct AgentTreeNode
{
internal int _begin;
internal int _end;
internal int _left;
internal int _right;
internal float _maxX;
internal float _maxY;
internal float _minX;
internal float _minY;
}
/// <summary>Defines a pair of scalar values.</summary>
private struct FloatPair
{
private readonly float _a;
private readonly float _b;
/// <summary>Constructs and initializes a pair of scalar
/// values.</summary>
///
/// <param name="a">The first scalar value.</param>
/// <param name="b">The second scalar value.</param>
internal FloatPair(float a, float b)
{
_a = a;
_b = b;
}
/// <summary>Returns true if the first pair of scalar values is less
/// than the second pair of scalar values.</summary>
///
/// <returns>True if the first pair of scalar values is less than the
/// second pair of scalar values.</returns>
///
/// <param name="pair1">The first pair of scalar values.</param>
/// <param name="pair2">The second pair of scalar values.</param>
public static bool operator <(FloatPair pair1, FloatPair pair2)
{
return pair1._a < pair2._a || pair1._a == pair2._a && pair1._b < pair2._b;
}
/// <summary>Returns true if the first pair of scalar values is less
/// than or equal to the second pair of scalar values.</summary>
///
/// <returns>True if the first pair of scalar values is less than or
/// equal to the second pair of scalar values.</returns>
///
/// <param name="pair1">The first pair of scalar values.</param>
/// <param name="pair2">The second pair of scalar values.</param>
public static bool operator <=(FloatPair pair1, FloatPair pair2)
{
return (pair1._a == pair2._a && pair1._b == pair2._b) || pair1 < pair2;
}
/// <summary>Returns true if the first pair of scalar values is
/// greater than the second pair of scalar values.</summary>
///
/// <returns>True if the first pair of scalar values is greater than
/// the second pair of scalar values.</returns>
///
/// <param name="pair1">The first pair of scalar values.</param>
/// <param name="pair2">The second pair of scalar values.</param>
public static bool operator >(FloatPair pair1, FloatPair pair2)
{
return !(pair1 <= pair2);
}
/// <summary>Returns true if the first pair of scalar values is
/// greater than or equal to the second pair of scalar values.
/// </summary>
///
/// <returns>True if the first pair of scalar values is greater than
/// or equal to the second pair of scalar values.</returns>
///
/// <param name="pair1">The first pair of scalar values.</param>
/// <param name="pair2">The second pair of scalar values.</param>
public static bool operator >=(FloatPair pair1, FloatPair pair2)
{
return !(pair1 < pair2);
}
}
/// <summary>Defines a node of an obstacle k-D tree.</summary>
private class ObstacleTreeNode
{
internal Obstacle _obstacle;
internal ObstacleTreeNode _left;
internal ObstacleTreeNode _right;
};
/// <summary>The maximum size of an agent k-D tree leaf.</summary>
/* Empirically chosen; balances tree depth against per-leaf work. */
private const int MAX_LEAF_SIZE = 10;
private Agent[] _agents;
private AgentTreeNode[] _agentTree;
private ObstacleTreeNode _obstacleTree;
/// <summary>Builds an agent k-D tree.</summary>
internal void BuildAgentTree()
{
if (_agents is null || _agents.Length != Simulator.Instance._agents.Count)
{
_agents = new Agent[Simulator.Instance._agents.Count];
for (int i = 0; i < _agents.Length; ++i)
{
_agents[i] = Simulator.Instance._agents[i];
}
_agentTree = new AgentTreeNode[2 * _agents.Length];
for (int i = 0; i < _agentTree.Length; ++i)
{
_agentTree[i] = new AgentTreeNode();
}
}
if (_agents.Length != 0)
{
BuildAgentTreeRecursive(0, _agents.Length, 0);
}
}
/// <summary>Builds an obstacle k-D tree.</summary>
internal void BuildObstacleTree()
{
_obstacleTree = new ObstacleTreeNode();
IList<Obstacle> obstacles = new List<Obstacle>(Simulator.Instance._obstacles.Count);
for (int i = 0; i < Simulator.Instance._obstacles.Count; ++i)
{
obstacles.Add(Simulator.Instance._obstacles[i]);
}
_obstacleTree = BuildObstacleTreeRecursive(obstacles);
}
/// <summary>Computes the agent neighbors of the specified agent.
/// </summary>
///
/// <param name="agent">The agent for which agent neighbors are to be
/// computed.</param>
/// <param name="rangeSq">The squared range around the agent.</param>
internal void ComputeAgentNeighbors(Agent agent, ref float rangeSq)
{
QueryAgentTreeRecursive(agent, ref rangeSq, 0);
}
/// <summary>Computes the obstacle neighbors of the specified agent.
/// </summary>
///
/// <param name="agent">The agent for which obstacle neighbors are to be
/// computed.</param>
/// <param name="rangeSq">The squared range around the agent.</param>
internal void ComputeObstacleNeighbors(Agent agent, float rangeSq)
{
QueryObstacleTreeRecursive(agent, rangeSq, _obstacleTree);
}
/// <summary>Queries the visibility between two points within a specified
/// radius.</summary>
///
/// <returns>True if q1 and q2 are mutually visible within the radius;
/// false otherwise.</returns>
///
/// <param name="q1">The first point between which visibility is to be
/// tested.</param>
/// <param name="q2">The second point between which visibility is to be
/// tested.</param>
/// <param name="radius">The radius within which visibility is to be
/// tested.</param>
internal bool QueryVisibility(Vector2 q1, Vector2 q2, float radius)
{
return QueryVisibilityRecursive(q1, q2, radius, _obstacleTree);
}
/// <summary>Recursive method for building an agent k-D tree.</summary>
///
/// <param name="begin">The beginning agent k-D tree node node index.
/// </param>
/// <param name="end">The ending agent k-D tree node index.</param>
/// <param name="node">The current agent k-D tree node index.</param>
private void BuildAgentTreeRecursive(int begin, int end, int node)
{
_agentTree[node]._begin = begin;
_agentTree[node]._end = end;
_agentTree[node]._minX = _agentTree[node]._maxX = _agents[begin]._position._x;
_agentTree[node]._minY = _agentTree[node]._maxY = _agents[begin]._position._y;
for (int i = begin + 1; i < end; ++i)
{
_agentTree[node]._maxX = Math.Max(_agentTree[node]._maxX, _agents[i]._position._x);
_agentTree[node]._minX = Math.Min(_agentTree[node]._minX, _agents[i]._position._x);
_agentTree[node]._maxY = Math.Max(_agentTree[node]._maxY, _agents[i]._position._y);
_agentTree[node]._minY = Math.Min(_agentTree[node]._minY, _agents[i]._position._y);
}
if (end - begin > MAX_LEAF_SIZE)
{
/* No leaf node. */
bool isVertical = _agentTree[node]._maxX - _agentTree[node]._minX > _agentTree[node]._maxY - _agentTree[node]._minY;
float splitValue = 0.5f * (isVertical ? _agentTree[node]._maxX + _agentTree[node]._minX : _agentTree[node]._maxY + _agentTree[node]._minY);
int left = begin;
int right = end;
while (left < right)
{
while (left < right && (isVertical ? _agents[left]._position._x : _agents[left]._position._y) < splitValue)
{
++left;
}
while (right > left && (isVertical ? _agents[right - 1]._position._x : _agents[right - 1]._position._y) >= splitValue)
{
--right;
}
if (left < right)
{
Agent tempAgent = _agents[left];
_agents[left] = _agents[right - 1];
_agents[right - 1] = tempAgent;
++left;
--right;
}
}
int leftSize = left - begin;
if (leftSize == 0)
{
++leftSize;
++left;
}
_agentTree[node]._left = node + 1;
_agentTree[node]._right = node + 2 * leftSize;
BuildAgentTreeRecursive(begin, left, _agentTree[node]._left);
BuildAgentTreeRecursive(left, end, _agentTree[node]._right);
}
}
/// <summary>Recursive method for building an obstacle k-D tree.
/// </summary>
///
/// <returns>An obstacle k-D tree node.</returns>
///
/// <param name="obstacles">A list of obstacles.</param>
private ObstacleTreeNode BuildObstacleTreeRecursive(IList<Obstacle> obstacles)
{
if (obstacles.Count == 0)
{
return null;
}
ObstacleTreeNode node = new();
int optimalSplit = 0;
int minLeft = obstacles.Count;
int minRight = obstacles.Count;
for (int i = 0; i < obstacles.Count; ++i)
{
int leftSize = 0;
int rightSize = 0;
Obstacle obstacleI1 = obstacles[i];
Obstacle obstacleI2 = obstacleI1._next;
/* Compute optimal split node. */
for (int j = 0; j < obstacles.Count; ++j)
{
if (i == j)
{
continue;
}
Obstacle obstacleJ1 = obstacles[j];
Obstacle obstacleJ2 = obstacleJ1._next;
float j1LeftOfI = RVOMath.LeftOf(obstacleI1._point, obstacleI2._point, obstacleJ1._point);
float j2LeftOfI = RVOMath.LeftOf(obstacleI1._point, obstacleI2._point, obstacleJ2._point);
if (j1LeftOfI >= -RVOMath.RVO_EPSILON && j2LeftOfI >= -RVOMath.RVO_EPSILON)
{
++leftSize;
}
else if (j1LeftOfI <= RVOMath.RVO_EPSILON && j2LeftOfI <= RVOMath.RVO_EPSILON)
{
++rightSize;
}
else
{
++leftSize;
++rightSize;
}
if (new FloatPair(Math.Max(leftSize, rightSize), Math.Min(leftSize, rightSize)) >= new FloatPair(Math.Max(minLeft, minRight), Math.Min(minLeft, minRight)))
{
break;
}
}
if (new FloatPair(Math.Max(leftSize, rightSize), Math.Min(leftSize, rightSize)) < new FloatPair(Math.Max(minLeft, minRight), Math.Min(minLeft, minRight)))
{
minLeft = leftSize;
minRight = rightSize;
optimalSplit = i;
}
}
{
/* Build split node. */
IList<Obstacle> leftObstacles = new List<Obstacle>(minLeft);
for (int n = 0; n < minLeft; ++n)
{
leftObstacles.Add(null);
}
IList<Obstacle> rightObstacles = new List<Obstacle>(minRight);
for (int n = 0; n < minRight; ++n)
{
rightObstacles.Add(null);
}
int leftCounter = 0;
int rightCounter = 0;
int i = optimalSplit;
Obstacle obstacleI1 = obstacles[i];
Obstacle obstacleI2 = obstacleI1._next;
for (int j = 0; j < obstacles.Count; ++j)
{
if (i == j)
{
continue;
}
Obstacle obstacleJ1 = obstacles[j];
Obstacle obstacleJ2 = obstacleJ1._next;
float j1LeftOfI = RVOMath.LeftOf(obstacleI1._point, obstacleI2._point, obstacleJ1._point);
float j2LeftOfI = RVOMath.LeftOf(obstacleI1._point, obstacleI2._point, obstacleJ2._point);
if (j1LeftOfI >= -RVOMath.RVO_EPSILON && j2LeftOfI >= -RVOMath.RVO_EPSILON)
{
leftObstacles[leftCounter++] = obstacles[j];
}
else if (j1LeftOfI <= RVOMath.RVO_EPSILON && j2LeftOfI <= RVOMath.RVO_EPSILON)
{
rightObstacles[rightCounter++] = obstacles[j];
}
else
{
/* Split obstacle j. */
float t = RVOMath.Det(obstacleI2._point - obstacleI1._point, obstacleJ1._point - obstacleI1._point) / RVOMath.Det(obstacleI2._point - obstacleI1._point, obstacleJ1._point - obstacleJ2._point);
Vector2 splitPoint = obstacleJ1._point + t * (obstacleJ2._point - obstacleJ1._point);
Obstacle newObstacle = new();
newObstacle._point = splitPoint;
newObstacle._previous = obstacleJ1;
newObstacle._next = obstacleJ2;
newObstacle._convex = true;
newObstacle._direction = obstacleJ1._direction;
newObstacle._id = Simulator.Instance._obstacles.Count;
Simulator.Instance._obstacles.Add(newObstacle);
obstacleJ1._next = newObstacle;
obstacleJ2._previous = newObstacle;
if (j1LeftOfI > 0.0f)
{
leftObstacles[leftCounter++] = obstacleJ1;
rightObstacles[rightCounter++] = newObstacle;
}
else
{
rightObstacles[rightCounter++] = obstacleJ1;
leftObstacles[leftCounter++] = newObstacle;
}
}
}
node._obstacle = obstacleI1;
node._left = BuildObstacleTreeRecursive(leftObstacles);
node._right = BuildObstacleTreeRecursive(rightObstacles);
return node;
}
}
/// <summary>Recursive method for computing the agent neighbors of the
/// specified agent.</summary>
///
/// <param name="agent">The agent for which agent neighbors are to be
/// computed.</param>
/// <param name="rangeSq">The squared range around the agent.</param>
/// <param name="node">The current agent k-D tree node index.</param>
private void QueryAgentTreeRecursive(Agent agent, ref float rangeSq, int node)
{
if (_agentTree[node]._end - _agentTree[node]._begin <= MAX_LEAF_SIZE)
{
for (int i = _agentTree[node]._begin; i < _agentTree[node]._end; ++i)
{
agent.InsertAgentNeighbor(_agents[i], ref rangeSq);
}
}
else
{
int leftNode = _agentTree[node]._left;
float leftDx = Math.Max(0.0f, _agentTree[leftNode]._minX - agent._position._x) + Math.Max(0.0f, agent._position._x - _agentTree[leftNode]._maxX);
float leftDy = Math.Max(0.0f, _agentTree[leftNode]._minY - agent._position._y) + Math.Max(0.0f, agent._position._y - _agentTree[leftNode]._maxY);
float distSqLeft = leftDx * leftDx + leftDy * leftDy;
int rightNode = _agentTree[node]._right;
float rightDx = Math.Max(0.0f, _agentTree[rightNode]._minX - agent._position._x) + Math.Max(0.0f, agent._position._x - _agentTree[rightNode]._maxX);
float rightDy = Math.Max(0.0f, _agentTree[rightNode]._minY - agent._position._y) + Math.Max(0.0f, agent._position._y - _agentTree[rightNode]._maxY);
float distSqRight = rightDx * rightDx + rightDy * rightDy;
if (distSqLeft < distSqRight)
{
if (distSqLeft < rangeSq)
{
QueryAgentTreeRecursive(agent, ref rangeSq, leftNode);
if (distSqRight < rangeSq)
{
QueryAgentTreeRecursive(agent, ref rangeSq, rightNode);
}
}
}
else
{
if (distSqRight < rangeSq)
{
QueryAgentTreeRecursive(agent, ref rangeSq, rightNode);
if (distSqLeft < rangeSq)
{
QueryAgentTreeRecursive(agent, ref rangeSq, leftNode);
}
}
}
}
}
/// <summary>Recursive method for computing the obstacle neighbors of the
/// specified agent.</summary>
///
/// <param name="agent">The agent for which obstacle neighbors are to be
/// computed.</param>
/// <param name="rangeSq">The squared range around the agent.</param>
/// <param name="node">The current obstacle k-D node.</param>
private void QueryObstacleTreeRecursive(Agent agent, float rangeSq, ObstacleTreeNode node)
{
if (node is not null)
{
Obstacle obstacle1 = node._obstacle;
Obstacle obstacle2 = obstacle1._next;
float agentLeftOfLine = RVOMath.LeftOf(obstacle1._point, obstacle2._point, agent._position);
QueryObstacleTreeRecursive(agent, rangeSq, agentLeftOfLine >= 0.0f ? node._left : node._right);
float distSqLine = agentLeftOfLine * agentLeftOfLine / RVOMath.AbsSq(obstacle2._point - obstacle1._point);
if (distSqLine < rangeSq)
{
if (agentLeftOfLine < 0.0f)
{
/*
* Try obstacle at this node only if agent is on right side of
* obstacle (and can see obstacle).
*/
agent.InsertObstacleNeighbor(node._obstacle, rangeSq);
}
/* Try other side of line. */
QueryObstacleTreeRecursive(agent, rangeSq, agentLeftOfLine >= 0.0f ? node._right : node._left);
}
}
}
/// <summary>Recursive method for querying the visibility between two
/// points within a specified radius.</summary>
///
/// <returns>True if q1 and q2 are mutually visible within the radius;
/// false otherwise.</returns>
///
/// <param name="q1">The first point between which visibility is to be
/// tested.</param>
/// <param name="q2">The second point between which visibility is to be
/// tested.</param>
/// <param name="radius">The radius within which visibility is to be
/// tested.</param>
/// <param name="node">The current obstacle k-D node.</param>
private bool QueryVisibilityRecursive(Vector2 q1, Vector2 q2, float radius, ObstacleTreeNode node)
{
if (node is null)
{
return true;
}
Obstacle obstacle1 = node._obstacle;
Obstacle obstacle2 = obstacle1._next;
float q1LeftOfI = RVOMath.LeftOf(obstacle1._point, obstacle2._point, q1);
float q2LeftOfI = RVOMath.LeftOf(obstacle1._point, obstacle2._point, q2);
float invLengthI = 1.0f / RVOMath.AbsSq(obstacle2._point - obstacle1._point);
float radiusSq = radius * radius;
if (q1LeftOfI >= 0.0f && q2LeftOfI >= 0.0f)
{
return QueryVisibilityRecursive(q1, q2, radius, node._left) && ((q1LeftOfI * q1LeftOfI * invLengthI >= radiusSq && q2LeftOfI * q2LeftOfI * invLengthI >= radiusSq) || QueryVisibilityRecursive(q1, q2, radius, node._right));
}
if (q1LeftOfI <= 0.0f && q2LeftOfI <= 0.0f)
{
return QueryVisibilityRecursive(q1, q2, radius, node._right) && ((q1LeftOfI * q1LeftOfI * invLengthI >= radiusSq && q2LeftOfI * q2LeftOfI * invLengthI >= radiusSq) || QueryVisibilityRecursive(q1, q2, radius, node._left));
}
if (q1LeftOfI >= 0.0f && q2LeftOfI <= 0.0f)
{
/* One can see through obstacle from left to right. */
return QueryVisibilityRecursive(q1, q2, radius, node._left) && QueryVisibilityRecursive(q1, q2, radius, node._right);
}
float point1LeftOfQ = RVOMath.LeftOf(q1, q2, obstacle1._point);
float point2LeftOfQ = RVOMath.LeftOf(q1, q2, obstacle2._point);
float invLengthQ = 1.0f / RVOMath.AbsSq(q2 - q1);
return point1LeftOfQ * point2LeftOfQ >= 0.0f && point1LeftOfQ * point1LeftOfQ * invLengthQ > radiusSq && point2LeftOfQ * point2LeftOfQ * invLengthQ > radiusSq && QueryVisibilityRecursive(q1, q2, radius, node._left) && QueryVisibilityRecursive(q1, q2, radius, node._right);
}
}
}

View File

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

View File

@@ -0,0 +1,42 @@
/*
* Line.cs
* RVO2 Library C#
*
* SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Please send all bug reports to <geom@cs.unc.edu>.
*
* The authors may be contacted via:
*
* Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
* Dept. of Computer Science
* 201 S. Columbia St.
* Frederick P. Brooks, Jr. Computer Science Bldg.
* Chapel Hill, N.C. 27599-3175
* United States of America
*
* <http://gamma.cs.unc.edu/RVO2/>
*/
namespace RVO
{
/// <summary>Defines a directed line.</summary>
public struct Line
{
public Vector2 Direction;
public Vector2 Point;
}
}

View File

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

View File

@@ -0,0 +1,47 @@
/*
* Obstacle.cs
* RVO2 Library C#
*
* SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Please send all bug reports to <geom@cs.unc.edu>.
*
* The authors may be contacted via:
*
* Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
* Dept. of Computer Science
* 201 S. Columbia St.
* Frederick P. Brooks, Jr. Computer Science Bldg.
* Chapel Hill, N.C. 27599-3175
* United States of America
*
* <http://gamma.cs.unc.edu/RVO2/>
*/
namespace RVO
{
/// <summary>Defines static obstacles in the simulation.</summary>
internal class Obstacle
{
internal Obstacle _next;
internal Obstacle _previous;
internal Vector2 _direction;
internal Vector2 _point;
internal int _id;
internal bool _convex;
}
}

View File

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

View File

@@ -0,0 +1,155 @@
/*
* RVOMath.cs
* RVO2 Library C#
*
* SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Please send all bug reports to <geom@cs.unc.edu>.
*
* The authors may be contacted via:
*
* Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
* Dept. of Computer Science
* 201 S. Columbia St.
* Frederick P. Brooks, Jr. Computer Science Bldg.
* Chapel Hill, N.C. 27599-3175
* United States of America
*
* <http://gamma.cs.unc.edu/RVO2/>
*/
using System;
namespace RVO
{
/// <summary>Contains functions and constants used in multiple classes.
/// </summary>
public struct RVOMath
{
/// <summary>A sufficiently small positive number.</summary>
internal const float RVO_EPSILON = 0.00001f;
/// <summary>Computes the length of a specified two-dimensional vector.
/// </summary>
///
/// <param name="vector">The two-dimensional vector whose length is to be
/// computed.</param>
/// <returns>The length of the two-dimensional vector.</returns>
public static float Abs(Vector2 vector)
{
return MathF.Sqrt(AbsSq(vector));
}
/// <summary>Computes the squared length of a specified two-dimensional
/// vector.</summary>
///
/// <returns>The squared length of the two-dimensional vector.</returns>
///
/// <param name="vector">The two-dimensional vector whose squared length
/// is to be computed.</param>
public static float AbsSq(Vector2 vector)
{
return vector * vector;
}
/// <summary>Computes the normalization of the specified two-dimensional
/// vector.</summary>
///
/// <returns>The normalization of the two-dimensional vector.</returns>
///
/// <param name="vector">The two-dimensional vector whose normalization
/// is to be computed.</param>
public static Vector2 Normalize(Vector2 vector)
{
float length = Abs(vector);
if (length <= RVO_EPSILON)
{
return new Vector2(0.0f, 0.0f);
}
return vector / length;
}
/// <summary>Computes the determinant of a two-dimensional square matrix
/// with rows consisting of the specified two-dimensional vectors.
/// </summary>
///
/// <returns>The determinant of the two-dimensional square matrix.
/// </returns>
///
/// <param name="vector1">The top row of the two-dimensional square
/// matrix.</param>
/// <param name="vector2">The bottom row of the two-dimensional square
/// matrix.</param>
internal static float Det(Vector2 vector1, Vector2 vector2)
{
return vector1._x * vector2._y - vector1._y * vector2._x;
}
/// <summary>Computes the squared distance from a line segment with the
/// specified endpoints to a specified point.</summary>
///
/// <returns>The squared distance from the line segment to the point.
/// </returns>
///
/// <param name="vector1">The first endpoint of the line segment.</param>
/// <param name="vector2">The second endpoint of the line segment.
/// </param>
/// <param name="vector3">The point to which the squared distance is to
/// be calculated.</param>
internal static float DistSqPointLineSegment(Vector2 vector1, Vector2 vector2, Vector2 vector3)
{
float lengthSq = AbsSq(vector2 - vector1);
if (lengthSq <= RVO_EPSILON * RVO_EPSILON)
{
/* Degenerate segment: both endpoints are the same point. */
return AbsSq(vector3 - vector1);
}
float r = ((vector3 - vector1) * (vector2 - vector1)) / lengthSq;
if (r < 0.0f)
{
return AbsSq(vector3 - vector1);
}
if (r > 1.0f)
{
return AbsSq(vector3 - vector2);
}
return AbsSq(vector3 - (vector1 + r * (vector2 - vector1)));
}
/// <summary>Computes the signed distance from a line connecting the
/// specified points to a specified point.</summary>
///
/// <returns>Positive when the point c lies to the left of the line ab.
/// </returns>
///
/// <param name="a">The first point on the line.</param>
/// <param name="b">The second point on the line.</param>
/// <param name="c">The point to which the signed distance is to be
/// calculated.</param>
internal static float LeftOf(Vector2 a, Vector2 b, Vector2 c)
{
return Det(a - c, b - a);
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,47 @@
/*
* UnityArgumentOutOfRange.cs
* Unity compatibility helpers for RVO2 Library C#.
*
* SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
* SPDX-License-Identifier: Apache-2.0
*/
using System;
namespace RVO
{
internal static class UnityArgumentOutOfRange
{
public static void ThrowIfNegative(int value, string paramName)
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(paramName);
}
}
public static void ThrowIfNegative(float value, string paramName)
{
if (value < 0f)
{
throw new ArgumentOutOfRangeException(paramName);
}
}
public static void ThrowIfNegativeOrZero(float value, string paramName)
{
if (value <= 0f)
{
throw new ArgumentOutOfRangeException(paramName);
}
}
public static void ThrowIfGreaterThanOrEqual(int value, int other, string paramName)
{
if (value >= other)
{
throw new ArgumentOutOfRangeException(paramName);
}
}
}
}

View File

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

View File

@@ -0,0 +1,240 @@
/*
* Vector2.cs
* RVO2 Library C#
*
* SPDX-FileCopyrightText: 2008 University of North Carolina at Chapel Hill
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Please send all bug reports to <geom@cs.unc.edu>.
*
* The authors may be contacted via:
*
* Jur van den Berg, Stephen J. Guy, Jamie Snape, Ming C. Lin, Dinesh Manocha
* Dept. of Computer Science
* 201 S. Columbia St.
* Frederick P. Brooks, Jr. Computer Science Bldg.
* Chapel Hill, N.C. 27599-3175
* United States of America
*
* <http://gamma.cs.unc.edu/RVO2/>
*/
using System;
using System.Globalization;
namespace RVO
{
/// <summary>Defines a two-dimensional vector.</summary>
public readonly struct Vector2 : IEquatable<Vector2>
{
internal readonly float _x;
internal readonly float _y;
/// <summary>Constructs and initializes a two-dimensional vector from the
/// specified xy-coordinates.</summary>
///
/// <param name="x">The x-coordinate of the two-dimensional vector.
/// </param>
/// <param name="y">The y-coordinate of the two-dimensional vector.
/// </param>
public Vector2(float x, float y)
{
_x = x;
_y = y;
}
/// <summary>Returns the string representation of this vector.</summary>
///
/// <returns>The string representation of this vector.</returns>
public override string ToString()
{
return $"({_x.ToString(CultureInfo.InvariantCulture)},{_y.ToString(CultureInfo.InvariantCulture)})";
}
/// <summary>Returns true if this vector equals the specified vector.
/// </summary>
///
/// <returns>True if this vector equals the specified vector.</returns>
///
/// <param name="other">The vector to compare with this vector.</param>
public bool Equals(Vector2 other)
{
return _x == other._x && _y == other._y;
}
/// <summary>Returns true if this vector equals the specified object.
/// </summary>
///
/// <returns>True if this vector equals the specified object.</returns>
///
/// <param name="obj">The object to compare with this vector.</param>
public override bool Equals(object obj)
{
return obj is Vector2 other && Equals(other);
}
/// <summary>Returns the hash code for this vector.</summary>
///
/// <returns>The hash code for this vector.</returns>
public override int GetHashCode()
{
return HashCode.Combine(_x, _y);
}
/// <summary>Returns true if the two vectors are equal.</summary>
///
/// <returns>True if the two vectors are equal.</returns>
///
/// <param name="left">The first vector.</param>
/// <param name="right">The second vector.</param>
public static bool operator ==(Vector2 left, Vector2 right)
{
return left.Equals(right);
}
/// <summary>Returns true if the two vectors are not equal.</summary>
///
/// <returns>True if the two vectors are not equal.</returns>
///
/// <param name="left">The first vector.</param>
/// <param name="right">The second vector.</param>
public static bool operator !=(Vector2 left, Vector2 right)
{
return !left.Equals(right);
}
/// <summary>Gets the x-coordinate of this two-dimensional vector.
/// </summary>
///
/// <value>The x-coordinate of the two-dimensional vector.</value>
public float X => _x;
/// <summary>Gets the y-coordinate of this two-dimensional vector.
/// </summary>
///
/// <value>The y-coordinate of the two-dimensional vector.</value>
public float Y => _y;
/// <summary>Returns the x-coordinate of this two-dimensional vector.
/// </summary>
///
/// <returns>The x-coordinate of the two-dimensional vector.</returns>
[Obsolete("Use the X property instead.", false)]
public float x()
{
return _x;
}
/// <summary>Returns the y-coordinate of this two-dimensional vector.
/// </summary>
///
/// <returns>The y-coordinate of the two-dimensional vector.</returns>
[Obsolete("Use the Y property instead.", false)]
public float y()
{
return _y;
}
/// <summary>Computes the dot product of the two specified
/// two-dimensional vectors.</summary>
///
/// <returns>The dot product of the two specified two-dimensional
/// vectors.</returns>
///
/// <param name="vector1">The first two-dimensional vector.</param>
/// <param name="vector2">The second two-dimensional vector.</param>
public static float operator *(Vector2 vector1, Vector2 vector2)
{
return vector1._x * vector2._x + vector1._y * vector2._y;
}
/// <summary>Computes the scalar multiplication of the specified
/// two-dimensional vector with the specified scalar value.</summary>
///
/// <returns>The scalar multiplication of the specified two-dimensional
/// vector with the specified scalar value.</returns>
///
/// <param name="scalar">The scalar value.</param>
/// <param name="vector">The two-dimensional vector.</param>
public static Vector2 operator *(float scalar, Vector2 vector)
{
return vector * scalar;
}
/// <summary>Computes the scalar multiplication of the specified
/// two-dimensional vector with the specified scalar value.</summary>
///
/// <returns>The scalar multiplication of the specified two-dimensional
/// vector with the specified scalar value.</returns>
///
/// <param name="vector">The two-dimensional vector.</param>
/// <param name="scalar">The scalar value.</param>
public static Vector2 operator *(Vector2 vector, float scalar)
{
return new Vector2(vector._x * scalar, vector._y * scalar);
}
/// <summary>Computes the scalar division of the specified
/// two-dimensional vector with the specified scalar value.</summary>
///
/// <returns>The scalar division of the specified two-dimensional vector
/// with the specified scalar value.</returns>
///
/// <param name="vector">The two-dimensional vector.</param>
/// <param name="scalar">The scalar value.</param>
public static Vector2 operator /(Vector2 vector, float scalar)
{
return new Vector2(vector._x / scalar, vector._y / scalar);
}
/// <summary>Computes the vector sum of the two specified two-dimensional
/// vectors.</summary>
///
/// <returns>The vector sum of the two specified two-dimensional vectors.
/// </returns>
///
/// <param name="vector1">The first two-dimensional vector.</param>
/// <param name="vector2">The second two-dimensional vector.</param>
public static Vector2 operator +(Vector2 vector1, Vector2 vector2)
{
return new Vector2(vector1._x + vector2._x, vector1._y + vector2._y);
}
/// <summary>Computes the vector difference of the two specified
/// two-dimensional vectors</summary>
///
/// <returns>The vector difference of the two specified two-dimensional
/// vectors.</returns>
///
/// <param name="vector1">The first two-dimensional vector.</param>
/// <param name="vector2">The second two-dimensional vector.</param>
public static Vector2 operator -(Vector2 vector1, Vector2 vector2)
{
return new Vector2(vector1._x - vector2._x, vector1._y - vector2._y);
}
/// <summary>Computes the negation of the specified two-dimensional
/// vector.</summary>
///
/// <returns>The negation of the specified two-dimensional vector.
/// </returns>
///
/// <param name="vector">The two-dimensional vector.</param>
public static Vector2 operator -(Vector2 vector)
{
return new Vector2(-vector._x, -vector._y);
}
}
}

View File

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

View File

@@ -0,0 +1,11 @@
# RVO2-CS
本目录导入的是 `snape/RVO2-CS` 的核心 `RVOCS` 源码,用于本地避障 benchmark 的 RVO2 方案。
- 上游仓库https://github.com/snape/RVO2-CS
- 官方 RVO2 页面https://gamma.cs.unc.edu/RVO2/
- 许可证Apache License 2.0,见 `LICENSE``LICENSES/Apache-2.0.txt`
Unity 兼容改动:
- `Simulator.cs` 中的 .NET 新版参数检查 API 已替换为 `UnityArgumentOutOfRange`,以兼容 Unity 2022 的运行时。

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b985d3a31ffcb5a44b2e2442cb2c8fb9
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -16,7 +16,7 @@
- 小鱼数量预设和滑条 - 小鱼数量预设和滑条
- 暂停 / 继续 / 重置 - 暂停 / 继续 / 重置
- 平均 FPS、1% Low FPS、模拟耗时、GC 分配 - 平均 FPS、1% Low FPS、模拟耗时、GC 分配
- 表现对象:运行时创建的 Cube 和 Capsule。 - 表现对象:鱼群为运行时圆锥,圆锥尖头表示朝向;鲨鱼为 Capsule。
- 共享场景: - 共享场景:
- 自由巡游 - 自由巡游
- 对向穿流 - 对向穿流
@@ -56,21 +56,21 @@ A* RVO、RVO2、Unity NavMesh 都通过同一个调试面板选择,用同一
- 如果类型存在adapter 会创建运行时 `RVOSimulator`,给 benchmark 对象添加 `RVOController`,把期望速度送入 A* RVO再把反射得到的避障速度写回 `BenchmarkAgent.ApplyVelocity` - 如果类型存在adapter 会创建运行时 `RVOSimulator`,给 benchmark 对象添加 `RVOController`,把期望速度送入 A* RVO再把反射得到的避障速度写回 `BenchmarkAgent.ApplyVelocity`
- 共享场景、HUD、数量档、指标和相机逻辑不变。 - 共享场景、HUD、数量档、指标和相机逻辑不变。
## RVO2 / RVO2-3D Adapter ## RVO2 Adapter
`Rvo2Adapter` 是反射 adapter。没有 RVO2 源码或包时,工程仍可编译 `Rvo2Adapter` 是反射 adapter。当前已在 `Assets/ThirdParty/RVO2-CS/` 导入 `snape/RVO2-CS` 的核心 `RVOCS` 源码,并保留 Apache-2.0 许可证说明
- 如果缺少 RVO2/RVO2-3D选择 `RVO2` 时会创建同样的 benchmark 对象,并在 Game 视口状态中提示缺包,随后退回基线移动 - 选择 `RVO2`adapter 会优先命中导入后的 `RVO.Simulator`
- 要启用真实框架,需要把 C# 版本 RVO2/RVO2-3D 放到 `Assets/``Packages/` - 如果之后移除 RVO2 源码工程仍可编译Game 视口状态会提示缺包,并退回基线移动
- adapter 会查找类似 `RVO.Simulator``RVO2.Simulator``RVO3D.Simulator``RVO.Simulator3D` 的 Simulator 类型。 - adapter 会查找类似 `RVO.Simulator``RVO2.Simulator``RVO3D.Simulator``RVO.Simulator3D` 的 Simulator 类型。
- 必要方法包括 `addAgent` / `AddAgent``setAgentPrefVelocity` / `SetAgentPrefVelocity``getAgentVelocity` / `GetAgentVelocity``doStep` / `DoStep` - 必要方法包括 `addAgent` / `AddAgent``setAgentPrefVelocity` / `SetAgentPrefVelocity``getAgentVelocity` / `GetAgentVelocity``doStep` / `DoStep`
- 如果存在 `setAgentPosition` / `SetAgentPosition`adapter 每次设置期望速度前会同步 Unity Transform 位置,避免 RVO 内部位置和 benchmark 边界夹取结果漂移。
- 2D 和 2.5D 使用 RVO 的 Vector2 风格类型,把 Unity `x/z` 映射到 RVO `x/y`3D 模式在可用时绑定 Vector3 风格类型。 - 2D 和 2.5D 使用 RVO 的 Vector2 风格类型,把 Unity `x/z` 映射到 RVO `x/y`3D 模式在可用时绑定 Vector3 风格类型。
## Unity NavMesh Adapter ## Unity NavMesh Adapter
`UnityNavMeshAdapter` 使用内置 `UnityEngine.AI.NavMeshAgent` 做对照组。 `UnityNavMeshAdapter` 使用内置 `UnityEngine.AI.NavMeshAgent` 做对照组。
- benchmark 场景里的鱼和鲨鱼是运行时创建的对象,空场景默认不会有可用 NavMesh - adapter 会使用 `NavMeshBuilder.BuildNavMeshData` 为 benchmark 场景运行时生成一张平面 NavMesh不需要手动烘焙场景
- 没有运行时 NavMesh 数据时adapter 会在 Game 视口状态提示,并退回基线移动,不会添加无效 `NavMeshAgent` - 如果运行时 NavMesh 构建失败,Game 视口状态提示,并退回基线移动,不会添加无效 `NavMeshAgent`
- 如果要测试 Unity NavMesh 避障,需要在选择 `NavMesh` 前提供 NavMesh 数据;烘焙场景 NavMesh 即可。 - 因为 NavMesh 是平面数据3D 压测更适合作为 2D / 2.5D 对照;完整 3D 体积避障仍需要专门方案。
- 如果希望运行时生成 benchmark 地面 NavMesh需要启用 `com.unity.ai.navigation` 并补 `NavMeshSurface` 构建流程。