mitsuba/src/librender/trimesh.cpp

1081 lines
31 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#include <mitsuba/render/trimesh.h>
#include <mitsuba/core/random.h>
#include <mitsuba/core/plugin.h>
#include <mitsuba/core/zstream.h>
#include <mitsuba/core/timer.h>
#include <mitsuba/core/lock.h>
#include <mitsuba/core/properties.h>
#include <mitsuba/render/subsurface.h>
#include <mitsuba/render/medium.h>
#include <mitsuba/render/bsdf.h>
#include <mitsuba/render/emitter.h>
#include <boost/filesystem/fstream.hpp>
#include <boost/unordered_map.hpp>
#define MTS_FILEFORMAT_HEADER 0x041C
#define MTS_FILEFORMAT_VERSION_V3 0x0003
#define MTS_FILEFORMAT_VERSION_V4 0x0004
MTS_NAMESPACE_BEGIN
TriMesh::TriMesh(const std::string &name, size_t triangleCount,
size_t vertexCount, bool hasNormals, bool hasTexcoords,
bool hasVertexColors, bool flipNormals, bool faceNormals)
: Shape(Properties()), m_triangleCount(triangleCount),
m_vertexCount(vertexCount), m_flipNormals(flipNormals),
m_faceNormals(faceNormals) {
m_name = name;
m_triangles = new Triangle[m_triangleCount];
m_positions = new Point[m_vertexCount];
m_normals = hasNormals ? new Normal[m_vertexCount] : NULL;
m_texcoords = hasTexcoords ? new Point2[m_vertexCount] : NULL;
m_colors = hasVertexColors ? new Color3[m_vertexCount] : NULL;
m_tangents = NULL;
m_surfaceArea = m_invSurfaceArea = -1;
m_mutex = new Mutex();
}
TriMesh::TriMesh(const Properties &props)
: Shape(props), m_triangles(NULL), m_positions(NULL),
m_normals(NULL), m_texcoords(NULL), m_tangents(NULL),
m_colors(NULL) {
/* By default, any existing normals will be used for
rendering. If no normals are found, Mitsuba will
automatically generate smooth vertex normals.
Setting the 'faceNormals' parameter instead forces
the use of face normals, which will result in a faceted
appearance.
*/
m_faceNormals = props.getBoolean("faceNormals", false);
/* Causes all normals to be flipped */
m_flipNormals = props.getBoolean("flipNormals", false);
m_triangles = NULL;
m_surfaceArea = m_invSurfaceArea = -1;
m_mutex = new Mutex();
}
TriMesh::TriMesh(Stream *stream, int index)
: Shape(Properties()), m_triangles(NULL),
m_positions(NULL), m_normals(NULL), m_texcoords(NULL),
m_tangents(NULL), m_colors(NULL) {
m_mutex = new Mutex();
loadCompressed(stream, index);
}
/* Flags used to identify available data during serialization */
enum ETriMeshFlags {
EHasNormals = 0x0001,
EHasTexcoords = 0x0002,
EHasTangents = 0x0004, // unused
EHasColors = 0x0008,
EFaceNormals = 0x0010,
ESinglePrecision = 0x1000,
EDoublePrecision = 0x2000
};
TriMesh::TriMesh(Stream *stream, InstanceManager *manager)
: Shape(stream, manager), m_tangents(NULL) {
m_name = stream->readString();
m_aabb = AABB(stream);
uint32_t flags = stream->readUInt();
m_vertexCount = stream->readSize();
m_triangleCount = stream->readSize();
m_positions = new Point[m_vertexCount];
stream->readFloatArray(reinterpret_cast<Float *>(m_positions),
m_vertexCount * sizeof(Point)/sizeof(Float));
m_faceNormals = flags & EFaceNormals;
if (flags & EHasNormals) {
m_normals = new Normal[m_vertexCount];
stream->readFloatArray(reinterpret_cast<Float *>(m_normals),
m_vertexCount * sizeof(Normal)/sizeof(Float));
} else {
m_normals = NULL;
}
if (flags & EHasTexcoords) {
m_texcoords = new Point2[m_vertexCount];
stream->readFloatArray(reinterpret_cast<Float *>(m_texcoords),
m_vertexCount * sizeof(Point2)/sizeof(Float));
} else {
m_texcoords = NULL;
}
if (flags & EHasColors) {
m_colors = new Color3[m_vertexCount];
stream->readFloatArray(reinterpret_cast<Float *>(m_colors),
m_vertexCount * sizeof(Color3)/sizeof(Float));
} else {
m_colors = NULL;
}
m_triangles = new Triangle[m_triangleCount];
stream->readUIntArray(reinterpret_cast<uint32_t *>(m_triangles),
m_triangleCount * sizeof(Triangle)/sizeof(uint32_t));
m_flipNormals = false;
m_surfaceArea = m_invSurfaceArea = -1;
m_mutex = new Mutex();
configure();
}
static void readHelper(Stream *stream, bool fileDoublePrecision,
Float *target, size_t count, size_t nelems) {
#if defined(SINGLE_PRECISION)
bool hostDoublePrecision = false;
#else
bool hostDoublePrecision = true;
#endif
size_t size = count * nelems;
if (fileDoublePrecision == hostDoublePrecision) {
/* Precision matches - load directly into memory */
stream->readFloatArray(target, size);
} else if (fileDoublePrecision) {
/* Double -> Single conversion */
double *temp = new double[size];
stream->readDoubleArray(temp, size);
for (size_t i=0; i<size; ++i)
target[i] = (Float) temp[i];
delete[] temp;
} else {
/* Single -> Double conversion */
float *temp = new float[size];
stream->readSingleArray(temp, size);
for (size_t i=0; i<size; ++i)
target[i] = (Float) temp[i];
delete[] temp;
}
}
void TriMesh::loadCompressed(Stream *_stream, int index) {
ref<Stream> stream = _stream;
if (stream->getByteOrder() != Stream::ELittleEndian)
Log(EError, "Tried to unserialize a shape from a stream, "
"which was not previously set to little endian byte order!");
const short version = readHeader(stream);
if (index != 0) {
const size_t offset = readOffset(stream, version, index);
stream->seek(offset);
stream->skip(sizeof(short) * 2); // Skip the header
}
stream = new ZStream(stream);
stream->setByteOrder(Stream::ELittleEndian);
uint32_t flags = stream->readUInt();
if (version == MTS_FILEFORMAT_VERSION_V4)
m_name = stream->readString();
m_vertexCount = stream->readSize();
m_triangleCount = stream->readSize();
bool fileDoublePrecision = flags & EDoublePrecision;
m_faceNormals = flags & EFaceNormals;
if (m_positions)
delete[] m_positions;
m_positions = new Point[m_vertexCount];
readHelper(stream, fileDoublePrecision,
reinterpret_cast<Float *>(m_positions),
m_vertexCount, sizeof(Point)/sizeof(Float));
if (m_normals)
delete[] m_normals;
if (flags & EHasNormals) {
m_normals = new Normal[m_vertexCount];
readHelper(stream, fileDoublePrecision,
reinterpret_cast<Float *>(m_normals),
m_vertexCount, sizeof(Normal)/sizeof(Float));
} else {
m_normals = NULL;
}
if (m_texcoords)
delete[] m_texcoords;
if (flags & EHasTexcoords) {
m_texcoords = new Point2[m_vertexCount];
readHelper(stream, fileDoublePrecision,
reinterpret_cast<Float *>(m_texcoords),
m_vertexCount, sizeof(Point2)/sizeof(Float));
} else {
m_texcoords = NULL;
}
if (m_colors)
delete[] m_colors;
if (flags & EHasColors) {
m_colors = new Color3[m_vertexCount];
readHelper(stream, fileDoublePrecision,
reinterpret_cast<Float *>(m_colors),
m_vertexCount, sizeof(Color3)/sizeof(Float));
} else {
m_colors = NULL;
}
m_triangles = new Triangle[m_triangleCount];
stream->readUIntArray(reinterpret_cast<uint32_t *>(m_triangles),
m_triangleCount * sizeof(Triangle)/sizeof(uint32_t));
m_surfaceArea = m_invSurfaceArea = -1;
m_flipNormals = false;
}
short TriMesh::readHeader(Stream *stream) {
short format = stream->readShort();
if (format == 0x1C04) {
Log(EError, "Encountered a geometry file generated by an old "
"version of Mitsuba. Please re-import the scene to update this file "
"to the current format.");
}
if (format != MTS_FILEFORMAT_HEADER) {
Log(EError, "Encountered an invalid file format!");
}
short version = stream->readShort();
if (version != MTS_FILEFORMAT_VERSION_V3 &&
version != MTS_FILEFORMAT_VERSION_V4) {
Log(EError, "Encountered an incompatible file version!");
}
return version;
}
size_t TriMesh::readOffset(Stream *stream, short version, int idx) {
const size_t streamSize = stream->getSize();
/* Determine the position of the requested substream. This is stored
at the end of the file */
stream->seek(streamSize - sizeof(uint32_t));
uint32_t count = stream->readUInt();
if (idx < 0 || idx > (int) count) {
Log(EError, "Unable to unserialize mesh, "
"shape index is out of range! (requested %i out of 0..%i)",
idx, count-1);
}
// Seek to the correct position
if (version == MTS_FILEFORMAT_VERSION_V4) {
stream->seek(stream->getSize() - sizeof(uint64_t) * (count-idx) - sizeof(uint32_t));
return stream->readSize();
} else {
Assert(version == MTS_FILEFORMAT_VERSION_V3);
stream->seek(stream->getSize() - sizeof(uint32_t) * (count-idx + 1));
return stream->readUInt();
}
}
int TriMesh::readOffsetDictionary(Stream *stream, short version,
std::vector<size_t>& outOffsets) {
const size_t streamSize = stream->getSize();
stream->seek(streamSize - sizeof(uint32_t));
const uint32_t count = stream->readUInt();
// Check if the stream is large enough to contain that number of meshes
const size_t minSize = sizeof(uint32_t) + count *
( 2*sizeof(uint16_t) // Header
+ sizeof(uint32_t) // Flags
+ sizeof(char) // Name
+ 2*sizeof(uint64_t) // Number of vertices and triangles
+ 3*sizeof(float) // One vertex
+ 3*sizeof(uint32_t)); // One triangle
if (streamSize >= minSize) {
outOffsets.resize(count);
if (version == MTS_FILEFORMAT_VERSION_V4) {
stream->seek(stream->getSize() - sizeof(uint64_t) * count - sizeof(uint32_t));
if (typeid(size_t) == typeid(uint64_t)) {
stream->readArray(&outOffsets[0], count);
} else {
for (size_t i = 0; i < count; ++i)
outOffsets[i] = stream->readSize();
}
} else {
stream->seek(stream->getSize() - sizeof(uint32_t) * (count + 1));
Assert(version == MTS_FILEFORMAT_VERSION_V3);
if (typeid(size_t) == typeid(uint32_t)) {
stream->readArray(&outOffsets[0], count);
} else {
for (size_t i = 0; i < count; ++i) {
outOffsets[i] = (size_t) stream->readUInt();
}
}
}
return count;
} else {
Log(EDebug, "The serialized mesh does not contain a valid dictionary");
return -1;
}
}
TriMesh::~TriMesh() {
if (m_positions)
delete[] m_positions;
if (m_normals)
delete[] m_normals;
if (m_texcoords)
delete[] m_texcoords;
if (m_tangents)
delete[] m_tangents;
if (m_colors)
delete[] m_colors;
if (m_triangles)
delete[] m_triangles;
}
std::string TriMesh::getName() const {
return m_name;
}
AABB TriMesh::getAABB() const {
return m_aabb;
}
Float TriMesh::pdfPosition(const PositionSamplingRecord &pRec) const {
return m_invSurfaceArea;
}
void TriMesh::configure() {
Shape::configure();
if (!m_aabb.isValid()) {
/* Most shape objects should compute the AABB while
loading the geometry -- but let's be on the safe side */
for (size_t i=0; i<m_vertexCount; i++)
m_aabb.expandBy(m_positions[i]);
}
/* Potentially compute/recompute/flip normals, as specified by the user */
computeNormals();
/* Compute proper position partials with respect to the UV paramerization when:
1. An anisotropic BRDF is attached to the shape
2. The material explicitly requests tangents so that it can do texture filtering
*/
if (hasBSDF() &&
((m_bsdf->getType() & BSDF::EAnisotropic) || m_bsdf->usesRayDifferentials()))
computeUVTangents();
/* For manifold exploration: always compute UV tangents when a glossy material
is involved. TODO: find a way to avoid this expense (compute on demand?) */
if (hasBSDF() && (m_bsdf->getType() & BSDF::EGlossy))
computeUVTangents();
}
void TriMesh::prepareSamplingTable() {
if (m_triangleCount == 0) {
Log(EError, "Encountered an empty triangle mesh!");
return;
}
LockGuard guard(m_mutex);
if (m_surfaceArea < 0) {
/* Generate a PDF for sampling wrt. area */
m_areaDistr.reserve(m_triangleCount);
for (size_t i=0; i<m_triangleCount; i++)
m_areaDistr.append(m_triangles[i].surfaceArea(m_positions));
m_surfaceArea = m_areaDistr.normalize();
m_invSurfaceArea = 1.0f / m_surfaceArea;
}
}
Float TriMesh::getSurfaceArea() const {
if (EXPECT_NOT_TAKEN(m_surfaceArea < 0))
const_cast<TriMesh *>(this)->prepareSamplingTable();
return m_surfaceArea;
}
void TriMesh::samplePosition(PositionSamplingRecord &pRec,
const Point2 &_sample) const {
if (EXPECT_NOT_TAKEN(m_surfaceArea < 0))
const_cast<TriMesh *>(this)->prepareSamplingTable();
Point2 sample(_sample);
size_t index = m_areaDistr.sampleReuse(sample.y);
pRec.p = m_triangles[index].sample(m_positions, m_normals,
m_texcoords, pRec.n, pRec.uv, sample);
pRec.pdf = m_invSurfaceArea;
pRec.measure = EArea;
}
struct Vertex {
Point p;
Point2 uv;
Color3 col;
inline Vertex() : p(0.0f), uv(0.0f), col(0.0f) { }
};
/// For using vertices as keys in an associative structure
struct vertex_key_order : public
std::binary_function<Vertex, Vertex, bool> {
static int compare(const Vertex &v1, const Vertex &v2) {
if (v1.p.x < v2.p.x) return -1;
else if (v1.p.x > v2.p.x) return 1;
if (v1.p.y < v2.p.y) return -1;
else if (v1.p.y > v2.p.y) return 1;
if (v1.p.z < v2.p.z) return -1;
else if (v1.p.z > v2.p.z) return 1;
if (v1.uv.x < v2.uv.x) return -1;
else if (v1.uv.x > v2.uv.x) return 1;
if (v1.uv.y < v2.uv.y) return -1;
else if (v1.uv.y > v2.uv.y) return 1;
for (int i=0; i<SPECTRUM_SAMPLES; ++i) {
if (v1.col[i] < v2.col[i]) return -1;
else if (v1.col[i] > v2.col[i]) return 1;
}
return 0;
}
bool operator()(const Vertex &v1, const Vertex &v2) const {
return compare(v1, v2) < 0;
}
};
/// Used in \ref TriMesh::rebuildTopology()
struct TopoData {
size_t idx; /// Triangle index
bool clustered; /// Has the tri-vert. pair been assigned to a cluster?
inline TopoData() { }
inline TopoData(size_t idx, bool clustered)
: idx(idx), clustered(clustered) { }
};
void TriMesh::rebuildTopology(Float maxAngle) {
typedef std::multimap<Vertex, TopoData, vertex_key_order> MMap;
typedef std::pair<Vertex, TopoData> MPair;
const Float dpThresh = std::cos(degToRad(maxAngle));
if (m_normals) {
delete[] m_normals;
m_normals = NULL;
}
if (m_tangents) {
delete[] m_tangents;
m_tangents = NULL;
}
Log(EInfo, "Rebuilding the topology of \"%s\" (" SIZE_T_FMT
" triangles, " SIZE_T_FMT " vertices, max. angle = %f)",
m_name.c_str(), m_triangleCount, m_vertexCount, maxAngle);
ref<Timer> timer = new Timer();
MMap vertexToFace;
std::vector<Point> newPositions;
std::vector<Point2> newTexcoords;
std::vector<Color3> newColors;
std::vector<Normal> faceNormals(m_triangleCount);
Triangle *newTriangles = new Triangle[m_triangleCount];
newPositions.reserve(m_vertexCount);
if (m_texcoords != NULL)
newTexcoords.reserve(m_vertexCount);
if (m_colors != NULL)
newColors.reserve(m_vertexCount);
/* Create an associative list and precompute a few things */
for (size_t i=0; i<m_triangleCount; ++i) {
const Triangle &tri = m_triangles[i];
Vertex v;
for (int j=0; j<3; ++j) {
v.p = m_positions[tri.idx[j]];
if (m_texcoords)
v.uv = m_texcoords[tri.idx[j]];
if (m_colors)
v.col = m_colors[tri.idx[j]];
vertexToFace.insert(MPair(v, TopoData(i, false)));
}
Point v0 = m_positions[tri.idx[0]];
Point v1 = m_positions[tri.idx[1]];
Point v2 = m_positions[tri.idx[2]];
faceNormals[i] = Normal(normalize(cross(v1 - v0, v2 - v0)));
for (int j=0; j<3; ++j)
newTriangles[i].idx[j] = 0xFFFFFFFFU;
}
/* Under the reasonable assumption that the vertex degree is
bounded by a constant, the following runs in O(n) */
for (MMap::iterator it = vertexToFace.begin(); it != vertexToFace.end();) {
MMap::iterator start = vertexToFace.lower_bound(it->first);
MMap::iterator end = vertexToFace.upper_bound(it->first);
/* Perform a greedy clustering of normals */
for (MMap::iterator it2 = start; it2 != end; it2++) {
const Vertex &v = it2->first;
const TopoData &t1 = it2->second;
Normal n1(faceNormals[t1.idx]);
if (t1.clustered)
continue;
uint32_t vertexIdx = (uint32_t) newPositions.size();
newPositions.push_back(v.p);
if (m_texcoords)
newTexcoords.push_back(v.uv);
if (m_colors)
newColors.push_back(v.col);
for (MMap::iterator it3 = it2; it3 != end; ++it3) {
TopoData &t2 = it3->second;
if (t2.clustered)
continue;
Normal n2(faceNormals[t2.idx]);
if (n1 == n2 || dot(n1, n2) > dpThresh) {
const Triangle &tri = m_triangles[t2.idx];
Triangle &newTri = newTriangles[t2.idx];
for (int i=0; i<3; ++i) {
if (m_positions[tri.idx[i]] == v.p)
newTri.idx[i] = vertexIdx;
}
t2.clustered = true;
}
}
}
it = end;
}
for (size_t i=0; i<m_triangleCount; ++i)
for (int j=0; j<3; ++j)
Assert(newTriangles[i].idx[j] != 0xFFFFFFFFU);
delete[] m_triangles;
m_triangles = newTriangles;
delete[] m_positions;
m_positions = new Point[newPositions.size()];
memcpy(m_positions, &newPositions[0], sizeof(Point) * newPositions.size());
if (m_texcoords) {
delete[] m_texcoords;
m_texcoords = new Point2[newTexcoords.size()];
memcpy(m_texcoords, &newTexcoords[0], sizeof(Point2) * newTexcoords.size());
}
if (m_colors) {
delete[] m_colors;
m_colors = new Color3[newColors.size()];
memcpy(m_colors, &newColors[0], sizeof(Color3) * newColors.size());
}
m_vertexCount = newPositions.size();
Log(EInfo, "Done after %i ms (mesh now has " SIZE_T_FMT " vertices)",
timer->getMilliseconds(), m_vertexCount);
configure();
}
void TriMesh::computeNormals(bool force) {
int invalidNormals = 0;
if (m_faceNormals) {
if (m_normals) {
delete[] m_normals;
m_normals = NULL;
}
if (m_flipNormals) {
/* Change the winding order */
for (size_t i=0; i<m_triangleCount; ++i) {
Triangle &t = m_triangles[i];
std::swap(t.idx[0], t.idx[1]);
}
}
} else {
if (m_normals && !force) {
if (m_flipNormals) {
for (size_t i=0; i<m_vertexCount; i++)
m_normals[i] *= -1;
} else {
/* Do nothing */
}
} else {
if (!m_normals)
m_normals = new Normal[m_vertexCount];
memset(m_normals, 0, sizeof(Normal)*m_vertexCount);
/* Well-behaved vertex normal computation based on
"Computing Vertex Normals from Polygonal Facets"
by Grit Thuermer and Charles A. Wuethrich,
JGT 1998, Vol 3 */
for (size_t i=0; i<m_triangleCount; i++) {
const Triangle &tri = m_triangles[i];
Normal n(0.0f);
for (int i=0; i<3; ++i) {
const Point &v0 = m_positions[tri.idx[i]];
const Point &v1 = m_positions[tri.idx[(i+1)%3]];
const Point &v2 = m_positions[tri.idx[(i+2)%3]];
Vector sideA(v1-v0), sideB(v2-v0);
if (i==0) {
n = cross(sideA, sideB);
Float length = n.length();
if (length == 0)
break;
n /= length;
}
Float angle = unitAngle(normalize(sideA), normalize(sideB));
m_normals[tri.idx[i]] += n * angle;
}
}
for (size_t i=0; i<m_vertexCount; i++) {
Normal &n = m_normals[i];
Float length = n.length();
if (m_flipNormals)
length *= -1;
if (length != 0) {
n /= length;
} else {
/* Choose some bogus value */
invalidNormals++;
n = Normal(1, 0, 0);
}
}
}
}
m_flipNormals = false;
if (invalidNormals > 0)
Log(EWarn, "\"%s\": Unable to generate %i vertex normals",
m_name.c_str(), invalidNormals);
}
void TriMesh::computeUVTangents() {
// int degenerate = 0;
if (!m_texcoords) {
bool anisotropic = hasBSDF() && m_bsdf->getType() & BSDF::EAnisotropic;
if (anisotropic)
Log(EError, "\"%s\": computeUVTangents(): texture coordinates "
"are required to generate tangent vectors. If you want to render with an anisotropic "
"material, please make sure that all associated shapes have valid texture coordinates.",
getName().c_str());
return;
}
if (m_tangents)
return;
m_tangents = new TangentSpace[m_triangleCount];
memset(m_tangents, 0, sizeof(TangentSpace)*m_triangleCount);
for (size_t i=0; i<m_triangleCount; i++) {
uint32_t idx0 = m_triangles[i].idx[0],
idx1 = m_triangles[i].idx[1],
idx2 = m_triangles[i].idx[2];
const Point
&v0 = m_positions[idx0],
&v1 = m_positions[idx1],
&v2 = m_positions[idx2];
const Point2
&uv0 = m_texcoords[idx0],
&uv1 = m_texcoords[idx1],
&uv2 = m_texcoords[idx2];
Vector dP1 = v1 - v0, dP2 = v2 - v0;
Vector2 dUV1 = uv1 - uv0, dUV2 = uv2 - uv0;
Normal n = Normal(cross(dP1, dP2));
Float length = n.length();
if (length == 0) {
// ++degenerate;
continue;
}
Float determinant = dUV1.x * dUV2.y - dUV1.y * dUV2.x;
if (determinant == 0) {
/* The user-specified parameterization is degenerate. Pick
arbitrary tangents that are perpendicular to the geometric normal */
coordinateSystem(n/length, m_tangents[i].dpdu, m_tangents[i].dpdv);
} else {
Float invDet = 1.0f / determinant;
m_tangents[i].dpdu = ( dUV2.y * dP1 - dUV1.y * dP2) * invDet;
m_tangents[i].dpdv = (-dUV2.x * dP1 + dUV1.x * dP2) * invDet;
}
}
#if 0
/* Don't be so noisy -- this isn't usually a problem.. */
if (degenerate > 0)
Log(EWarn, "\"%s\": computeTangentSpace(): Mesh contains %i "
"degenerate triangles!", getName().c_str(), degenerate);
#endif
}
void TriMesh::getNormalDerivative(const Intersection &its,
Vector &dndu, Vector &dndv, bool shadingFrame) const {
if (!shadingFrame || !m_normals) {
dndu = dndv = Vector(0.0f);
} else {
Assert(its.primIndex < m_triangleCount);
const Triangle &tri = m_triangles[its.primIndex];
uint32_t idx0 = tri.idx[0],
idx1 = tri.idx[1],
idx2 = tri.idx[2];
const Point
&p0 = m_positions[idx0],
&p1 = m_positions[idx1],
&p2 = m_positions[idx2];
/* Recompute the barycentric coordinates, since 'its.uv' may have been
overwritten with coordinates of the texture "parameterization". */
Vector rel = its.p - p0, du = p1 - p0, dv = p2 - p0;
Float b1 = dot(du, rel), b2 = dot(dv, rel), /* Normal equations */
a11 = dot(du, du), a12 = dot(du, dv),
a22 = dot(dv, dv),
det = a11 * a22 - a12 * a12;
if (det == 0) {
dndu = dndv = Vector(0.0f);
return;
}
Float invDet = 1.0f / det,
u = ( a22 * b1 - a12 * b2) * invDet,
v = (-a12 * b1 + a11 * b2) * invDet,
w = 1 - u - v;
const Normal
&n0 = m_normals[idx0],
&n1 = m_normals[idx1],
&n2 = m_normals[idx2];
/* Now compute the derivative of "normalize(u*n1 + v*n2 + (1-u-v)*n0)"
with respect to [u, v] in the local triangle parameterization.
Since d/du [f(u)/|f(u)|] = [d/du f(u)]/|f(u)|
- f(u)/|f(u)|^3 <f(u), d/du f(u)>, this results in
*/
Normal N(u * n1 + v * n2 + w * n0);
Float il = 1.0f / N.length(); N *= il;
dndu = (n1 - n0) * il; dndu -= N * dot(N, dndu);
dndv = (n2 - n0) * il; dndv -= N * dot(N, dndv);
if (m_tangents) {
/* Compute derivatives with respect to a specified texture
UV parameterization. */
const Point2
&uv0 = m_texcoords[idx0],
&uv1 = m_texcoords[idx1],
&uv2 = m_texcoords[idx2];
Vector2 duv1 = uv1 - uv0, duv2 = uv2 - uv0;
det = duv1.x * duv2.y - duv1.y * duv2.x;
if (det == 0) {
dndu = dndv = Vector(0.0f);
return;
}
invDet = 1.0f / det;
Vector dndu_ = ( duv2.y * dndu - duv1.y * dndv) * invDet;
Vector dndv_ = (-duv2.x * dndu + duv1.x * dndv) * invDet;
dndu = dndu_; dndv = dndv_;
}
}
}
ref<TriMesh> TriMesh::createTriMesh() {
return this;
}
void TriMesh::serialize(Stream *stream, InstanceManager *manager) const {
Shape::serialize(stream, manager);
uint32_t flags = 0;
if (m_normals)
flags |= EHasNormals;
if (m_texcoords)
flags |= EHasTexcoords;
if (m_colors)
flags |= EHasColors;
if (m_faceNormals)
flags |= EFaceNormals;
stream->writeString(m_name);
m_aabb.serialize(stream);
stream->writeUInt(flags);
stream->writeSize(m_vertexCount);
stream->writeSize(m_triangleCount);
stream->writeFloatArray(reinterpret_cast<Float *>(m_positions),
m_vertexCount * sizeof(Point)/sizeof(Float));
if (m_normals)
stream->writeFloatArray(reinterpret_cast<Float *>(m_normals),
m_vertexCount * sizeof(Normal)/sizeof(Float));
if (m_texcoords)
stream->writeFloatArray(reinterpret_cast<Float *>(m_texcoords),
m_vertexCount * sizeof(Point2)/sizeof(Float));
if (m_colors)
stream->writeFloatArray(reinterpret_cast<Float *>(m_colors),
m_vertexCount * sizeof(Color3)/sizeof(Float));
stream->writeUIntArray(reinterpret_cast<uint32_t *>(m_triangles),
m_triangleCount * sizeof(Triangle)/sizeof(uint32_t));
}
ref<TriMesh> TriMesh::fromBlender(const std::string &name,
size_t faceCount, void *_facePtr, size_t vertexCount, void *_vertexPtr, void *_uvPtr, void *_colPtr, short mat_nr) {
struct MFace {
uint32_t v[4];
int16_t mat_nr;
uint8_t edcode, flag;
};
struct MVert {
float co[3];
int16_t no[3];
uint8_t flag, bweight;
};
struct MCol {
uint8_t a, r, g, b;
};
struct MLoopUV {
float uv[2];
int32_t flag;
};
MFace *facePtr = (MFace *) _facePtr;
MVert *vertexPtr = (MVert *) _vertexPtr;
MCol *colPtr = (MCol *) _colPtr;
MLoopUV *uvPtr = (MLoopUV *) _uvPtr;
boost::unordered_map<uint32_t, uint32_t> vertexMap;
uint32_t triangleCtr = 0, vertexCtr = 0;
for (int i=0; i<faceCount; ++i) {
const MFace &face = facePtr[i];
if (face.mat_nr == mat_nr) {
bool triangle = face.v[3] == 0;
for (int j=0; j<(triangle ? 3 : 4); ++j) {
if (vertexMap.find(face.v[j]) == vertexMap.end())
vertexMap[face.v[j]] = vertexCtr++;
}
triangleCtr += triangle ? 1 : 2;
}
}
ref<TriMesh> triMesh = new TriMesh(name, triangleCtr, vertexCtr, true,
uvPtr != NULL, colPtr != NULL);
uint32_t *triangles = (uint32_t *) triMesh->getTriangles();
Point *vertexPositions = (Point *) triMesh->getVertexPositions();
Normal *vertexNormals = (Normal *) triMesh->getVertexNormals();
Color3 *vertexColors = (Color3 *) triMesh->getVertexColors();
Point2 *vertexTexcoords = (Point2 *) triMesh->getVertexTexcoords();
for (int i=0; i<faceCount; ++i) {
const MFace &face = facePtr[i];
if (face.mat_nr == mat_nr) {
*triangles++ = vertexMap[face.v[0]];
*triangles++ = vertexMap[face.v[1]];
*triangles++ = vertexMap[face.v[2]];
if (face.v[3] != 0) {
*triangles++ = vertexMap[face.v[0]];
*triangles++ = vertexMap[face.v[2]];
*triangles++ = vertexMap[face.v[3]];
}
}
}
const float normalScale = 1.0f / 32767.0f;
const float rgbScale = 1.0f / 255.0f;
for (boost::unordered_map<uint32_t, uint32_t>::iterator it = vertexMap.begin();
it != vertexMap.end(); ++it) {
const MVert &vertex = vertexPtr[it->first];
uint32_t idx = it->second;
vertexPositions[idx] = Point3(vertex.co[0], vertex.co[1], vertex.co[2]);
vertexNormals[idx] = normalize(Normal(
vertex.no[0] * normalScale,
vertex.no[1] * normalScale,
vertex.no[2] * normalScale
));
if (uvPtr) {
const MLoopUV &uv = uvPtr[it->first];
vertexTexcoords[idx] = Point2(uv.uv[0], uv.uv[1]);
}
if (colPtr) {
const MCol &col = colPtr[it->first];
vertexColors[idx] = Color3(
col.r * rgbScale,
col.g * rgbScale,
col.b * rgbScale
);
}
}
return triMesh;
}
void TriMesh::writeOBJ(const fs::path &path) const {
fs::ofstream os(path);
os << "o " << m_name << endl;
for (size_t i=0; i<m_vertexCount; ++i) {
os << "v "
<< m_positions[i].x << " "
<< m_positions[i].y << " "
<< m_positions[i].z << endl;
}
if (m_texcoords) {
for (size_t i=0; i<m_vertexCount; ++i) {
os << "vt "
<< m_texcoords[i].x << " "
<< m_texcoords[i].y << endl;
}
}
if (m_normals) {
for (size_t i=0; i<m_vertexCount; ++i) {
os << "vn "
<< m_normals[i].x << " "
<< m_normals[i].y << " "
<< m_normals[i].z << endl;
}
}
for (size_t i=0; i<m_triangleCount; ++i) {
uint32_t i0 = m_triangles[i].idx[0] + 1,
i1 = m_triangles[i].idx[1] + 1,
i2 = m_triangles[i].idx[2] + 1;
if (m_normals && m_texcoords) {
os << "f " << i0 << "/" << i0 << "/" << i0 << " "
<< i1 << "/" << i1 << "/" << i1 << " "
<< i2 << "/" << i2 << "/" << i2 << endl;
} else if (m_normals) {
os << "f " << i0 << "//" << i0 << " "
<< i1 << "//" << i1 << " "
<< i2 << "//" << i2 << endl;
} else {
os << "f " << i0 << " " << i1 << " " << i2 << endl;
}
}
os.close();
}
void TriMesh::serialize(Stream *_stream) const {
ref<Stream> stream = _stream;
if (stream->getByteOrder() != Stream::ELittleEndian)
Log(EError, "Tried to unserialize a shape from a stream, "
"which was not previously set to little endian byte order!");
stream->writeShort(MTS_FILEFORMAT_HEADER);
stream->writeShort(MTS_FILEFORMAT_VERSION_V4);
stream = new ZStream(stream);
#if defined(SINGLE_PRECISION)
uint32_t flags = ESinglePrecision;
#else
uint32_t flags = EDoublePrecision;
#endif
if (m_normals)
flags |= EHasNormals;
if (m_texcoords)
flags |= EHasTexcoords;
if (m_colors)
flags |= EHasColors;
if (m_faceNormals)
flags |= EFaceNormals;
stream->writeUInt(flags);
stream->writeString(m_name);
stream->writeSize(m_vertexCount);
stream->writeSize(m_triangleCount);
stream->writeFloatArray(reinterpret_cast<Float *>(m_positions),
m_vertexCount * sizeof(Point)/sizeof(Float));
if (m_normals)
stream->writeFloatArray(reinterpret_cast<Float *>(m_normals),
m_vertexCount * sizeof(Normal)/sizeof(Float));
if (m_texcoords)
stream->writeFloatArray(reinterpret_cast<Float *>(m_texcoords),
m_vertexCount * sizeof(Point2)/sizeof(Float));
if (m_colors)
stream->writeFloatArray(reinterpret_cast<Float *>(m_colors),
m_vertexCount * sizeof(Color3)/sizeof(Float));
stream->writeUIntArray(reinterpret_cast<uint32_t *>(m_triangles),
m_triangleCount * sizeof(Triangle)/sizeof(uint32_t));
}
size_t TriMesh::getPrimitiveCount() const {
return m_triangleCount;
}
size_t TriMesh::getEffectivePrimitiveCount() const {
return m_triangleCount;
}
std::string TriMesh::toString() const {
std::ostringstream oss;
oss << getClass()->getName() << "[" << endl
<< " name = \"" << m_name<< "\"," << endl
<< " triangleCount = " << m_triangleCount << "," << endl
<< " vertexCount = " << m_vertexCount << "," << endl
<< " faceNormals = " << (m_faceNormals ? "true" : "false") << "," << endl
<< " hasNormals = " << (m_normals ? "true" : "false") << "," << endl
<< " hasTexcoords = " << (m_texcoords ? "true" : "false") << "," << endl
<< " hasTangents = " << (m_tangents ? "true" : "false") << "," << endl
<< " hasColors = " << (m_colors ? "true" : "false") << "," << endl
<< " surfaceArea = " << m_surfaceArea << "," << endl
<< " aabb = " << m_aabb.toString() << "," << endl
<< " bsdf = " << indent(m_bsdf.toString()) << "," << endl;
if (isMediumTransition())
oss << " interiorMedium = " << indent(m_interiorMedium.toString()) << "," << endl
<< " exteriorMedium = " << indent(m_exteriorMedium.toString()) << "," << endl;
oss << " subsurface = " << indent(m_subsurface.toString()) << "," << endl
<< " emitter = " << indent(m_emitter.toString()) << endl
<< "]";
return oss.str();
}
MTS_IMPLEMENT_CLASS_S(TriMesh, false, Shape)
MTS_NAMESPACE_END