Agregar archivos de proyecto.

This commit is contained in:
alexb737
2022-06-18 07:40:24 +02:00
parent e4c2d17816
commit e1e34021f7
12 changed files with 689 additions and 0 deletions

9
JansenLegViewer/App.xaml Normal file
View File

@ -0,0 +1,9 @@
<Application x:Class="JansenLegViewer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:JansenLegViewer"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace JansenLegViewer
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,75 @@
using System;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading.Tasks;
using System.Collections.Generic;
using static System.Math;
using System.Numerics;
/*
Función que permite obtener las posiciones de las
posibles intersecciones entre dos circulos.
PD: le debeo un café a alguien random de internet
*/
namespace JansenLegSimulator
{
public class CosasQueNoSeExplicanEnMatesDePrimero
{
/// <summary>
/// Gets the intersections of two circles
/// </summary>
/// <param name="center1">The first circle's center</param>
/// <param name="center2">The second circle's center</param>
/// <param name="radius1">The first circle's radius</param>
/// <param name="radius2">The second circle's radius. If omitted, assumed to equal the first circle's radius</param>
/// <returns>An array of intersection points. May have zero, one, or two values</returns>
/// <remarks>Adapted from http://csharphelper.com/blog/2014/09/determine-where-two-circles-intersect-in-c/</remarks>
public static Vector2[] CalculateCircleIntersections(Vector2 center1, Vector2 center2, double radius1, double? radius2 = null)
{
// Definir radios. Si sólo se ha dado R1,
// asignar valor de R1 a los dos radios
var (r1, r2) = (radius1, radius2 ?? radius1);
// Definir centros de los circulos.
(double x1, double y1, double x2, double y2) = (center1.X, center1.Y, center2.X, center2.Y);
// Determinar la distancia entre los dos centros.
// Es el módulo del vector que une los dos centros.
double d = Sqrt(Pow(x1 - x2, 2) + Pow(y1 - y2, 2));
// Si la suma de radios es menor a la distancia que
// une los centros, devolver un array vacío.
// No existe intersección.
if (!(Abs(r1 - r2) <= d && d <= r1 + r2)) { return new Vector2[0]; }
// En este momento, se dan las condiciones para que
// por lo menos exista una intersección
var dsq = d * d; // Caucular el cuadrado de la distancia (d^2)
var (r1sq, r2sq) = (r1 * r1, r2 * r2); // Calcular los cuadrados de los radios (r^2)
var r1sq_r2sq = r1sq - r2sq; // Calcular la diferencia de los r^2 (incR^2)
var a = r1sq_r2sq / (2 * dsq); // Calcular la división (incR^2)/(2*d^2)
var c = Sqrt(2 * (r1sq + r2sq) / dsq - (r1sq_r2sq * r1sq_r2sq) / (dsq * dsq) - 1);
// Calcular las componentes de los resultados
var fx = (x1 + x2) / 2 + a * (x2 - x1);
var gx = c * (y2 - y1) / 2;
var fy = (y1 + y2) / 2 + a * (y2 - y1);
var gy = c * (x1 - x2) / 2;
// Generar los vectores de las intersecciones resultado
var i1 = new Vector2((float)(fx + gx), (float)(fy + gy));
var i2 = new Vector2((float)(fx - gx), (float)(fy - gy));
// Si son la misma solucción deveolver sólo i1.
// Si son ditintas, devolver ambas soluciones.
return i1 == i2 ?
new Vector2[] { i1 } :
new Vector2[] { i1, i2 };
}
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using static JansenLegSimulator.Rod;
namespace JansenLegSimulator
{
public partial class Cálculos
{
private void DefineSystem()
{
// Definir las barra motriz
M = new Rod(new Vector2(a, l)) { Length = m, RodName = "M" };
// Definir las barras semi-independientes
// Aquellas que tienen un extremo fijado a una coordenada
B = new Rod(new Vector2(0, 0)) { Length = b, RodName = "B" };
C = new Rod(new Vector2(0, 0)) { Length = c, RodName = "C" };
D = new Rod(new Vector2(0, 0)) { Length = d, RodName = "D" };
// Definir y conectar los inicios de las barras flotantes
// Aquellas cuyos dos extremos están conectados a otra barra
J = new Rod() { Length = j, RodName = "J" }; J.ConnectBegin(ref M);
K = new Rod() { Length = k, RodName = "K" }; K.ConnectBegin(ref M);
E = new Rod() { Length = e, RodName = "E" }; E.ConnectBegin(ref B);
G = new Rod() { Length = g, RodName = "G" }; G.ConnectBegin(ref C);
F = new Rod() { Length = f, RodName = "F" }; F.ConnectBegin(ref D);
H = new Rod() { Length = h, RodName = "H" }; H.ConnectBegin(ref G);
I = new Rod() { Length = i, RodName = "I" }; I.ConnectBegin(ref C);
// Añadir las barras definidas a la lista
// de elementos móviles.
Elements.Add(M);
Elements.Add(J);
Elements.Add(B);
Elements.Add(C);
Elements.Add(K);
Elements.Add(E);
Elements.Add(D);
Elements.Add(G);
Elements.Add(F);
Elements.Add(H);
Elements.Add(I);
}
[STAThread]
public void CalculateSystem()
{
// Para cada par de barras calcular y asignar los
// ángulos de rotación necesarios para unirlas
// Articulación JB
(J.Alpha, B.Alpha) = GetIntersectionAlphas(J, B);
// Articulación CK
(C.Alpha, K.Alpha) = GetIntersectionAlphas(C, K);
// Articulación ED
(E.Alpha, D.Alpha) = GetIntersectionAlphas(E, D);
// Articulación FG
(F.Alpha, G.Alpha) = GetIntersectionAlphas(F, G);
// Articulación HI
(H.Alpha, I.Alpha) = GetIntersectionAlphas(H, I);
}
}
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>

Binary file not shown.

View File

@ -0,0 +1,56 @@
<Window x:Class="JansenLegSimulator.Cálculos"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:JansenLegViewer"
Name="thisWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Margin="1,0,-1,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*" />
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="26"/>
</Grid.RowDefinitions>
<Canvas Name="plot" Grid.Column="0"/>
<GridSplitter BorderBrush="LightGray" BorderThickness="3"/>
<StackPanel Grid.Column="1">
<StackPanel Orientation="Horizontal">
<Button Name="btnPlay" Content=">" Click="btnPlay_Click"/>
<Button Name="btnPause" Content="| |" Click="btnPause_Click"/>
<Button Name="btnStop" Content="#" Click="btnStop_Click"/>
</StackPanel>
<Label Content="ω rotor (rad/s)"/>
<TextBox Text="{Binding ElementName=thisWindow, Path=Omega, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
<StackPanel Orientation="Horizontal">
<Label Content="Número de puntos"/>
<Label Content="{Binding ElementName=sbPoints, Path=Value}"/>
</StackPanel>
<ScrollBar Name="sbPoints" Orientation="Horizontal" Value="{Binding ElementName=thisWindow, Path=MaxPoints, UpdateSourceTrigger=PropertyChanged}" Minimum="0" Maximum="500"/>
<ListView ItemsSource="{Binding ElementName=thisWindow, Path=Elements}">
<ListView.ItemTemplate>
<DataTemplate DataType="local:Rod">
<Grid>
<StackPanel>
<TextBox Text="{Binding RodName, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
<TextBox Text="{Binding Length, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<StatusBar Grid.Row="1" Grid.ColumnSpan="2">
<StatusBarItem Name="SimulationStatus"/>
<StatusBarItem Name="Time"/>
</StatusBar>
</Grid>
</Window>

View File

@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Numerics;
using System.Drawing;
using System.Windows.Controls;
using static JansenLegSimulator.Rod;
namespace JansenLegSimulator
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class Cálculos : Window
{
// Origen de cordenadas
Vector2 Center;
// Dimensiones de los segmentos (iniciales, se pueden modificar des de la interfaz)
public float SCALE { get; set; } = 2.0f;
public float a => 38.0f * SCALE;
public float b => 41.5f * SCALE;
public float c => 39.3f * SCALE;
public float d => 40.1f * SCALE;
public float e => 55.8f * SCALE;
public float f => 39.4f * SCALE;
public float g => 36.7f * SCALE;
public float h => 65.7f * SCALE;
public float i => 49.0f * SCALE;
public float j => 50.0f * SCALE;
public float k => 61.9f * SCALE;
public float l => 7.8f * SCALE;
public float m => 15.0f * SCALE;
private Rod B, C, D, E, F, G, H, I, J, K, M;
public float Omega { get; set; } = (2 * MathF.PI);
System.Timers.Timer t = new(15);
private System.Diagnostics.Stopwatch Chrono = new();
public List<Rod> Elements { get; set; } = new List<Rod>();
private List<Vector2> Points = new();
public int MaxPoints { get; set; } = 25;
#region Constructor y handlers de la ventana
public Cálculos()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
this.Closing += MainWindow_Closing;
}
private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Environment.Exit(0);
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
DefineSystem();
DrawSystem();
t.Elapsed += Tick;
}
// Receptores de eventos de click de los botones de la interfaz
private void btnPlay_Click(object sender, RoutedEventArgs e) => StartSimulation();
private void btnPause_Click(object sender, RoutedEventArgs e) => PauseSimulation();
private void btnStop_Click(object sender, RoutedEventArgs e) => StopSimulation();
#endregion
// Receptor de evento de ciclo del reloj
private void Tick(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
// Actualizar el ángulo de la barra motriz
// con ω * t radianes
(Elements[0] as Rod).Alpha = (float)(Omega * Chrono.Elapsed.TotalSeconds) % 2 * MathF.PI;
lock (Points)
{
// Eliminar puntos antiguos si se ha superado el límite de puntos especificado
if (Points.Count > MaxPoints)
Points.RemoveAt(0);
}
// Ejecutar cálculos del sistema
CalculateSystem();
// Ejecutar renderizado del sistema en el hilo de principal
this.Dispatcher.Invoke(() => DrawSystem());
}
catch
{
// Si la geometría es inválida, se lanzará una excepción
// Detener simulación y mostrar el estado de error
this.Dispatcher.Invoke(() =>
{
StopSimulation();
SimulationStatus.Content = "Error";
});
}
}
#region Control de la simulación
// Inicia la simulación
public void StartSimulation()
{
SimulationStatus.Content = "Simulando...";
t.Start();
Chrono.Start();
}
// Pausa la simulación
public void PauseSimulation()
{
SimulationStatus.Content = "En pausa";
t.Stop();
Chrono.Stop();
}
// Detiene la simulación y reinicia las variables
// de tiempo
public void StopSimulation()
{
SimulationStatus.Content = "Detenido";
t.Stop();
Chrono.Stop();
Chrono.Reset();
}
#endregion
[STAThread]
public void DrawSystem()
{
plot.Children.Clear(); // Borrar el contenido anterior del lienzo
Center = new Vector2( // Calcular el centro del lienzo
(float)plot.ActualWidth / 2,
(float)plot.ActualHeight / 2);
foreach (var element in Elements) // Dibujar los elementos en el lienzo
{
if (element.GetType() == typeof(Rod))
DrawElement<Rod>(element);
if (element.GetType() == typeof(MotorRod))
DrawElement<MotorRod>(element);
}
// Registrar el punto que actualmente se ubica
// en el extremo final de la barra I
Points.Add(I.EndPosition);
lock (Points)
{
// Dibujar los puntos en el lienzo
foreach (Vector2 point in Points)
DrawPoint(point);
}
}
#region Inserción de objetos
public void DrawElement<Type>(object r) where Type : Rod
{
// Declarar una línea de color azul con 5px de grosos
// segmentada entre los dos extremos de la barra.
Line l = new Line()
{
Stroke = Brushes.Blue,
StrokeThickness = 5,
X1 = Center.X + (r as Type).StartPosition.X,
X2 = Center.X + (r as Type).EndPosition.X,
Y1 = Center.Y - (r as Type).StartPosition.Y,
Y2 = Center.Y - (r as Type).EndPosition.Y,
};
// Añadir la barra al lienzo
plot.Children.Add(l);
}
public void DrawPoint(Vector2 Location, double diameter = 5)
{
double r = diameter / 2; // Calcular radio del punto
// Declarar una elipse de color rojo con
// el mismo ancho que altura (diámetro).
Ellipse e = new Ellipse()
{
Fill = Brushes.Red,
Width = diameter,
Height = diameter
};
// Añadir la elipse al lienzo
plot.Children.Add(e);
// Situar la elipse dentro del lienzo.
// Los ejes de coordenadas del lienzo tienen origen en
// la esquina superior izquierda. El eje vertical se debe
// invertir.
Canvas.SetLeft(e, Center.X + Location.X - r);
Canvas.SetTop(e, plot.ActualHeight - (Center.Y + Location.Y - r));
}
#endregion
}
}

15
JansenLegViewer/Motor.cs Normal file
View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;
using System.Drawing;
namespace JansenLegSimulator
{
public class MotorRod : Rod
{
public override Vector2 StartPosition { get; set; }
}
}

199
JansenLegViewer/Rod.cs Normal file
View File

@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;
using System.Drawing;
namespace JansenLegSimulator
{
public class Rod
{
#region Constructores
// Constructor de barra libre
public Rod() { }
// Constructor de barra con un extremo fijado a un punto
public Rod(Vector2 begining)
{
_startPosition = begining;
}
#endregion
#region Conexiones
/// <summary>
/// Barra a la que se une por el extremo inicial
/// </summary>
private Rod JointBegining { get; set; } = null;
/// <summary>
/// Barras a las cuales se une por el extremo final
/// </summary>
private List<Rod> JointsEnding { get; set; } = new();
/// <summary>
/// Conecta el inicio de una barra a otra barra
/// </summary>
/// <param name="linkage">La barra a la cual se unirá esta</param>
public void ConnectBegin(ref Rod linkage)
{
if (linkage == null) throw new NullReferenceException("The rod cannot be null");
if (JointBegining != null) throw new Exception("The beginning is already assigned");
JointBegining = linkage;
}
/// <summary>
/// Conecta el final de una barra a otra barra
/// </summary>
/// <param name="linkage">La barra a la cual se unirá esta</param>
public void ConnectEnd(ref Rod linkage)
{
if (linkage == null) throw new NullReferenceException("The rod cannot be null");
if (!JointsEnding.Contains(linkage))
JointsEnding.Add(linkage);
}
/// <summary>
/// Desconecta (si está conectada) la barra especificada
/// </summary>
/// <param name="linkage">Barra a ser desconectada</param>
public void Disconnect(ref Rod linkage)
{
if (JointBegining == linkage) JointBegining = null;
if (JointsEnding.Contains(linkage)) JointsEnding.Remove(linkage);
}
#endregion
#region Propiedades de la barra
public string RodName { get; set; } = "Unnamed";
/// <summary>
/// Longitud de la barra
/// </summary>
public float Length { get; set; } = 20;
private float? _alpha = null;
/// <summary>
/// Ángulo de rotación de la barra. Si no se establece será 0 radianes.
/// </summary>
public float Alpha
{
get => (_alpha != null) ? (float)_alpha : 0;
set => _alpha = value;
}
private Vector2? _startPosition = null;
/// <summary>
/// Posición de incio (eje de esta barra).
/// Si se ha unido el inicio a otra barra, se calculará a partir de la posición final de la barra sobre la cual está
/// </summary>
public virtual Vector2 StartPosition
{
get => (_startPosition != null) ? (Vector2)_startPosition :
(JointBegining != null) ? JointBegining.EndPosition : new Vector2(0, 0);
set => _startPosition = value;
}
/// <summary>
/// Posición final.
/// Se calcula a partir de la posición inicial, el ángulo y la longitud.
/// </summary>
public virtual Vector2 EndPosition
{
get => new Vector2(
StartPosition.X + Length * MathF.Cos(Alpha),
StartPosition.Y + Length * MathF.Sin(Alpha)
);
}
/// <summary>
/// Posición central.
/// Se calcula a partir de la posición inicial, el ángulo y la longitud.
/// </summary>
public virtual Vector2 Center
{
get => new Vector2(
StartPosition.X + Length * MathF.Cos(Alpha) / 2,
StartPosition.Y + Length * MathF.Sin(Alpha) / 2
);
}
#endregion
/// <summary>
/// Calcula el punto de intersección entre dos barras de longitud y eje de rotación determinados.
/// </summary>
/// <param name="A">Primera barra</param>
/// <param name="B">Segunda barra</param>
/// <param name="solution">En caso de que existan dos intersecciones, número de la intersección a elegir</param>
/// <returns>El vector que representa el punto en el que se unen las dos barras.</returns>
/// <exception cref="RodsDoNotIntersectException">Si las barras no intersecan en ningún punto, lanzará una excepción.</exception>
/// <exception cref="IntersectionOutOfRangeException">Si las barras intersecan pero no se encuentra la intersección especificada, lanzará una excepción.</exception>
public static Vector2 GetIntersection(Rod A, Rod B, int solution = -1)
{
Vector2[] intersects = CosasQueNoSeExplicanEnMatesDePrimero.CalculateCircleIntersections(
A.StartPosition,
B.StartPosition,
A.Length,
B.Length);
return (intersects.Length == 0) ? throw new RodsDoNotIntersectException("No existe conexión posible entre estas dos barras. Pruebe a darles la vuelta.") :
(intersects.Length == 1 || solution < 0) ? intersects[0] :
(intersects.Length > solution) ? intersects[solution] :
throw new IntersectionOutOfRangeException("La intersección especificada no existe. Pruebe con las soluciones 0 y 1");
}
/// <summary>
/// Calcula la diferencia entre dos vectores
/// </summary>
/// <param name="A">Vector final</param>
/// <param name="B">Vector inicial</param>
/// <returns>Vector final - Vector inicial</returns>
public static Vector2 Delta(Vector2 A, Vector2 B) => A - B;
/// <summary>
/// Calcula para cada elemento el ángulo de la intersección de dos vectores a su posición respecto el 0 en el primer cuadrante.
/// </summary>
/// <param name="A">Vector 1</param>
/// <param name="B">Vector 2</param>
/// <param name="solution">Devuelve los angulos de cada eje a la intersección</param>
/// <returns></returns>
public static (float, float) GetIntersectionAlphas(Rod A, Rod B, int solution = -1)
{
(float, float) result = new();
// Calcular las intersecciones y los vectores de
// incremento entre el eje de la barra y su unión.
Vector2
I = GetIntersection(A, B, solution),
DeltaA = Delta(I, A.StartPosition),
DeltaB = Delta(I, B.StartPosition);
// Obtener los ángulos mediante la inversa del coseno
result.Item1 = MathF.Acos(DeltaA.X / DeltaA.Length());
result.Item2 = MathF.Acos(DeltaB.X / DeltaB.Length());
// Voltear verticalmente según la dirección vertical
// del vector incremento
if (DeltaA.Y < 0) result.Item1 *= -1.0f;
if (DeltaB.Y < 0) result.Item2 *= -1.0f;
return result;
}
[Serializable]
public class RodsDoNotIntersectException : Exception
{
public RodsDoNotIntersectException() { }
public RodsDoNotIntersectException(string message) : base(message) { }
public RodsDoNotIntersectException(string message, Exception inner) : base(message, inner) { }
protected RodsDoNotIntersectException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
[Serializable]
public class IntersectionOutOfRangeException : Exception
{
public IntersectionOutOfRangeException() { }
public IntersectionOutOfRangeException(string message) : base(message) { }
public IntersectionOutOfRangeException(string message, Exception inner) : base(message, inner) { }
protected IntersectionOutOfRangeException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
}
}