/// <summary> /// Intersect a ray against this object. /// </summary> /// <param name="start">The start position of the ray, in object space.</param> /// <param name="dir">The direction of the ray, in object space (not a unit vector).</param> /// <returns>Information about the nearest intersection, or null if no intersection.</returns> public IntersectionInfo IntersectRay(Vector start, Vector dir, RenderContext context) { // TODO: some rays should be short (e.g. ambient occlusion rays). Make the max ray distance an optional parameter? Vector end = start + dir * 10; //if (!boundingBox.OverlapsLineSegment(start, end)) // TODO: clipping line segment breaks IntersectionInfo.rayFrac, because ray is truncated, but rayFrac does not reflect this! if (!boundingBox.ClipLineSegment(ref start, ref end)) { return(null); } // Scale up from (unit) object space to voxel grid resolution var half = new Vector(0.5, 0.5, 0.5); start = (start * 0.5 + half) * ((double)GridSize - 0.001); end = (end * 0.5 + half) * ((double)GridSize - 0.001); int oldX = -1, oldY = -1, oldZ = -1; // TODO: a larger minStep (e.g. 1) makes the voxels look like thin squares facing a single direction. Why? foreach (var pos in LineWalker3D.WalkLine(start, end, 0.1)) { int x = (int)pos.x; var y = (int)pos.y; var z = (int)pos.z; Contract.Assert(x >= 0 && x < GridSize && y >= 0 && y < GridSize && z >= 0 && z < GridSize); // TODO: Contracts analyser says pos.z is not -1 here, and similarly for x and y if (x != oldX && y != oldY && z != oldZ) { uint colorSample = voxelColors[x, y, z]; // Treat black voxels as transparent if (colorSample != 0) { //var normal = new Vector(oldX - x, oldY - y, oldZ - z); //normal.Normalise(); var normal = voxelNormals[x, y, z]; Contract.Assume(normal.IsUnitVector); // DEBUGGING: visualise normals //colorSample = new Color(normal.x, normal.y, normal.z).ToARGB(); // TODO: also return correct rayFrac, pos and normal return(new IntersectionInfo { color = colorSample, normal = normal /*, pos = pos, rayFrac = (pos - start).Length */ }); //return new IntersectionInfo { color = sample, pos = pos, rayFrac = (pos - start).Length, normal = Vector.Up }; } } } return(null); }
// Test line walking by drawing lots of 2D lines on a view, to see how they look. public static void TestLineWalking(Surface view) { Contract.Requires(view != null); const int jump = 10; for (int col = 0; col < view.Width - 1; col++) { view.DrawPixel(col, 0, Color.Pink.ToARGB()); view.DrawPixel(col, view.Height - 1, Color.Pink.ToARGB()); } for (int col = 0; col < view.Height - 1; col++) { view.DrawPixel(0, col, Color.Cyan.ToARGB()); view.DrawPixel(view.Width - 1, col, Color.Cyan.ToARGB()); } for (int col = 0; col < view.Width / jump; col++) { foreach (var pt in LineWalker3D.WalkLine(new Vector(col * jump, 0, 0), new Vector(view.Width / 2, view.Height / 2, 0))) { view.DrawPixel((int)(pt.x + 0.5), (int)(pt.y + 0.5), Color.Blue.ToARGB()); } /* * foreach (var pt in LineWalker3D.WalkLine(col * jump, view.Height - 1, view.Width / 2, view.Height / 2)) * { * view.DrawViewPixel((int)(pt.x + 0.5), (int)(pt.y + 0.5), Color.Green.ToARGB()); * } */ } /* * for (int col = 0; col < view.Height / jump; col++) * { * foreach (var pt in LineWalker3D.WalkLine(0, col * jump, view.Width / 2, view.Height / 2)) * { * view.DrawViewPixel((int)(pt.x + 0.5), (int)(pt.y + 0.5), Color.Red.ToARGB()); * } * * foreach (var pt in LineWalker3D.WalkLine(view.Width - 1, col * jump, view.Width / 2, view.Height / 2)) * { * view.DrawViewPixel((int)(pt.x + 0), (int)(pt.y + 0), Color.Yellow.ToARGB()); * } * } */ //for (int row = 0; row < view.Height; row++) //{ // for (int col = 0; col < view.Width; col++) // { // Vector start = new Vector(mouseViewPos.x, mouseViewPos.y); // Vector end = new Vector(col, row); // var hitPt = map.IntersectWorldLine(start, end, 1); // var color = hitPt.IsInvalid() ? Color.Red : Color.Green; // //var color = map.IsSolidTile(col, row) ? Color.Red : Color.Green; // view.DrawViewPixel(col, row, color.ToARGB()); // } //} }