Code cleanup
This commit is contained in:
parent
7e3789e640
commit
dae8fa4ba7
|
@ -0,0 +1,49 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
project(renderer)
|
||||
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
|
||||
|
||||
add_executable(renderer)
|
||||
|
||||
target_compile_options(
|
||||
renderer
|
||||
PRIVATE
|
||||
-fno-exceptions
|
||||
-Wall)
|
||||
|
||||
target_sources(
|
||||
renderer
|
||||
PRIVATE
|
||||
Source/Engine.cpp
|
||||
Source/Loader.cpp
|
||||
Source/Main.cpp
|
||||
Source/Platform.cpp
|
||||
Source/Render.cpp
|
||||
Source/Transform.cpp)
|
||||
|
||||
target_sources(
|
||||
renderer
|
||||
PRIVATE
|
||||
Source/Camera.hpp
|
||||
Source/Color.hpp
|
||||
Source/Geometry.hpp
|
||||
Source/Matrix.hpp
|
||||
Source/Point.hpp
|
||||
Source/Vec.hpp)
|
||||
|
||||
target_include_directories(
|
||||
renderer
|
||||
PRIVATE
|
||||
Source)
|
||||
|
||||
target_link_libraries(
|
||||
renderer
|
||||
PRIVATE
|
||||
SDL2::SDL2)
|
61
Makefile
61
Makefile
|
@ -1,61 +0,0 @@
|
|||
# Verbosity of make output
|
||||
ifeq ("$(VERBOSE)","1")
|
||||
V :=
|
||||
else
|
||||
V := @
|
||||
endif
|
||||
|
||||
# Optimizations
|
||||
ifeq ("$(OPTIMIZE)","0")
|
||||
O := -O0
|
||||
else ifeq ("$(OPTIMIZE)","1")
|
||||
O := -O1
|
||||
else ifeq ("$(OPTIMIZE)", "2")
|
||||
O := -O2
|
||||
else ifeq ("$(OPTIMIZE)", "3")
|
||||
O := -O3
|
||||
else
|
||||
O := -O3
|
||||
endif
|
||||
|
||||
# Debugging
|
||||
ifeq ("$(DEBUG)","1")
|
||||
D := -g
|
||||
else
|
||||
D :=
|
||||
endif
|
||||
|
||||
|
||||
SRC_DIR=src
|
||||
INCLUDE_DIR=include
|
||||
BUILD_DIR=build
|
||||
|
||||
CC=clang++
|
||||
WARNINGS_ON=-Weverything
|
||||
WARNINGS_OFF=-Wno-missing-braces -Wno-gnu-anonymous-struct -Wno-old-style-cast\
|
||||
-Wno-zero-as-null-pointer-constant -Wno-nested-anon-types\
|
||||
-Wno-padded -Wno-exit-time-destructors -Wno-global-constructors\
|
||||
-Wno-c++98-compat
|
||||
|
||||
CFLAGS=$(D) $(O) -std=c++11 $(WARNINGS_ON) $(WARNINGS_OFF) -I$(INCLUDE_DIR)
|
||||
LIBS=-lSDL2
|
||||
|
||||
_HEADERS = camera.h color.h engine.h geometry.h light.h loader.h matrix.h\
|
||||
platform.h point.h render.h transform.h util.h vec.h
|
||||
HEADERS = $(patsubst %,$(INCLUDE_DIR)/%,$(_HEADERS))
|
||||
|
||||
_OBJS = engine.o light.o loader.o main.o platform.o render.o\
|
||||
transform.o
|
||||
OBJS = $(patsubst %,$(BUILD_DIR)/%,$(_OBJS))
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp $(HEADERS)
|
||||
@ if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
|
||||
$(V) $(CC) -c -o $@ $< $(CFLAGS)
|
||||
|
||||
$(BUILD_DIR)/engine: $(OBJS)
|
||||
$(V) $(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR)
|
|
@ -16,13 +16,6 @@ To best learn what a GPU is doing, I wanted to recreate the functionality
|
|||
of the GPU in software. It's not anywhere near as fast as a GPU of course,
|
||||
but it's not slow either (for a single model that is relatively low-poly).
|
||||
|
||||
### Simple code
|
||||
|
||||
Coming from a C background, I wanted to use features of C++ that I found made
|
||||
the code cleaner and easier to understand while avoiding many of the fancier
|
||||
ones that seemed unnecessary. Primarily I leveraged operator overloading for
|
||||
vector and matrix operations, and used some simple classes with constructors.
|
||||
|
||||
### Good cache performance
|
||||
|
||||
The biggest bottleneck of modern CPU performance is latency between memory and
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "Point.hpp"
|
||||
#include <cmath>
|
||||
|
||||
|
||||
constexpr float DegToRad(float degrees)
|
||||
{
|
||||
return degrees * (float)M_PI / 180.0f;
|
||||
}
|
||||
|
||||
|
||||
class Camera
|
||||
{
|
||||
public:
|
||||
Camera()
|
||||
{
|
||||
position.x = 0.0f;
|
||||
position.y = 0.0f;
|
||||
position.z = 0.0f;
|
||||
|
||||
rotation.x = 0.0f;
|
||||
rotation.y = 0.0f;
|
||||
rotation.z = 0.0f;
|
||||
|
||||
zClipBias0 =
|
||||
(CAMERA_FAR_CLIP + CAMERA_NEAR_CLIP)
|
||||
/ (CAMERA_FAR_CLIP - CAMERA_NEAR_CLIP);
|
||||
|
||||
zClipBias1 =
|
||||
(-2.0f * CAMERA_FAR_CLIP * CAMERA_NEAR_CLIP)
|
||||
/ (CAMERA_FAR_CLIP - CAMERA_NEAR_CLIP);
|
||||
|
||||
xZoom = 1.0f / tanf(DegToRad(CAMERA_FOV / 2.0f));
|
||||
yZoom = (xZoom * WINDOW_WIDTH) / WINDOW_HEIGHT;
|
||||
|
||||
xScale = (0.5f * WINDOW_WIDTH) - 0.5f;
|
||||
yScale = (0.5f * WINDOW_HEIGHT) - 0.5f;
|
||||
}
|
||||
|
||||
Point position;
|
||||
Point rotation;
|
||||
float zClipBias0, zClipBias1;
|
||||
float xZoom, yZoom;
|
||||
float xScale, yScale;
|
||||
};
|
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
class ColorU32
|
||||
{
|
||||
public:
|
||||
ColorU32()
|
||||
: b(0), g(0), r(0), a(0)
|
||||
{}
|
||||
|
||||
ColorU32(uint8_t b, uint8_t g, uint8_t r, uint8_t a)
|
||||
: b(b), g(g), r(r), a(a)
|
||||
{}
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint8_t b, g, r, a;
|
||||
};
|
||||
|
||||
uint32_t u32;
|
||||
};
|
||||
};
|
||||
|
||||
class ColorF32
|
||||
{
|
||||
public:
|
||||
ColorF32()
|
||||
: b(0.0f), g(0.0f), r(0.0f), a(0.0f)
|
||||
{}
|
||||
|
||||
ColorF32(float b, float g, float r, float a)
|
||||
: b(b), g(g), r(r), a(a)
|
||||
{}
|
||||
|
||||
void Scale()
|
||||
{
|
||||
float blue = std::max(b, 0.0f);
|
||||
float green = std::max(g, 0.0f);
|
||||
float red = std::max(r, 0.0f);
|
||||
float alpha = std::max(a, 0.0f);
|
||||
float max = std::max(std::max(std::max(blue, green), red), 1.0f);
|
||||
|
||||
ColorF32 scaled(blue, green, red, alpha);
|
||||
scaled /= max;
|
||||
|
||||
b = scaled.b;
|
||||
g = scaled.g;
|
||||
r = scaled.r;
|
||||
a = scaled.a;
|
||||
}
|
||||
|
||||
float b, g, r, a;
|
||||
|
||||
ColorF32 operator+(ColorF32 const& rhs) const
|
||||
{
|
||||
return ColorF32(
|
||||
b + rhs.b,
|
||||
g + rhs.g,
|
||||
r + rhs.r,
|
||||
a + rhs.a);
|
||||
}
|
||||
|
||||
ColorF32& operator+=(ColorF32 const& rhs)
|
||||
{
|
||||
b += rhs.b;
|
||||
g += rhs.g;
|
||||
r += rhs.r;
|
||||
a += rhs.a;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ColorF32 operator*(float f) const
|
||||
{
|
||||
return ColorF32(
|
||||
b * f,
|
||||
g * f,
|
||||
r * f,
|
||||
a * f);
|
||||
}
|
||||
|
||||
ColorF32 operator*(ColorF32 const& rhs) const
|
||||
{
|
||||
return ColorF32(
|
||||
b * rhs.b,
|
||||
g * rhs.g,
|
||||
r * rhs.r,
|
||||
a * rhs.a);
|
||||
}
|
||||
|
||||
ColorF32 operator/(float f) const
|
||||
{
|
||||
float fInv = 1.0f / f;
|
||||
|
||||
return ColorF32(
|
||||
b * fInv,
|
||||
g * fInv,
|
||||
r * fInv,
|
||||
a * fInv);
|
||||
}
|
||||
|
||||
ColorF32& operator/=(float f)
|
||||
{
|
||||
b /= f;
|
||||
g /= f;
|
||||
r /= f;
|
||||
a /= f;
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,345 @@
|
|||
#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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "Geometry.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
const int WINDOW_WIDTH = 1920;
|
||||
const int WINDOW_HEIGHT = 1080;
|
||||
const int WINDOW_FPS = 30;
|
||||
const float CAMERA_FOV = 90.0f;
|
||||
const float CAMERA_NEAR_CLIP = 5.0f;
|
||||
const float CAMERA_FAR_CLIP = 600.0f;
|
||||
|
||||
|
||||
enum EngineInput
|
||||
{
|
||||
TRANSLATE_X_POS,
|
||||
TRANSLATE_X_NEG,
|
||||
TRANSLATE_Y_POS,
|
||||
TRANSLATE_Y_NEG,
|
||||
TRANSLATE_Z_POS,
|
||||
TRANSLATE_Z_NEG,
|
||||
ROTATE_X_POS,
|
||||
ROTATE_X_NEG,
|
||||
ROTATE_Y_POS,
|
||||
ROTATE_Y_NEG,
|
||||
ROTATE_Z_POS,
|
||||
ROTATE_Z_NEG,
|
||||
SCALE_UP,
|
||||
SCALE_DOWN
|
||||
};
|
||||
|
||||
|
||||
struct EngineBuffer
|
||||
{
|
||||
uint32_t* buffer;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
struct EngineMemory
|
||||
{
|
||||
float zbuffer[WINDOW_HEIGHT][WINDOW_WIDTH];
|
||||
VertexList localVerts;
|
||||
VertexList transVerts;
|
||||
FaceList localFaces;
|
||||
FaceList transFaces;
|
||||
UVList uvs;
|
||||
MaterialList materials;
|
||||
TextureList textures;
|
||||
};
|
||||
|
||||
int EngineInit(char* objFilename, char* mtlFilename);
|
||||
void EngineRender(EngineBuffer& buffer, uint32_t input);
|
||||
void EngineShutdown();
|
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include "Color.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Vec.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
const int FACE_LIMIT = 30000;
|
||||
const int MATERIAL_LIMIT = 5;
|
||||
const int TEXTURE_SIZE_LIMIT = 1024;
|
||||
const int VERTEX_LIMIT = 20000;
|
||||
|
||||
|
||||
struct Texture
|
||||
{
|
||||
ColorU32 texels[TEXTURE_SIZE_LIMIT][TEXTURE_SIZE_LIMIT];
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
};
|
||||
|
||||
struct TextureList
|
||||
{
|
||||
Texture data[MATERIAL_LIMIT];
|
||||
};
|
||||
|
||||
struct Material
|
||||
{
|
||||
ColorF32 ambient;
|
||||
ColorF32 diffuse;
|
||||
ColorF32 specular;
|
||||
float glossiness;
|
||||
float opacity;
|
||||
};
|
||||
|
||||
struct MaterialList
|
||||
{
|
||||
Material data[MATERIAL_LIMIT];
|
||||
unsigned long size;
|
||||
};
|
||||
|
||||
struct UV
|
||||
{
|
||||
float u;
|
||||
float v;
|
||||
};
|
||||
|
||||
struct UVList
|
||||
{
|
||||
UV data[VERTEX_LIMIT];
|
||||
unsigned long size;
|
||||
};
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
Point position;
|
||||
Vector normal;
|
||||
ColorF32 color;
|
||||
};
|
||||
|
||||
struct VertexList
|
||||
{
|
||||
Vertex data[VERTEX_LIMIT];
|
||||
unsigned long size;
|
||||
};
|
||||
|
||||
struct Face
|
||||
{
|
||||
int vertIndex[3];
|
||||
int uvIndex[3];
|
||||
int materialIndex;
|
||||
};
|
||||
|
||||
struct FaceList
|
||||
{
|
||||
Face data[FACE_LIMIT];
|
||||
unsigned long size;
|
||||
};
|
||||
|
||||
struct Mesh
|
||||
{
|
||||
Point position;
|
||||
Point rotation;
|
||||
float scale;
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include "Camera.hpp"
|
||||
#include "Color.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
|
||||
class Light
|
||||
{
|
||||
public:
|
||||
ColorF32 Compute(Point& eye, Vector& normal, Material& material, Camera& camera)
|
||||
{
|
||||
// Point light intensity is a function of the falloff factors and the distance
|
||||
Vector direction = position - eye;
|
||||
direction.Normalize();
|
||||
float distance = direction.Length();
|
||||
|
||||
ColorF32 totalIntensity =
|
||||
(color * intensity)
|
||||
/ (falloffConstant + (falloffLinear * distance));
|
||||
|
||||
|
||||
// Diffuse Light = Kr * I * (alpha + (1 - alpha) * n.d)
|
||||
float dotNormalDirection = std::max(Vector::Dot(normal, direction), 0.0f);
|
||||
float alpha = 0.2f;
|
||||
|
||||
ColorF32 diffuseLight =
|
||||
material.diffuse
|
||||
* totalIntensity
|
||||
* (alpha + (1.0f - alpha) * dotNormalDirection);
|
||||
|
||||
|
||||
// Specular Light = Ks * I * (r.v)^sp
|
||||
Vector view = camera.position - eye;
|
||||
view.Normalize();
|
||||
Vector reflection = (normal * 2.0f * dotNormalDirection) - direction;
|
||||
float dotReflectionView = std::max(Vector::Dot(reflection, view), 0.0f);
|
||||
|
||||
ColorF32 specularLight =
|
||||
material.specular
|
||||
* totalIntensity
|
||||
* powf(dotReflectionView, material.glossiness);
|
||||
|
||||
|
||||
// Total light is sum of all lights
|
||||
ColorF32 result = diffuseLight + specularLight;
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Point position;
|
||||
ColorF32 color;
|
||||
float intensity;
|
||||
float falloffConstant;
|
||||
float falloffLinear;
|
||||
};
|
|
@ -0,0 +1,256 @@
|
|||
#include "Engine.hpp"
|
||||
#include "Loader.hpp"
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
static int LoadTexture(char* filename, Texture& texture, float opacity);
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct BMP_Header
|
||||
{
|
||||
uint16_t fileType;
|
||||
uint32_t fileSize;
|
||||
uint16_t reserved0;
|
||||
uint16_t reserved1;
|
||||
uint32_t bitmapOffset;
|
||||
uint32_t size;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint16_t planes;
|
||||
uint16_t bitsPerPixel;
|
||||
uint32_t compression;
|
||||
uint32_t sizeOfBitmap;
|
||||
int32_t horizRes;
|
||||
int32_t vertRes;
|
||||
uint32_t colorsUsed;
|
||||
uint32_t colorsImportant;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
int ParseOBJ(char* filename, EngineMemory& memory)
|
||||
{
|
||||
FILE* fp = fopen(filename, "r");
|
||||
|
||||
if (fp == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Error loading file: %s\n", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
char line[256];
|
||||
unsigned long vertIndex = 0;
|
||||
unsigned long uvIndex = 0;
|
||||
unsigned long faceIndex = 0;
|
||||
int materialIndex = -1;
|
||||
|
||||
VertexList& verts = memory.localVerts;
|
||||
FaceList& faces = memory.localFaces;
|
||||
UVList& uvs = memory.uvs;
|
||||
|
||||
while (fgets(line, sizeof(line), fp))
|
||||
{
|
||||
char* separator = strchr(line, ' ');
|
||||
|
||||
if (separator != nullptr)
|
||||
{
|
||||
*separator = '\0';
|
||||
char* type = line;
|
||||
char* data = separator + 1;
|
||||
|
||||
if (strcmp(type, "v") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%f %f %f",
|
||||
&verts.data[vertIndex].position.x,
|
||||
&verts.data[vertIndex].position.y,
|
||||
&verts.data[vertIndex].position.z);
|
||||
|
||||
verts.data[vertIndex].position.w = 1.0f;
|
||||
|
||||
++vertIndex;
|
||||
}
|
||||
|
||||
else if (strcmp(type, "vt") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%f %f",
|
||||
&uvs.data[uvIndex].u,
|
||||
&uvs.data[uvIndex].v);
|
||||
|
||||
++uvIndex;
|
||||
}
|
||||
else if (strcmp(type, "usemtl") == 0)
|
||||
{
|
||||
++materialIndex;
|
||||
}
|
||||
|
||||
else if (strcmp(type, "f") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%d/%d %d/%d %d/%d",
|
||||
&faces.data[faceIndex].vertIndex[0],
|
||||
&faces.data[faceIndex].uvIndex[0],
|
||||
&faces.data[faceIndex].vertIndex[1],
|
||||
&faces.data[faceIndex].uvIndex[1],
|
||||
&faces.data[faceIndex].vertIndex[2],
|
||||
&faces.data[faceIndex].uvIndex[2]);
|
||||
|
||||
// Convert to 0-indexed
|
||||
faces.data[faceIndex].vertIndex[0] -= 1;
|
||||
faces.data[faceIndex].vertIndex[1] -= 1;
|
||||
faces.data[faceIndex].vertIndex[2] -= 1;
|
||||
faces.data[faceIndex].uvIndex[0] -= 1;
|
||||
faces.data[faceIndex].uvIndex[1] -= 1;
|
||||
faces.data[faceIndex].uvIndex[2] -= 1;
|
||||
|
||||
faces.data[faceIndex].materialIndex = materialIndex;
|
||||
|
||||
++faceIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verts.size = vertIndex;
|
||||
uvs.size = uvIndex;
|
||||
faces.size = faceIndex;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ParseMTL(char* filename, EngineMemory& memory)
|
||||
{
|
||||
FILE* fp = fopen(filename, "r");
|
||||
|
||||
if (fp == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Error loading file: %s\n", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
int materialIndex = -1;
|
||||
|
||||
MaterialList& materials = memory.materials;
|
||||
TextureList& textures = memory.textures;
|
||||
|
||||
while (fgets(line, sizeof(line), fp))
|
||||
{
|
||||
char* separator = strchr(line, ' ');
|
||||
|
||||
if (separator != nullptr)
|
||||
{
|
||||
*separator = '\0';
|
||||
char* type = line;
|
||||
char* data = separator + 1;
|
||||
|
||||
if (strcmp(type, "newmtl") == 0)
|
||||
{
|
||||
++materialIndex;
|
||||
}
|
||||
|
||||
else if (strcmp(type, "Ns") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%f",
|
||||
&materials.data[materialIndex].glossiness);
|
||||
}
|
||||
|
||||
else if (strcmp(type, "Ka") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%f %f %f",
|
||||
&materials.data[materialIndex].ambient.r,
|
||||
&materials.data[materialIndex].ambient.g,
|
||||
&materials.data[materialIndex].ambient.b);
|
||||
}
|
||||
|
||||
else if (strcmp(type, "Kd") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%f %f %f",
|
||||
&materials.data[materialIndex].diffuse.r,
|
||||
&materials.data[materialIndex].diffuse.g,
|
||||
&materials.data[materialIndex].diffuse.b);
|
||||
}
|
||||
|
||||
else if (strcmp(type, "Ks") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%f %f %f",
|
||||
&materials.data[materialIndex].specular.r,
|
||||
&materials.data[materialIndex].specular.g,
|
||||
&materials.data[materialIndex].specular.b);
|
||||
}
|
||||
|
||||
else if (strcmp(type, "d") == 0)
|
||||
{
|
||||
sscanf(
|
||||
data, "%f",
|
||||
&materials.data[materialIndex].opacity);
|
||||
}
|
||||
|
||||
else if (strcmp(type, "map_Kd") == 0)
|
||||
{
|
||||
char* textureFilename = data;
|
||||
textureFilename[strcspn(textureFilename, "\r\n")] = 0;
|
||||
LoadTexture(
|
||||
textureFilename, textures.data[materialIndex],
|
||||
materials.data[materialIndex].opacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
materials.size = materialIndex + 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int LoadTexture(char* filename, Texture& texture, float opacity)
|
||||
{
|
||||
FILE* fp = fopen(filename, "r");
|
||||
if (fp == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Could not open file: %s\n", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
BMP_Header header = {};
|
||||
fread((void*)&header, sizeof(BMP_Header), 1, fp);
|
||||
fseek(fp, header.bitmapOffset, SEEK_SET);
|
||||
|
||||
// Padding is added to image to align to 4-byte boundaries
|
||||
unsigned long paddingSize = static_cast<unsigned long>(header.width % 4);
|
||||
|
||||
for (int y = 0; y < header.height; ++y)
|
||||
{
|
||||
for (int x = 0; x < header.width; ++x)
|
||||
{
|
||||
fread(&texture.texels[y][x].b, 1, 1, fp);
|
||||
fread(&texture.texels[y][x].g, 1, 1, fp);
|
||||
fread(&texture.texels[y][x].r, 1, 1, fp);
|
||||
texture.texels[y][x].a = (uint8_t)(255 * opacity);
|
||||
}
|
||||
|
||||
// Discard padding byte
|
||||
if (paddingSize != 0)
|
||||
{
|
||||
uint32_t padding;
|
||||
fread(&padding, paddingSize, 1, fp);
|
||||
}
|
||||
}
|
||||
|
||||
texture.width = (unsigned int)header.width;
|
||||
texture.height = (unsigned int)header.height;
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Engine.hpp"
|
||||
|
||||
|
||||
int ParseOBJ(char* filename, EngineMemory& memory);
|
||||
int ParseMTL(char* filename, EngineMemory& memory);
|
|
@ -0,0 +1,60 @@
|
|||
#include "Engine.hpp"
|
||||
#include "Platform.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
// MAIN
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <OBJ> <MTL>\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
char* objFilename = argv[1];
|
||||
char* mtlFilename = argv[2];
|
||||
Platform platform{};
|
||||
|
||||
if (Platform_Init(platform, WINDOW_WIDTH, WINDOW_HEIGHT) == PlatformStatus::Error)
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (EngineInit(objFilename, mtlFilename) < 0)
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
EngineBuffer buffer{};
|
||||
buffer.buffer = reinterpret_cast<uint32_t*>(platform.surface->pixels);
|
||||
buffer.width = platform.surface->w;
|
||||
buffer.height = platform.surface->h;
|
||||
|
||||
while (true)
|
||||
{
|
||||
Platform_GetFrameTime(platform);
|
||||
|
||||
if (Platform_CheckForEvents(platform) == PlatformStatus::Quit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Platform_ClearWindow(platform);
|
||||
|
||||
EngineRender(buffer, platform.input);
|
||||
|
||||
Platform_UpdateWindow(platform);
|
||||
|
||||
Platform_SyncToFramerate(platform);
|
||||
}
|
||||
|
||||
EngineShutdown();
|
||||
|
||||
Platform_Shutdown(platform);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
class Matrix
|
||||
{
|
||||
public:
|
||||
Matrix()
|
||||
{
|
||||
e11 = 1.0; e12 = 0.0; e13 = 0.0; e14 = 0.0;
|
||||
e21 = 0.0; e22 = 1.0; e23 = 0.0; e24 = 0.0;
|
||||
e31 = 0.0; e32 = 0.0; e33 = 1.0; e34 = 0.0;
|
||||
e41 = 0.0; e42 = 0.0; e43 = 0.0; e44 = 1.0;
|
||||
}
|
||||
|
||||
union
|
||||
{
|
||||
float e[4][4];
|
||||
|
||||
struct
|
||||
{
|
||||
float e11, e12, e13, e14;
|
||||
float e21, e22, e23, e24;
|
||||
float e31, e32, e33, e34;
|
||||
float e41, e42, e43, e44;
|
||||
};
|
||||
};
|
||||
|
||||
Matrix operator*(Matrix const& rhs)
|
||||
{
|
||||
Matrix result;
|
||||
|
||||
for (int row = 0; row < 4; ++row)
|
||||
{
|
||||
for (int col = 0; col < 4; ++col)
|
||||
{
|
||||
float sum = 0.0;
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
sum += e[row][i] * rhs.e[i][col];
|
||||
}
|
||||
|
||||
result.e[row][col] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,243 @@
|
|||
#include "Engine.hpp"
|
||||
#include "Platform.hpp"
|
||||
#include <cstdint>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
void constexpr SetBit(uint32_t& x, unsigned long bit)
|
||||
{
|
||||
x |= (1UL << bit);
|
||||
}
|
||||
|
||||
void constexpr ClearBit(uint32_t& x, unsigned long bit)
|
||||
{
|
||||
x &= ~(1UL << bit);
|
||||
}
|
||||
|
||||
static void HandleEvent(Platform& platform, SDL_Event& event);
|
||||
|
||||
|
||||
PlatformStatus Platform_Init(Platform& platform, int width, int height)
|
||||
{
|
||||
int result = SDL_Init(SDL_INIT_VIDEO);
|
||||
if (result < 0)
|
||||
{
|
||||
fprintf(stderr, "Error initializing SDL: %s\n", SDL_GetError());
|
||||
return PlatformStatus::Error;
|
||||
}
|
||||
|
||||
|
||||
SDL_Window* window = SDL_CreateWindow(
|
||||
"Soft 3D Engine",
|
||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
width, height,
|
||||
SDL_WINDOW_SHOWN);
|
||||
if (window == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Error creating SDL window: %s\n", SDL_GetError());
|
||||
return PlatformStatus::Error;
|
||||
}
|
||||
|
||||
|
||||
SDL_Surface* surface = SDL_GetWindowSurface(window);
|
||||
if (surface == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Error getting SDL window surface: %s\n", SDL_GetError());
|
||||
return PlatformStatus::Error;
|
||||
}
|
||||
|
||||
|
||||
result = SDL_ShowCursor(SDL_DISABLE);
|
||||
if (result < 0)
|
||||
{
|
||||
fprintf(stderr, "Error disabling cursor in SDL window: %s\n", SDL_GetError());
|
||||
return PlatformStatus::Error;
|
||||
}
|
||||
|
||||
platform.framerateMillis = (1000 / WINDOW_FPS);
|
||||
platform.window = window;
|
||||
platform.surface = surface;
|
||||
|
||||
return PlatformStatus::Ok;
|
||||
}
|
||||
|
||||
PlatformStatus Platform_CheckForEvents(Platform& platform)
|
||||
{
|
||||
SDL_Event event;
|
||||
|
||||
while (SDL_PollEvent(&event) != 0)
|
||||
{
|
||||
if (event.type == SDL_QUIT)
|
||||
{
|
||||
return PlatformStatus::Quit;
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleEvent(platform, event);
|
||||
}
|
||||
}
|
||||
|
||||
return PlatformStatus::Ok;
|
||||
}
|
||||
|
||||
void Platform_ClearWindow(Platform& platform)
|
||||
{
|
||||
SDL_LockSurface(platform.surface);
|
||||
SDL_FillRect(platform.surface, nullptr, 0);
|
||||
}
|
||||
|
||||
void Platform_UpdateWindow(Platform& platform)
|
||||
{
|
||||
SDL_UnlockSurface(platform.surface);
|
||||
SDL_UpdateWindowSurface(platform.window);
|
||||
}
|
||||
|
||||
void Platform_GetFrameTime(Platform& platform)
|
||||
{
|
||||
platform.frameStartMillis = SDL_GetTicks();
|
||||
}
|
||||
|
||||
void Platform_SyncToFramerate(Platform& platform)
|
||||
{
|
||||
uint32_t stopTimeMillis = SDL_GetTicks();
|
||||
uint32_t framerateMillis = stopTimeMillis - platform.frameStartMillis;
|
||||
|
||||
// Delay if time to spare
|
||||
if (framerateMillis < platform.framerateMillis)
|
||||
{
|
||||
uint32_t delayMillis = platform.framerateMillis - framerateMillis;
|
||||
SDL_Delay(delayMillis);
|
||||
}
|
||||
}
|
||||
|
||||
void Platform_Shutdown(Platform& platform)
|
||||
{
|
||||
SDL_DestroyWindow(platform.window);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
|
||||
// PRIVATE FUNCTIONS
|
||||
static void HandleEvent(
|
||||
Platform& platform, SDL_Event& event)
|
||||
{
|
||||
if (event.type == SDL_KEYDOWN)
|
||||
{
|
||||
if (event.key.keysym.sym == SDLK_w)
|
||||
{
|
||||
SetBit(platform.input, TRANSLATE_Z_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_s)
|
||||
{
|
||||
SetBit(platform.input, TRANSLATE_Z_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_a)
|
||||
{
|
||||
SetBit(platform.input, TRANSLATE_X_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_d)
|
||||
{
|
||||
SetBit(platform.input, TRANSLATE_X_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_q)
|
||||
{
|
||||
SetBit(platform.input, TRANSLATE_Y_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_e)
|
||||
{
|
||||
SetBit(platform.input, TRANSLATE_Y_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_i)
|
||||
{
|
||||
SetBit(platform.input, ROTATE_X_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_k)
|
||||
{
|
||||
SetBit(platform.input, ROTATE_X_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_j)
|
||||
{
|
||||
SetBit(platform.input, ROTATE_Y_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_l)
|
||||
{
|
||||
SetBit(platform.input, ROTATE_Y_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_u)
|
||||
{
|
||||
SetBit(platform.input, ROTATE_Z_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_o)
|
||||
{
|
||||
SetBit(platform.input, ROTATE_Z_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_UP)
|
||||
{
|
||||
SetBit(platform.input, SCALE_UP);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_DOWN)
|
||||
{
|
||||
SetBit(platform.input, SCALE_DOWN);
|
||||
}
|
||||
}
|
||||
else if (event.type == SDL_KEYUP)
|
||||
{
|
||||
if (event.key.keysym.sym == SDLK_w)
|
||||
{
|
||||
ClearBit(platform.input, TRANSLATE_Z_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_s)
|
||||
{
|
||||
ClearBit(platform.input, TRANSLATE_Z_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_a)
|
||||
{
|
||||
ClearBit(platform.input, TRANSLATE_X_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_d)
|
||||
{
|
||||
ClearBit(platform.input, TRANSLATE_X_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_q)
|
||||
{
|
||||
ClearBit(platform.input, TRANSLATE_Y_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_e)
|
||||
{
|
||||
ClearBit(platform.input, TRANSLATE_Y_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_i)
|
||||
{
|
||||
ClearBit(platform.input, ROTATE_X_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_k)
|
||||
{
|
||||
ClearBit(platform.input, ROTATE_X_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_j)
|
||||
{
|
||||
ClearBit(platform.input, ROTATE_Y_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_l)
|
||||
{
|
||||
ClearBit(platform.input, ROTATE_Y_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_u)
|
||||
{
|
||||
ClearBit(platform.input, ROTATE_Z_POS);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_o)
|
||||
{
|
||||
ClearBit(platform.input, ROTATE_Z_NEG);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_UP)
|
||||
{
|
||||
ClearBit(platform.input, SCALE_UP);
|
||||
}
|
||||
else if (event.key.keysym.sym == SDLK_DOWN)
|
||||
{
|
||||
ClearBit(platform.input, SCALE_DOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
enum class PlatformStatus
|
||||
{
|
||||
Error = -1,
|
||||
Ok,
|
||||
Quit
|
||||
};
|
||||
|
||||
|
||||
// STRUCTURES
|
||||
struct Platform
|
||||
{
|
||||
SDL_Window* window;
|
||||
SDL_Surface* surface;
|
||||
uint32_t input;
|
||||
uint32_t framerateMillis;
|
||||
uint32_t frameStartMillis;
|
||||
};
|
||||
|
||||
|
||||
// FUNCTIONS
|
||||
PlatformStatus Platform_Init(Platform& platform, int width, int height);
|
||||
|
||||
PlatformStatus Platform_CheckForEvents(Platform& platform);
|
||||
|
||||
void Platform_ClearWindow(Platform& platform);
|
||||
|
||||
void Platform_UpdateWindow(Platform& platform);
|
||||
|
||||
void Platform_GetFrameTime(Platform& platform);
|
||||
|
||||
void Platform_SyncToFramerate(Platform& platform);
|
||||
|
||||
void Platform_Shutdown(Platform& platform);
|
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include "Matrix.hpp"
|
||||
#include "Vec.hpp"
|
||||
|
||||
|
||||
class Point
|
||||
{
|
||||
public:
|
||||
Point()
|
||||
: x(0), y(0), z(0), w(1)
|
||||
{}
|
||||
|
||||
Point(float x, float y, float z)
|
||||
: x(x), y(y), z(z), w(1)
|
||||
{}
|
||||
|
||||
Point operator-() const
|
||||
{
|
||||
return Point(
|
||||
-x,
|
||||
-y,
|
||||
-z);
|
||||
}
|
||||
|
||||
Vector operator-(Point const& rhs) const
|
||||
{
|
||||
return Vector(
|
||||
x - rhs.x,
|
||||
y - rhs.y,
|
||||
z - rhs.z);
|
||||
}
|
||||
|
||||
Point operator*(Matrix const& rhs)
|
||||
{
|
||||
Point result;
|
||||
|
||||
for (int col = 0; col < 4; ++col)
|
||||
{
|
||||
float sum = 0.0;
|
||||
|
||||
for (int row = 0; row < 4; ++row)
|
||||
{
|
||||
sum += e[row] * rhs.e[row][col];
|
||||
}< |