Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions Engine3D/Engine3D-Tests/Raytrace/SpatialSubdivisionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Engine3D;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Engine3D.Raytrace;
using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
using Triangle = Engine3D.Raytrace.Triangle;
using Vector = Engine3D.Vector;

// TODO: Use Benchmark.net to micro-benchmark some raytracing and make performance measurements more accurate
// TODO: Make a test that passes random parameters to tree building (tree depth vs max tris per node) for large model, to find optimal parameters for given model

namespace Engine3D_Tests
{
[TestClass]
public class SpatialSubdivisionTests
{
private const string largeModelFileName = "../../large-model.3ds";

private static Random random = new Random();
private static RenderContext context = new RenderContext(random);

Expand Down Expand Up @@ -281,6 +290,142 @@ public void RayIntersectTreeMostlyFromOutside_Performance()
Console.WriteLine("Performance: {0} million ray/tri per second", millionRayTriPerSec);
}

[TestMethod]
public void RayIntersectLargeModelFromInside_Performance()
{
#if DEBUG
// my laptop in High Performance mode
const double minMillionRaysPerSec = 35.0;
const double maxMillionRaysPerSec = 45.0;
#elif APPVEYOR_PERFORMANCE_MARGINS
// AppVeyor build server
const double minMillionRaysPerSec = 210.0;
const double maxMillionRaysPerSec = 240.0;
#else
// my laptop in High Performance mode
const double minMillionRaysPerSec = 73.0;
const double maxMillionRaysPerSec = 90.0;
#endif

// Load 3D model from disk
var model = new Model();
using (Stream stream = new FileStream(largeModelFileName, FileMode.Open, FileAccess.Read))
{
model.Load3dsModelFromStream(stream);
}

List<Triangle> triangleList = MakeRayTracableGeometry_simple(model);
int numTriangles = triangleList.Count;

SpatialSubdivision geometryInTree = MakeRayTracableGeometry_subdivided(triangleList, model);

//TestTree_InsideOut(geometryInTree, triangleList, numRays: 10000); // 1m:25s

// Inside-out rays
// 1.3s for 1 million rays when using triangleSpaceSize (i.e. mostly missing model bounding box)
// 10.4s for 10,000 rays when using unit cube space!
const int numRays = 1000;
var numRaysHit = 0;
DateTime startTime = DateTime.Now;
for (var i = 1; i <= numRays; i++)
{
var start = MakeRandomVector(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5);
var dir = MakeRandomVector(-1, 1, -1, 1, -1, 1);
var info = geometryInTree.IntersectRay(start, dir, context);
if (info != null)
numRaysHit++;
}
var elapsedTime = DateTime.Now - startTime;
Assert.IsTrue(numRays * 0.4 < numRaysHit && numRaysHit < numRays * 0.5, "Num rays hit {0} should be 40-50% of total rays {1}", numRaysHit, numRays);
var millionRayTriPerSec = numRays / 1000000.0 / elapsedTime.TotalSeconds * numTriangles;
Assert.IsTrue(minMillionRaysPerSec < millionRayTriPerSec && millionRayTriPerSec < maxMillionRaysPerSec,
"Rays per second {0:f2} not between {1} and {2} (millions)", millionRayTriPerSec, minMillionRaysPerSec, maxMillionRaysPerSec);
Console.WriteLine("Performance: {0} million ray/tri per second", millionRayTriPerSec);
}

[TestMethod]
public void RayIntersectLargeModelMostlyFromOutside_Performance()
{
#if DEBUG
// my laptop in High Performance mode
const double minMillionRaysPerSec = 30.0;
const double maxMillionRaysPerSec = 33.0;
#elif APPVEYOR_PERFORMANCE_MARGINS
// AppVeyor build server
const double minMillionRaysPerSec = 130.0;
const double maxMillionRaysPerSec = 170.0;
#else
// my laptop in High Performance mode
const double minMillionRaysPerSec = 68.0;
const double maxMillionRaysPerSec = 79.0;
#endif

// Load 3D model from disk
var model = new Model();
using (Stream stream = new FileStream(largeModelFileName, FileMode.Open, FileAccess.Read))
{
model.Load3dsModelFromStream(stream);
}

List<Triangle> triangleList = MakeRayTracableGeometry_simple(model);
int numTriangles = triangleList.Count;

SpatialSubdivision geometryInTree = MakeRayTracableGeometry_subdivided(triangleList, model);

//TestTree_OutsideIn(geometryInTree, triangleList, numRays: 10000); // 1m:44s

// Outside-in rays
// 1.9s for 1 million rays when using triangleSpaceSize (i.e. mostly missing model bounding box)
// 12.8s for 10,000 rays when using unit cube space!
// So rendering a 1024x768 image should take around 13 mins. I measured the (4 thread) renderer taking 2.5 mins.
const int numRays = 1000;
var numRaysHit = 0;
DateTime startTime = DateTime.Now;
for (var i = 1; i <= numRays; i++)
{
var start = MakeRandomVector(-5, 5, -5, 5, -5, 5);
var end = MakeRandomVector(-0.5, 0.5, -0.5, 0.5, -0.5, 0.5);
var dir = end - start;
var info = geometryInTree.IntersectRay(start, dir, context);
if (info != null)
numRaysHit++;
}
var elapsedTime = DateTime.Now - startTime;
Assert.IsTrue(numRays * 0.6 < numRaysHit && numRaysHit < numRays * 0.7, "Num rays hit {0} should be 60-70% of total rays {1}", numRaysHit, numRays);
var millionRayTriPerSec = numRays / 1000000.0 / elapsedTime.TotalSeconds * numTriangles;
Assert.IsTrue(minMillionRaysPerSec < millionRayTriPerSec && millionRayTriPerSec < maxMillionRaysPerSec,
"Rays per second {0:f2} not between {1} and {2} (millions)", millionRayTriPerSec, minMillionRaysPerSec, maxMillionRaysPerSec);
Console.WriteLine("Performance: {0} million ray/tri per second", millionRayTriPerSec);
}

private static List<Triangle> MakeRayTracableGeometry_simple(Model model)
{
// Convert the 3D triangle model into a set of triangle objects that can be ray traced
var geom = new List<Triangle>();
int triIndex = 0;
foreach (Engine3D.Triangle tri in model.Triangles)
{
Vector v1 = model.Vertices[tri.vertexIndex1].pos;
Vector v2 = model.Vertices[tri.vertexIndex2].pos;
Vector v3 = model.Vertices[tri.vertexIndex3].pos;
uint color = Surface.PackColorAndAlpha(tri.diffuseMaterial, 1.0);
// let the triangle know its index within this collection of geometry
geom.Add(new Triangle(v1, v2, v3, color) {TriangleIndex = triIndex});
triIndex++;
}

return geom;
}

private static SpatialSubdivision MakeRayTracableGeometry_subdivided(List<Triangle> triangleList, Model model)
{
// Create a box bounding the 3D triangle model.
var boundingBox = new AxisAlignedBox(model.Min, model.Max);

// Create the spatial subdivison object.
return new SpatialSubdivision(triangleList, boundingBox);
}

[TestMethod]
public void TreeCorrectness1()
{
Expand Down
103 changes: 103 additions & 0 deletions Engine3D/Engine3D-Tests/Raytrace/TriangleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,109 @@ public void RayIntersectAABBPerformance()
Console.WriteLine("Performance: {0} million rays per second", millionRaysPerSec);
}

[TestMethod]
public void ClipLineSegmentToAABBPerformance()
{
#if DEBUG
// my laptop in High Performance mode
const double minMillionLinesPerSec = 0;
const double maxMillionLinesPerSec = 0;
#elif APPVEYOR_PERFORMANCE_MARGINS
// AppVeyor build server
const double minMillionLinesPerSec = 0;
const double maxMillionLinesPerSec = 0;
#else
// my laptop in High Performance mode
const double minMillionLinesPerSec = 0.45; // lines extend 100 units outside of box
const double maxMillionLinesPerSec = 0.60;
//const double minMillionLinesPerSec = 0.85; // lines extend barely outside of box
//const double maxMillionLinesPerSec = 1.2;
#endif

const int numLines = 1000000;
var box = new AxisAlignedBox(new Vector(-0.5, -0.5, -0.5), new Vector(0.5, 0.5, 0.5));
var lineTouchesBox = 0;

DateTime startTime = DateTime.Now;
for (var i = 0; i < numLines; i++)
{
//var start = new Vector(0.5, 0.5, 0.5);
//var start = MakeRandomVector(-0.3, 0.3, -0.3, 0.3, 0.5, 1);
//var end = MakeRandomVector(10, 10, 10);

var end = MakeRandomVector(-0.3, 0.3, -0.3, 0.3, 0.5, 1);
var dir = MakeRandomVector(-0.5, 0.5, -0.5, 0.5, -1, -1);
var start = end + dir * 100;

//var dir = MakeRandomVector(-0.5, 0.5, -0.5, 0.5, -1, -1);
//var dir = MakeRandomVector(1, 1, -1);
//var dir = end - start;

if(box.ClipLineSegment(ref start, ref end))
lineTouchesBox++;
}
var elapsedTime = DateTime.Now - startTime;
Assert.IsTrue(numLines * 0.998 < lineTouchesBox && lineTouchesBox <= numLines * 1.0,
"Num lines touching box {0} should be roughly same as total lines {1}", lineTouchesBox, numLines);
//Assert.IsTrue(numLines * 0.380 < lineTouchesBox && lineTouchesBox <= numLines * 0.383,
// "Num lines touching box {0} should be roughly 40% of total lines {1}", lineTouchesBox, numLines);
var millionLinesPerSec = numLines / 1000000.0 / elapsedTime.TotalSeconds;
Assert.IsTrue(minMillionLinesPerSec < millionLinesPerSec && millionLinesPerSec < maxMillionLinesPerSec,
"Lines per second {0:f3} not between {1} and {2} (millions)", millionLinesPerSec, minMillionLinesPerSec, maxMillionLinesPerSec);
Console.WriteLine("Performance: {0} million lines per second", millionLinesPerSec);
}

[TestMethod]
public void LineIntersectAABBPerformance()
{
#if DEBUG
// my laptop in High Performance mode
const double minMillionLinesPerSec = 0;
const double maxMillionLinesPerSec = 0;
#elif APPVEYOR_PERFORMANCE_MARGINS
// AppVeyor build server
const double minMillionLinesPerSec = 0;
const double maxMillionLinesPerSec = 0;
#else
// my laptop in High Performance mode
const double minMillionLinesPerSec = 0.94;
const double maxMillionLinesPerSec = 1.10;
#endif

const int numLines = 1000000;
var box = new AxisAlignedBox(new Vector(-0.5, -0.5, -0.5), new Vector(0.5, 0.5, 0.5));
var lineTouchesBox = 0;

DateTime startTime = DateTime.Now;
for (var i = 0; i < numLines; i++)
{
//var start = new Vector(0.5, 0.5, 0.5);
//var start = MakeRandomVector(-0.3, 0.3, -0.3, 0.3, 0.5, 1);
//var end = MakeRandomVector(10, 10, 10);

var end = MakeRandomVector(-0.3, 0.3, -0.3, 0.3, 0.5, 1);
var dir = MakeRandomVector(-0.5, 0.5, -0.5, 0.5, -1, -1);
var start = end + dir * 100;

//var dir = MakeRandomVector(-0.5, 0.5, -0.5, 0.5, -1, -1);
//var dir = MakeRandomVector(1, 1, -1);
//var dir = end - start;

var info = box.IntersectLineSegment(start, end);
if (info != null)
lineTouchesBox++;
}
var elapsedTime = DateTime.Now - startTime;
Assert.IsTrue(numLines * 0.998 < lineTouchesBox && lineTouchesBox <= numLines * 1.0,
"Num lines touching box {0} should be roughly same as total lines {1}", lineTouchesBox, numLines);
//Assert.IsTrue(numLines * 0.380 < lineTouchesBox && lineTouchesBox <= numLines * 0.383,
// "Num lines touching box {0} should be roughly 40% of total lines {1}", lineTouchesBox, numLines);
var millionLinesPerSec = numLines / 1000000.0 / elapsedTime.TotalSeconds;
Assert.IsTrue(minMillionLinesPerSec < millionLinesPerSec && millionLinesPerSec < maxMillionLinesPerSec,
"Lines per second {0:f3} not between {1} and {2} (millions)", millionLinesPerSec, minMillionLinesPerSec, maxMillionLinesPerSec);
Console.WriteLine("Performance: {0} million lines per second", millionLinesPerSec);
}

[TestMethod]
public void BuildVoxelGridPerformance()
{
Expand Down
Binary file added Engine3D/Engine3D-Tests/large-model.3ds
Binary file not shown.
8 changes: 7 additions & 1 deletion Engine3D/Raytrace/AxisAlignedBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public IntersectionInfo IntersectLineSegment(Vector start, Vector end)
IntersectionInfo closest = new IntersectionInfo();
closest.rayFrac = double.MaxValue;

// TODO: somehow intersect line against all planes at once, or guess at nearest plane?
foreach (Plane plane in planes)
{
// Does ray intersect this plane?
Expand Down Expand Up @@ -210,6 +211,11 @@ public bool ClipLineSegment(ref Vector start, ref Vector end)
// so original line segment must intersect box at two points.
intersection = IntersectLineSegment(end, originalStart);
Contract.Assume(intersection != null, "Intersection must exist");

// TODO: HACK! This should never happen - logic bug!
//if (intersection == null)
// return true;

end = intersection.pos;
}
return true;
Expand Down Expand Up @@ -305,7 +311,7 @@ private void ProjectOntoAxis(Vector axis, out double intervalMin, out double int
/// <param name="tri"></param>
/// <param name="planes"></param>
/// <returns>True iff triangle is 'outside' one of the planes</returns>
static private bool IsTrianglesOutsidePlanes(Triangle tri, IEnumerable<Plane> planes)
private static bool IsTrianglesOutsidePlanes(Triangle tri, IEnumerable<Plane> planes)
{
// TODO: a triangle can be 'inside' all planes (each plane has at least one triangle vertex 'inside' the plane),
// yet the triangle can still not intersect the volume defined by the planes.
Expand Down