/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2012 by Wenzel Jakob and others. Mitsuba is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. Mitsuba is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include // Uncomment to enable nearest-neighbor direction interpolation //#define VINTERP_NEAREST_NEIGHBOR // Number of power iteration steps used to find the dominant direction #define POWER_ITERATION_STEPS 5 MTS_NAMESPACE_BEGIN /*!\plugin{gridvolume}{Grid-based volume data source} * \parameters{ * \parameter{filename}{\String}{ * Specifies the filename of the volume data file to be loaded * } * \parameter{sendData}{\Boolean}{ * When this parameter is set to \code{true}, the implementation will * send all volume data to other network render nodes. Otherwise, they * are expected to have access to an identical volume data file that can be * mapped into memory. \default{\code{false}} * } * \parameter{toWorld}{\Transform}{ * Optional linear transformation that should be applied to the data * } * \parameter{min, max}{\Point}{ * Optional parameter that can be used to re-scale the data so that * it lies in the bounding box between \code{min} and \code{max}. * } * } * * This class implements access to memory-mapped volume data stored on a * 3D grid using a simple binary exchange format. * The format uses a little endian encoding and is specified as * follows:\vspace{3mm} * * \begin{center} * \begin{tabular}{>{\bfseries}p{2cm}p{11cm}} * \toprule * Position & Content\\ * \midrule * Bytes 1-3& ASCII Bytes '\code{V}', '\code{O}', and '\code{L}' \\ * Byte 4& File format version number (currently 3)\\ * Bytes 5-8& Encoding identifier (32-bit integer). The following * choices are available: * \begin{enumerate}[1.] * \item Dense \code{float32}-based representation * \item Dense \code{float16}-based representation (\emph{currently not supported by this implementation}) * \item Dense \code{uint8}-based representation (The range 0..255 will be mapped to 0..1) * \item Dense quantized directions. The directions are stored in spherical * coordinates with a total storage cost of 16 bit per entry. * \end{enumerate}\\ * Bytes 9-12 & Number of cells along the X axis (32 bit integer)\\ * Bytes 13-16 & Number of cells along the Y axis (32 bit integer)\\ * Bytes 17-20 & Number of cells along the Z axis (32 bit integer)\\ * Bytes 21-24 & Number of channels (32 bit integer, supported values: 1 or 3)\\ * Bytes 25-48 & Axis-aligned bounding box of the data stored in single * precision (order: xmin, ymin, zmin, xmax, ymax, zmax)\\ * Bytes 49-* & Binary data of the volume stored in the specified encoding. * The data are ordered so that the following C-style indexing * operation makes sense after the file has been mapped into memory:\newline * \ \ \code{data[((zpos*yres + ypos)*xres + xpos)*channels + chan]}\newline * where \code{(xpos, ypos, zpos, chan)} denotes the lookup location.\\ * * \bottomrule * \end{tabular} * \end{center} * * Note that Mitsuba expects that entries in direction volumes are either * zero or valid unit vectors. * * 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 Woodcock-Tracking integration method in * \pluginref{heterogeneous} will produce incorrect results. */ class GridDataSource : public VolumeDataSource { public: enum EVolumeType { EFloat32 = 1, EFloat16 = 2, EUInt8 = 3, EQuantizedDirections = 4 }; GridDataSource(const Properties &props) : VolumeDataSource(props) { m_volumeToWorld = props.getTransform("toWorld", Transform()); if (props.hasProperty("min") && props.hasProperty("max")) { /* Optionally allow to use an AABB other than the one specified by the grid file */ m_dataAABB.min = props.getPoint("min"); m_dataAABB.max = props.getPoint("max"); } /** * When 'sendData' is set to false, only the filename * is transmitted. A following unserialization of the * stream causes the implementation to then look for * the file (which had better exist if unserialization * occurs on a remote machine). */ m_sendData = props.getBoolean("sendData", false); loadFromFile(props.getString("filename")); } GridDataSource(Stream *stream, InstanceManager *manager) : VolumeDataSource(stream, manager) { m_volumeToWorld = Transform(stream); m_dataAABB = AABB(stream); m_sendData = stream->readBool(); if (m_sendData) { m_volumeType = (EVolumeType) stream->readInt(); m_res = Vector3i(stream); m_channels = stream->readInt(); m_filename = stream->readString(); size_t volumeSize = getVolumeSize(); m_data = new uint8_t[volumeSize]; stream->read(m_data, volumeSize); } else { fs::path filename = stream->readString(); loadFromFile(filename); } configure(); } virtual ~GridDataSource() { if (!m_mmap) delete[] m_data; } size_t getVolumeSize() const { size_t nEntries = (size_t) m_res.x * (size_t) m_res.y * (size_t) m_res.z; switch (m_volumeType) { case EFloat32: return 4 * nEntries * m_channels; case EFloat16: return 2 * nEntries * m_channels; case EUInt8: return 1 * nEntries * m_channels; case EQuantizedDirections: return 2 * nEntries; default: Log(EError, "Unknown volume format!"); return 0; } } void serialize(Stream *stream, InstanceManager *manager) const { VolumeDataSource::serialize(stream, manager); m_volumeToWorld.serialize(stream); m_dataAABB.serialize(stream); stream->writeBool(m_sendData); if (m_sendData) { stream->writeInt(m_volumeType); m_res.serialize(stream); stream->writeInt(m_channels); stream->writeString(m_filename.string()); stream->write(m_data, getVolumeSize()); } else { stream->writeString(m_filename.string()); } } void configure() { Vector extents(m_dataAABB.getExtents()); m_worldToVolume = m_volumeToWorld.inverse(); m_worldToGrid = Transform::scale(Vector( (m_res[0] - 1) / extents[0], (m_res[1] - 1) / extents[1], (m_res[2] - 1) / extents[2]) ) * Transform::translate(-Vector(m_dataAABB.min)) * m_worldToVolume; m_stepSize = std::numeric_limits::infinity(); for (int i=0; i<3; ++i) m_stepSize = 0.5f * std::min(m_stepSize, extents[i] / (Float) (m_res[i]-1)); m_aabb.reset(); for (int i=0; i<8; ++i) m_aabb.expandBy(m_volumeToWorld(m_dataAABB.getCorner(i))); /* Precompute cosine and sine lookup tables */ for (int i=0; i<255; i++) { Float angle = (float) i * ((float) M_PI / 255.0f); m_cosPhi[i] = std::cos(2.0f * angle); m_sinPhi[i] = std::sin(2.0f * angle); m_cosTheta[i] = std::cos(angle); m_sinTheta[i] = std::sin(angle); m_densityMap[i] = i/255.0f; } m_cosPhi[255] = m_sinPhi[255] = 0; m_cosTheta[255] = m_sinTheta[255] = 0; m_densityMap[255] = 1.0f; } void loadFromFile(const fs::path &filename) { m_filename = filename; fs::path resolved = Thread::getThread()->getFileResolver()->resolve(filename); m_mmap = new MemoryMappedFile(resolved); ref stream = new MemoryStream(m_mmap->getData(), m_mmap->getSize()); stream->setByteOrder(Stream::ELittleEndian); char header[3]; stream->read(header, 3); if (header[0] != 'V' || header[1] != 'O' || header[2] != 'L') Log(EError, "Encountered an invalid volume data file " "(incorrect header identifier)"); uint8_t version; stream->read(&version, 1); if (version != 3) Log(EError, "Encountered an invalid volume data file " "(incorrect file version)"); int type = stream->readInt(); int xres = stream->readInt(), yres = stream->readInt(), zres = stream->readInt(); m_res = Vector3i(xres, yres, zres); m_channels = stream->readInt(); std::string format; switch (type) { case EFloat32: if (m_channels != 1 && m_channels != 3) Log(EError, "Encountered an unsupported float32 volume data " "file (%i channels, only 1 and 3 are supported)", m_channels); format = "float32"; break; case EFloat16: format = "float16"; Log(EError, "Error: float16 volumes are not yet supported!"); case EUInt8: format = "uint8"; if (m_channels != 1 && m_channels != 3) Log(EError, "Encountered an unsupported uint8 volume data " "file (%i channels, only 1 and 3 are supported)", m_channels); break; case EQuantizedDirections: format = "qdir"; if (m_channels != 3) Log(EError, "Encountered an unsupported quantized direction " "volume data file (%i channels, only 3 are supported)", m_channels); break; default: Log(EError, "Encountered a volume data file of unknown type (type=%i, channels=%i)!", type, m_channels); } m_volumeType = (EVolumeType) type; if (!m_dataAABB.isValid()) { Float xmin = stream->readSingle(), ymin = stream->readSingle(), zmin = stream->readSingle(); Float xmax = stream->readSingle(), ymax = stream->readSingle(), zmax = stream->readSingle(); m_dataAABB = AABB(Point(xmin, ymin, zmin), Point(xmax, ymax, zmax)); } Log(EDebug, "Mapped \"%s\" into memory: %ix%ix%i (%i channels, format = %s), %s, %s", resolved.filename().string().c_str(), m_res.x, m_res.y, m_res.z, m_channels, format.c_str(), memString(m_mmap->getSize()).c_str(), m_dataAABB.toString().c_str()); m_data = (uint8_t *) (((float *) m_mmap->getData()) + 12); } /** * This is needed since Mitsuba might be * compiled with either single/double precision */ struct float3 { float value[3]; inline float3() { } inline float3(float a, float b, float c) { value[0] = a; value[1] = b; value[2] = c; } inline explicit float3(double a, double b, double c) { value[0] = (float) a; value[1] = (float) b; value[2] = (float) c; } inline float3 operator*(Float v) const { return float3((float) (value[0]*v), (float) (value[1]*v), (float) (value[2]*v)); } inline float3 operator+(const float3 &f2) const { return float3(value[0]+f2.value[0], value[1]+f2.value[1], value[2]+f2.value[2]); } inline Spectrum toSpectrum() const { Spectrum result; result.fromLinearRGB(value[0], value[1], value[2]); return result; } inline Vector toVector() const { return Vector(value[0], value[1], value[2]); } float operator[](int i) const { return value[i]; } inline Matrix3x3 tensor() const { return Matrix3x3( value[0]*value[0], value[0]*value[1], value[0]*value[2], value[1]*value[0], value[1]*value[1], value[1]*value[2], value[2]*value[0], value[2]*value[1], value[2]*value[2] ); } }; Float lookupFloat(const Point &_p) const { const Point p = m_worldToGrid.transformAffine(_p); const int x1 = math::floorToInt(p.x), y1 = math::floorToInt(p.y), z1 = math::floorToInt(p.z), x2 = x1+1, y2 = y1+1, z2 = z1+1; if (x1 < 0 || y1 < 0 || z1 < 0 || x2 >= m_res.x || y2 >= m_res.y || z2 >= m_res.z) return 0; const Float fx = p.x - x1, fy = p.y - y1, fz = p.z - z1, _fx = 1.0f - fx, _fy = 1.0f - fy, _fz = 1.0f - fz; switch (m_volumeType) { case EFloat32: { const float *floatData = (float *) m_data; const Float d000 = floatData[(z1*m_res.y + y1)*m_res.x + x1], d001 = floatData[(z1*m_res.y + y1)*m_res.x + x2], d010 = floatData[(z1*m_res.y + y2)*m_res.x + x1], d011 = floatData[(z1*m_res.y + y2)*m_res.x + x2], d100 = floatData[(z2*m_res.y + y1)*m_res.x + x1], d101 = floatData[(z2*m_res.y + y1)*m_res.x + x2], d110 = floatData[(z2*m_res.y + y2)*m_res.x + x1], d111 = floatData[(z2*m_res.y + y2)*m_res.x + x2]; return ((d000*_fx + d001*fx)*_fy + (d010*_fx + d011*fx)*fy)*_fz + ((d100*_fx + d101*fx)*_fy + (d110*_fx + d111*fx)*fy)*fz; } case EUInt8: { const Float d000 = m_densityMap[m_data[(z1*m_res.y + y1)*m_res.x + x1]], d001 = m_densityMap[m_data[(z1*m_res.y + y1)*m_res.x + x2]], d010 = m_densityMap[m_data[(z1*m_res.y + y2)*m_res.x + x1]], d011 = m_densityMap[m_data[(z1*m_res.y + y2)*m_res.x + x2]], d100 = m_densityMap[m_data[(z2*m_res.y + y1)*m_res.x + x1]], d101 = m_densityMap[m_data[(z2*m_res.y + y1)*m_res.x + x2]], d110 = m_densityMap[m_data[(z2*m_res.y + y2)*m_res.x + x1]], d111 = m_densityMap[m_data[(z2*m_res.y + y2)*m_res.x + x2]]; return ((d000*_fx + d001*fx)*_fy + (d010*_fx + d011*fx)*fy)*_fz + ((d100*_fx + d101*fx)*_fy + (d110*_fx + d111*fx)*fy)*fz; } default: return 0.0f; } } Spectrum lookupSpectrum(const Point &_p) const { const Point p = m_worldToGrid.transformAffine(_p); const int x1 = math::floorToInt(p.x), y1 = math::floorToInt(p.y), z1 = math::floorToInt(p.z), x2 = x1+1, y2 = y1+1, z2 = z1+1; if (x1 < 0 || y1 < 0 || z1 < 0 || x2 >= m_res.x || y2 >= m_res.y || z2 >= m_res.z) return Spectrum(0.0f); const Float fx = p.x - x1, fy = p.y - y1, fz = p.z - z1, _fx = 1.0f - fx, _fy = 1.0f - fy, _fz = 1.0f - fz; switch (m_volumeType) { case EFloat32: { const float3 *spectrumData = (float3 *) m_data; const float3 &d000 = spectrumData[(z1*m_res.y + y1)*m_res.x + x1], &d001 = spectrumData[(z1*m_res.y + y1)*m_res.x + x2], &d010 = spectrumData[(z1*m_res.y + y2)*m_res.x + x1], &d011 = spectrumData[(z1*m_res.y + y2)*m_res.x + x2], &d100 = spectrumData[(z2*m_res.y + y1)*m_res.x + x1], &d101 = spectrumData[(z2*m_res.y + y1)*m_res.x + x2], &d110 = spectrumData[(z2*m_res.y + y2)*m_res.x + x1], &d111 = spectrumData[(z2*m_res.y + y2)*m_res.x + x2]; return (((d000*_fx + d001*fx)*_fy + (d010*_fx + d011*fx)*fy)*_fz + ((d100*_fx + d101*fx)*_fy + (d110*_fx + d111*fx)*fy)*fz).toSpectrum(); } case EUInt8: { const float3 d000 = float3( m_densityMap[m_data[3*((z1*m_res.y + y1)*m_res.x + x1)+0]], m_densityMap[m_data[3*((z1*m_res.y + y1)*m_res.x + x1)+1]], m_densityMap[m_data[3*((z1*m_res.y + y1)*m_res.x + x1)+2]]), d001 = float3( m_densityMap[m_data[3*((z1*m_res.y + y1)*m_res.x + x2)+0]], m_densityMap[m_data[3*((z1*m_res.y + y1)*m_res.x + x2)+1]], m_densityMap[m_data[3*((z1*m_res.y + y1)*m_res.x + x2)+2]]), d010 = float3( m_densityMap[m_data[3*((z1*m_res.y + y2)*m_res.x + x1)+0]], m_densityMap[m_data[3*((z1*m_res.y + y2)*m_res.x + x1)+1]], m_densityMap[m_data[3*((z1*m_res.y + y2)*m_res.x + x1)+2]]), d011 = float3( m_densityMap[m_data[3*((z1*m_res.y + y2)*m_res.x + x2)+0]], m_densityMap[m_data[3*((z1*m_res.y + y2)*m_res.x + x2)+1]], m_densityMap[m_data[3*((z1*m_res.y + y2)*m_res.x + x2)+2]]), d100 = float3( m_densityMap[m_data[3*((z2*m_res.y + y1)*m_res.x + x1)+0]], m_densityMap[m_data[3*((z2*m_res.y + y1)*m_res.x + x1)+1]], m_densityMap[m_data[3*((z2*m_res.y + y1)*m_res.x + x1)+2]]), d101 = float3( m_densityMap[m_data[3*((z2*m_res.y + y1)*m_res.x + x2)+0]], m_densityMap[m_data[3*((z2*m_res.y + y1)*m_res.x + x2)+1]], m_densityMap[m_data[3*((z2*m_res.y + y1)*m_res.x + x2)+2]]), d110 = float3( m_densityMap[m_data[3*((z2*m_res.y + y2)*m_res.x + x1)+0]], m_densityMap[m_data[3*((z2*m_res.y + y2)*m_res.x + x1)+1]], m_densityMap[m_data[3*((z2*m_res.y + y2)*m_res.x + x1)+2]]), d111 = float3( m_densityMap[m_data[3*((z2*m_res.y + y2)*m_res.x + x2)+0]], m_densityMap[m_data[3*((z2*m_res.y + y2)*m_res.x + x2)+1]], m_densityMap[m_data[3*((z2*m_res.y + y2)*m_res.x + x2)+2]]); return (((d000*_fx + d001*fx)*_fy + (d010*_fx + d011*fx)*fy)*_fz + ((d100*_fx + d101*fx)*_fy + (d110*_fx + d111*fx)*fy)*fz).toSpectrum(); } default: return Spectrum(0.0f); } } Vector lookupVector(const Point &_p) const { const Point p = m_worldToGrid.transformAffine(_p); const int x1 = math::floorToInt(p.x), y1 = math::floorToInt(p.y), z1 = math::floorToInt(p.z), x2 = x1+1, y2 = y1+1, z2 = z1+1; if (x1 < 0 || y1 < 0 || z1 < 0 || x2 >= m_res.x || y2 >= m_res.y || z2 >= m_res.z) return Vector(0.0f); const Float fx = p.x - x1, fy = p.y - y1, fz = p.z - z1; Vector value; #if defined(VINTERP_NEAREST_NEIGHBOR) /* Nearest neighbor */ switch (m_volumeType) { case EFloat32: { const float3 *vectorData = (float3 *) m_data; value = vectorData[ (((fz < .5) ? z1 : z2) * m_res.y + ((fy < .5) ? y1 : y2)) * m_res.x + ((fx < .5) ? x1 : x2)].toVector(); } break; case EQuantizedDirections: { value = lookupQuantizedDirection( (((fz < .5) ? z1 : z2) * m_res.y + ((fy < .5) ? y1 : y2)) * m_res.x + ((fx < .5) ? x1 : x2)); } break; default: return Vector(0.0f); } #else Float _fx = 1.0f - fx, _fy = 1.0f - fy, _fz = 1.0f - fz; Matrix3x3 tensor(0.0f); switch (m_volumeType) { case EFloat32: { const float3 *vectorData = (float3 *) m_data; for (int k=0; k<8; ++k) { uint32_t index = (((k & 4) ? z2 : z1) * m_res.y + ((k & 2) ? y2 : y1)) * m_res.x + ((k & 1) ? x2 : x1); Float factor = ((k & 1) ? fx : _fx) * ((k & 2) ? fy : _fy) * ((k & 4) ? fz : _fz); Vector d = vectorData[index].toVector(); tensor(0, 0) += factor * d.x * d.x; tensor(0, 1) += factor * d.x * d.y; tensor(0, 2) += factor * d.x * d.z; tensor(1, 1) += factor * d.y * d.y; tensor(1, 2) += factor * d.y * d.z; tensor(2, 2) += factor * d.z * d.z; } } break; case EQuantizedDirections: { for (int k=0; k<8; ++k) { uint32_t index = (((k & 4) ? z2 : z1) * m_res.y + ((k & 2) ? y2 : y1)) * m_res.x + ((k & 1) ? x2 : x1); Float factor = ((k & 1) ? fx : _fx) * ((k & 2) ? fy : _fy) * ((k & 4) ? fz : _fz); Vector d = lookupQuantizedDirection(index); tensor(0, 0) += factor * d.x * d.x; tensor(0, 1) += factor * d.x * d.y; tensor(0, 2) += factor * d.x * d.z; tensor(1, 1) += factor * d.y * d.y; tensor(1, 2) += factor * d.y * d.z; tensor(2, 2) += factor * d.z * d.z; } } break; default: return Vector(0.0f); } tensor(1, 0) = tensor(0, 1); tensor(2, 0) = tensor(0, 2); tensor(2, 1) = tensor(1, 2); if (tensor.isZero()) return Vector(0.0f); #if 0 Float lambda[3]; eig3_noniter(tensor, lambda); value = tensor.col(0); Float specularity = 1-lambda[1]/lambda[0]; #else /* Square the structure tensor for faster convergence */ tensor *= tensor; const Float invSqrt3 = 0.577350269189626f; value = Vector(invSqrt3, invSqrt3, invSqrt3); /* Determine the dominant eigenvector using a few power iterations */ for (int i=0; i m_mmap; Float m_cosTheta[256], m_sinTheta[256]; Float m_cosPhi[256], m_sinPhi[256]; Float m_densityMap[256]; }; MTS_IMPLEMENT_CLASS_S(GridDataSource, false, VolumeDataSource); MTS_EXPORT_PLUGIN(GridDataSource, "Grid data source"); MTS_NAMESPACE_END