Meine Erkenntnisse bis jetzt:
Ich habe begonnen mit einer FireMonkey3D-Demo namens Arrows3D, siehe
http://cc.embarcadero.com/item/29148.
Bei einer Projektion vom 3-dimensionalen Raum auf einen Bildschirm kann man sich zwischen einr Parallelprojektion
und einer Zentralprojektion entscheiden.
FireMonkey3D benutzt eine Zentralprojektion: Der Bildpunkt P' von P ist der Schnittpunkt der Gerade OZ mit der
Bildebene E: P' = OZ geschnitten E.
Z ist das Projektionszentrum, bei Firemonkey die (absolute) Position der Camera.
Die Bildebene E ist die Senkrechte zur Camera-Richtung und geht durch den Nullpunkt.
In der Bildebene E legt man einen "Ursprung" VP und zwei Basisvektoren VH (horizontal) und VV (vertikal) fest.
Die Koordinaten des Bildpunkts P' sind dann s und t, wenn der Ortsvektor von P' gleich dem von VP + r*VH + s*VV ist.
Die Umwandlung von P' in Bildschirmpixel geschieht dann anhand einer "Karte":
Sie legt ein reelles Intervall [a,b] für die s-Koordinate und [c,d] für die t-Koordinate fest,
Randpixel pa (linker Rand) und Pd (oberer Rand) und es bzw. es (Pixel pro s-Einheit bzw. Pixel pro t-Einheit).
Sei [XVon, XBis] x [YVon, YBis] x [ZVon, ZBis] der sichtbare Bereich der x- y- und z-Achse.
Das kann man folgendermaßen in Delphi aufschreiben:
type
TKarte = record {Daten zur Transf.von xy-Koordinaten in Pixel}
a,b,c,d :Single; {sichtbares Rechteck}
pa,pd :integer; {Pixel von (a,d) = linke obere Ecke}
es,et :LongInt; {Pixel pro Einheit}
end;
TProjTyp=(parallel,zentral); {Parallel- oder Zentralprojektion }
TProjDaten=record {Proj.ebene: VX = VP+tVH+sVV }
PT:TProjTyp;
VS,VP,VH,VV:TVector3D; {VS: Proj.richtung bzw. -zentrum }
end;
var
Projektion: TProjDaten;
Karte:TKarte;
function XPix(s:Single):Single; {Umrechnung von s,t in Pixel}
begin
with Karte do
XPix:=pa+ex*(s-a);
end;
function YPix(t:Single):Single;
begin
with Karte do
YPix:=pd+ey*(d-t);
end;
procedure normiere(var v:TVector3D);
var t:Single;
begin
t:=sqrt(v.X*v.X+v.Y*v.Y+v.Z*v.Z);
if t<>0 then
begin
v.X:=v.X/t;
v.Y:=v.Y/t;
v.Z:=v.Z/t;
end else
raise
Exception.Create('Versuch,Nullvektor zu normieren!');
end;
procedure SetPDC(Camera:TCamera; w,h:Single); {setzt Projektion gemäß Camera}
var
N,V : TVector3D;
PD : TProjDaten;
begin
Karte.a:=XVon*1.25; // ist der tatsächlich in Viewport3D sichtbare Bereich
Karte.b:=XBis*1.25;
Karte.c:=YVon*1.1;
Karte.d:=YBis*1.1;
Karte.pa:=0;
Karte.pd:=0;
Karte.ex:=Round(w/(Karte.b-Karte.a)); //w=Viewport3D1.Width);
Karte.ey:=Round(h/(1*(YBis-YVon))); //h=Viewport3D1.Height);
PD.PT:=zentral;
PD.VS:=Camera.AbsolutePosition; {Projektions-Zentrum Z}
PD.VP.X:=0; {Ursprung des Koordinatensystems der Bildebene}
PD.VP.Y:=0;
PD.VP.Z:=0;
N:=Camera.AbsoluteDirection;
Normiere(N);
V.X:=-N.Y*N.X;
V.Y:=1-N.Y*N.Y;
V.Z:=-N.Y*N.Z;
Normiere(V);
PD.VV:=V;
PD.VH:=N.CrossProduct(V);
Projektion:=PD;
end;
procedure project(x,y,z:Single; var s,t:Single);
var
d,dx,dy:Single;
U1,U2,VH0,VV0:TVector3D;
k:integer;
begin
with Projektion do
begin {Berechnung d.Schnittpunkts}
if PT=zentral then
begin
U1.X:=VS.X-w.X;
U1.Y:=VS.Y-w.Y;
U1.Z:=VS.Z-w.Z;
end else
begin
U1.X:=VS.X; {U1=RichtungsTVector3D}
U1.Y:=VS.Y;
U1.Z:=VS.Z;
end;
U2.X:=w.X-VP.X; {U2=rechte Seite des LGS}
U2.Y:=w.Y-VP.Y;
U2.Z:=w.Z-VP.Z;
VH0:=VH;
VV0:=VV;
normiere(VH0);
normiere(VV0);
d:=det(U1,VH0,VV0); {Determinate}
dx:=det(U1,U2,VV0);
dy:=det(U1,VH0,U2);
if d=0 then
raise
Exception.Create('Projektionsgerade parallel zur Ebene! Prozedur abgebrochen')
else begin
s:=dx/d;
t:=dy/d;
end;
end;
end;
Der letzte Schritt (Umrechnung von s,t in Pixel) ist aber noch verbesserungsbedürftig.
WorldToScreen ist da genauer.