diff --git a/SConstruct b/SConstruct
index 6d43bcb4..1fd7dd21 100644
--- a/SConstruct
+++ b/SConstruct
@@ -519,7 +519,7 @@ plugins += env.SharedLibrary('plugins/ply', ['src/shapes/ply/ply.cpp', 'src/shap
plugins += env.SharedLibrary('plugins/serialized', ['src/shapes/serialized.cpp'])
plugins += env.SharedLibrary('plugins/sphere', ['src/shapes/sphere.cpp'])
plugins += env.SharedLibrary('plugins/cylinder', ['src/shapes/cylinder.cpp'])
-plugins += env.SharedLibrary('plugins/hair', ['src/shapes/hair.cpp'])
+plugins += env.SharedLibrary('plugins/hair', ['src/shapes/hair.cpp', 'src/shapes/miterseg.cpp'])
#plugins += env.SharedLibrary('plugins/group', ['src/shapes/group.cpp'])
# Samplers
diff --git a/src/shapes/hair.cpp b/src/shapes/hair.cpp
index bad59cc4..b75137e7 100644
--- a/src/shapes/hair.cpp
+++ b/src/shapes/hair.cpp
@@ -1,152 +1,102 @@
-/*
- This file is part of Mitsuba, a physically based rendering system.
-
- Copyright (c) 2007-2010 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
-#include
+
+#include "hair.h"
+#include "miterseg.h"
MTS_NAMESPACE_BEGIN
-/**
- * The 'Hair' primitive consists of a list of hair segments, which are
- * rasterized into cylinders and spheres. The file format is simply a
- * list of lines of the form "x y z" where an empty line indicates the beginning
- * of a new hair.
- */
-class Hair : public Shape {
- struct HairSegment {
- Point start, end;
-
- inline HairSegment(Point start, Point end)
- : start(start), end(end) {
- }
- };
-
- Float m_radius;
- std::vector m_segments;
-public:
- Hair(const Properties &props) : Shape(props) {
- fs::path filename = Thread::getThread()->getFileResolver()->resolve(
+Hair::Hair(const Properties &props) : Shape(props) {
+ std::string filename = props.getString("filename");
props.getString("filename"));
- m_radius = (Float) props.getFloat("radius", 0.05f);
- m_name = filename.leaf();
+ m_radius = (Float) props.getFloat("radius", 0.05f);
+ m_name = FileResolver::getInstance()->resolve(filename);
- Log(EInfo, "Loading hair geometry from \"%s\" ..", m_name.c_str());
+ Log(EInfo, "Loading hair geometry from \"%s\" ..", m_name.c_str());
- fs::ifstream is(m_name.c_str());
- if (is.fail())
- Log(EError, "Could not open \"%s\"!", m_name.c_str());
+ std::ifstream is(m_name.c_str());
+ if (is.fail())
+ Log(EError, "Could not open \"%s\"!", m_name.c_str());
- std::string line;
- int segments = 0;
- Point p(0.0f) , prev(0.0f);
- while (is.good()) {
- std::getline(is, line);
- if (line.length() > 0 && line[0] == '#')
- continue;
- if (line.length() == 0) {
- segments = 0;
- } else {
- std::istringstream iss(line);
- iss >> p.x >> p.y >> p.z;
- m_aabb.expandBy(m_objectToWorld(p));
-
- if (segments++ > 0)
- m_segments.push_back(HairSegment(prev, p));
-
- prev = p;
+ std::string line;
+ bool newFiber = true;
+ Point p;
+ while (is.good()) {
+ std::getline(is, line);
+ if (line.length() > 0 && line[0] == '#')
+ continue;
+ if (line.length() == 0) {
+ newFiber = true;
+ } else {
+ std::istringstream iss(line);
+ iss >> p.x >> p.y >> p.z;
+ if (!iss.fail()) {
+ m_vertices.push_back(p);
+ m_startFiber.push_back(newFiber);
+ newFiber = false;
}
}
- m_aabb.min -= Vector(m_radius, m_radius, m_radius);
- m_aabb.max += Vector(m_radius, m_radius, m_radius);
-
- Log(EDebug, "Read %i hair segments.", m_segments.size());
}
+ m_startFiber.push_back(true);
- Hair(Stream *stream, InstanceManager *manager) : Shape(stream, manager) {
- m_radius = stream->readFloat();
+ buildSegIndex();
- size_t segmentCount = stream->readUInt();
- m_segments.reserve(segmentCount);
- for (size_t i=0; ireadFloat();
+ size_t segmentCount = stream->readUInt();
- stream->writeFloat(m_radius);
- stream->writeUInt(m_segments.size());
- for (size_t i=0; ireadBool());
- Shape *getElement(int _index) {
- unsigned int index = _index / 2;
- if (index >= m_segments.size())
- return NULL;
+ buildSegIndex();
+}
- Point start = m_segments[index].start;
- Point end = m_segments[index].end;
- Float length = (end-start).length();
+void Hair::serialize(Stream *stream, InstanceManager *manager) const {
+ Shape::serialize(stream, manager);
- Vector axis = normalize(end-start);
- Vector rotAxis = normalize(cross(Vector(0,0,1), axis));
- Float rotAngle = radToDeg(std::acos(axis.z));
+ stream->writeFloat(m_radius);
+ size_t segmentCount = m_vertices.size();
+ stream->writeUInt(segmentCount);
- Transform trafo =
- m_objectToWorld
- * Transform::translate(Vector(start))
- * Transform::rotate(rotAxis, rotAngle);
+ for (size_t i=0; i (PluginManager::getInstance()->
- createObject(Shape::m_theClass, sphereProperties));
- sphere->addChild("bsdf", m_bsdf);
- sphere->configure();
- return sphere;
- } else {
- Properties cylinderProperties("cylinder");
- cylinderProperties.setFloat("radius", m_radius);
- cylinderProperties.setFloat("length", length);
- cylinderProperties.setTransform("toWorld", trafo);
- Shape *cylinder = static_cast (PluginManager::getInstance()->
- createObject(Shape::m_theClass, cylinderProperties));
- cylinder->addChild("bsdf", m_bsdf);
- cylinder->configure();
+ for (size_t i=0; iwriteBool(m_startFiber[i]);
+}
+
+Shape *Hair::getElement(int index) {
+ if ((size_t) index >= m_segIndex.size())
+ return NULL;
+
+ MiterHairSegment *segment = new MiterHairSegment(this, m_segIndex[index]);
+ segment->addChild("bsdf", m_bsdf);
+ segment->configure();
+ return segment;
+}
+
+
+void Hair::buildSegIndex() {
+ // Compute the index of the first vertex in each segment.
+ m_segIndex.clear();
+ for (size_t i=0; i
+
+MTS_NAMESPACE_BEGIN
+
+
+class Hair : public Shape {
+
+ // The radius of the hair fibers (all fibers have constant radius)
+ Float m_radius;
+
+ // The vertices of the hair: [0...nVertices)
+ std::vector m_vertices;
+
+ // An indication of which vertices start a new fiber: [0...nVertices+1)
+ std::vector m_startFiber;
+
+ // A mapping of segment indices to vertex indices (needed only for construction): [0...nSegments)
+ std::vector m_segIndex;
+
+public:
+
+ Hair(const Properties &props);
+
+ Hair(Stream *stream, InstanceManager *manager);
+
+ void serialize(Stream *stream, InstanceManager *manager) const;
+
+ bool isCompound() const {
+ return true;
+ }
+
+ Shape *getElement(int index);
+
+ Float radius() const { return m_radius; }
+
+ Point vertex(int iv) const { return m_vertices[iv]; }
+ void vertex(int iv, Point &p) const { p = m_vertices[iv]; }
+
+ bool vertexStartsFiber(int iv) const { return m_startFiber[iv]; }
+
+
+protected:
+
+ void buildSegIndex();
+
+ MTS_DECLARE_CLASS()
+};
+
+
+MTS_NAMESPACE_END
+
+
+#endif /* HAIR_H_ */
diff --git a/src/shapes/miterseg.cpp b/src/shapes/miterseg.cpp
new file mode 100644
index 00000000..3d3cd6a3
--- /dev/null
+++ b/src/shapes/miterseg.cpp
@@ -0,0 +1,223 @@
+#include
+#include "miterseg.h"
+
+MTS_NAMESPACE_BEGIN
+
+MiterHairSegment::MiterHairSegment(ref parent, int iv) : Shape(Properties()) {
+ m_hair = parent;
+ m_iv = iv;
+ configure();
+}
+
+
+void MiterHairSegment::configure() {
+ Shape::configure();
+
+ m_aabb.reset(); m_bsphere.radius = 0;
+
+ const Point start = firstVertex();
+ const Point end = secondVertex();
+ const Vector segment = end - start;
+
+ // Caution this is false for collapsing segments (where the intersection of the miter planes intersects the cylinder)
+ m_surfaceArea = 2*M_PI * m_hair->radius() * segment.length();
+ m_invSurfaceArea = 1.0f / m_surfaceArea;
+
+ m_aabb = getWorldAABB(0, 1);
+ m_bsphere.center = m_aabb.getCenter();
+ for (int i=0; i<8; ++i)
+ m_bsphere.expandBy(m_aabb.getCorner(i));
+}
+
+
+AABB MiterHairSegment::getWorldAABB(Float t0, Float t1) const {
+ AABB result;
+
+ // The bounding box is conservatively the bbox of two spheres at the
+ // endpoints of the segment. Each sphere's radius is the hair
+ // radius divided by the cosine of the steepest miter angle, making
+ // it a bounding sphere for the ellipsoidal boundary for that end
+ // of the segment.
+ // Side note: There is a possible problem here, that the miter angle
+ // can get arbitrarily steep if two segments form a very sharp angle.
+ // This may need to be addressed at some point.
+
+ const Point start = firstVertex();
+ const Point end = secondVertex();
+ const Vector segment = end - start;
+
+ const Point a = start + t0 * segment;
+ const Point b = start + t1 * segment;
+
+ // cosine of steepest miter angle
+ const Float cos0 = dot(firstMiterNormal(), tangent());
+ const Float cos1 = dot(secondMiterNormal(), tangent());
+ const Float maxInvCos = 1.0 / std::min(cos0, cos1);
+
+ const Float expandRadius = m_hair->radius() * maxInvCos;
+ const Vector expandVec(expandRadius, expandRadius, expandRadius);
+
+ result.expandBy(a - expandVec);
+ result.expandBy(a + expandVec);
+ result.expandBy(b - expandVec);
+ result.expandBy(b + expandVec);
+
+ return result;
+}
+
+AABB MiterHairSegment::getClippedAABB(const AABB &aabb) const {
+ AABB result(m_aabb);
+ result.clip(aabb);
+ return result;
+ /* The following is broken, I believe...
+ Float nearT, farT;
+ AABB result(m_aabb);
+ result.clip(aabb);
+
+ Point a = firstVertex();
+ Point b = secondVertex();
+
+ if (!result.rayIntersect(Ray(a, normalize(b-a)), nearT, farT))
+ return result; // that could be improved
+
+ nearT = std::max(nearT, (Float) 0);
+ farT = std::min(farT, m_length);
+ result = getWorldAABB(nearT, farT);
+ result.clip(aabb);
+
+ return result;*/
+}
+
+bool MiterHairSegment::rayIntersect(const Ray &ray, Float start, Float end, Float &t) const {
+ Float nearT, farT;
+
+ /* First compute the intersection with the infinite cylinder */
+
+ // Projection of ray onto subspace normal to axis
+ Vector axis = tangent();
+ Vector relOrigin = ray.o - firstVertex();
+ Vector projOrigin = relOrigin - dot(axis, relOrigin) * axis;
+ Vector projDirection = ray.d - dot(axis, ray.d) * axis;
+
+ // Quadratic to intersect circle in projection
+ const Float A = projDirection.lengthSquared();
+ const Float B = 2 * dot(projOrigin, projDirection);
+ const Float radius = m_hair->radius();
+ const Float C = projOrigin.lengthSquared() - radius*radius;
+
+ if (!solveQuadratic(A, B, C, nearT, farT))
+ return false;
+
+ if (nearT > end || farT < start)
+ return false;
+
+ /* Next check the intersection points against the miter planes */
+
+ Point pointNear = ray(nearT);
+ Point pointFar = ray(farT);
+ if (dot(pointNear - firstVertex(), firstMiterNormal()) >= 0 &&
+ dot(pointNear - secondVertex(), secondMiterNormal()) <= 0 &&
+ nearT >= start) {
+ t = nearT;
+ } else if (dot(pointFar - firstVertex(), firstMiterNormal()) >= 0 &&
+ dot(pointFar - secondVertex(), secondMiterNormal()) <= 0) {
+ if (farT > end)
+ return false;
+ t = farT;
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool MiterHairSegment::rayIntersect(const Ray &ray, Intersection &its) const {
+ if (!rayIntersect(ray, ray.mint, ray.maxt, its.t))
+ return false;
+ its.p = ray(its.t);
+
+
+ /* For now I don't compute texture coordinates at all. */
+ its.uv = Point2(0,0);
+ its.dpdu = Vector(0,0,0);
+ its.dpdv = Vector(0,0,0);
+
+ its.geoFrame.s = tangent();
+ const Vector relHitPoint = its.p - firstVertex();
+ const Vector axis = tangent();
+ its.geoFrame.n = Normal(relHitPoint - dot(axis, relHitPoint) * axis);
+ its.geoFrame.t = cross(its.geoFrame.n, its.geoFrame.s);
+ its.shFrame = its.geoFrame;
+ its.wi = its.toLocal(-ray.d);
+ its.hasUVPartials = false;
+ its.shape = this;
+
+ /* Intersection refinement step */
+ // Do I need this?
+ /*
+ Vector2 localDir(normalize(Vector2(local.x, local.y)));
+ Vector rel = its.p - m_objectToWorld(Point(m_radius * localDir.x,
+ m_radius * localDir.y, local.z));
+ Float correction = -dot(rel, its.geoFrame.n)/dot(ray.d, its.geoFrame.n);
+
+ its.t += correction;
+ if (its.t < ray.mint || its.t > ray.maxt) {
+ its.t = std::numeric_limits::infinity();
+ return false;
+ }
+
+ its.p += ray.d * correction;
+ */
+
+ return true;
+}
+
+#if defined(MTS_SSE)
+/* SSE-accelerated packet tracing is not supported for cylinders at the moment */
+__m128 MiterHairSegment::rayIntersectPacket(const RayPacket4 &packet, const
+ __m128 start, __m128 end, __m128 inactive, Intersection4 &its) const {
+ SSEVector result(_mm_setzero_ps()), mint(start), maxt(end), mask(inactive);
+ Float t;
+
+ for (int i=0; i<4; i++) {
+ Ray ray;
+ for (int axis=0; axis<3; axis++) {
+ ray.o[axis] = packet.o[axis].f[i];
+ ray.d[axis] = packet.d[axis].f[i];
+ }
+ if (mask.i[i] != 0)
+ continue;
+ if (rayIntersect(ray, mint.f[i], maxt.f[i], t)) {
+ result.i[i] = 0xFFFFFFFF;
+ its.t.f[i] = t;
+ }
+ }
+ return result.ps;
+}
+#endif
+
+Float MiterHairSegment::sampleArea(ShapeSamplingRecord &sRec, const Point2 &sample) const {
+ /* Luminaire sampling not supported */
+ Log(EError, "Area sampling not supported by MiterHairSegment");
+ return 0;
+ /*
+ Point p = Point(m_radius * std::cos(sample.y),
+ m_radius * std::sin(sample.y),
+ sample.x * m_length);
+ sRec.p = m_objectToWorld(p);
+ sRec.n = normalize(m_objectToWorld(Normal(p.x, p.y, 0.0f)));
+ return m_invSurfaceArea;
+ */
+}
+
+std::string MiterHairSegment::toString() const {
+ std::ostringstream oss;
+ oss << "MiterHairSegment [" << endl
+ << " index = " << m_iv << endl
+ << "]";
+ return oss.str();
+}
+
+
+MTS_IMPLEMENT_CLASS_S(MiterHairSegment, false, Shape)
+MTS_NAMESPACE_END
diff --git a/src/shapes/miterseg.h b/src/shapes/miterseg.h
new file mode 100644
index 00000000..1f61307f
--- /dev/null
+++ b/src/shapes/miterseg.h
@@ -0,0 +1,87 @@
+#ifndef MITERSEG_H_
+#define MITERSEG_H_
+
+#include
+#include
+
+#include "hair.h"
+
+MTS_NAMESPACE_BEGIN
+
+class MiterHairSegment : public Shape {
+private:
+ ref m_hair; // the hair array to which this segment belongs
+ int m_iv; // the index of this hair segment within the parent hair array
+
+public:
+
+ MiterHairSegment(ref parent, int index);
+
+ MiterHairSegment(const Properties &props) : Shape(props) {
+ Log(EError, "Miter Hair Segments cannot be created directly; they are created by Hair instances.");
+ }
+
+ MiterHairSegment(Stream *stream, InstanceManager *manager)
+ : Shape(stream, manager) {
+ AssertEx(false, "Hair Segments do not support serialization.");
+ }
+
+ bool isClippable() const {
+ return true;
+ }
+
+ void configure();
+
+ AABB getWorldAABB(Float start, Float end) const;
+
+ AABB getClippedAABB(const AABB &aabb) const;
+
+ bool rayIntersect(const Ray &_ray, Float start, Float end, Float &t) const;
+
+ bool rayIntersect(const Ray &ray, Intersection &its) const;
+
+#if defined(MTS_SSE)
+ __m128 rayIntersectPacket(const RayPacket4 &, const __m128, __m128, __m128, Intersection4 &) const;
+#endif
+
+ Float sampleArea(ShapeSamplingRecord &sRec, const Point2 &sample) const;
+
+ void serialize(Stream *stream, InstanceManager *manager) const {
+ AssertEx(false, "Hair Segments do not support serialization.");
+ }
+
+ std::string toString() const;
+
+ inline Point firstVertex() const { return m_hair->vertex(m_iv); }
+ inline Point secondVertex() const { return m_hair->vertex(m_iv+1); }
+ inline Vector tangent() const { return normalize(secondVertex() - firstVertex()); }
+
+ inline bool prevSegmentExists() const { return !m_hair->vertexStartsFiber(m_iv); }
+ inline Point prevVertex() const { return m_hair->vertex(m_iv-1); }
+ inline Vector prevTangent() const { return normalize(firstVertex() - prevVertex()); }
+
+ inline bool nextSegmentExists() const { return !m_hair->vertexStartsFiber(m_iv+2); }
+ inline Point nextVertex() const { return m_hair->vertex(m_iv+2); }
+ inline Vector nextTangent() const { return normalize(nextVertex() - secondVertex()); }
+
+ inline Vector firstMiterNormal() const {
+ if (prevSegmentExists())
+ return normalize(prevTangent() + tangent());
+ else
+ return tangent();
+ }
+
+inline Vector secondMiterNormal() const {
+ if (nextSegmentExists())
+ return normalize(tangent() + nextTangent());
+ else
+ return tangent();
+ }
+
+ MTS_DECLARE_CLASS()
+};
+
+MTS_NAMESPACE_END
+
+
+#endif /* MITERSEG_H_ */