From 7c62126ede3fdfeab1b8da2b9a3ef451bbcca4ef Mon Sep 17 00:00:00 2001 From: IDunnoDev Date: Sat, 11 Dec 2021 13:18:04 +0000 Subject: [PATCH] Initial Upload Week 4 [19/10] --- BaseFramework.sln | 31 ++++ BaseFramework.vcxproj | 173 +++++++++++++++++++++ BaseFramework.vcxproj.filters | 70 +++++++++ Bitmap.cpp | 111 ++++++++++++++ Bitmap.h | 26 ++++ Framework.cpp | 275 ++++++++++++++++++++++++++++++++++ Framework.h | 37 +++++ Matrix.cpp | 137 +++++++++++++++++ Matrix.h | 38 +++++ Rasteriser.cpp | 97 ++++++++++++ Rasteriser.h | 27 ++++ Rasteriser.ico | Bin 0 -> 46227 bytes Rasteriser.rc | Bin 0 -> 3960 bytes Resource.h | 27 ++++ Vertex.cpp | 84 +++++++++++ Vertex.h | 29 ++++ small.ico | Bin 0 -> 46227 bytes targetver.h | 8 + 18 files changed, 1170 insertions(+) create mode 100644 BaseFramework.sln create mode 100644 BaseFramework.vcxproj create mode 100644 BaseFramework.vcxproj.filters create mode 100644 Bitmap.cpp create mode 100644 Bitmap.h create mode 100644 Framework.cpp create mode 100644 Framework.h create mode 100644 Matrix.cpp create mode 100644 Matrix.h create mode 100644 Rasteriser.cpp create mode 100644 Rasteriser.h create mode 100644 Rasteriser.ico create mode 100644 Rasteriser.rc create mode 100644 Resource.h create mode 100644 Vertex.cpp create mode 100644 Vertex.h create mode 100644 small.ico create mode 100644 targetver.h diff --git a/BaseFramework.sln b/BaseFramework.sln new file mode 100644 index 0000000..755975a --- /dev/null +++ b/BaseFramework.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29326.143 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BaseFramework", "BaseFramework.vcxproj", "{E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Debug|x64.ActiveCfg = Debug|x64 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Debug|x64.Build.0 = Debug|x64 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Debug|x86.ActiveCfg = Debug|Win32 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Debug|x86.Build.0 = Debug|Win32 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Release|x64.ActiveCfg = Release|x64 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Release|x64.Build.0 = Release|x64 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Release|x86.ActiveCfg = Release|Win32 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4CC4CC5A-427D-44AF-A956-84094536848C} + EndGlobalSection +EndGlobal diff --git a/BaseFramework.vcxproj b/BaseFramework.vcxproj new file mode 100644 index 0000000..64ce85e --- /dev/null +++ b/BaseFramework.vcxproj @@ -0,0 +1,173 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {E78A1901-BE1C-4A6F-9C82-3BFC672AFE28} + Win32Proj + BaseFramework + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Level3 + Disabled + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + Disabled + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + true + + + Windows + true + + + + + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BaseFramework.vcxproj.filters b/BaseFramework.vcxproj.filters new file mode 100644 index 0000000..9484823 --- /dev/null +++ b/BaseFramework.vcxproj.filters @@ -0,0 +1,70 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Bitmap.cpp b/Bitmap.cpp new file mode 100644 index 0000000..3d1383f --- /dev/null +++ b/Bitmap.cpp @@ -0,0 +1,111 @@ +#include "Bitmap.h" + +Bitmap::Bitmap() +{ +} + +Bitmap::~Bitmap() +{ + // Ensure we clean up any existing bitmap + DeleteBitmap(); +} + +// Create a new bitmap of the specified width and height, deleting any existing bitmap +// +// Returns value of false if bitmap cannot be created. + +bool Bitmap::Create(HWND hWnd, unsigned int width, unsigned int height) +{ + bool status = false; + HDC hDc; + + // Delete any existing bitmap + DeleteBitmap(); + + // Create a device context compatible with the window device context + hDc = ::GetDC(hWnd); + _width = width; + _height = height; + _hMemDC = CreateCompatibleDC(hDc); + if (_hMemDC != 0) + { + // Create a bitmap compatible with the window + _hBitmap = CreateCompatibleBitmap(hDc, _width, _height); + if (_hBitmap != 0) + { + // Select the bitmap into the new device context, saving any old bitmap handle + _hOldBitmap = static_cast(SelectObject(_hMemDC, _hBitmap)); + status = true; + } + } + // Release the device context for the window + ReleaseDC(hWnd, hDc); + return status; +} + +// Return device context of bitmap + +HDC Bitmap::GetDC() const +{ + return _hMemDC; +} + +// Return width of bitmap + +unsigned int Bitmap::GetWidth() const +{ + return _width; +} + +// Return height of bitmap + +unsigned int Bitmap::GetHeight() const +{ + return _height; +} + +// Delete any existing bitmap + +void Bitmap::DeleteBitmap() +{ + // Select any default bitmap that existed for the device context + if (_hOldBitmap != 0 && _hMemDC != 0) + { + SelectObject(_hMemDC, _hOldBitmap); + _hOldBitmap = 0; + } + // Delete any existing bitmap + if (_hBitmap != 0) + { + DeleteObject(_hBitmap); + _hBitmap = 0; + } + // Delete any existing bitmap device context + if (_hMemDC != 0) + { + DeleteDC(_hMemDC); + _hMemDC = 0; + } +} + +// Clear bitmap using the specified brush + +void Bitmap::Clear(HBRUSH hBrush) const +{ + RECT rect; + + rect.left = 0; + rect.right = _width; + rect.top = 0; + rect.bottom = _height; + FillRect(_hMemDC, &rect, hBrush); +} + +// Clear bitmap using the specified colour + +void Bitmap::Clear(COLORREF colour) const +{ + HBRUSH brush = CreateSolidBrush(colour); + Clear(brush); + DeleteObject(brush); +} diff --git a/Bitmap.h b/Bitmap.h new file mode 100644 index 0000000..d663647 --- /dev/null +++ b/Bitmap.h @@ -0,0 +1,26 @@ +#pragma once +#include "windows.h" + +class Bitmap +{ +public: + Bitmap(); + ~Bitmap(); + + bool Create(HWND hWnd, unsigned int width, unsigned int height); + HDC GetDC() const; + unsigned int GetWidth() const; + unsigned int GetHeight() const; + void Clear(HBRUSH hBrush) const; + void Clear(COLORREF colour) const; + +private: + HBITMAP _hBitmap{ 0 }; + HBITMAP _hOldBitmap{ 0 }; + HDC _hMemDC{ 0 }; + unsigned int _width{ 0 }; + unsigned int _height{ 0 }; + + void DeleteBitmap(); +}; + diff --git a/Framework.cpp b/Framework.cpp new file mode 100644 index 0000000..c5c6d21 --- /dev/null +++ b/Framework.cpp @@ -0,0 +1,275 @@ +#include "Framework.h" + +const unsigned int DEFAULT_FRAMERATE = 30; + +// Reference to ourselves - primarily used to access the message handler correctly +// This is initialised in the constructor + +Framework * _thisFramework = NULL; + +// Flag set once the framework has been successfully initialised + +bool isInitialised = false; + +// Forward declaration of our window procedure +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + + // We can only run if an instance of a class that inherits from Framework + // has been created + if (_thisFramework) + { + return _thisFramework->Run(hInstance, nCmdShow); + } + return -1; +} + +Framework::Framework() : Framework(800, 600) +{ +} + +Framework::Framework(unsigned int width, unsigned int height) + : _hInstance(0), _hWnd(0), _width(width), _height(height) +{ + _thisFramework = this; +} + +Framework::~Framework() +{ +} + +int Framework::Run(HINSTANCE hInstance, int nCmdShow) +{ + int returnValue; + + _hInstance = hInstance; + if (!InitialiseMainWindow(nCmdShow)) + { + return -1; + } + if (!Initialise()) + { + return -1; + } + isInitialised = true; + returnValue = MainLoop(); + Shutdown(); + return returnValue; +} + +// Main program loop. + +int Framework::MainLoop() +{ + MSG msg; + HACCEL hAccelTable = LoadAccelerators(_hInstance, MAKEINTRESOURCE(IDC_RASTERISER)); + LARGE_INTEGER counterFrequency; + LARGE_INTEGER nextTime; + LARGE_INTEGER currentTime; + LARGE_INTEGER lastTime; + bool updateFlag = true; + + // Initialise timer + QueryPerformanceFrequency(&counterFrequency); + DWORD msPerFrame = static_cast(counterFrequency.QuadPart / DEFAULT_FRAMERATE); + double timeFactor = 1.0 / counterFrequency.QuadPart; + QueryPerformanceCounter(&nextTime); + lastTime = nextTime; + + // Main message loop: + msg.message = WM_NULL; + while (msg.message != WM_QUIT) + { + if (updateFlag) + { + QueryPerformanceCounter(¤tTime); + _timeSpan = (currentTime.QuadPart - lastTime.QuadPart) * timeFactor; + lastTime = currentTime; + Update(_bitmap); + updateFlag = false; + } + QueryPerformanceCounter(¤tTime); + // Is it time to render the frame? + if (currentTime.QuadPart > nextTime.QuadPart) + { + Render(_bitmap); + // Make sure that the window gets repainted + InvalidateRect(_hWnd, NULL, FALSE); + // Set time for next frame + nextTime.QuadPart += msPerFrame; + // If we get more than a frame ahead, allow one to be dropped + // Otherwise, we will never catch up if we let the error accumulate + // and message handling will suffer + if (nextTime.QuadPart < currentTime.QuadPart) + { + nextTime.QuadPart = currentTime.QuadPart + msPerFrame; + } + updateFlag = true; + } + // Each time we go through this loop, we look to see if there is a Windows message + // that needs to be processed + if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) + { + if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + return static_cast(msg.wParam); +} + +// Initialise the application. Called after the window and bitmap has been +// created, but before the main loop starts +// +// Return false if the application cannot be initialised. + +bool Framework::Initialise() +{ + return true; +} + +// Perform any updates to the structures that will be used +// to render the window (i.e. transformation matrices, etc). +// +// This should be overridden + +void Framework::Update(const Bitmap &bitmap) +{ + // Default update method does nothing +} + +// Render the window. This should be overridden + +void Framework::Render(const Bitmap &bitmap) +{ + // Default render method just sets the background to the default window colour + bitmap.Clear(reinterpret_cast(COLOR_WINDOW + 1)); +} + +// Perform any application shutdown that is needed + +void Framework::Shutdown() +{ +} + +// Register the window class, create the window and +// create the bitmap that we will use for rendering + +bool Framework::InitialiseMainWindow(int nCmdShow) +{ + #define MAX_LOADSTRING 100 + + WCHAR windowTitle[MAX_LOADSTRING]; + WCHAR windowClass[MAX_LOADSTRING]; + + LoadStringW(_hInstance, IDS_APP_TITLE, windowTitle, MAX_LOADSTRING); + LoadStringW(_hInstance, IDC_RASTERISER, windowClass, MAX_LOADSTRING); + + WNDCLASSEXW wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = _hInstance; + wcex.hIcon = LoadIcon(_hInstance, MAKEINTRESOURCE(IDI_RASTERISER)); + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.hbrBackground = reinterpret_cast(COLOR_WINDOW + 1); + wcex.lpszMenuName = nullptr; + wcex.lpszClassName = windowClass; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); + if (!RegisterClassExW(&wcex)) + { + return false; + } + + // Now work out how large the window needs to be for our required client window size + RECT windowRect = { 0, 0, static_cast(_width), static_cast(_height) }; + AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, FALSE); + _width = windowRect.right - windowRect.left; + _height = windowRect.bottom - windowRect.top; + + _hWnd = CreateWindowW(windowClass, + windowTitle, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, _width, _height, + nullptr, nullptr, _hInstance, nullptr); + if (!_hWnd) + { + return false; + } + ShowWindow(_hWnd, nCmdShow); + UpdateWindow(_hWnd); + + // Create a bitmap of the same size as the client area of the window. This is what we + // will be drawing on + RECT clientArea; + GetClientRect(_hWnd, &clientArea); + _bitmap.Create(_hWnd, clientArea.right - clientArea.left, clientArea.bottom - clientArea.top); + return true; +} + +// The WndProc for the current window. This cannot be a method, but we can +// redirect all messages to a method. + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (_thisFramework != NULL) + { + // If framework is started, then we can call our own message proc + return _thisFramework->MsgProc(hWnd, message, wParam, lParam); + } + else + { + // otherwise, we just pass control to the default message proc + return DefWindowProc(hWnd, message, wParam, lParam); + } +} + +// Our main WndProc + +LRESULT Framework::MsgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_PAINT: + { + // Copy the contents of the bitmap to the window + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + BitBlt(hdc, 0, 0, _bitmap.GetWidth(), _bitmap.GetHeight(), _bitmap.GetDC(), 0, 0, SRCCOPY); + EndPaint(hWnd, &ps); + } + break; + + case WM_SIZE: + // Delete any existing bitmap and create a new one of the required size. + _bitmap.Create(hWnd, LOWORD(lParam), HIWORD(lParam)); + // Now render to the resized bitmap (but only if we are fully initialised) + if (isInitialised) + { + Update(_bitmap); + Render(_bitmap); + InvalidateRect(hWnd, NULL, FALSE); + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + diff --git a/Framework.h b/Framework.h new file mode 100644 index 0000000..87c975b --- /dev/null +++ b/Framework.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include "Resource.h" +#include "Bitmap.h" + +using namespace std; + +class Framework +{ +public: + Framework(); + Framework(unsigned int width, unsigned int height); + virtual ~Framework(); + + int Run(HINSTANCE hInstance, int nCmdShow); + + LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + virtual bool Initialise(); + virtual void Update(const Bitmap &bitmap); + virtual void Render(const Bitmap &bitmap); + virtual void Shutdown(); + +private: + HINSTANCE _hInstance; + HWND _hWnd; + Bitmap _bitmap; + unsigned int _width; + unsigned int _height; + + // Used in timing loop + double _timeSpan{ 0 }; + + bool InitialiseMainWindow(int nCmdShow); + int MainLoop(); +}; + diff --git a/Matrix.cpp b/Matrix.cpp new file mode 100644 index 0000000..e5a23ed --- /dev/null +++ b/Matrix.cpp @@ -0,0 +1,137 @@ +#include "Matrix.h" + +Matrix::Matrix() : _matrix{ 0 } +{ +} + +Matrix::Matrix(std::initializer_list inputList) : _matrix{ 0 } +{ + if (inputList.size() != ROWS * COLS) + { + throw "You did not provide enough points to create the matrix"; + } + + auto listContainer = inputList.begin(); + for (int i = 0; i < ROWS; i++) + { + for (int j = 0; j < COLS; j++) + { + _matrix[i][j] = *listContainer++; + } + } +} + +Matrix::Matrix(const float arrayIn[ROWS][COLS]) +{ + for (int i = 0; i < ROWS; i++) + { + for (int j = 0; j < COLS; j++) + { + _matrix[i][j] = arrayIn[i][j]; + } + } +} + +Matrix::Matrix(const Matrix& other) +{ + Copy(other); +} + +Matrix::~Matrix() +{ + +} + +float Matrix::GetM(const int row, const int column) const +{ + return GetMatrixCell(row, column); +} + +float Matrix::GetMatrixCell(const int row, const int column) const +{ + return _matrix[row][column]; +} + +void Matrix::SetM(const int row, const int column, const float value) +{ + SetMatrixCell(row, column, value); +} + +void Matrix::SetMatrixCell(const int row, const int column, const float value) +{ + _matrix[row][column] = value; +} + +void Matrix::FromArray(const float arrayIn[ROWS][COLS]) +{ + for (int i = 0; i < ROWS; i++) + { + for (int j = 0; j < COLS; j++) + { + _matrix[i][j] = arrayIn[i][j]; + } + } +} + +Matrix& Matrix::operator= (const Matrix& rhs) +{ + if (this != &rhs) + { + Copy(rhs); + } + return *this; +} + +bool Matrix::operator== (const Matrix& other) const +{ + for (int i = 0; i < ROWS; i++) + { + for (int j = 0; j < COLS; j++) + { + if (_matrix[i][j] != other.GetMatrixCell(i, j)) + { + return false; + } + } + } + return true; +} + +const Matrix Matrix::operator*(const Matrix& other) const +{ + Matrix result; + for (int i = 0; i < ROWS; i++) + { + for (int j = 0; j < COLS; j++) + { + float resultValue = 0; + for (int k = 0; k < ROWS; k++) + { + resultValue += _matrix[i][k] * other.GetMatrixCell(k, j); + } + result.SetMatrixCell(i, j, resultValue); + } + } + return result; +} + +const Vertex Matrix::operator*(const Vertex& other) const +{ + Vertex newVertex(other); + + newVertex.SetX(_matrix[0][0] * other.GetX() + _matrix[0][1] * other.GetY() + _matrix[0][2] * other.GetW()); + newVertex.SetY(_matrix[1][0] * other.GetX() + _matrix[1][1] * other.GetY() + _matrix[1][2] * other.GetW()); + newVertex.SetW(_matrix[2][0] * other.GetX() + _matrix[2][1] * other.GetY() + _matrix[2][2] * other.GetW()); + return newVertex; +} + +void Matrix::Copy(const Matrix& other) +{ + for (int i = 0; i < ROWS; i++) + { + for (int j = 0; j < COLS; j++) + { + _matrix[i][j] = other.GetMatrixCell(i, j); + } + } +} \ No newline at end of file diff --git a/Matrix.h b/Matrix.h new file mode 100644 index 0000000..334d3e7 --- /dev/null +++ b/Matrix.h @@ -0,0 +1,38 @@ +#pragma once + +#include "Vertex.h" +#include + +// Size of the matrix +const int COLS = 3; +const int ROWS = 3; + +class Matrix +{ +public: + Matrix(); + Matrix(std::initializer_list inputList); + // Take in an array of floats matching the cols and rows + Matrix(const float arrayIn[ROWS][COLS]); + Matrix(const Matrix& other); + + ~Matrix(); + + float GetM(const int row, const int column) const; + float GetMatrixCell(const int row, const int column) const; + + void SetM(const int row, const int column, const float value); + void SetMatrixCell(const int row, const int column, const float value); + void FromArray(const float arrayIn[ROWS][COLS]); + + Matrix& operator= (const Matrix& rhs); + + bool operator==(const Matrix& other) const; + const Matrix operator*(const Matrix& other) const; + const Vertex operator*(const Vertex& other) const; + +private: + float _matrix[ROWS][COLS]; + void Copy(const Matrix& other); +}; + diff --git a/Rasteriser.cpp b/Rasteriser.cpp new file mode 100644 index 0000000..a63fb12 --- /dev/null +++ b/Rasteriser.cpp @@ -0,0 +1,97 @@ +#include "Rasteriser.h" +#include + +using namespace std; + +Rasteriser app; + +bool Rasteriser::Initialise() +{ + //_vertexArray.push_back(Vertex(175, 175, 1)); + //_vertexArray.push_back(Vertex(225, 175, 1)); + //_vertexArray.push_back(Vertex(225, 225, 1)); + //_vertexArray.push_back(Vertex(175, 225, 1)); + + _vertexArray.push_back(Vertex(150, 200, 1)); + _vertexArray.push_back(Vertex(187.5f, 187.5f, 1)); + _vertexArray.push_back(Vertex(200, 150, 1)); + _vertexArray.push_back(Vertex(212.5f, 187.5f, 1)); + _vertexArray.push_back(Vertex(250, 200, 1)); + _vertexArray.push_back(Vertex(212.5f, 212.5f, 1)); + _vertexArray.push_back(Vertex(200, 250, 1)); + _vertexArray.push_back(Vertex(187.5, 212.5f, 1)); + + return true; +} + +void Rasteriser::Update(const Bitmap& bitmap) +{ + for (int i = 0; i < _vertexArray.size(); i++) { + _vertexArray[i] = Translate(_vertexArray[i], 2, 0); + _vertexArray[i] = Rotate(_vertexArray[i], 1); + _vertexArray[i] = Scale(_vertexArray[i], 1.001f, 1.001f); + } + +} + +void Rasteriser::Render(const Bitmap& bitmap) +{ + bitmap.Clear(RGB(255, 255, 255)); + SelectObject(bitmap.GetDC(), GetStockObject(DC_BRUSH)); + //DrawSquare(bitmap.GetDC(), _vertexArray); + DrawShape(bitmap.GetDC(), _vertexArray); +} + +Vertex Rasteriser::Translate(const Vertex vertexIn, const float moveXBy, const float moveYBy) +{ + Matrix translationMatrix = Matrix({ 1, 0, moveXBy, 0, 1, moveYBy, 0, 0, 1 }); + return translationMatrix * vertexIn; +} + +Vertex Rasteriser::Scale(const Vertex vertexIn, const float scaleXBy, const float scaleYBy) +{ + Matrix scaleFactorMatrix = Matrix({ scaleXBy, 0, 0, 0, scaleYBy, 0, 0, 0, 1 }); + return scaleFactorMatrix * vertexIn; +} + +Vertex Rasteriser::Rotate(const Vertex vertexIn, const float rotationDegrees) +{ + float rotationRadian = DegreesToRadians(rotationDegrees); + Matrix rotationFactorMatrix = Matrix({ cos(rotationRadian), -sin(rotationRadian), 0, sin(rotationRadian), cos(rotationRadian), 0, 0, 0, 1}); + return rotationFactorMatrix * vertexIn; +} + +float Rasteriser::DegreesToRadians(const float degrees) +{ + return (degrees * _PI) / 180; +} + +void Rasteriser::DrawSquare(HDC hDc, const vector verticies) +{ + POINT pointArray[4]; + for (int i = 0; i < 4; i++) + { + pointArray[i].x = (long) verticies[i].GetX(); + pointArray[i].y = (long) verticies[i].GetY(); + } + + SetDCBrushColor(hDc, RGB(255, 0, 255)); + SetDCPenColor(hDc, RGB(0, 0, 255)); + Polygon(hDc, pointArray, 4); +} + +void Rasteriser::DrawShape(HDC hDc, const vector verticies) +{ + vector pointArray; + for (int i = 0; i < verticies.size(); i++) + { + POINT newPoint; + newPoint.x = (long)verticies[i].GetX(); + newPoint.y = (long)verticies[i].GetY(); + pointArray.push_back(newPoint); + } + + SetDCBrushColor(hDc, RGB(rand() % 256, rand() % 256, rand() % 256)); + SetDCPenColor(hDc, RGB(rand() % 256, rand() % 256, rand() % 256)); + Polygon(hDc, pointArray.data(), (int) verticies.size()); +} \ No newline at end of file diff --git a/Rasteriser.h b/Rasteriser.h new file mode 100644 index 0000000..9ec46d6 --- /dev/null +++ b/Rasteriser.h @@ -0,0 +1,27 @@ +#pragma once +#include "Framework.h" +#include "Vertex.h" +#include "Matrix.h" +#include + +class Rasteriser : public Framework +{ +public: + bool Initialise(); + void Update(const Bitmap& bitmap); + void Render(const Bitmap& bitmap); + + void DrawSquare(HDC hDc, const vector verticies); + void DrawShape(HDC hDc, const vector verticies); + + Vertex Translate(const Vertex vertexIn, const float moveXBy, const float moveYBy); + Vertex Scale(const Vertex vertexIn, const float scaleXBy, const float scaleYBy); + Vertex Rotate(const Vertex vertexIn, const float rotationDegrees); + + float DegreesToRadians(const float degrees); + +private: + vector _vertexArray; + const float _PI = (float) acos(-1); +}; + diff --git a/Rasteriser.ico b/Rasteriser.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3ec03bd617f32e58128fa977fd6ac9605124f4b GIT binary patch literal 46227 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