From 8c0e6e3d1821c33dc859c04e5690785dc4919d07 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Wed, 1 Sep 2010 22:41:59 +0200 Subject: [PATCH] committed miter hair segment code from Steve --- SConstruct | 2 +- src/shapes/hair.cpp | 198 +++++++++++++++-------------------- src/shapes/hair.h | 56 ++++++++++ src/shapes/miterseg.cpp | 223 ++++++++++++++++++++++++++++++++++++++++ src/shapes/miterseg.h | 87 ++++++++++++++++ 5 files changed, 449 insertions(+), 117 deletions(-) create mode 100644 src/shapes/hair.h create mode 100644 src/shapes/miterseg.cpp create mode 100644 src/shapes/miterseg.h diff --git a/SConstruct b/SConstruct index 2e3386d6..c5687241 100644 --- a/SConstruct +++ b/SConstruct @@ -491,7 +491,7 @@ plugins += env.SharedLibrary('plugins/obj', ['src/shapes/obj.cpp']) 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 e26e5f63..d93ca276 100644 --- a/src/shapes/hair.cpp +++ b/src/shapes/hair.cpp @@ -1,132 +1,98 @@ +#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; +Hair::Hair(const Properties &props) : Shape(props) { + std::string filename = props.getString("filename"); + m_radius = (Float) props.getFloat("radius", 0.05f); + m_name = FileResolver::getInstance()->resolve(filename); - inline HairSegment(Point start, Point end) - : start(start), end(end) { - } - }; + Log(EInfo, "Loading hair geometry from \"%s\" ..", m_name.c_str()); - Float m_radius; - std::vector m_segments; -public: - Hair(const Properties &props) : Shape(props) { - std::string filename = props.getString("filename"); - m_radius = (Float) props.getFloat("radius", 0.05f); - m_name = FileResolver::getInstance()->resolve(filename); + std::ifstream is(m_name.c_str()); + if (is.fail()) + Log(EError, "Could not open \"%s\"!", m_name.c_str()); - Log(EInfo, "Loading hair geometry from \"%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, prev; - 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; - } - } - 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()); - } - - Hair(Stream *stream, InstanceManager *manager) : Shape(stream, manager) { - m_radius = stream->readFloat(); - - size_t segmentCount = stream->readUInt(); - m_segments.reserve(segmentCount); - for (size_t i=0; iwriteFloat(m_radius); - stream->writeUInt(m_segments.size()); - for (size_t i=0; i= m_segments.size()) - return NULL; - - Point start = m_segments[index].start; - Point end = m_segments[index].end; - Float length = (end-start).length(); - - Vector axis = normalize(end-start); - Vector rotAxis = normalize(cross(Vector(0,0,1), axis)); - Float rotAngle = radToDeg(std::acos(axis.z)); - - Transform trafo = - m_objectToWorld - * Transform::translate(start) - * Transform::rotate(rotAxis, rotAngle); - - if ((_index % 2) == 0) { - Properties sphereProperties("sphere"); - sphereProperties.setFloat("radius", m_radius); - sphereProperties.setTransform("toWorld", trafo); - Shape *sphere = static_cast (PluginManager::getInstance()-> - createObject(Shape::m_theClass, sphereProperties)); - sphere->addChild("bsdf", m_bsdf); - sphere->configure(); - return sphere; + 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 { - 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(); - - return cylinder; + std::istringstream iss(line); + iss >> p.x >> p.y >> p.z; + m_vertices.push_back(p); + m_startFiber.push_back(newFiber); + newFiber = false; } } + m_startFiber.push_back(true); + + buildSegIndex(); + + Log(EDebug, "Read %i hair vertices, %i segments,", m_vertices.size(), m_segIndex.size()); +} + +Hair::Hair(Stream *stream, InstanceManager *manager) : Shape(stream, manager) { + m_radius = stream->readFloat(); + size_t segmentCount = stream->readUInt(); + + m_vertices.reserve(segmentCount); + for (size_t i=0; ireadBool()); + + buildSegIndex(); +} + +void Hair::serialize(Stream *stream, InstanceManager *manager) const { + Shape::serialize(stream, manager); + + stream->writeFloat(m_radius); + size_t segmentCount = m_vertices.size(); + stream->writeUInt(segmentCount); + + 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_ */