Code Samples

Various code snippets from projects I've worked on, including algorithms, design patterns and general systems.

Examples

A* Pathfinding w/DOTS

A* Pathfinding implementation using Unity DOTS. By taking advantage of the Jobs system it is possible to have the algorithm run at a 700 times faster speed than the standard GameObject approach.

A* Job
A* Support
[BurstCompile]
struct AStarPathfinding : IJob
{
	public GraphGrid Grid;
	public int2 Start;
	public int2 End;
	public NativeList<int2> Path;

	public void Execute()
	{
		NativeList<int> openList = new NativeList<int>(Allocator.Temp);
		NativeList<int> closedList = new NativeList<int>(Allocator.Temp);

		int endNodeIndex = GetIndex(End.x, End.y);

		Node startNode = Grid.Nodes[GetIndex(Start.x, Start.y)];
		startNode.g = 0;
		CalculateNodeF(startNode);
		Grid.Nodes[startNode.index] = startNode;
		openList.Add(startNode.index);

		while(openList.Length > 0)
		{
			Node currentNode = GetLowestCostNode(openList);

			if (currentNode.index == endNodeIndex)
			{
				Path = CalculatePath(currentNode);
				break;
			}

			for (int i = 0; i < openList.Length; i++)
			{
				if (openList[i] == currentNode.index)
				{
					openList.RemoveAtSwapBack(i);
					break;
				}
			}

			closedList.Add(currentNode.index);

			NativeArray<int2> neighbors = GetNeighbors(currentNode.coordinates);
			for(int i = 0;i < neighbors.Length; i++)
			{
				int neighbourIndex = GetIndex(neighbors[i].x, neighbors[i].y);
				Node neighbourNode = Grid.Nodes[neighbourIndex];

				if (!Grid.Nodes[neighbourIndex].walkable || closedList.Contains(neighbourIndex))
				{
					continue;
				}

				int tentativeGCost = currentNode.g + AStar.CalculateDistanceCost(currentNode.coordinates, neighbourNode.coordinates);
				if (tentativeGCost < neighbourNode.g)
				{
					neighbourNode.parent = currentNode.index;
					neighbourNode.g = tentativeGCost;
					CalculateNodeF(neighbourNode);
					Grid.Nodes[neighbourIndex] = neighbourNode;

					if (!openList.Contains(neighbourIndex))
					{
						openList.Add(neighbourIndex);
					}
				}
			}

			neighbors.Dispose();
		}

		ExecutePathfinding();

		openList.Dispose();
		closedList.Dispose();
	}

	private int GetIndex(int x, int y)
	{
		return x + y * Grid.GridSize.x;
	}

	private void CalculateNodeF(Node node)
	{
		node.f = node.g + node.h;
	}

	private Node GetLowestCostNode(NativeList<int> openList)
	{
		Node lowestCostNode = Grid.Nodes[openList[0]];

		for (int i = 0; i < openList.Length; i++)
		{
			Node sampleNode = Grid.Nodes[openList[i]];
			if (sampleNode.f < lowestCostNode.f)
			{
				lowestCostNode = sampleNode;
			}
		}

		return lowestCostNode;
	}

	private NativeArray<int2> GetNeighbors(int2 position)
	{
		NativeArray<int2> neighbors = new NativeArray<int2>(8, Allocator.Temp);
		int index = 0;

		for (int i = -1; i <= 1; i++)
		{
			for (int j = -1; j <= 1; j++)
			{
				if (i == 0 && j == 0)
					continue;

				int2 neighborPos = position + new int2(i, j);
				if (IsValidGridPosition(neighborPos))
				{
					neighbors[index] = neighborPos;
					index++;
				}
			}
		}

		return neighbors;
	}

	private bool IsValidGridPosition(int2 position)
	{
		return position.x >= 0 && position.x < Grid.GridSize.x && position.y >= 0 && position.y < Grid.GridSize.y;
	}


	private NativeList<int2> CalculatePath(Node endNode)
	{
		NativeList<int2> path = new NativeList<int2>(Allocator.Temp);
		path.Add(endNode.coordinates);

		Node currentNode = endNode;
		while(currentNode.parent != -1)
		{
			Node cameFromNode = Grid.Nodes[currentNode.parent];
			path.Add(cameFromNode.coordinates);
			currentNode = cameFromNode;
		}

		return path;
	}

	private void ExecutePathfinding()
	{
		// CODE TO BE EXECUTED AFTER FINDING A PATH
		// SAMPLE:
		foreach (int2 pathPosition in Path)
		{
			Debug.Log(pathPosition);
		}
	}
}				
										
using System.Linq;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;

public struct Node
{
	public int2 coordinates;
	public int g, h, f; // cost, heuristic, lowest cost
	public int parent;
	public int index;
	public bool walkable;
}

public struct GraphGrid
{
	public int2 GridSize;
	public int CellSize;
	public NativeArray<Node> Nodes;
}

public class AStar : MonoBehaviour
{
	public int2 gridSize = new int2(10, 10);
	public int cellSize = 1;
	public int2 startPosition = new int2(0, 0);
	public int2 endPosition = new int2(9, 9);

	private GraphGrid grid;
	private NativeList<int2> resultPath = new NativeList<int2>();

	private const int STRAIGHT_COST = 10;
	private const int DIAGONAL_COST = 14;

	private void Start()
	{
		InitializeGrid();
		CalculatePath();
	}

	private void InitializeGrid()
	{
		grid = new GraphGrid
		{
			GridSize = gridSize,
			CellSize = cellSize,
			Nodes = new NativeArray<Node>(gridSize.x * gridSize.y, Allocator.TempJob)
		};

		for (int x = 0; x < gridSize.x; x++)
		{
			for (int y = 0; y < gridSize.y; y++)
			{
				int index = y  * gridSize.x + x;
				bool isWalkable = true;
				grid.Nodes[index] = new Node
				{
					coordinates = new int2(x, y),
					walkable = isWalkable,
					index = x + y * gridSize.x,
					g = int.MaxValue,
					h = CalculateDistanceCost(new int2(x, y), endPosition),
					parent = -1
				};
			}
		}
	}

	private void CalculatePath()
	{
		resultPath = new NativeList<int2>(Allocator.TempJob);

		AStarPathfinding pathfinding = new AStarPathfinding
		{
			Grid = grid,
			Start = startPosition,
			End = endPosition,
			Path = resultPath
		};

		JobHandle jobHandle = pathfinding.Schedule();
		jobHandle.Complete();

		// Dealloc
		resultPath.Dispose();
		grid.Nodes.Dispose();
	}

	public static int CalculateDistanceCost(int2 a, int2 b)
	{
		int x =         math.abs(a.x - b.x);
		int y =         math.abs(a.y - b.y);
		int remaining = math.abs(x - y);

		return DIAGONAL_COST * math.min(x,y) + STRAIGHT_COST * remaining;
	}
}															
										

Abstract State Machine

An abstract state machine designed to abide by DRY (Don't Repeart Yourself). The state machine works by having an abstract implementation of the StateManager class handle all associated abstract implementations of the BaseState class, which are required to be keyed by an Enum. The abstract state machine can be easily extended with functionality based on this implementation.

BaseState
StateManager
Example Use
using System;

public abstract class BaseState<State> where State : Enum
{
	public BaseState(State StateKey)
	{
		this.StateKey = StateKey;
	}

	// A key is used as a unique identifier, enabling easy association within the manager's dictionary.
	public State StateKey { get; private set; }

	public abstract State GetNextState();

	public abstract void Enter();
	public abstract void Exit();
}						
										
using System;
using System.Collections.Generic;
using UnityEngine;

public abstract class StateManager<State> : MonoBehaviour where State : Enum
{
	protected Dictionary<State, BaseState<<State>> States = new Dictionary<State, BaseState<State>>();
	protected BaseState<State> CurrentState = null;

	public void NewState(State key)
	{
		CurrentState?.Exit();
		CurrentState = States[key];
		CurrentState.Enter();
	}
}												
										
public class ExampleStateMachine : StateManager<ExampleStateMachine.StateExamples>
{
public enum StateExamples
{
	Example,
	Print
}

void Start()
{
	States.Add(StateExamples.Example, new ExampleState());
	States.Add(StateExamples.Print, new PrintState());

	NewState(StateExamples.Example);
}
}


public class ExampleState : BaseState<ExampleStateMachine.StateExamples>
{
public ExampleState() : base(ExampleStateMachine.StateExamples.Example) { }

public override void Enter()
{
	Debug.Log("Example State Entered");
}

public override void Exit()
{
	Debug.Log("Example State Exited");
}

public override ExampleStateMachine.StateExamples GetNextState()
{
	return ExampleStateMachine.StateExamples.Print;
}
}


public class PrintState : BaseState<ExampleStateMachine.StateExamples>
{
public PrintState() : base(ExampleStateMachine.StateExamples.Print) { }

public override void Enter()
{
	Debug.Log($"Print State Entered: {Random.Range(1, 100)}");
}

public override void Exit()
{
	Debug.Log("Print State Exited");
}

public override ExampleStateMachine.StateExamples GetNextState()
{
	return ExampleStateMachine.StateExamples.Example;
}
}		
										

OpenGL C++

A small custom engine project that can create 3D scenes by utilizing OpenGL & GLUT. The implementation handles model processing and importing, lighting data, particle effects and vertex/fragment shaders.

Main
Primitives
Model
Shaders
GLSL
#include <iostream>
#include <string>
#include <cstring>

#include <GL/glew.h>
#include <GL/freeglut.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "glsl.h"

#include "objloader.h"
#include "texture.h"

#include "Model.h"
#include "Primitives.h"

using namespace std;

//--------------------------------------------------------------------------------
// Consts
//--------------------------------------------------------------------------------

const int WIDTH = 1000, HEIGHT = 800; // width and height of screen

const float FOV = 45; // camera field of view
const float FORWARD_MOVEMENT = 0.1f; // foward movement speed for camera movement

// shader defined paths
const char* fragshader_name = "fragmentshader.fsh";
const char* vertexshader_name = "vertexshader.vert";
const char* transparent_fs_name = "fragmentshader_transparent.fsh";

unsigned const int DELTA_TIME = 10;

//--------------------------------------------------------------------------------
// Variables
//--------------------------------------------------------------------------------

// ID's
GLuint default_id; // default shader
GLuint transparent_id; // transparent shader

// Matrices
glm::mat4 view, projection;   // view and projection matricies

// Camera / LookAt
glm::vec3 cameraEye; // camera position
glm::vec3 cameraCenter; // where the camera looks
glm::vec3 cameraUp; // cameras up vector

// Models
// house model components
Model houseBase;
Model houseRoof;
Model houseWindows;
Model houseDoor;

// small garden windmills
Model pole;
Model windmillWick;

// World Lists
vector<Model> models; // vector for models
vector<Primitives> primitives; // vector for primitives

// Lighting
LightingData defaultLight;

// drone mode vars
bool droneMode = false;
glm::vec3 droneCameraEye = glm::vec3(0, 10, 7); // default drone camera position for drone mode
glm::vec3 droneCameraCenter = glm::vec3(0, -1, -1); // default camera center for drone mode
glm::vec3 droneCameraUp = glm::vec3(0, 1, 0); // default camera up vector for drone mode

glm::vec3 savedCameraEye; // saved camera position for when swapping between modes
glm::vec3 savedCameraCenter; // saved camera center for when swapping between modes
glm::vec3 savedCameraUp; // saved camera up vector for when swapping between modes

//--------------------------------------------------------------------------------
// Keyboard handling
//--------------------------------------------------------------------------------

void keyboardHandler(unsigned char key, int a, int b)
{
	// exit
	if (key == 27)
		glutExit();

	// WASD
	// w - move forward
	if (key == 119) {
		cameraEye.z -= FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}

	// s - move backwards
	if (key == 115) {
		cameraEye.z += FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}

	// a - move left
	if (key == 97) {
		cameraEye.x -= FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}

	// d - move right
	if (key == 100) {
		cameraEye.x += FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}

	// IJKL
	// i - rotate camera up
	if (key == 105) {
		cameraCenter.y += FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}
		
	// k - rotate camera down
	if (key == 107) {
		cameraCenter.y -= FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}

	// j - rotate camera left
	if (key == 106) {
		cameraCenter.x -= FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}

	// l - rotate camera right
	if (key == 108) {
		cameraCenter.x += FORWARD_MOVEMENT;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}


	// DRONE MODE KEYS
	// v for drone mode
	if (key == 118) {
		if (droneMode) {
			droneMode = false;
			
			// update camera data to be equal to our saved data
			cameraEye = savedCameraEye;
			cameraCenter = savedCameraCenter;
			cameraUp = savedCameraUp;
		}
		else {
			droneMode = true;
			// save our camera data
			savedCameraEye = cameraEye;
			savedCameraCenter = cameraCenter;
			savedCameraUp = cameraUp;

			// update our current camera data to be equal to our drone presets
			cameraEye = droneCameraEye;
			cameraCenter = droneCameraCenter;
			cameraUp = droneCameraUp;
		}

		view = glm::lookAt(cameraEye, cameraCenter, cameraUp);
	}

	// Q - Move drone up
	if (key == 113 && droneMode) {
		cameraEye.y += FORWARD_MOVEMENT;
		if (cameraEye.y < 0) cameraEye.y = 0;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);
	}

	// E - Move drone down
	if (key == 101 && droneMode) {
		cameraEye.y -= FORWARD_MOVEMENT;
		if (cameraEye.y < 0) cameraEye.y = 0;
		view = glm::lookAt(cameraEye, cameraEye + cameraCenter, cameraUp);

	}

}

//--------------------------------------------------------------------------------
// Rendering & Animations
//--------------------------------------------------------------------------------

void Render()
{
	glClearColor(0.0, 0.0, 0.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Attach to default_id
	glUseProgram(default_id);

	// Render each model
	for (int i = 0; i < models.size(); i++) {

		// Check for and play animations
		// Play windmill animation for each windmill in the scene
		// Rotates them around in a circle
		if (models[i].modelName == "Windmill Wick") {
			models[i].Rotate(0, 0, 2, 0.01);
			models[i].UpdateModelVertices(view);
		}

		// Play smoke animation for each smoke effect in the scene
		// Translate a smoke effect upwards then repeat it by resetting its transformation
		if (models[i].modelName == "Smoke") {
			models[i].Translate(0, 0.1, 0);
			models[i].UpdateModelVertices(view);

			if (models[i].position.y >= 5.5) {
				models[i].Translate(0, -5.5, 0);
				models[i].UpdateModelVertices(view);
			}
		}

		// update buffers
		models[i].Render(view, projection);
		// if the model has a texture, apply it
		if (models[i].texture_id)
			glBindTexture(GL_TEXTURE_2D, models[i].texture_id);
		// send mvp for each mv
		glUniformMatrix4fv(models[i].uniform_mvp, 1, GL_FALSE, glm::value_ptr(models[i].mv));
		// send vao
		glBindVertexArray(models[i].vao);
		// draw
		glDrawArrays(GL_TRIANGLES, 0, models[i].vertices.size());  
	}

	// Render each primitive
	for (int i = 0; i < primitives.size(); i++)
	{
		primitives[i].Render();
		primitives[i].UpdateVertices(view);
		glUniformMatrix4fv(primitives[i].uniform_mvp, 1, GL_FALSE, glm::value_ptr(primitives[i].mv));
		glBindVertexArray(primitives[i].vao);
		glDrawElements(GL_TRIANGLES, sizeof(primitives[i].elements) / sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
	}

	// reset vao
	glBindVertexArray(0);
	glutSwapBuffers();
}

//------------------------------------------------------------
// void Render(int n)
// Render method that is called by the timer function
//------------------------------------------------------------

void Render(int n)
{
	Render();
	glutTimerFunc(DELTA_TIME, Render, 0);
}


//------------------------------------------------------------
// void InitGlutGlew(int argc, char **argv)
// Initializes Glut and Glew
//------------------------------------------------------------

void InitGlutGlew(int argc, char** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowSize(WIDTH, HEIGHT);
	glutCreateWindow("Neighbourhood Scene - Luke Tobin");
	glutDisplayFunc(Render);
	glutKeyboardFunc(keyboardHandler);
	glutTimerFunc(DELTA_TIME, Render, 0);

	glewInit();
}


//------------------------------------------------------------
// void InitShaders()
// Initializes the fragmentshader and vertexshader
//------------------------------------------------------------

void InitShaders()
{
	char* vertexshader = glsl::readFile(vertexshader_name);
	GLuint vsh_id = glsl::makeVertexShader(vertexshader);

	char* fragshader = glsl::readFile(fragshader_name);
	GLuint fsh_id = glsl::makeFragmentShader(fragshader);

	// custom transparency shader
	char* transparentshader = glsl::readFile(transparent_fs_name);
	GLuint trn_id = glsl::makeFragmentShader(transparentshader);

	default_id = glsl::makeShaderProgram(vsh_id, fsh_id);
	transparent_id = glsl::makeShaderProgram(vsh_id, trn_id);
}


//------------------------------------------------------------
// void InitMatrices()
//------------------------------------------------------------

/// <summary>
/// Setup our camera view and projection
/// </summary>
void InitMatrices()
{   
	cameraEye = glm::vec3(0, 1.75f, 7.0);
	cameraCenter = glm::vec3(0.0, 0, -1.0);
	cameraUp = glm::vec3(0.0, 1.0, 0.0);
	view = glm::lookAt(cameraEye, cameraCenter, cameraUp);  // up
	projection = glm::perspective(
		glm::radians(FOV),
		1.0f * WIDTH / HEIGHT, 0.1f,
		20.0f);
}

/// <summary>
/// Setup our models
/// </summary>
void InitLoadObjects() {
	// HOUSE MODEL
	houseBase = Model("House Base", "Models/house_base_a.obj","Textures/brick_wall.bmp", default_id, defaultLight);
	houseBase.UpdateModelVertices(view);
	models.push_back(houseBase);

	houseRoof = Model("House Roof", "Models/house_roof_a.obj","Textures/roof.bmp", default_id, defaultLight);
	houseRoof.Translate(0, 0.75, 0);
	houseRoof.UpdateModelVertices(view);
	models.push_back(houseRoof);

	houseWindows = Model("House Windows", "Models/house_windows_a.obj","Textures/glass.bmp", default_id, defaultLight);
	houseWindows.UpdateModelVertices(view);
	models.push_back(houseWindows);

	houseDoor = Model("House Door", "Models/door_a.obj", "Textures/door.bmp", default_id, defaultLight);
	houseDoor.Translate(-0.325, -1, 1.5);
	houseDoor.UpdateModelVertices(view);
	models.push_back(houseDoor);

	Model chimney = Model("Chimney", "Models/chimney.obj","Textures/brick_wall.bmp", default_id, defaultLight);
	chimney.Translate(-0.75, 1.25, 0);
	chimney.Scale(0.9, 0.9, 0.9);
	chimney.UpdateModelVertices(view);
	models.push_back(chimney);

#pragma region Grass

	// GRASS.
	Model grass = Model("Grass", "Models/grass.obj", "Textures/grass.bmp", default_id, defaultLight);
	grass.Translate(2.3, -1, 0);
	grass.Scale(0.4, 0.4, 0.4);
	grass.UpdateModelVertices(view);
	models.push_back(grass);

	Model grass2 = grass;
	grass2.Translate(0.4, 0, -0.6);
	grass2.UpdateModelVertices(view);
	models.push_back(grass2);

	Model grass3 = grass;
	grass3.Translate(0.6, 0, -0.3);
	grass3.UpdateModelVertices(view);
	models.push_back(grass3);

	Model grass4 = grass;
	grass4.Translate(-2.6, 0, 1);
	grass4.UpdateModelVertices(view);
	models.push_back(grass4);

	Model grass5 = grass;
	grass5.Translate(-10.6, 0, 2.2);
	grass5.UpdateModelVertices(view);
	models.push_back(grass5);

	Model grass6 = grass;
	grass6.Translate(-11.6, 0, 3.5);
	grass6.UpdateModelVertices(view);
	models.push_back(grass6);

	Model grass7 = grass;
	grass7.Translate(-11, 0, 3);
	grass7.UpdateModelVertices(view);
	models.push_back(grass7);

#pragma endregion Grass

#pragma region SmokeEffects
	// SMOKE EFFECTS
	// No textures being applied
	Model smokeEffect = Model("Smoke", "Models/basic_cube.obj",transparent_id, defaultLight);
	smokeEffect.Translate(-0.75, 1.3, -0.25);
	smokeEffect.Scale(0.2, 0.2, 0.05);
	smokeEffect.UpdateModelVertices(view);
	models.push_back(smokeEffect);

	Model smokeEffect2 = smokeEffect;
	smokeEffect2.Translate(-0.2, 0.4, 0);
	smokeEffect2.UpdateModelVertices(view);
	models.push_back(smokeEffect2);

	Model smokeEffect3 = smokeEffect;
	smokeEffect3.Translate(0.25, -0.65, 0);
	smokeEffect3.UpdateModelVertices(view);
	models.push_back(smokeEffect3);

	Model smokeEffect4 = smokeEffect;
	smokeEffect4.Translate(0.1, -1.2, 0);
	smokeEffect4.UpdateModelVertices(view);
	models.push_back(smokeEffect4);

	Model smokeEffect5 = smokeEffect;
	smokeEffect5.Translate(-0.15, -0.8, 0);
	smokeEffect5.UpdateModelVertices(view);
	models.push_back(smokeEffect5);

	Model smokeEffect6 = smokeEffect;
	smokeEffect6.Translate(0.1, -2, 0);
	smokeEffect6.UpdateModelVertices(view);
	models.push_back(smokeEffect6);

	Model smokeEffect7 = smokeEffect;
	smokeEffect7.Translate(-0.22, -2.5, 0);
	smokeEffect7.UpdateModelVertices(view);
	models.push_back(smokeEffect7);

	Model smokeEffect8 = smokeEffect;
	smokeEffect8.Translate(0.1, -2.8, 0);
	smokeEffect8.UpdateModelVertices(view);
	models.push_back(smokeEffect8);

	Model smokeEffect9 = smokeEffect;
	smokeEffect9.Translate(-0.2, -3, 0);
	smokeEffect9.UpdateModelVertices(view);
	models.push_back(smokeEffect9);

#pragma endregion SmokeEffects

	// DECORATIVE WINDMILL(S)
	pole = Model("Pole", "Models/pole.obj","Textures/wood_fence.bmp", default_id, defaultLight);
	pole.Translate(-2, -1, 0);
	pole.Scale(0.5, 0.5, 0.5);
	pole.UpdateModelVertices(view);
	models.push_back(pole);

	Model pole2 = pole;
	pole2.Translate(8, 0, 0);
	pole2.UpdateModelVertices(view);
	models.push_back(pole2);

	windmillWick = Model("Windmill Wick", "Models/windp.obj", "Textures/wood_fence.bmp", default_id, defaultLight);
	windmillWick.Translate(-2, -0.5, 0.045);
	windmillWick.Scale(0.5, 0.5, 0.5);
	windmillWick.UpdateModelVertices(view);
	models.push_back(windmillWick);

	Model windmillWick2 = windmillWick;
	windmillWick2.Translate(8, 0, 0);
	windmillWick2.UpdateModelVertices(view);
	models.push_back(windmillWick2);


	// FLOOR
	Model floor = Model("Floor", "Models/basic_cube.obj", "Textures/stone_path.bmp", default_id, defaultLight);
	floor.Scale(15, 0.5, 15);
	floor.Translate(0, -3, 0);
	floor.UpdateModelVertices(view);
	models.push_back(floor);


	// Primitive model
	Quad table = Quad(default_id);
	table.Translate(-2.5, -0.5, 3);
	table.Scale(0.5, 0.5, 0.5);
	table.UpdateVertices(view);
	primitives.push_back(table);

	Square tableLeg1 = Square(default_id);
	tableLeg1.Translate(-2.9, -0.8, 3.5);
	tableLeg1.Scale(0.1, 0.3, 0.1);
	tableLeg1.UpdateVertices(view);
	primitives.push_back(tableLeg1);

	Square tableLeg2 = tableLeg1;
	tableLeg2.Translate(8, 0, 0);
	tableLeg2.UpdateVertices(view);
	primitives.push_back(tableLeg2);

	Square tableLeg3 = tableLeg1;
	tableLeg3.Translate(0, 0, -10);
	tableLeg3.UpdateVertices(view);
	primitives.push_back(tableLeg3);

	Square tableLeg4 = tableLeg1;
	tableLeg4.Translate(8, 0, -10);
	tableLeg4.UpdateVertices(view);
	primitives.push_back(tableLeg4);
}

/// <summary>
/// Setup our lights
/// </summary>
void InitMaterialLight() {
	defaultLight = LightingData(glm::vec3(4, 4, 4),
								glm::vec3(0.2, 0.2, 0.1),
								glm::vec3(0.5, 0.5, 0.5),
								glm::vec3(0.7, 0.7, 0.7),
								1024);
}


int main(int argc, char** argv)
{
	// Initialize our scene
	InitGlutGlew(argc, argv);
	InitShaders();
	InitMatrices();
	InitMaterialLight();
	InitLoadObjects();
	
	// Allow blending and transparency 
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	// Allow depth of field
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);

	// Hide/Show console window
	HWND hWnd = GetConsoleWindow();
	ShowWindow(hWnd, SW_SHOW);

	// Main loop
	glutMainLoop();

	return 0;
}
											
										
#include "Primitives.h"

/// <summary>
/// Default constructor
/// </summary>
Primitives::Primitives()
{
}

/// <summary>
/// Default destructor
/// </summary>
Primitives::~Primitives()
{
}

/// <summary>
/// Render the primitive and initalize the buffers
/// </summary>
void Primitives::Render()
{
	GLuint position_id;
	GLuint color_id;

	GLuint vbo_vertices;
	GLuint vbo_colors;
	GLuint ibo_elements;

	// vbo for vertices
	glGenBuffers(1, &vbo_vertices);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_vertices);
	glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), &vertices[0], GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// vbo for colors
	glGenBuffers(1, &vbo_colors);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_colors);
	glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(GLfloat), &colors[0], GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// vbo for cube elements
	glGenBuffers(1, &ibo_elements);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_elements);
	glBufferData(
		GL_ELEMENT_ARRAY_BUFFER, elements.size() * sizeof(GLushort), &elements[0], GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);


	// Get vertex attributes
	position_id = glGetAttribLocation(shader_id, "position");
	color_id = glGetAttribLocation(shader_id, "color");

	// Allocate memory for vao
	glGenVertexArrays(1, &vao);

	// Bind to vao
	glBindVertexArray(vao);

	// Bind vertices to vao
	glBindBuffer(GL_ARRAY_BUFFER, vbo_vertices);
	glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(position_id);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// Bind colors to vao
	glBindBuffer(GL_ARRAY_BUFFER, vbo_colors);
	glVertexAttribPointer(color_id, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(color_id);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_elements);

	// Stop bind to vao
	glBindVertexArray(0);

	uniform_mvp = glGetUniformLocation(shader_id, "mv");
	glUseProgram(shader_id);
	glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, glm::value_ptr(mv));
}

/// <summary>
/// Set the data of the primitive
/// </summary>
/// <param name="vertices">Vertices for how the primitive will be displayed</param>
/// <param name="colors">Color of each vertice of the primitive</param>
/// <param name="elements">Elements for creating the primitive</param>
void Primitives::Set(vector<GLfloat> vertices, vector<GLfloat> colors, vector<GLushort> elements)
{
	this->vertices = vertices;
	this->colors = colors;
	this->elements = elements;
}

/// <summary>
/// Update our MV based on the view of our screen
/// </summary>
/// <param name="view"></param>
void Primitives::UpdateVertices(glm::mat4 view)
{
	mv = view * model;
}

/// <summary>
/// Move the primitive by towards a certain direction
/// </summary>
/// <param name="x">Move towards x axis by</param>
/// <param name="y">Move towards y axis by</param>
/// <param name="z">Move towards z axis by</param>
void Primitives::Translate(float x, float y, float z)
{
	position.Move(x, y, z);
	model = glm::translate(model, glm::vec3(x, y, z));
}

/// <summary>
/// Rotate primitive towards an angle
/// </summary>
/// <param name="x">Rotate towards x axis by</param>
/// <param name="y">Rotate towards y axis by</param>
/// <param name="z">Rotate towards z axis by</param>
/// <param name="angle">Rotate by angle</param>
void Primitives::Rotate(float x, float y, float z, float angle)
{
	rotation.Move(x * angle, y * angle, z * angle);
	model = glm::rotate(model, angle, glm::vec3(x, y, z));
}

/// <summary>
/// Scale primitive by passed values
/// </summary>
/// <param name="x">Scale x axis by</param>
/// <param name="y">Scale y axis by</param>
/// <param name="z">Scale z axis by</param>
void Primitives::Scale(float x, float y, float z)
{
	scale.Move((x - scale.x), (y - scale.y), (z - scale.z));
	model = glm::scale(model, glm::vec3(x, y, z));
}
											

										
#include "Model.h"

///////////////////////////
//  CLASS CONSTRUCTORS  //
/////////////////////////
#pragma region Constructors
/// <summary>
/// Default constructor
/// </summary>
Model::Model()
{
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
Model::Model(string modelName, const char* modelPath) {
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
/// <param name="shader">Shader ID to apply to the model</param>
Model::Model(string modelName, const char* modelPath, GLuint shader)
{
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
	shader_id = shader;
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
/// <param name="lightingData">Lighting data to be applied to the model</param>
Model::Model(string modelName, const char* modelPath, LightingData lightingData)
{
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
	lightData = lightingData;
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
/// <param name="texturePath">Directory of the texture to be applied</param>
Model::Model(string modelName, const char* modelPath, const char* texturePath)
{
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
	texture_id = loadBMP(texturePath);
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
/// <param name="shader">Shader ID to apply to the model</param>
/// <param name="lightingData">Lighting data to be applied to the model</param>
Model::Model(string modelName, const char* modelPath, GLuint shader, LightingData lightingData)
{
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
	shader_id = shader;
	lightData = lightingData;
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
/// <param name="texturePath">Directory of the texture to be applied</param>
/// <param name="shader">Shader ID to apply to the model</param>
Model::Model(string modelName, const char* modelPath, const char* texturePath, GLuint shader)
{
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
	texture_id = loadBMP(texturePath);
	shader_id = shader;
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
/// <param name="texturePath">Directory of the texture to be applied</param>
/// <param name="lightingData">Lighting data to be applied to the model</param>
Model::Model(string modelName, const char* modelPath, const char* texturePath, LightingData lightingData)
{
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
	texture_id = loadBMP(texturePath);
	lightData = lightingData;
}

/// <summary>
/// Constructor to create a model
/// </summary>
/// <param name="modelName">Name of model</param>
/// <param name="modelPath">Directory where model is stored</param>
/// <param name="texturePath">Directory of the texture to be applied</param>
/// <param name="shader">Shader ID to apply to the model</param>
/// <param name="lightingData">Lighting data to be applied to the model</param>
Model::Model(string modelName, const char* modelPath, const char* texturePath, GLuint shader, LightingData lightingData)
{
	this->modelName = modelName;
	loadOBJ(modelPath, vertices, uvs, normals);
	texture_id = loadBMP(texturePath);
	shader_id = shader;
	lightData = lightingData;
}

/// <summary>
/// Default destructor
/// </summary>
Model::~Model()
{
	// destructor
}

#pragma endregion Constructors

///////////////////////////
//    VISUAL RENDERING  //
/////////////////////////

/// <summary>
/// Create a buffer for our model and render it within the scene
/// </summary>
/// <param name="view">Current camera view</param>
/// <param name="projection">Camera Projection</param>
void Model::Render(glm::mat4 view, glm::mat4 projection)
{
	GLuint position_id;
	GLuint color_id;
	GLuint vbo_vertices;
	GLuint vbo_colors;
	GLuint ibo_elements;
	GLuint vbo_normals;
	GLuint vbo_uvs;
	GLuint normal_id;

	// vbo for vertices
	glGenBuffers(1, &vbo_vertices);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_vertices);
	glBufferData(GL_ARRAY_BUFFER,
		vertices.size() * sizeof(glm::vec3), &vertices[0],
		GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// vbo for normals
	glGenBuffers(1, &vbo_normals);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_normals);
	glBufferData(GL_ARRAY_BUFFER,
		normals.size() * sizeof(glm::vec3),
		&normals[0], GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// vbo for uvs
	glGenBuffers(1, &vbo_uvs);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_uvs);
	glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(glm::vec2),
		&uvs[0], GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// Get vertex attributes
	position_id = glGetAttribLocation(shader_id, "position");
	normal_id = glGetAttribLocation(shader_id, "normal");
	GLuint uv_id = glGetAttribLocation(shader_id, "uv");

	// Allocate memory for vao
	glGenVertexArrays(1, &vao);

	glBindVertexArray(vao);

	// Bind vertices to vao
	glBindBuffer(GL_ARRAY_BUFFER, vbo_vertices);
	glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(position_id);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// Bind normals to vao
	glBindBuffer(GL_ARRAY_BUFFER, vbo_normals);
	glVertexAttribPointer(normal_id, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(normal_id);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// Bind to vao
	glBindBuffer(GL_ARRAY_BUFFER, vbo_uvs);
	glVertexAttribPointer(uv_id, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(uv_id);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// Bind elements to vao
	// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_elements);

	// Stop bind to vao
	glBindVertexArray(0);

	// Make uniform vars
	uniform_mvp = glGetUniformLocation(shader_id, "mv");
	GLuint uniform_proj = glGetUniformLocation(shader_id, "projection");
	GLuint uniform_light_pos = glGetUniformLocation(shader_id, "light_pos");
	GLuint uniform_material_ambient = glGetUniformLocation(shader_id,
		"mat_ambient");
	GLuint uniform_material_diffuse = glGetUniformLocation(shader_id,
		"mat_diffuse");
	GLuint uniform_specular = glGetUniformLocation(
		shader_id, "mat_specular");
	GLuint uniform_material_power = glGetUniformLocation(
		shader_id, "mat_power");

	// Define model
	UpdateModelVertices(view);

	// Send mvp
	glUseProgram(shader_id);
	glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, glm::value_ptr(mv));
	glUniformMatrix4fv(uniform_proj, 1, GL_FALSE, glm::value_ptr(projection));
	glUniform3fv(uniform_light_pos, 1, glm::value_ptr(lightData.light_position));
	glUniform3fv(uniform_material_ambient, 1, glm::value_ptr(lightData.ambient_color));
	glUniform3fv(uniform_material_diffuse, 1, glm::value_ptr(lightData.diffuse_color));
	glUniform3fv(uniform_specular, 1, glm::value_ptr(lightData.specular));
	glUniform1f(uniform_material_power, lightData.power);
}

/// <summary>
/// Update our models verticies/MV
/// </summary>
/// <param name="view">Current camera view</param>
void Model::UpdateModelVertices(glm::mat4 view)
{
	mv = view * model;
}

///////////////////////////
//    TRANSFORMATIONS   //
/////////////////////////
/// <summary>
/// Move the model by towards a certain direction
/// </summary>
/// <param name="x">Move towards x axis by</param>
/// <param name="y">Move towards y axis by</param>
/// <param name="z">Move towards z axis by</param>
void Model::Translate(float x, float y, float z) 
{
	position.Move(x, y, z);
	model = glm::translate(model, glm::vec3(x, y, z));
}

/// <summary>
/// Rotate model towards an angle
/// </summary>
/// <param name="x">Rotate towards x axis by</param>
/// <param name="y">Rotate towards y axis by</param>
/// <param name="z">Rotate towards z axis by</param>
/// <param name="angle">Rotate by angle</param>
void Model::Rotate(float x, float y, float z, float angle)
{
	rotation.Move(x * angle, y * angle, z * angle);
	model = glm::rotate(model, angle, glm::vec3(x, y, z));
}

/// <summary>
/// Scale model by passed values
/// </summary>
/// <param name="x">Scale x axis by</param>
/// <param name="y">Scale y axis by</param>
/// <param name="z">Scale z axis by</param>
void Model::Scale(float x, float y, float z)
{   
	scale.Move((x - scale.x), (y - scale.y), (z - scale.z));
	model = glm::scale(model, glm::vec3(x, y, z));
}
											

										
// VERTEX SHADER

#version 430 core

uniform mat4 mv;
uniform vec3 light_pos;
uniform mat4 projection;


in vec3 position;
in vec3 normal;
in vec3 color;
in vec2 uv;
out vec2 UV;

out VS_OUT
{
	vec3 N;
	vec3 L;
	vec3 V;
} vs_out;


void main()
{
	// Calculate view-space coordinate
	vec4 P = mv * vec4(position, 1.0);

	// Calculate normal in view-space
	vs_out.N = mat3(mv) * normal;

	// Calculate light vector
	vs_out.L = light_pos - P.xyz;

	vs_out.V = -P.xyz;

	UV = uv;

	// Calculate the clip-space position of each vertex
	gl_Position = projection * P;
}


// FRAGMENT SHADER
#version 430 core

uniform vec3 mat_ambient;
uniform vec3 mat_diffuse;
uniform vec3 mat_specular;
uniform float mat_power;

in vec2 UV;
uniform sampler2D texsampler;


in VS_OUT
{
	vec3 N;
	vec3 L;
	vec3 V;
} fs_in;

out vec4 FragColor;

void main()
{
	// Normalize the incoming N and L vectors
	vec3 N = normalize(fs_in.N);
	vec3 L = normalize(fs_in.L);
	vec3 V = normalize(fs_in.V);

	// Calculate R locally
	vec3 R = reflect(-L, N);

	// Compute the diffuse component for each fragment
	//vec3 diffuse = max(dot(N, L), 0.0) * mat_diffuse;

	vec3 diffuse = max(dot(N, L), 0.0) *
		texture2D(texsampler, UV).rgb;

	// Compute the specular component for each fragment
	vec3 specular = pow(max(dot(R, V), 0.0), mat_power) * mat_specular;

	// Write final color to the framebuffer
	FragColor = vec4(mat_ambient + specular + diffuse, 1.0); // alpha set
}
										
#include "glsl.h"

char* glsl::contents;

char* glsl::readFile(const char* filename)
{
	// Open the file
	FILE* fp = fopen(filename, "r");
	// Move the file pointer to the end of the file and determing the length
	fseek(fp, 0, SEEK_END);
	long file_length = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	char* contents = new char[file_length + 1];
	// zero out memory
	for (int i = 0; i < file_length + 1; i++) 
	{
		contents[i] = 0;
	}
	// Here's the actual read
	fread(contents, 1, file_length, fp);
	// This is how you denote the end of a string in C
	contents[file_length + 1] = '\0';
	fclose(fp);
	return contents;
}

bool glsl::compiledStatus(GLint shaderID)
{
	GLint compiled = 0;
	glGetShaderiv(shaderID, GL_COMPILE_STATUS, &compiled);
	if (compiled) {
		return true;
	}
	else {
		GLint logLength;
		glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &logLength);
		char* msgBuffer = new char[logLength];
		glGetShaderInfoLog(shaderID, logLength, NULL, msgBuffer);
		printf("%s\n", msgBuffer);
		delete (msgBuffer);
		return false;
	}
}

GLuint glsl::makeVertexShader(const char* shaderSource)
{
	GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShaderID, 1, (const GLchar**)&shaderSource, NULL);
	glCompileShader(vertexShaderID);
	bool compiledCorrectly = compiledStatus(vertexShaderID);
	if (compiledCorrectly)
	{
		return vertexShaderID;
	}
	return -1;
}

GLuint glsl::makeFragmentShader(const char* shaderSource)
{
	GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShaderID, 1, (const GLchar**)&shaderSource, NULL);
	glCompileShader(fragmentShaderID);
	//delete[] source;
	bool compiledCorrectly = compiledStatus(fragmentShaderID);
	if (compiledCorrectly) {
		return fragmentShaderID;
	}
	return -1;
}

GLuint glsl::makeShaderProgram(GLuint vertexShaderID, GLuint fragmentShaderID)
{
	GLuint shaderID = glCreateProgram();
	glAttachShader(shaderID, vertexShaderID);
	glAttachShader(shaderID, fragmentShaderID);
	glLinkProgram(shaderID);
	return shaderID;
}