346 lines
6.7 KiB
C++
346 lines
6.7 KiB
C++
#include "Camera.hpp"
|
|
#include "Color.hpp"
|
|
#include "Engine.hpp"
|
|
#include "Geometry.hpp"
|
|
#include "Light.hpp"
|
|
#include "Loader.hpp"
|
|
#include "Matrix.hpp"
|
|
#include "Render.hpp"
|
|
#include "Transform.hpp"
|
|
#include "Vec.hpp"
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
|
|
|
|
unsigned long constexpr CheckBit(uint32_t x, unsigned long bit)
|
|
{
|
|
return x & (1UL << bit);
|
|
}
|
|
|
|
static Mesh mesh;
|
|
static Camera camera;
|
|
static Light light;
|
|
static Matrix tPersp;
|
|
static Matrix tScreen;
|
|
static EngineMemory memory;
|
|
|
|
|
|
static void ComputeNormals();
|
|
static void CheckInputs(uint32_t input);
|
|
static void ClearDepthBuffer();
|
|
static void TransformToClipSpace();
|
|
static void ClipAndCull();
|
|
static void TransformToScreenSpace();
|
|
static void LightMesh();
|
|
|
|
|
|
int EngineInit(char* objFilename, char* mtlFilename)
|
|
{
|
|
int result;
|
|
|
|
|
|
result = ParseOBJ(objFilename, memory);
|
|
if (result < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
result = ParseMTL(mtlFilename, memory);
|
|
if (result < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
|
|
printf("Verts: %lu\n", memory.localVerts.size);
|
|
printf("Faces: %lu\n", memory.localFaces.size);
|
|
printf("Materials: %lu\n", memory.materials.size);
|
|
|
|
memory.transVerts.size = memory.localVerts.size;
|
|
|
|
|
|
// Compute vertex and face normals for lighting calculation
|
|
ComputeNormals();
|
|
|
|
|
|
// Mesh configuration
|
|
mesh.position.z = 200;
|
|
mesh.position.y = -100;
|
|
mesh.scale = 1.0f;
|
|
|
|
|
|
// Light configuration
|
|
light.position = Point(-300.0f, 200.0f, 0.0f);
|
|
light.color = ColorF32(1.0f, 1.0f, 1.0f, 1.0f);
|
|
light.intensity = 2.0f;
|
|
light.falloffConstant = 1.0f;
|
|
light.falloffLinear = 0.001f;
|
|
|
|
|
|
// Transformation matrices that do not change
|
|
tPersp = Transform_Perspective(camera);
|
|
tScreen = Transform_Screen(camera);
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
void EngineRender(EngineBuffer& buffer, uint32_t input)
|
|
{
|
|
// Check for user input
|
|
CheckInputs(input);
|
|
|
|
|
|
// Clear the z-buffer
|
|
ClearDepthBuffer();
|
|
|
|
|
|
// Transform vertices to clip space
|
|
TransformToClipSpace();
|
|
|
|
|
|
// Clip near/far Z and cull backfaces
|
|
ClipAndCull();
|
|
|
|
|
|
// Light vertices and/or faces
|
|
LightMesh();
|
|
|
|
|
|
// Transform vertices to screen space
|
|
TransformToScreenSpace();
|
|
|
|
|
|
// Render
|
|
Render(buffer, memory);
|
|
}
|
|
|
|
void EngineShutdown()
|
|
{
|
|
}
|
|
|
|
|
|
// PRIVATE FUNCTIONS
|
|
static void CheckInputs(uint32_t input)
|
|
{
|
|
if (CheckBit(input, TRANSLATE_X_POS))
|
|
{
|
|
light.position.x += 10;
|
|
}
|
|
else if (CheckBit(input, TRANSLATE_X_NEG))
|
|
{
|
|
light.position.x -= 10;
|
|
}
|
|
|
|
if (CheckBit(input, TRANSLATE_Z_POS))
|
|
{
|
|
light.position.z += 10;
|
|
}
|
|
else if (CheckBit(input, TRANSLATE_Z_NEG))
|
|
{
|
|
light.position.z -= 10;
|
|
}
|
|
|
|
if (CheckBit(input, TRANSLATE_Y_POS))
|
|
{
|
|
light.position.y += 10;
|
|
}
|
|
else if (CheckBit(input, TRANSLATE_Y_NEG))
|
|
{
|
|
light.position.y -= 10;
|
|
}
|
|
|
|
if (CheckBit(input, ROTATE_X_POS))
|
|
{
|
|
mesh.rotation.x += 0.10f;
|
|
}
|
|
else if (CheckBit(input, ROTATE_X_NEG))
|
|
{
|
|
mesh.rotation.x -= 0.10f;
|
|
}
|
|
|
|
if (CheckBit(input, ROTATE_Y_POS))
|
|
{
|
|
mesh.rotation.y += 0.10f;
|
|
}
|
|
else if (CheckBit(input, ROTATE_Y_NEG))
|
|
{
|
|
mesh.rotation.y -= 0.10f;
|
|
}
|
|
|
|
if (CheckBit(input, ROTATE_Z_POS))
|
|
{
|
|
mesh.rotation.z += 0.10f;
|
|
}
|
|
else if (CheckBit(input, ROTATE_Z_NEG))
|
|
{
|
|
mesh.rotation.z -= 0.10f;
|
|
}
|
|
|
|
if (CheckBit(input, SCALE_UP))
|
|
{
|
|
light.color.b = 0.0f;
|
|
light.color.g = 0.0f;
|
|
light.color.r = 1.0f;
|
|
}
|
|
else if (CheckBit(input, SCALE_DOWN))
|
|
{
|
|
light.color.b = 1.0f;
|
|
light.color.g = 1.0f;
|
|
light.color.r = 1.0f;
|
|
}
|
|
}
|
|
|
|
|
|
static void ComputeNormals()
|
|
{
|
|
VertexList& verts = memory.localVerts;
|
|
FaceList& faces = memory.localFaces;
|
|
|
|
int vertexNormalCount[VERTEX_LIMIT] = {};
|
|
|
|
for (size_t f = 0; f < faces.size; ++f)
|
|
{
|
|
Face& face = faces.data[f];
|
|
|
|
Vertex& vert0 = verts.data[face.vertIndex[0]];
|
|
Vertex& vert1 = verts.data[face.vertIndex[1]];
|
|
Vertex& vert2 = verts.data[face.vertIndex[2]];
|
|
|
|
|
|
Vector v01 = vert1.position - vert0.position;
|
|
Vector v02 = vert2.position - vert0.position;
|
|
|
|
Vector normal = Vector::Cross(v01, v02);
|
|
|
|
// Add each vertex's normal to the sum for future averaging
|
|
vert0.normal += normal;
|
|
vert1.normal += normal;
|
|
vert2.normal += normal;
|
|
|
|
++vertexNormalCount[face.vertIndex[0]];
|
|
++vertexNormalCount[face.vertIndex[1]];
|
|
++vertexNormalCount[face.vertIndex[2]];
|
|
}
|
|
|
|
for (size_t v = 0; v < verts.size; ++v)
|
|
{
|
|
if (vertexNormalCount[v] > 0)
|
|
{
|
|
// Compute the average normal for this vertex
|
|
verts.data[v].normal /= static_cast<float>(vertexNormalCount[v]);
|
|
verts.data[v].normal.Normalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void ClearDepthBuffer()
|
|
{
|
|
memset(memory.zbuffer, 0, sizeof(memory.zbuffer));
|
|
}
|
|
|
|
|
|
static void TransformToClipSpace()
|
|
{
|
|
VertexList& localVerts = memory.localVerts;
|
|
VertexList& transVerts = memory.transVerts;
|
|
|
|
Matrix tTranslate = Transform_Translate(mesh.position);
|
|
Matrix tRotate = Transform_Rotate(mesh.rotation);
|
|
Matrix tScale = Transform_Scale(mesh.scale);
|
|
Matrix tView = Transform_View(camera);
|
|
|
|
for (size_t v = 0; v < localVerts.size; ++v)
|
|
{
|
|
transVerts.data[v].position =
|
|
localVerts.data[v].position * tScale * tRotate * tTranslate * tView * tPersp;
|
|
|
|
transVerts.data[v].normal =
|
|
localVerts.data[v].normal * tScale * tRotate * tTranslate;
|
|
}
|
|
}
|
|
|
|
void ClipAndCull()
|
|
{
|
|
FaceList& localFaces = memory.localFaces;
|
|
FaceList& transFaces = memory.transFaces;
|
|
VertexList& verts = memory.transVerts;
|
|
|
|
int faceIndex = 0;
|
|
|
|
for (size_t f = 0; f < localFaces.size; ++f)
|
|
{
|
|
Face& face = localFaces.data[f];
|
|
|
|
Point& p0 = verts.data[face.vertIndex[0]].position;
|
|
Point& p1 = verts.data[face.vertIndex[1]].position;
|
|
Point& p2 = verts.data[face.vertIndex[2]].position;
|
|
|
|
// Ignore this face if its Z is outside the Z clip planes
|
|
if ((p0.z < -p0.w)
|
|
|| (p0.z > p0.w)
|
|
|| (p1.z < -p1.w)
|
|
|| (p1.z > p1.w)
|
|
|| (p2.z < -p2.w)
|
|
|| (p2.z > p2.w))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
// Calculate the face's normal (inverted for Blender-compatibility)
|
|
Vector v01 = p1 - p0;
|
|
Vector v02 = p2 - p0;
|
|
Vector normal = -Vector::Cross(v01, v02);
|
|
|
|
// Eye vector to viewport
|
|
Vector view = camera.position - p0;
|
|
|
|
float dot = Vector::Dot(normal, view);
|
|
|
|
// Not a backface; add it to the list
|
|
if (dot < 0.0f)
|
|
{
|
|
transFaces.data[faceIndex] = face;
|
|
++faceIndex;
|
|
transFaces.size = (size_t)faceIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void TransformToScreenSpace()
|
|
{
|
|
VertexList& verts = memory.transVerts;
|
|
|
|
for (size_t v = 0; v < verts.size; ++v)
|
|
{
|
|
verts.data[v].position *= tScreen;
|
|
verts.data[v].position.x /= verts.data[v].position.w;
|
|
verts.data[v].position.y /= verts.data[v].position.w;
|
|
verts.data[v].position.z /= verts.data[v].position.w;
|
|
}
|
|
}
|
|
|
|
static void LightMesh()
|
|
{
|
|
VertexList& verts = memory.transVerts;
|
|
FaceList& faces = memory.transFaces;
|
|
MaterialList& materials = memory.materials;
|
|
|
|
for (size_t f = 0; f < faces.size; ++f)
|
|
{
|
|
Face& face = faces.data[f];
|
|
Material& material = materials.data[face.materialIndex];
|
|
|
|
// Gouraud shading
|
|
for (auto index : face.vertIndex)
|
|
{
|
|
Vertex& vert = verts.data[index];
|
|
|
|
vert.color = light.Compute(vert.position, vert.normal, material, camera);
|
|
}
|
|
}
|
|
}
|