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 de la aplicación:
- 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
- Que realizan una accion:
- ObjetosSimulados: UserControl de simulacion:
- Estado de la aplicacion en el directorio de trabajo:
- Datos sobre cada imagen: ubicacion y nivel de zoom.
- Lista de controles con sus datos.
- Estado de simulación:
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".
- Esto llamara a la función de apertura de directorio de windows.
- 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"
- Se divide en un nuevo grid de dos filas:
- 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
- Se divide con un grid en dos partes iguales:
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);
}
}
}