/*************************************************************************** * SphTri.C * * * * This file defines the SphericalTriangle class definition, which * * supports member functions for Monte Carlo sampling, point containment, * * and other basic operations on spherical triangles. * * * * Changes: * * 01/01/2000 arvo Added New_{Alpha,Beta,Gamma} methods. * * 12/30/1999 arvo Added VecIrrad method for "Vector Irradiance". * * 04/08/1995 arvo Further optimized sampling algorithm. * * 10/11/1994 arvo Added analytic sampling algorithm. * * 06/14/1994 arvo Initial implementation. * * * *--------------------------------------------------------------------------* * Copyright (C) 1995, 2000, James Arvo * * * * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation. See http://www.fsf.org/copyleft/gpl.html * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT EXPRESS OR IMPLIED WARRANTY of merchantability or fitness for * * any particular purpose. See the GNU General Public License for more * * details. * * * ***************************************************************************/ #include #include #include "SphTri.h" #include "form.h" namespace ArvoMath { /*-------------------------------------------------------------------------* * Constructor * * * * Construct a spherical triangle from three (non-zero) vectors. The * * vectors needn't be of unit length. * * * *-------------------------------------------------------------------------*/ SphericalTriangle::SphericalTriangle( const Vec3 &A0, const Vec3 &B0, const Vec3 &C0 ) { Init( A0, B0, C0 ); } /*-------------------------------------------------------------------------* * Init * * * * Construct the spherical triange from three vertices. Assume that the * * sphere is centered at the origin. The vectors A, B, and C need not * * be normalized. * * * *-------------------------------------------------------------------------*/ void SphericalTriangle::Init( const Vec3 &A0, const Vec3 &B0, const Vec3 &C0 ) { // Normalize the three vectors -- these are the vertices. A_ = Unit( A0 ); B_ = Unit( B0 ); C_ = Unit( C0 ); // Compute and save the cosines of the edge lengths. cos_a = B_ * C_; cos_b = A_ * C_; cos_c = A_ * B_; // Compute and save the edge lengths. a_ = ArcCos( cos_a ); b_ = ArcCos( cos_b ); c_ = ArcCos( cos_c ); // Compute the cosines of the internal (i.e. dihedral) angles. cos_alpha = CosDihedralAngle( C_, A_, B_ ); cos_beta = CosDihedralAngle( A_, B_, C_ ); cos_gamma = CosDihedralAngle( A_, C_, B_ ); // Compute the (dihedral) angles. alpha = ArcCos( cos_alpha ); beta = ArcCos( cos_beta ); gamma = ArcCos( cos_gamma ); // Compute the solid angle of the spherical triangle. area = alpha + beta + gamma - Pi; // Compute the orientation of the triangle. orient = Sign( A_ * ( B_ ^ C_ ) ); // Initialize three variables that are used for sampling the triangle. U = Unit( C_ / A_ ); // In plane of AC orthogonal to A. sin_alpha = sin( alpha ); product = sin_alpha * cos_c; } /*-------------------------------------------------------------------------* * Init * * * * Initialize all fields. Create a null spherical triangle. * * * *-------------------------------------------------------------------------*/ void SphericalTriangle::Init() { a_ = 0; A_ = 0; cos_alpha = 0; cos_a = 0; alpha = 0; b_ = 0; B_ = 0; cos_beta = 0; cos_b = 0; beta = 0; c_ = 0; C_ = 0; cos_gamma = 0; cos_c = 0; gamma = 0; area = 0; orient = 0; sin_alpha = 0; product = 0; U = 0; } /*-------------------------------------------------------------------------* * "( A, B, C )" operator. * * * * Construct the spherical triange from three vertices. Assume that the * * sphere is centered at the origin. The vectors A, B, and C need not * * be normalized. * * * *-------------------------------------------------------------------------*/ SphericalTriangle & SphericalTriangle::operator()( const Vec3 &A0, const Vec3 &B0, const Vec3 &C0 ) { Init( A0, B0, C0 ); return *this; } /*-------------------------------------------------------------------------* * Inside * * * * Determine if the vector W is inside the triangle. W need not be a * * unit vector * * * *-------------------------------------------------------------------------*/ int SphericalTriangle::Inside( const Vec3 &W ) const { Vec3 Z = Orient() * W; if( Z * ( A() ^ B() ) < 0.0 ) return 0; if( Z * ( B() ^ C() ) < 0.0 ) return 0; if( Z * ( C() ^ A() ) < 0.0 ) return 0; return 1; } /*-------------------------------------------------------------------------* * Chart * * * * Generate samples from the current spherical triangle. If x1 and x2 are * * random variables uniformly distributed over [0,1], then the returned * * points are uniformly distributed over the solid angle. * * * *-------------------------------------------------------------------------*/ Vec3 SphericalTriangle::Chart( float x1, float x2 ) const { // Use one random variable to select the area of the sub-triangle. // Save the sine and cosine of the angle phi. register float phi = x1 * area - Alpha(); register float s = sin( phi ); register float t = cos( phi ); // Compute the pair (u,v) that determines the new angle beta. register float u = t - cos_alpha; register float v = s + product ; // sin_alpha * cos_c // Compute the cosine of the new edge b. float q = ( cos_alpha * ( v * t - u * s ) - v ) / ( sin_alpha * ( u * t + v * s ) ); // Compute the third vertex of the sub-triangle. Vec3 C_new = q * A() + Sqrt( 1.0 - q * q ) * U; // Use the other random variable to select the height z. float z = 1.0 - x2 * ( 1.0 - C_new * B() ); // Construct the corresponding point on the sphere. Vec3 D = C_new / B(); // Remove B component of C_new. return z * B() + Sqrt( ( 1.0 - z * z ) / ( D * D ) ) * D; } /*-------------------------------------------------------------------------* * Coord * * * * Compute the two coordinates (x1,x2) corresponding to a point in the * * spherical triangle. This is the inverse of "Chart". * * * *-------------------------------------------------------------------------*/ Vec2 SphericalTriangle::Coord( const Vec3 &P1 ) const { Vec3 P = Unit( P1 ); // Compute the new C vertex, which lies on the arc defined by B-P // and the arc defined by A-C. Vec3 C_new = Unit( ( B() ^ P ) ^ ( C() ^ A() ) ); // Adjust the sign of C_new. Make sure it's on the arc between A and C. if( C_new * ( A() + C() ) < 0.0 ) C_new = -C_new; // Compute x1, the area of the sub-triangle over the original area. float cos_beta = CosDihedralAngle( A(), B(), C_new ); float cos_gamma = CosDihedralAngle( A(), C_new , B() ); float sub_area = Alpha() + acos( cos_beta ) + acos( cos_gamma ) - Pi; float x1 = sub_area / SolidAngle(); // Now compute the second coordinate using the new C vertex. float z = P * B(); float x2 = ( 1.0 - z ) / ( 1.0 - C_new * B() ); if( x1 < 0.0 ) x1 = 0.0; if( x1 > 1.0 ) x1 = 1.0; if( x2 < 0.0 ) x2 = 0.0; if( x2 > 1.0 ) x2 = 1.0; return Vec2( x1, x2 ); } /*-------------------------------------------------------------------------* * Dual * * * * Construct the dual triangle of the current triangle, which is another * * spherical triangle. * * * *-------------------------------------------------------------------------*/ SphericalTriangle SphericalTriangle::Dual() const { Vec3 dual_A = B() ^ C(); if( dual_A * A() < 0.0 ) dual_A *= -1.0; Vec3 dual_B = A() ^ C(); if( dual_B * B() < 0.0 ) dual_B *= -1.0; Vec3 dual_C = A() ^ B(); if( dual_C * C() < 0.0 ) dual_C *= -1.0; return SphericalTriangle( dual_A, dual_B, dual_C ); } /*-------------------------------------------------------------------------* * VecIrrad * * * * Return the "vector irradiance" due to a light source of unit brightness * * whose spherical projection is this spherical triangle. The negative of * * this vector dotted with the surface normal gives the (scalar) * * irradiance at the origin. * * * *-------------------------------------------------------------------------*/ Vec3 SphericalTriangle::VecIrrad() const { Vec3 Phi = a() * Unit( B() ^ C() ) + b() * Unit( C() ^ A() ) + c() * Unit( A() ^ B() ) ; if( Orient() ) Phi *= -1.0; return Phi; } /*-------------------------------------------------------------------------* * New_Alpha * * * * Returns a new spherical triangle derived from the original one by * * moving the "C" vertex along the edge "BC" until the new "alpha" angle * * equals the given argument. * * * *-------------------------------------------------------------------------*/ SphericalTriangle SphericalTriangle::New_Alpha( float alpha ) const { Vec3 V1( A() ), V2( B() ), V3( C() ); Vec3 E1 = Unit( V2 ^ V1 ); Vec3 E2 = E1 ^ V1; Vec3 G = ( cos(alpha) * E1 ) + ( sin(alpha) * E2 ); Vec3 D = Unit( V3 / V2 ); Vec3 C2 = ((G * D) * V2) - ((G * V2) * D); if( Triple( V1, V2, C2 ) > 0.0 ) C2 *= -1.0; return SphericalTriangle( V1, V2, C2 ); } std::ostream &operator<<( std::ostream &out, const SphericalTriangle &T ) { out << "SphericalTriangle:\n" << " " << T.A() << "\n" << " " << T.B() << "\n" << " " << T.C() << std::endl; return out; } };