Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3
Excerpt

Denne oppgaven handler om simulering av objekter i rommet, hvor en bruker arv både til rom-objektene og til rom-verdenen.

Merk: Denne oppgaven krever at tilleggspakken e(fx)clipse er installert! Hent lenke til update-site fra listen over tilleggspakker og installer e(fx)clipse. Tilleggspakkene kan finnes her: Eclipse-tillegg 

Simulering er en nyttig teknikk for å prøve ut ens forståelse av et fenomen, f.eks. fysikk. Enkelt sagt består en simulering av regler for hvordan objekter i en verden oppfører seg (både for seg selv og hvordan de spiller sammen), og en konkret verden i form av en start-konfigurasjon av objekter. Så tenker en seg at tiden går i små steg, og for hvert steg så oppdaterer en objektene i henhold til reglene.

...

intersects-metoden skal returnere true dersom to SpaceObject-instanser (this og argumentet) overlapper. Sjekk for overlapp kan gjøres på mange måter, f.eks. sjekke overlapp av såkalt "bounding box" (minste omsluttende rektangel) eller om midten og/eller hjørnene i det ene polygonet er inni det andre (og vice versa), eller en kombinasjon av flere av disse. contains-metoden BaseSpaceObject i er nyttig her. Enkle teknikker vil dekke mange relevante tilfeller, men for å ta alle hjørnetilfellene (wink) så må en være litt kløktig og kombinere flere typer teknikker. Figuren til høyre viser fire polygoner som har litt ulik type overlapp og tabellen bortenfor viser hvilke som overlapper (fasit for testkoden).

  • De blå, grønne og røde polygonene overlapper, og her vil en "bounding box"-test fungere. Men en slik test vil også gi overlapp med den gule, og det stemmer jo ikke.
  • En test for om hjørnene til den ene er inni den andre (og vice versa), vil fungere for de fleste, men ikke for den røde og grønne.
  • En test for om midt-punktet til den ene er inni den andre (og vice versa), vil fungere for de fleste, men ikke for den grønne og gule.

Det som altså fungerer best er en kombinasjon av alle disse teknikkene!

 Blå (so1)Grønn (so2)Rød (so3)Gul (so4)
Blåxxx 
Grønnxxxx
Rødxxx 
Gul x x
 

JExercise-testkode for Testkode for del 1 finner du her: inheritance/SpaceObjectTest.java. Originalkoden (jextest) for testen finner du her: inheritance/SpaceObject.jextest.

 

Del 2 - Oppsett av verden og simulering

PlantUML Macro
class "Pane (fra JavaFX)" as Pane {
}
class Asteroids {
}
Pane <|-- Asteroids
class SpaceObject {
}
Asteroids *-right-> "*" SpaceObject: spaceObjects

class Asteroids1 {
}
class Asteroids2 {
}
class Asteroids3 {
}

Asteroids <|-- Asteroids1
Asteroids <|-- Asteroids2
Asteroids2 <|-- Asteroids3

Denne delen tar utgangspunkt i en delvis ferdigskrevet Asteroids-klasse. Oppgaven til Asteroids-klassen er todelttodel: 1) å rigge opp verdenen med rom-objekter og 2) kjøre selve simuleringen. I koden for Asteroids-klassen, som ligger nederst på denne siden, legges det opp til at dette gjøres i henholdsvis init()- og run()-metoden, men du står forsåvidt fritt til å (gjen)bruke denne koden som du vil. Men for at du skal lære mest mulig om arv, så prøv å legge mest mulig av din egen kode i subklasser av Asteroids, heller enn å skrive den om. Vi foreslår at du gjør oppgaven i følgende trinn, med én subklasse pr. trinn.

Astroids-klassen har noen finesser, som kan nyttige ved feilsøking:

  • trykk '+' og '-' for zoome inn og ut
  • trykk ' ' (mellomrom) for å stoppe/starte simuleringen og '>' for å kjøre ett steg (tick)

Bruk du AsteroidsProgram som hovedprogramklasse. Du kan hvis du vil bruke  main-metoden til AsteroidsProgram, som tar tar inn navnet til Asteroids-(sub)klassen som eneste programargument. Det enkleste er kanskje å å lage en main-metode i din egen Asteroids-(sub)klasse (her kalt Asteroids1), slik:

Code Block
public static void main(String[] args) {
   AsteroidsProgram.main(new String[]{inheritance.Asteroids1.class.getName()}); // lager en String-tabell med klassenavnet som eneste element
}

Trinn 1 - Asteroids1

I dette trinnet er målet å få lagt inn noen SpaceObject-instanser, se (til) at simuleringen beveger dem på skjermen og få kollisjonsdeteksjon til å fungere.

SpaceObject-instansene opprettes i init()-metoden og legges inn med den ferdigskrevne add-metoden i Asteroids-klassen. For å få simuleringen til å virke, så må du fylle inn kode i tick()-metoden:

  • For hvert SpaceObject må du utføre ett simuleringstrinn.
  • For hvert SpaceObject-par må du sjekke for kollisjon, altså om de overlapper hverandre (se intersects-metoden i SpaceObject-klassen over).

Se http://stackoverflow.com/questions/345838/ball-to-ball-collision-detection-and-handling dersom du vil simulere kollisjoner som (fullstendig) elastiske støt.

Trinn 2 - Asteroids2

I dette trinnet er målet å få gravitasjonslogikken til å fungere og sjekke det ved å legge inn en sol (stor, gul og rund Asteroid-instans) og masse asteroider (Asteroid-instanser av med ulik tetthet og størrelse).

Utvid tick()-metoden slik at du for hvert SpaceObject-par beregner og håndterer den gjensidige gravitasjonskraften.

Trinn 3 - Asteroids3

I dette trinnet implementerer du støtte for å styre romskipet med piltastene (KeyCode.LEFTKeyCode.RIGHT og KeyCode.UP). Finn selv ut hvilke Asteroids-metoder som må redefineres, for å få dette til. Sørg for å legge til et romskip og prøv å unngå å kollidere eller å bli fanget av sola!

 

 

Code Block

...

titleFil-tre som må lastes ned evt. importeres med Digital Compendium-panelet
collapsetrue

...

emfs
/inheritance/ #java #package
	BaseSpaceObject.java			 	@ git@github.com:hallvard/jexercise/no.hal.jex.collection/src/inheritance/BaseSpaceObject.java;
        Asteroids.java			 	@ git@github.com:hallvard/jexercise/no.hal.jex.collection/src/inheritance/Asteroids.java;
        AsteroidsProgram.java			 	@ git@github.com:hallvard/jexercise/no.hal.jex.collection/src/inheritance/AsteroidsProgram.java;
        ;
/inheritance/ #java #package #test
        SpaceObjectTest.java                           @ git@github.com:hallvard/jexercise/no.hal.jex.collection/src-gen/inheritance/SpaceObjectTest.java;
        SpaceObject.jextest                           @ git@github.com:hallvard/jexercise/no.hal.jex.collection/tests/inheritance/SpaceObject.jextest;
languagejava
titleBaseSpaceObject
collapsetrue

 

 

Code Block
languagejava
titleBaseSpaceObject
package inheritance;

import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.shape.Polygon;

public class BaseSpaceObject extends Polygon {
    /**
     * Adds the point given by x,y to this Polygon
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    protected void addPoint(double x, double y) {
        getPoints().add(x);
        getPoints().add(y);
    }
    /**
     * Adds the points given by the sequence of x- and y-coordinates
     * @param xys the x- and y-coordinates
     */
    protected void addPoints(double... xys) {
        for (int i = 0; i < xys.length; i += 2) {
            addPoint(xys[i], xys[i + 1]);
        }
    }
    
    /**
     * Adds the point given by angle, length in polar coordinates
     * @param angle the angle
     * @param length the length
     */
    protected void addPolarPoint(double angle, double length) {
        addPoint(Math.cos(angle) * length, Math.sin(angle) * length);
    }
    
    /**
     * Returns the position of this Polygon as a Point2D object
     * @return the position as a Point2D object
     */
    public Point2D getPosition() {
        return new Point2D(getTranslateX(), getTranslateY());
    }
    /**
     * Moves (displaces) this Polygon by the given dx, dy
     * @param dx the x displacement
     * @param dy the y displacement
     */
    public void translate(double dx, double dy) {
        setTranslateX(getTranslateX() + dx);
        setTranslateY(getTranslateY() + dy);
    }
    
    /**
     * Returns the position of the center of this Polygon.
     * The asParentCoordinates determines if the position is transformed into the parent coordinate system.
     * @param asParentCoordinates determines if the position is transformed into the parent coordinate system
     * @return the center position
     */
    public Point2D getCenter(boolean asParentCoordinates) {
        Bounds bounds = getBoundsInLocal();
        Point2D center = new Point2D((bounds.getMaxX() + bounds.getMinX()) / 2, (bounds.getMaxY() + bounds.getMinY()) / 2);
        if (asParentCoordinates) {
            center = localToParent(center);
        }
        return center;
    }
    /**
     * Returns the number of points in this Polygon
     * @return the number of points in this Polygon
     */
    public int getPointCount() {
        return getPoints().size() / 2;
    }
    /**
     * Determines of a specific point of another Polygon is inside this Polygon
     * @param other the other Polygon
     * @param pointNum the number of the point to check
     * @return
     */
    public boolean contains(BaseSpaceObject other, int pointNum) {
        ObservableList<Double> points = other.getPoints();
        double x = points.get(pointNum * 2), y = points.get(pointNum * 2 + 1);
        return this.contains(parentToLocal(other.localToParent(x, y)));
    }
}
Code Block
languagejava
titleAsteroids
collapsetrue
package inheritance;

import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.util.Duration;

public class Asteroids extends Pane implements EventHandler<KeyEvent>  {

	private List<SpaceObject> spaceObjects;
    
    protected void add(SpaceObject so, double x, double y) {
        so.translate(x, y);
        getChildren().add(so);
        spaceObjects.add(so);
    }
    public void init() {
        spaceObjects = new ArrayList<SpaceObject>();
    }

	public void run() {
		// setter opp simuleringstakt
        Timeline tickTimer = new Timeline(new KeyFrame(Duration.millis(50), new EventHandler<ActionEvent>() {
    		private int tickCount = 0;
            @Override
            public void handle(ActionEvent event) {
                tick(tickCount++);
            }
        }));
        tickTimer.setCycleCount(Timeline.INDEFINITE);
        tickTimer.play();
        requestFocus();
        setOnKeyPressed(this);
        setOnKeyTyped(this);
    }

	// kalles når spesialtaster som pilene trykkes
	protected void handleKey(KeyCode keyCode) {
    }

	/*
     * Used for pausing or stepping
     */
    private int steps = -1;
    
    protected void handleKey(String character) {
        if ("+".equals(character)) {
            zoom(2);
        } else if ("-".equals(character)) {
            zoom(0.5);
        } else if (" ".equals(character)) {
            // toggle steps
            steps = (steps < 0 ? 0 : -1);
        } else if (">".equals(character)) {
            // one step
            steps = 1;
        }
    }

	private void zoom(double factor) {
        setScaleX(getScaleX() * factor);
        setScaleY(getScaleY() * factor);
    }
    
    @Override
    public void handle(KeyEvent keyEvent) {
        if (keyEvent.getCode() == KeyCode.UNDEFINED) {
            handleKey(keyEvent.getCharacter());
        } else {
            handleKey(keyEvent.getCode());
        }
    }

	private void tick(int tickCount) {
        
        if (steps == 0) {
            return;
        } else if (steps > 0) {
            steps--;
        }

		// accelerate due to gravity

		// lag en (dobbel) løkke her, som
		// håndterer gravitasjonskraften for alle par i spaceObjects-lista 

        // move all objects
        for (SpaceObject spaceObject : spaceObjects) {
            spaceObject.tick();
        }

        // check for collision
		// lag en (dobbel) løkke her, som
		// sjekker for og håndterer kollisjoner for alle par i spaceObjects-lista
    }

	public void handleGravity(SpaceObject spaceObject1, SpaceObject spaceObject2) {
		// regn ut avstanden mellom spaceObject1 og spaceObject2 (bruk getCenter-metoden),
		// beregn den gjensidige gravitasjonskraften basert på formelen
		// masse1 * masse2 / avstand ^ 2, og
		// påfør kraften med SpaceObject.applyForce-metoden
    }

	//

	public static void main(String[] args) {
   		AsteroidsProgram.main(new String[]{inheritance.Asteroids.class.getName()}); // lager en String-tabell med klassenavnet som eneste element
	}
}
Code Block
languagejava
titleAsteroidsProgram
collapsetruetitleAsteroidsProgram
package inheritance;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class AsteroidsProgram extends Application {
    private int width = 800, height = 600;
    @Override
    public void start(Stage stage) throws Exception {
        String paneClassName = getParameters().getRaw().get(0);
        final Asteroids asteroidsPane = (Asteroids) Class.forName(paneClassName).newInstance();
        asteroidsPane.setPrefSize(width, height);
        Scene scene = new Scene(asteroidsPane, width, height, Color.BLACK);
        stage.setScene(scene);
        stage.show();
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                asteroidsPane.init();
                asteroidsPane.run();
            }
        });
    }
    
    public static void main(String[] args) {
        launch(AsteroidsProgram.class, args);
    }
}

Exercise-panelet

Bruk av Exercise-panelet er obligatorisk for denne øvingen. Du må ha panelet åpent med Asteroids.ex-filen (tests > inheritance > Asteroids.ex) i før du begynner med oppgaven. For mer informasjon/hjelp, se nederst på forrige side, altså hovedsiden for Øving 9.

 

Include Page
Bunntekst for oppgaver
Bunntekst for oppgaver