RayCasting ohne Realzahlen?

Diskussion zum Thema Programmierung unter DOS (Intel x86)
Antworten
Brueggi

RayCasting ohne Realzahlen?

Beitrag von Brueggi »

Ich habe mich einmal ausführlich mit der Problematik einer Raycasting-Engine auseinander gesetzt - und nachdem ich die halbe Nacht Grundlagen gepaukt habe, habe ich eine ganz simple Engine gebastelt. Nachdem diese im Grafikmodus recht lahm war, habe ich sie für den Textmode 80x50 umgestrickt und noch ein Assembler-Programm gebastelt, dass die "Szene" darstellt und gleichzeitig die Farbabstufungen/Schraffuren vornimmt.

Das Problem ist: Die eigentliche Distanzberechnung geht über Realzahlen - und das wird - je größer die Distanz zu Wänden wird - extrem langsam auf meinen 486SX-25. Hat jemand von Euch Profis eine Idee, wie man um dieses ganze Realzahlen-Disaster herumkommt? Mein Ziel ist es, eine einfache, aber schnelle Engine zu basteln, um diese ggf. in zukünftigen Projekten (RPG?) zu nutzen. Nur nervt es mich extremst, dass es eben nur auf dem P1 gut läuft ;-)

Mir geht es auch nicht darum, hier fertige Quellcodes "abzugreifen" - ich erarbeite mir meinen Code lieber durch ausprobieren, Fehlschläge und Erfolge. Aber ein paar hilfreiche Tipps, für einen echten Dummi erklärt - dafür wäre ich echt dankbar :-)
Dateianhänge
Rayast.jpg
Rayast.jpg (40.85 KiB) 2980 mal betrachtet
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: RayCasting ohne Realzahlen?

Beitrag von freecrac »

Brueggi hat geschrieben:Hat jemand von Euch Profis eine Idee, wie man um dieses ganze Realzahlen-Disaster herumkommt? Mein Ziel ist es, eine einfache, aber schnelle Engine zu basteln, um diese ggf. in zukünftigen Projekten (RPG?) zu nutzen. Nur nervt es mich extremst, dass es eben nur auf dem P1 gut läuft ;-)
Ich bin zwar kein Rechenprofi, eher das Gegenteil davon, aber ich würde um das Komma wegzubekommen die Werte mit zehntausend multiplizieren und dann das Ergebniss wieder durch zehntausend teilen. Auf diese Weise kann man sich auch eine Sinus-Tabelle mit reinen Integerwerten anlegen. (Oder dort auch noch weitere Berechnungstufen gleich mit einbauen.)
Beispiel:

Code: Alles auswählen

.MODEL SMALL
.386P
.387
;────────────────────────────────────────────────────────────────────────────
          Scale   = 4
          Grad    = 360 * Scale
          Endtab  = 450 * Scale
          Foktor  = 10000h
;────────────────────────────────────────────────────────────────────────────
  CODE SEGMENT use16 'CODE'
 assume cs:CODE,ds:DATEN,ss:STAPEL
;---------------------------------------------------------------------------
 org 100h
;────────────────────────────────────────────────────────────────────────────
START:    mov     ax, DATEN
          mov     ds, ax
          finit
          call TABLE
          mov     dx, OFFSET TABNAM
          call MAKDAT
          xor     dx, dx
          mov     cx, Endtab*4
          call WRITE
          call CLOSE
;---------------------------------------------------------------------------
          xor     al, al              ; kein Fehler
DOS:      mov     ah, 4Ch             ; Dos-Rücksprung, Programm-Ende
          int   21h
;────────────────────────────────────────────────────────────────────────────
;                      U N T E R - R O U T I N E N
;────────────────────────────────────────────────────────────────────────────
 org START + ((($-START)/16)*16)+16
;─────────────────────────────────────────────────────────────────────────────
;                          S i n u s - T a b e l l e
;─────────────────────────────────────────────────────────────────────────────
TABLE:    xor     di, di              ; Sinus-Tabelle anlegen
;-------------------------------------
TAB:      fldpi                       ; PI laden
          fimul  DWORD PTR[I]         ; Zaehler mal PI
          fidiv  DWORD PTR[TEIL]      ; durch 180(INT) teilen
          fsin                        ; davon den Sinus
          fimul  DWORD PTR[FAKT]
          fistp  DWORD PTR[di]        ; in die Sinus-Tabelle schreiben
;-------------------------------------
          inc    WORD PTR[I]          ; Grad-Zähler erhöhen
          add     di, 4               ; Tabellen-Index erhöhen
          cmp    WORD PTR[I], Endtab  ;  Tabellen-Ende erreicht ?
          jnz TAB
          ret
;─────────────────────────────────────────────────────────────────────────────
 org START + ((($-START)/16)*16)+16
;----------------------------------------------------------------------------              
MAKDAT:   mov     ah, 3Ch             ;  Datei erstellen  (ah=3C/5B)
          xor     cx, cx              ;  ---------------
          int  21h
          mov     bx, ax
          ret
;─────────────────────────────────────────────────────────────────────────────
 org START + ((($-START)/16)*16)+16
;----------------------------------------------------------------------------
WRITE:    mov     ah, 40h             ;  Datei beschreiben
          int   21h
          ret
;─────────────────────────────────────────────────────────────────────────────
 org START + ((($-START)/16)*16)+16
;----------------------------------------------------------------------------              
CLOSE:    mov     ah, 3Eh             ;  Datei schließen
          int   21h
          ret
;---------------------------------------------------------------------------
  CODE ends
;────────────────────────────────────────────────────────────────────────────
;                       D A T E N - B E R E I C H
;────────────────────────────────────────────────────────────────────────────
  DATEN SEGMENT use32 'DATA'
 org 0
SINTAB DB Endtab DUP (?,?,?,?)
;-------------------------------------
TEIL   DW Grad/2, ?
I      DW 0, 0
FAKT   DD Foktor
TABNAM DB "Sin2Int.tab", 0
  DATEN ends
;────────────────────────────────────────────────────────────────────────────
  STAPEL SEGMENT use16 STACK 'STACK'
       DB 10h dup (?)
;---------------------------------------------------------------------------
  STAPEL ends
 end
Dirk
Zuletzt geändert von freecrac am Di 15. Jan 2013, 13:33, insgesamt 1-mal geändert.
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: RayCasting ohne Realzahlen?

Beitrag von DOSferatu »

(Im Code steht übrigens 10000h - was auch sinnvoller ist - nämlich 65536dez)
Es geht darum: Man benutzt einfach FESTKOMMA - was soviel bedeutet wie: GAR KEIN KOMMA.
Will sagen: Man benutzt z.B. 32bit Zahlen, die oberen 16 Bit stellen das "Vorkomma" dar, die unteren 16bit das "Nachkomma". Kann man natürlich beliebig variieren, wieviel Vorkomma- und Nachkomma-Bits man haben will, je nach Gesamtgröße der "Objekte" (oder Levels) und der gewünschten Genauigkeit. Auf jeden Fall ist Festkommaberechnung WESENTLICH schneller als Fließkomma - und gerade im Assemblerbereich würde ich mich auf Fließkomma gar nicht erst einlassen. (Ich könnte das zwar - aber wozu sollte ich?)
Bei "Festkomma"berechnung rechnet man quasi einfach mit Integerzahlen. Man denkt sich das Komma dazu. Selbst im Hochsprachenbereich vermeide ich Fließkomma, wo ich nur kann.

Und ja: Sinustabellen sind/waren schon immer eine hervorragende Idee. Sinus/Cosinus BERECHNEN lassen ist die reinste Pestilenz, das klaut Rechenzeit ohne Ende. Achja, und dabei ist es eine gute Idee, Winkel nicht mit Reichweite 0 bis 359 Grad oder sowas anzugeben, sondern mit einer Reichweite, die einer 2er Potenz entspricht, z.B. 0 bis 255, 0 bis 1023, 0 bis 8191 oder so etwas (je nachdem, wie genau man es braucht).


Zusatzbemerkung:
Leider führt "Hochsprachen-Only-Coding" (verzeiht dieses Wortkonstrukt), dadurch, daß "ja alles irgendwie geht" oft zum Programmieren von "Worst Case Szenarien", also, daß man Sourcecode bastelt, der von außen "schön und elegant" wirkt, weil er so klein ist und wie ein Gedicht aussieht - aber in compiliertem Zustand dann Code mit maximalem Aufwand an Ressourcen (Rechenzeit, Speicherplatz) erzeugt.

Ein Beispiel: Es gibt Programmiersprachen, die bieten Variablentypen wie "VARIANT" an. Klingt für den Programmierer toll: Es ist ein Datentyp, der ALLES enthalten kann: Strings, Integers, Fließkomma, Booleans, vielleicht sogar Matrizen. Denkt man aber einmal kurz darüber nach, was die Verwendung eines solchen Datentyps bedeutet, ergibt sich, daß bei jeder Verwendung einer Variable dieses Typs zu verschiedenen Zwecken viel Code (und Ausführungszeit) benötigt wird, um die Daten in das jeweils benötigte Format zu wandeln bzw zu prüfen, ob dies möglich ist, usw... - was genau DANN ein Problem (Geschwindigkeit, Codegröße) werden kann, wenn man so etwas - aus Bequemlichkeit - für JEDES und ALLES benutzt, anstatt nur da, wo absolut notwendig.

Ein anderes Beispiel: Compiler bieten an, die sogenannte Bereichsüberprüfung einzubauen - d.h. daß eine Variable nicht den für sie vorgesehenen Zahlenbereich überschreitet (und damit wrapt). Nette Idee für das DEBUGGEN von Programmen - aber nicht für ein fertig laufendes. Warum? Bei JEDER Verwendung einer Variable solcher Typen in einer Formel/Berechnung wird eine solche Bereichsüberprüfung durchgeführt - man muß kein Genie sein, um sich auszumalen, daß solche Dinge Zeit kosten. Besser ist es, gleich selbst durch die Programmierung dafür zu sorgen, daß solche Fälle nicht auftreten - oder, wenn dies nicht ohne weiteres möglich ist (da manche Werte erst zur Laufzeit bekannt sind, z.B. Usereingaben), die Bereiche SELBST zu prüfen, und dann nur einmal und nur an dieser Stelle.

(Abgesehen davon kann man mit dem Wrappen von Werten über den definierten Zahlenbereich hinaus oftmals auf einfachste Weise Dinge bewirken, die man anderenfalls umständlich "außenherum" programmieren müßte. Ein Beispiel? OK, ein einfaches: Nehmen wir an, das Ergebnis einer Berechnung landet in einer 16bit-Integer-Variable. Nun soll getestet werden, ob diese zwischen einschließlich 0 und 999 liegt. Also: IF (I>=0) AND (I<=999) THEN... Man kann aber auch an dieselbe Stelle im Speicher eine WORD (unsigned integer) legen und fragt einfach ab: IF (W<=999) THEN... oder auch IF W<1000) THEN... Das gleiche natürlich in Assembler. Spart schon wieder eine Abfrage. Wer weiß, wie negative Zahlen im Computer gespeichert werden, weiß auch, warum das funktioniert... Das geht aber natürlich nur, wenn die Datentypen feste binäre Größen haben und klar ist, wie die Zahlen abgelegt sind...) Das sind so schöne Dinge, an die ich manchmal denke, und auf die Leute, die "nur Hochsprache" oder "nur Skriptsprache" programmieren, gar nicht erst kommen, weil sich manche von denen mit Binärarithmetik gar nicht mehr auskennen...

Achja: Alles, was ich ab "Zusatzbemerkung:" geschrieben habe, gilt selbstredend nur, wenn das ausführende Zielsystem bekannt ist. Durch systemübergreifende Programmierung verbieten sich quasi die meisten dieser o.g. Dinge von selbst - d.h. hier können solche Dinge, die Performance erhöhen und Speicherverbrauch verringern, meist nicht genutzt werden (weshalb ich auch nicht gerade ein glühender Verehrer systemferner/systemübergreifender Programmierung bin - aber das ist jetzt nicht so wichtig).

Achja, seufz... ich hätte vielleicht gar nicht so viel schreiben sollen. War gerade wieder so in Fahrt...
freecrac
DOS-Guru
Beiträge: 861
Registriert: Mi 21. Apr 2010, 11:44
Wohnort: Hamburg Horn

Re: RayCasting ohne Realzahlen?

Beitrag von freecrac »

DOSferatu hat geschrieben:(Im Code steht übrigens 10000h - was auch sinnvoller ist - nämlich 65536dez)
ja richtig das stimmt natürlich, in der Praxis müssen wir dieses Prinzip noch an die binäre Welt anpassen.
Danke für den Hinweis. Das hatte ich ja wirklich vergessen zu erwähnen.
Aber im Code-Beispiel wird es zum Glück schon etwas deutlicher mit welchen Werten wir es zu tun bekommen.

Dirk
Antworten