"Kleines 3D"
Verfasst: 20.11.2006 21:07
Mich hatte am Wochendende die Wut gepackt und ich musste spontan das Basiswissen über Vektoren lernen. Nach dem ich enttäuscht feststellen musste, dass das Hautpproblem ja ohnehin das Rendern ist, habe ich mich dem Rendern zugewandt.
Ein zufällig generiertes Bild aus Wire-Frame-Polygonen:

Der Code ist noch nicht vollständig und das 3D-Objekt kann bisher nur auf der Y-Achse rotiert werden. Ich werde den Code eigenmächtig erweitern und versuchen, irgendwie eine Art Renderer hinzubekommen...
Ich bin offen für Vorschläge jeder Art
. Aber ich möchte keinen hier erwischen, der hier ganze 3D-Berechnungen reinstellt
. Höchstens ein paar Denkanstöße 
Das Ergebnis (unvollständig) sieht bisher so aus:
Ein zufällig generiertes Bild aus Wire-Frame-Polygonen:

Der Code ist noch nicht vollständig und das 3D-Objekt kann bisher nur auf der Y-Achse rotiert werden. Ich werde den Code eigenmächtig erweitern und versuchen, irgendwie eine Art Renderer hinzubekommen...
Ich bin offen für Vorschläge jeder Art



Das Ergebnis (unvollständig) sieht bisher so aus:
Code: Alles auswählen
;{-iso3d
Structure gVector
X.f
Y.f
Z.f
color.l
xPlot.l;letztes x auf screen
yPlot.l;letztes y auf screen
EndStructure
Structure gPolygon
Alpha.gVector
Betha.gVector
Gamma.gVector
EndStructure
Structure gQuad
Alpha.gVector
Betha.gVector
Gamma.gVector
Delta.gVector
EndStructure
Structure gCam
X.f ;x-verschiebung des mittelpunktes
Y.f ;y-verschiebung des mittelpunktes
Rotation.l ;die rotation der kamera um den mittelpunkt.
EndStructure
Structure gScreen
aspectX.f;aspekt von briete und höhe (kleinste seite = 1.0
aspectY.f;
UnitVectorSize.f;größe eines einheitsvektor auf dem bildschirm (abhängig von der bildschirmgröße)
sizeX.l;größte des bildschirms
sizeY.l;
UVsizeX.f;vorberechnete UnitVectorGrößen fürs Rendern
UVsizeY.f;
EndStructure
Structure gScene
gMaxUnitSize.l ;größte größe eines vektorgebildes
p_gSceneVectors.l ;zeiger auf szenen-speicher (wird nur bei alloziieren verändert)
p_gLastChangedVector.l ;pointer auf letzten veränderten vektor (schneller beim speichern)
gSceneSize.l ;größe des Szenenspeichers
gVectorsInScene.l ;anzahl vektoren im Szenenspeicher
gMaxPossibleVectors.l ;maximale anzahl von vektoren, die in eine szene passen
EndStructure
;{-konstanten
#gType_Vec = 0
#gType_Poly = 1
#gType_PolyEnd = 2
#gType_Quad = 3
#gType_Line_Part = 3 ;teil oder anfang einer linie
#gType_Line_End = 4 ;endstück einer linie (MUSS immer letztes element sein
#gPoke_X = 1
#gPoke_Y = #gPoke_X + SizeOf(Float)
#gPoke_Z = #gPoke_Y + SizeOf(Float)
#gPoke_Col = #gPoke_Z + SizeOf(Float)
#gFlags_Non = 0
#gFlags_DrawAxis = 1
#gFlags_SetUpScreen_StretchUV = 2
#gDegree_45 = 0.785398
#gDegree_90 = 1.570796
#gCOLOR_AXIS_TEXT = $FFFFFF
#gCOLOR_X_AXIS = $FF0000
#gCOLOR_Y_AXIS = $0000FF
#gCOLOR_Z_AXIS = $00FF00
#gAXIS_X_VECTOR_X = 1
#gAXIS_X_VECTOR_Y = 0
#gAXIS_X_VECTOR_Z = 0
#gAXIS_Y_VECTOR_X = 0
#gAXIS_Y_VECTOR_Y = 1
#gAXIS_Y_VECTOR_Z = 0
#gAXIS_Z_VECTOR_X = 0
#gAXIS_Z_VECTOR_Y = 0
#gAXIS_Z_VECTOR_Z = 1
;}-
Procedure.f AngleToSin(angle.w)
s.f = (#PI / 180) * angle
ProcedureReturn s
EndProcedure
Procedure gSetUpScreen(*screen.gScreen, sizeX.l,sizeY.l, uvs.f, flags)
*screen\sizeX = sizeX
*screen\sizeY = sizeY
If *screen\sizeX > *screen\sizeY
*screen\aspectX = *screen\sizeX / *screen\sizeY
*screen\aspectY = 1.0
ElseIf *screen\sizeY > *screen\sizeX
*screen\aspectX = 1.0
*screen\aspectY = *screen\sizeY / *screen\sizeX
EndIf
*screen\UnitVectorSize = uvs
If flags & #gFlags_SetUpScreen_StretchUV
;vorberechnete UV-Größen (Einheitsvektorgröße auf dem Bildschirm)
*screen\UVsizeX = *screen\UnitVectorSize * *screen\aspectX
*screen\UVsizeY = *screen\UnitVectorSize * *screen\aspectY
Else
*screen\UVsizeX = *screen\UnitVectorSize
*screen\UVsizeY = *screen\UnitVectorSize
EndIf
EndProcedure
Procedure gNewScene(*scene.gScene, vectors.l, gMaxUnitSize=1+SizeOf(gQuad))
*scene\gMaxUnitSize = gMaxUnitSize
*scene\p_gSceneVectors = AllocateMemory(vectors * gMaxUnitSize)
*scene\p_gLastChangedVector = *scene\p_gSceneVectors
*scene\gMaxPossibleVectors = vectors
*scene\gVectorsInScene = 0
*scene\gSceneSize = vectors * *scene\gMaxUnitSize
EndProcedure
Procedure gAddVectorDirect(*scene.gScene, vectortype.b, X.f, Y.f, Z.f, c.l)
;pointer auf letzten vektor um eine unitgröße erhöhen (wenn überhaupt gesetzt)
If *scene\gVectorsInScene > 0
*scene\p_gLastChangedVector + *scene\gMaxUnitSize
EndIf
;type poken
PokeB(*scene\p_gLastChangedVector, vectortype)
;daten sichern
PokeF(*scene\p_gLastChangedVector + #gPoke_X, X)
PokeF(*scene\p_gLastChangedVector + #gPoke_Y, Y)
PokeF(*scene\p_gLastChangedVector + #gPoke_Z, Z)
PokeL(*scene\p_gLastChangedVector + #gPoke_Col, c)
*scene\gVectorsInScene + 1
EndProcedure
Procedure.s gGetTypeString(vectortype.b)
Select vectortype
Case #gType_Poly: ProcedureReturn "Poly"
Case #gType_Quad: ProcedureReturn "Quad"
Case #gType_Vec: ProcedureReturn "Vec"
EndSelect
EndProcedure
Procedure.s gGetSceneContentAsString(*scene.gScene)
Protected back.s, *p
*p = *scene\p_gSceneVectors
back.s + "*gSceneVector: " + Str(*p) + Chr(10) + Chr(13)
back.s + "*gLastChangedVector: " + Str(*scene\p_gLastChangedVector) + Chr(10) + Chr(13)
back.s + "vectors in scene: " + Str(*scene\gVectorsInScene) + " from max. " + Str(*scene\gMaxPossibleVectors) + Chr(10) + Chr(13)
back.s + Chr(10) + Chr(13)
For Z = 0 To *scene\gVectorsInScene-1
back.s + "#" + Str(Z+1) + Chr(10) + Chr(13)
back.s + "type: " + gGetTypeString(PeekB(*p)) + Chr(10) + Chr(13)
back.s + "x: " + Str(PeekF(*p+#gPoke_X)) + Chr(10) + Chr(13)
back.s + "y: " + Str(PeekF(*p+#gPoke_Y)) + Chr(10) + Chr(13)
back.s + "z: " + Str(PeekF(*p+#gPoke_Z)) + Chr(10) + Chr(13)
back.s + Chr(10) + Chr(13)
*p + *scene\gMaxUnitSize
Next
ProcedureReturn back
EndProcedure
Procedure gRenderSzene(*scene.gScene, *screen.gScreen, xRender.l,yRender.l, yRot.f, xRot.f, zRot.f, flags.b=#gFlags_Non)
Protected *p
Protected *gPoly.gPolygon, *gQuad.gQuad, *gVec.gVector, *gLastVec.gVector, *gLastVec2.gVector, *gLastVec3.gVector
Protected type.l, lasttype.l, lasttype2.l, lasttype3.l
;x und y sind der mittelpunkt auf dem bildschirm...
;resolution ist die auflösung der szene, d.h. wie viele einheitsvektoren auf einem bild platz finden...
;arbeitspointer zuweisen
*p = *scene\p_gSceneVectors
If flags & #gFlags_DrawAxis
;x-achse
uvXx = xRender + Cos(yRot) * *screen\UVsizeX
uvXy = yRender + Sin(yRot) * *screen\UVsizeY
;y-achse
uvYx = xRender
uvYy = yRender - *screen\UnitVectorSize
;z-achse
uvZx = xRender + Cos(yRot + #gDegree_90) * *screen\UVsizeX
uvZy = yRender + Sin(yRot + #gDegree_90) * *screen\UVsizeY
;Von 0-Punkt aus Rendern
;~~~~~~~~~~~~~~~~~~~~~~~
LineXY(xRender,yRender, uvXx,uvXy, #gCOLOR_X_AXIS);(blau)
LineXY(xRender,yRender, uvYx,uvYy, #gCOLOR_Y_AXIS);(rot)
LineXY(xRender,yRender, uvZx,uvZy, #gCOLOR_Z_AXIS);(grün)
DrawText(uvXx,uvXy, "X", #gCOLOR_AXIS_TEXT, #gCOLOR_X_AXIS)
DrawText(uvYx,uvYy, "Y", #gCOLOR_AXIS_TEXT, #gCOLOR_Y_AXIS)
DrawText(uvZx,uvZy, "Z", #gCOLOR_AXIS_TEXT, #gCOLOR_Z_AXIS)
EndIf
;wenn vektoren vorhanden, alle durchgehen und rendern
If *scene\gVectorsInScene > 0
For Z=0 To *scene\gVectorsInScene-1
;{comment
;Render-Theorie:
;~~~~~~~~~~~~~~~~~~~~~~~~~~~
;Zuallererst berechnen wir die X-Position. X ist im Prinzip auch nur ein Punkt, der sich
;auf einem Kreis bewegt -> die Größe des Kreises ist dabei abhängig von der X-Koordinate
;des Vektors. Somit benutzen wir ganz normale Kreis-Punkt-Positionen. Die Rotation bezieht
;sich dabei auf die Rotation des Y-Achse (da diese die Position auf dem Kreis MITBESTIMMT!)
;
; M = Mittelpunkt
;
; Mx + cos Y-Rotation * X-Koordinate * Pixellänge eines Einheitsvektors
; My + sin Y-Rotation * X-Koordinate * Pixellänge eines Einheitsvektors
;
;Es ist wichtig, nur die X-Koordinaten im ersten Schritt zu nehmen, da die Kreisgröße sich
;in dieser orthogonalen Berechnung auf einen statischen Radius beschränkt, d.h. der Radius
;ist auf dem Bildschirm IMMER gleich.
;
;Danach folgt die Zurechnung der Y-Koordinaten! Da bei dieser einfachen Darstellung auf eine
;Z/X-Rotation verzichtet wird, kann ruhig nur die Y-Position des Vektors verändert (die auf
;dem Bildschirm!). Dabei müssen wir ganz einfach die Y-Koordinate im Raum von Y abziehen
;
; My - Y-Koordinate * Pixellänge eines Einheitsvektors
;
;Letzten Endes wird von dem nun besthenden Punkt (der im Moment nur eine 2dimensionale
;auf einer X/Y-Achse im Raum ist) eine Schlenker um 90 in Richtung der Innen-Raumes machen.
;Klingt seltsam, ist aber einfach. Da wir uns schon auf dem Kreis der X-Koordinate befinden,
;müssen wir vom Kreispunkt 90° abzweigen (da Z senkrecht zu X steht). Noch die X-Berechnung
;in Erinnerung? Ist ähnlich, da wir ja sozusagen einen zweiten Kreis erstellen müssen (dessen
;Punkt - wie schonmal zwei mal erwähnt - um 90 Grad versetzt ist als der Original-Punkt).
;
; Mx + cos (Y-Rotation + 90) * Z-Koordinate * Pixellänge eines Einheitsvektors
; My + sin (Y-Rotation + 90) * Z-Koordinate * Pixellänge eines Einheitsvektors
;
;}
;typ ziehen und arbeitsvektor setzen (*gVec)
type = PeekB(*p)
*gVec = *p + 1
;position auf x-achse setzen
*gVec\xPlot = xRender + Cos(yRot) * *gVec\X * *screen\UVsizeX
*gVec\yPlot = yRender + Sin(yRot) * *gVec\X * *screen\UVsizeY
;position auf y-achse erweitern
*gVec\yPlot - *gVec\Y * *screen\UnitVectorSize
;position auf z-achse erweitern
*gVec\xPlot + Cos(yRot + #gDegree_90) * *gVec\Z * *screen\UVsizeX
*gVec\yPlot + Sin(yRot + #gDegree_90) * *gVec\Z * *screen\UVsizeY
;einzeichnen (+ flächen rechnen)
Select type
Case #gType_Vec
Circle(*gVec\xPlot, *gVec\yPlot, 4, *gVec\color)
Case #gType_Poly
;wenn letzter vector auch vom polygon, dann
If lasttype = #gType_Poly
LineXY(*gLastVec\xPlot, *gLastVec\yPlot, *gVec\xPlot,*gVec\yPlot, *gLastVec\color) ;vom jetzigen zum letzten nachziehen
EndIf
;diesen vektor für den nächsten vektor speichern
*gLastVec3 = *gLastVec2
*gLastVec2 = *gLastVec
*gLastVec = *gVec
Case #gType_PolyEnd
;wenn letzter überhaupt ein polygon
If lasttype = #gType_Poly
LineXY(*gLastVec\xPlot, *gLastVec\yPlot, *gVec\xPlot,*gVec\yPlot, *gLastVec\color);vom jetzigen zum letzten nachziehen
LineXY(*gLastVec2\xPlot, *gLastVec2\yPlot, *gVec\xPlot,*gVec\yPlot, *gVec\color);vom jetzigen zum ersten
EndIf
;diesen vektor für den nächsten vektor speichern
*gLastVec3 = *gLastVec2
*gLastVec2 = *gLastVec
*gLastVec = *gVec
EndSelect
;pointer auf nächsten vektor erhöhen
*p + *scene\gMaxUnitSize
;letze typen speichern (wir benötigen insgesamt 4 variablen wegen poly(3vec) und quad(4vec)
lasttype3 = lasttype2
lasttype2 = lasttype
lasttype = type
Next
EndIf
EndProcedure
Procedure.l GetVector(*scene.gScene, vektorindex.l)
ProcedureReturn *scene\p_gSceneVectors + vektorindex * *scene\gMaxUnitSize
EndProcedure
;}-
;-deklarieren
Declare CreateScene()
;-initialisieren
InitSprite()
InitKeyboard()
Global myScreen.gScreen
Global myScene.gScene
Global mySceneRotation.l
;-fenster + szene erstellen
CreateScene()
mySceneRotation = 45
;-hauptschleife
Repeat
;event-handling
Select WindowEvent()
Case #PB_Event_CloseWindow: End
EndSelect
;rendern
ClearScreen(0)
StartDrawing(ScreenOutput())
gRenderSzene(@myScene, @myScreen, myScreen\sizeX/2,myScreen\sizeY/1.3, AngleToSin(mySceneRotation),AngleToSin(0),AngleToSin(0), #gFlags_DrawAxis)
StopDrawing()
FlipBuffers()
;delay
Delay(1)
mySceneRotation + 1
If mySceneRotation >= 359
mySceneRotation = 0
EndIf
ForEver
; Procedure MoveThem()
;
;-prozeduren
Procedure CreateScene()
gSetUpScreen(@myScreen, 640,480, 40, #gFlags_Non)
;RGB(255,255,255)
gNewScene(@myScene, 3)
For Z = 0 To 6
gAddVectorDirect(@myScene, #gType_Poly, Random(4),Random(4),Random(3), RGB(255,255,255))
gAddVectorDirect(@myScene, #gType_Poly, Random(4),Random(4),Random(3), RGB(255,255,255))
gAddVectorDirect(@myScene, #gType_PolyEnd, Random(4),Random(4),Random(3), RGB(255,255,255))
Next
OpenWindow(0, 0,0, myScreen\sizeX,myScreen\sizeY, "iso3d", #PB_Window_ScreenCentered|#PB_Window_SystemMenu|#PB_Window_TitleBar)
OpenWindowedScreen(WindowID(0), 0,0, myScreen\sizeX,myScreen\sizeY, 0,0,0)
EndProcedure