Autor Beitrag
P-A-L
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 30



BeitragVerfasst: Do 26.03.09 23:48 
Hallo zusammen,

für mein Open-Source-Projekt "Scorch/Artillery" möchte ich gern einen Algorithmus implementieren, welcher vollständig in der Luft hängende Landschaftsteile mit der Schwerkraft bekannt macht... ;)

Die Idee ist die: Ein Berg wird Stück für Stück zu Klump geschossen, so dass eine Art Tunnel entsteht. Statt aber jetzt jedes überhangende Stück sofort abrutschen zu lassen (das wäre einfach!), soll die Decke erst einstürzen, wenn das letzte Verbindungsstück zum Erdboden vernichtet wurde.

Das Problem: Es handelt sich um ein TPicture, welches ein 800x600 Bitmap zugewiesen bekommt. Auf diesem Bitmap gibt es die Farben WEISS (Himmel/Luft/Nichts) und beliebige andere Farben, welche homogen als Materie verwendet werden. Es lässt sich also nur prüfen, ob ein Pixel weiß ist oder nicht.

Gibt es einen Algorithmus, welcher nicht-weiße Pixelhaufen ohne Verbindung zum Erdboden erkennt? Ich könnte mir vorstellen, das Bild zeilenweise von unten nach oben zu durchlaufen und beim Auftreten von weiß in einem array zu vermerken, dass Spalte X zu Ende ist. Dann, wenn in der selben Spalte weiter oben wieder Farbe kommt, muss irgendwie überprüft werden, ob diese über einen beliebigen Weg mit dem Boden verbunden ist. Haben also alle Spalten am Ende des Durchlaufs den Wert "Ende erreicht, danach wieder etwas gefunden" oder nur "Ende erreicht", dann sind alle darüber liegenden Pixel unverbunden.

Das Ganze ist aber nicht sicher und auch nicht gerade einfach zu implementieren. Habt ihr vielleicht eine bessere Idee?
JayEff
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2971

Windows Vista Ultimate
D7 Enterprise
BeitragVerfasst: Fr 27.03.09 00:19 
Also eine Idee von mir ist die folgende:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
type myPixel = record
  matter, marked : Boolean;

myArray: [1 .. 8001 .. 600of myPixel;

Per Scanline wird das Array mit Materie/Nicht-Materie gefüllt und marked auf false gesetzt.
Nun wird von oben nach unten durchgegangen und auf materie geprüft. Findest du ein Materiepixel, markierst du es und rekursiv dessen Materie-Nachbarn. Das machst du solange, bis du keine unmarkierten Nachbarn mehr findest. Nun prüfst du die untere Zeile, ob dort ein unmarkiertes Materiepixel sitzt, falls ja, hast du gerade einen Haufen Pixel markiert, der herunterfallen muss. Dieses Herunterfallen führst du durch, dabei könnte dein Haufen auf einem weiteren Haufen liegen bleiben, der *auch* herunterfallen muss, also musst du das ganze so lange wiederholen, bis nichts mehr fallen muss. Aber mir fällt da grad ne Optimierung ein :mrgreen: Einfach umgekehrt machen. Markier rekursiv von allen Pixeln der untersten Zeile. Jedes Pixel, das NICHT fallen muss, wird auf diese Art markiert! Nun musst du nur noch das Fallen der unmarkierten Pixel realisieren.

Performance: Scanline ist ja bekanntlich ziemlich schnell. Mit einem Array kann man auch recht schnell umgehen. Deine rekursive Prozedur ist der Knackpunkt: Das Array darf nicht als Parameter sondern z.B. als Pointer übergeben werden, sonst is das ja auch Blödsinn. (Var-Parameter sind iirc Pointer - Bei Ada sind "in out"-Parameter Pointer und die sind in Delphi äquivalent zu var-Parametern, also hoffe ich mal.)
Die Frage ist, ob die Rekursion nicht zu groß wird. Nun, die Prozedur kann ja nur maximal 800*600 mal aufgerufen werden *insgesammt*. Deine Prozedur wird sich ja für jeden Pixel über, unter, rechts und links vom Aktuellen aufrufen, das heißt... für einen x auf y pixel großen rechteckigen freischwebenden block wird die funktion x*y mal aufgerufen, die Rekursionstiefe wird dabei ... äh, x+y-1 glaube ich, also im Worst-Case 600+800-1 = 1399 als Rekursionstiefe. Sollte eigentlich verkraftbar sein :nixweiss:

Edit: Ich hab's grad mal halbwegs implementiert. Dieser Code hier:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
procedure TForm1.Button1Click(Sender: TObject);
  type arr = array [1 .. 8001 .. 600of Boolean;

  procedure mark(var myArray: arr; x, y : Cardinal);
  begin
    if ((x <= 800and (x >= 1)) and ((y >= 1and (y <= 600)) then
      if not myArray[x, y] then
      begin
        myArray[x, y] := true;
        mark(myArray, x+1,y+1);
        mark(myArray, x+0,y+1);
        mark(myArray, x+1,y+0);
        mark(myArray, x+0,y+0);
      end;
  end;

  var a : arr;
  var x, y : Integer;
  ticks : Cardinal;
begin

  ticks := GetTickCount;

  for x := 1 to 800 do
    for y := 1 to 600 do
      a[x,y] := False;

  Label1.Caption := 'Initialisieren in: ' + IntToStr(GetTickCount - ticks) + 'ms';
  ticks := GetTickCount;

  mark(a, 11);
  Label2.Caption := 'Markiert in: ' + IntToStr(GetTickCount - ticks) + 'ms';
end;

Läuft in 0ms, 16ms bei mir, also markieren dauert gerade mal 16 ms, das sollte man verkraften können :zustimm:

Edit: Neuen Feldversuch gestartet, diesmal hab ich mit Paint ein Bildchen gemalt (war schwarzweiß, tut nichts zur Sache) und dieses in ein Image geladen, dann mit image.[..].ScanLine und dem oben beschriebenem Array gemessen: Die Initialisierung nahm ebenfalls 15 ms im avarage case in Anspruch, lief zwischen 0 und 32 ms :zustimm: Ich würd sagen, von der Laufzeit her ist die Methode benutzbar. Ich hoffe damit kommst du weiter :D

Edit: in meinem code steckt irgendwo ein fehler. :motz: Gib mir mal ne minute

Edit: Stack Overflow :cry:
Ok so gehts nicht, ich versuch das mal ohne rekursion zu lösen ... :motz:

_________________
>+++[>+++[>++++++++<-]<-]<++++[>++++[>>>+++++++<<<-]<-]<<++
[>++[>++[>>++++<<-]<-]<-]>>>>>++++++++++++++++++.+++++++.>++.-.<<.>>--.<+++++..<+.


Zuletzt bearbeitet von JayEff am Fr 27.03.09 01:34, insgesamt 1-mal bearbeitet
P-A-L Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 30



BeitragVerfasst: Fr 27.03.09 01:31 
JayEff, das ist genial! :D

Ich hab's noch nicht ausprobiert, aber allein der Gedanke ist logisch genug! Vielen, vielen Dank! Das Herunterfallen ist dann einfach: Jede Spalte von unten nach oben durchgehen, ausgehend vom letzten markierten y-Wert, und alle gefundenen Pixel so weit wie möglich nach unten verschieben.

Super, ich danke Dir!
JayEff
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2971

Windows Vista Ultimate
D7 Enterprise
BeitragVerfasst: Fr 27.03.09 01:35 
user profile iconP-A-L hat folgendes geschrieben Zum zitierten Posting springen:
JayEff, das ist genial! :D

Ich hab's noch nicht ausprobiert, aber allein der Gedanke ist logisch genug! Vielen, vielen Dank! Das Herunterfallen ist dann einfach: Jede Spalte von unten nach oben durchgehen, ausgehend vom letzten markierten y-Wert, und alle gefundenen Pixel so weit wie möglich nach unten verschieben.

Super, ich danke Dir!
Ja, ich fand mich auch unheimlich toll, bis ich den Stack Overflow bekommen hab weil das nicht rekursiv lösbar ist (Zumindest nicht mit begrenztem Stack ;) ). Ich werd's mal iterativ und damit hässlich versuchen. :motz:

Aber danke für die Blumen :mrgreen:

Edit: Grade ausprobiert, wenn mein "Boden" nicht zu groß ist = weniger rekursive Aufrufe, dann klappt meine Funktion, ich hänge mal mein Projekt mit funktionierender .bmp datei an.

_________________
>+++[>+++[>++++++++<-]<-]<++++[>++++[>>>+++++++<<<-]<-]<<++
[>++[>++[>>++++<<-]<-]<-]>>>>>++++++++++++++++++.+++++++.>++.-.<<.>>--.<+++++..<+.


Zuletzt bearbeitet von JayEff am Fr 27.03.09 01:38, insgesamt 1-mal bearbeitet
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Fr 27.03.09 01:38 
Moin!

Das Ganze sieht mir doch stark nach einer Flächenerkennung aus. ;) Kann man sicher nicht 1:1 gebrauchen, aber der FloodFill-Ansatz aus dem AGS07 sollte vielleicht was bringen. :idea: ;)

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
JayEff
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2971

Windows Vista Ultimate
D7 Enterprise
BeitragVerfasst: Fr 27.03.09 01:47 
Das ist dann meine Idee zusammen mit einem wesentlich effektiveren Algorithmus (Ich glaube mich zu erinnern, dass es im Forum mal einen sehr schnellen FloodFill algo gab) :mrgreen: Dennoch hänge ich hier mal mein Projekt an, auch wenn man die prozedur mark sicherlich wesentlich optimieren kann/muss :)
Einloggen, um Attachments anzusehen!
_________________
>+++[>+++[>++++++++<-]<-]<++++[>++++[>>>+++++++<<<-]<-]<<++
[>++[>++[>>++++<<-]<-]<-]>>>>>++++++++++++++++++.+++++++.>++.-.<<.>>--.<+++++..<+.
F34r0fTh3D4rk
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 5284
Erhaltene Danke: 27

Win Vista (32), Win 7 (64)
Eclipse, SciTE, Lazarus
BeitragVerfasst: So 29.03.09 17:02 
Meist ist es ohnehin sinnvoll seine Pixelhaufen zu gruppieren. Gerade wenn man Dinge wie eine Kollisionserkennung braucht (der folgende Code ist nicht von mir):
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
{
* This program is licensed under the GNU General Public License Version 2
* You should have recieved a copy of the license with this file.
* If not, see http://www.gnu.org/licenses/gpl.txt for more informations
*
* Project: 2D Bounding Boxes
* Author:  Andreas Stoeckel
* File: CollisionRects.pas
* Comment: With this unit you are able to create 2D Boundng boxes on a 2D landscape
}


unit CollisionRects;

interface

uses Windows, SysUtils, Classes, Graphics, Dialogs;

type TRects = array of TRect;

type
  TRGBTripleArray = array[0..32768of TRGBTriple;
  pRGBTripleArray = ^TRGBTripleArray;

//Create Bounding Boxes on a 2D Landscape
procedure MakeRects(img:TBitmap;TransCol:TColor;var rects:TRects);

//Procedure for connecting rects
procedure ConnectRects(var rects:TRects);

const
  RectSize = 10;
  MaxPixelDif = 9;

implementation


//Get the count of pixels in a specifc area of a bitmap
function PixelCount(area:TRect;img:TBitmap):integer;
var x,y:integer;
    p:pRGBTripleArray;
    t:TRGBTriple;
    w,h:integer;
begin
  Result := 0;

  //Look whether the area is in the image's bounds
  w := area.Right;
  if w > img.Width-1 then w := img.Width-1;
  h := area.Bottom;
  if h > img.Height-1 then h := img.Height-1;

  for y := area.Top to h do
  begin
    p := img.ScanLine[y];
    for x := area.Left to w do
    begin
      //If the current pixel is not whitce incrase result
      if p[x].rgbtBlue <> 255 then Result := Result + 1;
    end;
  end;

end;

procedure MakeRects(img:TBitmap;TransCol:TColor;var rects:TRects);
var x,y:integer;
    mask:TBitmap;
    r:TRect;
begin
  //Free the memory of "rects"
  finalize(rects);

  //Create a temporary bitmap
  mask := TBitmap.Create;
  mask.Assign(img);
  mask.Mask(TransCol); //Create a black/white map of the bitmap
  mask.PixelFormat := pf24Bit;  //Set the pixelformat to 24 Bit for Scanline
  //Divide the image into sectors and look whether there is something
  //in the sectors. If there is some land create a new rect.
  for x := 0 to img.Width div RectSize do
  begin
    for y :=  0 to img.Height div RectSize do
    begin
      r := rect(x*RectSize,y*RectSize,(x+1)*RectSize,(y+1)*RectSize);
      if PixelCount(r,mask) >= Sqr(RectSize)-Sqr(MaxPixelDif)+1 then
      begin
        SetLength(Rects,length(rects)+1);
        Rects[high(rects)] := r;
      end;
    end;
  end;

  //Connect the serval rects
  //Vertical
  ConnectRects(Rects);
  //Horizontal
  ConnectRects(Rects);

  mask.Free;
end;

//Procedure which removes a specific element from an dynamic array
procedure DeleteArrayElement(var Rects: TRects; const index: integer);
var i: integer;
begin
  if index < high(Rects) then
  begin
    for i:=index to high(Rects)-1 do
    begin
      Rects[i]:=Rects[i+1];
    end;
  end;
  SetLength(Rects, length(Rects)-1);
end;

//Connect the rects to one big rectangle
procedure ConnectRects(var Rects:TRects);
var i,j,c:integer;
begin
  c := high(rects);
  i := 0;
  while i <= c do
  begin
    j := 0;
    while j <= c do
    begin

      if (rects[j].Left = rects[i].Left) and (rects[j].Right = rects[i].Right) and
         ((rects[i].Top = rects[j].Bottom) or (rects[i].Bottom = rects[j].Top)) then
      begin
        if (rects[i].Top = rects[j].Bottom) then
        begin
          rects[i].Top := rects[j].Top;
        end
        else
        begin
          rects[i].Bottom := rects[j].Bottom;
        end;
        DeleteArrayElement(Rects,j);
        j := j - 1;
      end;

      if (rects[j].Top = rects[i].Top) and (rects[j].Bottom = rects[i].Bottom) and
         ((rects[i].Right = rects[j].Left) or (rects[i].Left = rects[j].Right)) then
      begin
        if (rects[i].Right = rects[j].Left) then
        begin
          rects[i].Right := rects[j].Right;
        end
        else
        begin
          rects[i].Left := rects[j].Left;
        end;
        DeleteArrayElement(Rects,j);
        j := j - 1;
      end;

      j := j + 1;
      c := high(rects);
    end;
    i := i + 1;
    c := high(rects);
  end;
end;



end.

Diese Rectangles berechnet man immer neu, wenn sich in der Szenerie etwas geändert hat. Am besten erweitert man diese Unit noch um eine Funktion die Rectangles nur updatet, anstatt alles neu zu berechnen.
Ich denke damit könnte man noch einiges an Geschwindigkeit (auch beim Finden der "Exklaven") herausholen.

mfg