module Bih (bih) where
import Vec
import Solid
import Data.List hiding (group) -- for "partition"
-- Bounding Interval Heirarchy
-- http://en.wikipedia.org/wiki/Bounding_interval_hierarchy
data Bih = Bih {bihbb :: Bbox, bihroot :: BihNode} deriving Show
data BihNode = BihLeaf !SolidItem
| BihBranch {lmax :: !Flt, rmin :: !Flt, ax :: !Int,
l :: BihNode, r :: BihNode} deriving Show
-- bih construction
build_leaf :: [(Bbox, SolidItem)] -> BihNode
build_leaf objs =
BihLeaf (group (map snd objs))
-- tuning parameter that controls threshold for separating
-- large objects from small objects instead of usual left/right
-- sorting
max_bih_sa = 0.3 :: Flt
build_rec :: [(Bbox,SolidItem)] -> Bbox -> Bbox -> Int -> BihNode
build_rec objs nodebox splitbox depth =
-- if (null objs) || (null $ tail objs) ||
-- (null $ tail $ tail objs)
if length objs < 2
then build_leaf objs
else
let (Bbox nodeboxp1 nodeboxp2) = nodebox
(Bbox splitboxp1 splitboxp2) = splitbox
axis = vmaxaxis (vsub splitboxp2 splitboxp1)
bbmin = va splitboxp1 axis
bbmax = va splitboxp2 axis
candidate = (bbmin + bbmax) * 0.5
in
if candidate > (va nodeboxp2 axis) then
build_rec objs nodebox
(Bbox splitboxp1 (vset splitboxp2 axis candidate))
depth
else
if candidate < (va nodeboxp1 axis) then
build_rec objs nodebox (
Bbox (vset splitboxp1 axis candidate) splitboxp2)
depth
else
-- not sure if this is a big win
let nbsa = bbsa nodebox
(big,small) = partition (\ (bb,_) ->
(bbsa bb) > (nbsa * max_bih_sa)) objs
in
if (not $ null big) && ((length big) < ((length small)*2))
then (BihBranch (va nodeboxp2 0) (va nodeboxp1 0) 0
(build_rec big nodebox splitbox (depth+1))
(build_rec small nodebox splitbox (depth+1)) )
else
let (l,r) = partition (\((Bbox bbp1 bbp2),_)->
(((va bbp1 axis)+(va bbp2 axis))*0.5)
< candidate ) objs
lmax = foldl fmax (-infinity) (map (\((Bbox _ p2),_) -> va p2 axis) l)
rmin = foldl fmin infinity (map (\((Bbox p1 _),_) -> va p1 axis) r)
(lsplit,rsplit) = bbsplit splitbox axis candidate
lnb = (Bbox nodeboxp1 (vset nodeboxp2 axis lmax))
rnb = (Bbox (vset nodeboxp1 axis rmin) nodeboxp2)
in
-- stop if there's no progress being made
if ((null l) && (rmin <= bbmin)) ||
((null r) && (lmax >= bbmax))
then build_leaf objs
else
(BihBranch (lmax+delta) (rmin-delta) axis
(build_rec l lnb lsplit (depth+1))
(build_rec r rnb rsplit (depth+1)) )
bih :: [SolidItem] -> SolidItem
bih [] = SolidItem Void
-- bih (sld:[]) = sld -- sometimes we'd like to be able to use a
-- single object bih just for its aabb
bih slds =
let objs = map (\x -> ((bound x),x)) (flatten_group slds)
bb = foldl bbjoin empty_bbox (map (\(b,_)->b) objs)
root = build_rec objs bb bb 0
(Bbox (Vec p1x p1y p1z) (Vec p2x p2y p2z)) = bb
in
if p1x == (-infinity) || p1y == (-infinity) || p1z == (-infinity) ||
p2x == infinity || p2y == infinity || p2z == infinity
then
error $ "bih: infinite bounding box " ++ (show objs)
else
SolidItem (Bih bb root)
rayint_bih :: Bih -> Ray -> Flt -> Texture -> Rayint
rayint_bih (Bih bb root) r d t =
let Ray orig dir = r
dir_rcp = vrcp dir
Interval near far = bbclip r bb
traverse (BihLeaf s) near far = rayint s r (fmin d far) t
traverse (BihBranch lsplit rsplit axis l r) near far =
let dirr = va dir_rcp axis
o = va orig axis
dl = (lsplit - o) * dirr
dr = (rsplit - o) * dirr
in
if near > far
then RayMiss
else
if dirr > 0
then
(nearest
(if near < dl
then traverse l near (fmin dl far)
else RayMiss)
(if dr < far
then traverse r (fmax dr near) far
else RayMiss))
else
(nearest
(if near < dr
then traverse r near (fmin dr far)
else RayMiss)
(if dl < far
then traverse l (fmax dl near) far
else RayMiss))
in
traverse root near far
-- This is unwieldy, but the performance gains
-- make it worthwhile. By testing 4 rays against
-- each cell, we do 1/4 the memory accesses.
-- One simplifying assumption we make that adds a
-- little bit of overhead: If one ray hits a cell,
-- we act as though they all do. For that reason,
-- this only works well with coherent rays.
packetint_bih :: Bih -> Ray -> Ray -> Ray -> Ray -> Flt -> Texture -> PacketResult
packetint_bih (Bih bb root) r1 r2 r3 r4 d t =
let bih = Bih bb root
Ray orig1 dir1 = r1
Ray orig2 dir2 = r2
Ray orig3 dir3 = r3
Ray orig4 dir4 = r4
dir_rcp1 = vrcp dir1
dir_rcp2 = vrcp dir2
dir_rcp3 = vrcp dir3
dir_rcp4 = vrcp dir4
in
-- We want all the ray components to have
-- at least the same sign.
if not $ veqsign dir_rcp1 dir_rcp2 &&
veqsign dir_rcp1 dir_rcp3 &&
veqsign dir_rcp1 dir_rcp4
then
PacketResult (rayint bih r1 d t)
(rayint bih r2 d t)
(rayint bih r3 d t)
(rayint bih r4 d t)
else
let Interval near1 far1 = bbclip r1 bb
Interval near2 far2 = bbclip r2 bb
Interval near3 far3 = bbclip r3 bb
Interval near4 far4 = bbclip r4 bb
near = fmin (fmin near1 near2) (fmin near3 near4)
far = fmax (fmax far1 far2) (fmax far3 far4)
traverse (BihLeaf s) near far = packetint s r1 r2 r3 r4 (fmin d far) t
traverse (BihBranch lsplit rsplit axis l r) near far =
if near > far
then packetmiss
else
let dirr1 = va dir_rcp1 axis
dirr2 = va dir_rcp2 axis
dirr3 = va dir_rcp3 axis
dirr4 = va dir_rcp4 axis
o1 = va orig1 axis
o2 = va orig2 axis
o3 = va orig3 axis
o4 = va orig4 axis
dl1 = (lsplit - o1) * dirr1
dl2 = (lsplit - o2) * dirr2
dl3 = (lsplit - o3) * dirr3
dl4 = (lsplit - o4) * dirr4
dr1 = (rsplit - o1) * dirr1
dr2 = (rsplit - o2) * dirr2
dr3 = (rsplit - o3) * dirr3
dr4 = (rsplit - o4) * dirr4
in
if dirr1 > 0 -- true for all, since signs match
then
let dl = fmax4 dl1 dl2 dl3 dl4
dr = fmin4 dr1 dr2 dr3 dr4
in
(nearest_packetresult
(if near < dl
then traverse l near (fmin dl far)
else packetmiss)
(if dr < far
then traverse r (fmax dr near) far
else packetmiss))
else
let dl = fmin4 dl1 dl2 dl3 dl4
dr = fmax4 dr1 dr2 dr3 dr4
in
(nearest_packetresult
(if near < dr
then traverse r near (fmin dr far)
else packetmiss)
(if dl < far
then traverse l (fmax dl near) far
else packetmiss))
in
traverse root near far
shadow_bih :: Bih -> Ray -> Flt -> Bool
shadow_bih (Bih bb root) r d =
let (Ray orig dir) = r
dir_rcp = vrcp dir
Interval near far = bbclip r bb
traverse (BihLeaf s) near far = shadow s r (fmin d far)
traverse (BihBranch lsplit rsplit axis l r) near far =
let dirr = va dir_rcp axis
o = va orig axis
dl = (lsplit - o) * dirr
dr = (rsplit - o) * dirr
in
if near > far
then False
else
if dirr > 0
then
((if near < dl
then traverse l near (fmin dl far)
else False)
||
(if dr < far
then traverse r (fmax dr near) far
else False))
else
((if near < dr
then traverse r near (fmin dr far)
else False)
||
(if dl < far
then traverse l (fmax dl near) far
else False))
in traverse root near far
inside_bih :: Bih -> Vec -> Bool
inside_bih (Bih (Bbox (Vec x1 y1 z1) (Vec x2 y2 z2)) root) pt =
let (Vec x y z) = pt
traverse (BihLeaf s) = inside s pt
traverse (BihBranch lsplit rsplit axis l r) =
let o = va pt axis
in (if o < lsplit
then (traverse l)
else False)
||
(if o > rsplit
then (traverse r)
else False)
in
(x > x1) && (x < x2) &&
(y > y1) && (y < y2) &&
(z > z1) && (z < z2) && (traverse root)
bound_bih :: Bih -> Bbox
bound_bih (Bih bb root) = bb
instance Solid Bih where
rayint = rayint_bih
packetint = packetint_bih
shadow = shadow_bih
inside = inside_bih
bound = bound_bih