diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9dba274..f907cb3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -14,11 +14,11 @@ jobs:
ROOT: ./src
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: 📂 Setup .NET Core
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: 8.0.x
+ dotnet-version: 10.0.x
- name: 📂 Files
working-directory: ${{env.ROOT}}
@@ -28,7 +28,7 @@ jobs:
working-directory: ${{env.ROOT}}/RenderBox
run: dotnet publish -p:PublishProfile=FolderProfile -c Release -o out
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v4
if: github.ref == 'refs/heads/master'
with:
name: builds
@@ -45,17 +45,17 @@ jobs:
NUGET_AUTH_TOKEN: ${{secrets.token}}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- - uses: actions/download-artifact@v2
+ - uses: actions/download-artifact@v4
with:
name: builds
path: ${{env.ROOT}}
- name: 📂 Setup .NET Core
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: 8.0.x
+ dotnet-version: 10.0.x
- name: 📂 Pack RenderBox
working-directory: ${{env.ROOT}}/RenderBox/out
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..34cc7a1
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "10.0.300",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/src/RenderBox.Benchmarks/RenderBox.Benchmarks.csproj b/src/RenderBox.Benchmarks/RenderBox.Benchmarks.csproj
index e14e43d..4c16c89 100644
--- a/src/RenderBox.Benchmarks/RenderBox.Benchmarks.csproj
+++ b/src/RenderBox.Benchmarks/RenderBox.Benchmarks.csproj
@@ -2,13 +2,13 @@
Exe
- net8.0
+ net10.0
enable
enable
-
+
diff --git a/src/RenderBox.Shared/Core/Color.cs b/src/RenderBox.Shared/Core/Color.cs
index b6341f2..ea98286 100644
--- a/src/RenderBox.Shared/Core/Color.cs
+++ b/src/RenderBox.Shared/Core/Color.cs
@@ -76,7 +76,7 @@ private static byte GetChannel(float n)
public override int GetHashCode() => GetRaw();
[MethodImpl(Runtime.IMPL_OPTIONS)]
- public override bool Equals(object obj)
+ public override bool Equals(object? obj)
{
if (obj is Color color)
{
diff --git a/src/RenderBox.Shared/Core/Vector.cs b/src/RenderBox.Shared/Core/Vector.cs
index a75b6be..800430a 100644
--- a/src/RenderBox.Shared/Core/Vector.cs
+++ b/src/RenderBox.Shared/Core/Vector.cs
@@ -85,12 +85,8 @@ public Vector2(float x) : this(x, x) { }
public float Length => MathHelpres.FastSqrt(x * x + y * y);
[MethodImpl(Runtime.IMPL_OPTIONS)]
- public override bool Equals(object obj)
- => EqualsInternal(obj as Vector2?);
-
- [MethodImpl(Runtime.IMPL_OPTIONS)]
- private bool EqualsInternal(Vector2? vector)
- => vector.HasValue && vector.Value == this;
+ public override bool Equals(object? obj)
+ => obj is Vector2 vector && vector == this;
public override int GetHashCode() => HashCode.Combine(x, y);
@@ -177,12 +173,8 @@ public Vector3(float x) : this(x, x, x) { }
public float Length => MathHelpres.FastSqrt(x * x + y * y + z * z);
[MethodImpl(Runtime.IMPL_OPTIONS)]
- public override bool Equals(object obj)
- => EqualsInternal(obj as Vector3?);
-
- [MethodImpl(Runtime.IMPL_OPTIONS)]
- private bool EqualsInternal(Vector3? vector)
- => vector.HasValue && vector.Value == this;
+ public override bool Equals(object? obj)
+ => obj is Vector3 vector && vector == this;
public override int GetHashCode() => HashCode.Combine(x, y, z);
}
@@ -289,15 +281,138 @@ public float this[int i]
public static bool operator !=(Quaternion a, Quaternion b) => !(a == b);
- public override bool Equals(object obj)
- => EqualsInternal(obj as Quaternion?);
-
- private bool EqualsInternal(Quaternion? vector)
- => vector.HasValue && vector.Value == this;
+ public override bool Equals(object? obj)
+ => obj is Quaternion vector && vector == this;
public override int GetHashCode() => HashCode.Combine(M);
}
+ public struct Matrix4x4
+ {
+ private readonly float[] M;
+
+ public static Matrix4x4 Identity => new(true);
+
+ public Matrix4x4(bool identity) : this()
+ {
+ M = new float[16];
+
+ if (!identity) return;
+
+ M[0] = 1.0f;
+ M[5] = 1.0f;
+ M[10] = 1.0f;
+ M[15] = 1.0f;
+ }
+
+ public float this[int i]
+ {
+ get => M[i];
+ set => M[i] = value;
+ }
+
+ public static Matrix4x4 CreateRotation(Vector3 rotation)
+ {
+ return CreateRotationZ(rotation.z) * CreateRotationY(rotation.y) * CreateRotationX(rotation.x);
+ }
+
+ public static Matrix4x4 CreateRotationX(float angle)
+ {
+ var result = Identity;
+ var sin = MathF.Sin(angle);
+ var cos = MathF.Cos(angle);
+
+ result[5] = cos;
+ result[6] = sin;
+ result[9] = -sin;
+ result[10] = cos;
+
+ return result;
+ }
+
+ public static Matrix4x4 CreateRotationY(float angle)
+ {
+ var result = Identity;
+ var sin = MathF.Sin(angle);
+ var cos = MathF.Cos(angle);
+
+ result[0] = cos;
+ result[2] = -sin;
+ result[8] = sin;
+ result[10] = cos;
+
+ return result;
+ }
+
+ public static Matrix4x4 CreateRotationZ(float angle)
+ {
+ var result = Identity;
+ var sin = MathF.Sin(angle);
+ var cos = MathF.Cos(angle);
+
+ result[0] = cos;
+ result[1] = sin;
+ result[4] = -sin;
+ result[5] = cos;
+
+ return result;
+ }
+
+ public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b)
+ {
+ var result = new Matrix4x4(false);
+
+ for (var row = 0; row < 4; row++)
+ {
+ for (var column = 0; column < 4; column++)
+ {
+ var value = 0f;
+ for (var i = 0; i < 4; i++)
+ {
+ value += a[row + i * 4] * b[i + column * 4];
+ }
+
+ result[row + column * 4] = value;
+ }
+ }
+
+ return result;
+ }
+
+ public Matrix4x4 Transpose()
+ {
+ var result = new Matrix4x4(false);
+
+ for (var row = 0; row < 4; row++)
+ {
+ for (var column = 0; column < 4; column++)
+ {
+ result[row + column * 4] = M[column + row * 4];
+ }
+ }
+
+ return result;
+ }
+
+ public Vector3 TransformPoint(Vector3 point)
+ {
+ return Transform(point, 1);
+ }
+
+ public Vector3 TransformDirection(Vector3 direction)
+ {
+ return Transform(direction, 0);
+ }
+
+ private Vector3 Transform(Vector3 vector, float w)
+ {
+ return new Vector3(
+ M[0] * vector.x + M[4] * vector.y + M[8] * vector.z + M[12] * w,
+ M[1] * vector.x + M[5] * vector.y + M[9] * vector.z + M[13] * w,
+ M[2] * vector.x + M[6] * vector.y + M[10] * vector.z + M[14] * w);
+ }
+ }
+
public static class VectorMath
{
#region Vector2
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Camera.cs b/src/RenderBox.Shared/Modules/PathTracer/Camera.cs
index d71d6cc..7cd8874 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Camera.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Camera.cs
@@ -5,6 +5,7 @@ namespace RenderBox.Shared.Modules.PathTracer
public class Camera
{
public Vector3 Position { get; set; }
+ public Vector3 Rotation { get; set; }
public Vector3 Target { get; set; }
public int MaxBounceDepth { get; set; } = 3;
public float FOV { get; set; } = 90;
@@ -21,8 +22,13 @@ public Camera(Vector3 position)
Target = new Vector3(0.0, 0.0, 0.0);
Position = position;
+ Rotation = Vector3.Zero;
+ ViewMatrix = new Quaternion(true);
+ PosMatrix = new Quaternion(true);
BiasMatrix = GetBiasMatrixInverse();
+ ViewPosMatrix = new Quaternion(true);
+ RayMatrix = new Quaternion(true);
}
public void LookAt(Vector3 position, Vector3 target, bool rotateAround)
@@ -52,6 +58,27 @@ public void CalculateRayMatrix()
RayMatrix = ViewMatrix * PosMatrix * BiasMatrix * ViewPosMatrix;
}
+ public Vector3 TransformDirection(Vector3 direction)
+ {
+ return Matrix4x4.CreateRotation(Rotation).TransformDirection(direction);
+ }
+
+ public Camera SetRotation(Vector3 rotation)
+ {
+ Rotation = rotation;
+ return this;
+ }
+
+ public Camera SetRotationDegrees(Vector3 rotation)
+ {
+ Rotation = new Vector3(
+ MathHelpres.DegToRad(rotation.x),
+ MathHelpres.DegToRad(rotation.y),
+ MathHelpres.DegToRad(rotation.z));
+
+ return this;
+ }
+
private Quaternion GetBiasMatrix()
{
var B = new Quaternion(false);
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Light.cs b/src/RenderBox.Shared/Modules/PathTracer/Light.cs
index 9819a13..70cb018 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Light.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Light.cs
@@ -11,7 +11,7 @@ public class Light
public float LinearAttenuation { get; set; }
public float QuadraticAttenuation { get; set; }
- public IShape Shape { get; set; }
+ public Shape? Shape { get; set; }
public Light(Color color, double intensity)
{
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Ray.cs b/src/RenderBox.Shared/Modules/PathTracer/Ray.cs
index 8c06fb2..4e88e51 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Ray.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Ray.cs
@@ -23,7 +23,7 @@ public struct Hit
{
public Vector3 Position { get; set; }
public Vector3 Normal { get; set; }
- public Shape HitObject { get; set; }
+ public Shape? HitObject { get; set; }
public bool IsHitting => HitObject != null;
}
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Scene.cs b/src/RenderBox.Shared/Modules/PathTracer/Scene.cs
index a50ef93..ee5474e 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Scene.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Scene.cs
@@ -7,7 +7,7 @@ public class Scene
public Color BackgroundColor { get; set; } = new Color(.2f, .2f, .2f);
public Color AmbientColor { get; set; } = new Color(.1f, .1f, .1f);
public List Shapes { get; set; } = new List();
- public IEnumerable Lights { get; private set; }
+ public IEnumerable Lights { get; private set; } = Array.Empty();
public bool LightingEnabled { get; set; } = true;
public bool ShadowsEnabled { get; set; } = true;
@@ -22,7 +22,7 @@ public Scene()
public void UpdateLights()
{
- Lights = Shapes.Where(x => x.Light != null).Select(x => x.Light).ToArray();
+ Lights = Shapes.Select(x => x.Light).Where(x => x?.Shape is not null).Cast().ToArray();
}
}
}
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Scenes/CornellBox.cs b/src/RenderBox.Shared/Modules/PathTracer/Scenes/CornellBox.cs
index 50befae..75b247e 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Scenes/CornellBox.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Scenes/CornellBox.cs
@@ -50,11 +50,11 @@ public CornellBox()
new Sphere(new Vector3(1.8, 0, 0), .2f, Color.White),
- new Box(new Vector3(0, -1.5, -1), Color.Yellow),
- new Box(new Vector3(1, -1.5, -1), Color.Red),
- new Box(new Vector3(-1, -1.5, -1), Color.Blue),
+ new Box(new Vector3(0, -1.5, -1), Color.Yellow).SetRotationDegrees(new Vector3(0, 35, 0)),
+ new Box(new Vector3(1, -1.5, -1), Color.Red).SetRotationDegrees(new Vector3(0, -25, 0)),
+ new Box(new Vector3(-1, -1.5, -1), Color.Blue).SetRotationDegrees(new Vector3(0, 0, 25)),
- new Box(new Vector3(-1.6, -1.9, 1.4), Color.White, new Vector3(0.24f, 0.24f, 0.24f)) { Material = metal },
+ new Box(new Vector3(-1.6, -1.9, 1.4), Color.White, new Vector3(0.24f, 0.24f, 0.24f)) { Material = metal }.SetRotationDegrees(new Vector3(0, 45, 0)),
});
UpdateLights();
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Shape.cs b/src/RenderBox.Shared/Modules/PathTracer/Shape.cs
index b37c8f9..c8aee6e 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Shape.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Shape.cs
@@ -14,12 +14,14 @@ public interface IShape
public abstract class Shape : IShape
{
public Vector3 Position { get; set; }
+ public Vector3 Rotation { get; set; }
public Material Material { get; set; }
- public Light Light { get; private set; }
+ public Light? Light { get; private set; }
public Shape(Vector3 pos, Color diffuse)
{
Position = pos;
+ Rotation = Vector3.Zero;
//
Material = new Material
{
@@ -35,6 +37,45 @@ public Shape SetLight(Light light)
return this;
}
+ public Shape SetRotation(Vector3 rotation)
+ {
+ Rotation = rotation;
+ return this;
+ }
+
+ public Shape SetRotationDegrees(Vector3 rotation)
+ {
+ Rotation = new Vector3(
+ MathHelpres.DegToRad(rotation.x),
+ MathHelpres.DegToRad(rotation.y),
+ MathHelpres.DegToRad(rotation.z));
+
+ return this;
+ }
+
+ protected Matrix4x4 RotationMatrix => Matrix4x4.CreateRotation(Rotation);
+ protected Matrix4x4 InverseRotationMatrix => RotationMatrix.Transpose();
+
+ protected Vector3 LocalToWorldPoint(Vector3 point)
+ {
+ return Position + RotationMatrix.TransformPoint(point);
+ }
+
+ protected Vector3 WorldToLocalPoint(Vector3 point)
+ {
+ return InverseRotationMatrix.TransformPoint(point - Position);
+ }
+
+ protected Vector3 LocalToWorldDirection(Vector3 direction)
+ {
+ return RotationMatrix.TransformDirection(direction);
+ }
+
+ protected Vector3 WorldToLocalDirection(Vector3 direction)
+ {
+ return InverseRotationMatrix.TransformDirection(direction);
+ }
+
public abstract Vector3 CalcNormal(Vector3 pos);
public abstract bool GetIntersection(Ray ray, double maxDistance, out Hit hit, out double distance);
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Shapes/Box.cs b/src/RenderBox.Shared/Modules/PathTracer/Shapes/Box.cs
index 508793a..5a11c89 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Shapes/Box.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Shapes/Box.cs
@@ -57,7 +57,7 @@ public Box(Vector3 position, Color diffuse, Vector3? scale = null) : base(positi
public override Vector3 GetLightEmission(Vector3 random)
{
- return Scale * random;
+ return LocalToWorldDirection(Scale * random);
}
}
}
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Shapes/Mesh.cs b/src/RenderBox.Shared/Modules/PathTracer/Shapes/Mesh.cs
index 8461c45..205e42a 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Shapes/Mesh.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Shapes/Mesh.cs
@@ -18,6 +18,9 @@ public Mesh(Vector3 position, Color diffuse) : base(position, diffuse)
public override bool GetIntersection(Ray ray, double maxDistance, out Hit hit, out double distance)
{
hit = new Hit();
+ distance = double.PositiveInfinity;
+
+ var localRay = new Ray(WorldToLocalPoint(ray.Origin), WorldToLocalDirection(ray.Direction));
for (int k = 0; k < TrianglesCount; ++k)
{
@@ -25,33 +28,39 @@ public override bool GetIntersection(Ray ray, double maxDistance, out Hit hit, o
var v1 = Vertices[Indices[k * 3 + 1]];
var v2 = Vertices[Indices[k * 3 + 2]];
- if (RayTriangleIntersect(v0, v1, v2, ray, out var localHit))
+ if (RayTriangleIntersect(v0, v1, v2, localRay, out var localHit))
{
if (localHit.IsHitting)
{
- var dist = (localHit.Position - ray.Origin).Length;
+ var worldPosition = LocalToWorldPoint(localHit.Position);
+ var dist = (worldPosition - ray.Origin).Length;
if (dist > maxDistance)
{
continue;
}
- hit = localHit;
+ if (dist < distance)
+ {
+ localHit.Position = worldPosition;
+ localHit.Normal = Normalize(LocalToWorldDirection(localHit.Normal));
+ hit = localHit;
+ distance = dist;
+ }
}
}
}
- distance = (hit.Position - ray.Origin).Length;
return hit.IsHitting;
}
public override Vector3 CalcNormal(Vector3 pos)
{
- return Normalize(pos - Position);
+ return Normalize(LocalToWorldDirection(Normalize(WorldToLocalPoint(pos))));
}
public void SetData(IEnumerable verts, List indices)
{
- Vertices = verts.Select(x => Position + x).ToList();
+ Vertices = verts.ToList();
Indices = indices;
TrianglesCount = indices.Count / 3;
}
diff --git a/src/RenderBox.Shared/Modules/PathTracer/Shapes/Sphere.cs b/src/RenderBox.Shared/Modules/PathTracer/Shapes/Sphere.cs
index 5940535..853ce5a 100644
--- a/src/RenderBox.Shared/Modules/PathTracer/Shapes/Sphere.cs
+++ b/src/RenderBox.Shared/Modules/PathTracer/Shapes/Sphere.cs
@@ -16,10 +16,11 @@ public override bool GetIntersection(Ray ray, double maxDistance, out Hit hit, o
{
hit = new Hit();
- var delta = ray.Origin - Position;
+ var localRay = new Ray(WorldToLocalPoint(ray.Origin), WorldToLocalDirection(ray.Direction));
+ var delta = localRay.Origin;
- var a = Dot(ray.Direction, ray.Direction);
- var b = 2 * Dot(ray.Direction, delta);
+ var a = Dot(localRay.Direction, localRay.Direction);
+ var b = 2 * Dot(localRay.Direction, delta);
var c = Dot(delta, delta) - Radius * Radius;
double dt = b * b - 4 * a * c;
@@ -38,7 +39,8 @@ public override bool GetIntersection(Ray ray, double maxDistance, out Hit hit, o
return false;
}
- var position = ray.Origin + ray.Direction * (float)D;
+ var localPosition = localRay.Origin + localRay.Direction * (float)D;
+ var position = LocalToWorldPoint(localPosition);
var dist = Distance(position, ray.Origin);
if (dist > maxDistance)
@@ -48,7 +50,7 @@ public override bool GetIntersection(Ray ray, double maxDistance, out Hit hit, o
}
hit.Position = position;
- hit.Normal = CalcNormal(hit.Position);
+ hit.Normal = CalcNormal(position);
hit.HitObject = this;
distance = dist;
@@ -58,12 +60,12 @@ public override bool GetIntersection(Ray ray, double maxDistance, out Hit hit, o
public override Vector3 CalcNormal(Vector3 pos)
{
- return Normalize(pos - Position);
+ return Normalize(LocalToWorldDirection(Normalize(WorldToLocalPoint(pos))));
}
public override Vector3 GetLightEmission(Vector3 random)
{
- return Normalize(random) * Radius;
+ return LocalToWorldDirection(Normalize(random) * Radius);
}
}
}
diff --git a/src/RenderBox.Shared/RenderBox.Shared.csproj b/src/RenderBox.Shared/RenderBox.Shared.csproj
index 2d6680a..54c936c 100644
--- a/src/RenderBox.Shared/RenderBox.Shared.csproj
+++ b/src/RenderBox.Shared/RenderBox.Shared.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
true
enable
enable
@@ -18,10 +18,10 @@
-
-
-
-
+
+
+
+
diff --git a/src/RenderBox.sln b/src/RenderBox.sln
index 9e1dcc2..44627d7 100644
--- a/src/RenderBox.sln
+++ b/src/RenderBox.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29411.108
+# Visual Studio Version 18
+VisualStudioVersion = 18.0.0.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderBox", "RenderBox\RenderBox.csproj", "{90B35404-D527-48B2-A12C-0A9A9097672D}"
EndProject
diff --git a/src/RenderBox/RenderBox.csproj b/src/RenderBox/RenderBox.csproj
index ccc4e98..1e1e04b 100644
--- a/src/RenderBox/RenderBox.csproj
+++ b/src/RenderBox/RenderBox.csproj
@@ -2,7 +2,7 @@
WinExe
- net8.0-windows
+ net10.0-windows
true
Resources\RenderBoxLogo.ico
true
@@ -33,4 +33,4 @@
-
\ No newline at end of file
+
diff --git a/src/RenderBox/Services/Renderers/MandelbrotRenderer.cs b/src/RenderBox/Services/Renderers/MandelbrotRenderer.cs
index b985ec4..0c1c8d3 100644
--- a/src/RenderBox/Services/Renderers/MandelbrotRenderer.cs
+++ b/src/RenderBox/Services/Renderers/MandelbrotRenderer.cs
@@ -18,7 +18,7 @@ public class MandelbrotRenderer : Renderer
public int Iterations { get; set; } = 100;
public double Extent { get; set; } = 2;
- public IPaletteFilter Filter { get; set; }
+ public IPaletteFilter? Filter { get; set; }
public MandelbrotRenderer(Paint paint) : base(paint)
{
diff --git a/src/RenderBox/Services/Renderers/PathTraceRenderer.cs b/src/RenderBox/Services/Renderers/PathTraceRenderer.cs
index 6b4a672..d72d61b 100644
--- a/src/RenderBox/Services/Renderers/PathTraceRenderer.cs
+++ b/src/RenderBox/Services/Renderers/PathTraceRenderer.cs
@@ -87,7 +87,7 @@ int GetRenderPriority(int x, int y)
var posX = (2 * (x + 0.5f) / width - 1) * aspectRatio * fovScale;
var posY = (1 - 2 * (y + 0.5f) / height) * fovScale;
//
- var dir = Normalize(new Vector3(posX, posY, -1));
+ var dir = Normalize(camera.TransformDirection(new Vector3(posX, posY, -1)));
var ray = new Ray(orig, dir);
//
var color = TracePath(context, camera, ray, Scene.BackgroundColor);
@@ -127,7 +127,7 @@ int GetRenderPriority(int x, int y)
}
}
- private Color TracePath(RenderContext context, Camera camera, Ray ray, Color back, int depth = 0, Shape currentShape = null)
+ private Color TracePath(RenderContext context, Camera camera, Ray ray, Color back, int depth = 0, Shape? currentShape = null)
{
if (Mode == RenderMode.Time)
{
@@ -148,7 +148,8 @@ private Color TracePath(RenderContext context, Camera camera, Ray ray, Color bac
return back; // Nothing was hit
}
- var material = hit.HitObject.Material;
+ var hitObject = hit.HitObject ?? throw new InvalidOperationException("A hit must include the shape it intersected.");
+ var material = hitObject.Material;
var emittance = material.Color; //material.emittance;
var position = hit.Position;
@@ -177,7 +178,7 @@ private Color TracePath(RenderContext context, Camera camera, Ray ray, Color bac
return new Color(x, y, z);
}
- if (hit.HitObject.Light != null)
+ if (hitObject.Light != null)
{
return emittance;
}
@@ -192,7 +193,7 @@ private Color TracePath(RenderContext context, Camera camera, Ray ray, Color bac
var newRayDirection = Refract(ray.Direction, normal, material.RefractionEta);
var newRay = new Ray(position, newRayDirection);
- var incoming = TracePath(context, camera, newRay, back, depth + 1, hit.HitObject);
+ var incoming = TracePath(context, camera, newRay, back, depth + 1, hitObject);
var refractedColor = incoming; //emittance * incoming;
@@ -285,6 +286,7 @@ private Color Enlight(Color emittance, Hit hit)
private Color LightIntensity(Hit hit, Light light, Vector3 lightPosition, Color ambientColor, float ambientFactor)
{
+ var hitObject = hit.HitObject ?? throw new InvalidOperationException("A hit must include the shape it intersected.");
var lightColor = light.Color;
var lightDirection = lightPosition - hit.Position;
@@ -302,25 +304,25 @@ private Color LightIntensity(Hit hit, Light light, Vector3 lightPosition, Color
if (ndotLD > 0)
{
- if (!IsShadow(hit.HitObject, lightPosition, lightDirection, (float)lightDistance))
+ if (!IsShadow(hitObject, lightPosition, lightDirection, (float)lightDistance))
{
var linghtnessMul = (ambientColor * ambientFactor + lightColor * ndotLD) / attenuation;
return lightColor * linghtnessMul;
}
}
- var refraction = hit.HitObject.Material.Refraction;
+ var refraction = hitObject.Material.Refraction;
if (refraction > 0)
{
- lightColor *= hit.HitObject.Material.Color;
+ lightColor *= hitObject.Material.Color;
}
var ambientMul = (ambientColor * ambientFactor + lightColor * ndotLD * refraction) / attenuation;
return lightColor * ambientMul;
}
- private bool IsShadow(Shape currentShape, Vector3 lightPosition, Vector3 lightDirection, float lightDistance)
+ private bool IsShadow(Shape? currentShape, Vector3 lightPosition, Vector3 lightDirection, float lightDistance)
{
if (!Scene.ShadowsEnabled)
{
@@ -374,7 +376,7 @@ private float CalcAmbientOcclusion(Hit hit)
return 1f - (factor / Scene.GISamples) * 4f;
}
- private Hit FindClosestHit(Ray ray, float maxDistance, Shape currentShape = null)
+ private Hit FindClosestHit(Ray ray, float maxDistance, Shape? currentShape = null)
{
var closestHit = new Hit();
@@ -415,7 +417,17 @@ public override void OnKeyPress(Key key, Action onRender)
if (key == Key.W) MainCamera.Position += Vector3.Back * 0.5f;
if (key == Key.S) MainCamera.Position += Vector3.Forward * 0.5f;
- if (origPos != MainCamera.Position)
+ var origRotation = MainCamera.Rotation;
+ var rotationStep = (float)MathHelpres.DegToRad(5);
+
+ if (key == Key.Left) MainCamera.Rotation += Vector3.Up * rotationStep;
+ if (key == Key.Right) MainCamera.Rotation += Vector3.Down * rotationStep;
+ if (key == Key.Up) MainCamera.Rotation += Vector3.Left * rotationStep;
+ if (key == Key.Down) MainCamera.Rotation += Vector3.Right * rotationStep;
+ if (key == Key.Z) MainCamera.Rotation += Vector3.Forward * rotationStep;
+ if (key == Key.X) MainCamera.Rotation += Vector3.Back * rotationStep;
+
+ if (origPos != MainCamera.Position || origRotation != MainCamera.Rotation)
{
onRender();
}
diff --git a/src/RenderBox/Services/Rendering/Paint.cs b/src/RenderBox/Services/Rendering/Paint.cs
index 3aea96c..dfb24f6 100644
--- a/src/RenderBox/Services/Rendering/Paint.cs
+++ b/src/RenderBox/Services/Rendering/Paint.cs
@@ -11,7 +11,7 @@ public class Paint : IDisposable
public const int DPI = 96;
public Image Image { get; private set; }
- public WriteableBitmap Bitmap { get; private set; }
+ public WriteableBitmap? Bitmap { get; private set; }
public int Width => Bitmap?.PixelWidth ?? 0;
public int Height => Bitmap?.PixelHeight ?? 0;
public double Scale { get; private set; }
@@ -45,7 +45,6 @@ private void CreateBitmap(Image img, int width, int height)
var bitmap = new WriteableBitmap(width, height, DPI, DPI, PixelFormats.Bgr32, null);
img.Source = bitmap;
- Bitmap = null;
Bitmap = bitmap;
}
diff --git a/src/RenderBox/Services/Rendering/Renderer.cs b/src/RenderBox/Services/Rendering/Renderer.cs
index ee5a984..568442b 100644
--- a/src/RenderBox/Services/Rendering/Renderer.cs
+++ b/src/RenderBox/Services/Rendering/Renderer.cs
@@ -13,18 +13,18 @@ public abstract class Renderer : IDisposable
public int BatchSize { get; set; } = 32;
public Paint Paint { get; private set; }
- public event RenderStartHandler OnRenderStarted;
- public event RenderCompleteHandler OnRenderComplete;
+ public event RenderStartHandler? OnRenderStarted;
+ public event RenderCompleteHandler? OnRenderComplete;
- private Thread _renderThread;
+ private Thread? _renderThread;
public Renderer(Paint paint)
{
Paint = paint;
}
- public Type GetOptionPageType()
+ public Type? GetOptionPageType()
{
var type = GetType();
var optionsType = typeof(IOptionsPage<>);
@@ -114,7 +114,7 @@ public virtual RenderContext BuildContext(Dispatcher dispatcher)
protected virtual void BatchScreen(RenderContext context,
RenderScreenBatch renderScreenBatch,
- GetRenderPriority getRenderPriority = null)
+ GetRenderPriority? getRenderPriority = null)
{
using var threadManager = new ThreadManager();
diff --git a/src/RenderBox/Services/Rendering/ThreadManager.cs b/src/RenderBox/Services/Rendering/ThreadManager.cs
index 4470f1e..926408a 100644
--- a/src/RenderBox/Services/Rendering/ThreadManager.cs
+++ b/src/RenderBox/Services/Rendering/ThreadManager.cs
@@ -7,8 +7,8 @@ public class ThreadManager : IDisposable
public ThreadManagerState State { get; private set; } = ThreadManagerState.Created;
private ConcurrentQueue _queue = new ConcurrentQueue();
- private Thread[] _pool;
- private Action _onDone;
+ private Thread[] _pool = Array.Empty();
+ private Action? _onDone;
private int _endedThreads = 0;
private struct Routine
@@ -70,18 +70,12 @@ private void SortQueue()
private void Kill()
{
- if (_pool is null) return;
-
_queue.Clear();
_onDone = null;
for (var i = 0; i < _pool.Length; i++)
{
- if (_pool[i] != null)
- {
- _pool[i].Interrupt();
- _pool[i] = null;
- }
+ _pool[i].Interrupt();
}
}
@@ -95,7 +89,7 @@ private void ThreadProcess()
{
if (_queue.TryDequeue(out var routine))
{
- routine.Action?.Invoke();
+ routine.Action.Invoke();
}
if (_queue.IsEmpty)
diff --git a/src/RenderBox/Views/MainWindow.xaml.cs b/src/RenderBox/Views/MainWindow.xaml.cs
index 4f33577..3104e5b 100644
--- a/src/RenderBox/Views/MainWindow.xaml.cs
+++ b/src/RenderBox/Views/MainWindow.xaml.cs
@@ -67,7 +67,7 @@ private void OnTabsSelectionChanged(object sender, SelectionChangedEventArgs e)
}
}
- private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
+ private void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e)
{
var renderers = _pages.Select(x => x.Value).Select(x => x.Renderer).ToArray();
diff --git a/src/RenderBox/Views/Pages/CanvasPage.xaml.cs b/src/RenderBox/Views/Pages/CanvasPage.xaml.cs
index 6edec06..5fe367e 100644
--- a/src/RenderBox/Views/Pages/CanvasPage.xaml.cs
+++ b/src/RenderBox/Views/Pages/CanvasPage.xaml.cs
@@ -6,7 +6,6 @@
using System.Windows.Input;
using RenderBox.Services.Options;
using RenderBox.Services.Rendering;
-using RenderBox.Shared.Modules.PathTracer;
namespace RenderBox.Views.Pages
{
@@ -15,9 +14,7 @@ public partial class CanvasPage : Page, IDisposable
public bool IsActive { get; set; }
public bool IsStarted { get; private set; }
- public Renderer Renderer { get; set; }
- public Camera MainCamera { get; set; }
- public Scene Scene { get; set; }
+ public Renderer? Renderer { get; private set; }
private readonly ObservableCollection _log;
private readonly Stopwatch _timer = new();
@@ -64,6 +61,11 @@ public void Dispose()
private void OnLoaded(object sender, RoutedEventArgs e)
{
var window = Window.GetWindow(this);
+ if (window is null)
+ {
+ return;
+ }
+
window.KeyUp += OnKeyPress;
//
SidePanel.Visibility = Visibility.Hidden;
@@ -71,7 +73,7 @@ private void OnLoaded(object sender, RoutedEventArgs e)
SizeChanged += OnSizeChanged;
}
- private void SetupRender(Type type)
+ private void SetupRender(Type? type)
{
var scale = Resolution.Value;
var w = (int)(ActualWidth * scale);
@@ -79,7 +81,17 @@ private void SetupRender(Type type)
if (Renderer == null)
{
- Renderer = (Renderer)Activator.CreateInstance(type, new Paint(Image, w, h, scale));
+ if (type is null)
+ {
+ throw new InvalidOperationException("A renderer type is required before the first render.");
+ }
+
+ if (Activator.CreateInstance(type, new Paint(Image, w, h, scale)) is not Renderer renderer)
+ {
+ throw new InvalidOperationException($"Could not create renderer '{type.FullName}'.");
+ }
+
+ Renderer = renderer;
Renderer.OnRenderStarted += () => _timer.Restart();
Renderer.OnRenderComplete += () => _log.Add($"Render frame: {_timer.ElapsedMilliseconds} ms");
@@ -89,7 +101,12 @@ private void SetupRender(Type type)
{
var page = Activator.CreateInstance(pageType);
var useSource = pageType.GetMethod(nameof(IOptionsPage.UseSource));
- _ = useSource.Invoke(page, new[] { Renderer });
+ if (page is null || useSource is null)
+ {
+ throw new InvalidOperationException($"Could not create options page '{pageType.FullName}'.");
+ }
+
+ _ = useSource.Invoke(page, new object[] { Renderer });
_ = OptionsFrame.Navigate(page);
}
@@ -102,6 +119,11 @@ private void SetupRender(Type type)
private void Render()
{
+ if (Renderer is null)
+ {
+ return;
+ }
+
Renderer.Render(Dispatcher);
}
@@ -166,7 +188,7 @@ private void Resolution_ValueChanged(object sender, RoutedPropertyChangedEventAr
}
}
- private void OnKeyPress(object sender, KeyEventArgs e)
+ private void OnKeyPress(object? sender, KeyEventArgs e)
{
if (IsActive)
{
diff --git a/src/RenderBox/Views/Pages/DrawingPage.xaml.cs b/src/RenderBox/Views/Pages/DrawingPage.xaml.cs
index 9cd80f3..54a8697 100644
--- a/src/RenderBox/Views/Pages/DrawingPage.xaml.cs
+++ b/src/RenderBox/Views/Pages/DrawingPage.xaml.cs
@@ -9,7 +9,8 @@ namespace RenderBox.Views.Pages
{
public partial class DrawingPage : Page, IOptionsPage
{
- private DrawingRenderer _source;
+ private DrawingRenderer? _source;
+ private DrawingRenderer Source => _source ?? throw new InvalidOperationException($"{nameof(UseSource)} must be called before using this page.");
public DrawingPage()
{
@@ -23,11 +24,11 @@ public void UseSource(DrawingRenderer source)
private void BlitButton_Click(object sender, RoutedEventArgs e)
{
- _source.SetRender((context) =>
+ Source.SetRender((context) =>
{
- _source.Paint.FillRect(0, 0, context.Width, context.Height, Color.Green);
+ Source.Paint.FillRect(0, 0, context.Width, context.Height, Color.Green);
});
- _source.Render(Dispatcher);
+ Source.Render(Dispatcher);
}
}
}
diff --git a/src/RenderBox/Views/Pages/MandelbrotPage.xaml.cs b/src/RenderBox/Views/Pages/MandelbrotPage.xaml.cs
index e7d2704..d9aaca7 100644
--- a/src/RenderBox/Views/Pages/MandelbrotPage.xaml.cs
+++ b/src/RenderBox/Views/Pages/MandelbrotPage.xaml.cs
@@ -7,7 +7,8 @@ namespace RenderBox.Views.Pages
{
public partial class MandelbrotPage : Page, IOptionsPage
{
- private MandelbrotRenderer _source;
+ private MandelbrotRenderer? _source;
+ private MandelbrotRenderer Source => _source ?? throw new InvalidOperationException($"{nameof(UseSource)} must be called before using this page.");
public MandelbrotPage()
{
@@ -27,8 +28,8 @@ public MandelbrotPage()
noFilterButton.Click += (s, e) =>
{
- _source.Filter = null;
- _source.Render(Dispatcher);
+ Source.Filter = null;
+ Source.Render(Dispatcher);
};
EffectsPanel.Children.Add(noFilterButton);
@@ -36,6 +37,10 @@ public MandelbrotPage()
foreach (var filter in filters)
{
var instance = Activator.CreateInstance(filter);
+ if (instance is not IPaletteFilter paletteFilter)
+ {
+ continue;
+ }
var button = new Button
{
@@ -45,8 +50,8 @@ public MandelbrotPage()
button.Click += (s, e) =>
{
- _source.Filter = (IPaletteFilter)instance;
- _source.Render(Dispatcher);
+ Source.Filter = paletteFilter;
+ Source.Render(Dispatcher);
};
EffectsPanel.Children.Add(button);
@@ -57,18 +62,18 @@ public void UseSource(MandelbrotRenderer source)
{
_source = source;
- Iterations.Text = _source.Iterations.ToString();
- Extent.Text = _source.Extent.ToString();
- BatchSize.Text = _source.BatchSize.ToString();
+ Iterations.Text = Source.Iterations.ToString();
+ Extent.Text = Source.Extent.ToString();
+ BatchSize.Text = Source.BatchSize.ToString();
}
private void ApplyButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
- _source.Iterations = int.TryParse(Iterations.Text, out var a) ? a : 0;
- _source.Extent = double.TryParse(Extent.Text, out var b) ? b : 0;
- _source.BatchSize = int.TryParse(BatchSize.Text, out var c) ? c : 0;
+ Source.Iterations = int.TryParse(Iterations.Text, out var a) ? a : 0;
+ Source.Extent = double.TryParse(Extent.Text, out var b) ? b : 0;
+ Source.BatchSize = int.TryParse(BatchSize.Text, out var c) ? c : 0;
- _source.Render(Dispatcher);
+ Source.Render(Dispatcher);
}
}
diff --git a/src/RenderBox/Views/Pages/PathTracePage.xaml.cs b/src/RenderBox/Views/Pages/PathTracePage.xaml.cs
index df1068d..07dccdf 100644
--- a/src/RenderBox/Views/Pages/PathTracePage.xaml.cs
+++ b/src/RenderBox/Views/Pages/PathTracePage.xaml.cs
@@ -6,7 +6,8 @@ namespace RenderBox.Views.Pages
{
public partial class PathTracePage : Page, IOptionsPage
{
- private PathTraceRenderer _source;
+ private PathTraceRenderer? _source;
+ private PathTraceRenderer Source => _source ?? throw new InvalidOperationException($"{nameof(UseSource)} must be called before using this page.");
public PathTracePage()
{
@@ -17,31 +18,31 @@ public void UseSource(PathTraceRenderer source)
{
_source = source;
- Lighting.IsChecked = _source.Scene.LightingEnabled;
- Shadows.IsChecked = _source.Scene.ShadowsEnabled;
- SoftShadows.IsChecked = _source.Scene.SoftShadows;
- AmbientOcclusion.IsChecked = _source.Scene.AmbientOcclusion;
- GISamples.Text = _source.Scene.GISamples.ToString();
- FOV.Text = _source.MainCamera.FOV.ToString();
- CameraDistance.Text = _source.MainCamera.MaxDistance.ToString();
+ Lighting.IsChecked = Source.Scene.LightingEnabled;
+ Shadows.IsChecked = Source.Scene.ShadowsEnabled;
+ SoftShadows.IsChecked = Source.Scene.SoftShadows;
+ AmbientOcclusion.IsChecked = Source.Scene.AmbientOcclusion;
+ GISamples.Text = Source.Scene.GISamples.ToString();
+ FOV.Text = Source.MainCamera.FOV.ToString();
+ CameraDistance.Text = Source.MainCamera.MaxDistance.ToString();
- BatchSize.Text = _source.BatchSize.ToString();
+ BatchSize.Text = Source.BatchSize.ToString();
}
private void ApplyButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
- _source.Mode = GetRenderMode();
- _source.BatchSize = int.TryParse(BatchSize.Text, out int num) ? num : 0;
+ Source.Mode = GetRenderMode();
+ Source.BatchSize = int.TryParse(BatchSize.Text, out int num) ? num : 0;
- _source.Scene.LightingEnabled = Lighting.IsChecked ?? false;
- _source.Scene.ShadowsEnabled = Shadows.IsChecked ?? false;
- _source.Scene.SoftShadows = SoftShadows.IsChecked ?? false;
- _source.Scene.AmbientOcclusion = AmbientOcclusion.IsChecked ?? false;
- _source.Scene.GISamples = int.TryParse(GISamples.Text, out num) ? num : 0;
- _source.MainCamera.FOV = float.TryParse(FOV.Text, out var f) ? f : 0;
- _source.MainCamera.MaxDistance = float.TryParse(CameraDistance.Text, out f) ? f : 0;
+ Source.Scene.LightingEnabled = Lighting.IsChecked ?? false;
+ Source.Scene.ShadowsEnabled = Shadows.IsChecked ?? false;
+ Source.Scene.SoftShadows = SoftShadows.IsChecked ?? false;
+ Source.Scene.AmbientOcclusion = AmbientOcclusion.IsChecked ?? false;
+ Source.Scene.GISamples = int.TryParse(GISamples.Text, out num) ? num : 0;
+ Source.MainCamera.FOV = float.TryParse(FOV.Text, out var f) ? f : 0;
+ Source.MainCamera.MaxDistance = float.TryParse(CameraDistance.Text, out f) ? f : 0;
- _source.Render(Dispatcher);
+ Source.Render(Dispatcher);
}
private RenderMode GetRenderMode()
diff --git a/src/RenderBox/Views/Pages/TerrainPage.xaml.cs b/src/RenderBox/Views/Pages/TerrainPage.xaml.cs
index 54dce4c..a9d0b04 100644
--- a/src/RenderBox/Views/Pages/TerrainPage.xaml.cs
+++ b/src/RenderBox/Views/Pages/TerrainPage.xaml.cs
@@ -6,8 +6,6 @@ namespace RenderBox.Views.Pages
{
public partial class TerrainPage : Page, IOptionsPage
{
- private TerrainRenderer _source;
-
public TerrainPage()
{
InitializeComponent();
@@ -15,7 +13,6 @@ public TerrainPage()
public void UseSource(TerrainRenderer source)
{
- _source = source;
}
}
}