Scanline Algorithmus für Polygone

Hallo Community,

ich habe Momentan die Aufgabe für meinen Kurs Computergrafik einen Scanline Algorithmus zu implementieren bin aber ziemlich überfordert.:frowning:

die genaue Aufgabenstellung findet man hier Blatt 3 das Projekt das man erweitern muss befindet sich im Anhang —> Computergrafik SoSe 2014

das sind die klassen wo ich momentan dran arbeite :```package model.drawables;

import java.awt.Graphics;
import java.util.LinkedList;
import java.util.List;

import model.Matrix;

/**

  • Ein grafisches Objekt, dass ein Polygon repraesentiert. Die einzelnen

  • Polygonpunkte sind in einer Liste abgelegt. Der erste Punkt ist nicht doppelt

  • abgelegt, d.h. die letzte Kante ist vom letzten Punkt zum ersten Punkt zu

  • ziehen.

  • @author
    */
    public class Polygon extends DrawableObject {

    protected List points; // Liste mit Eckpunkten
    protected boolean filled;
    protected List edges = new LinkedList();

    /**

    • Erstellt ein Polygon
    • @param points
    •        Punkte-Liste
      
    • @param filled
    •        soll das Polygon gefüllt sein?
      

    */
    public Polygon(List points) {
    this.points = points;
    }

    /**

    • Zeichnet das Polygon in den uebergebenen grafischen Kontext und fuellt es
    • gegebenenfalls.
    • @param g
    •        der grafische Kontext, in den dieses Objekt sich zeichnen soll
      

    */
    public void paint(Graphics g) {
    Line l;
    Point prev = null;

     for (Point p : points) {
         if (prev != null) {
             l = new Line(prev, p);
             Edge kante = new Edge()
             l.paint(g);
         }
         prev = p;
     }
    
     // Polygon schließen, Linie zeichnen
     l = new Line(prev, points.get(0));
     l.paint(g);
    

    }

    /**

    • Gibt true zurück, wenn der Punkt im Polygon liegt, false sonst.
    • @param p
    •        der zu prüfende Punkt
      
    • @return true, wenn p innerhalb des Polygons liegt
      */
      public boolean isNear(Point p) {
      return true; // noch zu implementieren
      }

    /**

    • Transformiert das Polygon mit Hilfe der übergebenen
    • Transformationsmatrix.
    • @param m
    •        die Transformationsmatrix
      

    */
    public void transformBy(Matrix m) {
    List a = new LinkedList();
    for (Point p : points) {

         p = m.multiply(p);
         a.add(p);
    
     }
    
     points = a;
     return; // noch zu implementieren
    

    }

    /**

    • Wendet das Scanline-Verfahren zum Fuellen eines Polygons an
    • @param g
    •        der grafische Kontext, in den dieses Objekt sich zeichnen soll
      

    */
    private void scanlineFill(Graphics g) {
    return; // noch zu implementieren
    }

}```


/**
 * 
 * @author Fujitsu
 * 
 *         Klasse zur Repräsentation einer Kante.
 * 
 */
public class Edge implements Comparable<Edge> {
    /** groesste y-Koordinate der Kante. */
    public int y_top;

    /** Schnittpunkt der Scan-Line mit der Kante. */
    public double x_int;

    /** y-Ausdehnung der Kante. */
    public int delta_y;

    /** inverse Steigung 1/s der Kante. */
    public double delta_x;

    /**
     * Erzeugt ein Objekt vom Typ Kante mit den uebergebenen Parametern.
     * */

    public Edge(int y_top, double x_int, int delta_y, double delta_x) {

        this.y_top = y_top;
        this.x_int = x_int;
        this.delta_y = delta_y;
        this.delta_x = delta_x;

    }

    /** Erzeugt ein Objekt vom Typ Kante mit den uebergebenen Parametern. */

    /** Erzeugt ein leeres Objekt vom Typ Kante. */

    public Edge() {

        this(0, 0.0, 0, 0.0);
    }

    @Override
    public int compareTo(Edge o) {
        
        return this.y_top-o.y_top;
    }

}```


ich habe die Klasse Edge geschrieben um die Kanten zu repräsentieren und das Interface Comparable implementiert um 2 Kanten nach ihrem Größten y Wert zu vergleichen. Außerdem speichere ich in der Klasse Polygon eine LinkedList von Edges.  Ist das schonmal grob in die richtige Richtung gedacht ?? Könnt ihr mir Tipps geben 


mit freundlichen Grüßen

Nun, eine konkrete Frage ist da jetzt nicht dabei. Man könnte auf 1000 Dingen “rumhacken”:

  • Fields sollten private sein
  • Namen sollten keine _ Unterstriche enthalten
  • Für eine List nimmt man “meistens” ArrayList, aber das ist hier nicht sooo wichtig

Ansonsten ist bisher nicht wirklich was “falsch”, aber einiges könnte vom Ansatz her ungünstig sein:

  • Die “Steigung” ist etwas suspekt, und spätestens bei einer senkrechten Kante problematisch
  • Eine Kante sollte durch 2 Punkte ausreichend definiert sein - und in diesem Fall könnte sie direkt Referenzen auf die beiden “Point”-Objekte speichern (bedenke, dass, wenn man das anders macht, alles, was in der Kante gespeichert ist, ggf. aktualisiert werden muss, wenn man die “transformBy”-Methode aufruft…

Nur erste Gedanken dazu…

danke für die Antwort :slight_smile:

das Fields im Sinne der Objektorientierung private sein sollten ist mir bewusst, dass wird aber in dem CG Kurs oft ignoriert.
Warum sollten Namen keine Unterstriche enthalten?

Ich programmiere morgen weiter und gucke dann mal welche konkreteren Fragen sich ergeben…

obwohl eine fällt mir gerade noch ein wie berechnet man die Schnittpunkte einer Kante mit der Scanline?

mfg

Das mit der Unterstrichen ist einfach eine Konvention. In Java sind Namen (außer bei Konstanten) immerImCamelCase, und nie_mit_Unterstrich geschrieben.

Ich müßte den Scanline-Algorithmus nochmal ansehen (ist schon ein paar Jährchen her…), und frage mich gerade, wo da wirklich Schnittpunkte ausgerechnet werden müssen, aber da schau’ ich ggf. “morgen” (d.h. später heute) nochmal

Warum sollten Namen keine Unterstriche enthalten?

Es gibt gewisse Code Conventions, an die man sich halten sollte. Dazu gehört unter anderem, dass man Variablen im lowerCamelCase schreibt.

[QUOTE=Marco13]Das mit der Unterstrichen ist einfach eine Konvention. In Java sind Namen (außer bei Konstanten) immerImCamelCase, und nie_mit_Unterstrich geschrieben.

…[/QUOTE]

vergiss das. hab grad das mit den “Außer bei Konstanten” gesehen^^

Hab’ grad nochmal kurz auf die Aufgabenstellung geschaut: Es gibt schon die Klasse „Line“. Sollte die nicht die Funktion übernehmen (können), die jetzt anscheinend mit der „Edge“ abgebildet werden soll? :confused:

ja stimmt hab ich jetzt auch so gemacht. Ich hab Line aber noch ein bischen erweitert.

meine neue Klasse Line :

 * Klasse, die eine Linie repräsentiert. Eine Linie besteht aus zwei
 * Punkten. Diese Punkte dienen als Start- und Endpunkt fuer den Bresenham
 * Algorithmus.
 * 
 * @author Nicolas Neubauer
 */
public class Line extends DrawableObject implements Comparable<Line> {
    protected Point a; // Anfangspunkt a
    protected Point e; // Endpunkt e
    public int ymin;
    public int ymax;
    public  int steigung;
    public int xmin;
    public int xmax;

    /**
     * Konstruktor zum erzeugen eines Linienobjektes
     * 
     * @param xa
     *            x-Koordinate des Startpunktes
     * @param ya
     *            y-Koordinate des Startpunktes
     * @param xe
     *            x-Koordinate des Endpunktes
     * @param ye
     *            y-Koordinate des Endpunktes
     */
    public Line(int xa, int ya, int xe, int ye) {
        this(new Point(xa, ya), new Point(xe, ye));

        if (a.y >= e.y) {
            ymax = a.y;
            ymin = e.y;
        } else {
            ymax = e.y;
            ymin = a.y;
        }
        
        
        if(a.x >= e.x) {
            xmax = a.x;
            xmin = e.x;
        } else {
            xmax = e.x;
            xmin = a.x;
        }
        
        
        
        steigung = (ymax - ymin) / (xmax- xmin);

    }

    /**
     * Konstruktor zum Erzeugen eines Linienobjektes
     * 
     * @param a
     *            Startpunktes
     * @param e
     *            Endpunktes
     */
    public Line(Point a, Point e) {
        this.a = a;
        this.e = e;

        if (a.y >= e.y) {
            ymax = a.y;
            ymin = e.y;
        } else {
            ymax = e.y;
            ymin = a.y;
        }
        
        if(a.x >= e.x) {
            xmax = a.x;
            xmin = e.x;
        } else {
            xmax = e.x;
            xmin = a.x;
        }
        
        steigung = (ymax - ymin) / (xmax- xmin);
        
        
    }

    /**
     * Paint-Methode der Linienklasse Nutzt den Bresenham-Algorithmus
     * 
     * @param g
     *            der Graphikkontext, in den das Objekt gezeichnet werden soll
     */
    public void paint(Graphics g) {
        Point p = new Point(a);

        int error, delta, schwelle, dx, dy, inc_x, inc_y;
        dx = e.x - p.x;
        dy = e.y - p.y;

        if (dx > 0)
            inc_x = 1;
        else
            inc_x = -1;
        if (dy > 0)
            inc_y = 1;
        else
            inc_y = -1;

        if (Math.abs(dy) < Math.abs(dx)) // flach nach oben oder unten
        {
            error = -Math.abs(dx);
            delta = 2 * Math.abs(dy);
            schwelle = 2 * error;
            while (p.x != e.x) {
                setPixel(p.x, p.y, g);
                p.x += inc_x;
                error = error + delta;
                if (error > 0) {
                    p.y += inc_y;
                    error = error + schwelle;
                }
            }
        } else // steil nach oben oder unten
        {
            error = -Math.abs(dy);
            delta = 2 * Math.abs(dx);
            schwelle = 2 * error;
            while (p.y != e.y) {
                setPixel(p.x, p.y, g);
                p.y += inc_y;
                error = error + delta;
                if (error > 0) {
                    p.x += inc_x;
                    error = error + schwelle;
                }
            }
        }

        setPixel(e.x, e.y, g);
    }

    /**
     * Liefert den Schnittpunkt, wenn diese Linie die spezifizierte Kante eines
     * Clipping-Rechtecks schneidet; null sonst.
     * 
     * @param value
     *            der Wert der interessanten Kante in Pixeln
     * @param edge
     *            die interessante Kante
     * @return der Schnittpunkt, falls er existiert; null sonst
     */
    public Point intersection(int value, int edge) {
        boolean avis; // ist Startpunkt drinnen?
        boolean evis; // ist Endpunkt drinnen?
        double slope; // Steigung dieser Linie
        Point p;

        avis = a.onVisibleSide(value, edge); // Startpunkt testen
        evis = e.onVisibleSide(value, edge); // Endpunkt testen

        if ((avis && evis) || (!avis && !evis)) // kein Schnittpunkt
            return null;
        else {
            p = new Point(a); // Hilfspunkt vorbereiten
            slope = (e.y - a.y) / (double) (e.x - a.x); // Steigung berechnen

            if ((edge == TOP) || (edge == BOTTOM)) // waagerechte Kante
            {
                p.x = (int) ((value - a.y) / slope) + a.x;
                p.y = value;
            } else // senkrechte Kante
            {
                p.x = value;
                p.y = (int) ((value - a.x) * slope) + a.y;
            }
        }

        return p; // Hilfspunkt zurueck
    }

    /**
     * Vergleicht zwei Linien anhand des maximalen Y-Wertes.
     */
    public int compareTo(Line l) {
        return l.ymax - this.ymax;
    }

}

meine neue Klasse Polygon :


import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import model.Matrix;

/**
 * Ein grafisches Objekt, dass ein Polygon repraesentiert. Die einzelnen
 * Polygonpunkte sind in einer Liste abgelegt. Der erste Punkt ist nicht doppelt
 * abgelegt, d.h. die letzte Kante ist vom letzten Punkt zum ersten Punkt zu
 * ziehen.
 * 
 * @author Nicolas Neubauer
 */
public class Polygon extends DrawableObject {

    protected List<Point> points; // Liste mit Eckpunkten
    protected List<Line> edges = new ArrayList<Line>();
    protected List<Line> activeEdges = new ArrayList<Line>();
    protected List<Point> Schnittpunktliste = new ArrayList<Point>();

    /**
     * Erstellt ein Polygon
     * 
     * @param points
     *            Punkte-Liste
     * @param filled
     *            soll das Polygon gefüllt sein?
     */
    public Polygon(List<Point> points) {
        this.points = points;
    }

    /**
     * Zeichnet das Polygon in den uebergebenen grafischen Kontext und fuellt es
     * gegebenenfalls.
     * 
     * @param g
     *            der grafische Kontext, in den dieses Objekt sich zeichnen soll
     */
    public void paint(Graphics g) {
        Line l;
        Point prev = null;

        for (Point p : points) {
            if (prev != null) {
                l = new Line(prev, p);
                edges.add(l);
                l.paint(g);
            }
            prev = p;
        }

        // Polygon schließen, Linie zeichnen
        l = new Line(prev, points.get(0));
        edges.add(l);
        l.paint(g);
        Collections.sort(edges);
        scanlineFill(g);
    }

    /**
     * Gibt true zurück, wenn der Punkt im Polygon liegt, false sonst.
     * 
     * @param p
     *            der zu prüfende Punkt
     * @return true, wenn p innerhalb des Polygons liegt
     */
    public boolean isNear(Point p) {
        return true; // noch zu implementieren
    }

    /**
     * Transformiert das Polygon mit Hilfe der übergebenen
     * Transformationsmatrix.
     * 
     * @param m
     *            die Transformationsmatrix
     */
    public void transformBy(Matrix m) {
        return; // noch zu implementieren
    }

    /**
     * Wendet das Scanline-Verfahren zum Fuellen eines Polygons an
     * 
     * @param g
     *            der grafische Kontext, in den dieses Objekt sich zeichnen soll
     */
    private void scanlineFill(Graphics g) {
        int scan;
        int globalminy;
        globalminy = 3000; //damit auf jeden Fall initialisiert ist auf 3000 setzen sonst Compilerfehler!
        for (Line L : edges) {

            if (L.ymin <= globalminy)
                globalminy = L.ymin;
        }

        Collections.sort(edges);

        for (scan = edges.get(0).ymax; scan >= globalminy; scan--) {

            for (Line L : edges) {

                if (scan == L.ymax) {
                    activeEdges.add(L);
                   

                }

                if (scan == L.ymin) {
                    activeEdges.remove(L);
                }
            }
           
            

        }

        return; // noch zu implementieren
    }

}```


Stimmt bei der Scanline die Liste der aktiven Kanten?

wie berechne ich den ersten Schnittpunkt einer Kante mit der Scanline ?? dannach kann man die weiteren mit der inversen Steigung ausrechnen?!?

*** Edit ***

ich hab mir mal dafür ne ArrayList<Point> für die Schnittpunkte angelegt aber wie man die berechnet gerade mit den Sonderfällen :/

Das mit der Liste der aktiven Kanten ist bisher noch nicht “falsch”. Etwas anders, als bei http://de.wikipedia.org/wiki/Rasterung_von_Polygonen#Aktive_Kantenliste beschrieben, aber die Beschreibung dort ist etwas verwirrend (es gibt immer mehrere Möglichkeiten).

Die Steigung speicherst du im Momment als int, und berechest sie als
steigung = (ymax - ymin) / (xmax- xmin);
Das müßte ggf. ein float sein, und entsprechend als
steigung = (float)(ymax - ymin) / (xmax- xmin);
berechnet werden.

Aaaaber wie schon gesagt ist das etwas problematisch, weil senkrechte Kanten damit nicht funktionieren. Beim Scanline so wie er auf Wikipedia beschrieben ist wird ja die inverse der Steigung verwendet. Das ist dann wieder recht leicht: Die ist nämlich nur bei waagerechten Kanten 0, und die Fallen beim Scanline sowieso weg.

Die Schnittpunkte lassen sich mit der inversen Steigung auch direkt berechnen: Wenn man eine Kante (x0,y0)-(x1,y1) hat, ist deren Steigung s=(dy/dx) mit dx=x1-x0 und dy=y1-y0. Wenn man nun eine Scanline bei “y” hat, ist der Schnittpunkt der Kante mit dieser Scanline bei ( (y-y0)/s, y). (Das “/s” ist dabei eine Multiplikation mit der Inversen Steigung)

danke für die hilfe!
ich habe das Programm bis jetzt nicht hinbekommen, muss mich aber erstmal mit anderen Aufgaben beschäftigen