Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
257 lines (206 sloc) 13 KB
% build me with xelatex + bibtex
\documentclass[a4paper,10pt]{article}
\usepackage{hyperref}
\usepackage{mathpazo}
\usepackage{amsmath}
\usepackage[small,bf,hang]{caption}
\usepackage{xunicode}
\usepackage[numbers,square,sort]{natbib}
\usepackage{tikz}
\usetikzlibrary{calc,3d}
\setlength{\oddsidemargin}{-0.4mm} % 25 mm left margin - 1 in
\setlength{\evensidemargin}{\oddsidemargin}
\setlength{\topmargin}{-5.4mm} % 20 mm top margin - 1 in
\setlength{\textwidth}{160mm} % 20/25 mm right margin
\setlength{\textheight}{247mm} % 20 mm bottom margin
\setlength{\headheight}{5.1mm}
\setlength{\headsep}{5mm}
\setlength{\parindent}{0mm}
\setlength{\parskip}{\medskipamount}
\title{Details of Ray/OBB Intersection}
\author{Wildfire Games -- \url{http://wildfiregames.com/}}
\hypersetup {
pdfauthor = {Wildfire Games},
pdftitle = {Details of Ray/OBB Intersection},
pdfsubject = {Some details of how the slabs method for determining ray/OBB intersection works.}
pdfkeywords = {Ray, Box, OBB, Intersect},
}
\newcommand{\vectornorm}[1]{\left|\left|#1\right|\right|}
\newcommand{\mathdef}{\ensuremath{\overset{\Delta}{=}}}
\begin{document}
\maketitle
\section{Definitions}
A ray is defined as a collection of points $\mathbf{r}(t)$ such that:
\begin{equation*}
\mathbf{r}(t) = \mathbf{o} + t \mathbf{d}
\end{equation*}
where $\mathbf{o}$ denotes the origin of the ray, $\mathbf{d}$ is a unit vector indicating the direction of the ray, and $t$ is an independent scalar parameter that specifies the distance of $\mathbf{r}(t)$ from the origin of the ray. Points that are generated by negative values of $t$ are said to lie behind the ray origin, and are not considered part of the ray.
An OBB, or Oriented Bounding Box, is an arbitarily-rotated three-dimensional rectangular cuboid defined by a center point $\mathbf{a}_c$, a set of mutually orthonormal basis vectors $(\mathbf{a}_u, \mathbf{a}_v, \mathbf{a}_w)$, and the half-distances $(h_u, h_v, h_w)$ of each face from the origin along their respective axes.
\section{Method}
In 0 A.D., rays are tested for intersection with OBBs using the slab method as outlined in \cite{real_time_rendering_3}. In brief, the goal of the algorithm is to compute the distances from the ray's origin to its intersection points with each of three slabs (one for each dimension). By then performing a clever comparison of these intersection point distances, it is able to determine whether the ray hits or misses the shape.
This document is concerned with providing some details of how these distances are computed, in order to allow the reader to more thoroughly comprehend the algorithm. Before continuing, the reader should have the algorithm as it appears in \cite{real_time_rendering_3} at hand. For the most part, the same variable names will be used here.
Figure \ref{fig:visual_2d} presents a visual illustration of a sample 2D case, for one particular slab. The points $\mathbf{t}_1$ and $\mathbf{t}_2$ are the intersection points of the ray with the slab. The goal is to determine the distances between $\mathbf{o}$ and these points in the general case. Observe that each basis vector $\mathbf{a}_i$ corresponds to a slab of which it is the normal vector.
As in the algorithm, in what follows, let:
\begin{eqnarray*}
f & \mathdef & \mathbf{a}_i \cdot \mathbf{d} = \cos \theta \\
e & \mathdef & \mathbf{a}_i \cdot \mathbf{p} \\
\end{eqnarray*}
\vspace{-1cm}
Both $\mathbf{a}_i$ and $\mathbf{d}$ are unit vectors; therefore by definition of the dot product, $f = \cos \theta$ where $\theta$ is the angle between the two; i.e., the ray direction and the slab normal. The scalar $e$ is the distance between $\mathbf{o}$ and the center of the box $\mathbf{a_c}$, projected onto the slab normal $\mathbf{a}_i$.
\begin{figure}[ht]
\centering
\begin{tikzpicture}[>=stealth,scale=1]
\tikzstyle{empty}=[inner sep=0pt]
\tikzstyle{dot}=[shape=circle,draw,fill=black,inner sep=0pt,minimum size=1mm]
\begin{scope}[rotate=-15]
%\draw[help lines] (-5,-6) grid (7,4);
% box points (top right, bottom right, etc.)
\node[empty] (box tr) at (4,2) {};
\node[empty] (box br) at (4,-2) {};
\node[empty] (box bl) at (-4,-2) {};
\node[empty] (box tl) at (-4,2) {};
% slab points (same as box, but a bit wider)
\node[empty] (slab tl) at (-5.5,2) {};
\node[empty] (slab bl) at (-5.5,-2) {};
\node[empty] (slab tr) at (7.5,2) {};
\node[empty] (slab br) at (7.5,-2) {};
\node[empty] (center) at (0,0) {};
\node[empty] (ray origin) at (-2.5,-5) {};
\node[empty] (ray direction) at (0.78221, 0.640184) {};
\node[empty] (ray end) at ($(ray origin) + 12*(ray direction)$) {};
\node[empty] (p) at +($(center) - (ray origin)$) {};
% intersection points between ray and slab lines
\node[empty] (t1) at (intersection cs:
first line={(ray origin) -- (ray end)},
second line={(slab bl) -- (slab br)}) {};
\node[empty] (t2) at (intersection cs:
first line={(ray origin) -- (ray end)},
second line={(slab tl) -- (slab tr)}) {};
% normal vector and axis through center, t1 and t2
\node[empty] (normal) at (0,1) {};
\node[empty] (normal through center start) at ($(center) - 6*(normal)$) {};
\node[empty] (normal through center end) at ($(center) + 4*(normal)$) {};
\node[empty] (normal through t1 start) at ($(t1) - 4*(normal)$) {};
\node[empty] (normal through t1 end) at ($(t1) + 6*(normal)$) {};
\node[empty] (normal through t2 start) at ($(t2) - 8*(normal)$) {};
\node[empty] (normal through t2 end) at ($(t2) + 2*(normal)$) {};
\node[empty] (q1) at ($(normal through t1 start)!(ray origin)!(normal through t1 end)$) {};
\node[empty] (q2) at ($(normal through t2 start)!(ray origin)!(normal through t2 end)$) {};
% draw the slab background and the box
\draw[fill=gray!10,fill opacity=80,draw=green!0] (slab bl) rectangle (box tl);
\draw[fill=gray!10,fill opacity=80,draw=green!0] (box br) rectangle (slab tr);
\draw[dashed] (slab tl) -- (box tl);
\draw[dashed] (box tr) -- (slab tr);
\draw[dashed] (box br) -- (slab br);
\draw[dashed] (slab bl) -- (box bl);
\draw (box bl) rectangle (box tr);
% draw node points
\filldraw[fill=black] (center) circle (1.5pt) node[below right,xshift=-1mm] {$\mathbf{a}_c$};
\filldraw[fill=black] (ray origin) circle (1.5pt) node[below] {$\mathbf{o}$};
\filldraw[fill=black] (t1) circle (1.5pt) node[above left,xshift=1mm] {$t_1$};
\filldraw[fill=black] (t2) circle (1.5pt) node[above left,xshift=1mm] {$t_2$};
\filldraw[fill=black] (q1) circle (1.5pt) node[below,xshift=2mm,yshift=-.5mm] {$q_1$};
\filldraw[fill=black] (q2) circle (1.5pt) node[below,xshift=2mm,yshift=-.5mm] {$q_2$};
% draw normal vector, ray, p
\draw[->] (center) -- (0,1) node[left] {$\mathbf{a}_i$};
\draw[->] (ray origin) -- (ray end);
\draw[->|] (ray origin) -- +($(ray direction)$) node[midway,right,yshift=-1mm] {$\mathbf{d}$};
\draw[->,shorten >=1mm] (ray origin) -- +($(p)$) node[midway,left] {$\mathbf{p}$};
% line parallel to ray through origin, and normal axes through t1 and t2
\draw[dashed] ($(center) - 6*(ray direction)$) -- ($(center) + 6*(ray direction)$);
\draw[dashed] (normal through t1 start) -- (normal through t1 end);
\draw[dashed] (normal through t2 start) -- (normal through t2 end);
% draw angle arcs
% TODO: let TikZ figure out that the angle is about 50 degrees
\draw (center) ++ (40:0.5cm) arc (40:90:0.5cm) node[right,yshift=1mm,xshift=1mm] {$\theta$};
\draw (t1) ++ (-90:0.5cm) arc (-90:-140:0.5cm) node[below,yshift=-1mm] {$\theta$};
\draw (t2) ++ (-90:0.5cm) arc (-90:-140:0.5cm) node[below,yshift=-1mm] {$\theta$};
% draw line between (ray origin) and (ray origin) projected onto (normaxis start) -- (normaxis end)
\draw[dashed] (ray origin) -- ($(normal through t1 start)!(ray origin)!(normal through t1 end)$);
\draw[dashed] (ray origin) -- ($(normal through t2 start)!(ray origin)!(normal through t2 end)$);
% draw e, e+h and e-h distance indicators, and perpendicularity markers
\begin{scope}[color=blue]
%\draw[<->,shorten <=.7mm] (center) -- ($(normal through center start)!(t1)!(normal through center end)$) node[midway,left] {$h$};
\draw[<->,shorten <=.7mm] (center) -- ($(normal through center start)!(ray origin)!(normal through center end)$) node[midway,left] {$e$};
\draw[|<->|] ($(t1) + (1.5mm,0)$) -- ($(q1) + (1.5mm,0)$) node[midway,right] {$e-h_i$};
\draw[|<->|] ($(t2) + (1.5mm,0)$) -- ($(q2) + (1.5mm,0)$) node[midway,right] {$e+h_i$};
\end{scope}
\draw ($(q1) + (-1.2mm,1.2mm) - (2.5mm,0)$) -- ($(q1) + (-1.2mm,1.2mm)$) -- ($(q1) + (-1.2mm,1.2mm) + (0,2.5mm)$);
\draw ($(q2) + (-1.2mm,1.2mm) - (2.5mm,0)$) -- ($(q2) + (-1.2mm,1.2mm)$) -- ($(q2) + (-1.2mm,1.2mm) + (0,2.5mm)$);
\end{scope}
\end{tikzpicture}
\caption{A visual representation of how the distances between $\mathbf{o}$ and $\mathbf{t}_1$ and $\mathbf{t}_2$ are computed in the two-dimensional case, for a generic slab $i \in \{u,v\}$ with normal vector $\mathbf{a}_i$ and half-size $h_i$.}
\label{fig:visual_2d}
\end{figure}
In a right-angled triangle, per definition of the cosine, the hypotenuse's length is $\cos \theta$ times the length of the adjacent side:
\begin{center}
\begin{tikzpicture}[scale=1]
\draw (0,0) -- (3,0) node[midway,below] {$x$} -- (3,2) -- (0,0) node[midway,sloped,above] {$x \cos \theta$};
\draw (0,0) -- (5mm,0) arc (0:33:5mm) node[right,xshift=1mm,yshift=-.5mm] {$\theta$};
\begin{scope}[xshift=-1.2mm,yshift=1.2mm]
\draw ($(3,0) - (2.5mm,0)$) -- (3,0) -- ($(3,0) + (0,2.5mm)$);
\end{scope}
\end{tikzpicture}
\end{center}
Thus, looking at the right-angled triangles $\Delta \mathbf{o} q_1 t_1$ and $\Delta \mathbf{o} q_2 t_2$ in Figure \ref{fig:visual_2d}, we find that:
\begin{eqnarray*}
\vectornorm{\mathbf{o} - \mathbf{t_1}} & = & \frac{e - h_i}{\cos \theta}\\
\vectornorm{\mathbf{o} - \mathbf{t_2}} & = & \frac{e + h_i}{\cos \theta}\\
\end{eqnarray*}
\section{Degenerate cases}
It's prudent to consider what happens for the degenerate case of testing a ray for intersections against an OBB that has no size in one or more dimensions. Figure \ref{fig:degenerate_2d_1dim} depicts the degenerate variant of the 2D case seen earlier, where the box now has a null size in the $u$ dimension.
\begin{figure}[ht]
\centering
\begin{tikzpicture}
\tikzstyle{empty}=[inner sep=0pt]
\tikzstyle{dot}=[shape=circle,draw,fill=black,inner sep=0pt,minimum size=1mm]
% two rays
\node[dot] (ray 1 origin) at (-5,-2) [label=below:{$\mathbf{o}_1$}] {};
\node[empty] (ray 1 direction) at (0.861934, 0.50702) {};
\node[empty] (ray 1 end) at ($(ray 1 origin) + 12*(ray 1 direction)$) {};
%\node[dot] (ray 2 origin) at (-5,-6) [label=below:{$\mathbf{o}_2$}] {};
%\node[empty] (ray 2 direction) at (0.861934, 0.50702) {};
%\node[empty] (ray 2 end) at ($(ray 2 origin) + 12*(ray 2 direction)$) {};
\begin{scope}[rotate=-15]
%\draw[help lines] (-6,-4) grid (5,4);
% box points (top right, bottom right, etc.)
\node[empty] (box tr) at (0,2) {};
\node[empty] (box br) at (0,-2) {};
\node[empty] (box bl) at (0,-2) {};
\node[empty] (box tl) at (0,2) {};
% slab points (same as box, but a bit wider)
\node[empty] (slab tl) at (-5.5,2) {};
\node[empty] (slab bl) at (-5.5,-2) {};
\node[empty] (slab tr) at (5.5,2) {};
\node[empty] (slab br) at (5.5,-2) {};
\node[empty] (center) at (0,0) {};
% draw the slab background and the box
\draw[fill=gray!10,fill opacity=80,draw=green!0] (slab bl) rectangle (box tl);
\draw[fill=gray!10,fill opacity=80,draw=green!0] (box br) rectangle (slab tr);
\draw[dashed] (slab tl) -- (box tl);
\draw[dashed] (box tr) -- (slab tr);
\draw[dashed] (box br) -- (slab br);
\draw[dashed] (slab bl) -- (box bl);
\draw (box bl) rectangle (box tr);
% draw rays
\draw[->] (ray 1 origin) -- (ray 1 end);
%\draw[->] (ray 2 origin) -- (ray 2 end);
\draw[->] (ray 1 origin) -- +($(ray 1 direction)$) node[midway,below,xshift=2mm,yshift=1mm] {$\mathbf{d}_1$};
%\draw[->] (ray 2 origin) -- +($(ray 2 direction)$) node[midway,below,xshift=2mm,yshift=1mm] {$\mathbf{d}_2$};
% draw basis vectors
\begin{scope}[xshift=-7cm]
\draw[->] (0,0) -- (1,0) node[midway,below] {$\mathbf{a}_u$};
\draw[->] (0,0) -- (0,1) node[midway,left] {$\mathbf{a}_v$};
\end{scope}
% draw node points
\filldraw[fill=black] (center) circle (1.5pt) node[below right,xshift=-1mm] {$\mathbf{a}_c$};
\end{scope}
\end{tikzpicture}
\caption{Degenerate 2D case with null size in the $u$ dimension.}
\label{fig:degenerate_2d_1dim}
\end{figure}
In the method seen above, the only prerequisites to computing the distances between the ray origin and its intersection points with the slab are $\cos \theta$ and $e \pm h_i$. In the degenerate case, $h_u$ is 0, and so both intersection points will be correctly found at $e / \cos \theta$. None of the other terms depend upon the size of the box along the dimension at hand, and hence remain unaffected. However, they do depend on the degenerate dimension's normal vector remaining well-defined and non-null.
%\bibliographystyle{plainnat-keepcase}
\bibliographystyle{plainnat}
\bibliography{ray_intersect}
\end{document}
Something went wrong with that request. Please try again.