func ExampleBoundingBox_AddPoint() { box := NewBoundingBox() box.AddPoint(maths.NewVec3(-1, -1, -1)) box.AddPoint(maths.NewVec3(0, 5, -0.5)) box.AddPoint(maths.NewVec3(1, 0, 2)) fmt.Printf("%s\n", box) // Output: // bbox[min: (-1, -1, -1), max: (1, 5, 2)] }
func ExampleBoundingBox_Inside() { box := &BoundingBox{ MinVolume: [3]float64{-1, -1, -1}, MaxVolume: [3]float64{1, 1, 1}, } fmt.Printf("0, 0, 0: %v\n", box.Inside(maths.NewVec3(0, 0, 0))) fmt.Printf("0, 0, 2: %v\n", box.Inside(maths.NewVec3(0, 0, 2))) // Output: // 0, 0, 0: true // 0, 0, 2: false }
func TestGetBoundingBox(t *testing.T) { meshData := []byte(`{ "vertices": [ { "normal": [0, 0, 1], "coordinates": [-1, -2, 0] }, { "normal": [0, 0, 1], "coordinates": [1, 5, -8] }, { "normal": [0, 0, 1], "coordinates": [1, 2, 0] }, { "normal": [0, 0, 1], "coordinates": [1, 1, 0] } ], "faces": [ { "vertices": [0, 1, 2], "material": 42 }, { "vertices": [1, 2, 3], "material": 42 } ] }`) mesh := &Mesh{} err := json.Unmarshal(meshData, &mesh) if err != nil { t.Fatalf("Error reading json: %s\n", err) return } mesh.Init() bbox := mesh.GetBoundingBox() assertEqualVectors(t, maths.NewVec3(1, 5, 0), maths.NewVec3Array(bbox.MaxVolume)) assertEqualVectors(t, maths.NewVec3(-1, -2, -8), maths.NewVec3Array(bbox.MinVolume)) }
func ExamplePinholeCamera_ShootRay_weirdfocus() { var c Camera = &PinholeCamera{ Focus: *maths.NewVec3(-2, 15, 3), TopLeft: *maths.NewVec3(-3, 16, 4), TopRight: *maths.NewVec3(-1, 16, 4), BottomLeft: *maths.NewVec3(-3, 16, 2), } ray := c.ShootRay(0, 0) fmt.Printf("0, 0: %s\n", ray) ray = c.ShootRay(0.5, 0.5) fmt.Printf("0.5, 0.5: %s\n", ray) // Output: // 0, 0: (-2, 15, 3) -> (-0.577, 0.577, 0.577) // 0.5, 0.5: (-2, 15, 3) -> (0, 1, 0) }
// Vec3Sphere returns a random unit vector func (r *Random) Vec3Sphere() *maths.Vec3 { u := r.FloatAB(-1, 1) theta := r.Float02Pi() return maths.NewVec3( math.Sqrt(1-u*u)*math.Cos(theta), math.Sqrt(1-u*u)*math.Sin(theta), u, ) }
func TestPinholeCameraJson(t *testing.T) { data := []byte(`{ "focus": [1, 2, 3], "top_left": [4, 5, 6], "top_right": [7, 8, 9], "bottom_left": [10, 11, 12] }`) c := &PinholeCamera{} err := json.Unmarshal(data, &c) if err != nil { t.Error(err) } assertEqualVectors(t, maths.NewVec3(1, 2, 3), &c.Focus) assertEqualVectors(t, maths.NewVec3(4, 5, 6), &c.TopLeft) assertEqualVectors(t, maths.NewVec3(7, 8, 9), &c.TopRight) assertEqualVectors(t, maths.NewVec3(10, 11, 12), &c.BottomLeft) }
func ExamplePinholeCamera_ShootRay() { var c Camera = &PinholeCamera{ Focus: *maths.NewVec3(0, 0, 0), TopLeft: *maths.NewVec3(-1, 1, 1), TopRight: *maths.NewVec3(1, 1, 1), BottomLeft: *maths.NewVec3(-1, 1, -1), } ray := c.ShootRay(0, 0) fmt.Printf("0, 0: %s\n", ray) ray = c.ShootRay(0.5, 0.5) fmt.Printf("0.5, 0.5: %s\n", ray) // Output: // 0, 0: (0, 0, 0) -> (-0.577, 0.577, 0.577) // 0.5, 0.5: (0, 0, 0) -> (0, 1, 0) }
// Init of Mesh sets the Triangle indices in the Faces array, calculates the bounding box // and KD tree, sets the surfaceOx and Oy and Cross Products of the sides of each triangle func (m *Mesh) Init() { allIndices := make([]int, len(m.Faces)) for i := range allIndices { allIndices[i] = i } m.BoundingBox = m.GetBoundingBox() m.tree = m.newKDtree(m.BoundingBox, allIndices, 0) for i := range m.Faces { triangle := &m.Faces[i] A := &m.Vertices[triangle.Vertices[0]].Coordinates B := &m.Vertices[triangle.Vertices[1]].Coordinates C := &m.Vertices[triangle.Vertices[2]].Coordinates AB := maths.MinusVectors(B, A) AC := maths.MinusVectors(C, A) triangle.AB = AB triangle.AC = AC triangle.ABxAC = maths.CrossProduct(AB, AC) surfaceA := &m.Vertices[triangle.Vertices[0]].UV surfaceB := &m.Vertices[triangle.Vertices[1]].UV surfaceC := &m.Vertices[triangle.Vertices[2]].UV surfaceAB := maths.MinusVectors(surfaceB, surfaceA) surfaceAC := maths.MinusVectors(surfaceC, surfaceA) // Solve using Cramer: // |surfaceAB.X * px + surfaceAX.X *qx + 1 = 0 // |surfaceAB.Y * px + surfaceAX.Y *qx = 0 // and // surfaceAB.X * py + surfaceAX.X *qy = 0 // surfaceAB.X * py + surfaceAX.X *qy + 1 = 0 px, qx := maths.SolveEquation(surfaceAB, surfaceAC, maths.NewVec3(1, 0, 0)) py, qy := maths.SolveEquation(surfaceAB, surfaceAC, maths.NewVec3(0, 1, 0)) triangle.surfaceOx = maths.AddVectors(AB.Scaled(px), AC.Scaled(qx)) triangle.surfaceOy = maths.AddVectors(AB.Scaled(py), AC.Scaled(qy)) } }
func TestAnyCameraJson(t *testing.T) { data := []byte(`{ "type": "pinhole", "focus": [0, 0, 0], "top_left": [-1, 1, 1], "top_right": [1, 1, 1], "bottom_left": [-1, 1, -1] }`) c := &AnyCamera{} err := json.Unmarshal(data, &c) if err != nil { t.Error(err) } ray := c.ShootRay(0.5, 0.5) assertEqualVectors(t, maths.NewVec3(0, 1, 0), &ray.Direction) assertEqualVectors(t, maths.NewVec3(0, 0, 0), &ray.Start) }
func TestBoundingBoxSplit(t *testing.T) { box := &BoundingBox{ MinVolume: [3]float64{-3.18, -3.96, -1.77}, MaxVolume: [3]float64{4.58, 1.74, 4.56}, } ray := &ray.Ray{ Start: *maths.NewVec3(4.49, -7.3, 5.53), Direction: *maths.NewVec3(-0.60, 0.5, -0.62), } ray.Init() if !box.Intersect(ray) { t.Error("ray should intersect big box") } left, right := box.Split(2, 1.39) assertEqualVectors(t, maths.NewVec3(-3.18, -3.96, -1.77), maths.NewVec3Array(left.MinVolume)) assertEqualVectors(t, maths.NewVec3(-3.18, -3.96, 1.39), maths.NewVec3Array(right.MinVolume)) assertEqualVectors(t, maths.NewVec3(4.58, 1.74, 1.39), maths.NewVec3Array(left.MaxVolume)) assertEqualVectors(t, maths.NewVec3(4.58, 1.74, 4.56), maths.NewVec3Array(right.MaxVolume)) if box.IntersectWall(2, 1.39, ray) { t.Error("ray shouldn't intersect the wall") } if right.Intersect(ray) { t.Error("ray shouldn't intersect right") } if !left.Intersect(ray) { t.Error("ray should intersect left") } }
// UnmarshalJSON implements the json.Unmarshaler interface func (n *NumberSampler) UnmarshalJSON(data []byte) error { err := json.Unmarshal(data, &n.Fac) if err != nil { return err } n.Colour = hdrcolour.New( float32(n.Fac), float32(n.Fac), float32(n.Fac), ) n.Vector = maths.NewVec3(n.Fac, n.Fac, n.Fac) return nil }
func ExampleBoundingBox_Intersect() { box := &BoundingBox{ MinVolume: [3]float64{-1, -1, -1}, MaxVolume: [3]float64{1, 1, 1}, } ray1 := &ray.Ray{ Start: *maths.NewVec3(0, 0, 0), Direction: *maths.NewVec3(1, 0, 0), } ray2 := &ray.Ray{ Start: *maths.NewVec3(-5, 0, 0.5), Direction: *maths.NewVec3(1, 0, 0), } ray3 := &ray.Ray{ Start: *maths.NewVec3(-5, 0, 0.5), Direction: *maths.NewVec3(-1, 0, 0), } ray4 := &ray.Ray{ Start: *maths.NewVec3(-5, 0, 0), Direction: *maths.NewVec3(1, 0, 5), } ray1.Init() ray2.Init() ray3.Init() ray4.Init() fmt.Printf("ray 1: %v\n", box.Intersect(ray1)) fmt.Printf("ray 2: %v\n", box.Intersect(ray2)) fmt.Printf("ray 3: %v\n", box.Intersect(ray3)) fmt.Printf("ray 4: %v\n", box.Intersect(ray4)) // Output: // ray 1: true // ray 2: true // ray 3: false // ray 4: false }
// Vec3HemiCos returns a random unit vector chosen on a // cosine-weighed hemisphere defined by normal func (r *Random) Vec3HemiCos(normal *maths.Vec3) *maths.Vec3 { ox := maths.CrossProduct(maths.NewVec3(42, 56, -15), normal) for math.Abs(ox.Length()) < maths.Epsilon { ox = maths.CrossProduct(r.Vec3Sphere(), normal) } oy := maths.CrossProduct(ox, normal) ox.Normalise() oy.Normalise() u := r.Float01() radius := math.Sqrt(u) theta := r.Float02Pi() vec := normal.Scaled(math.Sqrt(math.Max(0, 1-u))) vec.Add(ox.Scaled(radius * math.Cos(theta))) vec.Add(oy.Scaled(radius * math.Sin(theta))) return vec }
func TestIntersectBoundingBoxZeroVolume(t *testing.T) { box := NewBoundingBox() box.AddPoint(maths.NewVec3(0, 0, 0)) box.AddPoint(maths.NewVec3(1, 0, 0)) box.AddPoint(maths.NewVec3(0, 1, 0)) box.AddPoint(maths.NewVec3(1, 1, 0)) ray := &ray.Ray{ Start: *maths.NewVec3(0.5, 0.5, 1), Direction: *maths.NewVec3(0, 0, -1), } if !box.Intersect(ray) { t.Error("ray.Ray must intersect box") } }
// GetVec3 returns the colour at (u, v)'s components as a vector func (i *ImageTexture) GetVec3(intersection *ray.Intersection) *maths.Vec3 { colour := i.GetColour(intersection) return maths.NewVec3(float64(colour.R), float64(colour.G), float64(colour.B)) }
func TestIntersectTwoTriangles(t *testing.T) { meshData := []byte(`{ "vertices": [ { "normal": [0, 0, 1], "coordinates": [0, 0, 0], "uv": [0, 0] }, { "normal": [0, 0, 1], "coordinates": [1, 0, 0], "uv": [1, 0] }, { "normal": [0, 0, 1], "coordinates": [0, 1, 0], "uv": [0, 1] }, { "normal": [0, 0, -1], "coordinates": [0, 0, 4], "uv": [0, 0] }, { "normal": [0, 0, -1], "coordinates": [1, 0, 4], "uv": [1, 0] }, { "normal": [0, 0, -1], "coordinates": [0, 1, 4], "uv": [0, 1] } ], "faces": [ { "vertices": [3, 4, 5], "material": 5 }, { "vertices": [0, 1, 2], "material": 42 } ] }`) mesh := &Mesh{} err := json.Unmarshal(meshData, &mesh) if err != nil { t.Fatalf("Error reading json: %s\n", err) } mesh.Init() testRay := &ray.Ray{ Start: *maths.NewVec3(0.15, 0.11, 1), Direction: *maths.NewVec3(0, 0, -1), } var intersection *ray.Intersection intersection = mesh.Intersect(testRay) if intersection == nil { t.Fatal("Intersection shouldn't be nil") } if intersection.Material != 42 { t.Error("Intersected wrong triangle") } testRay.Direction.Z = 1 intersection = mesh.Intersect(testRay) if intersection == nil { t.Fatal("Intersection shouldn't be nil") } if intersection.Material != 5 { t.Error("Intersected wrong triangle") } }
func ExampleMesh_Intersect() { meshData := []byte(`{ "vertices": [ { "normal": [0, 0, 1], "coordinates": [0, 0, 0], "uv": [0, 0] }, { "normal": [0, 0, 1], "coordinates": [1, 0, 0], "uv": [1, 0] }, { "normal": [0, 0, 1], "coordinates": [0, 1, 0], "uv": [0, 1] } ], "faces": [ { "vertices": [0, 1, 2], "material": 42 } ] }`) mesh := &Mesh{} err := json.Unmarshal(meshData, &mesh) if err != nil { fmt.Printf("Error reading json: %s\n", err) return } mesh.Init() testRay := &ray.Ray{ Start: *maths.NewVec3(0.15, 0.11, 1), Direction: *maths.NewVec3(0, 0, -1), } var ( intersection *ray.Intersection ) intersection = mesh.Intersect(testRay) if intersection == nil { fmt.Printf("no intersection\n") } else { fmt.Printf("intersection point: %s\n", intersection.Point) fmt.Printf("caused by ray: %s\n", intersection.Incoming) fmt.Printf("at a distance: %.3g\n", intersection.Distance) fmt.Printf("with surface coordinates: (%.3g, %.3g)\n", intersection.U, intersection.V) fmt.Printf("surface normal: %s\n", intersection.Normal) fmt.Printf("surface coordinate system: Ox: %s, Oy: %s\n", intersection.SurfaceOx, intersection.SurfaceOy) fmt.Printf("surface material: %d\n", intersection.Material) } // Output: // intersection point: (0.15, 0.11, 0) // caused by ray: (0.15, 0.11, 1) -> (0, 0, -1) // at a distance: 1 // with surface coordinates: (0.15, 0.11) // surface normal: (0, 0, 1) // surface coordinate system: Ox: (1, 0, 0), Oy: (0, 1, 0) // surface material: 42 }