From df3c9fac815ed949737f8890ca50e8ae03bc3287 Mon Sep 17 00:00:00 2001 From: IDunnoDev Date: Sat, 11 Dec 2021 19:26:56 +0000 Subject: [PATCH] Week9 [23/11] - [29/11] Added Lighting Changes for Vertex Normal Calculation Added Method to Get a Polygons Vertices as a Vector array Added Methods to Add the Color to a Polygon and Vertex Object Added Normalization to the Polygon normal vector Added Vector Normal Calculation to the Model Class Added Light Calculation method to the Rasterizer Class Added Flag to Rasterizer to stop processing when the screen is minimised Added Flat and Gouraud Drawing Methods to the Resterizer Added Standard and INT triangle drawing Methods Added Function to mimic sgn function from Java Added Length to Vector3D class Added / operator to Vector3D class Added Get(x,y,z,w)Int methods to Vertex class Changed Color Variables on the Vertex/Polygon classes to use r, g and b values rather than COLORREF Changed Camera translation functions to use normal Translation Functions Cleaned up Code Removed Unused code from the Camera Class --- AmbientLight.cpp | 12 +- AmbientLight.h | 3 + Camera.cpp | 90 +++---- Camera.h | 26 +- DirectionalLight.cpp | 22 +- DirectionalLight.h | 3 + Light.h | 1 + Model.cpp | 60 ++++- Model.h | 7 +- PointLight.cpp | 20 +- PointLight.h | 2 + Polygon3D.cpp | 19 +- Polygon3D.h | 7 +- Rasteriser.cpp | 627 +++++++++++++++++++++++++++++++++++++------ Rasteriser.h | 16 ++ SharedTools.cpp | 6 +- SharedTools.h | 4 + Vector3D.cpp | 33 ++- Vector3D.h | 5 +- Vertex.cpp | 148 +++++++++- Vertex.h | 33 +++ cow.md2 | Bin 0 -> 147744 bytes teapot.md2 | Bin 0 -> 122140 bytes 23 files changed, 956 insertions(+), 188 deletions(-) create mode 100644 cow.md2 create mode 100644 teapot.md2 diff --git a/AmbientLight.cpp b/AmbientLight.cpp index c1dda3d..18474ba 100644 --- a/AmbientLight.cpp +++ b/AmbientLight.cpp @@ -5,7 +5,7 @@ AmbientLight::AmbientLight(const AmbientLight& other) : Light(other) Copy(other); } -COLORREF AmbientLight::CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn) +COLORREF AmbientLight::CalculateLightShared(const Model& currentModel, COLORREF colorIn) { float workingRed = (float)GetRedLightIntensity(); float workingGreen = (float)GetGreenLightIntensity(); @@ -27,6 +27,16 @@ COLORREF AmbientLight::CalculateLight(const Model& currentModel, const Polygon3D return outputColor; } +COLORREF AmbientLight::CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn) +{ + return CalculateLightShared(currentModel, colorIn); +} + +COLORREF AmbientLight::CalculateLight(const Model& currentModel, const Vertex& currentVertex, COLORREF colorIn) +{ + return CalculateLightShared(currentModel, colorIn); +} + AmbientLight& AmbientLight::operator= (const AmbientLight& rhs) { if (this != &rhs) diff --git a/AmbientLight.h b/AmbientLight.h index 91b45df..3dd8a41 100644 --- a/AmbientLight.h +++ b/AmbientLight.h @@ -9,7 +9,10 @@ public: AmbientLight(int red, int green, int blue) : Light(red, green, blue) {}; AmbientLight(const AmbientLight& other); + COLORREF CalculateLightShared(const Model& currentModel, COLORREF colorIn); + COLORREF CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn); + COLORREF CalculateLight(const Model& currentModel, const Vertex& currentVertex, COLORREF colorIn); AmbientLight& operator= (const AmbientLight& rhs); diff --git a/Camera.cpp b/Camera.cpp index dd5ccdf..20520de 100644 --- a/Camera.cpp +++ b/Camera.cpp @@ -1,19 +1,11 @@ #include "Camera.h" -Camera::Camera() -{ -} - Camera::Camera(float xRot, float yRot, float zRot, const Vertex& pos) { - _initialRotation[0] = xRot; - _currentRotation[0] = xRot; - _initialRotation[1] = yRot; - _currentRotation[1] = yRot; - _initialRotation[2] = zRot; - _currentRotation[2] = zRot; - _initialPosition = pos; - _currentPosition = pos; + _xRot = xRot; + _yRot = yRot; + _zRot = zRot; + _cameraPosition = pos; } Camera::Camera(const Camera& other) @@ -27,61 +19,56 @@ Camera::~Camera() Matrix Camera::UpdateCameraPosition(const Vertex& newPos) { - _currentPosition = newPos; + _cameraPosition = newPos; return GetCurrentCameraTransformMatrix(); } Matrix Camera::RotateCamera(float xRot, float yRot, float zRot) { - _currentRotation[0] = xRot; - _currentRotation[1] = yRot; - _currentRotation[2] = zRot; + _xRot = xRot; + _yRot = yRot; + _zRot = zRot; return GetCurrentCameraTransformMatrix(); } Matrix Camera::GetCurrentCameraTransformMatrix() { - return GetCameraRotationMatrix(Axis::X, _currentRotation[0]) * GetCameraRotationMatrix(Axis::Y, _currentRotation[1]) * GetCameraRotationMatrix(Axis::Z, _currentRotation[2]) * GetCameraTranslateMatrix(_currentPosition); + return GetCameraRotationMatrix(Axis::X, _xRot) * GetCameraRotationMatrix(Axis::Y, _yRot) * GetCameraRotationMatrix(Axis::Z, _zRot) * GetCameraTranslateMatrix(_cameraPosition); } -float Camera::GetCurrentRotationValue(const Axis axis) +void Camera::SetXRot(const float value) { - switch (axis) - { - case (Axis::X): - return _currentRotation[0]; - case (Axis::Y): - return _currentRotation[1]; - case (Axis::Z): - return _currentRotation[2]; - default: - throw "No valid axis?"; - } + _xRot = value; } -float Camera::GetInitialRotationValue(const Axis axis) +float Camera::GetXRot() const { - switch (axis) - { - case (Axis::X): - return _initialRotation[0]; - case (Axis::Y): - return _initialRotation[1]; - case (Axis::Z): - return _initialRotation[2]; - default: - throw "No valid axis?"; - } + return _xRot; } -Vertex Camera::GetCurrentPosition() +void Camera::SetYRot(const float value) { - return _currentPosition; + _yRot = value; } -Vertex Camera::GetInitialPosition() +float Camera::GetYRot() const { - return _initialPosition; + return _yRot; +} + +void Camera::SetZRot(const float value) +{ + _zRot = value; +} + +float Camera::GetZRot() const +{ + return _zRot; +} + +Vertex Camera::GetCameraPosition() const +{ + return _cameraPosition; } Camera& Camera::operator=(const Camera& rhs) @@ -95,12 +82,9 @@ Camera& Camera::operator=(const Camera& rhs) void Camera::Copy(const Camera& other) { - _initialRotation[0] = other._initialRotation[0]; - _currentRotation[0] = other._currentRotation[0]; - _initialRotation[1] = other._initialRotation[1]; - _currentRotation[1] = other._currentRotation[1]; - _initialRotation[2] = other._initialRotation[2]; - _currentRotation[2] = other._currentRotation[2]; - _initialPosition = other._initialPosition; - _currentPosition = other._currentPosition; + _xRot = other.GetXRot(); + _yRot = other.GetYRot(); + _zRot = other.GetZRot(); + + _cameraPosition = other.GetCameraPosition(); } \ No newline at end of file diff --git a/Camera.h b/Camera.h index a19322d..ad4c8cb 100644 --- a/Camera.h +++ b/Camera.h @@ -8,7 +8,7 @@ using namespace SharedTools; class Camera { public: - Camera(); + Camera() : _xRot(0), _yRot(0), _zRot(0) {}; Camera(float xRot, float yRot, float zRot, const Vertex& pos); Camera(const Camera& other); @@ -17,23 +17,23 @@ public: Matrix UpdateCameraPosition(const Vertex& newPos); Matrix RotateCamera(float xRot, float yRot, float zRot); - Matrix GetCurrentCameraTransformMatrix(); + void SetXRot(const float value); + float GetXRot() const; + void SetYRot(const float value); + float GetYRot() const; + void SetZRot(const float value); + float GetZRot() const; - float GetCurrentRotationValue(const Axis axis); - float GetInitialRotationValue(const Axis axis); - Vertex GetCurrentPosition(); - Vertex GetInitialPosition(); + Matrix GetCurrentCameraTransformMatrix(); + Vertex GetCameraPosition() const; Camera& operator= (const Camera& rhs); private: - float _initialRotation[3] = { 0 }; - Vertex _initialPosition; - - float _currentRotation[3] = { 0 }; - Vertex _currentPosition; - - Matrix _currentCameraTransform; + float _xRot; + float _yRot; + float _zRot; + Vertex _cameraPosition; void Copy(const Camera& other); }; diff --git a/DirectionalLight.cpp b/DirectionalLight.cpp index a7ad8d0..816eaef 100644 --- a/DirectionalLight.cpp +++ b/DirectionalLight.cpp @@ -34,17 +34,17 @@ Vector3D DirectionalLight::GetLightDirection() const return _lightDirection; } -COLORREF DirectionalLight::CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn) +COLORREF DirectionalLight::CalculateLightShared(const Model& currentModel, const Vector3D& currentNormal, COLORREF colorIn) { - float workingRed = (float) GetRedLightIntensity(); - float workingGreen = (float) GetGreenLightIntensity(); - float workingBlue = (float) GetBlueLightIntensity(); + float workingRed = (float)GetRedLightIntensity(); + float workingGreen = (float)GetGreenLightIntensity(); + float workingBlue = (float)GetBlueLightIntensity(); workingRed *= currentModel.GetRedReflectionCoefficient(); workingGreen *= currentModel.GetGreenReflectionCoefficient(); workingBlue *= currentModel.GetBlueReflectionCoefficient(); - - float lightDotProd = Vector3D::DotProduct(_normalizedLightDirection, currentPolygon.GetNormal()); + + float lightDotProd = Vector3D::DotProduct(_normalizedLightDirection, currentNormal); workingRed *= lightDotProd; workingGreen *= lightDotProd; @@ -62,6 +62,16 @@ COLORREF DirectionalLight::CalculateLight(const Model& currentModel, const Polyg return outputColor; } +COLORREF DirectionalLight::CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn) +{ + return CalculateLightShared(currentModel, currentPolygon.GetNormal(), colorIn); +} + +COLORREF DirectionalLight::CalculateLight(const Model& currentModel, const Vertex& currentVertex, COLORREF colorIn) +{ + return CalculateLightShared(currentModel, currentVertex.GetNormal(), colorIn); +} + DirectionalLight& DirectionalLight::operator= (const DirectionalLight& rhs) { if (this != &rhs) diff --git a/DirectionalLight.h b/DirectionalLight.h index dec5def..6c000f2 100644 --- a/DirectionalLight.h +++ b/DirectionalLight.h @@ -13,7 +13,10 @@ public: void SetLightDirection(Vector3D direction); Vector3D GetLightDirection() const; + COLORREF CalculateLightShared(const Model& currentModel, const Vector3D& currentNormal, COLORREF colorIn); + COLORREF CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn); + COLORREF CalculateLight(const Model& currentModel, const Vertex& currentVertex, COLORREF colorIn); DirectionalLight& operator= (const DirectionalLight& rhs); diff --git a/Light.h b/Light.h index 05e1b51..07b39e8 100644 --- a/Light.h +++ b/Light.h @@ -27,6 +27,7 @@ public: int GetBlueLightIntensity() const; virtual COLORREF CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn) = 0; + virtual COLORREF CalculateLight(const Model& currentModel, const Vertex& currentVertex, COLORREF colorIn) = 0; Light& operator= (const Light& rhs); diff --git a/Model.cpp b/Model.cpp index 65d1e23..fdb026c 100644 --- a/Model.cpp +++ b/Model.cpp @@ -16,7 +16,7 @@ vector& Model::GetPolygons() return _polygons; } -const vector& Model::GetVertices() +vector& Model::GetVertices() { return _vertices; } @@ -77,6 +77,27 @@ const Vertex& Model::GetVertex(int index) const return _transformedVertices[index]; } +vector Model::GetPolygonVertexArray(int index) const +{ + vector polygonVerticies = {}; + + for (int i = 0; i < GetPolygon(index).GetPolygonVertexCount(); i++) + { + polygonVerticies.push_back(GetVertex(index, i)); + } + return polygonVerticies; +} + +void Model::SetPolygonColor(int index, COLORREF color) +{ + _polygons[index].SetColor(color); +} + +void Model::SetVertexColor(int polyIndex, int vertIndex, COLORREF color) +{ + _transformedVertices[GetPolygon(polyIndex).GetIndex(vertIndex)].SetColor(color); +} + void Model::SetReflectionCoefficient(float red, float green, float blue) { _kdRed = BoundsCheck(0.0f, 1.0f, red); @@ -171,7 +192,7 @@ void Model::ApplyTransformToTransformedVertices(const Matrix& transform) void Model::DehomogenizeAllVertices() { - for (Vertex ¤tVertex : _transformedVertices) + for (Vertex& currentVertex : _transformedVertices) { currentVertex.Dehomogenize(); } @@ -179,12 +200,14 @@ void Model::DehomogenizeAllVertices() void Model::CalculateBackfaces(Camera& currentCamera) { - for (Polygon3D ¤tPolygon : _polygons) + for (Polygon3D& currentPolygon : _polygons) { Vector3D vectorA = _transformedVertices[currentPolygon.GetIndex(1)] - _transformedVertices[currentPolygon.GetIndex(0)]; Vector3D vectorB = _transformedVertices[currentPolygon.GetIndex(2)] - _transformedVertices[currentPolygon.GetIndex(0)]; - currentPolygon.SetNormal(Vector3D::CrossProduct(vectorA, vectorB)); - Vector3D eyeVector = _transformedVertices[currentPolygon.GetIndex(0)] - currentCamera.GetCurrentPosition(); + currentPolygon.SetNormal(Vector3D::CrossProduct(vectorA, vectorB)); + currentPolygon.NormalizeNormal(); + Vector3D eyeVector = _transformedVertices[currentPolygon.GetIndex(0)] - currentCamera.GetCameraPosition(); + eyeVector.Normalize(); float dotProduct = Vector3D::DotProduct(currentPolygon.GetNormal(), eyeVector); if (dotProduct < 0) @@ -198,6 +221,31 @@ void Model::CalculateBackfaces(Camera& currentCamera) } } +void Model::CalculateVertexNormals() +{ + for (Vertex& currentVertex : _transformedVertices) + { + currentVertex.ResetNormal(true); + } + + for (int pi = 0; pi < GetPolygonCount(); pi++) + { + for (int i = 0; i < _polygons[pi].GetPolygonVertexCount(); i++) + { + _transformedVertices[_polygons[pi].GetIndex(i)].SetNormal(_transformedVertices[_polygons[pi].GetIndex(i)].GetNormal() + _polygons[pi].GetNormal()); + _transformedVertices[_polygons[pi].GetIndex(i)].IncrementContributeCount(); + } + } + + for (Vertex& currentVertex : _transformedVertices) + { + + currentVertex.SetNormal(currentVertex.GetNormal() / currentVertex.GetContributeCount()); + currentVertex.NormalizeNormal(); + + } +} + bool DepthCompare(Polygon3D& a, Polygon3D& b) { return a.GetDepth() > b.GetDepth(); @@ -205,7 +253,7 @@ bool DepthCompare(Polygon3D& a, Polygon3D& b) void Model::Sort() { - for (Polygon3D ¤tPolygon : _polygons) + for (Polygon3D& currentPolygon : _polygons) { float zTotal = 0.0f; for (int i = 0; i < currentPolygon.GetPolygonVertexCount(); i++) diff --git a/Model.h b/Model.h index a0ccca7..1742d03 100644 --- a/Model.h +++ b/Model.h @@ -16,7 +16,7 @@ public: ~Model(); vector& GetPolygons(void); - const vector& GetVertices(void); + vector& GetVertices(void); const vector& GetPendingTransforms(void); size_t GetPolygonCount(void); @@ -32,6 +32,10 @@ public: const Vertex& GetVertex(int polygonIndex, int vertexPolygonIndex) const; const Vertex& GetVertex(int index) const; + vector GetPolygonVertexArray(int index) const; + void SetPolygonColor(int index, COLORREF color); + void SetVertexColor(int polyIndex, int vertIndex, COLORREF color); + void SetReflectionCoefficient(float red, float green, float blue); void SetReflectionCoefficient(ColRef color, float value); void SetRedReflectionCoefficient(float value); @@ -48,6 +52,7 @@ public: void DehomogenizeAllVertices(void); void CalculateBackfaces(Camera& currentCamera); + void CalculateVertexNormals(); void Sort(void); private: diff --git a/PointLight.cpp b/PointLight.cpp index e479289..4e3ca73 100644 --- a/PointLight.cpp +++ b/PointLight.cpp @@ -75,25 +75,25 @@ Vertex PointLight::GetLightPosition() const return _lightPosition; } -COLORREF PointLight::CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn) +COLORREF PointLight::CalculateLightShared(const Model& currentModel, const Vertex& currentVertex, const Vector3D& currentNormal, COLORREF colorIn) { float workingRed = (float)GetRedLightIntensity(); float workingGreen = (float)GetGreenLightIntensity(); float workingBlue = (float)GetBlueLightIntensity(); - float fudge = 50; + float fudge = 100; workingRed *= currentModel.GetRedReflectionCoefficient(); workingGreen *= currentModel.GetGreenReflectionCoefficient(); workingBlue *= currentModel.GetBlueReflectionCoefficient(); - Vector3D lightSource = currentModel.GetVertex(currentPolygon.GetIndex(0)) - _lightPosition; + Vector3D lightSource = currentVertex - _lightPosition; Vector3D lightSourceNormalized = lightSource; lightSourceNormalized.Normalize(); - float distance = Vector3D::DotProduct(lightSource, currentPolygon.GetNormal()); + float distance = Vector3D::Length(currentNormal, lightSource); - float lightDotProd = Vector3D::DotProduct(lightSourceNormalized, currentPolygon.GetNormal()); + float lightDotProd = Vector3D::DotProduct(lightSourceNormalized, currentNormal); float attenuation = 1 / (GetAttenuationA() + GetAttenuationB() * distance + GetAttenuationC() * (distance * distance)); workingRed *= lightDotProd; @@ -120,6 +120,16 @@ COLORREF PointLight::CalculateLight(const Model& currentModel, const Polygon3D& return outputColor; } +COLORREF PointLight::CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn) +{ + return CalculateLightShared(currentModel, currentModel.GetVertex(currentPolygon.GetIndex(0)), currentPolygon.GetNormal(), colorIn); +} + +COLORREF PointLight::CalculateLight(const Model& currentModel, const Vertex& currentVertex, COLORREF colorIn) +{ + return CalculateLightShared(currentModel, currentVertex, currentVertex.GetNormal(), colorIn); +} + PointLight& PointLight::operator= (const PointLight& rhs) { if (this != &rhs) diff --git a/PointLight.h b/PointLight.h index 69d1d76..af239d2 100644 --- a/PointLight.h +++ b/PointLight.h @@ -21,8 +21,10 @@ public: float GetAttenuationB(); float GetAttenuationC(); + COLORREF CalculateLightShared(const Model& currentModel, const Vertex& currentVertex, const Vector3D& currentNormal, COLORREF colorIn); COLORREF CalculateLight(const Model& currentModel, const Polygon3D& currentPolygon, COLORREF colorIn); + COLORREF CalculateLight(const Model& currentModel, const Vertex& currentVertex, COLORREF colorIn); PointLight& operator= (const PointLight& rhs); diff --git a/Polygon3D.cpp b/Polygon3D.cpp index 3995aeb..1b37b9f 100644 --- a/Polygon3D.cpp +++ b/Polygon3D.cpp @@ -4,6 +4,7 @@ Polygon3D::Polygon3D() : _indices{ 0 } { _depth = 0.0f; _color = RGB(0, 0, 0); + _culled = false; } Polygon3D::Polygon3D(int index0, int index1, int index2) @@ -13,6 +14,7 @@ Polygon3D::Polygon3D(int index0, int index1, int index2) _indices[2] = index2; _depth = 0.0f; _color = RGB(0, 0, 0); + _culled = false; } Polygon3D::Polygon3D(const Polygon3D& other) @@ -39,11 +41,16 @@ void Polygon3D::SetNormal(const Vector3D& normal) _normal = normal; } -Vector3D Polygon3D::GetNormal() const +const Vector3D& Polygon3D::GetNormal() const { return _normal; } +void Polygon3D::NormalizeNormal() +{ + _normal.Normalize(); +} + void Polygon3D::SetColor(int red, int green, int blue) { int redChecked = BoundsCheck(0, 255, red); @@ -96,10 +103,10 @@ void Polygon3D::Copy(const Polygon3D& other) { for (int i = 0; i < sizeof(_indices)/sizeof(_indices[0]); i++) { - _indices[i] = other.GetIndex(i); - _normal = other.GetNormal(); - _culled = other.GetCulled(); - _depth = other.GetDepth(); - _color = other.GetColor(); + _indices[i] = other.GetIndex(i); } + _normal = other.GetNormal(); + _culled = other.GetCulled(); + _depth = other.GetDepth(); + _color = other.GetColor(); } \ No newline at end of file diff --git a/Polygon3D.h b/Polygon3D.h index 8de3a5a..5307a87 100644 --- a/Polygon3D.h +++ b/Polygon3D.h @@ -19,7 +19,8 @@ public: int GetIndex(int index) const; void SetNormal(const Vector3D& normal); - Vector3D GetNormal() const; + const Vector3D& GetNormal() const; + void NormalizeNormal(); void SetColor(int red, int green, int blue); void SetColor(const COLORREF color); @@ -35,8 +36,8 @@ public: private: int _indices[3]; Vector3D _normal; - float _depth = 0.0f; - bool _culled = false; + float _depth; + bool _culled; COLORREF _color; void Copy(const Polygon3D& other); diff --git a/Rasteriser.cpp b/Rasteriser.cpp index ba9e4cd..39ba2d8 100644 --- a/Rasteriser.cpp +++ b/Rasteriser.cpp @@ -5,8 +5,6 @@ Rasteriser app; bool Rasteriser::Initialise() { Model modelA; - Model modelB; - Model modelC; if (MD2Loader::LoadModel(".\\marvin.MD2", modelA, &Model::AddPolygon, &Model::AddVertex)) { _sceneModels.push_back(modelA); @@ -15,29 +13,15 @@ bool Rasteriser::Initialise() { return false; } + - if (MD2Loader::LoadModel(".\\megaman.MD2", modelB, &Model::AddPolygon, &Model::AddVertex)) - { - _sceneModels.push_back(modelB); - } - else - { - return false; - } + _lights.push_back(new AmbientLight(150, 150, 150)); + //_lights.push_back(new AmbientLight(255, 255, 255)); + _lights.push_back(new DirectionalLight(Vector3D(-1, 0, -1), 150, 150, 150)); + //_lights.push_back(new PointLight(Vertex(-10, 0, 10), 0, 1, 0, 100, 0, 0)); + _cameras.push_back(Camera(0.0f, 0.0f, 0.0f, Vertex(0.0f, 0.0f, -50.0f))); - if (MD2Loader::LoadModel(".\\w_railgun.MD2", modelC, &Model::AddPolygon, &Model::AddVertex)) - { - _sceneModels.push_back(modelC); - } - else - { - return false; - } - - _lights.push_back(new AmbientLight(50, 50, 50)); - _lights.push_back(new DirectionalLight(Vector3D(1, 0, 1), 50, 0, 0)); - _lights.push_back(new PointLight(Vertex(0, 10, 0), 0, 1, 0, 100, 0, 0)); - _cameras.push_back(Camera(0.0f, 0.0f, 0.0f, Vertex(0.0f, 0.0f, -100.0f))); + _screenMinimized = false; return true; } @@ -60,79 +44,124 @@ Camera& Rasteriser::GetCurrentCamera() return GetCamera(_currentCamera); } +void Rasteriser::CalculateLighting(Model& currentModel, bool fullLightRender) +{ + { + for (int pi = 0; pi < currentModel.GetPolygonCount(); pi++) + { + if (!currentModel.GetPolygon(pi).GetCulled()) + { + if (fullLightRender) + { + for (int i = 0; i < currentModel.GetPolygon(pi).GetPolygonVertexCount(); i++) + { + COLORREF colorWorking = RGB(0, 0, 0); + for (Light* currentLight : _lights) + { + colorWorking = currentLight->CalculateLight(currentModel, currentModel.GetVertex(pi, i), colorWorking); + } + currentModel.SetVertexColor(pi, i, colorWorking); + } + } + else + { + COLORREF colorWorking = RGB(0, 0, 0); + for (Light* currentLight : _lights) + { + colorWorking = currentLight->CalculateLight(currentModel, currentModel.GetPolygon(pi), colorWorking); + } + currentModel.SetPolygonColor(pi, colorWorking); + } + } + } + } +} + void Rasteriser::Update(const Bitmap& bitmap) { - int currentRot = (_rotation % 360); - Vertex startPos = Vertex(-50, 0, 0); - int currentZOff = 0; - int currentModelIndex = 0; + if (bitmap.GetWidth() == 0 || bitmap.GetHeight() == 0) + { + _screenMinimized = true; + } + else + { + _screenMinimized = false; + } - for (Model ¤tModel : _sceneModels) - { - currentModel.EnqueueTransform(GetTranslateMatrix(startPos)); - currentModel.EnqueueTransform(GetRotationMatrix(Axis::Y, (float) currentRot)); - startPos.SetX(startPos.GetX() + 50); - if ((currentModelIndex % 2) == 1) + if (!_screenMinimized) + { + int currentRot = (_rotation % 360); + Vertex startPos = Vertex(0, 0, 20); + int currentZOff = 0; + int currentModelIndex = 0; + + for (Model& currentModel : _sceneModels) { - currentZOff = 0; + currentModel.EnqueueTransform(GetTranslateMatrix(startPos)); + currentModel.EnqueueTransform(GetRotationMatrix(Axis::Y, (float)currentRot)); + //startPos.SetX(startPos.GetX() + 100); + if ((currentModelIndex % 2) == 1) + { + currentZOff = 0; + } + else + { + currentZOff = 100; + } + //startPos.SetZ((float)currentZOff); + currentModelIndex++; + } + + _currentAspectRatio = (float)(bitmap.GetWidth() / (float)bitmap.GetHeight()); + _currentPerspectiveMatrix = GetPerspectiveProjectionMatrix(1, _currentAspectRatio); + _currentViewMatrix = GetViewMatrix(1, bitmap.GetWidth(), bitmap.GetHeight()); + + if (_rotation == 360) + { + _rotation = 0; } else { - currentZOff = 100; - } - startPos.SetZ((float)currentZOff); - currentModelIndex++; + _rotation += 1; + } } - - _currentAspectRatio = (float)(bitmap.GetWidth() / bitmap.GetHeight()); - _currentPerspectiveMatrix = GetPerspectiveProjectionMatrix(1, _currentAspectRatio); - _currentViewMatrix = GetViewMatrix(1, bitmap.GetWidth(), bitmap.GetHeight()); - - if (_rotation == 360) - { - _rotation = 0; - } - else - { - _rotation += 1; - } } void Rasteriser::Render(const Bitmap& bitmap) { - ClearViewport(bitmap); - SelectObject(bitmap.GetDC(), GetStockObject(DC_BRUSH)); - SelectObject(bitmap.GetDC(), GetStockObject(DC_PEN)); - - for (Model ¤tModel : _sceneModels) + if (!_screenMinimized) { - currentModel.SetReflectionCoefficient(0.5f, 0.5f, 0.5f); - Matrix workingMatrix = workingMatrix.IdentityMatrix(); - for (Matrix currentTransform : currentModel.GetPendingTransforms()) + ClearViewport(bitmap); + SelectObject(bitmap.GetDC(), GetStockObject(DC_BRUSH)); + SelectObject(bitmap.GetDC(), GetStockObject(DC_PEN)); + + for (Model& currentModel : _sceneModels) { - workingMatrix *= currentTransform; - } - currentModel.ApplyTransformToLocalVertices(workingMatrix); - currentModel.CalculateBackfaces(GetCurrentCamera()); - for (Polygon3D ¤tPolygon : currentModel.GetPolygons()) - { - if (!currentPolygon.GetCulled()) + currentModel.SetReflectionCoefficient(0.5f, 0.5f, 0.5f); + Matrix workingMatrix = workingMatrix.IdentityMatrix(); + for (Matrix currentTransform : currentModel.GetPendingTransforms()) { - COLORREF colorWorking = RGB(0, 0, 0); - for (Light* currentLight : _lights) - { - colorWorking = currentLight->CalculateLight(currentModel, currentPolygon, colorWorking); - } - currentPolygon.SetColor(colorWorking); + workingMatrix *= currentTransform; } + currentModel.ApplyTransformToLocalVertices(workingMatrix); + currentModel.ApplyTransformToTransformedVertices(GetCurrentCamera().GetCurrentCameraTransformMatrix()); + + currentModel.CalculateBackfaces(GetCurrentCamera()); + currentModel.Sort(); + + currentModel.CalculateVertexNormals(); + CalculateLighting(currentModel, true); + + currentModel.ApplyTransformToTransformedVertices(_currentPerspectiveMatrix); + currentModel.DehomogenizeAllVertices(); + currentModel.ApplyTransformToTransformedVertices(_currentViewMatrix); + + //DrawWireFrame(bitmap.GetDC(), currentModel); + //DrawSolidFlat(bitmap.GetDC(), currentModel); + //DrawRasterisedSolidFlat(bitmap.GetDC(), currentModel); + DrawGouraud(bitmap.GetDC(), currentModel); + currentModel.ClearPendingTransforms(); } - currentModel.ApplyTransformToTransformedVertices(GetCurrentCamera().GetCurrentCameraTransformMatrix()); - currentModel.Sort(); - currentModel.ApplyTransformToTransformedVertices(_currentPerspectiveMatrix); - currentModel.DehomogenizeAllVertices(); - currentModel.ApplyTransformToTransformedVertices(_currentViewMatrix); - DrawSolidFlat(bitmap.GetDC(), currentModel); - currentModel.ClearPendingTransforms(); } } @@ -221,4 +250,442 @@ void Rasteriser::DrawSolidFlat(HDC hDc, Model& model) Polygon(hDc, pointArray.data(), currentPolygonVertexCount); } } -} \ No newline at end of file +} + +void Rasteriser::DrawRasterisedSolidFlat(HDC hDc, Model& model) +{ + int modelPolygonCount = (int)model.GetPolygonCount(); + for (int i = 0; i < modelPolygonCount; i++) + { + + if (!model.GetPolygon(i).GetCulled()) + { + vector vertexArray = model.GetPolygonVertexArray(i); + FillPolygonFlat(hDc, vertexArray, model.GetPolygon(i).GetColor()); + } + } +} + +void Rasteriser::DrawGouraud(HDC hDc, Model& model) +{ + int modelPolygonCount = (int)model.GetPolygonCount(); + for (int i = 0; i < modelPolygonCount; i++) + { + + if (!model.GetPolygon(i).GetCulled()) + { + vector vertexArray = model.GetPolygonVertexArray(i); + FillPolygonGouraud(hDc, vertexArray); + } + } +} + +bool VerticiesYCompareAsc(Vertex& v1, Vertex& v2) +{ + return v1.GetY() < v2.GetY(); +} + +void Rasteriser::FillPolygonFlat(HDC hDc, vector& verts, COLORREF colorIn) +{ + sort(verts.begin(), verts.end(), VerticiesYCompareAsc); + if (verts[1].GetY() == verts[2].GetY()) + { + FillFlatSideTriangle(hDc, verts[0], verts[1], verts[2], colorIn); + } + else if (verts[0].GetY() == verts[1].GetY()) + { + FillFlatSideTriangle(hDc, verts[2], verts[0], verts[1], colorIn); + } + else + { + Vertex temp = Vertex(verts[0].GetX() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetX() - verts[0].GetX()), verts[1].GetY(), verts[1].GetZ()); + if (verts[1].GetX() < temp.GetX()) + { + FillFlatSideTriangle(hDc, verts[0], verts[1], temp, colorIn); + FillFlatSideTriangle(hDc, verts[2], verts[1], temp, colorIn); + } + else + { + FillFlatSideTriangle(hDc, verts[0], temp, verts[1], colorIn); + FillFlatSideTriangle(hDc, verts[2], temp, verts[1], colorIn); + } + } +} + +void Rasteriser::FillFlatSideTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3, COLORREF colorIn) +{ + Vertex tempA = Vertex(v1); + Vertex tempB = Vertex(v1); + + bool changed1 = false; + bool changed2 = false; + + int dx1 = (int)ceil(abs(v2.GetX() - v1.GetX())); + int dy1 = (int)ceil(abs(v2.GetY() - v1.GetY())); + + int dx2 = (int)ceil(abs(v3.GetX() - v1.GetX())); + int dy2 = (int)ceil(abs(v3.GetY() - v1.GetY())); + + int signx1 = (int)sgn(v2.GetX() - v1.GetX()); + int signx2 = (int)sgn(v3.GetX() - v1.GetX()); + + int signy1 = (int)sgn(v2.GetY() - v1.GetY()); + int signy2 = (int)sgn(v3.GetY() - v1.GetY()); + + if (dy1 > dx1) + { + int tempInt = dx1; + dx1 = dy1; + dy1 = tempInt; + changed1 = true; + } + + if (dy2 > dx2) + { + int tempInt = dx2; + dx2 = dy2; + dy2 = tempInt; + changed2 = true; + } + + int e1 = 2 * dy1 - dx1; + int e2 = 2 * dy2 - dx2; + + for (int i = 0; i <= dx1; i++) + { + int startPoint; + int endPoint; + + if (tempA.GetXInt() < tempB.GetXInt()) + { + startPoint = tempA.GetXInt(); + endPoint = tempB.GetXInt(); + } + else + { + startPoint = tempB.GetXInt(); + endPoint = tempA.GetXInt(); + } + + for (int xi = (int)ceil(startPoint); xi <= endPoint; xi++) + { + SetPixel(hDc, xi, tempA.GetYInt(), colorIn); + } + + + while (e1 >= 0) + { + if (changed1) + { + tempA.SetX(tempA.GetX() + signx1); + } + else + { + tempA.SetY(tempA.GetY() + signy1); + } + e1 = e1 - 2 * dx1; + } + + if (changed1) + { + tempA.SetY(tempA.GetY() + signy1); + } + else + { + tempA.SetX(tempA.GetX() + signx1); + } + + e1 = e1 + 2 * dy1; + + while (tempB.GetY() != tempA.GetY()) + { + while (e2 >= 0) + { + if (changed2) + { + tempB.SetX(tempB.GetX() + signx2); + } + else + { + tempB.SetY(tempB.GetY() + signy2); + } + e2 = e2 - 2 * dx2; + } + + if (changed2) + { + tempB.SetY(tempB.GetY() + signy2); + } + else + { + tempB.SetX(tempB.GetX() + signx2); + } + e2 = e2 + 2 * dy2; + } + } +} + +void Rasteriser::FillPolygonGouraud(HDC hDc, vector& verts) +{ + sort(verts.begin(), verts.end(), VerticiesYCompareAsc); + if (verts[1].GetY() == verts[2].GetY()) + { + FillGouraudBottomFlatTriangle(hDc, verts[0], verts[1], verts[2]); + } + else if (verts[0].GetY() == verts[1].GetY()) + { + FillGouraudTopFlatTriangle(hDc, verts[0], verts[1], verts[2]); + } + else + { + Vertex temp = Vertex(verts[0].GetX() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetX() - verts[0].GetX()), verts[1].GetY(), verts[1].GetZ()); + temp.SetNormal(verts[1].GetNormal()); + + float cRed = GetRValue(verts[0].GetColor()) + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (GetRValue(verts[2].GetColor()) - GetRValue(verts[0].GetColor())); + float cGreen = GetGValue(verts[0].GetColor()) + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (GetGValue(verts[2].GetColor()) - GetGValue(verts[0].GetColor())); + float cBlue = GetBValue(verts[0].GetColor()) + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (GetBValue(verts[2].GetColor()) - GetBValue(verts[0].GetColor())); + temp.SetColor(RGB((int)cRed, (int)cGreen, (int)cBlue)); + + temp.SetColor(verts[1].GetColor()); + + FillGouraudBottomFlatTriangle(hDc, verts[0], verts[1], temp); + FillGouraudTopFlatTriangle(hDc, verts[1], temp, verts[2]); + } +} + +void Rasteriser::FillGouraudBottomFlatTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3) +{ + float slope1 = (float)(v2.GetX() - v1.GetX()) / (float)(v2.GetY() - v1.GetY()); + float slope2 = (float)(v3.GetX() - v1.GetX()) / (float)(v3.GetY() - v1.GetY()); + + float x1 = (float)v1.GetX(); + float x2 = (float)v1.GetX() + 0.5f; + + if (slope2 < slope1) + { + float slopeTmp = slope1; + slope1 = slope2; + slope2 = slopeTmp; + } + + for (int scanlineY = (int)v1.GetY(); scanlineY <= (int)v2.GetY(); scanlineY++) + { + float iRedA = (scanlineY - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetR() + (v1.GetY() - scanlineY) / (v1.GetY() - v2.GetY()) * v2.GetR(); + float iRedB = (scanlineY - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetR() + (v1.GetY() - scanlineY) / (v1.GetY() - v3.GetY()) * v3.GetR(); + float iGreenA = (scanlineY - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetG() + (v1.GetY() - scanlineY) / (v1.GetY() - v2.GetY()) * v2.GetG(); + float iGreenB = (scanlineY - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetG() + (v1.GetY() - scanlineY) / (v1.GetY() - v3.GetY()) * v3.GetG(); + float iBlueA = (scanlineY - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetB() + (v1.GetY() - scanlineY) / (v1.GetY() - v2.GetY()) * v2.GetB(); + float iBlueB = (scanlineY - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetB() + (v1.GetY() - scanlineY) / (v1.GetY() - v3.GetY()) * v3.GetB(); + + for (int xi = (int)ceil(x1); xi < (int)x2; xi++) + { + float redTmp = (x2 - xi) / (x2 - x1) * iRedA + (xi - x1) / (x2 - x1) * iRedB; + float greenTmp = (x2 - xi) / (x2 - x1) * iGreenA + (xi - x1) / (x2 - x1) * iGreenB; + float blueTmp = (x2 - xi) / (x2 - x1) * iBlueA + (xi - x1) / (x2 - x1) * iBlueB; + + COLORREF currentColor = RGB(BoundsCheck(0, 255, (int)redTmp), BoundsCheck(0, 255, (int)greenTmp), BoundsCheck(0, 255, (int)blueTmp)); + SetPixel(hDc, xi, scanlineY, currentColor); + } + + x1 += slope1; + x2 += slope2; + } +} + +void Rasteriser::FillGouraudTopFlatTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3) +{ + float slope1 = (float)(v3.GetX() - v1.GetX()) / (float)(v3.GetY() - v1.GetY()); + float slope2 = (float)(v3.GetX() - v2.GetX()) / (float)(v3.GetY() - v2.GetY()); + + float x1 = (float)v3.GetX(); + float x2 = (float)v3.GetX() + 0.5f; + + if (slope1 < slope2) + { + float slopeTmp = slope1; + slope1 = slope2; + slope2 = slopeTmp; + } + + for (int scanlineY = (int)v3.GetY(); scanlineY > (int)v1.GetY(); scanlineY--) + { + float iRedA = (scanlineY - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetR() + (v1.GetY() - scanlineY) / (v1.GetY() - v2.GetY()) * v2.GetR(); + float iRedB = (scanlineY - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetR() + (v1.GetY() - scanlineY) / (v1.GetY() - v3.GetY()) * v3.GetR(); + float iGreenA = (scanlineY - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetG() + (v1.GetY() - scanlineY) / (v1.GetY() - v2.GetY()) * v2.GetG(); + float iGreenB = (scanlineY - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetG() + (v1.GetY() - scanlineY) / (v1.GetY() - v3.GetY()) * v3.GetG(); + float iBlueA = (scanlineY - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetB() + (v1.GetY() - scanlineY) / (v1.GetY() - v2.GetY()) * v2.GetB(); + float iBlueB = (scanlineY - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetB() + (v1.GetY() - scanlineY) / (v1.GetY() - v3.GetY()) * v3.GetB(); + + for (int xi = (int)ceil(x1); xi < (int)x2; xi++) + { + float redTmp = (x2 - xi) / (x2 - x1) * iRedA - (xi - x1) / (x2 - x1) * iRedB; + float greenTmp = (x2 - xi) / (x2 - x1) * iGreenA - (xi - x1) / (x2 - x1) * iGreenB; + float blueTmp = (x2 - xi) / (x2 - x1) * iBlueA - (xi - x1) / (x2 - x1) * iBlueB; + + COLORREF currentColor = RGB(BoundsCheck(0, 255, (int)redTmp), BoundsCheck(0, 255, (int)greenTmp), BoundsCheck(0, 255, (int)blueTmp)); + SetPixel(hDc, xi, scanlineY, currentColor); + } + + x1 -= slope1; + x2 -= slope2; + } +} + +void Rasteriser::FillPolygonGouraudInt(HDC hDc, vector& verts) +{ + sort(verts.begin(), verts.end(), VerticiesYCompareAsc); + if (verts[1].GetY() == verts[2].GetY()) + { + FillGouraudSideTriangle(hDc, verts[0], verts[1], verts[2]); + } + else if (verts[0].GetY() == verts[1].GetY()) + { + FillGouraudSideTriangle(hDc, verts[2], verts[0], verts[1]); + } + else + { + Vertex temp = Vertex(verts[0].GetX() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetX() - verts[0].GetX()), verts[1].GetY(), verts[1].GetZ()); + temp.SetNormal(verts[1].GetNormal()); + + float cRed = verts[0].GetR() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetR() - verts[0].GetR()); + float cGreen = verts[0].GetG() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetG() - verts[0].GetG()); + float cBlue = verts[0].GetB() + ((verts[1].GetY() - verts[0].GetY()) / (verts[2].GetY() - verts[0].GetY())) * (verts[2].GetB() - verts[0].GetB()); + temp.SetColor(BoundsCheck(0, 255, (int)cRed), BoundsCheck(0, 255, (int)cGreen), BoundsCheck(0, 255, (int)cBlue)); + + if (verts[1].GetX() < temp.GetX()) + { + FillGouraudSideTriangle(hDc, verts[0], verts[1], temp); + FillGouraudSideTriangle(hDc, verts[2], verts[1], temp); + } + else + { + FillGouraudSideTriangle(hDc, verts[0], temp, verts[1]); + FillGouraudSideTriangle(hDc, verts[2], temp, verts[1]); + } + } +} + +void Rasteriser::FillGouraudSideTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3) +{ + Vertex tempA = Vertex(v1); + Vertex tempB = Vertex(v1); + + bool changed1 = false; + bool changed2 = false; + + int dx1 = (int)ceil(abs(v2.GetX() - v1.GetX())); + int dy1 = (int)ceil(abs(v2.GetY() - v1.GetY())); + + int dx2 = (int)ceil(abs(v3.GetX() - v1.GetX())); + int dy2 = (int)ceil(abs(v3.GetY() - v1.GetY())); + + int signx1 = (int)ceil(sgn(v2.GetX() - v1.GetX())); + int signx2 = (int)ceil(sgn(v3.GetX() - v1.GetX())); + + int signy1 = (int)ceil(sgn(v2.GetY() - v1.GetY())); + int signy2 = (int)ceil(sgn(v3.GetY() - v1.GetY())); + + if (dy1 > dx1) + { + int tempDx = dx1; + dx1 = dy1; + dy1 = tempDx; + changed1 = true; + } + + if (dy2 > dx2) + { + int tempDx = dx2; + dx2 = dy2; + dy2 = tempDx; + changed2 = true; + } + + int e1 = 2 * (int)dy1 - (int)dx1; + int e2 = 2 * (int)dy2 - (int)dx2; + + for (int i = 0; i <= (int)dx1; i++) + { + float leftEndPoint; + float rightEndPoint; + + if (tempA.GetX() < tempB.GetX()) + { + leftEndPoint = tempA.GetX(); + rightEndPoint = tempB.GetX(); + } + else + { + leftEndPoint = tempB.GetX(); + rightEndPoint = tempA.GetX(); + } + + float iRedA = (tempA.GetY() - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetR() + (v1.GetY() - tempA.GetY()) / (v1.GetY() - v2.GetY()) * v2.GetR(); + float iRedB = (tempA.GetY() - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetR() + (v1.GetY() - tempA.GetY()) / (v1.GetY() - v3.GetY()) * v3.GetR(); + float iGreenA = (tempA.GetY() - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetG() + (v1.GetY() - tempA.GetY()) / (v1.GetY() - v2.GetY()) * v2.GetG(); + float iGreenB = (tempA.GetY() - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetG() + (v1.GetY() - tempA.GetY()) / (v1.GetY() - v3.GetY()) * v3.GetG(); + float iBlueA = (tempA.GetY() - v2.GetY()) / (v1.GetY() - v2.GetY()) * v1.GetB() + (v1.GetY() - tempA.GetY()) / (v1.GetY() - v2.GetY()) * v2.GetB(); + float iBlueB = (tempA.GetY() - v3.GetY()) / (v1.GetY() - v3.GetY()) * v1.GetB() + (v1.GetY() - tempA.GetY()) / (v1.GetY() - v3.GetY()) * v3.GetB(); + + for (int xi = (int)ceil(leftEndPoint); xi <= (int)rightEndPoint; xi++) + { + float redTmp = (rightEndPoint - xi) / (rightEndPoint - leftEndPoint) * iRedA + (xi - leftEndPoint) / (rightEndPoint - leftEndPoint) * iRedB; + float greenTmp = (rightEndPoint - xi) / (rightEndPoint - leftEndPoint) * iGreenA + (xi - leftEndPoint) / (rightEndPoint - leftEndPoint) * iGreenB; + float blueTmp = (rightEndPoint - xi) / (rightEndPoint - leftEndPoint) * iBlueA + (xi - leftEndPoint) / (rightEndPoint - leftEndPoint) * iBlueB; + + COLORREF currentColor = RGB(BoundsCheck(0, 255, (int)redTmp), BoundsCheck(0, 255, (int)greenTmp), BoundsCheck(0, 255, (int)blueTmp)); + SetPixel(hDc, xi, tempA.GetYInt(), currentColor); + } + + + while (e1 >= 0) + { + if (changed1) + { + tempA.SetX((float)tempA.GetXInt() + (float)signx1); + } + else + { + tempA.SetY((float)tempA.GetYInt() + (float)signy1); + } + e1 = e1 - 2 * dx1; + } + + if (changed1) + { + tempA.SetY((float)tempA.GetYInt() + (float)signy1); + } + else + { + tempA.SetX((float)tempA.GetXInt() + (float)signx1); + } + + e1 = e1 + 2 * dy1; + + while (tempB.GetY() < tempA.GetY() && tempB.GetY() > tempA.GetY()) + { + while (e2 >= 0) + { + if (changed2) + { + tempB.SetX((float)tempB.GetXInt() + (float)signx2); + } + else + { + tempB.SetY((float)tempB.GetYInt() + (float)signy2); + } + e2 = e2 - 2 * dx2; + } + + if (changed2) + { + tempB.SetY((float)tempB.GetYInt() + (float)signy2); + } + else + { + tempB.SetX((float)tempB.GetXInt() + (float)signx2); + } + e2 = e2 + 2 * dy2; + } + } +} diff --git a/Rasteriser.h b/Rasteriser.h index f7ae6a7..7ea264b 100644 --- a/Rasteriser.h +++ b/Rasteriser.h @@ -28,11 +28,25 @@ public: Camera& GetCamera(int index); Camera& GetCurrentCamera(); + void CalculateLighting(Model& currentModel, bool fullLightRender); + void DrawSquare(HDC hDc, const vector verticies); void DrawShape(HDC hDc, const vector verticies); void DrawWireFrame(HDC hDc, Model& model); void DrawSolidFlat(HDC hDc, Model& model); + void DrawRasterisedSolidFlat(HDC hDc, Model& model); + void DrawGouraud(HDC hDc, Model& model); + + void FillPolygonFlat(HDC hDc, vector& verticies, COLORREF colorIn); + void FillFlatSideTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3, COLORREF colorIn); + + void FillPolygonGouraud(HDC hDc, vector& verticies); + void FillGouraudTopFlatTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3); + void FillGouraudBottomFlatTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3); + + void FillPolygonGouraudInt(HDC hDc, vector& verticies); + void FillGouraudSideTriangle(HDC hDc, const Vertex& v1, const Vertex& v2, const Vertex& v3); private: vector _sceneModels; @@ -45,5 +59,7 @@ private: Matrix _currentViewMatrix; float _currentAspectRatio = 0.0f; int _rotation = 0; + + bool _screenMinimized = false; }; diff --git a/SharedTools.cpp b/SharedTools.cpp index f72c8d3..3eb1439 100644 --- a/SharedTools.cpp +++ b/SharedTools.cpp @@ -92,12 +92,12 @@ Matrix SharedTools::GetViewMatrix(float d, int width, int height) Matrix SharedTools::GetCameraTranslateMatrix(const float x, const float y, const float z) { - return Matrix({ 1, 0, 0, -x, 0, 1, 0, -y, 0, 0, 1, -z, 0, 0, 0, 1 }); + return GetTranslateMatrix(-x, -y, -z); } Matrix SharedTools::GetCameraTranslateMatrix(const Vertex& position) { - return GetCameraTranslateMatrix(position.GetX(), position.GetY(), position.GetZ()); + return GetTranslateMatrix(-position.GetX(), -position.GetY(), -position.GetZ()); } @@ -115,4 +115,4 @@ Matrix SharedTools::GetCameraRotationMatrix(const Axis rotAxis, const float rotD default: throw "Invalid axis selected"; } -} \ No newline at end of file +} diff --git a/SharedTools.h b/SharedTools.h index 05b4731..d99ccd2 100644 --- a/SharedTools.h +++ b/SharedTools.h @@ -5,6 +5,10 @@ enum class Axis { X, Y, Z }; enum class ColRef { Red, Green, Blue }; +template int sgn(T val) { + return (T(0) < val) - (val < T(0)); +} + namespace SharedTools { const float PI = (float)acos(-1); diff --git a/Vector3D.cpp b/Vector3D.cpp index 385eff0..96dba3f 100644 --- a/Vector3D.cpp +++ b/Vector3D.cpp @@ -2,16 +2,16 @@ Vector3D::Vector3D() { - SetX(1); - SetY(1); - SetZ(1); + _x = 1; + _y = 1; + _z = 1; } Vector3D::Vector3D(float x, float y, float z) { - SetX(x); - SetY(y); - SetZ(z); + _x = x; + _y = y; + _z = z; } Vector3D::Vector3D(const Vector3D& other) @@ -54,7 +54,7 @@ void Vector3D::SetZ(const float z) _z = z; } -void Vector3D::Normalize() +const void Vector3D::Normalize() { float length = sqrt(_x * _x + _y * _y + _z * _z); @@ -68,12 +68,17 @@ void Vector3D::Normalize() float Vector3D::DotProduct(const Vector3D v1, const Vector3D v2) { - return ((v1.GetX() * v2.GetX()) + (v1.GetY() * v2.GetY()) + (v1.GetZ() * v2.GetZ())); + return v1.GetX() * v2.GetX() + v1.GetY() * v2.GetY() + v1.GetZ() * v2.GetZ(); +} + +float Vector3D::Length(const Vector3D v1, const Vector3D v2) +{ + return (float)sqrt(pow((v1.GetX() - v2.GetX()), 2) + pow((v1.GetY() - v2.GetY()), 2) + pow((v1.GetZ() - v2.GetZ()), 2)); } Vector3D Vector3D::CrossProduct(const Vector3D v1, const Vector3D v2) { - return Vector3D((v1.GetY() * v2.GetZ()) - (v1.GetZ() * v2.GetY()), (v1.GetZ() * v2.GetX()) - (v1.GetX() * v2.GetZ()), (v1.GetX() * v2.GetY()) - (v1.GetY() * v2.GetX())); + return Vector3D(v1.GetY() * v2.GetZ() - v1.GetZ() * v2.GetY(), v1.GetZ() * v2.GetX() - v1.GetX() * v2.GetZ(), v1.GetX() * v2.GetY() - v1.GetY() * v2.GetX()); } @@ -82,6 +87,16 @@ const Vector3D Vector3D::operator+ (const Vector3D& rhs) const return Vector3D(_x + rhs.GetX(), _y + rhs.GetY(), _z + rhs.GetZ()); } +const Vector3D Vector3D::operator/ (const float rhs) const +{ + return Vector3D(_x / rhs, _y / rhs, _z / rhs); +} + +const Vector3D Vector3D::operator/ (const int rhs) const +{ + return Vector3D(_x / rhs, _y / rhs, _z / rhs); +} + void Vector3D::Copy(const Vector3D& other) { _x = other.GetX(); diff --git a/Vector3D.h b/Vector3D.h index 4f39ec9..f4bc9c5 100644 --- a/Vector3D.h +++ b/Vector3D.h @@ -18,12 +18,15 @@ public: float GetZ() const; void SetZ(const float z); - void Normalize(); + const void Normalize(); static float DotProduct(const Vector3D v1, const Vector3D v2); + static float Length(const Vector3D v1, const Vector3D v2); static Vector3D CrossProduct(const Vector3D v1, const Vector3D v2); const Vector3D operator+ (const Vector3D& rhs) const; + const Vector3D operator/ (const int rhs) const; + const Vector3D operator/ (const float rhs) const; private: float _x; diff --git a/Vertex.cpp b/Vertex.cpp index caab4d8..f878424 100644 --- a/Vertex.cpp +++ b/Vertex.cpp @@ -6,6 +6,11 @@ Vertex::Vertex() _y = 0.0f; _z = 0.0f; _w = 0.0f; + _contributeCount = 0; + _normal = Vector3D(0, 0, 0); + _r = 0; + _g = 0; + _b = 0; } Vertex::Vertex(const float x, const float y, const float z) @@ -14,6 +19,11 @@ Vertex::Vertex(const float x, const float y, const float z) _y = y; _z = z; _w = 1.0f; + _contributeCount = 0; + _normal = Vector3D(0, 0, 0); + _r = 0; + _g = 0; + _b = 0; } Vertex::Vertex(const float x, const float y, const float z, const float w) @@ -22,6 +32,11 @@ Vertex::Vertex(const float x, const float y, const float z, const float w) _y = y; _z = z; _w = w; + _contributeCount = 0; + _normal = Vector3D(0, 0, 0); + _r = 0; + _g = 0; + _b = 0; } Vertex::Vertex(const Vertex & other) @@ -69,6 +84,132 @@ void Vertex::SetW(const float w) _w = w; } +const int Vertex::GetContributeCount() const +{ + return _contributeCount; +} + +void Vertex::IncrementContributeCount() +{ + _contributeCount++; +} + +void Vertex::ResetContributeCount() +{ + _contributeCount = 0; +} + +const Vector3D& Vertex::GetNormal() const +{ + return _normal; +} + +void Vertex::SetNormal(float x, float y, float z) +{ + _normal.SetX(x); + _normal.SetY(y); + _normal.SetZ(z); +} + +void Vertex::SetNormal(const Vector3D& normal) +{ + SetNormal(normal.GetX(), normal.GetY(), normal.GetZ()); +} + +void Vertex::NormalizeNormal() +{ + _normal.Normalize(); +} + +void Vertex::ResetNormal(bool resetCount) +{ + SetNormal(0, 0, 0); + if (resetCount) + { + ResetContributeCount(); + } +} + +const COLORREF Vertex::GetColor() const +{ + return RGB(_r, _g, _b); +} + +int Vertex::GetR() const +{ + return _r; +} + +int Vertex::GetG() const +{ + return _g; +} + +int Vertex::GetB() const +{ + return _b; +} + +void Vertex::SetColor(int r, int g, int b) +{ + _r = r; + _g = g; + _b = b; +} + +void Vertex::SetColor(const COLORREF colorIn) +{ + SetColor((int)GetRValue(colorIn), (int)GetGValue(colorIn), (int)GetBValue(colorIn)); +} + +int Vertex::GetXInt(bool forceRoundUp) const +{ + if (forceRoundUp) + { + return (int)ceil(GetX()); + } + else + { + return (int)floor(GetX()); + } +;} + +int Vertex::GetYInt(bool forceRoundUp) const +{ + if (forceRoundUp) + { + return (int)ceil(GetY()); + } + else + { + return (int)floor(GetY()); + } +} + +int Vertex::GetZInt(bool forceRoundUp) const +{ + if (forceRoundUp) + { + return (int)ceil(GetZ()); + } + else + { + return (int)floor(GetZ()); + } +} + +int Vertex::GetWInt(bool forceRoundUp) const +{ + if (forceRoundUp) + { + return (int)ceil(GetW()); + } + else + { + return (int)floor(GetW()); + } +} + void Vertex::Dehomogenize() { _x = _x / _w; @@ -90,7 +231,7 @@ Vertex& Vertex::operator=(const Vertex& rhs) const Vector3D Vertex::operator-(const Vertex& rhs) const { - return Vector3D(rhs.GetX() - GetX(), rhs.GetY() - GetY(), rhs.GetZ() - GetZ()); + return Vector3D(rhs.GetX() - _x, rhs.GetY() - _y, rhs.GetZ() - _z); } // The const at the end of the declaraion for '==" indicates that this operation does not change @@ -118,4 +259,9 @@ void Vertex::Copy(const Vertex& other) _y = other.GetY(); _z = other.GetZ(); _w = other.GetW(); + + _contributeCount = other.GetContributeCount(); + SetNormal(other.GetNormal()); + + SetColor(other.GetR(), other.GetG(), other.GetB()); } diff --git a/Vertex.h b/Vertex.h index 71cc658..86e3f13 100644 --- a/Vertex.h +++ b/Vertex.h @@ -1,5 +1,6 @@ #pragma once #include "Vector3D.h" +#include "windows.h" class Vertex { @@ -19,6 +20,31 @@ public: float GetW() const; void SetW(const float w); + const int GetContributeCount() const; + void IncrementContributeCount(); + void ResetContributeCount(); + + const Vector3D& GetNormal() const; + void SetNormal(float x, float y, float z); + void SetNormal(const Vector3D& normal); + void NormalizeNormal(); + void ResetNormal(bool resetCount = false); + + const COLORREF GetColor() const; + int GetR() const; + int GetG() const; + int GetB() const; + void SetColor(const int r, const int g, const int b); + void SetColor(const COLORREF colorIn); + + // Accessor Methods for returning the private x, y, z and w values as integeres instead of floats + // the ceil function to round the number up by defaults but using providing a false param will + // use the floor function instead to round the number down + int GetXInt(bool forceRoundUp = false) const; + int GetYInt(bool forceRoundUp = false) const; + int GetZInt(bool forceRoundUp = false) const; + int GetWInt(bool forceRoundUp = false) const; + void Dehomogenize(); // Assignment operator @@ -35,6 +61,13 @@ private: float _z; float _w; + int _contributeCount; + Vector3D _normal; + + int _r; + int _g; + int _b; + void Copy(const Vertex& other); }; diff --git a/cow.md2 b/cow.md2 new file mode 100644 index 0000000000000000000000000000000000000000..34b2c2bbb3b1309cd4b496ab5a15c02c0044f55e GIT binary patch literal 147744 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 AqyPW_ literal 0 HcmV?d00001