VR 콘텐츠 제작

디스크 골프 [#4] - 프리스비 운동 R&D

송현호 2023. 11. 15. 14:42

팀원과 회의를 통해 다른 시뮬레이터를 사용해서 프리스비 R&D를 진행하기로 헀다.

 

using OVR;
using System;
using UnityEditor;
using UnityEngine;

public class DiscController : MonoBehaviour
{
    private float alpha = 0;
    private float RHO = 1.23f; // 공기 밀도
    private float roll;
    private float spin;
    private float pitch;

    [SerializeField] bool isLift;
    [SerializeField] bool isDrag;
    [SerializeField] bool isGravity;
    [SerializeField] bool isTorque;

    [SerializeField] private float rotationalSpeed = 50;
    [SerializeField] private float speed = 100;

    [SerializeField] private Rigidbody rigidBody;

    [SerializeField] private float CLO = 0.1f; // 양력
    [SerializeField] private float CLA = 1.4f; // 양력
    [SerializeField] private float CDO = 0.08f;
    [SerializeField] private float CDA = 2.72f;
    [SerializeField] private float CRR = 0.014f;
    [SerializeField] private float CRP = -0.0055f;
    [SerializeField] private float CNR = -0.0000071f;
    [SerializeField] private float CM0 = -0.08f;
    [SerializeField] private float CMA = 0.43f;
    [SerializeField] private float CMQ = -0.005f;

    [SerializeField] private float ALPHA0 = -4;
    [SerializeField] private float AREA = 0.0568f; // 표준 프리스비의 면적

    [SerializeField] private float diameter = 0.21f; // in m
    [SerializeField] private float m = 0.176f; // 무게?

    [SerializeField] private float GLIDE = 3f;
    [SerializeField] private float SPEED = 3f;
    [SerializeField] private float TURN = 0;
    [SerializeField] private float FADE = 1;

    private void Start()
    {
        rigidBody = GetComponent<Rigidbody>();
        alpha = Vector3.Angle(rigidBody.velocity, transform.forward);
        Debug.Log(alpha);

        rigidBody.maxAngularVelocity = 2000;
        rigidBody.drag = 0;
        rigidBody.mass = m;
        rigidBody.useGravity = false;
        rigidBody.isKinematic = false;

        rigidBody.AddTorque(transform.up * rotationalSpeed * 0.01f, ForceMode.Impulse);
        rigidBody.AddForce((transform.right+transform.up) * speed / 3.6f, ForceMode.Impulse);
    }
    private void FixedUpdate()
    {
        if (isLift)
        {
            Lift();
        }
        if (isDrag)
        {
            Drag();
        }
        if (isGravity)
        {
            Gravity();
        }
        if (isTorque)
        {
            Torque();
        }
        Debug.LogFormat("alpha : {0}", alpha);



    }
    private void Lift()
    {
        float cl = CLO + CLA * alpha * Mathf.PI / 180;
        float lift = (RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * cl / 2 / m) * Time.deltaTime * 4 + GLIDE / 9;
        rigidBody.AddForce(transform.up.normalized * (float)lift, ForceMode.Acceleration);
    }
    private void Drag()
    {
        float cd = CDO + CDA * Mathf.Pow((float)(alpha - (ALPHA0) * Mathf.PI / 180), 2);
        float drag = (RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * cd) / 2 * (15 / SPEED) / 1.5f;
        rigidBody.AddForce(-rigidBody.velocity.normalized * (float)drag, ForceMode.Acceleration);
    }
    private void Gravity()
    {
        rigidBody.AddForce(0, -9.82f, 0, ForceMode.Acceleration);
    }
    private void Torque()
    {
        roll = (CRR * rigidBody.angularVelocity.y + CRP * rigidBody.angularVelocity.x) * 1/2 * RHO * (float)Math.Pow(rigidBody.velocity.magnitude, 2) * AREA * diameter * 0.01f * 6 - TURN / 2;
        roll -= FADE * 3;
        spin = -(CNR * rigidBody.angularVelocity.y) * 1 / 2 * RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * diameter * 0.01f;
        pitch = (CM0 + CMA * (Mathf.PI / 180 * (alpha)) + CMQ * rigidBody.angularVelocity.z) * 1/2 * RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * diameter * 0.01f * 6;
        
        rigidBody.AddTorque(Vector3.Cross(transform.up, rigidBody.velocity).normalized*roll, ForceMode.Acceleration);
        rigidBody.AddTorque(transform.up*spin, ForceMode.Acceleration);
        rigidBody.AddTorque(rigidBody.velocity.normalized*pitch, ForceMode.Acceleration);
    }
}

 

기본적인 공식자체는 전에 쓰던 시뮬레이터와 동일하며 전에 쓰던 코드는 배열로 알파값을 만들었는데 이 코드는 알파값을 리지드바디의 속도와 트랜스폼의 포워드를 사용해서 알파값을 정의했기 때문에 더 직관적이다.

 

일단은 vr기기를 활용해서 프리스비에 Oculus SDK의 throw기능을 추가한다음 실제로 잘 던져지는지 확인해보았다.

 

잘 던져지지만 아무래도 실제 프리스비를 던질때 진행방향이 뱀처럼 휘는듯한 느낌은 나지 않았다. 그래서 어떻게 수정해야 할지 계속 찾아봤는데

 

https://ko.wikipedia.org/wiki/%EB%A7%88%EA%B7%B8%EB%88%84%EC%8A%A4_%ED%9A%A8%EA%B3%BC

 

마그누스 효과 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 마그누스 효과를 공에 적용한 그림. V는 유체의 속도, F는 작용하는 힘을 나타낸다. 마그누스 효과( - 效果, 영어: Magnus effect)는 유체(액체 또는 기체) 속에 잠긴

ko.wikipedia.org

프리스비에 마그누스 효과를 추가해서 회전값이 커질수록 원반이 휘는 효과를 만들어보기로 하였다.

 

마그누스 효과의 공식은

 

F=1/2ρωrVAl

 

인데 변수가 너무 많아서

 

Fm = ½ * CL * ρ * A * v2 (ω x v)

공식을 활용을 활용해서 마그누스 효과를 만들어보기로 했다.

 

CL은 리프트 계수를 의미하고

 

p는 공기밀도

 

A는 단면적

 

v의 제곱부분이 좀 이해가 안되었는데 단순제곱인가 싶다가도 소괄호안에 회전각속도와 속도를 곱했다고 써져 있어서 더 헷갈렸다.

 

그래서 다른 유니티 프로젝트의 마그누스 효과를 사용하는 프로젝트를 참고 했다.

 

https://forum.unity.com/threads/magnus-effect-for-tennis-ball-physics-simulation.1319592/

 

Question - Magnus Effect for tennis ball physics simulation

I saw this post: https://forum.unity.com/threads/tennis-ball-simulation.399725/ But I can't find the Youtube video referenced: youtube]ROYdWCs4ANs...

forum.unity.com

 

위 코드의 경우는 테니스 볼에 마그누스 효과를 적용한 경우인데 

나는 원반을 사용하므로 반지름으로 단면적을 구할 필요는 없었고 

코드에 사전에 정의되어 있는 단면적의 값(A)를 넣어 주었다.

 

그리고 헷갈렸던 v 제곱부분은 벡터와 벡터를 곱한 것이기 때문에 벡터의 외적 Vector3.Cross가 사용되었다.

 

일관성 있는 코드를 위해 시리얼라이즈 필드로 마그누스를 체크할 수 있는 칸을 만들어주고 

FixedUpdate에서 마그누스가 체크되있을 경우 원반이 마그누스 효과를 받도록 만들어 주었다.

 

 

 

using System;
using UnityEditor;
using UnityEngine;

public class DiscController : MonoBehaviour
{
    private float alpha = 0;
    private float RHO = 1.23f; // 공기 밀도
    private float roll;
    private float spin;
    private float pitch;

    [SerializeField] bool isLift;
    [SerializeField] bool isDrag;
    [SerializeField] bool isGravity;
    [SerializeField] bool isTorque;
    [SerializeField] bool isMagnus;

    [SerializeField] private float rotationalSpeed = 50;
    [SerializeField] private float speed = 100;

    [SerializeField] private Rigidbody rigidBody;

    [SerializeField] private float CLO = 0.1f; // 양력
    [SerializeField] private float CLA = 1.4f; // 양력
    [SerializeField] private float CDO = 0.08f;
    [SerializeField] private float CDA = 2.72f;
    [SerializeField] private float CRR = 0.014f;
    [SerializeField] private float CRP = -0.0055f;
    [SerializeField] private float CNR = -0.0000071f;
    [SerializeField] private float CM0 = -0.08f;
    [SerializeField] private float CMA = 0.43f;
    [SerializeField] private float CMQ = -0.005f;

    [SerializeField] private float ALPHA0 = -4;
    [SerializeField] private float AREA = 0.0568f; // 표준 프리스비의 면적

    [SerializeField] private float diameter = 0.21f; // in m
    [SerializeField] private float m = 0.176f; // 무게?

    [SerializeField] private float GLIDE = 3f;
    [SerializeField] private float SPEED = 3f;
    [SerializeField] private float TURN = 0;
    [SerializeField] private float FADE = 1;

    private void Start()
    {
        rigidBody = GetComponent<Rigidbody>();
        

        rigidBody.maxAngularVelocity = 2000;
        rigidBody.drag = 0;
        rigidBody.mass = m;
        rigidBody.useGravity = false;
        rigidBody.isKinematic = false;

        rigidBody.AddTorque(transform.up * rotationalSpeed * 0.01f, ForceMode.Impulse);
        rigidBody.AddForce(transform.forward * speed / 3.6f, ForceMode.Impulse);
    }
    private void FixedUpdate()
    {
        if (isLift)
        {
            Lift();
        }
        if (isDrag)
        {
            Drag();
        }
        if (isGravity)
        {
            Gravity();
        }
        if (isTorque)
        {
            Torque();
        }
        if (isMagnus)
        {
            Magnus();
        }


    }
    private void Lift()
    {
        float cl = CLO + CLA * alpha * Mathf.PI / 180;
        float lift = (RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * cl / 2 / m) * Time.deltaTime * 4 + GLIDE / 9;
        rigidBody.AddForce(transform.up.normalized * (float)lift, ForceMode.Acceleration);
    }
    private void Drag()
    {
        float cd = CDO + CDA * Mathf.Pow((float)(alpha - (ALPHA0) * Mathf.PI / 180), 2);
        float drag = (RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * cd) / 2 * (15 / SPEED) / 1.5f;
        rigidBody.AddForce(-rigidBody.velocity.normalized * (float)drag, ForceMode.Acceleration);
    }
    private void Gravity()
    {
        rigidBody.AddForce(0, -9.82f, 0, ForceMode.Acceleration);
    }
    private void Torque()
    {
        roll = (CRR * rigidBody.angularVelocity.y + CRP * rigidBody.angularVelocity.x) * 1 / 2 * RHO * (float)Math.Pow(rigidBody.velocity.magnitude, 2) * AREA * diameter * 0.01f * 6 - TURN / 2;
        roll -= FADE * 3;
        spin = -(CNR * rigidBody.angularVelocity.y) * 1 / 2 * RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * diameter * 0.01f;
        pitch = (CM0 + CMA * (Mathf.PI / 180 * (alpha)) + CMQ * rigidBody.angularVelocity.z) * 1 / 2 * RHO * Mathf.Pow(rigidBody.velocity.magnitude, 2) * AREA * diameter * 0.01f * 6;

        rigidBody.AddTorque(Vector3.Cross(transform.up, rigidBody.velocity).normalized * roll, ForceMode.Acceleration);
        rigidBody.AddTorque(transform.up * spin, ForceMode.Acceleration);
        rigidBody.AddTorque(rigidBody.velocity.normalized * pitch, ForceMode.Acceleration);
    }

    private void Magnus()
    {
        var direction = Vector3.Cross(rigidBody.angularVelocity, rigidBody.velocity);
        float cl = CLO + CLA * alpha * Mathf.PI / 180;
        Vector3 magnus = cl * 1 / 2 * RHO * direction * AREA;
        Debug.Log(magnus);
        this.rigidBody.AddForce(magnus, ForceMode.Acceleration);
    }
}

 

유니티 환경에서 마그누스 효과 온오프한 상태로 원반의 진행방향이 어떻게 변하는지 실험해보았다.

 

 

영향을 안 받는건 아닌거 같은데 수치가 상당히 미묘하다. 아무래도 마그누스 공식의 리프트 계수가 알파값을 0으로 받아서 생기는 문제인 것 같았다.

알파값을 임의로 0.5와 1로 지정하고 코드를 돌려 봤다.

 

0.5 일때
1일때

 

그런데 알파값이 커질수록 Lift뿐만 아니라 Drag의 값도 같이 상승해서 원반이 뒤로 가는 웃지 못할 상황이 발생 했다. 

아마 리프트와 드래그를 설정하는 함수에 어떤 공식의 오류가 있을 것 같은데 다시 한번 천천히 뜯어보면서 찾아봐야 할 것 같다.

 

이번엔 임의로 마그누스값을 크게 해서 작동시켜 보았다.

 

 

...?

 

아무래도 값을 잘 조정할 필요가 있을 것 같다.