Obsidean_VM/07 - Desarrolo de Proyectos.../Proyectos Visual Studio/ROI Editor/CtrEditor - Descripción de ...

13 KiB

Aplicacion: CtrEditor

Detalles técnicos:

  • Para Windows: en C# con WPF y .NET 7.0
  • usar un patrón MVVM.

Concepto de la aplicación:


Simular procesos mediante una grafica de base sobre el "ImagenEnTrabajoCanvas" canvas principal. Esto se realizara usando un clock de simulacion llamando a cada UserControl definido para la imagen. Todas las UserControl poseen una funcion de simulacion que recive el estado actual de los datos de simulacion y puede dibujarse a si misma. Las estructura de datos requeridas son:

  • Estado salvado en la ubicacion de la aplicacion en JSON:
    • Estado de la aplicación:
      • Ultimo directorio utilizado
  • Estado salvadado en el directorio de trabajo:
    • Estado de simulación:
      • ObjetosSimulados: UserControl de simulacion:
        • ObjetoSimuladoBotella
        • ObjetoSimuladoPack
      • ObjetosFuncion: UserControl de funcion:
        • Que realizan una accion:
          • funcionFotocelula
          • funcionPiston
          • funcionTransporte
          • funcionFreno
          • funcionDivider
          • funcionCombiner
    • Estado de la aplicacion en el directorio de trabajo:
      • Datos sobre cada imagen: ubicacion y nivel de zoom.
      • Lista de controles con sus datos.

Funcionamiento:


La aplicación funcionara basada en un directorio de trabajo donde se leeran y guardara toda la informacion. La aplicación funciona cargando una imagen en el canvas "ImagenEnTrabajoCanvas" y luego cargando todos los controles asignados a esta imagen. Los controles se cargan en "ListaROIs" El trabajo de la aplicación es permitir la adicion/modificacion de controles que fueron programados desde la "ListaFunciones". Estos Usercontrol exponen algunos parámetros que se pueden modificar en el area "PanelEdicion" . Ademas de estos controles dibujarse a si mismos tienen una funcionalidad que sera llamada. Existira un clock de la applicazioni que podra iniciarse/detenerse o configurarse desde el menu principal. Este clock es el que se utilizara para llamar a la funcion "simulación" de cada usercontrol.

Directorio de trabajo:

  • En este directorio de trabajo se salvara y leerá el ultimo estado de la aplicación. Archivo JSON.
  • En este directorio estarán los archivos PNG que se usaran para la lista de ListaImagenes
  • Se creara / actualizara un base de datos usando el paquete Microsoft.Data.Sqlite

El estado de la aplicación se mantendrá serializando una clase de estado con la ultima informacion de la aplciacion.

Este directorio de trabajo se elegirá desde el menu principal. Menu "Archivo" -> "Directorio de trabajo".

  1. Esto llamara a la función de apertura de directorio de windows.
  2. El directorio de trabajo se mostrara en la barra de titulo

Diseño de la ventana principal:


  • Quisiera que la ventana principal tenga un menu general donde va el titulo
  • Este diseñada para poder modificar el tamaño de la ventana y usando grid todos los controles excepto los botones se deben redimensionar.
  • Estructura de la ventana principal:
    • Se divide en 3 columnas. El espacio horizontal se divide en 1/4 para la primera columna, 2/4 para la segunda y 1/4 para la tercera columna.
    • Primera columna:
      • Se divide en un nuevo grid de dos filas:
        • 1 fila : 2/3 del espacio vertical: ListBox de nombre "ListaImagenes"
        • 2 fila : 1/3 del espacio vertical: ListBox de nombre "ListaFunciones"
    • Segunda columna:
      • Un Cavas de nombre "ImagenEnTrabajoCanvas" y un menu en la parte superior del canvas
    • Tercera columna:
      • Se divide con un grid en dos partes iguales:
        • Primera Fila: Una ListBox con el nombre "ListaROIs"
        • Segunda Fila: Un area "PanelEdicion" para permitir la edicion de los parametros expuestos de los UserControl tipo ViewModel

Simulación:


Quisiera que me ayudes a desarrollar una simulación en C# .NET 8 con System.Numerics o alguna biblioteca que sea mejor, que permita representar la interacción entre círculos, líneas y rectángulos. Los círculos representan botellas, los rectángulos actúan como transportes motorizados (por ejemplo, cintas transportadoras) y las líneas indican las barreras que los círculos no pueden superar. Los círculos y las líneas comparten la dimensión Z, mientras que los rectángulos se sitúan por debajo, ejerciendo una fuerza única sobre los círculos.

Todos los elementos se definen como sigue:

Circulos: ( se mueven )

  • Left
  • Top
  • Diametro
  • Masa
  • Dinamicos:
    • Angulo: de movimiento en grados
    • Velocidad

Rectangulos: ( fijos )

  • Left
  • Top
  • Largo
  • Ancho
  • Angulo : angulo en grados en el que se encuentra el rectangulo
  • Velocidad

Lineas: ( fijos )

  • Left
  • Top
  • Largo DX
  • Ancho DY
  • Angulo : ángulo en grados en el que se encuentra la linea

La SimulationManager se puede definir de la siguiente manera:

// Clase principal que gestiona la simulación
public class SimulationManager
{
    public List<Circle> circles;
    public List<Rectangle> rectangles;
    public List<Line> lines;    


    public SimulationManager()
    {
        circles = new List<Circle>();
        rectangles = new List<Rectangle>();
        lines = new List<Line>();
    }

    public void Step(float timeStep)
    {
        foreach (var circle in circles)
        {
            circle.Move(timeStep, circles, rectangles, lines);
        }
    }
}

La primera funcion que me interesa es: calcular un porcentaje aproximado de la superficie de un circulo se sobrepone a la superficie de un rectangulo. Esto sirve para calcular cuanta transferencia de velocidad y con que angulo se asignara al circulo. Tener en cuenta que los rectangulos tienen un angulo basados en el punto de ancla: Top Left osea no el Length no corre sobre el exe X del sistema de coordenadas del sistema sino que depende de Angle.

Las interacciones son:

  • los círculos pueden iniciar movimiento por estar sobre el area de un rectangulo. Dependiendo de el area aproximada que un circulo comparta con un rectangulo se transferira mayor fuerza hasta alcanzar la velocidad del rectangulo.
  • los circulos pueden iniciar movimiento al contacto con otro circulo con movimiento por impacto. Solo interesa el angulo de impacto para calcular aproximadamente el movimiento resultante del circulo que recibe el impacto. No se calcula perdida de fuerza sobre el circulo que original el movimiento para simplicar la dinamica.
  • los circulos sobre las lineas : dependiendo del ángulo de impacto con las líneas, los círculos pueden cambiar su dirección de movimiento. Esto se divide en tres casos: si el ángulo de impacto es < 85, el resultado es el mismo ángulo de la línea; si es >95, el resultado es contrario; si está entre 85 y 95, se produce una cancelación de movimiento.

Quisiera crear una funcion CalculateOverlapPercentage para calcular el porcentaje de area en comun entre un circulo y un rectangulo rotado por el pivote Top,Left. En lugar de un círculo podemos usar un cuadrado para simplificar los calculos que puede estar en cualquier orientacion que simplifique el calculo. La idea seria usar el teorema de Separating Axis Theorem, SAT para calcular el porcentaje del area en comun. Para acercarnos mas podemos reducir los lados del cuadrado para aproximarlo a la superficie del círculo en ( \frac{L \sqrt{\pi}}{2} ) qué es la constante entre la superficie del círculo de diámetro L inscrito en un cuadrado de lado L. La funcion recibe CalculateOverlapPercentage(Circle circle, Rectangle rectangle) que estan definidos como :

public class Circle
{
    private Vector2 position;
    public float Left
    {
        get { return position.X; }
        set { position.X = value; }
    }
    public float Top
    {
        get { return position.Y; }
        set { position.Y = value; }
    }
    public float Diameter { get; set; }
    public float Mass { get; set; }
    public float AngleofMovement { get; set; }  // En grados
    public float Speed { get; set; }
    public float Overlap { get; set; }
}

public class Rectangle
{
    private Vector2 position;
    public float Left
    {
        get { return position.X; }
        set { position = new Vector2(value, position.Y); }
    }
    public float Top
    {
        get { return position.Y; }
        set { position = new Vector2(position.X, value); }
    }
    public float Length { get; set; }
    public float Width { get; set; }
    public float Angle { get; set; }  // En grados
    public float Speed { get; set; }  // Velocidad del rectángulo

    public Rectangle(float left = 0, float top = 0, float length = 10, float width = 10, float angle = 0, float speed = 0)
    {
        position = new Vector2(left, top);
        Length = length;
        Width = width;
        Angle = angle;
        Speed = speed;
    }

}

public class Line
{
    private Vector2 position;
    public float Left
    {
        get { return position.X; }
        set { position = new Vector2(value, position.Y); }
    }
    public float Top
    {
        get { return position.Y; }
        set { position = new Vector2(position.X, value); }
    }
    public float Length { get; set; }
    public float Width { get; set; }
    public float Angle { get; set; }  // En grados
    public float Grip { get; set; }  // Friccion por contacto

    public Line(float left = 0, float top = 0, float length = 10, float angle = 0, float grip = 0)
    {
        position = new Vector2(left, top);
        Length = length;
        Angle = angle;
        Grip = grip;
    }
}

// Clase principal que gestiona la simulación
public class SimulationManager
{
    public List<Circle> circles;
    public List<Rectangle> rectangles;
    public List<Line> lines;    


    public SimulationManager()
    {
        circles = new List<Circle>();
        rectangles = new List<Rectangle>();
        lines = new List<Line>();
    }

    public void Step(float timeStep)
    {
        foreach (var circle in circles)
        {
            circle.Move(timeStep, circles, rectangles, lines);
        }
    }
}
    public void Move(float timeStep_ms, List<Circle> circles, List<Rectangle> rectangles, List<Line> lines)
    {
        // Convertir timeStep de milisegundos a segundos para la simulación
        float timeStepInSeconds = timeStep_ms / 1000.0f;
        bool isTracted = false; // Indicador para verificar si el círculo está siendo traccionado
        Overlap = 0;

        // Aplicar fuerza desde el rectángulo si está sobre uno
        foreach (var rectangle in rectangles)
        {
            float overlap = CalculateOverlapPercentage(this, rectangle);
            if (overlap > 10)
            {
                Overlap += overlap;
                isTracted = true; // El círculo está siendo traccionado por un rectángulo
                                  // Convertir la velocidad del rectángulo de metros por minuto a metros por segundo
                float rectangleSpeedInMetersPerSecond = rectangle.Speed / 60.0f;

                if (rectangleSpeedInMetersPerSecond < Speed)
                {
                    // Aplicar una fuerza de frenado si la velocidad del rectángulo es menor que la velocidad del círculo
                    float brakingForce = (Speed - rectangleSpeedInMetersPerSecond) * (overlap / 100.0f);
                    Speed -= brakingForce * timeStepInSeconds;
                }
                else
                {
                    // Alinear gradualmente la velocidad del círculo con la del rectángulo si es mayor
                    Speed += (rectangleSpeedInMetersPerSecond - Speed) * (overlap / 100.0f) * timeStepInSeconds;
                }

                AngleofMovement = rectangle.Angle;
            }
        }

        // Si el círculo no está siendo traccionado, aplicar desaceleración
        if (!isTracted)
        {
            float deceleration = (1.0f / Mass) * 10.0f; // Coeficiente de desaceleración inversamente proporcional a la masa
            Speed -= deceleration * timeStepInSeconds;
            if (Speed < 0) Speed = 0; // Evitar que la velocidad sea negativa
        }

        // Calcular nueva posición
        Vector2 direction = new Vector2((float)Math.Cos(AngleofMovement * Math.PI / 180), (float)Math.Sin(AngleofMovement * Math.PI / 180));
        Vector2 velocity = direction * Speed * timeStepInSeconds;
        position += velocity;

        // Ajustar por colisiones con líneas
        foreach (var line in lines)
        {
            Vector2 movementVector = Vector2FromPolar(Speed, AngleofMovement);
            Vector2 circleCenter = GetCircleCenter(this);

            // Calcular la nueva posición tentativa del centro del círculo
            Vector2 newPosition = circleCenter + movementVector * timeStepInSeconds;

            if (LineCircleCollision(newPosition, Diameter / 2, line, out Vector2 collisionPoint))
            {
                // Ajustar la posición del centro del círculo y el vector de movimiento
                AdjustCircleAfterCollision(ref newPosition, ref movementVector, line, collisionPoint, Diameter / 2);
            }

            // Actualizar la posición del círculo basada en el nuevo centro
            Left = newPosition.X - Diameter / 2;
            Top = newPosition.Y - Diameter / 2;
            Speed = movementVector.Length();
            AngleofMovement = PolarAngleFromVector(movementVector);
        }

        // Ajustar por superposición con otros círculos
        foreach (var other in circles)
        {
            if (this != other && IsColliding(this, other))
            {
                AdjustForOverlap(other);
            }
        }

    }