Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: Java: Metaballs

Sivun loppuun

FooBat [23.08.2004 02:53:02]

#

Java-esimerkkien vähäisyyden vuoksi metapallot vaihteeksi javalla toteutettuna.

Toisin kuin yleisesti luullaan, java ei enää nykyään ole kovin paljon hitaanpaa kuin esim. C++. Tästä esimerkkinä laiton metapalloja ohjelmaa hulppeat 75, joiden pitäisi liikkua suht' sulavasti vähän vanhemmallakin koneella.

Nopeus perustuu pitkälti taulukkoon laskettuun metapallon kuvaajaan ja rajoittumiseen yhden pallon laskennassa melko pienelle alueelle.

Tässä on käytössä myös harvemmin java-esimerkeissä käytetty 320x200 fullscreen moodi.

Valmiiksi käännetty binaari ja alkuperäinen tiedosto
http://www.niksula.cs.hut.fi/~jasainio/java/metaballs.zip

Ajetaan komentoriviltä: java MetaBalls

MetaBalls.java

import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;

import java.util.Arrays;
import java.util.Random;

public class MetaBalls extends Canvas implements KeyListener {
  /** Ruudun muistipuskuri */
  private int[] screen;

  //pallojen lukumäärä ja tietorakenteet
  private static final int BALLS = 75;
  private int[] ballXY = new int[BALLS];
  private int[] ballSpeed = new int[BALLS];
  private int[] ballRadius = new int[BALLS];

  /** Taulukko, jossa pallon kuvaaja */
  private int[] metaArray;

  /** Käytetty väripaletti */
  private int[] colorMap = new int[256];

  /** Ruudulle piirrettävä renderöity kuva */
  private Image screenImage;

  /**
   * Taikakalu, joka saa screenImagen käyttämään
   * screen-taulukkoa muistipuskurina
   */
  private MemoryImageSource mis;

  /** Kuvan koko */
  public int w, h;

  private Random r = new Random();


  public  MetaBalls() {

    w = 320;
    h = 200;


    metaArray = new int[256*256];
    initMetaArray();
    initBalls();
    initColorMap();
    screen = new int[ w * h ];


    // luodaan yhteys integer taulukon ja piirrettävän kuvan välille
    mis=new MemoryImageSource(w,h,ColorModel.getRGBdefault(),screen,0,w);
    mis.setAnimated(true);
    mis.setFullBufferUpdates(true);
    screenImage = createImage(mis);

    addKeyListener(this);
  }

  /**
   * Luo väripaletin.
   */
  private void initColorMap() {
    int r = 255;
    int g = 0;
    int b = 0;
    // musta -> punainen
    for (int i = 0; i < 128; i++) {
      colorMap[i] = 0xff000000 | (((int)(( i * r )/128.0))<<16);
    }

    int g2 = 255;
    int b2 = 0;
    // punainen -> keltainen
    for (int i = 128; i < 192; i++) {
      int green = g + (int)(( (i-128) * ( g2-g))/64.0);
      int blue = b + (int)(( (i-128) * ( b2-b))/64.0);
      colorMap[i] = 0xff000000 |(255 <<16)|(green<<8)|blue;
    }

    b = 255;
    // keltainen -> valkoinen
    for (int i = 192; i < 256; i++) {
      int blue = b2 + (int)(( (i-192) * ( b-b2))/64.0);
      colorMap[i] = 0xff000000 |(255 <<16)|(255<<8)|blue;
    }
  }

  /**
   * Arpoo palloille alkupaikan, koon ja nopeuden.
   */
  private void initBalls() {
    int n = w*h;
    for (int i = 0; i < BALLS; i++) {
       ballXY[i] = r.nextInt(n);
       ballSpeed[i] = (r.nextInt(16)-8)*w+ r.nextInt(16)-8;
       ballRadius[i] = 40 + r.nextInt(40);

    }
  }

  /**
   * Lasketaan taulukkoon yhden metapallon arvot.
   * Kaikki pallot pirretään skaalaamalla koordinaatit
   * tähän taulukkoon.
   */
  private void initMetaArray() {

    //luku, jolla kerrotaan kaavan tulokset
    //ja jota pienempiä arvoa ei sallita
    //(jotta kuvio pysyisi pyöreänä)
    int cutLevel = 4;

    for (int x = 0; x < 256; x++)
      for (int y = 0; y < 256; y++) {

	int dx = x-128;
	int dy = y-128;
	int D = dx*dx + dy*dy;
	double d;
	if (D != 0) {
	  d = (cutLevel*16384.0 / D); //c*128*128 / d^2

	  if (d > 255)
	    D = 255;
	  else if (d < cutLevel)
	    D = 0;
	  else
	    D = (int)d;

	}
	else
	  D = 255;

	metaArray[(y<<8) + x] =  D;
      }
  }

  /**
   * Piirretään ainoastaan jo valmiiksi laskettu kuva
   */
  public void paint(Graphics g) {
    g.drawImage(screenImage,0,0,this);
  }

  public void update(Graphics g) {
    paint(g);
  }


  /**
   * Renderointilooppi. Piirtää yhden kuvan muistipuskuriin.
   */
  private final void render() {


    //tyhjennetään ruutu
    Arrays.fill(screen, 0);

    for (int i = 0; i < BALLS; i++) {

      int r = ballRadius[i];

      //pallojen liikutus
      ballXY[i] -= ballSpeed[i];
      if (ballXY[i] < -r*w)
	ballXY[i] += w*(h+r);
      else if (ballXY[i] > (h+r)*w)
	ballXY[i] -= w*(h+r);

      int bY = ballXY[i] / w;
      int bX = ballXY[i] % w;

      int minY = bY-r+1;
      if (minY < 0) minY = 0;
      int maxY = bY+r-1;
      if (maxY >= h) maxY = h-1;
      int minX = bX-r+1;
      if (minX < 0) minX = 0;
      int maxX = bX+r-1;
      if (maxX >= w) maxX = w-1;

      int rowStart = minY*w;
      int rowMinX = minX;

      //lasketaan rajat taulukkoon sopivaksi
      //(jaetaan lisäksi säteellä ja lisätään 128
      // niin saadaan arvoja 0-255)
      minX = (minX - bX) << 7;
      maxX = (maxX - bX) << 7;

      minY = (minY - bY) << 7;
      maxY = (maxY - bY) << 7;


      int offs;

      // Pääasiallinen laskentalooppi
      for (int yDiff = minY; yDiff < maxY; yDiff+=128) {
	offs = rowStart+rowMinX;
	int mbY  =
	  ((128 - yDiff/r) //skaalataan etäisyys taulukkoon sopivaksi
	   << 8 )     // Y on taulukon rivi (*256)
	  +128;       // X valmiiksi puoleenväliin

	for (int xDiff = minX; xDiff < maxX; xDiff+= 128) {
	  screen[offs++ ] += metaArray[mbY + xDiff/r];
	}

	rowStart += w;
      }

    }

    // piirretään lopullinen kuva ruudulle
    int n = w*h;
    for (int i = 0; i < n; i++) {
      int x  = screen[i];
      if (x > 255)
	x = 255;
      else if (x < 30)
	x = 0;

      screen[ i  ] = colorMap[x];
      //screen[ i  ] = 0xff000000 | x;
    }

  }


  public void keyPressed(KeyEvent e) {
     if (e.getKeyCode() == e.VK_ESCAPE)
      System.exit(0);
  }
  public void keyReleased(KeyEvent e) {}
  public void keyTyped(KeyEvent e) {}

  /**
   * Laskee seuraavan kuvan
   * ja ilmoittaa, että uusi kuva
   * olisi tarjolla.
   */
  public void nextFrame() {
    render();
    mis.newPixels(0, 0, w, h);
  }


  public static void main(String[] args) {

    MetaBalls mb = new MetaBalls();
    JFrame frame = new JFrame("MetaBalls");
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    frame.setSize(mb.w,mb.h);
    frame.getContentPane().add(mb);

    GraphicsEnvironment env = GraphicsEnvironment.
      getLocalGraphicsEnvironment();
    GraphicsDevice device = env.getDefaultScreenDevice();

    DisplayMode[] modes = device.getDisplayModes();
    try {
      //vaihdettaan 320x200 fullscreen moodiin, jos sitä tuetaan
      for (int i = 0; i < modes.length; i++) {
	if (modes[i].getWidth() == 320
	    &&modes[i].getHeight() == 200
	    &&modes[i].getBitDepth() == 32) {

	  frame.setUndecorated(true);
	  device.setFullScreenWindow(frame);
	  device.setDisplayMode(modes[i]);
	  break;
	}
      }

      frame.show();

      //focus sisäkomponentille, jolla on näppänhandleri
      mb.requestFocus();

      Thread.currentThread().setPriority(Thread.MIN_PRIORITY);


      //päivityslooppi
      while(frame.isShowing()) {
	mb.nextFrame();

	try {
	  Thread.sleep(1);
	}catch(Exception e) {return;}

      }

    }
    finally { //pois fullscreenistä
      device.setFullScreenWindow(null);
    }
  }
}

tsuriga [23.08.2004 16:42:45]

#

Varsin kauniin näköinen efekti, ja suht. nopeakin vielä. Voisitko kommentoida jonkun seuraavanlaisen pätkän:

colorMap[i] = 0xff000000 | (((int)(( i * r )/128.0))<<16);

Itselleni on hieman epäselvää, mitä tuo vaiheittain tekee.

Sami [23.08.2004 17:43:34]

#

Toimii tosiaan ihan kivalla nopeudella. Oma kone pyörittää 200 palloa ilman mitään ongelmia, ja tuhannella pallollakin yhden kuvan renderöinti kestää n. 150 millisekuntia (vaikka tulos onkin melko tylsän yksivärinen...)

kaviaari [23.08.2004 20:11:22]

#

Aikas nätti

FooBat [23.08.2004 21:12:34]

#

colorMap[i] = 0xff000000 | (((int)(( i * r )/128.0))<<16);

Tuossa lasketaan punavärin liuku 0-255.

"0xff000000" on tuon värimallin vaatima alpha-arvo eli läpinäkyvyys (asetataan maksimiin eli ei läpinäkyvyyttä)

"<< 16" siirtää sisemmässä lausekkeessa lasketun arvon punavärin paikkaan rgb-arvossa.

"i * r / 128.0"
tuossa i saa arvot 0-127 ja r = 255, joten punainen värikomponentti saa arvoja 0-255.

Tuo ei kuitenkaan ole kovin kriittinen asia koodissa. Kyseinen väripaletti lasketaan vain kerran ohjelman alussa ja väripalettina voi käyttää mitä palettia vaan.

Yleisesti olen joka paikassa pyrkinyt korvaamaan kaikki jako ja kertolaskut shift operaatioilla, koska ne vaan ovat niin paljon nopeampia. Eli vasemmalle shiftaus (<<) kertoo integer lukua jollakin 2:n potenssilla ja oikealle shiftaus (>>) jakaa samaisella luvulla.

tsuriga [23.08.2004 22:12:23]

#

Kiitoksia selityksistä. Itse en ole vielä tuohon shiftaukseen pahemmin tutustunut. Löytyisikö noiden värien määrityksestä jotain URLia, kun nuo ovat ilmeisesti jonkin yleisen säännön mukaan määritelty? Tuossa "i * r / 128.0" suurin arvo taitaa olla 253(?), mutta sillä nyt ei liene niin kovin suurta väliä :).

EDIT: Nyt meni hieman keskusteluksi, mutta annan tämän viestin kuitenkin olla, jotta yhteys seuraavaan vastaukseen säilyisi muillekin lukijoille selvänä.

FooBat [23.08.2004 22:22:43]

#

Yleisesti argb arvot esitetään integereinä siten, että ylimmät 8 bittiä ovat alpha arvoa, seuraavat 8 puna-arvoa, sitten 8 bittiä vihreää ja 8 sinistä.

8 bittiä pystyy sisältämään lukuarvoja 0-255. Yleisesti esim. HTML-puolella väriarvot esitetään heksa-lukuna jolloin kunkin 8 bittiä pystyy esittämään kahdella heksamarkillä (00-FF).
Koko luku voidaankin esittää siis muodossa 0xAARRGGBB, jossa kirjainten paikalle asetetaan haluttu arvo (0x alussa on C:n ja Javan tapa ilmaista luku heksana).

True-color väreissä siis lasketaan jokaiselle värille luku 0-255 ja siten yhdistetään ne yhdeksi luvuksi.

(alpha<<24) | (red << 16) | (green << 8) | blue

Shiftaukset siirtävät luvun oikeaan paikkaan ja OR-operaatio yhdistää luvut bitti kerrallaan (bitti on 1, jos jommassa kummassa operaation luvussa kyseinen bitti on 1, muutoin 0).

Metabolix [24.08.2004 16:24:59]

#

Onko tuo Javassa erilailla kuin C:ssä vai oletko nyt hieman erehtynyt? Tässä on C-kääntäjän RGB-makro, joka siis luo väriarvoista yhtenäisen luvun.
#define RGB(r,g,b) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
Kuten näet, (r | (g << 8) | (b << 16) | (a << 24))

FooBat [24.08.2004 19:11:22]

#

C on vähän laiteläheisempi kieli kuin java ja noiden väritavujen järjestys riippuu siitä onko kone big endian vai
little endian eli onko merkitsevin bitti lopussa vai alussa. En ole kuitenkaan grafiikkaa viimeaikoina koodannut C:llä, joten en osaa tarkenpaa sanoa C:n toiminnasta.

Javassa asia kuitenkin on noin kuten tuossa esitin.

thefox [25.08.2004 11:18:03]

#

Metabolix: tuo nyt ei kuitenkaan ole C-kääntäjän makro, vaan windows.h:n. Eli itse C-kielen kanssa tuolla ei sinänsä ole tekemistä.

Fisher [22.09.2004 13:33:24]

#

Harmi, se käyttää Swingiä... Joillekin vanhoille kääntäjille extends Frame-jutut olisi mukavampia

Fisher [22.09.2004 13:33:30]

#

Harmi, se käyttää Swingiä... Joillekin vanhoille kääntäjille extends Frame-jutut olisi mukavampia

FooBat [03.10.2004 20:13:13]

#

lainaus:

Harmi, se käyttää Swingiä... Joillekin vanhoille kääntäjille extends Frame-jutut olisi mukavampia

Eipä se nyt kovin paljon swingiä käytä; JFrame taisi tulla vanhasta muistista. Tein toisen version, jossa käytetään Frame:a. http://www.niksula.cs.hut.fi/~jasainio/java/metaballs2.zip

Toisaalta vanhojen java-kääntäjien ja virtuaalikoneiden käyttämiseen ei ole kovin monia hyviä syitä. (MS:n bugisen virtuaalikoneen tukeminen ei ole hyvä syy :)

killerfox [14.10.2005 19:59:52]

#

Oon miettiny pääni puhki, että miten niitä palloja voisi lisätä VK_UP nappia painamalla yksitellen, ja vielä lisäksi ne seurais hiirtä. Jos tiedät miten se onnistuu niin voitko neuvoa :D Nuo ois kivoja lisiä, ehkä vähän monimutkaisempia, tuohon ohjelmaan.

FooBat [18.10.2005 19:42:37]

#

Tein tuollaisen esimerkin, jossa palloja voi lisäillä ja poistaa nuolinäppäimillä. Pallot nyt myös pörräävät hiiren ympärillä. Muistaakseni metapalloista oli ihan hyvä hiirtä seuraava esimerkki myös VB esimerkkien puolella.
http://www.niksula.cs.hut.fi/~jasainio/java/MB.zip


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta