What I have to do is create a square that is made up of 8 triangles, all the same size, using arrays. The coordinates of the four corners of the square are, (-10, -10, 10), (-10, -10, -10), (10, -10, -10), (10, -10, 10). And that's starting with the upper left and going counter clockwise.
I have already created it before just entering values into the array but now I have to figure out how to do it using for loops in C++. So I know that for each array (I need to create a vertex, index and color array) I need to create a for loop and that that for loop has to have a for loop inside of it.
I like to use Eigen::Vector2f for Vec but anything with a similar interface should work:
template< typename Vec >
void glVec2d( const Vec& vec )
{
glVertex2d( vec.x(), vec.y() );
}
template< typename Vec >
void glTex2d( const Vec& vec )
{
glTexCoord2d( vec.x(), vec.y() );
}
template< typename Vec >
void glQuad2d
(
const Vec& A, // lower left coord
const Vec& B, // lower right coord
const Vec& C, // upper right coord
const Vec& D, // upper left coord
unsigned int divs = 2,
const Vec& At = Vec(0,0),
const Vec& Bt = Vec(1,0),
const Vec& Ct = Vec(1,1),
const Vec& Dt = Vec(0,1)
)
{
// base case
if( divs == 0 )
{
glTex2d( At );
glVec2d( A );
glTex2d( Bt );
glVec2d( B );
glTex2d( Ct );
glVec2d( C );
glTex2d( Dt );
glVec2d( D );
return;
}
Vec AB = (A+B) * 0.5;
Vec BC = (B+C) * 0.5;
Vec CD = (C+D) * 0.5;
Vec AD = (A+D) * 0.5;
Vec ABCD = (AB+CD) * 0.5;
Vec ABt = (At+Bt) * 0.5;
Vec BCt = (Bt+Ct) * 0.5;
Vec CDt = (Ct+Dt) * 0.5;
Vec ADt = (At+Dt) * 0.5;
Vec ABCDt = (ABt+CDt) * 0.5;
// subdivided point layout
// D CD C
//
// AD ABCD BC
//
// A AB B
// subdivide
glQuad2d( A, AB, ABCD, AD, divs - 1, At, ABt, ABCDt, ADt );
glQuad2d( AB, B, BC, ABCD, divs - 1, ABt, Bt, BCt, ABCDt );
glQuad2d( ABCD, BC, C, CD, divs - 1, ABCDt, BCt, Ct, CDt );
glQuad2d( AD, ABCD, CD, D, divs - 1, ADt, ABCDt, CDt, Dt );
}
It's currently recursive but you could always add an explicit stack for some for-loop action.
Related
I am using Ceres Solver to perform non-linear curve fits on small data sets. Following the examples I am able to generate perfectly reasonable fit parameters for models that match my data well. I am also trying to compute the parameter variances and this is where things are falling apart. The code executes but results seem incorrect, often many orders of magnitude larger than the the parameter itself. The number of points (x, y) in the data sets I am fitting is similar to the number of parameter in the fit models, e.g. 4 data points, 3 parameters.
I came across a similar SO question here: Ceres: Compute uncertainty on parameter, which was helpful in linking the Ceres wiki on using the covariance class, but the issue was not marked as resolved. I, like the previous poster, looked at the parameter variances produced using the Python lmfit (https://lmfit.github.io/lmfit-py/index.html) package and found that it provides much more reasonable results.
The Ceres description of the covariance class (http://ceres-solver.org/nnls_covariance.html#example-usage) described a potential issue where if the residuals of the cost functor are not scaled correctly, i.e. by the positive semi-definite covariance matrix of the observed data, then the parameter covariance matrix computation can't be trusted. Not being a mathematician, I am not certain how to satisfy this requirement.
Below is some sample code showing the cost function that I've implemented as well as the usage of the covariance class. Any advice would be greatly appreciated.
Cost function:
struct BiExponential1 {
BiExponential1( double x, double y, double s ) : x_( x ), y_( y ), s_( s ) {}
template <typename T>
bool operator()( const T* const a, const T* const b, const T* const c, T* residual ) const {
residual[0] = y_ - a[0] * ( exp( -b[0] * x_ ) - exp( -c[0] * x_ ) ); // observed - estimated = y - ( a' [exp( -b' * x ) - exp(-c' * x)] )
return true;
}
private:
const double x_;
const double y_;
};
Solver/Covariance usage:
double a = init_value_a;
double b = init_value_b;
double c = init_value_c;
double data_x[nData] = {<dummy data>}
double data_y[nData] = {<dummy data>}
for ( int i = 0; i < nData; ++i ) {
problem.AddParameterBlock( &a, 1 );
problem.AddParameterBlock( &b, 1 );
problem.AddParameterBlock( &c, 1 );
problem.AddResidualBlock(
new ceres::AutoDiffCostFunction<BiExponential1, 1, 1, 1, 1>(
new BiExponential1( data_x[i], data_y[i] ) ),
nullptr,
&a,
&b,
&c );
}
// Run the solver and record the results.
ceres::Solve( solverOptions, &problem, &summary );
// Variance estimates
// Code adapted from: http://ceres-solver.org/nnls_covariance.html#example-usage
Covariance::Options covOptions;
// TESTED non-default algorithm type - no effect.
//covOptions.algorithm_type = ceres::CovarianceAlgorithmType::DENSE_SVD;
Covariance covariance( covOptions );
std::vector<std::pair<const double*, const double*> > covariance_blocks;
covariance_blocks.push_back( std::make_pair( &a, &a ) );
covariance_blocks.push_back( std::make_pair( &b, &b ) );
covariance_blocks.push_back( std::make_pair( &c, &c ) );
covariance_blocks.push_back( std::make_pair( &a, &b ) );
covariance_blocks.push_back( std::make_pair( &a, &c ) );
covariance_blocks.push_back( std::make_pair( &b, &c ) );
CHECK( covariance.Compute( covariance_blocks, &problem ) );
// Get the diagonal variance terms
double covariance_aa[1 * 1];
double covariance_bb[1 * 1];
double covariance_cc[1 * 1];
covariance.GetCovarianceBlock( &a, &a, covariance_aa );
covariance.GetCovarianceBlock( &b, &b, covariance_bb );
covariance.GetCovarianceBlock( &c, &c, covariance_cc );
After ray vs triangle intersection test in 8 wide simd, I'm left with updating t, u and v which I've done in scalar below (find lowest t and updating t,u,v if lower than previous t). Is there a way to do this in simd instead of scalar?
int update_tuv(__m256 t, __m256 u, __m256 v, float* t_out, float* u_out, float* v_out)
{
alignas(32) float ts[8];_mm256_store_ps(ts, t);
alignas(32) float us[8];_mm256_store_ps(us, u);
alignas(32) float vs[8];_mm256_store_ps(vs, v);
int min_index{0};
for (int i = 1; i < 8; ++i) {
if (ts[i] < ts[min_index]) {
min_index = i;
}
}
if (ts[min_index] >= *t_out) { return -1; }
*t_out = ts[min_index];
*u_out = us[min_index];
*v_out = vs[min_index];
return min_index;
}
I haven't found a solution that finds the horizontal min t and shuffles/permutes it's pairing u and v along the way other than permuting and min testing 8 times.
First find horizontal minimum of the t vector. This alone is enough to reject values with your first test.
Then find index of that first minimum element, extract and store that lane from u and v vectors.
// Horizontal minimum of the vector
inline float horizontalMinimum( __m256 v )
{
__m128 i = _mm256_extractf128_ps( v, 1 );
i = _mm_min_ps( i, _mm256_castps256_ps128( v ) );
i = _mm_min_ps( i, _mm_movehl_ps( i, i ) );
i = _mm_min_ss( i, _mm_movehdup_ps( i ) );
return _mm_cvtss_f32( i );
}
int update_tuv_avx2( __m256 t, __m256 u, __m256 v, float* t_out, float* u_out, float* v_out )
{
// Find the minimum t, reject if t_out is larger than that
float current = *t_out;
float ts = horizontalMinimum( t );
if( ts >= current )
return -1;
// Should compile into vbroadcastss
__m256 tMin = _mm256_set1_ps( ts );
*t_out = ts;
// Find the minimum index
uint32_t mask = (uint32_t)_mm256_movemask_ps( _mm256_cmp_ps( t, tMin, _CMP_EQ_OQ ) );
// If you don't yet have C++/20, use _tzcnt_u32 or _BitScanForward or __builtin_ctz intrinsics
int minIndex = std::countr_zero( mask );
// Prepare a permutation vector for the vpermps AVX2 instruction
// We don't care what's in the highest 7 integer lanes in that vector, only need the first lane
__m256i iv = _mm256_castsi128_si256( _mm_cvtsi32_si128( (int)minIndex ) );
// Permute u and v vector, moving that element to the first lane
u = _mm256_permutevar8x32_ps( u, iv );
v = _mm256_permutevar8x32_ps( v, iv );
// Update the outputs with the new numbers
*u_out = _mm256_cvtss_f32( u );
*v_out = _mm256_cvtss_f32( v );
return minIndex;
}
While relatively straightforward and probably faster than your current method with vector stores followed by scalar loads, the performance of the above function is only great when that if branch is well-predicted.
When that branch is unpredictable (statistically, results in random outcomes), a completely branchless implementation might be a better fit. Gonna be more complicated though, load old values with _mm_load_ss, conditionally update with _mm_blendv_ps, and store back with _mm_store_ss.
I'm trying to find the midpoint of a two 3D vectors (A and B). I believe it should be VectorA + VectorB/2 will give the midpoint. But how would I use create a fucntion that returns the midpoint like this V3D Midpoint(V3D A, V3D B); if I have a class like this:
class V3D
{
public:
V3D();
V3D(float x, float y, float z);
~V3D();
V3D operator+( const V3D& V ) const;
V3D operator(const V3D& V ) const;
V3D operator*( float Scale ) const;
V3D operator/( FLOAT Scale ) const;
float dot( const V3D& V ) const;
V3D cross( const V3D& V ) const;
V3D Normal() const; // this vector's unit vector
float Size() const; // magnitude of this vector
private:
float X, Y, Z;
};
Doesn't this work ?
V3D Midpoint(const V3D& A, const V3D& B)
{
return (A + B) / 2.0;
}
This is simply writing in code what you specified in the question.
In my project I need to position things around other things in a spherical way, so I figured I needed a spherical coordinate representation. I'm using Ogre3D graphic rendering engine which provide some maths constructs which at the moment I use everywhere in my project.
The source code of Ogre is available there, in the 1.9 branch as I'm using an old version of it. I checked yesterday that the maths types didn't change in a long time so my code is relying on the code you can see there, in particular Maths, Vector3 and Quaternion. Here I'm assuming that Ogre's code is correct, but you never know, which is why I'm pointing to it as my code is relying heavily on it. Also know that the referential used in Ogre is right handed, with -Z being the default orientation of any entity, +Y being up of the screen, +X being the right side of the screen, and +Z being the direction toward you, -Z being the direction toward the screen.
Now, here is the spherical coordinate code I have a problem with:
#ifndef HGUARD_NETRUSH_ZONEVIEW_SPHERICAL_HPP__
#define HGUARD_NETRUSH_ZONEVIEW_SPHERICAL_HPP__
#include <iosfwd>
#include <OgreMath.h>
#include <OgreVector3.h>
#include <OgreQuaternion.h>
#include "api.hpp"
namespace netrush {
namespace zoneview {
/** Spherical coordinates vector, used for spherical coordinates and transformations.
Some example values:
( radius = 1.0, theta = 0.0deg , phi = 0.0deg ) <=> Y unit vector in cartesian space
( radius = 1.0, theta = 90.0deg, phi = 0.0deg ) <=> Z unit vector in cartesian space
( radius = 1.0, theta = 90.0deg , phi = 90.0deg ) <=> X unit vector in cartesian space
*/
struct SphereVector
{
Ogre::Real radius; ///< Rho or Radius is the distance from the center of the sphere.
Ogre::Radian theta; ///< Theta is the angle around the x axis (latitude angle counterclockwise), values range from 0 to PI.
Ogre::Radian phi; ///< Phi is the angle around the y axis (longitude angle counterclockwise), values range from 0 to 2PI.
NETRUSH_ZONEVIEW_API static const SphereVector ZERO;
NETRUSH_ZONEVIEW_API static const SphereVector UNIT_X;
NETRUSH_ZONEVIEW_API static const SphereVector UNIT_Y;
NETRUSH_ZONEVIEW_API static const SphereVector UNIT_Z;
NETRUSH_ZONEVIEW_API static const SphereVector NEGATIVE_UNIT_X;
NETRUSH_ZONEVIEW_API static const SphereVector NEGATIVE_UNIT_Y;
NETRUSH_ZONEVIEW_API static const SphereVector NEGATIVE_UNIT_Z;
SphereVector() = default;
SphereVector( Ogre::Real radius, Ogre::Radian theta, Ogre::Radian phi )
: radius( std::move(radius) ), theta( std::move(theta) ), phi( std::move(phi) )
{}
explicit SphereVector( const Ogre::Vector3& cartesian_vec )
{
*this = from_cartesian( cartesian_vec );
}
void normalize()
{
using namespace Ogre;
while( phi > Degree(360.f) ) phi -= Degree(360.f);
while( theta > Degree(180.f) ) theta -= Degree(180.f);
while( phi < Radian(0) ) phi += Degree(360.f);
while( theta < Radian(0) ) theta += Degree(180.f);
}
SphereVector normalized() const
{
SphereVector svec{*this};
svec.normalize();
return svec;
}
/** #return a relative Cartesian vector coordinate from this relative spherical coordinate. */
Ogre::Vector3 to_cartesian() const
{
using namespace Ogre;
const auto svec = normalized();
Vector3 result;
result.x = radius * Math::Sin( svec.phi ) * Math::Sin( svec.theta );
result.z = radius * Math::Cos( svec.phi ) * Math::Sin( svec.theta );
result.y = radius * Math::Cos( svec.theta );
return result;
}
/** #return a relative spherical coordinate from a cartesian vector. */
static SphereVector from_cartesian( const Ogre::Vector3& cartesian )
{
using namespace Ogre;
SphereVector result = SphereVector::ZERO;
result.radius = cartesian.length();
if( result.radius == 0 )
return result;
result.phi = Math::ATan2( cartesian.x, cartesian.z );
result.theta = Math::ATan2( Math::Sqrt( Math::Sqr( cartesian.x ) + Math::Sqr( cartesian.z ) ), cartesian.y );
result.normalize();
return result;
}
friend SphereVector operator-( const SphereVector& value )
{
SphereVector result;
result.radius = -value.radius;
result.theta = -value.theta;
result.phi = -value.phi;
return result;
}
friend SphereVector operator+( const SphereVector& left, const SphereVector& right )
{
SphereVector result;
result.radius = left.radius + right.radius;
result.theta = left.theta + right.theta;
result.phi = left.phi + right.phi;
return result;
}
friend SphereVector operator-( const SphereVector& left, const SphereVector& right )
{
return left + (-right);
}
SphereVector& operator+=( const SphereVector& other )
{
*this = *this + other;
return *this;
}
SphereVector& operator-=( const SphereVector& other )
{
*this = *this - other;
return *this;
}
/// Rotation of the position around the relative center of the sphere.
friend SphereVector operator*( const SphereVector& sv, const Ogre::Quaternion& rotation )
{
const auto cartesian_vec = sv.to_cartesian();
const auto rotated_vec = rotation * cartesian_vec;
SphereVector result { rotated_vec };
result.normalize();
return result;
}
/// Rotation of the position around the relative center of the sphere.
friend SphereVector operator*( const Ogre::Quaternion& rotation, const SphereVector& sv ) { return sv * rotation; }
/// Rotation of the position around the relative center of the sphere.
SphereVector& operator*=( const Ogre::Quaternion& rotation )
{
*this = *this * rotation;
return *this;
}
friend bool operator==( const SphereVector& left, const SphereVector& right )
{
return Ogre::Math::RealEqual( left.radius, right.radius )
&& left.phi == right.phi
&& left.theta == right.theta
;
}
friend bool operator!=( const SphereVector& left, const SphereVector& right )
{
return !( left == right );
}
};
inline std::ostream& operator<<( std::ostream& out, const SphereVector& svec )
{
out << "{ radius = " << svec.radius
<< ", theta = " << svec.theta
<< ", phi = " << svec.phi
<< " }";
return out;
}
inline bool real_equals( const SphereVector& left, const SphereVector& right, Ogre::Real tolerance = 1e-03 )
{
using namespace Ogre;
return Math::RealEqual( left.radius, right.radius, tolerance )
&& Math::RealEqual( left.theta.valueAngleUnits(), right.theta.valueAngleUnits(), tolerance )
&& Math::RealEqual( left.phi.valueAngleUnits(), right.phi.valueAngleUnits(), tolerance )
;
}
}}
#endif
The constants are defined in the cpp:
#include "spherical.hpp"
namespace netrush {
namespace zoneview {
const SphereVector SphereVector::ZERO( 0.f, Ogre::Radian( 0.f ), Ogre::Radian( 0.f ) );
const SphereVector SphereVector::UNIT_X( Ogre::Vector3::UNIT_X );
const SphereVector SphereVector::UNIT_Y( Ogre::Vector3::UNIT_Y );
const SphereVector SphereVector::UNIT_Z( Ogre::Vector3::UNIT_Z );
const SphereVector SphereVector::NEGATIVE_UNIT_X( Ogre::Vector3::NEGATIVE_UNIT_X );
const SphereVector SphereVector::NEGATIVE_UNIT_Y( Ogre::Vector3::NEGATIVE_UNIT_Y );
const SphereVector SphereVector::NEGATIVE_UNIT_Z( Ogre::Vector3::NEGATIVE_UNIT_Z );
}}
The failing test (the lines marked ast FAILURE):
inline bool check_compare( SphereVector left, SphereVector right )
{
std::cout << "----"
<< "\nComparing "
<< "\n Left: " << left
<< "\n Right: " << right
<< std::endl;
return real_equals( left, right );
}
// ...
TEST( Test_SphereVector, axe_rotation_quaternion )
{
using namespace Ogre;
const auto init_svec = SphereVector::NEGATIVE_UNIT_Z;
static const auto ROTATION_TO_X = Vector3::NEGATIVE_UNIT_Z.getRotationTo( Vector3::UNIT_X );
static const auto ROTATION_TO_Y = Vector3::NEGATIVE_UNIT_Z.getRotationTo( Vector3::UNIT_Y );
static const auto ROTATION_TO_Z = Vector3::NEGATIVE_UNIT_Z.getRotationTo( Vector3::UNIT_Z );
static const auto ROTATION_TO_NEGATIVE_X = Vector3::NEGATIVE_UNIT_Z.getRotationTo( Vector3::NEGATIVE_UNIT_X );
static const auto ROTATION_TO_NEGATIVE_Y = Vector3::NEGATIVE_UNIT_Z.getRotationTo( Vector3::NEGATIVE_UNIT_Y );
static const auto ROTATION_TO_NEGATIVE_Z = Vector3::NEGATIVE_UNIT_Z.getRotationTo( Vector3::NEGATIVE_UNIT_Z );
static const auto ROTATION_360 = ROTATION_TO_Z * 2;
const auto svec_x = init_svec * ROTATION_TO_X;
const auto svec_y = init_svec * ROTATION_TO_Y;
const auto svec_z = init_svec * ROTATION_TO_Z;
const auto svec_nx = init_svec * ROTATION_TO_NEGATIVE_X;
const auto svec_ny = init_svec * ROTATION_TO_NEGATIVE_Y;
const auto svec_nz = init_svec * ROTATION_TO_NEGATIVE_Z;
const auto svec_360 = init_svec * ROTATION_360;
EXPECT_TRUE( check_compare( svec_x.to_cartesian() , Vector3::UNIT_X ) );
EXPECT_TRUE( check_compare( svec_y.to_cartesian() , Vector3::UNIT_Y ) );
EXPECT_TRUE( check_compare( svec_z.to_cartesian() , Vector3::UNIT_Z ) );
EXPECT_TRUE( check_compare( svec_nx.to_cartesian() , Vector3::NEGATIVE_UNIT_X ) );
EXPECT_TRUE( check_compare( svec_ny.to_cartesian() , Vector3::NEGATIVE_UNIT_Y ) );
EXPECT_TRUE( check_compare( svec_nz.to_cartesian() , Vector3::NEGATIVE_UNIT_Z ) );
EXPECT_TRUE( check_compare( svec_360.to_cartesian(), Vector3::NEGATIVE_UNIT_Z ) ); // FAILURE 1
EXPECT_TRUE( check_compare( svec_x , SphereVector::UNIT_X ) );
EXPECT_TRUE( check_compare( svec_y , SphereVector::UNIT_Y ) ); // FAILURE 2
EXPECT_TRUE( check_compare( svec_z , SphereVector::UNIT_Z ) );
EXPECT_TRUE( check_compare( svec_nx , SphereVector::NEGATIVE_UNIT_X ) );
EXPECT_TRUE( check_compare( svec_ny , SphereVector::NEGATIVE_UNIT_Y ) ); // FAILURE 3
EXPECT_TRUE( check_compare( svec_nz , SphereVector::NEGATIVE_UNIT_Z ) );
EXPECT_TRUE( check_compare( svec_360, SphereVector::NEGATIVE_UNIT_Z ) ); // FAILURE 4
}
Excerpt from the test report:
Failure 1:
4> ----
4> Comparing
4> Left: Vector3(9.61651e-007, -3.0598e-007, 7)
4> Right: Vector3(0, 0, -1)
4>e:\projects\games\netrush\netrush_projects\projects\netrush\zoneview\tests\spherevector.cpp(210): error : Value of: check_compare( svec_360.to_cartesian(), Vector3::NEGATIVE_UNIT_Z )
4> Actual: false
4> Expected: true
Failure 2:
4> ----
4> Comparing
4> Left: { radius = 1, theta = Radian(1.4783e-007), phi = Radian(5.65042) }
4> Right: { radius = 1, theta = Radian(0), phi = Radian(0) }
4>e:\projects\games\netrush\netrush_projects\projects\netrush\zoneview\tests\spherevector.cpp(213): error : Value of: check_compare( svec_y , SphereVector::UNIT_Y )
4> Actual: false
4> Expected: true
Failure 3:
4> ----
4> Comparing
4> Left: { radius = 1, theta = Radian(3.14159), phi = Radian(5.82845) }
4> Right: { radius = 1, theta = Radian(3.14159), phi = Radian(0) }
4>e:\projects\games\netrush\netrush_projects\projects\netrush\zoneview\tests\spherevector.cpp(216): error : Value of: check_compare( svec_ny , SphereVector::NEGATIVE_UNIT_Y )
4> Actual: false
4> Expected: true
Failure 4:
4> Comparing
4> Left: { radius = 7, theta = Radian(1.5708), phi = Radian(1.37379e-007) }
4> Right: { radius = 1, theta = Radian(1.5708), phi = Radian(3.14159) }
4>e:\projects\games\netrush\netrush_projects\projects\netrush\zoneview\tests\spherevector.cpp(218): error : Value of: check_compare( svec_360, SphereVector::NEGATIVE_UNIT_Z )
4> Actual: false
4> Expected: true
The same code on gist: https://gist.github.com/Klaim/8633895 with constants defined there: https://gist.github.com/Klaim/8634224
Full tests (using GTest): https://gist.github.com/Klaim/8633917
Full test report: https://gist.github.com/Klaim/8633937
(I can't put it here because of the text size limitation)
As you can see in the error report, there is 4 errors. I just can't find a solution for these, so maybe someone here could point me to what I'm doing wrong. I believe the problem could be from the test itself, but I'm not sure at all. Also, note that there are tests missing that I plan to add. The
The api.hpp include only expose the macros for shared library symbol export/import, used for constants.
This code is supposed to be extracted to be provided as a separate small open source library.
What I'm asking is: is this code incorrect? Or is my test incorrect?
I'm having this weird issue and I'm hoping someone could clear this up for me so I can understand what's wrong and act accordingly. In OpenGL (fixed-function) I'm rendering a tube with inner faces in an orthographic projection.
The image below shows the result. It consists of 4 rings of vertices which are forming triangles using the index pattern shown at the left. I numbered the vertices on the tube for your convenience. At the right is the texture being used:
As you can see the texture is being heavily distorted. As I initially created the tube with only 2 rings of vertices, I thought raising the amount of rings would fix the distortion but no joy. Also glHint doesn't seem to affect this specific problem.
The texture coordinates seem to be alright. Also, the checker pattern seems to render correctly, but I guess the distortion is just not visible in that very specific pattern.
Please ignore the crossed lines as one of those is a non-existent edge; I rendered the wireframe through GL_LINE_LOOP.
This particular effect is caused by the way texture coordinates are interpolated in a triangle. What happens is, that one direction becomes the major component, whereas the other is skewed. Your specimen happens to be very prone to this. This also happens to be a problem with perspective projections and textures on floors or walls. What you need is so called "perspective correct texturing" PCT. There's a glHint for this but I guess, you already tried that.
Frankly the only way to avoid this is by subdividing and applying the perspective correction as well. But fortunately this is easy enough for quadrilateral based geoemtry (like yours). When subdividing the edges interpolate the texture coordinates at the subdivision centers along all 4 edges and use the mean value of a 4 of them. Interpolating the texture coordinate only along one edge is exactly what you want to avoid.
If you want to keep your geometry data untouched you can implement PCT in the fragment shader.
Try some subdivision:
template< typename Vec >
void glVec2d( const Vec& vec )
{
glVertex2f( static_cast<float>( vec.x() ) , static_cast<float>( vec.y() ) );
}
template< typename Vec >
void glTex2d( const Vec& vec )
{
glTexCoord2f( static_cast<float>( vec.x() ) , static_cast<float>( vec.y() ) );
}
template< typename Vec >
void glQuad
(
const Vec& A,
const Vec& B,
const Vec& C,
const Vec& D,
unsigned int divs = 2,
const Vec& At = Vec(0,0),
const Vec& Bt = Vec(1,0),
const Vec& Ct = Vec(1,1),
const Vec& Dt = Vec(0,1)
)
{
// base case
if( divs == 0 )
{
glTex2d( At );
glVec2d( A );
glTex2d( Bt );
glVec2d( B );
glTex2d( Ct );
glVec2d( C );
glTex2d( Dt );
glVec2d( D );
return;
}
Vec AB = (A+B) * 0.5;
Vec BC = (B+C) * 0.5;
Vec CD = (C+D) * 0.5;
Vec AD = (A+D) * 0.5;
Vec ABCD = (AB+CD) * 0.5;
Vec ABt = (At+Bt) * 0.5;
Vec BCt = (Bt+Ct) * 0.5;
Vec CDt = (Ct+Dt) * 0.5;
Vec ADt = (At+Dt) * 0.5;
Vec ABCDt = (ABt+CDt) * 0.5;
// subdivided point layout
// D CD C
//
// AD ABCD BC
//
// A AB B
// subdivide
glQuad2d( A, AB, ABCD, AD, divs - 1, At, ABt, ABCDt, ADt );
glQuad2d( AB, B, BC, ABCD, divs - 1, ABt, Bt, BCt, ABCDt );
glQuad2d( ABCD, BC, C, CD, divs - 1, ABCDt, BCt, Ct, CDt );
glQuad2d( AD, ABCD, CD, D, divs - 1, ADt, ABCDt, CDt, Dt );
}
I usually use Eigen::Vector2f for Vec.
Why are you using an orthographic projection for this? If you were using a perspective projection, OpenGL would be correcting the texture mapping for you.