[英]How to compare between two lines?
我有一個代碼可以讓我畫線並限制可以畫的線數。
我的問題是我想創建一條線(例如使用線渲染器),然后允許用戶嘗試繪制一條相似(不一定完全相同)的線,並且代碼需要根據設置知道該線是否相似夠不夠,但我想不通。
我將不勝感激任何提示。
public class DrawLine : MonoBehaviour
{
public GameObject linePrefab;
public GameObject currentLine;
public LineRenderer lineRenderer;
public EdgeCollider2D edgeCollider;
public List<Vector2> fingerPositions;
public Button[] answers;
public bool isCurrButtonActive;
int mouseButtonState = 0;
void Update()
{
Debug.Log(rfgrhe);
if (isCurrButtonActive)
{
if (Input.GetMouseButtonDown(0))
{
if (mouseButtonState == 0)
{
CreateLine();
}
}
if (Input.GetMouseButtonUp(0))
{
mouseButtonState++;
}
if (Input.GetMouseButton(0))
{
if (mouseButtonState == 1)
{
Debug.Log(Input.mousePosition.ToString());
if (Input.mousePosition.x < 100 || Input.mousePosition.y > 420 || Input.mousePosition.x > 660 || Input.mousePosition.y < 7)
{
return;
}
Vector2 tempFingerPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Vector2.Distance(tempFingerPos, fingerPositions[fingerPositions.Count - 1]) > .1f)
{
UpdateLine(tempFingerPos);
}
}
}
}
}
void CreateLine()
{
mouseButtonState++;
currentLine = Instantiate(linePrefab, Vector3.zero, Quaternion.identity);
lineRenderer = currentLine.GetComponent<LineRenderer>();
edgeCollider = currentLine.GetComponent<EdgeCollider2D>();
fingerPositions.Clear();
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
lineRenderer.SetPosition(0, fingerPositions[0]);
lineRenderer.SetPosition(1, fingerPositions[1]);
edgeCollider.points = fingerPositions.ToArray();
}
void UpdateLine(Vector2 newFingerPos)
{
fingerPositions.Add(newFingerPos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(lineRenderer.positionCount - 1, newFingerPos);
edgeCollider.points = fingerPositions.ToArray();
}
public void ActivateCurrentButton()
{
// Debug.Log(isCurrButtonActive);
isCurrButtonActive = true;
for (int i = 0; i < answers.Length; i++)
{
if (answers[i].CompareTag("onePoint"))
{
answers[i].GetComponent<MapLvl>().isCurrButtonActive = false;
}
else if (answers[i].CompareTag("TwoPoints"))
{
answers[i].GetComponent<DrawLine>().isCurrButtonActive = false;
}
}
}
}
例如,在這種情況下,藍線是正確的,綠線和紅線是用戶回答的兩個選項。 我想要的是該程序只會將綠線確認為正確答案。
編輯:因為現在我們想要的更清楚了,這里有一種實現它的方法:
下面的函數float DifferenceBetweenLines(Vector3[], Vector3[])
為您提供了“兩條線之間的距離”的度量。
它沿着線行走以匹配最大步長,並為每個點計算與繪制線上最近點的距離。 它將這些距離的平方相加並將它們除以匹配的線的長度(不要讓我用數學嚴謹來解釋這一點)。
返回值越小,第一行與第二行越接近——閾值由您決定。
float DifferenceBetweenLines(Vector3[] drawn, Vector3[] toMatch) {
float sqrDistAcc = 0f;
float length = 0f;
Vector3 prevPoint = toMatch[0];
foreach (var toMatchPoint in WalkAlongLine(toMatch)) {
sqrDistAcc += SqrDistanceToLine(drawn, toMatchPoint);
length += Vector3.Distance(toMatchPoint, prevPoint);
prevPoint = toMatchPoint;
}
return sqrDistAcc / length;
}
/// <summary>
/// Move a point from the beginning of the line to its end using a maximum step, yielding the point at each step.
/// </summary>
IEnumerable<Vector3> WalkAlongLine(IEnumerable<Vector3> line, float maxStep = .1f) {
using (var lineEnum = line.GetEnumerator()) {
if (!lineEnum.MoveNext())
yield break;
var pos = lineEnum.Current;
while (lineEnum.MoveNext()) {
Debug.Log(lineEnum.Current);
var target = lineEnum.Current;
while (pos != target) {
yield return pos = Vector3.MoveTowards(pos, target, maxStep);
}
}
}
}
static float SqrDistanceToLine(Vector3[] line, Vector3 point) {
return ListSegments(line)
.Select(seg => SqrDistanceToSegment(seg.a, seg.b, point))
.Min();
}
static float SqrDistanceToSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point) {
var projected = ProjectPointOnLineSegment(linePoint1, linePoint1, point);
return (projected - point).sqrMagnitude;
}
/// <summary>
/// Outputs each position of the line (but the last) and the consecutive one wrapped in a Segment.
/// Example: a, b, c, d --> (a, b), (b, c), (c, d)
/// </summary>
static IEnumerable<Segment> ListSegments(IEnumerable<Vector3> line) {
using (var pt1 = line.GetEnumerator())
using (var pt2 = line.GetEnumerator()) {
pt2.MoveNext();
while (pt2.MoveNext()) {
pt1.MoveNext();
yield return new Segment { a = pt1.Current, b = pt2.Current };
}
}
}
struct Segment {
public Vector3 a;
public Vector3 b;
}
//This function finds out on which side of a line segment the point is located.
//The point is assumed to be on a line created by linePoint1 and linePoint2. If the point is not on
//the line segment, project it on the line using ProjectPointOnLine() first.
//Returns 0 if point is on the line segment.
//Returns 1 if point is outside of the line segment and located on the side of linePoint1.
//Returns 2 if point is outside of the line segment and located on the side of linePoint2.
static int PointOnWhichSideOfLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 lineVec = linePoint2 - linePoint1;
Vector3 pointVec = point - linePoint1;
if (Vector3.Dot(pointVec, lineVec) > 0) {
return pointVec.magnitude <= lineVec.magnitude ? 0 : 2;
} else {
return 1;
}
}
//This function returns a point which is a projection from a point to a line.
//The line is regarded infinite. If the line is finite, use ProjectPointOnLineSegment() instead.
static Vector3 ProjectPointOnLine(Vector3 linePoint, Vector3 lineVec, Vector3 point){
//get vector from point on line to point in space
Vector3 linePointToPoint = point - linePoint;
float t = Vector3.Dot(linePointToPoint, lineVec);
return linePoint + lineVec * t;
}
//This function returns a point which is a projection from a point to a line segment.
//If the projected point lies outside of the line segment, the projected point will
//be clamped to the appropriate line edge.
//If the line is infinite instead of a segment, use ProjectPointOnLine() instead.
static Vector3 ProjectPointOnLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 vector = linePoint2 - linePoint1;
Vector3 projectedPoint = ProjectPointOnLine(linePoint1, vector.normalized, point);
switch (PointOnWhichSideOfLineSegment(linePoint1, linePoint2, projectedPoint)) {
case 0:
return projectedPoint;
case 1:
return linePoint1;
case 2:
return linePoint2;
default:
//output is invalid
return Vector3.zero;
}
}
最后的數學函數來自3d Math Functions - Unify Community Wiki
以下是如何使用它來比較 LineRenderer 與另一個:
Array.Resize(ref lineBuffer1, lineRenderer1.positionCount);
Array.Resize(ref lineBuffer2, lineRenderer2.positionCount);
lineRenderer1.GetPositions(lineBuffer1);
lineRenderer2.GetPositions(lineBuffer2);
float diff = DifferenceBetweenLines(lineBuffer1, lineBuffer2);
const float threshold = 5f;
Debug.Log(diff < threshold ? "Pretty close!" : "Not that close...");
需要考慮的幾點:
SqrDistanceToLine
的性能肯定可以改進DifferenceBetweenLines
來解決這個問題,交換參數,並取兩者中最大的結果。正如@Jonathan 指出的那樣,您需要更精確地了解“足夠相似” :
正如您可能猜到的,這些標准越少,就越難。 因為您的相似性概念將從您最初獲得的原始位置變得越來越抽象。
例如,如果用戶需要繪制一個十字,恰好有兩個筆划,或多或少覆蓋一個定義的區域,那么任務就變得很簡單:
您可以測量該區域的角與每個筆划的第一個和最后一個點之間的距離,並檢查線條是否筆直。
如果您想檢查用戶是否在任何方向繪制了一個完美的心形,這顯然更加棘手......
為此,您可能不得不求助於專門的圖書館。
另一件要考慮的事情是,用戶是否真的需要將一條線與另一條線相似,還是應該足夠接近以便與其他可能的線區分開來? 考慮這個例子:
用戶需要繪制一個十字 (X) 或一個圓 (O):
在這種情況下,一個更復雜的系統可能會矯枉過正。
假設簡單的需求(因為假設相反,我將無能為力),這里有一些元素:
用戶必須在可見線的頂部繪制:這是最簡單的場景。
對於他的直線上的每個點,找出與參考線上最近點的距離。 對這些距離的平方求和——出於某種原因,它比對距離本身求和更有效,而且直接計算平方距離也更便宜。
非常特定於您的用例,因為您使用的是 Unity 的 LineRenderer,所以值得知道它包含了一個Simplify(float)
方法,該方法降低了曲線的分辨率,使其更易於處理,並且如果線條為匹配由一些直線段(不是復雜的曲線)組成。
有時您需要檢查線的不同子線段之間的角度,而不是它們的(相對)長度。 無論比例如何,它都會測量方向的變化,這可以更直觀。
檢測等邊三角形的簡單示例:
對於任意線,您可以運行該線以通過與用戶繪制的線相同的“過濾器”進行匹配,並比較這些值。 您可以決定哪些屬性最重要(角度/距離/比例...),以及閾值是多少。
我個人會沿着用戶線取點,然后找出線上的角度,如果平均角度在特定范圍內,那么這是可以接受的。 如果您繪制角度的點足夠接近,那么您應該非常准確地了解用戶是否靠近同一條線。
此外,如果該線需要在特定區域內,那么您只需檢查並確保該線位於“控制”線的指定距離內。 一旦你有了積分,這些的數學就應該很簡單了。 我確信還有很多其他方法可以實現這一點,但我個人會這樣做。 希望這可以幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.