본문 바로가기
개발/Unity

유니티 - Vector3.Cross로 평면 위에서 시계 방향 판단하기 (How to Determine Clockwise on the Plane with Vector3.Cross)

by 피로물든딸기 2022. 11. 11.
반응형

Unity 전체 링크

 

참고

평면과 점 사이의 최단거리 구하기

- 평면과 점 사이의 최단거리를 만드는 평면 위의 점 구하기

- 다각형 좌표의 시계 방향 판단하기 (How to Determine If a Polygon is Clocwise)

 

세 개의 점이 다음과 같다.

 

d1 = new Vector3(0, 0, 0);
d2 = new Vector3(1, 0, 1);
d3 = new Vector3(0, 0, 1);

 

d1 → d2 → d3반시계 방향이고, d1 → d3 → d2시계 방향이다.

 

모든 좌표가 y = 0이기 때문에 ccwBy2D가 + 시계 방향, -반시계 방향이다.

참고로 이 방향은 점 3개로 Plane을 만들 때의 방향과 동일하다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ClockWiseForPlane : MonoBehaviour
{
    float ccwBy2D(Vector3 a, Vector3 b, Vector3 c)
    {
        Vector3 p = b - a;
        Vector3 q = c - b;

        return Vector3.Cross(p, q).y;
    }

    void Start()
    {
        Vector3 d1, d2, d3;

        d1 = new Vector3(0, 0, 0);
        d2 = new Vector3(1, 0, 1);
        d3 = new Vector3(0, 0, 1);

        Plane p1 = new Plane(d1, d2, d3);
        Plane p2 = new Plane(d1, d3, d2);

        Debug.Log("p1 normal : " + p1.normal);
        Debug.Log("p2 normal : " + p2.normal);
        Debug.Log("counterclockwise : " + ccwBy2D(d1, d2, d3));
        Debug.Log("clockwise : " + ccwBy2D(d1, d3, d2));
    }
}

 

하지만 이 경우는 y = 0인 평면에 대해서만 시계 방향이거나 반시계 방향을 정한 경우다.


기준에 따라 달라지는 방향

 

아래의 초록색  파란색  빨간색의 공은 반시계 방향이다.

 

하지만 시점을 변경해서 본 초록색  파란색  빨간색의 공은 시계 방향이다.

 

따라서 평면의 기준에 따라 시계 방향인지 반시계 방향인지 판단하는 함수를 만들 필요가 있다.


구현

 

먼저 점 3개 모두 기준이 되는 평면에 가장 가까운 위치를 찾아야 한다.

평면과 점 사이의 최단거리최단거리를 만드는 평면 위의 점을 구하는 함수는 다음과 같다.

    float distancePlaneToPoint(Vector3 normal, Vector3 planeDot, Vector3 point)
    {
        Plane plane = new Plane(normal, planeDot);
        return plane.GetDistanceToPoint(point);
    }

    Vector3 getPositionOnthePlane(Vector3 normal, Vector3 planeDot, Vector3 position)
    {
        float distance = distancePlaneToPoint(normal, planeDot, position);
        return position - normal * distance;
    }

 

 

위의 함수를 이용하여 해당 평면에 대해 시계 방향을 판단하는 함수를 만들 수 있다.

    float ccwByPlane(Vector3 a, Vector3 b, Vector3 c, Vector3 normal, Vector3 planeDot)
    {
        Vector3 d = getPositionOnthePlane(normal, planeDot, a);
        Vector3 e = getPositionOnthePlane(normal, planeDot, b);
        Vector3 f = getPositionOnthePlane(normal, planeDot, c);

        Vector3 p = e - d;
        Vector3 q = f - e;

        return Vector3.Dot(Vector3.Cross(p, q), normal);
    }

 

ccwBy2DVector3.Cross(p, q).y은 외적과 normal 벡터가 y축인 평면의 내적의 결과다.

따라서 Plane의 normal과 내적을 해서 양수시계 방향, 음수반시계 방향이 된다.

 

위 함수의 정상 동작을 확인하기 위해 Update에서 아래의 코드를 실행한다.

각 점을 평면에 가장 가까운 곳으로 mapping시키고

그 평면에서 시계 방향이면 material빨간색으로, 반시계 방향이면 파란색으로 변경되도록 하였다.

    for(int i = 0; i < planeObject.Length; i++)
    {
        Vector3 normal = planeObject[i].transform.up;
        Vector3 planeDot = planeObject[i].transform.position;

        for(int k = 0; k < 3; k++)
        {
            Vector3 point = clockCheckObject[k].transform.position;
            dotsOnthePlane[i * 3 + k].transform.position = getPositionOnthePlane(normal, planeDot, point);
        }

        Vector3 d1 = dotsOnthePlane[i * 3 + 0].transform.position;
        Vector3 d2 = dotsOnthePlane[i * 3 + 1].transform.position;
        Vector3 d3 = dotsOnthePlane[i * 3 + 2].transform.position;

        if(ccwByPlane(d1, d2, d3, normal, planeDot) > 0)
        {
            for(int k = 0; k < 3; k++)
            {
                Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
                rd.material = red;
            }
        }
        else
        {
            for (int k = 0; k < 3; k++)
            {
                Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
                rd.material = blue;
            }
        }
    }

 

라인 렌더러까지 적절히 추가하면 아래의 결과를 볼 수 있다.

 

최종 코드는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ClockWiseForPlane : MonoBehaviour
{
    public Material red, blue;
    public GameObject[] planeObject;
    public GameObject[] clockCheckObject;
    LineRenderer lr;
    List<GameObject> dotsOnthePlane = new List<GameObject>();

    float ccwBy2D(Vector3 a, Vector3 b, Vector3 c)
    {
        Vector3 p = b - a;
        Vector3 q = c - b;

        return Vector3.Cross(p, q).y;
    }

    float ccwByPlane(Vector3 a, Vector3 b, Vector3 c, Vector3 normal, Vector3 planeDot)
    {
        Vector3 d = getPositionOnthePlane(normal, planeDot, a);
        Vector3 e = getPositionOnthePlane(normal, planeDot, b);
        Vector3 f = getPositionOnthePlane(normal, planeDot, c);

        Vector3 p = e - d;
        Vector3 q = f - e;

        return Vector3.Dot(Vector3.Cross(p, q), normal);
    }

    float distancePlaneToPoint(Vector3 normal, Vector3 planeDot, Vector3 point)
    {
        Plane plane = new Plane(normal, planeDot);
        return plane.GetDistanceToPoint(point);
    }

    Vector3 getPositionOnthePlane(Vector3 normal, Vector3 planeDot, Vector3 position)
    {
        float distance = distancePlaneToPoint(normal, planeDot, position);
        return position - normal * distance;
    }

    void Start()
    {
        lr = this.GetComponent<LineRenderer>();
        lr.startWidth = lr.endWidth = 0.2f;
        lr.material.color = Color.yellow;

        lr.positionCount = 3;

        for(int i = 0; i < planeObject.Length * 3; i++)
        {
            GameObject dot = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            dotsOnthePlane.Add(dot);
        }
    }

    void Update()
    {
        Vector3 a, b, c;

        a = clockCheckObject[0].transform.position;
        b = clockCheckObject[1].transform.position;
        c = clockCheckObject[2].transform.position;

        lr.SetPosition(0, a);
        lr.SetPosition(1, b);
        lr.SetPosition(2, c);

        for(int i = 0; i < planeObject.Length; i++)
        {
            Vector3 normal = planeObject[i].transform.up;
            Vector3 planeDot = planeObject[i].transform.position;

            for(int k = 0; k < 3; k++)
            {
                Vector3 point = clockCheckObject[k].transform.position;
                dotsOnthePlane[i * 3 + k].transform.position = getPositionOnthePlane(normal, planeDot, point);
            }

            Vector3 d1 = dotsOnthePlane[i * 3 + 0].transform.position;
            Vector3 d2 = dotsOnthePlane[i * 3 + 1].transform.position;
            Vector3 d3 = dotsOnthePlane[i * 3 + 2].transform.position;

            if(ccwByPlane(d1, d2, d3, normal, planeDot) > 0)
            {
                for(int k = 0; k < 3; k++)
                {
                    Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
                    rd.material = red;
                }
            }
            else
            {
                for (int k = 0; k < 3; k++)
                {
                    Renderer rd = dotsOnthePlane[i * 3 + k].GetComponent<Renderer>();
                    rd.material = blue;
                }
            }
        }
    }
}

 

위의 실행결과는 아래의 unitypackage에서 확인 가능하다.

ClockWiseOnthePlane.unitypackage
0.05MB

 

Unity Plus:

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Unity Pro:

 

Unity Pro

The complete solutions for professionals to create and operate.

unity.com

 

Unity 프리미엄 학습:

 

Unity Learn

Advance your Unity skills with live sessions and over 750 hours of on-demand learning content designed for creators at every skill level.

unity.com

반응형

댓글