// Vec3Hemi returns a random unit vector in the hemisphere defined by normal func (r *Random) Vec3Hemi(normal *maths.Vec3) *maths.Vec3 { vec := r.Vec3Sphere() if maths.DotProduct(vec, normal) < 0 { vec.Negate() } return vec }
// Shade returns the emitted colour after intersecting the material func (m *RefractiveMaterial) Shade(intersection *ray.Intersection, raytracer Raytracer) *hdrcolour.Colour { incoming := &intersection.Incoming.Direction normal := intersection.Normal ior := m.IOR.GetFac(intersection) colour := m.Colour.GetColour(intersection) refracted := &maths.Vec3{} startPoint := &maths.Vec3{} if maths.DotProduct(incoming, normal) < 0 { refracted = maths.Refract(incoming, normal, 1/ior) } else { refracted = maths.Refract(incoming, normal.Negative(), ior) } if refracted != nil { // regular refraction - push the starting point a tiny bit // through the surface startPoint = maths.MinusVectors( intersection.Point, normal.FaceForward(incoming).Scaled(maths.Epsilon), ) } else { // total inner reflection refracted = incoming.Reflected(normal.FaceForward(incoming)) // push the starting point a tiny bit away from the surface startPoint = maths.AddVectors( intersection.Point, normal.FaceForward(incoming).Scaled(maths.Epsilon), ) } newRay := &ray.Ray{Depth: intersection.Incoming.Depth + 1} newRay.Start = *startPoint newRay.Direction = *refracted return hdrcolour.MultiplyColours(raytracer.Raytrace(newRay), colour) }
func TestVectors(t *testing.T) { assert := assert.New(t) rnd := New(42) vec := rnd.Vec3Sphere() assert.InDelta(1, vec.Length(), maths.Epsilon, "Random unit vector's length should be 1") norm := vec vec = rnd.Vec3Hemi(norm) if maths.DotProduct(vec, norm) < 0 { t.Error("Random hemi vector is in the wrong hemisphere") } vec = rnd.Vec3HemiCos(norm) if maths.DotProduct(vec, norm) < 0 { t.Error("Random cosine-weighed hemi vector is in the wrong hemisphere") } }
// IntersectTriangle find whether there's an intersection point between the ray and the triangle // using barycentric coordinates and calculate the distance func IntersectTriangle(ray *ray.Ray, A, B, C *maths.Vec3) (bool, float64) { AB := maths.MinusVectors(B, A) AC := maths.MinusVectors(C, A) reverseDirection := ray.Direction.Negative() distToA := maths.MinusVectors(&ray.Start, A) ABxAC := maths.CrossProduct(AB, AC) det := maths.DotProduct(ABxAC, reverseDirection) reverseDet := 1 / det if math.Abs(det) < maths.Epsilon { return false, maths.Inf } lambda2 := maths.MixedProduct(distToA, AC, reverseDirection) * reverseDet lambda3 := maths.MixedProduct(AB, distToA, reverseDirection) * reverseDet gamma := maths.DotProduct(ABxAC, distToA) * reverseDet if gamma < 0 { return false, maths.Inf } if lambda2 < 0 || lambda2 > 1 || lambda3 < 0 || lambda3 > 1 || lambda2+lambda3 > 1 { return false, maths.Inf } return true, gamma }
// IntersectTriangle checks if the bounding box intersects with a triangle // 1) To have a vertex in the box // 2) The edge of the triangle intersects with the box // 3) The middle of the triangle to be inside the box, while the vertices aren't func (b *BoundingBox) IntersectTriangle(A, B, C *maths.Vec3) bool { if b.Inside(A) || b.Inside(B) || b.Inside(C) { return true } // we construct the ray from A->B, A->C, etc and check whether it intersects with the box triangle := [3]*maths.Vec3{A, B, C} ray := &ray.Ray{} for rayStart := 0; rayStart < 3; rayStart++ { for rayEnd := rayStart + 1; rayEnd < 3; rayEnd++ { ray.Start = *(triangle[rayStart]) ray.Direction = *maths.MinusVectors(triangle[rayEnd], triangle[rayStart]) ray.Init() // Check if there's intersection between AB and the box (example) if b.Intersect(ray) { // we check whether there's intersection between BA and the Box too // to be sure the edge isn't outside the box // _____ // | | <----AB there is intersection, but BA-----> // |____| ray.Start = *triangle[rayEnd] ray.Direction = *maths.MinusVectors(triangle[rayStart], triangle[rayEnd]) ray.Init() if b.Intersect(ray) { return true } } } } // we have to check if the inside of the triangle intersects with the box AB := maths.MinusVectors(B, A) AC := maths.MinusVectors(C, A) ABxAC := maths.CrossProduct(AB, AC) distance := maths.DotProduct(A, ABxAC) rayEnd := &maths.Vec3{} for edgeMask := 0; edgeMask < 7; edgeMask++ { for axis := uint(0); axis < 3; axis++ { if edgeMask&(1<<axis) != 0 { continue } if edgeMask&1 != 0 { ray.Start.X = b.MaxVolume[0] } else { ray.Start.X = b.MinVolume[0] } if edgeMask&2 != 0 { ray.Start.Y = b.MaxVolume[1] } else { ray.Start.Y = b.MinVolume[1] } if edgeMask&4 != 0 { ray.Start.Z = b.MaxVolume[2] } else { ray.Start.Z = b.MinVolume[2] } *rayEnd = ray.Start rayEnd.SetDimension(int(axis), b.MaxVolume[axis]) if (maths.DotProduct(&ray.Start, ABxAC)-distance)*(maths.DotProduct(rayEnd, ABxAC)-distance) <= 0 { ray.Direction = *maths.MinusVectors(rayEnd, &ray.Start) ray.Init() intersected, distance := IntersectTriangle(ray, A, B, C) if intersected && distance < 1.0000001 { return true } } } } return false }
// IntersectTriangle returns whether there's an intersection between the ray and the triangle, // using barycentric coordinates and takes the point only if it's closer to the // previously found intersection and the point is within the bounding box func (m *Mesh) intersectTriangle(ray *ray.Ray, triangle *Triangle, intersection *ray.Intersection, boundingBox *BoundingBox) bool { // lambda2 * AB + lambda3 * AC - intersectDist*rayDir = distToA // If the triangle is ABC, this gives you A A := &m.Vertices[triangle.Vertices[0]].Coordinates distToA := maths.MinusVectors(&ray.Start, A) rayDir := ray.Direction ABxAC := triangle.ABxAC // We will find the barycentric coordinates using Cramer's formula, so we'll need the determinant // det is (AB^AC)*dir of the ray, but we're gonna use 1/det, so we find the recerse: det := -maths.DotProduct(ABxAC, &rayDir) if math.Abs(det) < maths.Epsilon { return false } reverseDet := 1 / det intersectDist := maths.DotProduct(ABxAC, distToA) * reverseDet if intersectDist < 0 || intersectDist > intersection.Distance { return false } // lambda2 = (dist^dir)*AC / det // lambda3 = -(dist^dir)*AB / det lambda2 := maths.MixedProduct(distToA, &rayDir, triangle.AC) * reverseDet lambda3 := -maths.MixedProduct(distToA, &rayDir, triangle.AB) * reverseDet if lambda2 < 0 || lambda2 > 1 || lambda3 < 0 || lambda3 > 1 || lambda2+lambda3 > 1 { return false } ip := maths.AddVectors(&ray.Start, (&rayDir).Scaled(intersectDist)) // If we aren't inside the bounding box, there could be a closer intersection // within the bounding box if boundingBox != nil && !boundingBox.Inside(ip) { return false } intersection.Point = ip intersection.Distance = intersectDist if triangle.Normal != nil { intersection.Normal = triangle.Normal } else { // We solve intersection.normal = Anormal + AB normal * lambda2 + ACnormal * lambda 3 Anormal := &m.Vertices[triangle.Vertices[0]].Normal Bnormal := &m.Vertices[triangle.Vertices[1]].Normal Cnormal := &m.Vertices[triangle.Vertices[2]].Normal ABxlambda2 := maths.MinusVectors(Bnormal, Anormal).Scaled(lambda2) ACxlambda3 := maths.MinusVectors(Cnormal, Anormal).Scaled(lambda3) intersection.Normal = maths.AddVectors(Anormal, maths.AddVectors(ABxlambda2, ACxlambda3)) } uvA := &m.Vertices[triangle.Vertices[0]].UV uvB := &m.Vertices[triangle.Vertices[1]].UV uvC := &m.Vertices[triangle.Vertices[2]].UV // We solve intersection.uv = uvA + uvAB * lambda2 + uvAC * lambda 3 uvABxlambda2 := maths.MinusVectors(uvB, uvA).Scaled(lambda2) uvACxlambda3 := maths.MinusVectors(uvC, uvA).Scaled(lambda3) uv := maths.AddVectors(uvA, maths.AddVectors(uvABxlambda2, uvACxlambda3)) intersection.U = uv.X intersection.V = uv.Y intersection.SurfaceOx = triangle.surfaceOx intersection.SurfaceOy = triangle.surfaceOy intersection.Incoming = ray intersection.Material = triangle.Material return true }