diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj
index ab6ff8e..066e68d 100644
--- a/dev/msvc/olcPGE3.vcxproj
+++ b/dev/msvc/olcPGE3.vcxproj
@@ -182,6 +182,7 @@
+
true
true
diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters
index 04da150..b68a605 100644
--- a/dev/msvc/olcPGE3.vcxproj.filters
+++ b/dev/msvc/olcPGE3.vcxproj.filters
@@ -201,6 +201,9 @@
Extensions\MiniAudio
+
+ Utilities
+
diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj
index f044f79..46c3503 100644
--- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj
+++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj
@@ -107,6 +107,10 @@
true
true
+
+ false
+ false
+
true
true
@@ -164,8 +168,8 @@
true
true
- false
- false
+ true
+ true
true
@@ -242,15 +246,19 @@
$(SolutionDir)..\..\
+ $(VC_IncludePath);$(WindowsSDK_IncludePath);../../../;
$(SolutionDir)..\..\
+ $(VC_IncludePath);$(WindowsSDK_IncludePath);../../../;
$(SolutionDir)..\..\
+ $(VC_IncludePath);$(WindowsSDK_IncludePath);../../../;
$(SolutionDir)..\..\
+ $(VC_IncludePath);$(WindowsSDK_IncludePath);../../../;
diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters
index 1da31ea..aafb310 100644
--- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters
+++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters
@@ -99,6 +99,9 @@
Source Files
+
+ Source Files
+
diff --git a/dev/src/image.cpp b/dev/src/image.cpp
index b54fa07..0e67566 100644
--- a/dev/src/image.cpp
+++ b/dev/src/image.cpp
@@ -72,6 +72,37 @@ namespace olc
return onCPU;
}
+ bool Image::set(int x, int y, olc::Pixel p)
+ {
+ if (x >= 0 && y >= 0 && x < dimensions.x && y < dimensions.y)
+ {
+ pixels[int(y) * dimensions.x + int(x)] = p;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ olc::Pixel Image::get(int x, int y)
+ {
+ if (x >= 0 && y >= 0 && x < dimensions.x && y < dimensions.y)
+ {
+ return pixels[int(y) * dimensions.x + int(x)];
+ }
+ else
+ return olc::Colour::BLACK;
+ }
+
+ int Image::width()
+ {
+ return dimensions.x;
+ }
+
+ int Image::height()
+ {
+ return dimensions.y;
+ }
+
olc::ImageRegion Image::all()
{
return olc::ImageRegion(*this);
diff --git a/dev/src/image.h b/dev/src/image.h
index 0f57c0f..0c1263e 100644
--- a/dev/src/image.h
+++ b/dev/src/image.h
@@ -82,6 +82,13 @@ namespace olc
bool BoundToGPU() const;
bool BoundToCPU() const;
+
+ // SW Renderer IFace
+ bool set(int x, int y, olc::Pixel p);
+ olc::Pixel get(int x, int y);
+ int width();
+ int height();
+
public:
olc::ImageRegion all();
olc::ImageRegion region(const olc::vf2d pos, const olc::vf2d& size);
diff --git a/examples/olcPGE3_SoftwareRendering.cpp b/examples/olcPGE3_SoftwareRendering.cpp
new file mode 100644
index 0000000..9a5a4bc
--- /dev/null
+++ b/examples/olcPGE3_SoftwareRendering.cpp
@@ -0,0 +1,200 @@
+/*
+ olc::PixelGameEngine3 Example - Software Rendering
+
+ Draws transformed and textured graphics entirely with the CPU
+
+ Licenced under the OLC-3 License
+*/
+
+
+// Define OLC_PGE3_APPLICATION to include the implementation of
+// the Pixel Game Engine as part of this translation unit
+#define OLC_PGE3_APPLICATION
+#include "../olcPixelGameEngine3.h"
+#include "../utilities/olcUTIL3_SWRender.h"
+
+struct TextSurface
+{
+ TextSurface()
+ {
+ sSurface.resize(vSize.area(), '-');
+ }
+
+ std::string sSurface;
+ olc::vi2d vSize = { 32, 30 };
+
+ bool set(int x, int y, char c)
+ {
+ if (x >= 0 && y >= 0 && x < vSize.x && y < vSize.y)
+ {
+ sSurface[y * vSize.x + x] = c;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ char get(int x, int y)
+ {
+ if (x >= 0 && y >= 0 && x < vSize.x && y < vSize.y)
+ {
+ return sSurface[y * vSize.x + x];
+ }
+ else
+ return 'x';
+ }
+
+ int width() { return vSize.x; }
+ int height() { return vSize.y; }
+};
+
+
+
+class Example_SoftwareRenderer : public olc::PixelGameEngine
+{
+public:
+ Example_SoftwareRenderer()
+ {
+ sAppName = "Example - Lines";
+ }
+
+public:
+ // Called once at the start, so create things here
+ bool OnUserCreate() override
+ {
+ // Nothing to do here, so return true
+ CreateImageFromFile(imgTest, "./assets/sanity_texture.png");
+ return true;
+ }
+
+ // Called every frame, so update things here
+ bool OnUserUpdate(float fElapsedTime) override
+ {
+
+ draw.Clear(olc::Colour::CYAN);
+ draw.Pixel({ 0,0 },olc::Colour::GREEN);
+
+ DrawSW.SetTarget(draw.GetTarget());
+
+ DrawSW.Clear(olc::Colour::VERY_DARK_BLUE);
+
+ DrawSW.Plot({ 4, 4 }, olc::Colour::RED);
+
+ DrawSW.Line({ 10,4 }, mouse.GetPosition(), olc::Colour::MAGENTA, olc::Colour::VERY_DARK_BLUE);
+
+ DrawSW.FilledRect({ 10,22 }, { 32, 32 }, olc::Colour::BLACK);
+
+ DrawSW.FilledRect({ 6, 18 }, { 32, 32 },
+ olc::Colour::RED, olc::Colour::YELLOW,
+ olc::Colour::GREEN, olc::Colour::MAGENTA);
+
+ DrawSW.Rect({ 6, 18 }, { 32, 32 }, olc::Colour::WHITE);
+
+
+
+ float fTotal = float(TotalTimeElapsed());
+
+ DrawSW.WorldOffset({ 68, 68 });
+ DrawSW.WorldRotate(fTotal, { 0 ,0 });
+ DrawSW.FilledRect({ -16, -16 }, { 32, 32 }, olc::Colour::BLACK);
+
+ DrawSW.WorldReset();
+ DrawSW.WorldOffset({ 64, 64 });
+ DrawSW.WorldRotate(fTotal, { 0 ,0 });
+ DrawSW.FilledRect({ -16, -16 }, { 32, 32 },
+ olc::Colour::RED, olc::Colour::YELLOW,
+ olc::Colour::GREEN, olc::Colour::MAGENTA);
+
+ DrawSW.Rect({ -16, -16 }, { 32, 32 }, olc::Colour::WHITE);
+
+
+ DrawSW.WorldReset();
+ DrawSW.WorldOffset({ 128, 68 });
+ DrawSW.WorldRotate(fTotal, { 0 ,0 });
+ DrawSW.WorldScale({ 2, 2 });
+ DrawSW.FilledRect({ -16, -16 }, { 32, 32 }, olc::Colour::BLACK);
+
+ DrawSW.WorldReset();
+ DrawSW.WorldOffset({ 124, 64 });
+ DrawSW.WorldRotate(fTotal, { 0 ,0 });
+ DrawSW.WorldScale({ 2, 2 });
+ DrawSW.TexturedRect({ -16, -16 }, { 32, 32 },
+ imgTest);
+
+
+
+
+
+ DrawSW.WorldReset();
+ DrawSW.TexturedTriangle(
+ { 20,180 },
+ mouse.GetPosition(),
+ { 250, 230 },
+ olc::Colour::RED,
+ olc::Colour::GREEN,
+ olc::Colour::BLUE,
+ { 0,0 },
+ { 1,0 },
+ { 1,1 }, imgTest);
+
+
+
+ DrawSW.Circle(mouse.GetPosition(), 20, olc::Colour::GREEN);
+ DrawSW.FilledCircle(mouse.GetPosition(), 10, olc::Colour::CYAN);
+
+
+ //// Clear
+ //TextDraw.SetTarget(textSurface);
+ //
+ //
+ //TextDraw.Clear('-');
+
+
+ //TextDraw.FilledTriangle(
+ // { 20 / 8, 180 / 8 },
+ // mouse.GetPosition() / 8,
+ // { 250 / 8, 230 / 8 },
+ // 'O');
+
+
+ //TextDraw.Line({ 10 / 8,10 / 8 }, mouse.GetPosition() / 8, 'A');
+
+
+ //// Draw TextSurface to Screen
+ //for (int y = 0; y < textSurface.height(); y++)
+ //{
+ // for (int x = 0; x < textSurface.width(); x++)
+ // {
+ // draw.String({ x * 8.0f, y * 8.0f }, std::string(1, textSurface.get(x, y)));
+ // }
+ //}
+
+ // Successful frame
+ return true;
+ }
+
+protected:
+ olc::utils::SWDraw DrawSW;
+ olc::Image imgTest;
+
+ TextSurface textSurface;
+ olc::utils::SoftwareRenderer TextDraw;
+};
+
+
+// Main entry point for the application
+int main()
+{
+ // Construct demo application
+ Example_SoftwareRenderer demo;
+
+ // Create "screen" of 256x240 "pixels"
+ // with a pixel size of 4x4 actual screen pixels
+ if (demo.Construct({ 256, 240 }, { 4, 4 }))
+ {
+ // Start the application
+ demo.Start();
+ }
+
+ return 0;
+}
\ No newline at end of file
diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h
index 6a4ad22..c4b1408 100644
--- a/olcPixelGameEngine3.h
+++ b/olcPixelGameEngine3.h
@@ -2379,6 +2379,13 @@ namespace olc
bool BoundToGPU() const;
bool BoundToCPU() const;
+
+ // SW Renderer IFace
+ bool set(int x, int y, olc::Pixel p);
+ olc::Pixel get(int x, int y);
+ int width();
+ int height();
+
public:
olc::ImageRegion all();
olc::ImageRegion region(const olc::vf2d pos, const olc::vf2d& size);
@@ -20668,6 +20675,37 @@ namespace olc
return onCPU;
}
+ bool Image::set(int x, int y, olc::Pixel p)
+ {
+ if (x >= 0 && y >= 0 && x < dimensions.x && y < dimensions.y)
+ {
+ pixels[int(y) * dimensions.x + int(x)] = p;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ olc::Pixel Image::get(int x, int y)
+ {
+ if (x >= 0 && y >= 0 && x < dimensions.x && y < dimensions.y)
+ {
+ return pixels[int(y) * dimensions.x + int(x)];
+ }
+ else
+ return olc::Colour::BLACK;
+ }
+
+ int Image::width()
+ {
+ return dimensions.x;
+ }
+
+ int Image::height()
+ {
+ return dimensions.y;
+ }
+
olc::ImageRegion Image::all()
{
return olc::ImageRegion(*this);
diff --git a/utilities/olcUTIL3_SWRender.h b/utilities/olcUTIL3_SWRender.h
new file mode 100644
index 0000000..50c7900
--- /dev/null
+++ b/utilities/olcUTIL3_SWRender.h
@@ -0,0 +1,769 @@
+/*
+ OneLoneCoder - Software Renderer v3.0
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ A collection of software rendering routines. Typically these model
+ that of the basic Draw.xx() routines in PGE3, however they don't
+ have to be olc::Pixel in olc::Image. As long as your container
+ and element fulfil the conceptual requirements you can render
+ anything into anything!
+
+
+ License (OLC-3)
+ ~~~~~~~~~~~~~~~
+
+ Copyright 2018 - 2026 OneLoneCoder.com
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions or derivations of source code must retain the above
+ copyright notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions or derivative works in binary form must reproduce
+ the above copyright notice. This list of conditions and the following
+ disclaimer must be reproduced in the documentation and/or other
+ materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ Links
+ ~~~~~
+ YouTube: https://www.youtube.com/javidx9
+ Discord: https://discord.gg/WhwHUMV
+ Twitter: https://www.twitter.com/javidx9
+ Twitch: https://www.twitch.tv/javidx9
+ GitHub: https://www.github.com/onelonecoder
+ Homepage: https://www.onelonecoder.com
+
+ Author
+ ~~~~~~
+ David Barr, aka javidx9, ©OneLoneCoder 2019 - 2026
+
+ Version
+ ~~~~~~~
+
+ 3.0 +Initial Release
+
+*/
+
+
+
+#pragma once
+
+#include
+#include
+
+namespace olc::utils
+{
+ template
+ concept RenderSurface = requires(grid g, int x, int y, T t)
+ {
+ { g.set(x, y, t) } -> std::same_as;
+ { g.get(x, y) } -> std::convertible_to;
+ { g.width() } -> std::integral;
+ { g.height() } -> std::integral;
+ };
+
+ template
+ concept lerpable = requires(T a, T b, float t)
+ {
+ { a * t } -> std::convertible_to;
+ { b * t } -> std::convertible_to;
+ { a + b } -> std::convertible_to;
+ };
+
+ template requires lerpable
+ T flerp(const T& a, const T& b, const float t)
+ {
+ return (b * t) + a * (1.0f - t);
+ }
+
+ template requires RenderSurface
+ class SoftwareRenderer
+ {
+
+
+ public:
+ void SetTarget(grid& target)
+ {
+ pTarget = ⌖
+ vScanlines.resize(pTarget->height());
+ }
+
+ void SetWorldTransform(const olc::tf2d& trans)
+ {
+ transformAffine = trans;
+ }
+
+ olc::tf2d& GetWorldTransform()
+ {
+ return transformAffine;
+ }
+
+ void WorldReset()
+ {
+ transformAffine = olc::tf2d();
+ }
+
+ void WorldScale(const olc::vf2d& vScale)
+ {
+ transformAffine.scale(vScale);
+ }
+
+ void WorldOffset(const olc::vf2d& vOffset)
+ {
+ transformAffine.translate(vOffset);
+ }
+
+ void WorldRotate(const float& fTheta, const olc::vf2d& vPoint)
+ {
+ transformAffine.rotate(fTheta, vPoint);
+ }
+
+
+ public:
+
+
+ bool Plot(const olc::vi2d& pos, const T& element)
+ {
+ const olc::vi2d vTransformedPoints = transformAffine.forward(pos);
+ return PlotRaw(vTransformedPoints.x, vTransformedPoints.y, element);
+ }
+
+ bool PlotRaw(const int x, const int y, const T& element)
+ {
+ return pTarget->set(x, y, element);
+ }
+
+ void Clear(const T& element)
+ {
+ for (int y = 0; y < pTarget->height(); y++)
+ {
+ for (int x = 0; x < pTarget->width(); x++)
+ {
+ PlotRaw(x, y, element);
+ }
+ }
+ }
+
+ void Line(const olc::vf2d& p1, const olc::vf2d& p2, const T& element)
+ {
+ const auto vTransformedPoints = transformAffine.forward({ p1, p2 });
+ swRasterShadedLine(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[1]),
+ element, element);
+ }
+
+ void Line(const olc::vf2d& p1, const olc::vf2d& p2, const T& element1, const T& element2)
+ {
+ const auto vTransformedPoints = transformAffine.forward({ p1, p2 });
+ swRasterShadedLine(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[1]),
+ element1, element2);
+ }
+
+ void Rect(const olc::vf2d& pos, const olc::vf2d& size, const T& col)
+ {
+ Rect(pos, size, col, col, col, col);
+ }
+
+ void Rect(const olc::vf2d& pos, const olc::vf2d& size, const T& colTL, const T& colTR, const T& colBL, const T& colBR)
+ {
+ Line(pos, { pos.x + size.x, pos.y }, colTL, colTR);
+ Line({ pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, colTR, colBR);
+ Line({ pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y }, colBR, colBL);
+ Line({ pos.x, pos.y + size.y }, pos, colBL, colTL);
+ }
+
+ void FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const T& col)
+ {
+ FilledRect(pos, size, col, col, col, col);
+ }
+
+ void FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const T& colTL, const T& colTR, const T& colBL, const T& colBR)
+ {
+ const auto vTransformedPoints = transformAffine.forward(
+ { pos,{ pos.x + size.x, pos.y },
+ pos + size,{ pos.x, pos.y + size.y }
+ });
+
+ // Check if all one colour
+ if (colTL == colBL && colTL == colTR && colTL == colBR)
+ {
+ // Check if axis aligned
+ if (vTransformedPoints[0].y == vTransformedPoints[1].y &&
+ vTransformedPoints[1].x == vTransformedPoints[2].x &&
+ vTransformedPoints[2].y == vTransformedPoints[3].y &&
+ vTransformedPoints[3].x == vTransformedPoints[0].x)
+ {
+ // Clip to target
+ olc::vi2d p1 = vTransformedPoints[0].max({ 0,0 });
+ olc::vi2d p2 = vTransformedPoints[2].min(pTarget->Size());
+
+ // Draw filled rectangle
+ for (int32_t y = p1.y; y < p2.y; y++)
+ for (int32_t x = p1.x; x < p2.x; x++)
+ PlotRaw(x, y, colTL);
+
+ // Exit early
+ return;
+ }
+ }
+
+ // Fallback to general case rasteriser, where we split into two triangles
+ swRasterShadedTriangle(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[1]),
+ olc::vi2d(vTransformedPoints[2]),
+ colTL, colTR, colBR);
+ swRasterShadedTriangle(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[2]),
+ olc::vi2d(vTransformedPoints[3]),
+ colTL, colBR, colBL);
+ }
+
+ void TexturedRect(
+ const olc::vf2d& pos,
+ const olc::vf2d& size,
+ olc::Image& texture,
+ const olc::vf2d& t1 = { 0,0 },
+ const olc::vf2d& t2 = { 1,0 },
+ const olc::vf2d& t3 = { 1,1 },
+ const olc::vf2d& t4 = { 0,1 },
+ const T& colTL = olc::Colour::WHITE,
+ const T& colTR = olc::Colour::WHITE,
+ const T& colBL = olc::Colour::WHITE,
+ const T& colBR = olc::Colour::WHITE)
+ {
+ const auto vTransformedPoints = transformAffine.forward(
+ { pos,{ pos.x + size.x, pos.y },
+ pos + size,{ pos.x, pos.y + size.y }
+ });
+
+ swRasterTexturedTriangle(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[1]),
+ olc::vi2d(vTransformedPoints[2]),
+ colTL, colTR, colBR,
+ t1, t2, t3, texture);
+ swRasterTexturedTriangle(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[2]),
+ olc::vi2d(vTransformedPoints[3]),
+ colTL, colBR, colBL,
+ t1, t3, t4, texture);
+ }
+
+
+ void Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const T& col)
+ {
+ Triangle(p1, p2, p3, col, col, col);
+ }
+
+ void Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const T& c1, const T& c2, const T& c3)
+ {
+ Line(p1, p2, c1, c2);
+ Line(p2, p3, c2, c3);
+ Line(p3, p1, c3, c1);
+ }
+
+ void FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const T& col)
+ {
+ FilledTriangle(p1, p2, p3, col, col, col);
+ }
+
+ void FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const T& c1, const T& c2, const T& c3)
+ {
+ const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 });
+ swRasterShadedTriangle(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[1]),
+ olc::vi2d(vTransformedPoints[2]),
+ c1, c2, c3);
+ }
+
+ void TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const T& c1, const T& c2, const T& c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture)
+ {
+ const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 });
+ swRasterTexturedTriangle(
+ olc::vi2d(vTransformedPoints[0]),
+ olc::vi2d(vTransformedPoints[1]),
+ olc::vi2d(vTransformedPoints[2]),
+ c1, c2, c3,
+ t1, t2, t3,
+ texture);
+ }
+
+
+ void Circle(const olc::vf2d& p, float radius, const T& element, uint8_t mask = 0xFF)
+ {
+ const olc::vi2d vTransformedPoint = transformAffine.forwardRound(p);
+ const int fTransformedRadius = int(transformAffine.scale().x * radius);
+
+ if (fTransformedRadius < 0
+ || vTransformedPoint.x < -fTransformedRadius
+ || vTransformedPoint.y < -fTransformedRadius
+ || vTransformedPoint.x - pTarget->width() > fTransformedRadius
+ || vTransformedPoint.y - pTarget->height() > fTransformedRadius)
+ return;
+
+ if (fTransformedRadius > 0)
+ {
+ int x0 = 0;
+ int y0 = int(fTransformedRadius);
+ int d = 3 - 2 * y0;
+
+ while (y0 >= x0) // only formulate 1/8 of circle
+ {
+ // Draw even octants
+ if (mask & 0x01) PlotRaw(vTransformedPoint.x + x0, vTransformedPoint.y - y0, element);// Q6 - upper right right
+ if (mask & 0x04) PlotRaw(vTransformedPoint.x + y0, vTransformedPoint.y + x0, element);// Q4 - lower lower right
+ if (mask & 0x10) PlotRaw(vTransformedPoint.x - x0, vTransformedPoint.y + y0, element);// Q2 - lower left left
+ if (mask & 0x40) PlotRaw(vTransformedPoint.x - y0, vTransformedPoint.y - x0, element);// Q0 - upper upper left
+ if (x0 != 0 && x0 != y0)
+ {
+ if (mask & 0x02) PlotRaw(vTransformedPoint.x + y0, vTransformedPoint.y - x0, element);// Q7 - upper upper right
+ if (mask & 0x08) PlotRaw(vTransformedPoint.x + x0, vTransformedPoint.y + y0, element);// Q5 - lower right right
+ if (mask & 0x20) PlotRaw(vTransformedPoint.x - y0, vTransformedPoint.y + x0, element);// Q3 - lower lower left
+ if (mask & 0x80) PlotRaw(vTransformedPoint.x - x0, vTransformedPoint.y - y0, element);// Q1 - upper left left
+ }
+
+ if (d < 0)
+ d += 4 * x0++ + 6;
+ else
+ d += 4 * (x0++ - y0--) + 10;
+ }
+ }
+ else
+ PlotRaw(vTransformedPoint.x, vTransformedPoint.y, element);
+ }
+
+
+ void FilledCircle(const olc::vf2d& p, float radius, const T& element)
+ {
+ const olc::vi2d vTransformedPoint = transformAffine.forwardRound(p);
+ const int fTransformedRadius = int(transformAffine.scale().x * radius);
+
+ if (fTransformedRadius < 0
+ || vTransformedPoint.x < -fTransformedRadius
+ || vTransformedPoint.y < -fTransformedRadius
+ || vTransformedPoint.x - pTarget->width() > fTransformedRadius
+ || vTransformedPoint.y - pTarget->height() > fTransformedRadius)
+ return;
+
+ if (fTransformedRadius > 0)
+ {
+ int x0 = 0;
+ int y0 = int(fTransformedRadius);
+ int d = 3 - 2 * y0;
+
+ auto drawline = [&](int sx, int ex, int y)
+ {
+ for (int x = sx; x <= ex; x++)
+ PlotRaw(x, y, element);
+ };
+
+ while (y0 >= x0)
+ {
+ drawline(vTransformedPoint.x - y0, vTransformedPoint.x + y0, vTransformedPoint.y - x0);
+ if (x0 > 0) drawline(vTransformedPoint.x - y0, vTransformedPoint.x + y0, vTransformedPoint.y + x0);
+
+ if (d < 0)
+ d += 4 * x0++ + 6;
+ else
+ {
+ if (x0 != y0)
+ {
+ drawline(vTransformedPoint.x - x0, vTransformedPoint.x + x0, vTransformedPoint.y - y0);
+ drawline(vTransformedPoint.x - x0, vTransformedPoint.x + x0, vTransformedPoint.y + y0);
+ }
+ d += 4 * (x0++ - y0--) + 10;
+ }
+ }
+ }
+ else
+ PlotRaw(vTransformedPoint.x, vTransformedPoint.y, element);
+ }
+
+
+
+ protected:
+ // Clips a line to a rectangular region, returns true if line is visible.
+ // The returned weights correspond to distance along the line from v0 to v1
+ bool swClipWeightedLine(olc::vf2d& v0, olc::vf2d& v1, const olc::vf2d& vMin, const olc::vf2d& vMax, float& w0, float& w1)
+ {
+ // Liang-Barsky line clipping algorithm adapted for weighted lines
+ // https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm
+
+ olc::vf2d diff = v1 - v0;
+
+ float p[4] = { -diff.x, diff.x, -diff.y, diff.y };
+
+ float q[4] =
+ {
+ v0.x - vMin.x,
+ vMax.x - v0.x,
+ v0.y - vMin.y,
+ vMax.y - v0.y
+ };
+
+ // Weights are ideal to start, we'll contact them as we clip
+ w0 = 0.0f;
+ w1 = 1.0f;
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (p[i] == 0.0f)
+ {
+ if (q[i] < 0.0f)
+ return false; // Line is parallel and outside the clipping boundary
+ }
+ else
+ {
+ float t = float(q[i]) / float(p[i]);
+ if (p[i] < 0.0f)
+ {
+ if (t > w1)
+ return false; // Line is outside the clipping boundary
+ else if (t > w0)
+ w0 = t;
+ }
+ else
+ {
+ if (t < w0)
+ return false; // Line is outside the clipping boundary
+ else if (t < w1)
+ w1 = t;
+ }
+ }
+ }
+
+ if (w1 < w0)
+ return false; // Line is outside the clipping boundary
+
+ // Return new line segment ends
+ olc::vf2d v = v0;
+ v0 = v + (diff * w0);
+ v1 = v + (diff * w1);
+
+ // Line has visible pixels inside clipping boundary
+ return true;
+ }
+
+ // Rasterises a shaded line in integer space
+ void swRasterShadedLine(const olc::vi2d& v1, const olc::vi2d& v2, const T& c1, const T& c2)
+ {
+ // Lambda to draw a pixel gated by a pattern bit
+ uint32_t pattern = 0xFFFFFFFF;
+ auto rol = [&](void)
+ {
+ pattern = (pattern << 1) | (pattern >> 31);
+ return pattern & 1;
+ };
+
+ // Clip line to draw target
+ olc::vf2d clipped_p1 = v1;
+ olc::vf2d clipped_p2 = v2;
+
+ float w0 = 0, w1 = 1;
+ if (!swClipWeightedLine(clipped_p1, clipped_p2, { 0,0 }, olc::vf2d(float(pTarget->width()), float(pTarget->height())), w0, w1))
+ return;
+
+ // Move to integer space
+ olc::vi2d ip1 = clipped_p1;
+ olc::vi2d ip2 = clipped_p2;
+ olc::vi2d pixel;
+
+ // Calculate deltas
+ int dx = ip2.x - ip1.x;
+ int dy = ip2.y - ip1.y;
+ int absDx = std::abs(dx);
+ int absDy = std::abs(dy);
+
+ // Determine dominant axis
+ bool xMajor = absDx >= absDy;
+ int steps = xMajor ? absDx : absDy;
+
+ // Handle degenerate case (single pixel)
+ if (steps == 0)
+ {
+ PlotRaw(ip1.x, ip1.y, c1);
+ return;
+ }
+
+ // Calculate step increments
+ float xStep = float(dx) / float(steps);
+ float yStep = float(dy) / float(steps);
+ float colorStep = 1.0f / float(steps) * (w1 - w0);
+
+ T cStart = flerp(c1, c2, w0);
+ T cEnd = flerp(c1, c2, w1);
+
+
+
+ // Starting position and color interpolation parameter
+ float x = float(ip1.x);
+ float y = float(ip1.y);
+ float t = 0.0f;
+
+ // Draw line pixel by pixel
+ for (int i = 0; i <= steps; i++)
+ {
+ // Interpolate color
+ //olc::Pixel col = olc::PixelLerp(c1, c2, t);
+ T col = flerp(cStart, cEnd, t);
+
+ // Plot pixel
+ if (rol())
+ PlotRaw((int)std::round(x), (int)std::round(y), col);
+
+ // Step to next pixel
+ x += xStep;
+ y += yStep;
+ t += colorStep;
+ }
+ }
+
+ void swRasterShadedTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3, const T& c1, const T& c2, const T& c3)
+ {
+ // Calculate barycentric outline
+ auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3);
+
+ // Now draw the scanlines
+ for (int32_t y = y_min; y < y_max; y++)
+ {
+ const auto& scanline = vScanlines[y];
+
+ int32_t xStart = scanline.nMin;
+ int32_t xEnd = scanline.nMax;
+
+ int32_t x_min = std::max(0, xStart);
+ int32_t x_max = std::min(xEnd, pTarget->width());
+
+ float fSpan = float(xEnd - xStart);
+ float fSpanStep = fSpan > 0.0f ? 1.0f / fSpan : 0.0f;
+
+ float b0_step = fSpanStep * (scanline.fBaryMax[0] - scanline.fBaryMin[0]);
+ float b1_step = fSpanStep * (scanline.fBaryMax[1] - scanline.fBaryMin[1]);
+ float b2_step = fSpanStep * (scanline.fBaryMax[2] - scanline.fBaryMin[2]);
+
+ float b0 = scanline.fBaryMin[0];
+ float b1 = scanline.fBaryMin[1];
+ float b2 = scanline.fBaryMin[2];
+
+ if (xStart < 0)
+ {
+ b0 = scanline.fBaryMin[0] + (-xStart * b0_step);
+ b1 = scanline.fBaryMin[1] + (-xStart * b1_step);
+ b2 = scanline.fBaryMin[2] + (-xStart * b2_step);
+ }
+
+ for (int32_t x = x_min; x < x_max; x++)
+ {
+ T col = c1 * b0 + c2 * b1 + c3 * b2;
+ PlotRaw(x, y, col);
+ b0 += b0_step;
+ b1 += b1_step;
+ b2 += b2_step;
+ }
+ }
+ }
+
+ void swRasterTexturedTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3, const T& c1, const T& c2, const T& c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, grid& texture)
+ {
+ auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3);
+
+ // Now draw the scanlines
+ for (int32_t y = y_min; y < y_max; y++)
+ {
+ const auto& scanline = vScanlines[y];
+
+ int32_t xStart = scanline.nMin;
+ int32_t xEnd = scanline.nMax;
+
+ int32_t x_min = std::max(0, xStart);
+ int32_t x_max = std::min(xEnd, pTarget->width());
+
+ float fSpan = float(xEnd - xStart);
+ float fSpanStep = fSpan > 0.0f ? 1.0f / fSpan : 0.0f;
+
+ float b0_step = fSpanStep * (scanline.fBaryMax[0] - scanline.fBaryMin[0]);
+ float b1_step = fSpanStep * (scanline.fBaryMax[1] - scanline.fBaryMin[1]);
+ float b2_step = fSpanStep * (scanline.fBaryMax[2] - scanline.fBaryMin[2]);
+
+ float b0 = scanline.fBaryMin[0];
+ float b1 = scanline.fBaryMin[1];
+ float b2 = scanline.fBaryMin[2];
+
+ if (xStart < 0)
+ {
+ b0 = scanline.fBaryMin[0] + (-xStart * b0_step);
+ b1 = scanline.fBaryMin[1] + (-xStart * b1_step);
+ b2 = scanline.fBaryMin[2] + (-xStart * b2_step);
+ }
+
+ float w = float(texture.width());
+ float h = float(texture.height());
+
+ for (int32_t x = x_min; x < x_max; x++)
+ {
+ T col = c1 * b0 + c2 * b1 + c3 * b2;
+
+ olc::vf2d uv = olc::vf2d(
+ b0 * t1.x + b1 * t2.x + b2 * t3.x,
+ b0 * t1.y + b1 * t2.y + b2 * t3.y);
+
+ PlotRaw(x, y, texture.get(int(uv.x * w), int(uv.y * h)));
+ b0 += b0_step;
+ b1 += b1_step;
+ b2 += b2_step;
+ }
+ }
+
+ return;
+ }
+
+
+ protected:
+ grid* pTarget = nullptr;
+ olc::tf2d transformAffine;
+
+ struct Scanline
+ {
+ int32_t nMin = std::numeric_limits::max();
+ int32_t nMax = std::numeric_limits::min();
+ std::array fBaryMin;
+ std::array fBaryMax;
+ };
+ std::vector vScanlines;
+
+ // Fills scanline buffer with visible triangle extents and barycentric coordinates.
+ // Returns vertical, visible extents of triangle scanlines
+ std::pair swBaryFillTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3)
+ {
+ // Get height of triangle in whole pixels
+ int32_t nMinY = std::min({ v1.y, v2.y, v3.y });
+ int32_t nMaxY = std::max({ v1.y, v2.y, v3.y });
+ int32_t nHeight = nMaxY - nMinY;
+
+ if (nHeight <= 0)
+ return { 0, 0 }; // Degenerate triangle
+
+ // Scanline buffer is already allocated to be the max vertical size
+ // of the draw target. Obviously it only represents visible scanlines
+ // that are to be filled for the current triangle.
+
+ // Get visible height of triangle
+ int32_t y_min = std::max(0, nMinY);
+ int32_t y_max = std::min(nMaxY, pTarget->height());
+
+ // Zero out scanline buffer (by resetting min and max values)
+ for (int32_t y = y_min; y < y_max; y++)
+ {
+ vScanlines[y].nMin = std::numeric_limits::max();
+ vScanlines[y].nMax = std::numeric_limits::min();
+ }
+
+ // This function scans an edge of the triangle, updating
+ // the scanline buffer with min/max extents and barycentric coords.
+ // It returns the number of scanlines updated.
+ auto scanEdge = [&](olc::vi2d p0, olc::vi2d p1, int id1, int id2) -> size_t
+ {
+ if (p0.y == p1.y)
+ return 0;
+
+ // Ensure p0.y < p1.y
+ bool swapped = false;
+ if (p0.y > p1.y)
+ {
+ std::swap(p0, p1);
+ swapped = true;
+ }
+
+ // Cache edge step deltas
+ int dy = p1.y - p0.y;
+ float dx_step = (p1.x - p0.x) / float(dy);
+ float dy_step = 1.0f / float(dy);
+ float x = float(p0.x);
+
+ // Rasterise edge - if pixel lies on visible scanline then
+ // update the scanline bounds and barycentric coords
+ size_t nScanline = 0;
+ for (int y = p0.y; y <= p1.y; y++)
+ {
+ // If this pixel row is visible
+ if (y >= 0 && y < vScanlines.size())
+ {
+ int ix = int(std::round(x));
+
+ // interpolation along edge
+ // Note: We may need to do this differently when clipping
+ float t = (y - p0.y) * dy_step;
+
+ std::array bary = { 0.0f, 0.0f, 0.0f };
+
+ // Set barycentric coords depending on edge direction
+ if (swapped)
+ {
+ bary[id1] = t;
+ bary[id2] = 1.0f - t;
+ }
+ else
+ {
+ bary[id1] = 1.0f - t;
+ bary[id2] = t;
+ }
+
+ // Update scanline extents and barycentric coords
+ if (ix < vScanlines[y].nMin)
+ {
+ vScanlines[y].nMin = ix;
+ vScanlines[y].fBaryMin = bary;
+ }
+
+ if (ix > vScanlines[y].nMax)
+ {
+ vScanlines[y].nMax = ix;
+ vScanlines[y].fBaryMax = bary;
+ }
+
+ nScanline++;
+ }
+
+ x += dx_step;
+ }
+
+ return nScanline;
+ };
+
+ // Rasterise triangle edges into scanline buffer
+ scanEdge(v1, v2, 0, 1);
+ scanEdge(v1, v3, 0, 2);
+ scanEdge(v2, v3, 1, 2);
+
+ return { y_min, y_max };
+ }
+
+ };
+
+
+ typedef SoftwareRenderer SWDraw;
+}
\ No newline at end of file