merged with Steve's miter hair changes

metadata
Wenzel Jakob 2010-10-16 16:06:23 +02:00
commit 1be325172e
5 changed files with 439 additions and 123 deletions

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <fstream>
#include <mitsuba/render/shape.h>
#include <mitsuba/render/bsdf.h>
#include <mitsuba/core/plugin.h>
#include <mitsuba/core/fresolver.h>
#include <mitsuba/core/properties.h>
#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<HairSegment> 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; i<segmentCount; ++i)
m_segments.push_back(HairSegment(Point(stream), Point(stream)));
}
Log(EDebug, "Read %i hair vertices, %i segments,", m_vertices.size(), m_segIndex.size());
}
void serialize(Stream *stream, InstanceManager *manager) const {
Shape::serialize(stream, manager);
Hair::Hair(Stream *stream, InstanceManager *manager) : Shape(stream, manager) {
m_radius = stream->readFloat();
size_t segmentCount = stream->readUInt();
stream->writeFloat(m_radius);
stream->writeUInt(m_segments.size());
for (size_t i=0; i<m_segments.size(); ++i) {
m_segments[i].start.serialize(stream);
m_segments[i].end.serialize(stream);
}
}
m_vertices.reserve(segmentCount);
for (size_t i=0; i<segmentCount; ++i)
m_vertices.push_back(Point(stream));
bool isCompound() const {
return true;
}
m_startFiber.reserve(segmentCount+1);
for (size_t i=0; i<segmentCount+1; ++i)
m_startFiber.push_back(stream->readBool());
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<segmentCount; ++i)
m_vertices[i].serialize(stream);
if ((_index % 2) == 0) {
Properties sphereProperties("sphere");
sphereProperties.setFloat("radius", m_radius);
sphereProperties.setTransform("toWorld", trafo);
Shape *sphere = static_cast<Shape *> (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<Shape *> (PluginManager::getInstance()->
createObject(Shape::m_theClass, cylinderProperties));
cylinder->addChild("bsdf", m_bsdf);
cylinder->configure();
for (size_t i=0; i<segmentCount; ++i)
stream->writeBool(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<m_vertices.size(); i++)
if (!m_startFiber[i+1])
m_segIndex.push_back(i);
}
return cylinder;
}
}
MTS_DECLARE_CLASS()
};
MTS_IMPLEMENT_CLASS_S(Hair, false, Shape)
MTS_EXPORT_PLUGIN(Hair, "Hair geometry");

56
src/shapes/hair.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef HAIR_H_
#define HAIR_H_
#include <mitsuba/render/shape.h>
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<Point> m_vertices;
// An indication of which vertices start a new fiber: [0...nVertices+1)
std::vector<bool> m_startFiber;
// A mapping of segment indices to vertex indices (needed only for construction): [0...nSegments)
std::vector<int> 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_ */

223
src/shapes/miterseg.cpp Normal file
View File

@ -0,0 +1,223 @@
#include <mitsuba/render/shape.h>
#include "miterseg.h"
MTS_NAMESPACE_BEGIN
MiterHairSegment::MiterHairSegment(ref<Hair> 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<Float>::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

87
src/shapes/miterseg.h Normal file
View File

@ -0,0 +1,87 @@
#ifndef MITERSEG_H_
#define MITERSEG_H_
#include <mitsuba/render/shape.h>
#include <mitsuba/core/ref.h>
#include "hair.h"
MTS_NAMESPACE_BEGIN
class MiterHairSegment : public Shape {
private:
ref<Hair> 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<Hair> 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_ */