odyssey

#21 odyssey 개발일지 : 지형과 지형을 부드럽게 잇기

san10 2024. 1. 21. 18:46

지형을 만들때 베지어 곡선을 통해 만들었는데,

전에 언급했던 것처럼 곡률이 안맞아서 어색한 지형이 생기기도 한다.

이런 식으로 곡률이 안맞는 문제는 동적으로 지형을 이을 떄도 문제가 되지만,

지형 프리셋을 만들 때도 일일이 곡률을 맞춰서 지형을 만들어야 하니 엄청 불편하다..

감으로 맞추니깐 약간 어색한 부분이 생기기도 하고..

 

 

이런 일을 해결하기 위해, 저번엔 캣멀롬 스플라인을 이용해서 해결하려 했으나..

https://san10.tistory.com/42

 

#16 odyssey 개발일지 : 캣멀롬 스플라인으로 지형 보간

베지어 곡선으로 지형을 만들고 붙이는건 좋은데.. 지형의 곡률이 안맞아서 어색한 장면이 생기기도 한다.. 그래서 보간 곡선으로 지형과 지형을 자연스럽게 이을 수 있는 지형을 만들려고 한다

san10.tistory.com

생각보다 잘 동작하지는 않았다.

 

그때는 막연하게 보간곡선을 사용해야 곡선을 부드럽게 이을 수 있다고 생각했었는데,

베지어 곡선만으로도 부드럽게 이을 수 있다는 것을 알았다!

복합 베지어 곡선 혹은 베지어 스플라인이라고 부르는 듯?

https://en.wikipedia.org/wiki/Composite_B%C3%A9zier_curve

 

Composite Bézier curve - Wikipedia

From Wikipedia, the free encyclopedia Geometric shape Beziergon – The red beziergon passes through the blue vertices, the green points are control points that determine the shape of the connecting Bézier curves In geometric modelling and in computer gra

en.wikipedia.org

3차 베지어를 부드럽게 연결하려면

맞닿는 점 주변의 컨트롤 포인트가 대칭의 형태가 되면 된다.

기울기가 같기 때문에 C1의 연속성이 나온다. (아마도)

 

 

구현

지형과 지형을 부드럽게 잇는 기능을 추가하기 전에,

기존의 지형을 생성하던 BezierMeshEditor에 

콜라이더가 있는지 없는지 감지해서 지형 콜라이더가 있다면

끝점에(또는 첫점에) 붙게 기능을 추가했다.

 

여러개의 지형을 합쳐 하나의 지형 프리셋을 만드는데 

지형의 시작점과 끝점을 손수 잇는게 불편하고 귀찮았기 때문이다.

private void OnSceneGUI()
{
    bezierMeshGenerator = (BezierMeshGenerator)target;

    bezierMeshGenerator.p1 = Handles.PositionHandle(bezierMeshGenerator.p1, Quaternion.identity);
    Handles.Label(bezierMeshGenerator.p1, "p1");
    bezierMeshGenerator.p2 = Handles.PositionHandle(bezierMeshGenerator.p2, Quaternion.identity);
    Handles.Label(bezierMeshGenerator.p2, "p2");
    bezierMeshGenerator.p3 = Handles.PositionHandle(bezierMeshGenerator.p3, Quaternion.identity);
    Handles.Label(bezierMeshGenerator.p3, "p3");
    bezierMeshGenerator.p4 = Handles.PositionHandle(bezierMeshGenerator.p4, Quaternion.identity);
    Handles.Label(bezierMeshGenerator.p4, "p4");

    int count = 30;
    for(float i=0; i<count; i++)
    {
        float beforeIndex = i / count;
        Vector3 beforePoint = bezierMeshGenerator.bezierGenerator.BezierPoint(
            bezierMeshGenerator.p1, bezierMeshGenerator.p2, bezierMeshGenerator.p3, bezierMeshGenerator.p4, beforeIndex);

        float afterIndex =( i+1) / count;
        Vector3 afterPoint =  bezierMeshGenerator.bezierGenerator.BezierPoint(
            bezierMeshGenerator.p1, bezierMeshGenerator.p2, bezierMeshGenerator.p3, bezierMeshGenerator.p4, afterIndex);

        Handles.DrawLine(beforePoint, afterPoint);
    }


    Collider2D terrain1 = Physics2D.OverlapCircle(bezierMeshGenerator.p1, 1f);
    prevTerrain = terrain1;
    if (terrain1 != null)
    {
        PolygonCollider2D collider = terrain1.GetComponent<PolygonCollider2D>();
        bezierMeshGenerator.p1 = collider.transform.TransformPoint(collider.points[collider.points.Length - 2]);
    }

    Collider2D terrain2 = Physics2D.OverlapCircle(bezierMeshGenerator.p4, 1f);
    nextTerrain = terrain2;
    if (terrain2 != null)
    {
        PolygonCollider2D collider = terrain2.GetComponent<PolygonCollider2D>();
        bezierMeshGenerator.p4 = collider.transform.TransformPoint(collider.points[1]);
    }
}

대충 이런 기능이다

 

곡선을 부드럽게 잇기

위에서 말했듯이 곡선이 부드럽게 이어지려면 

맞닿는 점 주변의 컨트롤 포인트의 기울기가 같으면 된다.

 

원래는 지형을 생성할 때 컨트롤 포인트를 따로 담아두지 않았는데..

컨트롤 포인트가 필요해져서 지형을 생성할 때 TerrainData라는 컴포넌트에 담아두도록 변경했다.

 public void CreateBezierMesh()
{
    GameObject ground = new GameObject();
    PolygonCollider2D polygonCollider2D= new PolygonCollider2D();

    Vector2[] bezierPoints = bezierGenerator.GenerateBezierList(p1, p2, p3, p4,pointNum);
    polygonCollider2D= ground.AddComponent<PolygonCollider2D>();
    polygonCollider2D.points = bezierPoints;

    generatorMesh.generateMesh(ground,meshName);
    ground.AddComponent<TerrainData>();
    ground.GetComponent<TerrainData>().Init(p1, p2, p3, p4);
}

 

그리고 BezierMeshEditor에 p2와 p3가

이전 혹은 이후의 지형의 컨트롤 포인트와 대칭이 되도록 계산해서 위치를 조정해주는 함수를 만들었다.

private void SetP2Pos()
{
    TerrainData data = prevTerrain.GetComponent<TerrainData>();
    Vector3 offset = data.cp4 - data.cp3;
    bezierMeshGenerator.p2 = bezierMeshGenerator.p1 + offset;
}

private void SetP3Pos()
{
    TerrainData data = nextTerrain.GetComponent<TerrainData>();
    Vector3 offset = data.cp1 - data.cp2;
    bezierMeshGenerator.p3 = bezierMeshGenerator.p4 + offset;
}

public override void OnInspectorGUI()
{
    base.OnInspectorGUI();
    if (GUILayout.Button("MeshGenerate"))
    {
        bezierMeshGenerator.CreateBezierMesh();
    }

    if (GUILayout.Button("SmoothP2"))
    {
        SetP2Pos();
    }

    if (GUILayout.Button("SmoothP3"))
    {
        SetP3Pos();
    }
}

 

실행 결과

아마 꼭 대칭이 아니더라도 기울기만 맞으면 될 것 같은데

필요하면 같은 기울기로만 움직이는 기능도 만들 것 같다