Движение без потерь в шарнирных соединениях - Единство - PullRequest
0 голосов
/ 26 декабря 2018

Я создал простой маятник в Unity - GameObject с компонентами Rigidbody и Hinge Joint.Я установил оба угла лобового сопротивления на 0. С начальным положением в 90 градусов я бы ожидал, что маятник будет качаться взад и вперед от 90 до -90 градусов.Однако это не так - амплитуда затухает очень быстро, но при малых углах маятник выглядит так, как будто он никогда не остановится.

Мой вопрос: как мне настроить шарнирные соединения, чтобы добиться полного контроля надфизика и силы, которые сопротивляются движению?Моя цель - сделать физическое моделирование настолько точным, насколько это возможно, даже за счет производительности.

Я уже пытался сократить временные интервалы для фиксированного шага и увеличить количество решающих итераций - ни одна из них не сработала.

Зачем мне это нужно?Я планирую разработать систему управления несколькими перевернутыми маятниками на тележке.У меня есть математическая модель моего маятника, реализованная в Matlab, и я хотел проверить ее, используя простую модель в Unity (потому что в этом случае я настраиваю все параметры, начальные условия и т. Д., И физический движок рассчитывает все для меня).Если окажется, что физический движок, поддерживающий Unity, недостаточно надежен, какое другое программное обеспечение вы бы мне порекомендовали?

1 Ответ

0 голосов
/ 26 декабря 2018

Насколько я понимаю, из-за того, как работает физика Unity, в этом виде маятникового движения со временем может произойти потеря кинетической энергии, если вы используете только шарнирное соединение.По сути, если вам нужна точная симуляция маятника, вы должны обойти физический движок и реализовать его напрямую.

В игре gamedev есть очень хороший пост * exchange 1001 *, изначально опубликованный MLM о том, как реализовать более точную симуляцию маятника в Unity, которую я вставил ниже.


Я думал, что это будет относительно простой задачей, но я потратил пару дней, пытаясьвыяснить, как, черт возьми, имитировать движение маятника.Я не хотел обманывать и просто менять положение x, y на основе кривых sin (theta) и cos (theta).Вместо этого я хотел иметь дело с двумя силами, которые применяются в реальной жизни, Гравитация и Напряжение .Основной частью, которой мне не хватало, была центростремительная сила.

На странице Маятниковая (математика) википедии есть отличная анимация (ниже, слева), объясняющая движение маятника.Вы можете видеть мой результат (справа), поразительно похожий на эту диаграмму

«Боб» - это качающийся объект, а «стержень» - это источник / корень.

Pendulum Motion: velocity and acceleration

Я также нашел эту статью и диаграмму (ниже) довольно полезными:


Theta равно углумежду канатом и направлением силы тяжести.

Когда боб находится слева или справа, натяжение равно:

m*g*cos(theta)

Причина, по которой сила натяжения большекогда боб приближается к точке равновесия (в середине), это происходит из-за центростремительной силы :

(m*v^2)/ropeLength

Таким образом, формула растягивающего усилия выглядит следующим образом:1049 *

m*g*cos(theta) + (m*v^2)/ropeLength

В маятниковой системе действуют две силы:

  • Гравитация
    • GravityForce = mass * gravity.magnitude
    • GravityDirection = gravity.normalized
  • Напряжение
    • TensionForce = (mass * gravity * Cos(theta)) + ((mass * velocityTangent^2)/ropeLength)
    • TensionDirection = ropeDirection = bob to pivot

Просто приложите гравитациюк вашему объекту, как вы бы длянормальный объект, а затем применить натяжение.Когда вы применяете силы, просто умножьте силу на направление и deltaTime.

Ниже приведен скрипт Pendulum.cs (также как GitHub Gist ).Это работает довольно хорошо, но есть некоторое отклонение от ошибки округления, если вы оставите его на некоторое время (не вернется в точно такое же положение).

Скрипт работает в 3D, но, конечно, маятник качается только в 2Dсамолет.Он также работает с гравитацией в любом направлении.Так, например, если вы инвертируете гравитацию, маятник работает вверх ногами.Edit->Project Settings->Physics->Gravity

Очень важно иметь постоянный относительно небольшой deltaTime при обновлении маятника, чтобы вы не колебались вокруг кривой.Я использую технику, описанную в этой статье, FIX YOUR TIMESTEP!Гленн Фидлер , чтобы сделать это.Проверьте приведенную ниже функцию Update(), чтобы увидеть, как я ее реализовал.

Также как GitHub Gist

using UnityEngine;
using System.Collections;

// Author: Eric Eastwood (ericeastwood.com)
//
// Description:
//      Written for this gd.se question: http://gamedev.stackexchange.com/a/75748/16587
//      Simulates/Emulates pendulum motion in code
//      Works in any 3D direction and with any force/direciton of gravity
//
// Demonstration: https://i.imgur.com/vOQgFMe.gif
//
// Usage: https://i.imgur.com/BM52dbT.png
public class Pendulum : MonoBehaviour {

    public GameObject Pivot;
    public GameObject Bob;


    public float mass = 1f;

    float ropeLength = 2f;

    Vector3 bobStartingPosition;
    bool bobStartingPositionSet = false;

    // You could define these in the `PendulumUpdate()` loop 
    // But we want them in the class scope so we can draw gizmos `OnDrawGizmos()`
    private Vector3 gravityDirection;
    private Vector3 tensionDirection;

    private Vector3 tangentDirection;
    private Vector3 pendulumSideDirection;

    private float tensionForce = 0f;
    private float gravityForce = 0f;


    // Keep track of the current velocity
    Vector3 currentVelocity = new Vector3();

    // We use these to smooth between values in certain framerate situations in the `Update()` loop
    Vector3 currentStatePosition;
    Vector3 previousStatePosition;

    // Use this for initialization
    void Start () {
        // Set the starting position for later use in the context menu reset methods
        this.bobStartingPosition = this.Bob.transform.position;
        this.bobStartingPositionSet = true;

        this.PendulumInit();
    }


    float t = 0f;
    float dt = 0.01f;
    float currentTime = 0f;
    float accumulator = 0f;

    void Update()
    {
        /* */
        // Fixed deltaTime rendering at any speed with smoothing
        // Technique: http://gafferongames.com/game-physics/fix-your-timestep/
        float frameTime = Time.time - currentTime;
        this.currentTime = Time.time;

        this.accumulator += frameTime;

        while (this.accumulator >= this.dt)
        {
            this.previousStatePosition = this.currentStatePosition;
            this.currentStatePosition = this.PendulumUpdate(this.currentStatePosition, this.dt);
            //integrate(state, this.t, this.dt);
            accumulator -= this.dt;
            this.t += this.dt;
        }

        float alpha = this.accumulator/this.dt;

        Vector3 newPosition = this.currentStatePosition*alpha + this.previousStatePosition*(1f-alpha);

        this.Bob.transform.position = newPosition; //this.currentStatePosition;
        /* */

        //this.Bob.transform.position = this.PendulumUpdate(this.Bob.transform.position, Time.deltaTime);
    }


    // Use this to reset forces and go back to the starting position
    [ContextMenu("Reset Pendulum Position")]
    void ResetPendulumPosition()
    {
        if(this.bobStartingPositionSet)
            this.MoveBob(this.bobStartingPosition);
        else
            this.PendulumInit();
    }

    // Use this to reset any built up forces
    [ContextMenu("Reset Pendulum Forces")]
    void ResetPendulumForces()
    {
        this.currentVelocity = Vector3.zero;

        // Set the transition state
        this.currentStatePosition = this.Bob.transform.position;
    }

    void PendulumInit()
    {
        // Get the initial rope length from how far away the bob is now
        this.ropeLength = Vector3.Distance(Pivot.transform.position, Bob.transform.position);
        this.ResetPendulumForces();
    }

    void MoveBob(Vector3 resetBobPosition)
    {
        // Put the bob back in the place we first saw it at in `Start()`
        this.Bob.transform.position = resetBobPosition;

        // Set the transition state
        this.currentStatePosition = resetBobPosition;
    }


    Vector3 PendulumUpdate(Vector3 currentStatePosition, float deltaTime)
    {
        // Add gravity free fall
        this.gravityForce = this.mass * Physics.gravity.magnitude;
        this.gravityDirection = Physics.gravity.normalized;
        this.currentVelocity += this.gravityDirection * this.gravityForce * deltaTime;

        Vector3 pivot_p = this.Pivot.transform.position;
        Vector3 bob_p = this.currentStatePosition;


        Vector3 auxiliaryMovementDelta = this.currentVelocity * deltaTime;
        float distanceAfterGravity = Vector3.Distance(pivot_p, bob_p + auxiliaryMovementDelta);

        // If at the end of the rope
        if(distanceAfterGravity > this.ropeLength || Mathf.Approximately(distanceAfterGravity, this.ropeLength))
        {

            this.tensionDirection = (pivot_p - bob_p).normalized;

            this.pendulumSideDirection = (Quaternion.Euler(0f, 90f, 0f) * this.tensionDirection);
            this.pendulumSideDirection.Scale(new Vector3(1f, 0f, 1f));
            this.pendulumSideDirection.Normalize();

            this.tangentDirection = (-1f * Vector3.Cross(this.tensionDirection, this.pendulumSideDirection)).normalized;


            float inclinationAngle = Vector3.Angle(bob_p-pivot_p, this.gravityDirection);

            this.tensionForce = this.mass * Physics.gravity.magnitude * Mathf.Cos(Mathf.Deg2Rad * inclinationAngle);
            float centripetalForce = ((this.mass * Mathf.Pow(this.currentVelocity.magnitude, 2))/this.ropeLength);
            this.tensionForce += centripetalForce;

            this.currentVelocity += this.tensionDirection * this.tensionForce * deltaTime;
        }

        // Get the movement delta
        Vector3 movementDelta = Vector3.zero;
        movementDelta += this.currentVelocity * deltaTime;


        //return currentStatePosition + movementDelta;

        float distance = Vector3.Distance(pivot_p, currentStatePosition + movementDelta);
        return this.GetPointOnLine(pivot_p, currentStatePosition + movementDelta, distance <= this.ropeLength ? distance : this.ropeLength);
    }

    Vector3 GetPointOnLine(Vector3 start, Vector3 end, float distanceFromStart)
    {
        return start + (distanceFromStart * Vector3.Normalize(end - start));
    }

    void OnDrawGizmos()
    {
        // purple
        Gizmos.color = new Color(.5f, 0f, .5f);
        Gizmos.DrawWireSphere(this.Pivot.transform.position, this.ropeLength);

        Gizmos.DrawWireCube(this.bobStartingPosition, new Vector3(.5f, .5f, .5f));


        // Blue: Auxilary
        Gizmos.color = new Color(.3f, .3f, 1f); // blue
        Vector3 auxVel = .3f * this.currentVelocity;
        Gizmos.DrawRay(this.Bob.transform.position, auxVel);
        Gizmos.DrawSphere(this.Bob.transform.position + auxVel, .2f);

        // Yellow: Gravity
        Gizmos.color = new Color(1f, 1f, .2f);
        Vector3 gravity = .3f * this.gravityForce*this.gravityDirection;
        Gizmos.DrawRay(this.Bob.transform.position, gravity);
        Gizmos.DrawSphere(this.Bob.transform.position + gravity, .2f);

        // Orange: Tension
        Gizmos.color = new Color(1f, .5f, .2f); // Orange
        Vector3 tension = .3f * this.tensionForce*this.tensionDirection;
        Gizmos.DrawRay(this.Bob.transform.position, tension);
        Gizmos.DrawSphere(this.Bob.transform.position + tension, .2f);

        // Red: Resultant
        Gizmos.color = new Color(1f, .3f, .3f); // red
        Vector3 resultant = gravity + tension;
        Gizmos.DrawRay(this.Bob.transform.position, resultant);
        Gizmos.DrawSphere(this.Bob.transform.position + resultant, .2f);


        /* * /
        // Green: Pendulum side direction
        Gizmos.color = new Color(.3f, 1f, .3f);
        Gizmos.DrawRay(this.Bob.transform.position, 3f*this.pendulumSideDirection);
        Gizmos.DrawSphere(this.Bob.transform.position + 3f*this.pendulumSideDirection, .2f);
        /* */

        /* * /
        // Cyan: tangent direction
        Gizmos.color = new Color(.2f, 1f, 1f); // cyan
        Gizmos.DrawRay(this.Bob.transform.position, 3f*this.tangentDirection);
        Gizmos.DrawSphere(this.Bob.transform.position + 3f*this.tangentDirection, .2f);
        /* */
    }
}

Подробнеегламурные снимки:

...