Simulación gráfica animada

Iniciado por niano, 26 Junio 2019, 08:30 AM

0 Miembros y 1 Visitante están viendo este tema.

niano

Hola, buenos días.

Estoy haciendo una especie de animación simulada con java y resulta que soy algo nuevo en esto de la programación gráfica, por lo que tengo muchas dudas. Voy a intentar subiros los fragmentos de código que me generan más dudas de la manera más resumida posible para ver si podéis echarme una mano, ya que el programa no funciona. Gracias a todos y todas de antemano.

Pues bien, la simulación lo que tiene que hacer es dibujar unos círculos en un panel que se deben ir moviendo según unos parámetros booleanos que introducimos en unas casillas de activación. El número de círculos que se deben dibujar se introducen en un cuadro de texto. En este enlace hay un vídeo con el resultado que debo conseguir.

Lo que creo que debo hacer es definir:

Una clase vector con las operaciones básicas que se pueden hacer con los vectores en dos dimensiones. Esto va bien.

Una clase circulo con los atributos de cada uno de los círculos (diámetro, color, posición, velocidad, aceleración), con un método pintar que dibuje un círculo a partir de un objeto de la clase graphics y otro método actualizar que en cada iteración actualice la posición de un círculo según sus propios atributos. También contiene otros métodos. Esto creo que también va más o menos bien.

Una clase Panel, que debe ser un panel gráfico sensible a los movimientos del ratón que contenga como atributos una colección de círculos y los datos de la IGU. Aquí empiezan mis problemas. Entiendo, en un principio, que Panel podría ser una subclase de java.awt.graphics2D y que podría utilizar la interfaz MouseMotionListener.

Un programa principal que contenga la definición y comportamiento de la interfaz. Mi idea es hacer esto con una subclase de JFrame, pero también ando algo perdido con esto.

Bueno, ahí va lo que en un principio había concebido para la clase Panel. Lo que ocurrió es me surgieron muchos problemas y lo fui modificando. Entre esos problemas, el NetBeans me obligaba a poner un abstract en la declaración de la clase. Pero no me daba buena espina eso, por lo que también fui probando con la clase java.awt.Canvas sin conseguir mucho. En cualquier caso, me gustaría hacerlo con graphics2D:


public class Panel extends java.awt.graphics2D implements MouseMotionListener
{
    //Atributos de la clase. No creo que tenga problemas aquí.
    circulo bolas[]; // Un array con las bolas;
    int nbolas; // El número de bolas
    boolean follow_mouse // Nos indica si la casilla Follow Mouse está activada o no.
    //...

    //Luego el constructor.
    public Panel (int Nbolas, boolean walls, boolean mouse, int ancho, int alto)
    {
        setSize(ancho, alto); // Dimensiono el panel
        bolas = new circulo (...);  // Reservo memoria para el array de círculos y construyo
                                              //esos círculos según el constructor de esa clase.
        // Y otras cosas así
        //...
    }
    //...

    public void paintComponent (Graphics g)
    {
        //Aquí hay un bucle en el que llamo al método [b]pintar [/b] para cada uno de los
        //circulos del array introduciéndolo el arguento g.
    }
    public void actualizarBolas (void);
    {
        //Aquí hay un bucle en el que llamo al método [b]actualizar[/b] para cada uno de
        //los circulos del array
    }

    //Los métodos de la interfaz.
    //Cuando el ratón se mueva y en el caso de que la casilla Follow Mousse esté activada
    //se debe cambiar el centro del movimiento
    public void mouseDragged (MouseEvent e)
    {
        if (follow_mouse==1)
        {
            Centro.cambiarx(e.getX());
            Centro.cambiary(e.getY());
        }
    }
    public void mouseDragged (MouseEvent e) {}
}


Y ahora un esbozo de la clase principal. Creo que aquí es donde la lío más. Me gustaría hacerlo con un JFrame, aunque si tuviese que cambiar de clase no me importaría con tal de que el programa funcionase. Esto lo fui también modificando de una manera un tanto arbitraria, pero nada. Ni siquiera fui capaz de visualizar el panel:


public class app extends JFrame implements ItemListener, ActionListener
{
    //Atributos:
    JCheckBox check1, check2;
    JTextField text;
    Panel p;
    Graphics g;
   
    //Constructor. No creo que esto esté bien:
    public app()
    {
        p=new Panel(....) // Construyo el panel con unos parámetros adecuados.
        Container c=getContentPane();
        check1=new JCheckBox("With walls");
        check2=new JCheckBox("Follow mouse");
        text=new JTextField (20);

        check1.addItemListener(this);
        check2.addItemListener(this);
        text.addActionListener(this);

        c.add(check1);
        c.add(check2);
        c.add(p);

       

    }
   
    //Los métodos de las interfaces:
    public void itemStateChanged (ItemEvent e)
    {
        if (e.getItemSelectable==check1)
        {
              p.cambiarWalls(check1.isSelected());
        }
        if (e.getItemSelectable==check2)
        {
              p.cambiarMouse(check2.isSelected());
        }

    }
    public void actionPerformed (ActionEvent e)
    {
        if (e.getSource==text)
        {
             //Aquí obtengo el texto que hay en el cuadro, lo paso a un int y vuelvo a
             //construir el panel p con los parámetros actuales.
        }
    }
    // El main.
    public static void main (String args[])
    {
        final JFrame f=new app();
        fsetBounds(50,50,500,500);
        f.setVisible(true);

        // Y ahora el bucle que crea la simulación. No estoy seguro de que esto vaya aquí,
        // pero  no sé dónde meterlo... :)
        while (1==1)
        {
             p.paintComponent(g);
             p.actualizarBolas();
             p.repaint();
        }
    }
}


A ver qué os parece. Yo estoy bastante perdido y falto de ideas.

Muchísimas gracias de antemano por vuestro tiempo.

niano


Serapis

#2
Pués me alegro que lo tengas solucionado...

Aunque veo demasiadas líneas de código...

La idea es crear un panel donde se presente todo, eso queda claro.
El panel debe alojar circulos, luego también procede crear una clase círculo con sus atributos...
...pero como son muchos, lo ideal es que se use una colección para contenerlos a todos. Un array puede valer, pero al caso iría mejor una lista  enlazada, después de todo, cualquier acción con ellas es síncrona, luego se trata de recorrer la colección y hacer lo mismo para cada objeto en dicha colección.

Luego hay un bucle principal que podría ser controlado por un temporizador (con eventos del ratón, no porque cuando permanece inmóvil, no tendríamos acciones). Es en dicho temporizador donde transcurre toda la acción. En los eventos del ratón simplemente actualizar las cordenadas actuales del ratón (esto es cuando se mueva, se actualizan las cordenadas compartidas a nivel del panel).


enumeracion DinamicaCirculos
   DINAMICA_CIRCULO_NADA = 0
   DINAMICA_CIRCULO_WALLS = 1
   DINAMICA_CIRCULO_FMOUSE = 2
   DINAMICA_CIRCULO_WALLS_FMOUSE = 3
fin enumeracion
Dinamicacirculos dinamica  // el valor se actualiza conforme se pulsen los 2 checkboxes.
lista enlazada colCirculos // para contener todos los circulos
int mouseX, MouseY
int numCirculos

funcion Panel_Mousemoved(int x, int Y)
  mouseX = x
  mouseY = y
fin funcion

funcion checkNumCirculos_Changed(int Valor)
    Si (valor < numCirculos)  // Eliminar sobrantes
         bucle para k desde numCirculos hasta valor regresivo
             colcirculos.DeleteLast  // elimina el que ahora está en último lugar. También podría eliminarse el primero (siempre hay 1 primero mientras no esté vacío).
         siguiente 
     Si (valor > numCirculos)  // añadir faltantes
         bucle para k desde numCirculos hasta valor
             colCirculos.Add(new circulo(a,b,c,d))  // añadir un nuevo círculo con los parámetros que proceda.
         siguiente         
     fin si

     numCirculos = valor
fin funcion

funcion Temporizador_elapsed
   Borrar panel
   por cada circulo en colCirculos
       circulo.Actualizar(mouseX, MouseY, dinamica)
   siguiente
fin funcion


Cuando cambia el valor (cantidad) de círculos, si es menor que el valor previo, señala que se deben eliminar los elementos que sobran de la colección. Si es mayor que se deben añadir. El color del círculo así como su posición incial, supongo que se eligen más o menos al azar (no veo descripción del caso), y la posición luego se va actualizando según 'danza' dicho círculo.
Ten en cuenta que cuanto más rápido (corto) sea el lapso del temporizador tanto menos tiempo necesitan los círculos para aproximarse al cursor... quizás haya un límite de proximidad tal que sobrepasado dicho valor, no se actulice, o se deje en el valor límite de proximidad.

La dinámica de cada uno de los 4 movimientos, no las describes en ningún momento, tan solo viendo el vídeo aclara en parte el asunto...
- 'with Walls', parece indicar que acepte rebotes al llegar a los bordes del panel, pero no queda claro que debe suceder sin rebotes (que si queda fuera del campo de visión no se dibuja, pero mantiene sus cordenadas actuales?, que las cordenadas actuales cambién como emergiendo desde el lado opuesto(conservando la dirección)?
- 'follow mouse', queda claro que cuando está activo, la marcha de cada círculo tiene en cuanta la proximidad a la posición actual del ratón, pero igualmente no queda claro que debe hacerse cuando dicha casilla no está activa.
Sea cual sea la dinámica aplicada para calcular la nueva posición del ´circulo, al final de dicho cálculo se dibuja.

Nota que en el método circulo.Actualizar, he puesto 3 parámetros, la posición actual del ratón (X e Y) y la dinámica que debe aplicarse, así tal método tendrá un 'if dinamica = 0 ...  ...albergando las 4 posibilidades. Haciéndolo así el código final resultanto sin duda debe ser muchísimo menor que las alrededor de 500 líneas de código que tienes... (en realidad esa función actualizar sería la que tendría todo el meollo del asunto), controlado por el bucle dentro dle temporizador.

Finalmente, además parece que todos los círculos giran en sentido horario, no observo ninguna que gire en sentido contrario, tampoco queda claro en parte alguna una descripción al respecto.

Pero, si al final has conseguido que te funcione, pués listo...

niano

#3
Hola NEBIRE. (Editado. Había escrito mal el nick)

Antes de nada, gracias por tu tiempo y por tus sabios consejos. Soy prácticamente nuevo en POO, en Java y en interfaces gráficas, así que toda ayuda es más que bienvenida por mi parte. Mira, te comento alguna cosilla más.

En este hilo en el que estamos publiqué el código que tenía entonces. La mayoría de las cosas que hice en él relacionadas con botones, paneles, casillas de activación, ratón y cosas de esas fueron un tanto arbitrarias. Las metí a partir de ejemplos que fui viendo por aquí y por allá y al final me quedó un batiburrillo bastante elegante.

Unos días después publiqué otro hilo en este mismo foro que también enlacé en mi respuesta anterior. Ese código está algo más evolucionado, ya que he ido entendiendo las cosas de porqué esto y porqué lo otro, o al menos a mí eso me parece. Estaría encantado de que me lo criticases también, aunque sea a vuelapluma, ya que tengo versiones posteriores del código en las que se han ido mejorando cosillas y con lo que estoy aprendiendo es fácil que en las próximas semanas el código sufra otras muchas otras modificaciones. Pero vamos, que todo comentario es bienvenido.

No entiendo lo que dices de que toda acción con una lista enlazada es síncrona. ¿Te importaría explicarme un poquillo ese concepto? Gracias. He manejado listas enlazadas cuando aprendí a programar. Eso fue en C. C puro, sin el ++  :D y entiendo lo que son listas enlazadas. Lo que no entiendo es lo de que las acciones sobre ellas sean síncronas.

Disculpa que no haya dejado nada claro el enunciado. Es un ejemplo que he pillado por ahí y que me gustaría reproducir grosso modo para aprender un poco sobre esto de interfaces gráficas, gráficos, animaciones... No hace falta que lo reproduzca de manera exacta.

Gracias por todo y un saludo ;).

Serapis

#4
Con 'síncrono' quiero señalar que recorres en orden correlativo todos los elementos, que no hay necesidad de acceder a un índice dado... (que es la situación (acceso aleatorio) donde un array supera a todas las otras estructuras), por lo que usar una lista enlazada es más adecuado (recurres a item.siguiente en los bucles), y la lista enlazada es más efectiva (que el array) para añadir y eliminar elementos dinámicamente.



p.d:
Con pocos elementos (5-20), añadir o eliminar a un array, no se aprecia retaso significativo. Cuando cambia el tamaño de un array se debe construir el nuevo array al completo, con la lista enlazada no se toca los no afectados, así si por ejemplo tuvieres 10.000.000 y luego pones 10.000.005 (que solo varía 5 elementos), el array tendría que generar un nuevo array y copiar los 10 millones previos... la lista enlazada solo afecta a esos 5 elementos, si nuevamente ahora reduces a 10.000.001 se debe volver a recontruir un nuevo array y copiar esos 10millones +1, la lista enlazada, simplemente deshecha los últimos 4.

niano

Hola NEBIRE.

Vale sí, claro, claro, lo entiendo, lo entiendo. Muy interesante esto de empezar pensando si el acceso es síncrono o aleatorio para usar una lista enlazada o un array, sobre todo si la dimensión de la estructura puede hacerse muy grande. Aquí, en principio no quería utilizar el programilla este para simular una situación con un gran número de bolas, así que creo que lo voy a dejar así, de momento, pero gracias por el consejo. Lo tendré en cuenta para futuras batallas.  ;)

Un saludo.