/******************************************************************/
/* This file is part of the homework assignments for CSCI-427/527 */
/* at The College of William & Mary and authored by Pieter Peers. */
/* No part of this file, whether altered or in original form, can */
/* be distributed or used outside the context of CSCI-427/527 */
/* without consent of either the College of William & Mary or */
/* Pieter Peers. */
/******************************************************************/
#include <cassert>
#include <algorithm>
#include "triangle.h"
#include "constants.h"
//////////////////
// Constructors //
//////////////////
triangle::triangle(void)
{
_vertex_list = nullptr;
_normal_list = nullptr;
_textureCoord_list = nullptr;
}
//////////////////////
// Copy Constructor //
//////////////////////
triangle::triangle(const triangle& t)
: triangle()
{
// share ptr to list
_vertex_list = t._vertex_list;
_normal_list = t._normal_list;
_textureCoord_list = t._textureCoord_list;
// copy indices
_vertex_idx = t._vertex_idx;
_normal_idx = t._normal_idx;
_textureCoord_idx = t._textureCoord_idx;
}
//////////////////////
// Move Constructor //
//////////////////////
triangle::triangle(triangle&& t)
: triangle()
{
// swap list (no need to increment ref-count)
std::swap(_vertex_list, t._vertex_list);
std::swap(_normal_list, t._normal_list);
std::swap(_textureCoord_list, t._textureCoord_list);
// copy indices
_vertex_idx = t._vertex_idx;
_normal_idx = t._normal_idx;
_textureCoord_idx = t._textureCoord_idx;
}
//////////////////////////
// General Constructors //
//////////////////////////
triangle::triangle(const vec3d& v1, const vec3d& v2, const vec3d& v3)
: triangle()
{
// create a list of three vertices
auto vertex_list = std::make_shared<std::vector<vec3d>>(3);
_vertex_list = vertex_list;
// copy values
(*vertex_list)[0] = v1;
(*vertex_list)[1] = v2;
(*vertex_list)[2] = v3;
// set indices
_vertex_idx = {0,1,2};
}
triangle::triangle(size_t v1_idx, size_t v2_idx, size_t v3_idx, const std::shared_ptr<const std::vector<vec3d>>& vertex_list)
: triangle()
{
// share vertex list
_vertex_list = vertex_list;
// copy indices
_vertex_idx[0] = v1_idx;
_vertex_idx[1] = v2_idx;
_vertex_idx[2] = v3_idx;
}
triangle::triangle(const vec3d& v1, const vec3d& v2, const vec3d& v3,
const vec3d& n1, const vec3d& n2, const vec3d& n3)
: triangle(v1, v2, v3)
{
// create a list of three normals
auto normal_list = std::make_shared<std::vector<vec3d>>(3);
_normal_list = normal_list;
// copy values
(*normal_list)[0] = n1;
(*normal_list)[1] = n2;
(*normal_list)[2] = n3;
// set indices
_normal_idx = {0,1,2};
}
triangle::triangle(size_t v1_idx, size_t v2_idx, size_t v3_idx, const std::shared_ptr<const std::vector<vec3d>>& vertex_list,
size_t n1_idx, size_t n2_idx, size_t n3_idx, const std::shared_ptr<const std::vector<vec3d>>& normal_list)
: triangle(v1_idx, v2_idx, v3_idx, vertex_list)
{
// share normal list
_normal_list = normal_list;
// copy indices
_normal_idx[0] = n1_idx;
_normal_idx[1] = n2_idx;
_normal_idx[2] = n3_idx;
}
triangle::triangle(const vec3d& v1, const vec3d& v2, const vec3d& v3,
const vec2d& t1, const vec2d& t2, const vec2d& t3)
: triangle(v1, v2, v3)
{
// create a list of three texture coordinates
auto textureCoord_list = std::make_shared<std::vector<vec2d>>(3);
_textureCoord_list = textureCoord_list;
// copy values
(*textureCoord_list)[0] = t1;
(*textureCoord_list)[1] = t2;
(*textureCoord_list)[2] = t3;
// set indices
_textureCoord_idx = {0,1,2};
}
triangle::triangle(size_t v1_idx, size_t v2_idx, size_t v3_idx, const std::shared_ptr<const std::vector<vec3d>>& vertex_list,
size_t t1_idx, size_t t2_idx, size_t t3_idx, const std::shared_ptr<const std::vector<vec2d>>& texcoord_list)
: triangle(v1_idx, v2_idx, v3_idx, vertex_list)
{
// share normal list
_textureCoord_list = texcoord_list;
// copy indices
_textureCoord_idx[0] = t1_idx;
_textureCoord_idx[1] = t2_idx;
_textureCoord_idx[2] = t3_idx;
}
triangle::triangle(const vec3d& v1, const vec3d& v2, const vec3d& v3,
const vec3d& n1, const vec3d& n2, const vec3d& n3,
const vec2d& t1, const vec2d& t2, const vec2d& t3)
: triangle(v1, v2, v3, n1, n2, n3)
{
// create a list of three texture coordinates
auto textureCoord_list = std::make_shared<std::vector<vec2d>>(3);
_textureCoord_list = textureCoord_list;
// copy values
(*textureCoord_list)[0] = t1;
(*textureCoord_list)[1] = t2;
(*textureCoord_list)[2] = t3;
// set indices
_textureCoord_idx = {0,1,2};
}
triangle::triangle(size_t v1_idx, size_t v2_idx, size_t v3_idx, const std::shared_ptr<const std::vector<vec3d>>& vertex_list,
size_t n1_idx, size_t n2_idx, size_t n3_idx, const std::shared_ptr<const std::vector<vec3d>>& normal_list,
size_t t1_idx, size_t t2_idx, size_t t3_idx, const std::shared_ptr<const std::vector<vec2d>>& texcoord_list)
: triangle(v1_idx, v2_idx, v3_idx, vertex_list,
n1_idx, n2_idx, n3_idx, normal_list)
{
// share normal list
_textureCoord_list = texcoord_list;
// copy indices
_textureCoord_idx[0] = t1_idx;
_textureCoord_idx[1] = t2_idx;
_textureCoord_idx[2] = t3_idx;
}
////////////////
// Inspectors //
////////////////
const vec3d& triangle::vertex(size_t index) const
{
// bounds check
assert(_vertex_list && index < 3);
// Done.
return (*_vertex_list)[_vertex_idx[index]];
}
const vec3d& triangle::normal(size_t index) const
{
// bounds check
assert(_normal_list && index < 3);
// Done.
return (*_normal_list)[_normal_idx[index]];
}
const vec2d& triangle::textureCoordinate(size_t index) const
{
// bounds check
assert(_textureCoord_list && index < 3);
// Done.
return (*_textureCoord_list)[_textureCoord_idx[index]];
}
bool triangle::hasPerVertexNormals(void) const
{
return (bool)(_normal_list);
}
bool triangle::hasPerVertexTextureCoordinates(void) const
{
return (bool)(_textureCoord_list);
}
///////////////
// Operators //
///////////////
triangle& triangle::operator=(const triangle& t)
{
_assign(t);
return *this;
}
triangle& triangle::operator=(triangle&& t)
{
// swap list (no need to increment ref-count)
std::swap(_vertex_list, t._vertex_list);
std::swap(_normal_list, t._normal_list);
std::swap(_textureCoord_list, t._textureCoord_list);
// copy indices
_vertex_idx = t._vertex_idx;
_normal_idx = t._normal_idx;
_textureCoord_idx = t._textureCoord_idx;
// Done.
return *this;
}
/////////////
// Methods //
/////////////
bool triangle::intersect(const ray& r, vec3d& barycentricCoord, float& t) const
{
// Ray-Triangle intersection [Moller and Trumbore 1997]
//
// [t ] 1 [ ((r.origin-v0) x (v1-v0)).(v2-v0) ]
// [barycenter.y] = -------------------------- [ (r.dir x (v2-v0)).(r.origin-v0) ]
// [barycenter.z] ((r.dir x (v2-vo)).(v1-v0) [ ((r.origin-v0) x (v1-v0)).r.dir ]
//
// compute denominator
vec3d e1 = vertex(1) - vertex(0);
vec3d e2 = vertex(2) - vertex(0);
vec3d p = r.direction().cross(e2);
float denom = p.dot(e1);
// check if parallel (denominator == 0)
if(fabs(denom) < EPSILON) return false;
denom = 1.0f / denom;
// compute barycentricCoord.y
vec3d diff = r.origin() - vertex(0);
barycentricCoord.y = denom * p.dot(diff);
// check if barycenter.y inside triangle
if(barycentricCoord.y < -EPSILON || barycentricCoord.y > 1.0f+EPSILON) return false;
// compute barycentricCoord.z
vec3d diffXe1 = diff.cross(e1);
barycentricCoord.z = denom * diffXe1.dot(r.direction());
// check if barycenter.z inside triangle
if(barycentricCoord.z < -EPSILON || barycentricCoord.z > 1.0f+EPSILON) return false;
// compute barycenter.x and check if inside
barycentricCoord.x = 1.0f - barycentricCoord.y - barycentricCoord.z;
if(barycentricCoord.y + barycentricCoord.z > 1.0f+EPSILON) return false;
// compute t
t = denom * diffXe1.dot(e2);
// check if in front (i.e., t>0)
if(t < EPSILON) return false;
// Clamp (to compensate for roundoff errors)
barycentricCoord.clamp(0.0f, 1.0f);
// Done.
return true;
}
boundingBox triangle::boundingbox(void) const
{
boundingBox bb;
for(unsigned int i=0; i < 3; i++)
bb += vertex(i);
return bb;
}
vec3d triangle::vertex(const vec3d& barycentricCoord) const
{
vec3d result(0.0f);
for(unsigned int i=0; i < 3; i++)
result += vertex(i) * barycentricCoord[i];
return result;
}
vec3d triangle::normal(void) const
{
vec3d e1 = vertex(1) - vertex(0);
vec3d e2 = vertex(2) - vertex(0);
return e1.cross(e2).normalize();
}
vec3d triangle::shadingAxis(void) const
{
// follow texture coordinates if defined, otherwise, return the first axis.
if(!_textureCoord_list) return normalize(vertex(1)-vertex(0));
// find the vector that follows the U-axis of the texture coordinates
// solve:
// [ (t1-t0) (t2-t0) ] [ delta_beta delta_gamma ]^T = [1 0]^T
// for delta_beta and delta_gamma.
vec3d v1 = vertex(1) - vertex(0);
vec3d v2 = vertex(2) - vertex(0);
vec2d t1 = textureCoordinate(1) - textureCoordinate(0);
vec2d t2 = textureCoordinate(2) - textureCoordinate(0);
// check special cases where U or V is aligned with a vertex
if( fabs(t1.v) < EPSILON ) return normalize(t1.u*v1);
if( fabs(t2.v) < EPSILON ) return normalize(t2.u*v2);
// compute delta_beta
float inv_delta_beta = t1.u - t1.v*t2.u/t2.v;
// => degenrate case
if(fabs(inv_delta_beta) < EPSILON) return normalize(v1);
float delta_beta = 1.0f / inv_delta_beta;
float delta_gamma = -delta_beta * t1.v / t2.v;
// compute U
return normalize( delta_beta*v1 + delta_gamma*v2 );
}
vec3d triangle::normal(const vec3d& barycentricCoord) const
{
// sanity check
if(!hasPerVertexNormals()) return normal();
// interpolate
vec3d result(0.0f);
for(unsigned int i=0; i < 3; i++)
result += normal(i) * barycentricCoord[i];
return result.normalize();
}
vec2d triangle::textureCoordinate(const vec3d& barycentricCoord) const
{
// sanity check
if(!hasPerVertexTextureCoordinates()) return vec2d();
// interpolate
vec2d result(0.0f);
for(unsigned int i=0; i < 3; i++)
result += textureCoordinate(i) * barycentricCoord[i];
return result;
}
float triangle::area(void) const
{
vec3d e1 = vertex(1) - vertex(0);
vec3d e2 = vertex(2) - vertex(0);
return 0.5f * e1.cross(e2).length();
}
vec3d triangle::sample(float r1, float r2, vec3d& barycentricCoord, float& pdf) const
{
r2 = sqrt(r2);
// compute barycentric coordinate
barycentricCoord.x = r1*r2;
barycentricCoord.y = (1.0f - r2);
barycentricCoord.z = 1.0f - barycentricCoord.x - barycentricCoord.y;
// compute pdf
pdf = 1.0f / area();
// Done.
return vertex(barycentricCoord);
}
/////////////////////
// Private Methods //
/////////////////////
void triangle::_assign(const triangle& t)
{
// avoid self-assign
if(&t == this) return;
// share ptr to list
_vertex_list = t._vertex_list;
_normal_list = t._normal_list;
_textureCoord_list = t._textureCoord_list;
// copy indices
_vertex_idx = t._vertex_idx;
_normal_idx = t._normal_idx;
_textureCoord_idx = t._textureCoord_idx;
}
void triangle::_swap(triangle& t)
{
// copy list ptr
std::swap(_vertex_list, t._vertex_list);
std::swap(_normal_list, t._normal_list);
std::swap(_textureCoord_list, t._textureCoord_list);
// copy indices
std::swap(_vertex_idx, t._vertex_idx);
std::swap(_normal_idx, t._normal_idx);
std::swap(_textureCoord_idx, t._textureCoord_idx);
}