Есть ли способ решить систему ODE в Unity так, как мне нужно? - PullRequest
0 голосов
/ 14 февраля 2020

Я хочу «вырастить» PreFab в единое целое, используя некоторые параметры, которые являются численным решением системы ODE. У меня есть два сценария: один буквально увеличивает префаб, а другой - решение ODE с использованием численного метода Рунге-Кутты 4-го порядка.

Вот код для решения ODE:

    using System;
    namespace RungeKutta4
    {
        class Program
        {
            static void Main(string[] args)
            {
                //Incrementers to pass into the known solution
                double t = 0.0;
                double T = 1.0;
                double dt = 0.1;

                // Assign the number of elements needed for the arrays
                int n = (int)(((T - t) / dt)) + 1;

                // Initialize the arrays for the time index 's' and estimates 'annualGrowth' at each index 'i'
                double[] annualGrowth = new double[n]; //y
                double[] s = new double[n]; //t

                // RK4 Variables
                double dy1;
                double dy2;
                double dy3;
                double dy4;

                // RK4 Initializations condizioni iniziali
                int i = 0;
                s[i] = 0.0; //t0 = 0
                annualGrowth[i] = 1.0; //y(t0) = y(0) = 1

                // Iterate and implement the Rk4 Algorithm
                while (i < annualGrowth.Length - 1)
                {

                    dy1 = dt * equation(s[i], annualGrowth[i]);
                    dy2 = dt * equation(s[i] + dt / 2, annualGrowth[i] + dy1 / 2);
                    dy3 = dt * equation(s[i] + dt / 2, annualGrowth[i] + dy2 / 2);
                    dy4 = dt * equation(s[i] + dt, annualGrowth[i] + dy3);

                    s[i + 1] = s[i] + dt;
                    annualGrowth[i + 1] = annualGrowth[i] + (dy1 + 2 * dy2 + 2 * dy3 + dy4) / 6;

                    double t_rounded = Math.Round(t + dt, 2);

                    if (t_rounded % 1 == 0)
                    {
                       Console.WriteLine(" y(" + t_rounded + ")" + " " .PadRight(10) + annualGrowth[i + 1]);
                    }

                    i++;
                    t += dt; //t = t + dt


                };//End Rk4                
                Console.ReadLine();
            }

            // Differential Equation
            public static double equation(double t, double annualGrowth)
            {
                double y_prime;
                double k = 0.1;
                double maxHeight = 5.0;
                return y_prime = k * annualGrowth * (1-(annualGrowth/maxHeight));
            }
        }
    }

Затем у меня есть «растущий» сценарий в единстве:

    public class Growth : MonoBehaviour
{
    SetupScene setup;
    TimeManager timeManager;
    float currentLenght;
    float currentWidth;
    Genetic geneticInfo;
    bool isMaxLenght; 
    bool isMaxWeigth;
    GameObject newInternode;
    float annualLengthGrowth = 0.05f; 
    float annualWidthGrowth = 0.01f;
    Vector3 annualGrowth;
    bool hasChild;


    // Start is called before the first frame update
    void Awake()
    {
        annualGrowth = new Vector3(annualWidthGrowth, annualLengthGrowth, annualWidthGrowth);
        setup = GameObject.Find("Setup").GetComponent<SetupScene>();
        timeManager = GameObject.Find("Time Manager").GetComponent<TimeManager>();
        currentLenght = 0f;
        currentWidth = 0f;
        geneticInfo = new Genetic(1);
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("ciao, sono nato " + gameObject.name);
        Debug.Log(timeManager.IsYearElapsed + " " + gameObject.name);
        if (timeManager.IsYearElapsed)
        {

            float scaleFactorXZ = 1f;
            float scaleFactorY = 1f;
            if (gameObject.name != "Seed")
            {
                scaleFactorXZ = geneticInfo.InternodeMaxWidtht;
                scaleFactorY = geneticInfo.InternodeMaxLenght;
            }
            currentLenght = (gameObject.transform.lossyScale.y + annualGrowth.y);
            currentWidth = (gameObject.transform.lossyScale.x + annualGrowth.x);



            if(currentWidth > geneticInfo.InternodeMaxWidtht)
            {
                currentWidth = geneticInfo.InternodeMaxWidtht;
                isMaxWeigth = true;
            }

            if(currentLenght > geneticInfo.InternodeMaxLenght)
            {
                currentLenght = geneticInfo.InternodeMaxLenght;
                isMaxLenght = true;
            }



            gameObject.transform.localScale = new Vector3(currentWidth/scaleFactorXZ, currentLenght/scaleFactorY, currentWidth/scaleFactorXZ);



            if (isMaxLenght)
            {

                if (!hasChild && setup.Manager.InternodeNumber < geneticInfo.MaxInternodeNumber)
                {
                    setup.Manager.InternodeNumber++;
                    Debug.Log("nuovo internodo creato");
                    setup.LastInternodePosition = gameObject.transform.position + new Vector3(0f, geneticInfo.InternodeMaxLenght, 0f);
                    newInternode = Instantiate(setup.Prefab, new Vector3(setup.LastInternodePosition.x, setup.LastInternodePosition.y, setup.LastInternodePosition.z), Quaternion.identity);
                    newInternode.transform.localScale = Vector3.zero;
                    newInternode.transform.parent = setup.LastInternode.transform;
                    setup.LastInternode = newInternode;

                    newInternode.AddComponent<Growth>();
                    hasChild = true;
                }

                if (isMaxWeigth)
                {
                    gameObject.GetComponent<Growth>().enabled = false;
                }

            }
        }
    }
}

Я хочу связать численное решение сценария ODE со значением float annualLengthGrowth = 0.05f; в сценарии единства, который на данный момент жестко закодирован. Проблема заключается в том, что при каждом обновлении (или временном шаге) начальное значение системы ODE должно быть окончательным численным расчетным значением предыдущего шага.

Например, учитывая, что на первом временном шаге начальное значение yearGrowth в Сценарий ODE равен 1.0, окончательное вычисленное значение (например) annualGrowthT1 = 2.35f, я должен использовать это значение в сценарии Unity на первом этапе как annualLengthGrowth. На следующем временном шаге начальное значение для сценария ODE должно быть annualGrowthT1 = 2.35f, затем рассчитанное конечное значение должно быть (например) annualGrowthT2 = 3.56f, и это значение должно использоваться на втором временном шаге в сценарии единицы как новое annualLengthGrowth , И так далее.

Это должно быть понятно благодаря этой картинке:

enter image description here

1 Ответ

0 голосов
/ 16 февраля 2020

Я попытался собрать все вместе, но консоль Unity выдает мне ошибку:

Ошибка CS0161: 'Growth.Runge.runge (float, float, float, float, Growth.Runge.Function)': не все пути кода возвращают значение.

Я думаю, что пропустил return где-то в коде, но я не знаю где. Вот мой новый код:

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

public class Growth : MonoBehaviour
{
    SetupScene setup; //mi serve per prendere il prefab
    TimeManager timeManager;
    float currentLenght;
    float currentWidth;
    Genetic geneticInfo;
    bool isMaxLenght;  //internodo ha raggiunto la lunghezza massima
    bool isMaxWeigth;
    GameObject newInternode;
    float annualLengthGrowth = Runge.runge(0.0f, 1.0f, 1.0f, .1f, new Runge.Function(Equation.equation)); //0.05f; // per ora lo fissiamo da codice, successivamente verra' calcolato
    float annualWidthGrowth = 0.01f;
    Vector3 annualGrowth;
    bool hasChild;


    // Start is called before the first frame update
    void Awake()
    {
        annualGrowth = new Vector3(annualWidthGrowth, annualLengthGrowth, annualWidthGrowth);
        setup = GameObject.Find("Setup").GetComponent<SetupScene>();
        timeManager = GameObject.Find("Time Manager").GetComponent<TimeManager>();
        currentLenght = 0f;
        currentWidth = 0f;
        geneticInfo = new Genetic(1);
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("ciao, sono nato " + gameObject.name);
        Debug.Log(timeManager.IsYearElapsed + " " + gameObject.name);
        if (timeManager.IsYearElapsed)
        {

            //calcolo lunghezza e larghezza corrente
            float scaleFactorXZ = 1f;
            float scaleFactorY = 1f;
            if (gameObject.name != "Seed")
            {
                scaleFactorXZ = geneticInfo.InternodeMaxWidtht;
                scaleFactorY = geneticInfo.InternodeMaxLenght;
            }
            currentLenght = (gameObject.transform.lossyScale.y + annualGrowth.y);
            Debug.Log("Lunghezza corrente internodo = " + currentLenght);
            currentWidth = (gameObject.transform.lossyScale.x + annualGrowth.x);



            if(currentWidth > geneticInfo.InternodeMaxWidtht)
            {
                currentWidth = geneticInfo.InternodeMaxWidtht;
                isMaxWeigth = true;
            }

            if(currentLenght > geneticInfo.InternodeMaxLenght)
            {
                currentLenght = geneticInfo.InternodeMaxLenght;
                isMaxLenght = true;
            }



            gameObject.transform.localScale = new Vector3(currentWidth/scaleFactorXZ, currentLenght/scaleFactorY, currentWidth/scaleFactorXZ);



            if (isMaxLenght)
            {
                //crea nuovo internodo come figlio di quello corrente
                if (!hasChild && setup.Manager.InternodeNumber < geneticInfo.MaxInternodeNumber)
                {
                    setup.Manager.InternodeNumber++;
                    Debug.Log("nuovo internodo creato");
                    setup.LastInternodePosition = gameObject.transform.position + new Vector3(0f, geneticInfo.InternodeMaxLenght, 0f);
                    newInternode = Instantiate(setup.Prefab, new Vector3(setup.LastInternodePosition.x, setup.LastInternodePosition.y, setup.LastInternodePosition.z), Quaternion.identity);
                    newInternode.transform.localScale = Vector3.zero;
                    newInternode.transform.parent = setup.LastInternode.transform;
                    setup.LastInternode = newInternode;
                    //creare nuova classe gestore Gerarchia
                    newInternode.AddComponent<Growth>();
                    hasChild = true;
                }

                if (isMaxWeigth)
                {
                    gameObject.GetComponent<Growth>().enabled = false;
                }

            }
        }
    }

    public class Runge
    {
        //declare a delegate that takes a double and returns
        public delegate float Function(float t, float annualGrowth);

        public static float runge(float a, float b, float value, float step, Function f)
        {
            float t, w, k1, k2, k3, k4;
            t = a;
            w = value;
            for (int i = 0; i < (b - a) / step; i++)
            {
                k1 = step * f(t, w);
                k2 = step * f(t + step / 2f, w + k1 / 2f);
                k3 = step * f(t + step / 2f, w + k2 / 2f);
                k4 = step * f(t + step, w + k3);
                w = w + (k1 + 2f * k2 + 2f * k3 + k4) / 6f;
                t = a + i * step;
                //Console.WriteLine("{0} {1} ", Math.Round(t, 1), w);
            }

        }
    }

    class Equation
    {
        public static float equation(float t, float annualGrowth)
        {
            float y_prime;
            float k = 0.1f; //fattore di crescita, deve essere non hardcodato
            float maxHeight = 5.0f; //altezza max, deve essere non hardcodato
            return y_prime = k * annualGrowth * (1 - (annualGrowth / maxHeight));
        }

    }
}
...