-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathactors.go
159 lines (136 loc) · 4.23 KB
/
actors.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package gotrace
import (
"math/rand"
"sort"
)
// HitRecord defines the intersection of a Ray and an Actor
type HitRecord struct {
Distance float64
Position Vec3
Normal Vec3
Material Material
U, V float64
}
// Actor is an object on the scene having a shape and a material
type Actor struct {
shape Geometry
material Material
}
// Hit checks if the geometry is hit by the ray, and creates a HitRecord with the actor's material
func (a Actor) Hit(ray Ray, tMin float64, tMax float64) (bool, *HitRecord) {
if hit, record := a.shape.Hit(ray, tMin, tMax); hit {
record.Material = a.material
return true, record
}
return false, nil
}
// Bound returns the bounding box of an actor, which is defined by its shape
func (a Actor) Bound(startTime float64, endTime float64) (bool, *Bbox) {
return a.shape.Bound(startTime, endTime)
}
// A Collection is a group of Actors
type Collection []Actor
// Add appends new Actors to the collection
func (c *Collection) Add(actors ...Actor) {
*c = append(*c, actors...)
}
// Hit returns the closest intersection of a Ray with a Collection if such an intersection exists, otherwise it returns false with a nil pointer
func (c Collection) Hit(ray Ray, tMin float64, tMax float64) (bool, *HitRecord) {
hitAnything := false
closestHit := tMax
var closestRecord *HitRecord = nil
for i := 0; i < len(c); i++ {
if hit, record := c[i].Hit(ray, tMin, closestHit); hit {
closestRecord = record
closestHit = record.Distance
hitAnything = true
}
}
return hitAnything, closestRecord
}
// Bound computes the bounding box of a Collection
func (c Collection) Bound(tMin float64, tMax float64) (bool, *Bbox) {
if len(c) == 0 {
return false, nil
}
var collectionBox Bbox
firstBox := true
for _, actor := range c {
if bounded, box := actor.shape.Bound(tMin, tMax); bounded {
if firstBox {
collectionBox = *box
firstBox = false
} else {
collectionBox = collectionBox.Merge(*box)
}
} else {
return false, nil
}
}
return true, &collectionBox
}
// Comparator returns a comparison function of objects in the collection along the given axis. Used for Index sorting.
func (c Collection) Comparator(startTime, endTime float64, axis int) func(i, j int) bool {
return func(i, j int) bool {
leftBound, leftBox := c[i].Bound(startTime, endTime)
rightBound, rightBox := c[j].Bound(startTime, endTime)
if !leftBound || !rightBound {
panic("no bounding box")
}
return leftBox.Min.AsArray()[axis] < rightBox.Min.AsArray()[axis]
}
}
// Index is a binary tree forming a bounding volume hierarchy of objects satisfying the geometry interface
type Index struct {
box Bbox
left Geometry
right Geometry
}
// NewIndex builds a bounding volume hierarchy
func NewIndex(world Collection, start, end int, startTime, endTime float64) *Index {
// chose random axis for sorting
comparator := world.Comparator(startTime, endTime, rand.Intn(3))
idx := Index{}
span := end - start + 1
if span == 1 {
idx.left = world[start]
idx.right = world[start]
} else if span == 2 {
if comparator(start, end) {
idx.left = world[start]
idx.right = world[start+1]
} else {
idx.left = world[start+1]
idx.right = world[start]
}
} else {
sort.Slice(world[start:end], comparator) //TODO does this sort the view or the slice ?
mid := start + span/2
idx.left = NewIndex(world, start, mid, startTime, endTime)
idx.right = NewIndex(world, mid, end, startTime, endTime)
}
_, leftBox := idx.left.Bound(startTime, endTime) // TODO error handling if no box (infinite planes)
_, rightBox := idx.right.Bound(startTime, endTime)
idx.box = leftBox.Merge(*rightBox)
return &idx
}
// Hit implements the hit interface for the Index
func (idx *Index) Hit(ray Ray, tMin float64, tMax float64) (bool, *HitRecord) {
if !idx.box.Hit(ray, tMin, tMax) {
return false, nil
}
// two different records to not override by nil pointer if not in right
hitLeft, recordLeft := idx.left.Hit(ray, tMin, tMax)
if hitLeft {
tMax = recordLeft.Distance
}
hitRight, recordRight := idx.right.Hit(ray, tMin, tMax)
if hitRight {
return true, recordRight
}
return hitLeft, recordLeft
}
// Bound returns the bounding box of the Index
func (idx *Index) Bound(tMin float64, tMax float64) (bool, *Bbox) {
return true, &idx.box
}