mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
390 lines
11 KiB
390 lines
11 KiB
// This test case is taken from https://blogs.msdn.microsoft.com/lukeh/2007/10/01/taking-linq-to-objects-to-extremes-a-fully-linqified-raytracer/ |
|
|
|
using System.Linq; |
|
using System; |
|
using System.Collections.Generic; |
|
|
|
namespace RayTracer |
|
{ |
|
public class RayTracer |
|
{ |
|
static void Main() |
|
{ |
|
const int width = 50; |
|
const int height = 50; |
|
|
|
RayTracer rayTracer = new RayTracer(width, height, (int x, int y, Color color) => { |
|
Console.Write("{0},{1}:{2};", x, y, color); |
|
}); |
|
rayTracer.Render(rayTracer.DefaultScene); |
|
} |
|
|
|
private int screenWidth; |
|
private int screenHeight; |
|
private const int MaxDepth = 5; |
|
|
|
public Action<int, int, Color> setPixel; |
|
|
|
public RayTracer(int screenWidth, int screenHeight, Action<int, int, Color> setPixel) |
|
{ |
|
this.screenWidth = screenWidth; |
|
this.screenHeight = screenHeight; |
|
this.setPixel = setPixel; |
|
} |
|
|
|
private class Wrap<T> |
|
{ |
|
public readonly Func<Wrap<T>, T> It; |
|
public Wrap(Func<Wrap<T>, T> it) { It = it; } |
|
} |
|
|
|
public static Func<T, U> Y<T, U>(Func<Func<T, U>, Func<T, U>> f) |
|
{ |
|
Func<Wrap<Func<T, U>>, Func<T, U>> g = wx => f(wx.It(wx)); |
|
return g(new Wrap<Func<T, U>>(wx => f(y => wx.It(wx)(y)))); |
|
} |
|
|
|
class TraceRayArgs |
|
{ |
|
public readonly Ray Ray; |
|
public readonly Scene Scene; |
|
public readonly int Depth; |
|
|
|
public TraceRayArgs(Ray ray, Scene scene, int depth) { Ray = ray; Scene = scene; Depth = depth; } |
|
} |
|
|
|
internal void Render(Scene scene) |
|
{ |
|
var pixelsQuery = |
|
from y in Enumerable.Range(0, screenHeight) |
|
let recenterY = -(y - (screenHeight / 2.0)) / (2.0 * screenHeight) |
|
select from x in Enumerable.Range(0, screenWidth) |
|
let recenterX = (x - (screenWidth / 2.0)) / (2.0 * screenWidth) |
|
let point = |
|
Vector.Norm(Vector.Plus(scene.Camera.Forward, |
|
Vector.Plus(Vector.Times(recenterX, scene.Camera.Right), |
|
Vector.Times(recenterY, scene.Camera.Up)))) |
|
let ray = new Ray() { Start = scene.Camera.Pos, Dir = point } |
|
let computeTraceRay = (Func<Func<TraceRayArgs, Color>, Func<TraceRayArgs, Color>>) |
|
(f => traceRayArgs => |
|
(from isect in |
|
from thing in traceRayArgs.Scene.Things |
|
select thing.Intersect(traceRayArgs.Ray) |
|
where isect != null |
|
orderby isect.Dist |
|
let d = isect.Ray.Dir |
|
let pos = Vector.Plus(Vector.Times(isect.Dist, isect.Ray.Dir), isect.Ray.Start) |
|
let normal = isect.Thing.Normal(pos) |
|
let reflectDir = Vector.Minus(d, Vector.Times(2 * Vector.Dot(normal, d), normal)) |
|
let naturalColors = |
|
from light in traceRayArgs.Scene.Lights |
|
let ldis = Vector.Minus(light.Pos, pos) |
|
let livec = Vector.Norm(ldis) |
|
let testRay = new Ray() { Start = pos, Dir = livec } |
|
let testIsects = from inter in |
|
from thing in traceRayArgs.Scene.Things |
|
select thing.Intersect(testRay) |
|
where inter != null |
|
orderby inter.Dist |
|
select inter |
|
let testIsect = testIsects.FirstOrDefault() |
|
let neatIsect = testIsect == null ? 0 : testIsect.Dist |
|
let isInShadow = !((neatIsect > Vector.Mag(ldis)) || (neatIsect == 0)) |
|
where !isInShadow |
|
let illum = Vector.Dot(livec, normal) |
|
let lcolor = illum > 0 ? Color.Times(illum, light.Color) : Color.Make(0, 0, 0) |
|
let specular = Vector.Dot(livec, Vector.Norm(reflectDir)) |
|
let scolor = specular > 0 |
|
? Color.Times(Math.Pow(specular, isect.Thing.Surface.Roughness), |
|
light.Color) |
|
: Color.Make(0, 0, 0) |
|
select Color.Plus(Color.Times(isect.Thing.Surface.Diffuse(pos), lcolor), |
|
Color.Times(isect.Thing.Surface.Specular(pos), scolor)) |
|
let reflectPos = Vector.Plus(pos, Vector.Times(.001, reflectDir)) |
|
let reflectColor = traceRayArgs.Depth >= MaxDepth |
|
? Color.Make(.5, .5, .5) |
|
: Color.Times(isect.Thing.Surface.Reflect(reflectPos), |
|
f(new TraceRayArgs(new Ray() { |
|
Start = reflectPos, |
|
Dir = reflectDir |
|
}, |
|
traceRayArgs.Scene, |
|
traceRayArgs.Depth + 1))) |
|
select naturalColors.Aggregate(reflectColor, |
|
(color, natColor) => Color.Plus(color, natColor)) |
|
).DefaultIfEmpty(Color.Background).First()) |
|
let traceRay = Y(computeTraceRay) |
|
select new { X = x, Y = y, Color = traceRay(new TraceRayArgs(ray, scene, 0)) }; |
|
|
|
foreach (var row in pixelsQuery) |
|
foreach (var pixel in row) |
|
setPixel(pixel.X, pixel.Y, pixel.Color); |
|
} |
|
|
|
internal readonly Scene DefaultScene = |
|
new Scene() { |
|
Things = new SceneObject[] { |
|
new Plane() { |
|
Norm = Vector.Make(0,1,0), |
|
Offset = 0, |
|
Surface = Surfaces.CheckerBoard |
|
}, |
|
new Sphere() { |
|
Center = Vector.Make(0,1,0), |
|
Radius = 1, |
|
Surface = Surfaces.Shiny |
|
}, |
|
new Sphere() { |
|
Center = Vector.Make(-1,.5,1.5), |
|
Radius = .5, |
|
Surface = Surfaces.Shiny |
|
}}, |
|
Lights = new Light[] { |
|
new Light() { |
|
Pos = Vector.Make(-2,2.5,0), |
|
Color = Color.Make(.49,.07,.07) |
|
}, |
|
new Light() { |
|
Pos = Vector.Make(1.5,2.5,1.5), |
|
Color = Color.Make(.07,.07,.49) |
|
}, |
|
new Light() { |
|
Pos = Vector.Make(1.5,2.5,-1.5), |
|
Color = Color.Make(.07,.49,.071) |
|
}, |
|
new Light() { |
|
Pos = Vector.Make(0,3.5,0), |
|
Color = Color.Make(.21,.21,.35) |
|
}}, |
|
Camera = Camera.Create(Vector.Make(3, 2, 4), Vector.Make(-1, .5, 0)) |
|
}; |
|
} |
|
|
|
static class Surfaces |
|
{ |
|
// Only works with X-Z plane. |
|
public static readonly Surface CheckerBoard = |
|
new Surface() { |
|
Diffuse = pos => ((Math.Floor(pos.Z) + Math.Floor(pos.X)) % 2 != 0) |
|
? Color.Make(1, 1, 1) |
|
: Color.Make(0, 0, 0), |
|
Specular = pos => Color.Make(1, 1, 1), |
|
Reflect = pos => ((Math.Floor(pos.Z) + Math.Floor(pos.X)) % 2 != 0) |
|
? .1 |
|
: .7, |
|
Roughness = 150 |
|
}; |
|
|
|
|
|
public static readonly Surface Shiny = |
|
new Surface() { |
|
Diffuse = pos => Color.Make(1, 1, 1), |
|
Specular = pos => Color.Make(.5, .5, .5), |
|
Reflect = pos => .6, |
|
Roughness = 50 |
|
}; |
|
} |
|
|
|
class Vector |
|
{ |
|
public readonly double X; |
|
public readonly double Y; |
|
public readonly double Z; |
|
|
|
public Vector(double x, double y, double z) { X = x; Y = y; Z = z; } |
|
|
|
public static Vector Make(double x, double y, double z) { return new Vector(x, y, z); } |
|
public static Vector Times(double n, Vector v) |
|
{ |
|
return new Vector(v.X * n, v.Y * n, v.Z * n); |
|
} |
|
public static Vector Minus(Vector v1, Vector v2) |
|
{ |
|
return new Vector(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); |
|
} |
|
public static Vector Plus(Vector v1, Vector v2) |
|
{ |
|
return new Vector(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); |
|
} |
|
public static double Dot(Vector v1, Vector v2) |
|
{ |
|
return (v1.X * v2.X) + (v1.Y * v2.Y) + (v1.Z * v2.Z); |
|
} |
|
public static double Mag(Vector v) { return Math.Sqrt(Dot(v, v)); } |
|
public static Vector Norm(Vector v) |
|
{ |
|
double mag = Mag(v); |
|
double div = mag == 0 ? double.PositiveInfinity : 1 / mag; |
|
return Times(div, v); |
|
} |
|
public static Vector Cross(Vector v1, Vector v2) |
|
{ |
|
return new Vector(((v1.Y * v2.Z) - (v1.Z * v2.Y)), |
|
((v1.Z * v2.X) - (v1.X * v2.Z)), |
|
((v1.X * v2.Y) - (v1.Y * v2.X))); |
|
} |
|
public static bool Equals(Vector v1, Vector v2) |
|
{ |
|
return (v1.X == v2.X) && (v1.Y == v2.Y) && (v1.Z == v2.Z); |
|
} |
|
} |
|
|
|
public class Color |
|
{ |
|
public double R; |
|
public double G; |
|
public double B; |
|
|
|
public Color(double r, double g, double b) { R = r; G = g; B = b; } |
|
|
|
public static Color Make(double r, double g, double b) { return new Color(r, g, b); } |
|
|
|
public static Color Times(double n, Color v) |
|
{ |
|
return new Color(n * v.R, n * v.G, n * v.B); |
|
} |
|
public static Color Times(Color v1, Color v2) |
|
{ |
|
return new Color(v1.R * v2.R, v1.G * v2.G, v1.B * v2.B); |
|
} |
|
|
|
public static Color Plus(Color v1, Color v2) |
|
{ |
|
return new Color(v1.R + v2.R, v1.G + v2.G, v1.B + v2.B); |
|
} |
|
public static Color Minus(Color v1, Color v2) |
|
{ |
|
return new Color(v1.R - v2.R, v1.G - v2.G, v1.B - v2.B); |
|
} |
|
|
|
public static readonly Color Background = Make(0, 0, 0); |
|
public static readonly Color DefaultColor = Make(0, 0, 0); |
|
|
|
private double Legalize(double d) |
|
{ |
|
return d > 1 ? 1 : d; |
|
} |
|
|
|
public override string ToString() |
|
{ |
|
return string.Format("[{0},{1},{2}]", R, G, B); |
|
} |
|
} |
|
|
|
class Ray |
|
{ |
|
public Vector Start; |
|
public Vector Dir; |
|
} |
|
|
|
class ISect |
|
{ |
|
public SceneObject Thing; |
|
public Ray Ray; |
|
public double Dist; |
|
} |
|
|
|
class Surface |
|
{ |
|
public Func<Vector, Color> Diffuse; |
|
public Func<Vector, Color> Specular; |
|
public Func<Vector, double> Reflect; |
|
public double Roughness; |
|
} |
|
|
|
class Camera |
|
{ |
|
public Vector Pos; |
|
public Vector Forward; |
|
public Vector Up; |
|
public Vector Right; |
|
|
|
public static Camera Create(Vector pos, Vector lookAt) |
|
{ |
|
Vector forward = Vector.Norm(Vector.Minus(lookAt, pos)); |
|
Vector down = new Vector(0, -1, 0); |
|
Vector right = Vector.Times(1.5, Vector.Norm(Vector.Cross(forward, down))); |
|
Vector up = Vector.Times(1.5, Vector.Norm(Vector.Cross(forward, right))); |
|
|
|
return new Camera() { Pos = pos, Forward = forward, Up = up, Right = right }; |
|
} |
|
} |
|
|
|
class Light |
|
{ |
|
public Vector Pos; |
|
public Color Color; |
|
} |
|
|
|
abstract class SceneObject |
|
{ |
|
public Surface Surface; |
|
public abstract ISect Intersect(Ray ray); |
|
public abstract Vector Normal(Vector pos); |
|
} |
|
|
|
class Sphere : SceneObject |
|
{ |
|
public Vector Center; |
|
public double Radius; |
|
|
|
public override ISect Intersect(Ray ray) |
|
{ |
|
Vector eo = Vector.Minus(Center, ray.Start); |
|
double v = Vector.Dot(eo, ray.Dir); |
|
double dist; |
|
if (v < 0) { |
|
dist = 0; |
|
} else { |
|
double disc = Math.Pow(Radius, 2) - (Vector.Dot(eo, eo) - Math.Pow(v, 2)); |
|
dist = disc < 0 ? 0 : v - Math.Sqrt(disc); |
|
} |
|
if (dist == 0) return null; |
|
return new ISect() { |
|
Thing = this, |
|
Ray = ray, |
|
Dist = dist |
|
}; |
|
} |
|
|
|
public override Vector Normal(Vector pos) |
|
{ |
|
return Vector.Norm(Vector.Minus(pos, Center)); |
|
} |
|
} |
|
|
|
class Plane : SceneObject |
|
{ |
|
public Vector Norm; |
|
public double Offset; |
|
|
|
public override ISect Intersect(Ray ray) |
|
{ |
|
double denom = Vector.Dot(Norm, ray.Dir); |
|
if (denom > 0) return null; |
|
return new ISect() { |
|
Thing = this, |
|
Ray = ray, |
|
Dist = (Vector.Dot(Norm, ray.Start) + Offset) / (-denom) |
|
}; |
|
} |
|
|
|
public override Vector Normal(Vector pos) |
|
{ |
|
return Norm; |
|
} |
|
} |
|
|
|
class Scene |
|
{ |
|
public SceneObject[] Things; |
|
public Light[] Lights; |
|
public Camera Camera; |
|
|
|
public IEnumerable<ISect> Intersect(Ray r) |
|
{ |
|
return from thing in Things |
|
select thing.Intersect(r); |
|
} |
|
} |
|
} |