metadata
Wenzel Jakob 2013-01-09 22:37:28 -05:00
commit 4d9d668a58
16 changed files with 291 additions and 44 deletions

View File

@ -285,7 +285,7 @@ if needsBuildDependencies:
print '\nThe dependency directory and your Mitsuba codebase have different version'
print 'numbers! Your copy of Mitsuba has version %s, whereas the dependencies ' % MTS_VERSION
print 'have version %s. Please bring them into sync, either by running\n' % depVersion
print '$ hg update -r v%s\n' % depVersion
print '$ hg update -r v%s\n' % depVersion
print 'in the Mitsuba directory, or by running\n'
print '$ cd dependencies'
print '$ hg pull'
@ -353,7 +353,7 @@ def configure_for_objective_cpp(env):
env.RemoveFlags(['-fstrict-aliasing', '-ftree-vectorize',
'-std=c\+\+0x'])
# Remove Intel compiler-specific optimization flags
env.RemoveFlags(['-x.*', '-ax.*', '-ipo', '-no-prec-div',
env.RemoveFlags(['-x.*', '-ax.*', '-ipo', '-no-prec-div',
'-fp-model', 'fast=.*', '-wd.*', '-openmp'])
env['CCFLAGS'] += ['-fno-strict-aliasing']
# Enforce GCC usage (Intel compiler doesn't handle Objective C/C++)
@ -374,7 +374,7 @@ env.__class__.ConfigureForObjectiveCPP = configure_for_objective_cpp
env.__class__.RelaxCompilerSettings = relax_compiler_settings
if hasCollada:
env.Append(CPPDEFINES = [['MTS_HAS_COLLADA', 1]] )
env.Append(CPPDEFINES = [['MTS_HAS_COLLADA', 1]])
env.SConsignFile()

View File

@ -4,7 +4,7 @@ Priority: optional
Maintainer: Wenzel Jakob <wenzel@cs.cornell.edu>
Build-Depends: debhelper (>= 7), build-essential, scons, qt4-dev-tools,
libpng12-dev, libjpeg-dev, libilmbase-dev, libopenexr-dev,
libxerces-c-dev, libboost-dev, libglewmx1.5-dev, libxxf86vm-dev,
libxerces-c-dev, libboost-dev, libglewmx-dev, libxxf86vm-dev,
collada-dom-dev, libboost-system-dev, libboost-filesystem-dev,
libboost-python-dev, libboost-thread-dev, libgl1-mesa-dev,
libglu1-mesa-dev, pkg-config, libeigen3-dev
@ -24,7 +24,7 @@ Description: Mitsuba renderer
Package: mitsuba-dev
Architecture: any
Depends: qt4-dev-tools, libpng12-dev, libjpeg-dev, libilmbase-dev,
libopenexr-dev, libxerces-c-dev, libboost-dev, libglewmx1.5-dev,
libopenexr-dev, libxerces-c-dev, libboost-dev, libglewmx-dev,
libxxf86vm-dev, collada-dom-dev, libboost-system-dev,
libboost-filesystem-dev, libboost-python-dev, libboost-thread-dev,
libeigen3-dev, mitsuba

View File

@ -2,11 +2,18 @@
\subsection{Subsurface scattering models}
\label{sec:subsurface}
There are two ways of simulating subsurface scattering within Mitsuba:
participating media and subsurface scattering models. The latter are described
in this section and can be thought of as a first-order approximation of the
former. For this reason, subsurface scattering models should be preferred when
visually appealing output should be generated quickly and the demands on
physical realism are secondary.
participating media and subsurface scattering models.
\begin{description}
\item[Subsurface scattering models:] Described in this section. These can be thought
of as a first-order approximation of what happens inside a participating medium.
They are preferable when visually appealing output should be generated
\emph{quickly} and the demands on accuracy are secondary.
At the moment, there is only one subsurface scattering model (the
\pluginref{dipole}), which is described on the next page.
\item[Participating media:] Described in Section~\ref{sec:media}. When modeling
subsurface scattering using a participating medium, Mitsuba performs a \emph{full}
radiative transport simulation, which correctly accounts for all scattering events.
This is more accurate but generally significantly slower.
\end{description}
At the moment, there is only one subsurface scattering model (the
\pluginref{dipole}), which is described on the next page.

View File

@ -403,7 +403,7 @@ public:
void colorBalance(Float r, Float g, Float b);
/// Draw a filled rectangle with the specified position and size
void fill(const Point2i &offset, const Vector2i &size, const Spectrum &value);
void fillRect(Point2i offset, Vector2i size, const Spectrum &value);
/// Bitmap equality operator (useful for unit-tests etc.)
bool operator==(const Bitmap &bitmap) const;
@ -699,7 +699,25 @@ public:
* use different component formats or channels, or when the
* component format is \ref EBitmask.
*/
void accumulate(const Bitmap *bitmap, const Point2i &offset);
void accumulate(const Bitmap *bitmap, Point2i sourceOffset,
Point2i targetOffset, Vector2i size);
/**
* \brief Accumulate the contents of another bitmap into the
* region of the specified offset
*
* This convenience function calls the main <tt>accumulate()</tt>
* implementation with <tt>size</tt> set to <tt>bitmap->getSize()</tt>
* and <tt>sourceOffset</tt> set to zero. Out-of-bounds regions are
* ignored. It is assumed that <tt>bitmap != this</tt>.
*
* \remark This function throws an exception when the bitmaps
* use different component formats or channels, or when the
* component format is \ref EBitmask.
*/
inline void accumulate(const Bitmap *bitmap, Point2i targetOffset) {
accumulate(bitmap, Point2i(0), targetOffset, bitmap->getSize());
}
/**
* \brief Up- or down-sample this image to a different resolution

View File

@ -76,12 +76,22 @@ public:
/// Allocate memory for a certain font
Font(EFont font);
/// Draw text to the specified bitmap
void drawText(Bitmap *dest, Point2i pos, const std::string &text) const;
/// Compute the size covered by the given string when rendered using this font
Vector2i getSize(const std::string &text) const;
/// Upload the font to the GPU
void init(Renderer *renderer);
/// Free the GPU memory
void cleanup();
/// Convert the underlying bitmap to a different pixel format
void convert(Bitmap::EPixelFormat pixelFormat,
Bitmap::EComponentFormat componentFormat, Float gamma);
/// Return the name of this font
inline const std::string &getName() const { return m_name; }

View File

@ -74,6 +74,9 @@ public:
/// Serialize to a binary data stream
virtual void serialize(Stream *stream, InstanceManager *manager) const;
/// Return the underlying bitmap representation (if any)
virtual ref<Bitmap> getBitmap() const;
MTS_DECLARE_CLASS()
protected:
Texture(const Properties &props);

View File

@ -20,6 +20,7 @@
#include <mitsuba/core/fstream.h>
#include <mitsuba/core/bitmap.h>
#include <mitsuba/core/statistics.h>
#include <mitsuba/hw/font.h>
#include <boost/algorithm/string.hpp>
#include "banner.h"
@ -114,7 +115,33 @@ MTS_NAMESPACE_BEGIN
* <boolean name="banner" value="false"/>
* </film>
* \end{xml}
*
* \subsubsection*{Annotations:}
* \label{sec:film-annotations}
* The \pluginref{ldrfilm} and \pluginref{hdrfilm} plugins support an additional
* feature referred to as \emph{annotations}, which can be quite useful under
* certain circumstances.
*
* Annotations are used to embed useful information inside a rendered image so
* that this information is later available to anyone viewing the image.
* Exemplary uses of this feature might be to store the frame or take number,
* camera parameters, or other relevant scene information.
*
* Annotations can either be created by means of a \emph{tag}, which is an entry
* in the metadata table of the image file (does not modify the actual image data),
* or a \emph{text} label which is ``burned'' into the image.
*
* The syntax of this looks as follows:
*
* \begin{xml}
* <film type="ldrfilm">
* <!-- Create a new metadata entry 'my_tag_name' and set it to the value 'my_tag_value' -->
* <string name="tag('my_tag_name')" value="my_tag_value"/>
*
* <!-- Add the label 'Hello' at the image position X=50, Y=80 -->
* <string name="text(50,80)" value="Hello!"/>
* </film>
* \end{xml}
*/
class HDRFilm : public Film {
public:
@ -204,6 +231,25 @@ public:
}
std::vector<std::string> keys = props.getPropertyNames();
for (size_t i=0; i<keys.size(); ++i) {
std::string key = boost::to_lower_copy(keys[i]);
if (boost::starts_with(key, "tag('") && boost::ends_with(key, "')")) {
m_tags[keys[i].substr(5, key.length()-7)] = props.getString(keys[i]);
} else if (boost::starts_with(key, "text(") && boost::ends_with(key, ")")) {
std::vector<std::string> args = tokenize(key.substr(5, key.length()-6), " ,");
if (args.size() != 2)
Log(EError, "Text command '%s' has an invalid number of arguments!", key.c_str());
Annotation annotation;
annotation.offset = Point2i(atoi(args[0].c_str()), atoi(args[1].c_str()));
annotation.text = props.getString(keys[i]);
m_annotations.push_back(annotation);
}
}
m_storage = new ImageBlock(Bitmap::ESpectrumAlphaWeight, m_cropSize);
}
@ -321,6 +367,23 @@ public:
}
}
if (!m_annotations.empty()) {
ref<Font> font = new Font(Font::EBitstreamVeraMono14);
font->convert(bitmap->getPixelFormat(), bitmap->getComponentFormat(), 1.0f);
for (size_t i=0; i<m_annotations.size(); ++i) {
const Point2i &offset = m_annotations[i].offset;
const std::string &text = m_annotations[i].text;
Vector2i size = font->getSize(text);
bitmap->fillRect(offset-Vector2i(4, 4), size + Vector2i(8, 8), Spectrum(0.0f));
font->drawText(bitmap, offset, text);
}
}
for (std::map<std::string, std::string>::const_iterator it = m_tags.begin();
it != m_tags.end(); ++it)
bitmap->getMetadata()[it->first] = it->second;
fs::path filename = m_destFile;
std::string extension = boost::to_lower_copy(filename.extension().string());
std::string properExtension = (m_fileFormat == Bitmap::EOpenEXR) ? ".exr" : ".rgbe";
@ -367,6 +430,11 @@ public:
MTS_DECLARE_CLASS()
protected:
struct Annotation {
Point2i offset;
std::string text;
};
Bitmap::EFileFormat m_fileFormat;
Bitmap::EPixelFormat m_pixelFormat;
Bitmap::EComponentFormat m_componentFormat;
@ -374,6 +442,9 @@ protected:
bool m_attachLog;
fs::path m_destFile;
ref<ImageBlock> m_storage;
std::vector<Annotation> m_annotations;
std::map<std::string, std::string> m_tags;
};
MTS_IMPLEMENT_CLASS_S(HDRFilm, false, Film)

View File

@ -20,6 +20,7 @@
#include <mitsuba/core/fstream.h>
#include <mitsuba/core/bitmap.h>
#include <mitsuba/core/statistics.h>
#include <mitsuba/hw/font.h>
#include <boost/algorithm/string.hpp>
#include "banner.h"
@ -113,6 +114,9 @@ MTS_NAMESPACE_BEGIN
* The RGB values exported by this plugin correspond to the ITU-R Rec. BT. 709-3
* primaries with a D65 white point. When $\texttt{gamma}$ is set to $\code{-1}$ (the default),
* the output is in the sRGB color space and will display as intended on compatible devices.
*
* Note that this plugin supports render-time \emph{annotations}, which
* are described on page~\pageref{sec:film-annotations}.
*/
class LDRFilm : public Film {
public:
@ -176,6 +180,25 @@ public:
m_reinhardKey = props.getFloat("key", 0.18f);
m_reinhardBurn = props.getFloat("burn", 0.0);
std::vector<std::string> keys = props.getPropertyNames();
for (size_t i=0; i<keys.size(); ++i) {
std::string key = boost::to_lower_copy(keys[i]);
if (boost::starts_with(key, "tag('") && boost::ends_with(key, "')")) {
m_tags[keys[i].substr(5, key.length()-7)] = props.getString(keys[i]);
} else if (boost::starts_with(key, "text(") && boost::ends_with(key, ")")) {
std::vector<std::string> args = tokenize(key.substr(5, key.length()-6), " ,");
if (args.size() != 2)
Log(EError, "Text command '%s' has an invalid number of arguments!", key.c_str());
Annotation annotation;
annotation.offset = Point2i(atoi(args[0].c_str()), atoi(args[1].c_str()));
annotation.text = props.getString(keys[i]);
m_annotations.push_back(annotation);
}
}
m_storage = new ImageBlock(Bitmap::ESpectrumAlphaWeight, m_cropSize);
}
@ -313,6 +336,23 @@ public:
}
}
if (!m_annotations.empty()) {
ref<Font> font = new Font(Font::EBitstreamVeraMono14);
font->convert(bitmap->getPixelFormat(), bitmap->getComponentFormat(), m_gamma);
for (size_t i=0; i<m_annotations.size(); ++i) {
const Point2i &offset = m_annotations[i].offset;
const std::string &text = m_annotations[i].text;
Vector2i size = font->getSize(text);
bitmap->fillRect(offset-Vector2i(4, 4), size + Vector2i(8, 8), Spectrum(0.0f));
font->drawText(bitmap, offset, text);
}
}
for (std::map<std::string, std::string>::const_iterator it = m_tags.begin();
it != m_tags.end(); ++it)
bitmap->getMetadata()[it->first] = it->second;
fs::path filename = m_destFile;
std::string extension = boost::to_lower_copy(filename.extension().string());
std::string expectedExtension;
@ -367,6 +407,11 @@ public:
MTS_DECLARE_CLASS()
protected:
struct Annotation {
Point2i offset;
std::string text;
};
Bitmap::EFileFormat m_fileFormat;
Bitmap::EPixelFormat m_pixelFormat;
bool m_hasBanner;
@ -375,6 +420,9 @@ protected:
ref<ImageBlock> m_storage;
ETonemapMethod m_tonemapMethod;
Float m_exposure, m_reinhardKey, m_reinhardBurn;
std::vector<Annotation> m_annotations;
std::map<std::string, std::string> m_tags;
};
MTS_IMPLEMENT_CLASS_S(LDRFilm, false, Film)

View File

@ -432,7 +432,7 @@ public:
bool develop(const Point2i &sourceOffset, const Vector2i &size,
const Point2i &targetOffset, Bitmap *target) const {
target->fill(targetOffset, size, Spectrum(0.0f));
target->fillRect(targetOffset, size, Spectrum(0.0f));
return false; /* Not supported by the tiled EXR film! */
}

View File

@ -430,47 +430,59 @@ void Bitmap::flipVertically() {
}
}
void Bitmap::accumulate(const Bitmap *bitmap, const Point2i &offset) {
void Bitmap::accumulate(const Bitmap *bitmap, Point2i sourceOffset,
Point2i targetOffset, Vector2i size) {
Assert(getPixelFormat() == bitmap->getPixelFormat() &&
getComponentFormat() == bitmap->getComponentFormat() &&
getChannelCount() == bitmap->getChannelCount());
const int
offsetX = std::max(offset.x, 0),
offsetY = std::max(offset.y, 0),
endX = std::min(offset.x + bitmap->getSize().x, m_size.x),
endY = std::min(offset.y + bitmap->getSize().y, m_size.y);
Vector2i offsetIncrease(
std::max(0, std::max(-sourceOffset.x, -targetOffset.x)),
std::max(0, std::max(-sourceOffset.y, -targetOffset.y))
);
if (offsetX >= endX || offsetY >= endY)
sourceOffset += offsetIncrease;
targetOffset += offsetIncrease;
size -= offsetIncrease;
Vector2i sizeDecrease(
std::max(0, std::max(sourceOffset.x + size.x - bitmap->getWidth(), targetOffset.x + size.x - getWidth())),
std::max(0, std::max(sourceOffset.y + size.y - bitmap->getHeight(), targetOffset.y + size.y - getHeight())));
size -= sizeDecrease;
if (size.x <= 0 || size.y <= 0)
return;
const size_t
columns = (endX - offsetX) * m_channelCount,
columns = size.x * m_channelCount,
pixelStride = getBytesPerPixel(),
sourceStride = bitmap->getSize().x * pixelStride,
targetStride = m_size.x * pixelStride;
sourceStride = bitmap->getWidth() * pixelStride,
targetStride = getWidth() * pixelStride;
const uint8_t *source = bitmap->getUInt8Data() +
(offsetX - offset.x + (offsetY - offset.y) * bitmap->getSize().x) * pixelStride;
(sourceOffset.x + sourceOffset.y * (size_t) bitmap->getWidth()) * pixelStride;
uint8_t *target = m_data +
(offsetX + offsetY * m_size.x) * pixelStride;
(targetOffset.x + targetOffset.y * (size_t) m_size.x) * pixelStride;
for (int y = offsetY; y < endY; ++y) {
for (int y = 0; y < size.y; ++y) {
switch (m_componentFormat) {
case EUInt8:
for (size_t i = 0; i < columns; ++i)
((uint8_t *) target)[i] += ((uint8_t *) source)[i];
((uint8_t *) target)[i] = (uint8_t) std::min(0xFF, ((uint8_t *) source)[i] + ((uint8_t *) target)[i]);
break;
case EUInt16:
for (size_t i = 0; i < columns; ++i)
((uint16_t *) target)[i] += ((uint16_t *) source)[i];
((uint16_t *) target)[i] = (uint16_t) std::min(0xFFFF, ((uint16_t *) source)[i] + ((uint16_t *) target)[i]);
break;
case EUInt32:
for (size_t i = 0; i < columns; ++i)
((uint32_t *) target)[i] += ((uint32_t *) source)[i];
((uint32_t *) target)[i] = std::min((uint32_t) 0xFFFFFFFFUL, ((uint32_t *) source)[i] + ((uint32_t *) target)[i]);
break;
case EFloat16:
@ -554,8 +566,9 @@ void Bitmap::setPixel(const Point2i &pos, const Spectrum &value) {
}
void Bitmap::drawHLine(int y, int x1, int x2, const Spectrum &value) {
AssertEx( y >= 0 && y < m_size.y &&
x1 >= 0 && x2 < m_size.x, "Bitmap::drawVLine(): out of bounds!");
if (y < 0 || y >= m_size.y)
return;
x1 = std::max(x1, 0); x2 = std::min(x2, m_size.x-1);
const FormatConverter *cvt = FormatConverter::getInstance(
std::make_pair(EFloat, m_componentFormat)
@ -574,8 +587,9 @@ void Bitmap::drawHLine(int y, int x1, int x2, const Spectrum &value) {
}
void Bitmap::drawVLine(int x, int y1, int y2, const Spectrum &value) {
AssertEx( x >= 0 && x < m_size.x &&
y1 >= 0 && y2 < m_size.y, "Bitmap::drawVLine(): out of bounds!");
if (x < 0 || x >= m_size.x)
return;
y1 = std::max(y1, 0); y2 = std::min(y2, m_size.y-1);
const FormatConverter *cvt = FormatConverter::getInstance(
std::make_pair(EFloat, m_componentFormat)
@ -601,9 +615,12 @@ void Bitmap::drawRect(const Point2i &offset, const Vector2i &size, const Spectru
drawVLine(offset.x + size.x - 1, offset.y, offset.y + size.y - 1, value);
}
void Bitmap::fill(const Point2i &offset, const Vector2i &size, const Spectrum &value) {
AssertEx(offset.x >= 0 && offset.x + size.x <= m_size.x &&
offset.y >= 0 && offset.y + size.y <= m_size.y, "Bitmap::fill(): out of bounds!");
void Bitmap::fillRect(Point2i offset, Vector2i size, const Spectrum &value) {
int sx = std::max(0, -offset.x), sy = std::max(0, -offset.y);
size.x -= sx; size.y -= sy; offset.x += sx; offset.y += sy;
size.x -= std::max(0, offset.x + size.x - m_size.x);
size.y -= std::max(0, offset.y + size.y - m_size.y);
const FormatConverter *cvt = FormatConverter::getInstance(
std::make_pair(EFloat, m_componentFormat)

View File

@ -74,6 +74,70 @@ Font::Font(EFont font) {
dscStream->read(m_kerningMatrix, 256*256);
}
void Font::convert(Bitmap::EPixelFormat pixelFormat, Bitmap::EComponentFormat componentFormat, Float gamma) {
m_bitmap = m_bitmap->convert(pixelFormat, componentFormat, gamma);
}
void Font::drawText(Bitmap *dest, Point2i pos, const std::string &text) const {
int initial = pos.x;
for (size_t i=0; i<text.length(); i++) {
char character = text[i];
if (character == '\r')
continue;
if (character == '\n') {
pos.x = initial;
pos.y += (int) (getMaxVerticalBearing()*4.0/3.0);
continue;
}
const Font::Glyph &glyph = getGlyph(character);
Point2i targetOffset = pos + Vector2i(
glyph.horizontalBearing,
getMaxVerticalBearing() - glyph.verticalBearing - 1
);
Point2i sourceOffset(
glyph.tx.x * m_bitmap->getWidth(),
glyph.tx.y * m_bitmap->getHeight());
dest->accumulate(m_bitmap.get(), sourceOffset, targetOffset, glyph.size);
pos.x += glyph.horizontalAdvance;
if (i+1 < text.length())
pos.x += getKerning(character, text[i+1]);
}
}
Vector2i Font::getSize(const std::string &text) const {
Vector2i size(0, getMaxVerticalBearing());
int pos = 0;
for (size_t i=0; i<text.length(); i++) {
char character = text[i];
if (character == '\r')
continue;
if (character == '\n') {
size.y += getMaxVerticalBearing()*(4.0 / 3.0);
size.x = std::max(size.x, pos);
pos = 0;
continue;
}
const Font::Glyph &glyph = getGlyph(character);
pos += glyph.horizontalAdvance;
if (i+1 < text.length())
pos += getKerning(character, text[i+1]);
}
size.x = std::max(size.x, pos);
return size;
}
void Font::init(Renderer *renderer) {
m_texture = renderer->createGPUTexture(m_name, m_bitmap);
m_texture->setFilterType(GPUTexture::ENearest);

View File

@ -711,6 +711,9 @@ void export_core() {
.def("clear", &InterpolatedSpectrum::clear)
.def("zeroExtend", &InterpolatedSpectrum::zeroExtend);
void (Bitmap::*accumulate_1)(const Bitmap *bitmap, Point2i sourceOffset, Point2i targetOffset, Vector2i size) = &Bitmap::accumulate;
void (Bitmap::*accumulate_2)(const Bitmap *bitmap, Point2i targetOffset) = &Bitmap::accumulate;
BP_CLASS(Bitmap, Object, (bp::init<Bitmap::EPixelFormat, Bitmap::EComponentFormat, const Vector2i &>()))
.def(bp::init<Bitmap::EPixelFormat, Bitmap::EComponentFormat, const Vector2i &, int>())
.def(bp::init<Bitmap::EFileFormat, Stream *>())
@ -719,7 +722,8 @@ void export_core() {
.def("expand", &Bitmap::expand, BP_RETURN_VALUE)
.def("flipVertically", &Bitmap::flipVertically)
.def("crop", &Bitmap::crop)
.def("accumulate", &Bitmap::accumulate)
.def("accumulate", accumulate_1)
.def("accumulate", accumulate_2)
.def("clear", &Bitmap::clear)
.def("write", &bitmap_write)
.def("setString", &Bitmap::setString)

View File

@ -47,6 +47,7 @@ Spectrum Texture::getMinimum() const { NotImplementedError("getMinimum"); }
Spectrum Texture::getMaximum() const { NotImplementedError("getMaximum"); }
bool Texture::isConstant() const { NotImplementedError("isConstant"); }
bool Texture::usesRayDifferentials() const { NotImplementedError("usesRayDifferentials"); }
ref<Bitmap> Texture::getBitmap() const { return NULL; }
ref<Texture> Texture::expand() {
return this;

View File

@ -208,9 +208,9 @@ static int irrOctreeIndex = 0;
* rendered using diffusion theory and radiative transport, respectively.
* The former produces an incorrect result, since the assumption of
* many scattering events breaks down.
* \textbf{(c)}: When the number of irradiance samples is too low, the
* resulting noise becomes visible as ``blotchy'' artifacts in the
* rendering.}
* \textbf{(c)}: When the number of irradiance samples is too low when rendering
* with the dipole model, the resulting noise becomes visible as ``blotchy'' artifacts
* in the rendering.}
* }
*
* \subsubsection*{Typical material setup}

View File

@ -400,6 +400,10 @@ public:
return result;
}
ref<Bitmap> getBitmap() const {
return m_mipmap1.get() ? m_mipmap1->toBitmap() : m_mipmap3->toBitmap();
}
Spectrum eval(const Point2 &uv, const Vector2 &d0, const Vector2 &d1) const {
stats::filteredLookups.incrementBase();
++stats::filteredLookups;

View File

@ -93,7 +93,7 @@ MTS_NAMESPACE_BEGIN
*
* When using this data source to represent floating point density volumes,
* please ensure that the values are all normalized to lie in the
* range $[0, 1]$---otherwise, the Woocock-Tracking integration method in
* range $[0, 1]$---otherwise, the Woodcock-Tracking integration method in
* \pluginref{heterogeneous} will produce incorrect results.
*/
class GridDataSource : public VolumeDataSource {