簡體   English   中英

如何比較兩條線?

[英]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來解決這個問題,交換參數,並取兩者中最大的結果。
  • 我們可以使用 Vector2 而不是 Vector3


原答案:

相似的?

正如@Jonathan 指出的那樣,您需要更精確地了解“足夠相似”

  • 大小的相似性重要嗎?
  • 方向重要嗎?
  • 比例的相似性(或僅線的“方向變化”)重要嗎?
  • ...

正如您可能猜到的,這些標准越少,就越難 因為您的相似性概念將從您最初獲得的原始位置變得越來越抽象。

  • 例如,如果用戶需要繪制一個十字,恰好有兩個筆划,或多或少覆蓋一個定義的區域,那么任務就變得很簡單:

    您可以測量該區域的角與每個筆划的第一個和最后一個點之間的距離,並檢查線條是否筆直。

  • 如果您想檢查用戶是否在任何方向繪制了一個完美的心形,這顯然更加棘手......

    為此,您可能不得不求助於專門的圖書館。

另一件要考慮的事情是,用戶是否真的需要將一條線與另一條線相似,還是應該足夠接近以便與其他可能的線區分開來 考慮這個例子:

用戶需要繪制一個十字 (X) 或一個圓 (O):

  • 如果只有一個筆划回到起點附近,假設一個圓圈。
  • 如果筆畫的一般方向是正交的,則假設為十字。

在這種情況下,一個更復雜的系統可能會矯枉過正。

一些“原始指針”

假設簡單的需求(因為假設相反,我將無能為力),這里有一些元素:

完全符合

用戶必須在可見線的頂部繪制:這是最簡單的場景。

對於他的直線上的每個點,找出與參考線上最近點的距離。 對這些距離的平方求和——出於某種原因,它比對距離本身求和更有效,而且直接計算平方距離也更便宜。

LineRenderer.Simplify

非常特定於您的用例,因為您使用的是 Unity 的 LineRenderer,所以值得知道它包含了一個Simplify(float)方法,該方法降低了曲線的分辨率,使其更易於處理,並且如果線條為匹配由一些直線段(不是復雜的曲線)組成。

使用角度

有時您需要檢查線的不同子線段之間的角度,而不是它們的(相對)長度。 無論比例如何,它都會測量方向的變化,這可以更直觀。

例子

檢測等邊三角形的簡單示例:

  • LineRenderer.Simplify
  • 如果末端足夠接近,則關閉形狀
  • 檢查每個角度都是 ~60 度:

測試三角形

對於任意線,您可以運行該線以通過與用戶繪制的線相同的“過濾器”進行匹配,並比較這些值。 您可以決定哪些屬性最重要(角度/距離/比例...),以及閾值是多少。

我個人會沿着用戶線取點,然后找出線上的角度,如果平均角度在特定范圍內,那么這是可以接受的。 如果您繪制角度的點足夠接近,那么您應該非常准確地了解用戶是否靠近同一條線。

此外,如果該線需要在特定區域內,那么您只需檢查並確保該線位於“控制”線的指定距離內。 一旦你有了積分,這些的數學就應該很簡單了。 我確信還有很多其他方法可以實現這一點,但我個人會這樣做。 希望這可以幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM