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); | ||
|  | 		} | ||
|  | 	} | ||
|  | } |