Startseite < Informatik < Algorithmen Datenstrukturen / Software-Engineering / Programmiersprachen < Compiler Interpreter < Setty Tinyray < Tinyray-Scanner Tinyray-Parser / Tinyray-Language Tinyray-Raytracer < Tinyray-RaytracerKit [ DRUCK , 2004 , 2005 , 2006 , 2007 , 2008 , 2009 ] Tinyray-Elements Tinyray-Viewport > / Tinyray-Wallpapers > > / Java C/C++ POV-Ray LaTeX > / Künstliche Intelligenz > Schach Privates / Inhalt >
Tinyray-Elements
Die Knoten des Tinyray-Raytracers wie beispielsweise die Szenenknoten.
Einführung Allgemeines zu den Knoten des Raytracers
Die Knoten des Syntaxbaumes, die der Tinyray-Parser mit Hilfe des Tinyray-RaytracerKit erzeugt, sind von der abstrakten Klasse Element abgeleitet. Diese Knoten stellen alle Methoden zur Verfügung, die der Tinyray-Raytracer benötigt.

Die Wurzel (Tinyray-Knoten) des Syntaxbaumes ist der Raytracer selbst; die Kindknoten sind die Szenenobjekte.
Standalone
Eigentlich benötigt vorliegender Raytracer keinen
  1. Scanner,
  2. Parser und
  3. RaytracerKit.
Es werden lediglich vorliegende Knoten-Elemente und der Tinyray-Viewport benötigt, um 3D-Szenen rendern zu können.

Man müsste lediglich die 3D-Szene in Java anstatt mit Tinyray implementieren.

Im Folgenden sehen Sie den Raytracer im Einsatz ohne Interpreter-Frontend, d. h. ohne Parser, ohne Scanner, ohne ParseTreeKit bzw. RaytracerKit.
Standalone.java
package tinyray.examples;

import java.io.IOException;

import tinyray.raytracer.Sphere;
import tinyray.raytracer.Sun;
import tinyray.raytracer.Tinyray;
import tinyray.raytracer.Vector3D;
import tinyray.raytracer.Viewport;


public class Standalone {

    // Die 3D-Szene generieren, was sonst der Parser machen würde.
    private static Tinyray helloWorldScene() {
        // Tinyray { ... }
        Tinyray result = new Tinyray();
        // Sphere { [0.0, 0.0, 0.0]; 2.0 }
        result.addGeo(new Sphere(new Vector3D(0.0), 2.0));
        // Sun { [1.0, 1.0, 1.0]; [1.0, 1.0, 1.0] }
        result.addSun(new Sun(new Vector3D(1.0), new Vector3D(1.0)));
        return result;
    }

    // Das Hauptprogramm.
    public static void main(String[] arguments) throws IOException {

        // 3D-Szene zuweisen.
        Tinyray tinyray = helloWorldScene();

        // Ausgabebild initialisieren.
        Viewport viewport = new Viewport(320, 240);

        // Ausgabebild rendern.
        tinyray.render(viewport, 1, 6);

        // Ausgabebild speichern.
        viewport.storeAsPNG("Standalone.png");

        // Der Raytracer braucht also keinen Parser usw.
    }
}
Standalone.png
Hallo Welt Szene - von Standalone.java
Dieses Bild wurde von Standalone.java generiert.

Dabei sei noch einmal darauf hingewiesen, dass vorliegendes Bild lediglich mit den Knoten-Elementen des Raytracers und mit dem Viewport gerendert wurde: ohne Parser, ohne Kit, ohne Scanner und ohne Tokens.
Zum Vergleich das entsprechende „Hallo Welt”-Programm mit Tinyray, welches mir persönlich besser gefällt. :-)
Hallo Welt!
// Hallo Welt!

Tinyray {
    Sphere {
        [0, 0, 0]; // Wo liegt die Kugel?
        2.0        // Welchen Radius besitzt die Kugel?
    }
    Sun {
        [1, 1, 1]; // In welcher Richtung befindet sich die Sonne?
        [1, 1, 1]  // Welche Farbe hat die Sonne?
    }
}
Implementierung Implementierung der Knoten des RaytracerKit
Im Folgenden sind die Knoten-Elemente des Raytracers implementiert.
Element.java
package tinyray.raytracer;

/**
 * Die Klasse Element ist die abstrakte Basisklasse des Raytracers.
 * Eigentlich wäre hier ein Interface, anstatt einer abstrakten Klasse
 * angebracht, aber Geschwindigkeitstest mit dem Raytracer haben ergeben,
 * dass seltsamerweise die abstrakte Klasse als Basisklasse für die Knoten
 * des Syntaxbaumes den Raytracer schneller macht.
 */

public abstract class Element {}
Tinyray.java
package tinyray.raytracer;

import java.util.Iterator;
import java.util.LinkedList;

/**
 * Die Klasse Tinyray repräsentiert das Tinyray-Programm als Raytracer.
 */

public class Tinyray extends Element {

    // Die globale Kamera.
    private Camera camera;

    // Der Hintergrund (die Leinwand).
    private Background background;

    // Das globale Umgebungslicht.
    private Ambience ambience;

    // Der globale Nebel.
    private Fog fog;

    // Die geometrischen Objekte der Szene.
    private LinkedList<Geo> geos;

    // Die Sonnen als Lichtquellen der Szene.
    private LinkedList<Sun> suns;

    /**
     * Initialisiert den Raytracer.
     */

    public Tinyray() {
        super();
        this.camera = new Camera();
        this.background = new Background(new Vector3D(0.0));
        this.ambience = new Ambience(new Vector3D(1.0));
        this.fog = new Fog(new Vector3D(1.0), 0.0);

        this.geos = new LinkedList<Geo>();
        this.suns = new LinkedList<Sun>();
    }

    /**
     * Gibt die Kamera des Raytracers zurück.
     * @return Die Kamera.
     */

    public Camera getCamera() {
        return this.camera;
    }

    /**
     * Setzt die Kamera des Raytracers.
     * @param camera Die Kamera.
     */

    public void setCamera(Camera camera) {
        this.camera = camera;
    }

    /**
     * Gibt den Hintergrund des Raytraces zurück.
     * @return Der Hintergrund.
     */

    public Background getBackground() {
        return this.background;
    }

    /**
     * Setzt den Hintergrund des Raytracers.
     * @param background Der Hintergrund.
     */

    public void setBackground(Background background) {
        this.background = background;
    }

    /**
     * Gibt die globale Ambienz des Raytracers zurück.
     * @return Die globale Ambienz.
     */

    public Ambience getAmbience() {
        return this.ambience;
    }

    /**
     * Setzt die globale Ambienz des Raytracers.
     * @param ambience Die globale Ambienz.
     */

    public void setAmbience(Ambience ambience) {
        this.ambience = ambience;
    }

    /**
     * Gibt den Nebel des Raytracers zurück.
     * @return Der Nebel.
     */

    public Fog getFog() {
        return this.fog;
    }

    /**
     * Setzt den Nebel des Raytracers.
     * @param fog Der Nebel.
     */

    public void setFog(Fog fog) {
        this.fog = fog;
    }

    /**
     * Gibt die Geoobjekte der Szene zurück.
     * @return Die Geoobjekte.
     */

    public Iterator<Geo> getGeos() {
        return this.geos.iterator();
    }

    /**
     * Fügt der Szene ein Geoobjekt hinzu.
     * @param geo Das Geoobjekt.
     */

    public void addGeo(Geo geo) {
        this.geos.add(geo);
    }

    /**
     * Gibt die Sonnen der Szene zurück.
     * @return Die Sonnen.
     */

    public Iterator<Sun> getSuns() {
        return this.suns.iterator();
    }

    /**
     * Fügt der Szene eine Sonne hinzu.
     * @param sun Die Sonne.
     */

    public void addSun(Sun sun) {
        this.suns.add(sun);
    }

    /**
     * Gibt den Raytracer als Tinyray-Code zurück.
     */

    public String toString() {
        String result = "Tinyray {";
        result += "\n    " + this.getCamera();
        result += "\n    " + this.getBackground();
        result += "\n    " + this.getAmbience();
        result += "\n    " + this.getFog();
        Iterator<Geo> iterGeos = this.getGeos();
        while (iterGeos.hasNext()) {
            result += "\n    " + iterGeos.next();
        }
        Iterator<Sun> iterSuns = this.getSuns();
        while (iterSuns.hasNext()) {
            result += "\n    " + iterSuns.next();
        }
        result += "\n}";
        return result;
    }

    /**
     * Rendert die Szene des Raytracers und gibt das resultierende Bild auf
     * den Viewport aus. Trifft ein Strahl (Ray) auf die Oberfläche eines
     * Geoobjektes der Szene, so prallt dieser wieder ab. Die Tiefe legt fest,
     * wie oft ein Strahl maximal abprallen kann.
     * Damit die Szene nicht so pixelig wirkt, wird jedes Pixel des Viewports
     * in ein quadratisches Raster unterteilt, damit gleich mehrere Strahlen
     * durch ein Pixel geschossen, geschickt oder gelegt werden können.
     * @param viewport Das resultierende Bild.
     * @param maxdepth Die Tiefe.
     * @param antialias Die Rasterbreite bzw. -höhe in einem Pixel.
     */

    public void render(Viewport viewport, int maxdepth, int antialias) {

        // Zusichern, dass antialias mindestens den Wert 1 hat.
        antialias = Math.max(1, antialias);

        // viewDir = normalize(cam.LookAt - cam.Location).
        Vector3D viewDir = Vector3D.sub(this.camera.getLookAt(),
                            this.camera.getLocation()).getNormalized();

        // viewRight = viewDir x cam.Up.
        Vector3D viewRight = Vector3D.cross(viewDir, this.camera.getUp());

        // viewUp = viewRight x viewDir.
        Vector3D viewUp = Vector3D.cross(viewRight, viewDir);

        // Breite des Viewport als Double.
        double viewportWidth = (double)viewport.getWidth();
        // Höhe des Viewport als Double.
        double viewportHeight = (double)viewport.getHeight();

        // Field Of View mit 30 Grad.
        double wh = 2.0 * Math.tan(Math.PI / 180.0 * (30.0 / 2.0));

        // Höhe und Breite des virtuellen Bildes ins richtige Verhaeltnis
        // setzen (Schlagwort: aspectRatio).
        double width = wh;
        double height = wh;
        if (viewportWidth > viewportHeight) {
            width = viewportWidth / viewportHeight * height;
        } else {
            height = viewportHeight / viewportWidth * width;
        }

        // Rasterabstände des virtuellen Bildes ermitteln.
        double dy = height / viewportHeight;
        double dx = width / viewportWidth;

        /* Der eigentliche Rendervorgang. */

        double y = height / 2.0;

        for (int sy = 0; sy < viewport.getHeight(); sy++) {

            // Gibt die Renderinformation aus.
            if (this.renderLineInfo(sy, viewport.getHeight())) {
                return;
            }

            double x = -width / 2.0;

            for (int sx = 0; sx < viewport.getWidth(); sx++) {

                LinkedList<Vector3D> colors = new LinkedList<Vector3D>();

                // Erzeugt in Abhängigkeit von antialias:
                // (antialias * antialias)-viele Strahlen pro Pixel,
                // wobei jeder Strahl in eine leicht andere Richtung zeigt.
                for (int ai = 0; ai < antialias; ai++) {
                    for (int aj = 0; aj < antialias; aj++) {

                        double di = dx * (double)ai / (double)antialias;
                        double dj = dy * (double)aj / (double)antialias;

                        // Richtung des jeweiligen Strahles.
                        Vector3D dir = Vector3D.add(
                                viewDir,
                                Vector3D.add(
                                        Vector3D.mult(+ di, viewRight),
                                        Vector3D.mult(+ dj, viewUp)
                                )
                        );

                        // Erzeugt einen Strahl.
                        Ray theRay = new Ray(this, this.camera.getLocation(),
                                dir.getNormalized(), 0, maxdepth);

                        // Berechnet den Farbwert des Strahles.
                        colors.add(theRay.getColor());
                    }
                }

                // Berechnet den Farbwert des Pixels.
                Vector3D color = Vector3D.ZERO;
                Iterator<Vector3D> iterator = colors.listIterator();
                while (iterator.hasNext()) {
                    color = Vector3D.add(color, iterator.next());
                }
                int raysPerPixel = antialias * antialias;
                color = Vector3D.mult(1.0 / (double)raysPerPixel, color);

                // Setzt den Farbwert des Pixels im Viewport.
                viewport.setPixel(sx, sy, color);

                x += dx;
            }
            y -= dy;
        }

        // Zu guter Letzt noch die Renderinformation ausgeben.
        this.renderLineInfo(viewport.getHeight(), viewport.getHeight());
    }

    /**
     * Gibt eine kurze Information über den Rendervorgang auf der Konsole aus.
     * Bevor eine Zeile gerendert wird, sollte dem Anwender eine Fortschritts-
     * information des Rendervorgangs angezeigt werden.
     * @param line Die aktuelle Zeile.
     * @param height Die Anzahl der zu rendernden Zeilen.
     * @return true, falls der Rendervorgang abgebrochen werden soll.
     *         Hier wird der Rendervorgang nie abgebrochen, es wird stets
     *         falls zurückgegeben. Die Rückgabe von true, wäre beispielsweise
     *         interessant, wenn man den Rendervorgang vorzeitig abbrechen
     *         möchte.
     */

    protected boolean renderLineInfo(int line, int height) {
        System.out.print("Reihe " + line + " von " + height);
        System.out.print(" (" + (line * 100 / height) + "%)");
        System.out.print("                         \r");
        if (line == height) {
            System.out.print("Rendering beendet!");
            System.out.println("                         ");
        }
        return false;
    }

    /**
     * Die innere Klasse Ray stellt einen Strahl, der durch die Szene
     * geschickt werden kann, dar.
     */

    public final class Ray {

        // Das Tinyray-Programm, welches die Szene kapselt.
        private Tinyray tinyray;

        // Der Ursprung des Strahles.
        private Vector3D origin;

        // Die Richtung des Strahles.
        private Vector3D direction;

        // Die aktuelle Rekursionstiefe.
        private int depth;

        // Die maximale Rekursionstiefe.
        private int maxdepth;

        /**
         * Initialisiert den Strahl.
         * @param tinyray Das dazugehörige Tinyray-Programm.
         * @param origin Der Ursprung des Strahles.
         * @param direction Die Richtung des Strahles.
         * @param depth Die aktuelle Rekursionstiefe.
         * @param maxdepth Die maximale Rekursionstiefe.
         */

        public Ray(Tinyray tinyray, Vector3D origin, Vector3D direction,
                int depth, int maxdepth) {
            this.tinyray = tinyray;
            this.origin = origin.copy();
            this.direction = direction.copy();
            this.depth = depth;
            this.maxdepth = maxdepth;
        }

        /**
         * Gibt den Ursprung des Strahles zurück.
         * @return Der Ursprung.
         */

        public Vector3D getOrigin() {
            return this.origin;
        }

        /**
         * Gibt die Richtung des Strahles zurück.
         * @return Die Richtung.
         */

        public Vector3D getDirection() {
            return this.direction;
        }

        /**
         * Berechnet den Farbwert, den der Strahl besitzt, wenn dieser durch
         * die Szene geschickt wird.
         * @return Der teuer berechnete Farbwert.
         */

        public Vector3D getColor() {

            Vector3D result = Vector3D.ZERO; // Schwarze Farbe

            // Gewinner feststellen
            Geo closest = null;
            double minDist = Double.MAX_VALUE;

            Iterator<Geo> geos = this.tinyray.getGeos();
            while (geos.hasNext()) {
                Geo geo = geos.next();
                Geo.Distance geoDistance = geo.getIntersect(this);
                if (geoDistance != null) {
                    double distance = geoDistance.getDistance();
                    if ((0.0 < distance) && (distance < minDist)) {
                        minDist = distance;
                        closest = geoDistance.getGeo();
                    }
                }
            }

            if (closest == null) {

                if (this.depth <= 0) {
                    result = this.tinyray.getBackground().getColor();
                }

            } else if (this.maxdepth < 0) {

                // Ray-Casting, falls die maximale Tiefe negativ ist.
                result = closest.getParameters().getColor();

            } else {

                /* Ray-Tracing */

                Vector3D intersectionPosition = Vector3D.add(
                        this.origin,
                        Vector3D.mult(minDist, this.direction)
                );
                Vector3D normal = closest.getNormal(intersectionPosition);

                // Berechnet den vom getroffenen Objekt reflektierten Strahl.
                Ray reflectedRay = new Ray(this.tinyray, intersectionPosition,
                        this.direction.getReflectedAt(normal).getNormalized(),
                        this.depth + 1, this.maxdepth);

                // Ermittelt in Abhängigkeit jeder Sonne einen Farbwert.
                Iterator<Sun> suns = this.tinyray.getSuns();
                while (suns.hasNext()) {
                    Sun sun = suns.next();
                    // Nur wenn der Strahl selbst nicht zur Sonne zeigt, wird
                    // die Farbe berechnet.

                    // Liegt zw. der Sonne und dem Geoobjekt ein Hindernis?
                    Ray rayOfSun = new Ray(this.tinyray, Vector3D.add(
                            intersectionPosition, Vector3D.mult(
                                    Vector3D.EPSILON, sun.getDirection())),
                            sun.getDirection(), 0, this.maxdepth);
                    boolean somethingIntersected = false;
                    Iterator<Geo> geos_sun = this.tinyray.getGeos();
                    while (geos_sun.hasNext()) {
                        Geo geo = geos_sun.next();
                        Geo.Distance distance = geo.getIntersect(rayOfSun);
                        if (distance != null) {
                            if (distance.getDistance() > 0.0) {
                                somethingIntersected = true;
                                break;
                            }
                        }
                    }
                    // Falls kein Hindernis zw. Sonne und dem getroffenen
                    // Geoobjekt (closest) vorhanden ist, dann wird auf den
                    // aktuellen Farbwert ein weiterer Farbwert in
                    // Abhängigkeit der aktuellen Sonne hinzuaddiert.
                    if (!somethingIntersected) {
                        result = Vector3D.add(
                                result,
                                this.getPhongLightingColor(reflectedRay,
                                        normal, closest, sun)
                        );
                    }
                }

                // Wenn nun alle Sonnen durchiteriert sind, wird noch das
                // globale Umgebungslichtfarbe mit der lokalen, ambienten
                // Farbe multipliziert, und schließlich zum aktuellen
                // Farbwert hinzuaddiert.
                result = Vector3D.add(
                        Vector3D.mult(
                                this.tinyray.getAmbience().getColor(),
                                closest.getParameters().getAmbient()
                        ),
                        result
                );

                // Spiegelung mit Rekursion
                if (this.depth < this.maxdepth) {
                    result = Vector3D.add(
                            result,
                            Vector3D.mult(
                                    closest.getParameters().getMirror(),
                                    reflectedRay.getColor()
                            )
                    );
                }
            }
            // Fog-Berechnung
            if (this.tinyray.getFog().getDensity() != 0.0) {
                Vector3D fogColor = this.tinyray.getFog().getColor();
                if (minDist >= 0.0) {

                    // Nebeldichte.
                    double d = this.tinyray.getFog().getDensity();
                    // Exponentieller (EXP2) Nebelwert.
                    double f = Math.exp(-(minDist * d) * (minDist * d));
                    f = Math.min(Math.max(f, 0.0), 1.0);
                    result = Vector3D.add(
                            Vector3D.mult(f, result),
                            Vector3D.mult(1.0 - f, fogColor)
                    );

                } else {
                    result = fogColor;
                }
            }
            // Endlich die Rückgabe des Farbwertes zum Strahl, der durch die
            // Szene geschickt wurde.
            return result;
        }

        // Farbberechnung mittels Phong-Beleuchtungsmodell.
        private Vector3D getPhongLightingColor(Ray reflectedRay,
                Vector3D normal, Geo geo, Sun sun) {

            Vector3D result = Vector3D.ZERO;

            /* Diffusen Farbwert berechnen (diffuse). */

            double diffuseFactor = Math.max(
                    0.0,
                    Vector3D.dot(sun.getDirection(), normal.getNormalized())
            );

            Vector3D diffuse = Vector3D.mult(
                    diffuseFactor,
                    geo.getParameters().getColor()
            );

            /* Die Lichtreflection berechnen (specularity).
             *
             * Das Herzstück des Phong-Beleuchtungsmodells,
             * die Spiegelung der Lichtquelle im geometrischen Objekt.
             * Je besser das Licht ins Auge des Betrachters trifft,
             * desto heller (perfekter) wird die Lichtspiegelung.
             */


            Vector3D sunRefl = sun.getDirection().getReflectedAt(normal);
            Vector3D eyeRefl = Vector3D.sub(
                    reflectedRay.origin,
                    this.origin
            );
            double sun_eye = Math.max(
                    0.0,
                    Vector3D.dot(
                            sunRefl.getNormalized(),
                            eyeRefl.getNormalized())
            );

            double exponent = Math.pow(
                    sun_eye,
                    geo.getParameters().getShininess()
            );

            double specular = geo.getParameters().getSpecular() * exponent;

            Vector3D specularity = Vector3D.mult(specular, sun.getColor());

            /* Zusammenführen der Farbanteile (diffuse und specularity). */

            result = Vector3D.add(diffuse, specularity);

            // Natürlich hängt der resultierende Farbwert von der Farbe
            // der Sonne ab.
            result = Vector3D.mult(sun.getColor(), result);

            return result;
        }
    }
}
Geo.java
package tinyray.raytracer;

/**
 * Die Klasse Geo ist die abstrakte Basisklasse für beliebige geometrische,
 * dreidimensionale Objekte in der Szene (3D-Welt).
 */

public abstract class Geo extends Element {

    // Die allgemeinen Parameter eines geometrischen Objekts.
    private Parameters parameters;

    /**
     * Initialisiert das Geoobjekt mit den Parametern.
     */

    public Geo() {
        super();
        this.parameters = new Parameters();
    }

    /**
     * Gibt die Parameter des Geoobjekts zurück.
     * @return Die Parameter.
     */

    public Parameters getParameters() {
        return this.parameters;
    }

    /**
     * Setzt die Parameter des Geoobjekts.
     * @param parameters Die Parameter.
     */

    public void setParameters(Parameters parameters) {
        this.parameters = parameters.copy();
    }

    /* Abstrakte Methoden */

    /**
     * Gibt den Normalenvektor, der Vektor, der senkrecht zu Oberfläche
     * steht, zurück. Da die Oberfläche eines Geoobjektes auch gekrümmt
     * sein kann und es viele, unterschiedliche Normalenvektoren zum Geoobjekt
     * geben kann, wird die Normale bezüglich eines Oberflächenpunkts
     * berechnet.
     * @param pointOfSurface Der Oberflächenpunkt.
     * @return Der Normalenvektor, welcher auf die Länge 1 normalisiert ist.
     */

    public abstract Vector3D getNormal(Vector3D pointOfSurface);

    /**
     * Gibt die Entfernung zurück, die ein Strahl zurücklegen muss,
     * um das Geoobjekt zu schneiden.
     * @param ray Der Strahl.
     * @return Die Entfernung.
     */

    public abstract Geo.Distance getIntersect(Tinyray.Ray ray);

    /**
     * Gibt die Min-Werte des Geoobjekts zurück.
     * Ein Geoobjekt beansprucht einen bestimmten Bereich in der 3D-Welt,
     * dieser Bereich kann mit einer sogenannten Boundingbox umhüllt werden.
     * Um eine Boundingbox erzeugen zu können, benötigt man mindestens zwei
     * Eckpunkte, die Min-Werte stellen einen Eckpunkt der dazugehörigen
     * Boundingbox dar.
     * @return Die Min-Werte.
     */

    public abstract Vector3D getMinValues();

    /**
     * Gibt die Max-Werte des Geoobjekts zurück.
     * Die Max-Werte stellen einen Eckpunkt der dazugehörigen
     * Boundingbox dar.
     * @return Die Min-Werte.
     */

    public abstract Vector3D getMaxValues();

    /**
     * Die Klasse Distance ist eine Hilfsklasse, mit der ein Entfernungswert
     * einem Geoobjekt zugeordnet werden kann.
     */

    public final class Distance {

        // Das Geoobjekt, dem eine Entfernung zugeordnet wird.
        private Geo geo;

        // Der Entfernungswert.
        private double distance;

        /**
         * Initialisiert die Entfernung meist vom Ursprung des Strahles
         * zu einem Geoobjekt.
         * @param geo Das Geoobjekt.
         * @param distance Die Entfernung.
         */

        public Distance(Geo geo, double distance) {
            this.geo = geo;
            this.distance = distance;
        }

        /**
         * Gibt das Geoobjekt der eine Entfernung zugeordnet wurde zurück.
         * @return Das Geoobjekt.
         */

        public Geo getGeo() {
            return this.geo;
        }

        /**
         * Gibt die Entfernung zu einem Geoobjekt zurück.
         * @return Die Entfernung.
         */

        public double getDistance() {
            return this.distance;
        }
    }
}
Triangle.java
package tinyray.raytracer;

/**
 * Die Klasse Geo repräsentiert ein Dreieck (geometrisches Objekt),
 * welche in eine Szene plaziert werden kann.
 */

public final class Triangle extends Geo {

    // Die drei Eckpunkte.
    private Vector3D x;
    private Vector3D y;
    private Vector3D z;

    // Hilfsvektor yx = y - x.
    private Vector3D yx;

    // Hilfsvektor zx = z - x.
    private Vector3D zx;

    // Hilfsvektor, die Normale zum Dreieck.
    private Vector3D normal;

    /**
     * Initialisiert das Dreieck mit drei Ortsvektoren.
     * Diese Ortsvektoren sind die Eckpunkte des Dreiecks.
     * @param x Eckpunkt.
     * @param y Eckpunkt.
     * @param z Eckpunkt.
     */

    public Triangle(Vector3D x, Vector3D y, Vector3D z) {
        super();
        this.= x.copy();
        this.= y.copy();
        this.= z.copy();

        this.yx = Vector3D.sub(this.x, this.y);
        this.zx = Vector3D.sub(this.x, this.z);

        // Die Normale muss nur einmal berechnet werden.
        this.normal = Vector3D.cross(this.yx, this.zx).getNormalized();
    }

    /**
     * Gibt den Normale des Dreiecks zurück.
     * Die Normale ist ein Vektor, der senkrecht zur Oberfläche steht.
     */

    public Vector3D getNormal(Vector3D v) {
        return this.normal;
    }

    /**
     * Gibt die Entfernung zurück, die ein Strahl zurücklegen muss,
     * um das Dreieck zu schneiden.
     */

    public Geo.Distance getIntersect(Tinyray.Ray ray) {

        Vector3D a = this.yx;
        Vector3D b = this.zx;
        Vector3D c = ray.getDirection();
        Vector3D p = Vector3D.sub(this.x, ray.getOrigin());

        double det1 = Vector3D.det(p, b, c);
        double det2 = Vector3D.det(a, p, c);
        double det3 = Vector3D.det(a, b, p);
        double detN = Vector3D.det(a, b, c);

        if (detN != 0.0) {

            double beta = det1 / detN;
            double gamma = det2 / detN;

            if ((0.0 < beta) && (0.0 < gamma) && (beta + gamma < 1.0)) {
                double distance = (det3 / detN) - Vector3D.EPSILON;
                return new Geo.Distance(this, distance);
            }
        }
        return null;
    }

    /**
     * Gibt die Min-Werte des Dreiecks zurück.
     */

    public Vector3D getMinValues() {

        double minX = Math.min(this.x.getX(), this.y.getX());
        minX = Math.min(minX, this.z.getX()) - Vector3D.EPSILON;

        double minY = Math.min(this.x.getY(), this.y.getY());
        minY = Math.min(minY, this.z.getY()) - Vector3D.EPSILON;

        double minZ = Math.min(this.x.getZ(), this.y.getZ());
        minZ = Math.min(minZ, this.z.getZ()) - Vector3D.EPSILON;

        return new Vector3D(minX, minY, minZ);
    }

    /**
     * Gibt die Max-Werte des Dreiecks zurück.
     */

    public Vector3D getMaxValues() {

        double maxX = Math.max(this.x.getX(), this.y.getX());
        maxX = Math.max(maxX, this.z.getX()) + Vector3D.EPSILON;

        double maxY = Math.max(this.x.getY(), this.y.getY());
        maxY = Math.max(maxY, this.z.getY()) + Vector3D.EPSILON;

        double maxZ = Math.max(this.x.getZ(), this.y.getZ());
        maxZ = Math.max(maxZ, this.z.getZ()) + Vector3D.EPSILON;

        return new Vector3D(maxX, maxY, maxZ);
    }

    /**
     * Gibt das Dreieck als Tinyray-Code zurück.
     */

    public String toString() {
        return "Triangle { " + this.+ "; " + this.+ "; " + this.+ "; "
                    + this.getParameters() + " }";
    }
}
Sphere.java
package tinyray.raytracer;

/**
 * Die Klasse Sphere repräsentiert eine Kugel (geometrisches Objekt),
 * welche in eine Szene plaziert werden kann.
 */

public final class Sphere extends Geo {

    // Der Zentrum der Kugel.
    private Vector3D center;

    // Der Radius der Kugel.
    private double radius;

    /**
     * Initialisiert die Kugel mit Zentrum und Radius.
     * @param center Das Zentrum.
     * @param radius Der Radius.
     */

    public Sphere(Vector3D center, double radius) {
        super();
        this.center = center.copy();
        this.radius = radius;
    }

    /**
     * Gibt den Normale der Kugel zurück.
     * Die Normale ist ein Vektor, der senkrecht zur Oberfläche steht.
     */

    public Vector3D getNormal(Vector3D pointOfSurface) {

        return Vector3D.sub(pointOfSurface, this.center).getNormalized();
    }

    /**
     * Gibt die Entfernung zurück, die ein Strahl zurücklegen muss,
     * um die Kugel zu schneiden.
     */

    public Geo.Distance getIntersect(Tinyray.Ray ray) {

        double a = Vector3D.dot(ray.getDirection(), ray.getDirection());

        if (!= 0.0) {

            Vector3D oc = Vector3D.sub(ray.getOrigin(), this.center);

            double b = 2.0 * Vector3D.dot(oc, ray.getDirection());
            double c = Vector3D.dot(oc, oc) - this.radius * this.radius;

            double disc = b * b - 4.0 * a * c;

            if (disc >= 0.0) {

                double x1 = (-+ Math.sqrt(disc)) / (+ a);
                double x2 = (-- Math.sqrt(disc)) / (+ a);

                if ((x1 > 0.0) & (x2 > 0.0)) {
                    double x = Math.min(x1, x2);
                    return new Geo.Distance(this, x - Vector3D.EPSILON);
                } else if (x1 > 0.0) {
                    return new Geo.Distance(this, x1 - Vector3D.EPSILON);
                } else if (x2 > 0.0) {
                    return new Geo.Distance(this, x2 - Vector3D.EPSILON);
                }
            }
        }

        return null;
    }

    /**
     * Gibt die Min-Werte der Kugel zurück.
     */

    public Vector3D getMinValues() {
        double minX = this.center.getX() - this.radius - Vector3D.EPSILON;
        double minY = this.center.getY() - this.radius - Vector3D.EPSILON;
        double minZ = this.center.getZ() - this.radius - Vector3D.EPSILON;
        return new Vector3D(minX, minY, minZ);
    }

    /**
     * Gibt die Max-Werte der Kugel zurück.
     */

    public Vector3D getMaxValues() {
        double maxX = this.center.getX() + this.radius + Vector3D.EPSILON;
        double maxY = this.center.getY() + this.radius + Vector3D.EPSILON;
        double maxZ = this.center.getZ() + this.radius + Vector3D.EPSILON;
        return new Vector3D(maxX, maxY, maxZ);
    }

    /**
     * Gibt die Kugel als Tinyray-Code zurück.
     */

    public String toString() {
        return "Sphere { " + this.center + "; " + this.radius + "; "
                    + this.getParameters() + " }";
    }
}
Plane.java
package tinyray.raytracer;

/**
 * Die Klasse Geo repräsentiert eine Ebene (unendliches Geoobjekt),
 * welche in eine Szene plaziert werden kann.
 */

public final class Plane extends Geo {

    // Drei Punkte der Ebene.
    private Vector3D x;
    private Vector3D y;
    private Vector3D z;

    // Hilfsvektor yx = y - x.
    private Vector3D yx;

    // Hilfsvektor zx = z - x.
    private Vector3D zx;

    // Hilfsvektor, die Normale zur Ebene.
    private Vector3D normal;

    /**
     * Initialisiert die Ebene mit drei Ortsvektoren.
     * Diese Ortsvektoren (Punkte) liegen genau in der Ebene.
     * @param x Ein Ortsvektor.
     * @param y Ein Ortsvektor.
     * @param z Ein Ortsvektor.
     */

    public Plane(Vector3D x, Vector3D y, Vector3D z) {
        super();
        this.= x.copy();
        this.= y.copy();
        this.= z.copy();

        this.yx = Vector3D.sub(this.x, this.y);
        this.zx = Vector3D.sub(this.x, this.z);

        this.normal = Vector3D.cross(this.yx, this.zx).getNormalized();
    }

    /**
     * Gibt die Normale der Ebene zurück.
     * Die Normale ist ein Vektor, der senkrecht zur Oberfläche steht.
     */

    public Vector3D getNormal(Vector3D pointOfSurface) {
        return this.normal;
    }

    /**
     * Gibt die Entfernung zurück, die ein Strahl zurücklegen muss,
     * um die Ebene zu schneiden.
     */

    public Geo.Distance getIntersect(Tinyray.Ray ray) {
        Vector3D a = this.yx;
        Vector3D b = this.zx;
        Vector3D c = ray.getDirection();
        Vector3D p = Vector3D.sub(this.x, ray.getOrigin());

        double det3 = Vector3D.det(a, b, p);
        double detN = Vector3D.det(a, b, c);

        if (detN == 0.0) {
            return null;
        } else {
            return new Geo.Distance(this, (det3 / detN) - Vector3D.EPSILON);
        }
    }

    /**
     * Gibt die Min-Werte der Ebene zurück.
     * Da die Ebene unendliche Ausmaße hat, so erhält man die
     * kleinstmöglichen Werte (Double.MIN_VALUE).
     * Eine Ebene in eine Boundingbox zu integrieren,
     * ist zwar möglich, würde aber wenig Sinn machen,
     * da sonst die Boundingbox unendliche Größe hätte.
     */

    public Vector3D getMinValues() {
        return new Vector3D(Double.MIN_VALUE);
    }

    /**
     * Gibt die Max-Werte der Ebene zurück.
     * Da die Ebene unendliche Ausmaße hat, so erhält man die
     * größtmöglichen Werte (Double.MAX_VALUE).
     * Eine Ebene in eine Boundingbox zu integrieren,
     * würde demnach wenig Sinn machen.
     */

    public Vector3D getMaxValues() {
        return new Vector3D(Double.MAX_VALUE);
    }

    /**
     * Gibt die Ebene als Tinyray-Code zurück.
     */

    public String toString() {
        return "Plane { " + this.+ "; " + this.+ "; " + this.+
                "; " + this.getParameters() + " }";
    }
}
Bounding.java
package tinyray.raytracer;

import java.util.Iterator;
import java.util.LinkedList;

/**
 * Die Klasse Bounding repräsentiert die sogenannte Boundingbox.
 * Mit der Boundingbox wird eine Menge anderer geometrischer Objekte
 * umschlossen. So kann die Effizienz des Schnittpunkttests verbessert
 * werden. Erst wenn die Boundingbox geschnitten wird, werden die
 * enthaltenen Goeobjekte auf Schnitt getestet.
 */

public final class Bounding extends Geo {

    // Liste der enthaltenen Geoobjekte.
    private LinkedList<Geo> geos;

    // Zwei Eckpunkte der Boundingbox.
    private Vector3D minValues;
    private Vector3D maxValues;

    // Die Boundingbox besteht aus sechs Vierecken.
    private Bounding.Rectangle[] boundingBox;

    /**
     * Initialisiert die Boundingbox, welche noch keine geometrischen
     * Objekte enthält.
     */

    public Bounding() {
        super();
        this.geos = new LinkedList<Geo>();
        this.minValues = Vector3D.ZERO;
        this.maxValues = Vector3D.ZERO;
        this.boundingBox = new Bounding.Rectangle[6];
        this.updateBoundingBox();
    }

    /**
     * Fügt der Boundingbox ein Geoobjekt hinzu.
     * Die Ausmaße der Boundingbox passen sich der Größe des Geoobjekts an.
     * @param geo Das Geoobjekt.
     */

    public void addGeo(Geo geo) {
        Vector3D geoMin = geo.getMinValues().copy();
        Vector3D geoMax = geo.getMaxValues().copy();
        if (this.geos.isEmpty()) {
            this.minValues = geoMin;
            this.maxValues = geoMax;
        } else {
            double minX = Math.min(this.minValues.getX(), geoMin.getX());
            double minY = Math.min(this.minValues.getY(), geoMin.getY());
            double minZ = Math.min(this.minValues.getZ(), geoMin.getZ());
            this.minValues = new Vector3D(minX, minY, minZ);
            double maxX = Math.max(this.maxValues.getX(), geoMax.getX());
            double maxY = Math.max(this.maxValues.getY(), geoMax.getY());
            double maxZ = Math.max(this.maxValues.getZ(), geoMax.getZ());
            this.maxValues = new Vector3D(maxX, maxY, maxZ);
        }
        this.geos.add(geo);
        this.updateBoundingBox();
    }

    /**
     * Gibt einen Nullvektor zurück.
     * Da die Boundingbox kein sichtbares Objekt ist,
     * sondern nur ein Kontainer für andere Geoobjekte,
     * so wird die Oberflächennormale zur Farbbestimmung nicht benötigt,
     * weshalb auch der Nullvektor zurückgegeben wird.
     */

    public Vector3D getNormal(Vector3D vector3D) {
        return Vector3D.ZERO;
    }

    /**
     * Gibt die Entfernung zurück, die ein Strahl zurücklegen muss,
     * um die Boundingbox zu schneiden.
     */

    public Geo.Distance getIntersect(Tinyray.Ray ray) {

        boolean testGeos = false;

        // Rentabilität prüfen
        if (this.geos.size() <= 6) {
            // Da die Boundingbox höchstens 6 Geo-Objekte umschließt,
            // wäre der Hittest der Boundingbox aufwendiger als der
            // Hittest all seiner Geo-Objekte.
            testGeos = true;
        } else {
            // Prüfen, ob Boundingbox vom Ray getroffen wird.
            for (int i = 0; i < this.boundingBox.length; i++) {
                if (this.boundingBox[i].hitTest(ray)) {
                    testGeos = true;
                    break;
                }
            }
        }

        if (testGeos) {
            double minDistance = Double.MAX_VALUE;

            // Gewinner feststellen
            Geo closest = null;
            Iterator<Geo> iterator = this.geos.iterator();
            while (iterator.hasNext()) {
                Geo geo = iterator.next();
                Geo.Distance geoDistance = geo.getIntersect(ray);
                if (geoDistance != null) {
                    double distance = geoDistance.getDistance();
                    if ((0.0 < distance) && (distance < minDistance)) {
                        minDistance = distance;
                        closest = geoDistance.getGeo();
                    }
                }
            }
            // Wenn ein Gewinner feststeht, dann wird die Distanz
            // zu ihm zurückgegeben.
            if (closest != null) {
                return new Geo.Distance(closest, minDistance);
            }
        }
        return null;
    }

    /**
     * Gibt die Min-Werte der Boundingbox zurück.
     */

    public Vector3D getMinValues() {
        return this.minValues;
    }

    /**
     * Gibt die Max-Werte der Boundingbox zurück.
     */

    public Vector3D getMaxValues() {
        return this.maxValues;
    }

    /**
     * Gibt die Boundingbox als Tinyray-Code zurück.
     */

    public String toString() {
        String result = "Bounding {\n";
        result += "    // Min = " + this.minValues + "\n";
        result += "    // Max = " + this.maxValues + "\n";
        Iterator<Geo> iterator = this.geos.iterator();
        while (iterator.hasNext()) {
            result += "    " + iterator.next() + "\n";
        }
        result += "    }";

        return result;
    }

    // Aktualisiert die Boundingbox mit Hilfe der Min- und Max-Werte.
    private void updateBoundingBox() {

        Vector3D c1 = new Vector3D(this.minValues.getX(),
                this.minValues.getY(), this.maxValues.getZ());

        Vector3D c2 = new Vector3D(this.maxValues.getX(),
                this.minValues.getY(), this.maxValues.getZ());

        Vector3D c3 = this.maxValues.copy();

        Vector3D c4 = new Vector3D(this.minValues.getX(),
                this.maxValues.getY(), this.maxValues.getZ());

        Vector3D c5 = this.minValues.copy();

        Vector3D c6 = new Vector3D(this.maxValues.getX(),
                this.minValues.getY(), this.minValues.getZ());

        Vector3D c7 = new Vector3D(this.maxValues.getX(),
                this.maxValues.getY(), this.minValues.getZ());

        Vector3D c8 = new Vector3D(this.minValues.getX(),
                this.maxValues.getY(), this.minValues.getZ());

        this.boundingBox[0] = new Bounding.Rectangle(c1, c4, c2); // front
        this.boundingBox[1] = new Bounding.Rectangle(c6, c7, c5); // back
        this.boundingBox[2] = new Bounding.Rectangle(c4, c8, c3); // top
        this.boundingBox[3] = new Bounding.Rectangle(c1, c5, c2); // bottom
        this.boundingBox[4] = new Bounding.Rectangle(c1, c4, c5); // left
        this.boundingBox[5] = new Bounding.Rectangle(c2, c3, c6); // right
    }

    // Die Klasse Rectangle repräsentiert eine rechtwicklige Fläche
    // der Boundingbox.
    private final class Rectangle {

        // Drei Eckpunkte der Fläche.
        private Vector3D x;
        private Vector3D y;
        private Vector3D z;

        // Zwei unabhänige Richtungsvektoren der Fläche.
        private Vector3D yx;
        private Vector3D zx;

        // Initialisiert die Fläche mit drei Eckpunkten.
        private Rectangle(Vector3D x, Vector3D y, Vector3D z) {
            this.= x.copy();
            this.= y.copy();
            this.= z.copy();

            this.yx = Vector3D.sub(this.x, this.y);
            this.zx = Vector3D.sub(this.x, this.z);
        }

        // Der Schnittpunkttest zur Fläche.
        private boolean hitTest(Tinyray.Ray ray) {
            Vector3D a = this.yx;
            Vector3D b = this.zx;
            Vector3D c = ray.getDirection();
            Vector3D p = Vector3D.sub(this.x, ray.getOrigin());

            double det1 = Vector3D.det(p, b, c);
            double det2 = Vector3D.det(a, p, c);
            double detN = Vector3D.det(a, b, c);

            if (detN == 0.0) {
                return false;
            } else {

                double beta = det1 / detN;
                double gamma = det2 / detN;

                return (0.0 < beta)
                    && (0.0 < gamma)
                    && (beta < 1.0)
                    && (gamma < 1.0);
            }
        }
    }
}
Camera.java
package tinyray.raytracer;

/**
 * Die Klasse Camera repräsentiert eine Kamera, die die Szene von einer
 * bestimmten Position aus betrachtet.
 */

public final class Camera extends Element {

    // Die Position der Kamera.
    private Vector3D location;

    // Die Kamera ist auf den Punkt lookAt ausgerichtet.
    private Vector3D lookAt;

    // Wo ist oben? up ist die Antwort.
    private Vector3D up;

    /**
     * Initialisiert die Kamera.
     */

    public Camera() {
        super();
        this.location = new Vector3D(0.0, 0.0, 10.0);
        this.lookAt = new Vector3D(0.0);
        this.up = new Vector3D(0.0, 1.0, 0.0);
    }

    /**
     * Gibt die Position der Kamera zurück.
     * @return Die Position.
     */

    public Vector3D getLocation() {
        return this.location;
    }

    /**
     * Setzt die Position der Kamera.
     * @param location Die Position.
     */

    public void setLocation(Vector3D location) {
        this.location = location.copy();
    }

    /**
     * Gibt den Punkt zurück, auf den die Kamera gerichtet ist.
     * Dieser Punkt liegt genau in der Mitte des resultierenden Bildes.
     * @return Der Punkt.
     */

    public Vector3D getLookAt() {
        return this.lookAt;
    }

    /**
     * Setzt den Punkt, auf den die Kamera zeigt.
     * @param lookAt Der Punkt.
     */

    public void setLookAt(Vector3D lookAt) {
        this.lookAt = lookAt.copy();
    }

    /**
     * Gibt den Up-Vektor zurück.
     * @return Der Up-Vektor.
     */

    public Vector3D getUp() {
        return this.up;
    }

    /**
     * Setzt den Up-Vektor.
     * @param up Der Up-Vektor.
     */

    public void setUp(Vector3D up) {
        this.up = up.copy();
    }

    /**
     * Gibt die Kamera als Tinyray-Code zurück.
     */

    public String toString() {
        return "Camera { " + this.location + "; " + this.lookAt
                + "; " + this.up + " }";
    }
}
Background.java
package tinyray.raytracer;

/**
 * Die Klasse Background repräsentiert die Leinwand im Hintergrund der Szene.
 */

public final class Background extends Element {

    // Die Hintergrundfarbe.
    private Vector3D color;

    /**
     * Initialisiert die Leinwand mit einer Farbe.
     * @param color Die Hintergrundfarbe.
     */

    public Background(Vector3D color) {
        super();
        this.color = color.copy();
    }

    /**
     * Gibt die Hintergrundfarbe als Vector zurück.
     * @return Die Hintergrundfarbe.
     */

    public Vector3D getColor() {
        return this.color;
    }

    /**
     * Gibt das Leinwand als Tinyray-Code zurück.
     */

    public String toString() {
        return "Background { " + this.color + " }";
    }
}
Ambience.java
package tinyray.raytracer;

/**
 * Die Klasse Ambience repräsentiert das globale Umgebungslicht,
 * welches alle Szenenobjekte gleichermaßen beleuchtet.
 */

public final class Ambience extends Element {

    // Die Farbe des Umgebungslichtes.
    private Vector3D color;

    /**
     * Initialisiert das Umgebungslicht mit einer Farbe.
     * @param color Die Farbe des Umgebungslichtes.
     */

    public Ambience(Vector3D color) {
        super();
        this.color = color.copy();
    }

    /**
     * Gibt die Farbe des Umgebungslichtes zurück.
     * @return Die ambiente Farbe.
     */

    public Vector3D getColor() {
        return this.color;
    }

    /**
     * Gibt das Umgebungslicht als Tinyray-Code zurück.
     */

    public String toString() {
        return "Ambience { " + this.color + " }";
    }
}
Fog.java
package tinyray.raytracer;

/**
 * Die Klasse Fog repräsentiert den Nebel der Szene.
 */

public final class Fog extends Element {

    // Die Nebelfarbe.
    private Vector3D color;

    // Die Nebeldichte.
    private double density;

    /**
     * Initialisiert den Nebel mit einer Farbe und einer Dichte.
     * @param color Die Nebelfarbe.
     * @param density Die Nebeldichte.
     */

    public Fog(Vector3D color, double density) {
        super();
        this.color = color.copy();
        this.density = density;
    }

    /**
     * Gibt die Farbe des Nebels zurück.
     * @return Die Nebelfarbe.
     */

    public Vector3D getColor() {
        return this.color;
    }

    /**
     * Gibt die Dichte des Nebels zurück.
     * @return Die Nebeldichte.
     */

    public double getDensity() {
        return this.density;
    }

    /**
     * Gibt den Nebel als Tinyray-Code zurück.
     */

    public String toString() {
        return "Fog { " + this.color + "; " + this.density + " }";
    }
}
Parameters.java
package tinyray.raytracer;

/**
 * Die Klasse Parameters repräsentiert die allgemeinen Parameter
 * eines geometrischen Objekts wie zum Beispiel die Farbwerte.
 */

public final class Parameters extends Element {

    // Die vom Licht abhängige, diffuse Farbe.
    private Vector3D color;

    // Die vom Licht unabhängige, ambiente Farbe.
    private Vector3D ambient;

    // Die Materialkonstante für die Lichtreflektion (Phong).
    private double specular;

    // Die Oberflächenbeschaffenheit für die Lichtreflektion (Phong).
    private double shininess;

    // Die Reflektionskonstante.
    private double mirror;

    /**
     * Initialisiert die allgemeinen Parameter eines GeoObjekts.
     */

    public Parameters() {
        super();
        this.color = new Vector3D(0.5);
        this.ambient = new Vector3D(0.1);
        this.specular = 0.5;
        this.shininess = 1000.0;
        this.mirror = 0.4;
    }

    /**
     * Klont die Parameter.
     * @return Der Klon.
     */

    public Parameters copy() {
        Parameters result = new Parameters();
        result.color = this.color.copy();
        result.ambient = this.ambient.copy();
        result.specular = this.specular;
        result.shininess = this.shininess;
        result.mirror = this.mirror;
        return result;
    }

    /**
     * Gibt die lichtabhängige, diffuse Farbe zurück.
     * @return Die diffuse Farbe.
     */

    public Vector3D getColor() {
        return this.color;
    }

    /**
     * Setzt die lichtabhängige, diffuse Farbe.
     * @param color Die diffuse Farbe.
     */

    public void setColor(Vector3D color) {
        this.color = color.copy();
    }

    /**
     * Gibt die lichtabhängige, ambiente Farbe zurück.
     * @return Die ambiente Farbe.
     */

    public Vector3D getAmbient() {
        return this.ambient;
    }

    /**
     * Setzt die lichtunabhängige, ambiente Farbe.
     * @param ambient Die ambiente Farbe.
     */

    public void setAmbient(Vector3D ambient) {
        this.ambient = ambient.copy();
    }

    /**
     * Gibt die Materialkonstante für die Lichtreflektion zurück.
     * Dieser Parameter wird benötigt, um das Phong-Lighting realisieren
     * zu können.
     * @return Die Materialkonstante.
     */

    public double getSpecular() {
        return this.specular;
    }

    /**
     * Setzt die Materialkonstante für die Lichtreflektion.
     * @param specular Die Materialkonstante.
     */

    public void setSpecular(double specular) {
        this.specular = specular;
    }

    /**
     * Gibt die Oberflächenbeschaffenheit für die Lichtreflektion zurück.
     * Dieser Parameter wird benötigt, um das Phong-Lighting realisieren
     * zu können.
     * @return Die Oberflächenbeschaffenheit.
     */

    public double getShininess() {
        return this.shininess;
    }

    /**
     * Setzt die Oberflächenbeschaffenheit für die Lichtreflektion.
     * Ist der Wert kleiner als 32, dann ist das dazugehörige Geoobjekt rau,
     * ist er größer oder gleich 32, dann glatt.
     * Je größer der Wert, desto perfekter die Spiegelwirkung (nur im Bezug
     * auf die Lichtreflektion).
     * @param shininess Die Oberflächenbeschaffenheit.
     */

    public void setShininess(double shininess) {
        this.shininess = shininess;
    }

    /**
     * Gibt die Reflektionskonstante zurück.
     * Dieser Wert ist verantwortlich dafür, wie stark sich andere Geoobjekte
     * in diesem Geoobjekt spiegeln können.
     * @return Die Reflektionskonstante.
     */

    public double getMirror() {
        return this.mirror;
    }

    /**
     * Setzt die Reflektionskonstante.
     * @param mirror Die Reflektionskonstante.
     */

    public void setMirror(double mirror) {
        this.mirror = mirror;
    }

    /**
     * Gibt die Parameter als Tinyray-Code zurück.
     */

    public String toString() {
        String result = "";
        result += this.getColor() + "; ";
        result += this.getAmbient() + "; ";
        result += this.getSpecular() + "; ";
        result += this.getShininess() + "; ";
        result += this.getMirror();
        return result;
    }
}
Sun.java
package tinyray.raytracer;

/**
 * Die Klasse Sun repräsentiert eine Sonne, also eine Lichtquelle,
 * die sich in unendlich weiter Entfernung zur Szene befindet.
 * Die "Sonne" wird auf der Erde als parallele Lichtquelle empfunden.
 */

public final class Sun extends Element {

    // Die Richtung, in der sich die Sonne befindet.
    private Vector3D direction;

    // Die Farbe der Sonne.
    private Vector3D color;

    /**
     * Initialisiert die Sonne mit der Richtung, in der sie sich befindet
     * und mit der Sonnenfarbe.
     * @param direction Die Richtung zur Sonne.
     * @param color Die Sonnenfarbe.
     */

    public Sun(Vector3D direction, Vector3D color) {
        super();
        this.direction = direction.getNormalized();
        this.color = color.copy();
    }

    /**
     * Gibt die Richtung, in der sich die Sonne befindet, zurück.
     * @return Die Richtung zur Sonne.
     */

    public Vector3D getDirection() {
        return this.direction;
    }

    /**
     * Gibt die Farbe der Sonne als Vektor zurück.
     * @return Die Farbe.
     */

    public Vector3D getColor() {
        return this.color;
    }

    /**
     * Gibt die Sonne als Tinyray-Code zurück.
     */

    public String toString() {
        return "Sun { " + this.direction + "; " + this.color + " }";
    }
}
Vector3D.java
package tinyray.raytracer;

/**
 * Die Klasse Vector3D repräsentiert einen Vektor im dreidimensionalen Raum.
 * Der Grundkörper des Vektors (Skalar) ist vom Typ double.
 */

public final class Vector3D extends Element {

    /**
     * Der maximal tolerierbare Fehler zum Beispiel
     * beim Vergleich von Skalarwerten.
     */

    public final static double EPSILON = 0.00000000001;

    /**
     * Der Nullvektor.
     */

    public final static Vector3D ZERO = new Vector3D(0.0);

    /**
     * Der Basisvektor in X-Richtung.
     */

    public final static Vector3D X = new Vector3D(1.0, 0.0, 0.0);

    /**
     * Der Basisvektor in Y-Richtung.
     */

    public final static Vector3D Y = new Vector3D(0.0, 1.0, 0.0);

    /**
     * Der Basisvektor in Z-Richtung.
     */

    public final static Vector3D Z = new Vector3D(0.0, 0.0, 1.0);

    // Die X-Komponente.
    private double x;

    // Die Y-Komponente.
    private double y;

    // Die Z-Komponente.
    private double z;

    /**
     * Initialisiert den Vektor mit den drei Komponenten.
     * @param x Die X-Komponente.
     * @param y Die Y-Komponente.
     * @param z Die Z-Komponente.
     */

    public Vector3D(double x, double y, double z) {
        super();
        this.= x;
        this.= y;
        this.= z;
    }

    /**
     * Initialisiert die drei Komponenten des Vektors mit einem Skalarwert.
     * @param scalar Der Skalarwert.
     */

    public Vector3D(double scalar) {
        this(scalar, scalar, scalar);
    }

    /**
     * Initialisiert den Vektor mit den Komponenten gegebenen Vektors.
     * @param vector Der gegebene Vektor.
     */

    public Vector3D(Vector3D vector) {
        this(vector.x, vector.y, vector.z);
    }

    /**
     * Klont den Vektor.
     * @return Der Klon.
     */

    public Vector3D copy() {
        return new Vector3D(this.x, this.y, this.z);
    }

    /**
     * Gibt die X-Komponente des Vektors zurück.
     * @return Die X-Komponente.
     */

    public double getX() {
        return this.x;
    }

    /**
     * Gibt die Y-Komponente des Vektors zurück.
     * @return Die Y-Komponente.
     */

    public double getY() {
        return this.y;
    }

    /**
     * Gibt die Z-Komponente des Vektors zurück.
     * @return Die Z-Komponente.
     */

    public double getZ() {
        return this.z;
    }

    /**
     * Gibt die Länge des Vektors zurück.
     * @return Die Länge des Vektors.
     */

    public double getMagnitude() {
        return Math.sqrt(Vector3D.dot(this, this));
    }

    /**
     * Gibt eine Kopie, welche auf die Länge 1 normiert ist, zurück.
     * @return Die normalisierte Kopie.
     */

    public Vector3D getNormalized() {
        double magnitude = this.getMagnitude();
        if (magnitude > Vector3D.EPSILON) {
            return new Vector3D(
                    this./ magnitude,
                    this./ magnitude,
                    this./ magnitude);
        } else {
            return Vector3D.ZERO;
        }
    }

    /**
     * Berechnet den Reflektionsvektor bezüglich einer gegebenen Normalen.
     * @param n Die Normale.
     * @return Der Reflektionsvektor.
     */

    public Vector3D getReflectedAt(Vector3D n) {
        return Vector3D.sub(
                this,
                Vector3D.mult(2.0 * Vector3D.dot(n, this), n));
    }

    /**
     * Gibt den Vektor als Tinyray-Code zurück.
     */

    public String toString() {
        return "[" + this.+ ", " + this.+ ", " + this.+ "]";
    }

    /**
     * Berechnet die Determinante von drei Spaltenvektoren (3x3-Matrix).
     * @param a Spaltenvektor.
     * @param b Spaltenvektor.
     * @param c Spaltenvektor.
     * @return Die Determinante.
     */

    public static double det(Vector3D a, Vector3D b, Vector3D c) {
        return
            + a.* b.* c.z
            + b.* c.* a.z
            + c.* a.* b.z
            - c.* b.* a.z
            - b.* a.* c.z
            - a.* c.* b.z;
    }

    /**
     * Berechnet das Skalarprodukt zwischen zwei Vektoren.
     * @param v1 Vektor.
     * @param v2 Vektor.
     * @return Das Skalarprodukt.
     */

    public static double dot(Vector3D v1, Vector3D v2) {
        return v1.* v2.+ v1.* v2.+ v1.* v2.z;
    }

    /**
     * Berechnet das Kreuzprodukt zwischen zwei Vektoren.
     * @param v1 Vektor.
     * @param v2 Vektor.
     * @return Das Kreuzprodukt.
     */

    public static Vector3D cross(Vector3D v1, Vector3D v2) {
        return new Vector3D(
                v1.* v2.- v1.* v2.y,
                v1.* v2.- v1.* v2.z,
                v1.* v2.- v1.* v2.x);
    }

    /**
     * Berechnet die Summe zweier Vektoren.
     * @param v1 Vektor..
     * @param v2 Vektor.
     * @return Die Summe.
     */

    public static Vector3D add(Vector3D v1, Vector3D v2) {
        return new Vector3D(v1.+ v2.x, v1.+ v2.y, v1.+ v2.z);
    }

    /**
     * Berechnet die Differenz zweier Vektoren.
     * @param v1 Der Vektor als Minuend.
     * @param v2 Der Vektor als Subtrahend.
     * @return Die Differenz.
     */

    public static Vector3D sub(Vector3D v1, Vector3D v2) {
        return new Vector3D(v1.- v2.x, v1.- v2.y, v1.- v2.z);
    }

    /**
     * Berechnet das Produkt zwischen Skalar und Vektor.
     * @param scalar Der Skalarwert.
     * @param v Der Vektor.
     * @return Das Produkt.
     */

    public static Vector3D mult(double scalar, Vector3D v) {
        return new Vector3D(scalar * v.x, scalar * v.y, scalar * v.z);
    }

    /**
     * Berechnet das Vektorprodukt zweier Vektoren.
     * Das Vektorprodukt ist die komponentenweise Multiplikation
     * aller beteiligten Vektoren.
     * @param v1 Vektor.
     * @param v2 Vektor.
     * @return Das Vektorprodukt.
     */

    public static Vector3D mult(Vector3D v1, Vector3D v2) {
        return new Vector3D(v1.* v2.x, v1.* v2.y, v1.* v2.z);
    }
}
Real.java
package tinyray.raytracer;

/**
 * Die Klasse Real repräsentiert eine Fließkommazahl.
 */

public final class Real extends Element {

    // Die Fließkommazahl.
    private double value;

    /**
     * Initialisiert die Fließkommazahl mit einem Double-Wert.
     * @param value Der Double-Wert.
     */

    public Real(double value) {
        super();
        this.value = value;
    }

    /**
     * Gibt die Fließkommazahl als Double-Wert zurück.
     * @return Die Fließkommazahl.
     */

    public double getValue() {
        return this.value;
    }

    /**
     * Gibt die Fließkommazahl als Tinyray-Code zurück.
     */

    public String toString() {
        return "" + this.value;
    }
}