Autor |
Beitrag |
P-A-L
      
Beiträge: 30
|
Verfasst: 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
      
Beiträge: 2971
Windows Vista Ultimate
D7 Enterprise
|
Verfasst: Fr 27.03.09 00:19
Also eine Idee von mir ist die folgende:
Delphi-Quelltext 1: 2: 3: 4:
| type myPixel = record matter, marked : Boolean;
myArray: [1 .. 800, 1 .. 600] of 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  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
Edit: Ich hab's grad mal halbwegs implementiert. Dieser Code hier:
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 .. 800, 1 .. 600] of Boolean;
procedure mark(var myArray: arr; x, y : Cardinal); begin if ((x <= 800) and (x >= 1)) and ((y >= 1) and (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, 1, 1); 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
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  Ich würd sagen, von der Laufzeit her ist die Methode benutzbar. Ich hoffe damit kommst du weiter
Edit: in meinem code steckt irgendwo ein fehler. Gib mir mal ne minute
Edit: Stack Overflow
Ok so gehts nicht, ich versuch das mal ohne rekursion zu lösen ... 
_________________ >+++[>+++[>++++++++<-]<-]<++++[>++++[>>>+++++++<<<-]<-]<<++
[>++[>++[>>++++<<-]<-]<-]>>>>>++++++++++++++++++.+++++++.>++.-.<<.>>--.<+++++..<+.
Zuletzt bearbeitet von JayEff am Fr 27.03.09 01:34, insgesamt 1-mal bearbeitet
|
|
P-A-L 
      
Beiträge: 30
|
Verfasst: Fr 27.03.09 01:31
JayEff, das ist genial!
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
      
Beiträge: 2971
Windows Vista Ultimate
D7 Enterprise
|
Verfasst: Fr 27.03.09 01:35
P-A-L hat folgendes geschrieben : | JayEff, das ist genial!
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.
Aber danke für die Blumen
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
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: 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.
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
JayEff
      
Beiträge: 2971
Windows Vista Ultimate
D7 Enterprise
|
Verfasst: 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)  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
      
Beiträge: 5284
Erhaltene Danke: 27
Win Vista (32), Win 7 (64)
Eclipse, SciTE, Lazarus
|
Verfasst: 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):
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:
|
unit CollisionRects;
interface
uses Windows, SysUtils, Classes, Graphics, Dialogs;
type TRects = array of TRect;
type TRGBTripleArray = array[0..32768] of TRGBTriple; pRGBTripleArray = ^TRGBTripleArray;
procedure MakeRects(img:TBitmap;TransCol:TColor;var rects:TRects);
procedure ConnectRects(var rects:TRects);
const RectSize = 10; MaxPixelDif = 9;
implementation
function PixelCount(area:TRect;img:TBitmap):integer; var x,y:integer; p:pRGBTripleArray; t:TRGBTriple; w,h:integer; begin Result := 0;
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 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 finalize(rects);
mask := TBitmap.Create; mask.Assign(img); mask.Mask(TransCol); mask.PixelFormat := pf24Bit; 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;
ConnectRects(Rects); ConnectRects(Rects);
mask.Free; end;
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;
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
|
|
|