/*
OneLoneCoder - 3D Graphics Engine Part #3 - Cameras & Clipping
Version 1.0
From Leyvin
-----------
3D Graphics is a fascinating subject, which today the fundamentals tend
to be obfuscated by both 3D Acceleration Hardware and Graphics APIs.
This project (based upon JavidX9's excellent YouTube Series) is showcasing
how to produce the same results in AppGameKit BASIC (Tier 1).
This strictly isn't practical for a Modern 3D Game, as performance isn't
going to be as good as Native 3D Acceleration... but I think from an educational
or curisoity standpoint it's quite interesting and satisfying.
Still there are elements that are useful if you plan to do more "Low-Level"
Graphics, such-as OpenGL Shader Language for Custom Effects.
As you'll start to understand What and Why things like the Model, View,
Translation and Projection Matrix exist.
This version doesn't include the ConsoleGameEngine.agc, which expands further
into the topic of building a Rasterisation Engine with Memory Blocks.
Keep an eye out for Version 2.0 if that is something that interests you.
This uses AppGameKit Advanced Array from AppGameKit v3 ("Studio") and so might
not work in AppGameKit v2 ("Standard") and will not work in v1 ("Classic").
*/ // Last Updated: 16.03.2020
#insert "agkcommon.agc" // generic AppGameKit stuff
Type Vector_t
x As Float
y As Float
z As Float
w As Float // = 1.0 // ideally we want to do this so this always starts at 1.0 but we can't :'(
// so just remember this is the case
EndType
Type Triangle_t
depth As Float
p As Vector_t[3]
Color As Integer
EndType
Type Mesh_t
tris As Triangle_t[]
EndType
Type Matrix_t
m As Float[4,4]
EndType
agk_Create( 1024, 960, "3D Game Engine", AGK_DEBUG_MODE_DISABLE )
// OnCreate( ) Prototype
Function OnCreate()
Global iRasterMode As Integer = 1
// Add Creation Nonsense Here
Global meshCube As Mesh_t
Global matrixProj As Matrix_t
Global vCamera As Vector_t : Vector_Set( vCamera, 0.0, 0.0, 0.0, 0.0 )
Global fTheta As Float
// Load Object From File
LoadFromObjectFile( meshCube, "raw:E:\AGK\OneLoneCoder\OLC 3D Graphics Engine\media\teapot.obj" )
Global bSetFill As Integer
// Create Projection Matrix
Matrix_MakeProjection( matrixProj, 90.0, ((GetDeviceHeight() * 1.0) / (GetDeviceWidth() * 1.0)), 0.1, 1000.0 )
EndFunction AGK_TRUE
// OnUpdate( ) Prototype
Function OnUpdate( fElapsedTime As Float )
// Main Loop Actions
Local sOutput As String = " "
// Setup Rotation and Translation Matrices
Local matrixRotZ As Matrix_t
Local matrixRotX As Matrix_t
Inc fTheta, (0.1 * fElapsedTime)
Matrix_MakeRotationX( matrixRotX, fTheta )
Matrix_MakeRotationZ( matrixRotZ, fTheta )
Local matrixTrans As Matrix_t
Matrix_MakeTranslation( matrixTrans, 0.0, 0.0, 8.0 )
// Remember Order ... Identity >> RotationZ>Y>X >> Translation
// this is important as if you don't rotate then translate; things get a bit "weird" ;)
Local matrixWorld As Matrix_t
Matrix_MakeIdentity( matrixWorld )
Matrix_MultiplyMatrix( matrixWorld, matrixRotZ, matrixRotX )
Matrix_MultiplyMatrix( matrixWorld, matrixWorld, matrixTrans )
// Triangle Raster Buffer
Local vecTriangleToRaster As Triangle_t[]
For this = 0 To meshCube.tris.length
// Calculate World Transformation
Local triProjected As Triangle_t
Local triTransformed As Triangle_t
Matrix_MultiplyVector( triTransformed.p[0], matrixWorld, meshCube.tris[this].p[0] )
Matrix_MultiplyVector( triTransformed.p[1], matrixWorld, meshCube.tris[this].p[1] )
Matrix_MultiplyVector( triTransformed.p[2], matrixWorld, meshCube.tris[this].p[2] )
// Calculate Normals
Local Normal As Vector_t
Local Line1 As Vector_t
Local Line2 As Vector_t
// Get lines either side of the triangle
Vector_Sub( Line1, triTransformed.p[1], triTransformed.p[0] )
Vector_Sub( Line2, triTransformed.p[2], triTransformed.p[0] )
// Take the Cross Product to get Normal
Vector_CrossProduct( Normal, Line1, Line2 )
Vector_Normalise( Normal, Normal ) // Normalise the Normal ;)
// Calculate the Ray between Camera and Triangle
Local CameraRay As Vector_t
Vector_Sub( CameraRay, triTransformed.p[0], vCamera )
// If Ray is aligned with Normal, then Triangle is (potentially) Visible
If Vector_DotProduct( Normal, CameraRay ) < 0.0
// Illumination
Local LightDirection As Vector_t : Vector_Set( LightDirection, 0.0, 0.0, -1.0, 0.0 )
Local LightLength As Float : LightLength = Vector_Length( LightDirection )
Local LdotN As Float : LdotN = Max( 0.1, Vector_DotProduct( LightDirection, Normal ) )
triTransformed.Color = MakeColor( LdotN * 255, LdotN * 255, LdotN * 255 ) // Set Triangle Color
// Project Triangles from 3D -> 2D
Matrix_MultiplyVector( triProjected.p[0], matrixProj, triTransformed.p[0] )
Matrix_MultiplyVector( triProjected.p[1], matrixProj, triTransformed.p[1] )
Matrix_MultiplyVector( triProjected.p[2], matrixProj, triTransformed.p[2] )
triProjected.Color = triTransformed.Color
// Scale into view, we moved the normalising into cartesian space
// out of the matrix•vector function from previous source, so
// we do this manually
Vector_Div( triProjected.p[0], triProjected.p[0], triProjected.p[0].w )
Vector_Div( triProjected.p[1], triProjected.p[1], triProjected.p[1].w )
Vector_Div( triProjected.p[2], triProjected.p[2], triProjected.p[2].w )
// Offset into Visible Normalised Space
Local OffsetView As Vector_t : Vector_Set( OffsetView, 1.0, 1.0, 0.0, 1.0 )
Vector_Add( triProjected.p[0], triProjected.p[0], OffsetView )
Vector_Add( triProjected.p[1], triProjected.p[1], OffsetView )
Vector_Add( triProjected.p[2], triProjected.p[2], OffsetView )
triProjected.p[0].x = (GetDeviceWidth() * 0.5) * triProjected.p[0].x : triProjected.p[0].y = (GetDeviceHeight() * 0.5) * triProjected.p[0].y
triProjected.p[1].x = (GetDeviceWidth() * 0.5) * triProjected.p[1].x : triProjected.p[1].y = (GetDeviceHeight() * 0.5) * triProjected.p[1].y
triProjected.p[2].x = (GetDeviceWidth() * 0.5) * triProjected.p[2].x : triProjected.p[2].y = (GetDeviceHeight() * 0.5) * triProjected.p[2].y
EndIf
vecTriangleToRaster.length = vecTriangleToRaster.length + 1
vecTriangleToRaster[vecTriangleToRaster.length] = triProjected
Next
// With a Triangle Buffer we can now Sort these for Rasterisation
// This takes advantage of the Array.Sort() ... so we needed to add a new variable
// to the top of our Triangle Type (in this case called Depth)
For i = 0 To vecTriangleToRaster.length
vecTriangleToRaster[i].depth = -((vecTriangleToRaster[i].p[0].z + vecTriangleToRaster[i].p[1].z + vecTriangleToRaster[i].p[2].z) / 3.0)
Next
vecTriangleToRaster.sort()
// Raster Based on Selected Mode
For i = 0 To vecTriangleToRaster.length
Select iRasterMode
Case 0:
DrawTriangle( vecTriangleToRaster[i], vecTriangleToRaster[i].Color, AGK_TRUE )
EndCase
Case 1:
DrawTriangle( vecTriangleToRaster[i], vecTriangleToRaster[i].Color, AGK_TRUE )
DrawTriangle( vecTriangleToRaster[i], 0x000000, AGK_FALSE )
EndCase
Case 2:
DrawTriangle( vecTriangleToRaster[i], 0x808080, AGK_TRUE )
DrawTriangle( vecTriangleToRaster[i], 0x000000, AGK_FALSE )
EndCase
Case 3:
DrawTriangle( vecTriangleToRaster[i], 0xFFFFFF, AGK_FALSE )
EndCase
EndSelect
Next
// Chose Raster Mode
If agk_Key[KEY_1].bPressed Then iRasterMode = 0
If agk_Key[KEY_2].bPressed Then iRasterMode = 1
If agk_Key[KEY_3].bPressed Then iRasterMode = 2
If agk_Key[KEY_4].bPressed Then iRasterMode = 3
Print( "Triangle Count: " + Str( meshCube.tris.length ) )
// Raster Type Notification
Select iRasterMode
Case 0:
Print( "Raster Mode: Lit Polygon" )
EndCase
Case 1:
Print( "Raster Mode: Lit Polygon + Wireframe" )
EndCase
Case 2:
Print( "Raster Mode: Unlit Polygon + Wireframe" )
EndCase
Case 3:
Print( "Raster Mode: Wireframe" )
EndCase
EndSelect
// Model Information
Local VertexCount As Integer = 0
Local FaceCount As Integer = 0
Local FileSize As Integer
OpenToRead( 1, "raw:E:\AGK\OneLoneCoder\OLC 3D Graphics Engine\media\VideoShip.obj" )
Repeat
Output$ = ""
Repeat : Output$ = Output$ + Chr( ReadByte( 1 ) ) : Inc FileSize : Until Right( Output$, 1 ) = Chr( 0x0A )
If Left( Output$, 1 ) = "v" Then Inc VertexCount
If Left( Output$, 1 ) = "f" Then Inc FaceCount
Until FileSize >= GetFileSize( 1 )
CloseFile( 1 )
Print( Str( VertexCount ) )
Print( Str( FaceCount ) )
If agk_Key[KEY_ESCAPE].bPressed Then ExitFunction AGK_FALSE
EndFunction AGK_TRUE
// Core Math Utility Functions
// Oddly these don't exist in AGK :\
// -----------------------------------------------------------------------------
Function Max( A As Float, B As Float )
If A > B Then ExitFunction A
EndFunction B
Function Min( A As Float, B As Float )
If A > B Then ExitFunction B
EndFunction A
// Vector Utility Functions
// All of these *always* use the 1st Input as Typed Output
// Hopefully TheGameCreators will improve AGK Functions so we can use As <DataType> for Output :)
// ------------------------------------------------------------------------------
Function Triangle_Set( pTriangle Ref As Triangle_t, PointA As Vector_t, PointB As Vector_t, PointC As Vector_t )
Vector_Set( pTriangle.p[0], PointA.x, PointA.y, PointA.z, PointA.w )
Vector_Set( pTriangle.p[1], PointB.x, PointB.y, PointB.z, PointB.w )
Vector_Set( pTriangle.p[2], PointC.x, PointC.y, PointC.z, PointC.w )
EndFunction
Function Vector_Set( pVector Ref As Vector_t, x As Float, y As Float, z As Float, w As Float )
pVector.x = x
pVector.y = y
pVector.z = z
pVector.w = w
EndFunction
Function Vector_Add( pVector Ref As Vector_t, VectorA As Vector_t, VectorB As Vector_t )
pVector.x = VectorA.x + VectorB.x
pVector.y = VectorA.y + VectorB.y
pVector.z = VectorA.z + VectorB.z
EndFunction
Function Vector_Sub( pVector Ref As Vector_t, VectorA As Vector_t, VectorB As Vector_t )
pVector.x = VectorA.x - VectorB.x
pVector.y = VectorA.y - VectorB.y
pVector.z = VectorA.z - VectorB.z
EndFunction
Function Vector_Mul( pVector Ref As Vector_t, VectorA As Vector_t, Scalar As Float )
pVector.x = VectorA.x * Scalar
pVector.y = VectorA.y * Scalar
pVector.z = VectorA.z * Scalar
EndFunction
Function Vector_Div( pVector Ref As Vector_t, VectorA As Vector_t, Scalar As Float )
If Scalar <> 0.0
pVector.x = VectorA.x / Scalar
pVector.y = VectorA.y / Scalar
pVector.z = VectorA.z / Scalar
EndIf
EndFunction
Function Vector_DotProduct( VectorA As Vector_t, VectorB As Vector_t )
Local Product As Float
Product = VectorA.x * VectorB.x + VectorA.y * VectorB.y + VectorA.z * VectorB.z
EndFunction Product
Function Vector_Length( VectorA As Vector_t )
Local Length As Float
Length = Sqrt( Vector_DotProduct( VectorA, VectorA ) )
EndFunction Length
Function Vector_Normalise( pVector Ref As Vector_t, VectorA As Vector_t )
Local Length As Float
Length = Vector_Length( VectorA )
pVector.x = VectorA.x / Length
pVector.y = VectorA.y / Length
pVector.z = VectorA.z / Length
EndFunction
Function Vector_CrossProduct( pVector Ref As Vector_t, VectorA As Vector_t, VectorB As Vector_t )
pVector.x = VectorA.y * VectorB.z - VectorA.z * VectorB.y
pVector.y = VectorA.z * VectorB.x - VectorA.x * VectorB.z
pVector.z = VectorA.x * VectorB.y - VectorA.y * VectorB.x
EndFunction
Function SwapVector( X Ref As Vector_t, Y Ref As Vector_t )
Local Z As Vector_t
Z.x = X.x : Z.y = X.y : Z.z = X.z
X.x = Y.x : X.y = Y.y : X.z = Y.z
Y.x = Z.x : Y.y = Z.y : Y.z = Z.z
EndFunction
// Matrix Utility Functions
// While we don't strictly have to "Fully Populate" the Matrices for them to work,
// I tend to find it best to ensure no errant data... plus it helps better visualise
// which index is actually interacting with the Mathematics
// -------------------------------------------------------------------------
Function Matrix_MultiplyVector( pVector Ref As Vector_t, Matrix As Matrix_t, Vector As Vector_t )
pVector.x = Vector.x * Matrix.m[0,0] + Vector.y * Matrix.m[1,0] + Vector.z * Matrix.m[2,0] + Vector.w * Matrix.m[3,0]
pVector.y = Vector.x * Matrix.m[0,1] + Vector.y * Matrix.m[1,1] + Vector.z * Matrix.m[2,1] + Vector.w * Matrix.m[3,1]
pVector.z = Vector.x * Matrix.m[0,2] + Vector.y * Matrix.m[1,2] + Vector.z * Matrix.m[2,2] + Vector.w * Matrix.m[3,2]
pVector.w = Vector.x * Matrix.m[0,3] + Vector.y * Matrix.m[1,3] + Vector.z * Matrix.m[2,3] + Vector.w * Matrix.m[3,3]
EndFunction
Function Matrix_MakeIdentity( pMatrix Ref As Matrix_t )
pMatrix.m[0,0] = 1.0 : pMatrix.m[0,1] = 0.0 : pMatrix.m[0,2] = 0.0 : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = 0.0 : pMatrix.m[1,1] = 1.0 : pMatrix.m[1,2] = 0.0 : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = 0.0 : pMatrix.m[2,1] = 0.0 : pMatrix.m[2,2] = 1.0 : pMatrix.m[2,3] = 0.0
pMatrix.m[3,0] = 0.0 : pMatrix.m[3,1] = 0.0 : pMatrix.m[3,2] = 0.0 : pMatrix.m[3,3] = 1.0
EndFunction
Function Matrix_MakeRotationX( pMatrix Ref As Matrix_t, AngleRadian As Float )
pMatrix.m[0,0] = 1.0 : pMatrix.m[0,1] = 0.0 : pMatrix.m[0,2] = 0.0 : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = 0.0 : pMatrix.m[1,1] = cos(AngleRadian) : pMatrix.m[1,2] = sin(AngleRadian) : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = 0.0 : pMatrix.m[2,1] = -sin(AngleRadian) : pMatrix.m[2,2] = cos(AngleRadian) : pMatrix.m[2,3] = 0.0
pMatrix.m[3,0] = 0.0 : pMatrix.m[3,1] = 0.0 : pMatrix.m[3,2] = 0.0 : pMatrix.m[3,3] = 1.0
EndFunction
Function Matrix_MakeRotationY( pMatrix Ref As Matrix_t, AngleRadian As Float )
pMatrix.m[0,0] = cos(AngleRadian) : pMatrix.m[0,1] = 0.0 : pMatrix.m[0,2] = sin(AngleRadian) : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = 0.0 : pMatrix.m[1,1] = 1.0 : pMatrix.m[1,2] = 0.0 : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = -sin(AngleRadian) : pMatrix.m[2,1] = 0.0 : pMatrix.m[2,2] = cos(AngleRadian) : pMatrix.m[2,3] = 0.0
pMatrix.m[3,0] = 0.0 : pMatrix.m[3,1] = 0.0 : pMatrix.m[3,2] = 0.0 : pMatrix.m[3,3] = 1.0
EndFunction
Function Matrix_MakeRotationZ( pMatrix Ref As Matrix_t, AngleRadian As Float )
pMatrix.m[0,0] = cos(AngleRadian) : pMatrix.m[0,1] = sin(AngleRadian) : pMatrix.m[0,2] = 0.0 : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = -sin(AngleRadian) : pMatrix.m[1,1] = cos(AngleRadian) : pMatrix.m[1,2] = 0.0 : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = 0.0 : pMatrix.m[2,1] = 0.0 : pMatrix.m[2,2] = 1.0 : pMatrix.m[2,3] = 0.0
pMatrix.m[3,0] = 0.0 : pMatrix.m[3,1] = 0.0 : pMatrix.m[3,2] = 0.0 : pMatrix.m[3,3] = 1.0
EndFunction
Function Matrix_MakeTranslation( pMatrix Ref As Matrix_t, X As Float, Y As Float, Z As Float )
pMatrix.m[0,0] = 1.0 : pMatrix.m[0,1] = 0.0 : pMatrix.m[0,2] = 0.0 : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = 0.0 : pMatrix.m[1,1] = 1.0 : pMatrix.m[1,2] = 0.0 : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = 0.0 : pMatrix.m[2,1] = 0.0 : pMatrix.m[2,2] = 1.0 : pMatrix.m[2,3] = 0.0
pMatrix.m[3,0] = X : pMatrix.m[3,1] = Y : pMatrix.m[3,2] = Z : pMatrix.m[3,3] = 1.0
EndFunction
Function Matrix_MakeProjection( pMatrix Ref As Matrix_t, FovDegree As Float, AspectRatio As Float, Near As Float, Far As Float )
Local FovRadian As Float
FovRadian = 1.0 / TanRad( FovDegree * 0.5 / 180.0 * 3.14159 )
pMatrix.m[0,0] = AspectRatio * FovRadian : pMatrix.m[0,1] = 0.0 : pMatrix.m[0,2] = 0.0 : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = 0.0 : pMatrix.m[1,1] = FovRadian : pMatrix.m[1,2] = 0.0 : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = 0.0 : pMatrix.m[2,1] = 0.0 : pMatrix.m[2,2] = Far / (Far - Near) : pMatrix.m[2,3] = 1.0
pMatrix.m[3,0] = 0.0 : pMatrix.m[3,1] = 0.0 : pMatrix.m[3,2] = (-Far * Near) / (Far - Near) : pMatrix.m[3,3] = 0.0
EndFunction
Function Matrix_MultiplyMatrix( pMatrix Ref As Matrix_t, MatrixA As Matrix_t, MatrixB As Matrix_t )
For col = 0 To 3
For row = 0 To 3
pMatrix.m[row,col] = MatrixA.m[row,0] * MatrixB.m[0,col] + MatrixA.m[row,1] * MatrixB.m[1,col] + MatrixA.m[row,2] * MatrixB.m[2,col] + MatrixA.m[row,3] * MatrixB.m[3,col]
Next
Next
EndFunction
Function Matrix_PointAt( pMatrix Ref As Matrix_t, Position As Vector_t, Target As Vector_t, Up As Vector_t )
// Calculate new Forward Direction
Local newForward As Vector_t
Vector_Sub( newForward, Target, Up )
Vector_Normalise( newForward, newForward )
// Calculate new Up Direction
Local A As Vector_t
Vector_Mul( A, newForward, Vector_DotProduct( Up, newForward ) )
Local newUp As Vector_t
Vector_Sub( newUp, Up, A )
Vector_Normalise( newUp, newUp )
// Calculate new Right Direction, which is just the CrossProduct (Normal) of the Up and Forward
Local newRight As Vector_t
Vector_CrossProduct( newRight, newUp, newForward )
// Now we can construct the Transposition Matrix
pMatrix.m[0,0] = newRight.x : pMatrix.m[0,1] = newRight.y : pMatrix.m[0,2] = newRight.z : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = newUp.x : pMatrix.m[1,1] = newUp.y : pMatrix.m[1,2] = newUp.z : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = newForward.x : pMatrix.m[2,1] = newForward.y : pMatrix.m[2,2] = newForward.z : pMatrix.m[2,3] = 0.0
pMatrix.m[3,0] = Position.x : pMatrix.m[3,1] = Position.y : pMatrix.m[3,2] = Position.z : pMatrix.m[3,3] = 1.0
EndFunction
// For Rotation and Translation Matrices
Function Matrix_QuickInverse( pMatrix Ref As Matrix_t, Matrix As Matrix_t )
pMatrix.m[0,0] = Matrix.m[0,0] : pMatrix.m[0,1] = Matrix.m[1,0] : pMatrix.m[0,2] = Matrix.m[2,0] : pMatrix.m[0,3] = 0.0
pMatrix.m[1,0] = Matrix.m[0,1] : pMatrix.m[1,1] = Matrix.m[1,1] : pMatrix.m[1,2] = Matrix.m[2,1] : pMatrix.m[1,3] = 0.0
pMatrix.m[2,0] = Matrix.m[0,2] : pMatrix.m[2,1] = Matrix.m[1,2] : pMatrix.m[2,2] = Matrix.m[2,2] : pMatrix.m[2,3] = 0.0
pMatrix.m[3,0] = -(Matrix.m[3,0] * pMatrix.m[0,0] + Matrix.m[3,1] * pMatrix.m[1,0] + Matrix.m[3,2] * pMatrix.m[2,0])
pMatrix.m[3,1] = -(Matrix.m[3,0] * pMatrix.m[0,1] + Matrix.m[3,1] * pMatrix.m[1,1] + Matrix.m[3,2] * pMatrix.m[2,1])
pMatrix.m[3,2] = -(Matrix.m[3,0] * pMatrix.m[0,2] + Matrix.m[3,1] * pMatrix.m[1,2] + Matrix.m[3,2] * pMatrix.m[2,2])
pMatrix.m[3,3] = 1.0
EndFunction
// Draw Operations
// -------------------------------------------------------------------------------
Function DrawTriangle( tTris As Triangle_t, iColor As Integer, bFill As Integer )
Local iRed, iGreen, iBlue As Integer
iRed = GetColorRed( iColor )
iGreen = GetColorGreen( iColor )
iBlue = GetColorBlue( iColor )
// Resolve Floating-Point Inaccuracy in Screen Space
For i = 0 To 2
tTris.p[i].x = Trunc(tTris.p[i].x)
tTris.p[i].y = Trunc(tTris.p[i].y)
Next
If Not(bFill)
// Draw Triangle Outline
DrawLine( tTris.p[0].x, tTris.p[0].y, tTris.p[1].x, tTris.p[1].y, iRed, iGreen, iBlue )
DrawLine( tTris.p[1].x, tTris.p[1].y, tTris.p[2].x, tTris.p[2].y, iRed, iGreen, iBlue )
DrawLine( tTris.p[2].x, tTris.p[2].y, tTris.p[0].x, tTris.p[0].y, iRed, iGreen, iBlue )
Else
// Draw Scanline Filled Triangle
Local Vert4 As Vector_t // we need this to split general case triangles into 2
// Sort Vertex (By Y)
For i = 0 To 1
If tTris.p[0].y > tTris.p[1].y Then SwapVector( tTris.p[0], tTris.p[1] )
If tTris.p[0].y > tTris.p[2].y Then SwapVector( tTris.p[0], tTris.p[2] )
If tTris.p[1].y > tTris.p[2].y Then SwapVector( tTris.p[1], tTris.p[2] )
Next
// Fill Triangle
// First check Triangle Direction (Point Up?)
If tTris.p[1].y = tTris.p[2].y
FillTriangleBF( tTris.p[0], tTris.p[1], tTris.p[2], iRed, iGreen, iBlue )
Else
// (Point Down?)
If tTris.p[0].y = tTris.p[1].y
FillTriangleTF( tTris.p[0], tTris.p[1], tTris.p[2], iRed, iGreen, iBlue )
Else
// General Case
Vector_Set( Vert4, tTris.p[0].x + ( (tTris.p[1].y - tTris.p[0].y) / (tTris.p[2].y - tTris.p[0].y) ) * (tTris.p[2].x - tTris.p[0].x), tTris.p[1].y, 1.0, 1.0 )
FillTriangleBF( tTris.p[0], tTris.p[1], Vert4, iRed, iGreen, iBlue )
FillTriangleTF( tTris.p[1], Vert4, tTris.p[2], iRed, iGreen, iBlue )
EndIf
EndIf
EndIf
EndFunction
// Triangle with a Flat Bottom
Function FillTriangleBF( Vert1 As Vector_t, Vert2 As Vector_t, Vert3 As Vector_t, iRed, iGreen, iBlue )
Local invSlope1 As Float : invSlope1 = (Vert2.x - Vert1.x) / (Vert2.y - Vert1.y)
Local invSlope2 As Float : invSlope2 = (Vert3.x - Vert1.x) / (Vert3.y - Vert1.y)
Local cX1 As Float : cX1 = Vert1.x
Local cX2 As Float : cX2 = Vert1.x + 0.5
Local ScanlineY As Integer : ScanlineY = Vert1.y
While ScanlineY <= Vert2.y
DrawLine( cX1, ScanlineY, cX2, ScanlineY, iRed, iGreen, iBlue )
cX1 = cX1 + invSlope1
cX2 = cX2 + invSlope2
ScanlineY = ScanlineY + 1
EndWhile
EndFunction
// Triangle with a Flat Top
Function FillTriangleTF( Vert1 As Vector_t, Vert2 As Vector_t, Vert3 As Vector_t, iRed, iGreen, iBlue )
Local invSlope1 As Float : invSlope1 = (Vert3.x - Vert1.x) / (Vert3.y - Vert1.y)
Local invSlope2 As Float : invSlope2 = (Vert3.x - Vert2.x) / (Vert3.y - Vert2.y)
Local cX1 As Float : cX1 = Vert3.x
Local cX2 As Float : cX2 = Vert3.x + 0.5
Local ScanlineY As Integer : ScanlineY = Vert3.y
While ScanlineY > Vert1.y
DrawLine( cX1, ScanlineY, cX2, ScanlineY, iRed, iGreen, iBlue )
cX1 = cX1 - invSlope1
cX2 = cX2 - invSlope2
ScanlineY = ScanlineY - 1
EndWhile
EndFunction
// -------------------------------
// Misc Utility Functions
// Object Format (.obj) Load Function
Function LoadFromObjectFile( tObject Ref As Mesh_t, sFilename As String )
Local iFile As Integer = 0
Local Pos As Integer = 0
Local Offset As Integer = 0
Local FileSize As Integer = 0
Local sBuffer As String
Local Point As Float[3]
Local Face As Integer[3]
Local Vertex As Vector_t[]
If Not( GetFileExists( sFilename ) ) Then ExitFunction AGK_FALSE
iFile = OpenToRead( sFilename )
Repeat
Output$ = ""
Repeat : Output$ = Output$ + Chr( ReadByte( iFile ) ) : Inc FileSize : Until Right( Output$, 1 ) = Chr( 0x0A )
// Handle Vertex Loading
If Left( Output$, 1 ) = "v"
Pos = 3
For i = 0 To 2
If Mid(Output$, Pos, 1) = "-" Then Offset = 1 Else Offset = 0
Point[i] = ValFloat( Mid( Output$, Pos, 8 + Offset ) )
Inc Pos, 9 + Offset
Next
Vertex.length = Vertex.length + 1
Vector_Set( Vertex[Vertex.length], Point[0], Point[1], Point[2], 1.0 )
EndIf
// Handle Face Loading
If Left( Output$, 1 ) = "f"
Pos = 3
For i = 0 To 2
Repeat
Buffer$ = Buffer$ + Mid(Output$, Pos, 1)
Inc Pos
Check$ = Right( Buffer$, 1 )
Until Check$ = Chr( 0x20 ) Or Check$ = Chr( 0x0A )
Face[i] = Val( Buffer$ )
Buffer$ = ""
Next
// Push Triangle
tObject.tris.length = tObject.tris.length + 1
Triangle_Set( tObject.tris[tObject.tris.length], Vertex[Face[0] - 1], Vertex[Face[1] - 1], Vertex[Face[2] - 1] )
EndIf
Until FileSize >= GetFileSize( iFile )
CloseFile( iFile )
EndFunction AGK_TRUE
Alright so, this isn't strictly "Part 3" .... instead this is more a "I've run in to a bit of a road block"
That is to say that while this is working "Fine" with Low Triangle (Polygon) Counts., well it seems AppGameKit Arrays take a nose dive in performance the larger they are.
As a result, loading the "Teapot.obj" (which is only 6,300 ish Triangles) results in bringing things to single digit Frames Per Second...
Now I was doing some experiments... and the actual creation (i.e. resizing and sorting) is still just fine, and has very little impact on the framerate., at least on it's own.
As I actually took those away and just manually set the Array Size... same issue.
I'm going to experiment a bit more, but it might cause some issues going forward.
In any case I've posted the Refactored Code and added the Teapot.obj as an attachment.