Spherical coordinate representation (using Ogre maths constructs) fails tests - c++

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?

Related

Logical const in a container in C++

Edited to include MWE (removing example-lite) and added details about compilation and Valgrind output.
I am using the mutable keyword to achieve the result of lazy evaluation and caching a result. This works fine for a single object, but doesn't seem to work as expected for a collection.
My case is more complex, but let's say I have a triangle class that can calculate the area of a triangle and cache the result. I use pointers in my case because the thing being lazily evaluated is a more complex class (it is actually another instance of the same class, but I'm trying to simplify this example).
I have another class that is essentially a collection of triangles. It has a way to calculate the total area of all the contained triangles.
Logically, tri::Area() is const -- and mesh::Area() is const. When implemented as above, Valgrind shows a memory leak (m_Area).
I believe since I am using a const_iterator, the call to tri::Area() is acting on a copy of the triangle. Area() is called on that copy, which does the new, calculates the area, and returns the result. At that point, the copy is lost and the memory is leaked.
In addition, I believe this means the area is not actually cached. The next time I call Area(), it leaks more memory and does the calculation again. Obviously, this is non-ideal.
One solution would be to make mesh::Area() non-const. This isn't great because it needs to be called from other const methods.
I think this might work (mark m_Triangles as mutable and use a regular iterator):
However, I don't love marking m_Triangles as mutable -- I'd prefer to keep the compiler's ability to protect the constiness of m_Triangles in other un-related methods. So, I'm tempted to use const_cast to localize the ugly to just the method that needs it. Something like this (mistakes likely):
Not sure how to implement with const_cast -- should I be casting m_Triangles or this? If I cast this, is m_Triangles visible (since it is private)?
Is there some other way that I'm missing?
The effect I want is to keep mesh::Area() marked const, but have calling it cause all the tris calculate and cache their m_Area. While we're at it -- no memory leaks and Valgrind is happy.
I've found plenty of examples of using mutable in an object -- but nothing about using that object in a collection from another object. Links to a blog post or tutorial article on this would be great.
Thanks for any help.
Update
From this MWE, it looks like I was wrong about the point of the leak.
The code below is Valgrind-clean if the call to SplitIndx() is removed.
In addition, I added a simple test to confirm that the cached value is getting stored and updated in the container-stored objects.
It now appears that the call m_Triangles[indx] = t1; is where the leak occurs. How should I plug this leak?
#include <cmath>
#include <map>
#include <cstdio>
class point
{
public:
point()
{
v[0] = v[1] = v[2] = 0.0;
}
point( double x, double y, double z )
{
v[0] = x; v[1] = y; v[2] = z;
}
double v[3];
friend point midpt( const point & p1, const point & p2 );
friend double dist( const point & p1, const point & p2 );
friend double area( const point & p1, const point & p2, const point & p3 );
};
point midpt( const point & p1, const point & p2 )
{
point pmid;
pmid.v[0] = 0.5 * ( p1.v[0] + p2.v[0] );
pmid.v[1] = 0.5 * ( p1.v[1] + p2.v[1] );
pmid.v[2] = 0.5 * ( p1.v[2] + p2.v[2] );
return pmid;
}
double dist( const point & p1, const point & p2 )
{
double dx = p2.v[0] - p1.v[0];
double dy = p2.v[1] - p1.v[1];
double dz = p2.v[2] - p1.v[2];
return sqrt( dx * dx + dy * dy + dz * dz );
}
double area( const point & p1, const point & p2, const point & p3 )
{
double a = dist( p1, p2 );
double b = dist( p1, p3 );
double c = dist( p2, p3 );
// Place in increasing order a, b, c.
if ( a < b )
{
std::swap( a, b );
}
if ( a < c )
{
std::swap( a, c );
}
if ( b < c )
{
std::swap( b, c );
}
if ( c-(a-b) < 0.0 )
{
// Not a real triangle.
return 0.0;
}
return 0.25 * sqrt( ( a + ( b + c ) ) * ( c - ( a - b ) ) * ( c + ( a - b ) ) * ( a + ( b - c ) ) );
}
class tri
{
public:
tri()
{
m_Area = NULL;
}
tri( const point & p1, const point & p2, const point & p3 )
{
m_P1 = p1; m_P2 = p2; m_P3 = p3;
m_Area = NULL;
}
~tri() {
delete m_Area;
}
tri( const tri & t )
{
m_P1 = t.m_P1;
m_P2 = t.m_P2;
m_P3 = t.m_P3;
if ( t.m_Area )
{
m_Area = new double( *(t.m_Area) );
}
else
{
m_Area = NULL;
}
}
tri & operator=( const tri & t )
{
if ( this != &t )
{
m_P1 = t.m_P1;
m_P2 = t.m_P2;
m_P3 = t.m_P3;
if ( t.m_Area )
{
m_Area = new double( *(t.m_Area) );
}
else
{
m_Area = NULL;
}
}
return *this;
}
bool KnowsArea() const
{
if ( !m_Area ) return false;
return true;
}
void SetPts( const point & p1, const point & p2, const point & p3 )
{
m_P1 = p1; m_P2 = p2; m_P3 = p3;
delete m_Area;
m_Area = NULL;
}
double Area() const
{
if ( !m_Area )
{
m_Area = new double;
*m_Area = area( m_P1, m_P2, m_P3 );
}
return *m_Area;
}
void Split( tri & t1, tri & t2 )
{
point p4 = midpt( m_P2, m_P3 );
t1.SetPts( m_P1, m_P2, p4 );
t2.SetPts( m_P1, p4, m_P3 );
}
private:
point m_P1;
point m_P2;
point m_P3;
mutable double * m_Area;
};
class mesh
{
public:
double Area() const
{
double area = 0;
std::map<int,tri>::const_iterator it;
for (it=m_Triangles.begin(); it!=m_Triangles.end(); ++it)
{
area += it->second.Area();
}
return area;
}
std::map<int, tri> m_Triangles;
int KnownArea() const
{
int count = 0;
std::map<int,tri>::const_iterator it;
for (it=m_Triangles.begin(); it!=m_Triangles.end(); ++it)
{
if ( it->second.KnowsArea() ) count++;
}
return count;
}
void SplitIndx( int indx )
{
tri t1, t2;
m_Triangles[indx].Split( t1, t2 );
m_Triangles[indx] = t1;
m_Triangles[m_Triangles.size()+1] = t2;
}
int NumTri() const
{
return m_Triangles.size();
}
};
int main( void )
{
point p1( 0, 0, 0 );
point p2( 1, 0, 0 );
point p3( 0, 1, 0 );
point p4( 1, 1, 0 );
point p5( 3, 4, 0 );
tri t1( p1, p2, p3 );
tri t2( p1, p2, p4 );
tri t3( p1, p3, p4 );
tri t4( p1, p3, p5 );
tri t5( p1, p4, p5 );
mesh m;
m.m_Triangles[1] = t1;
m.m_Triangles[2] = t2;
m.m_Triangles[3] = t3;
m.m_Triangles[4] = t4;
m.m_Triangles[5] = t5;
printf( "Known areas before total %d of %d\n", m.KnownArea(), m.NumTri() );
double area = m.Area();
printf( "Total area is %f\n", area );
printf( "Known areas after total %d of %d\n", m.KnownArea(), m.NumTri() );
printf( "Splitting\n" );
m.SplitIndx( 3 );
printf( "Known areas before total %d of %d\n", m.KnownArea(), m.NumTri() );
area = m.Area();
printf( "Total area is %f\n", area );
printf( "Known areas after total %d of %d\n", m.KnownArea(), m.NumTri() );
return 0;
}
Compiled with:
clang++ -Wall -std=c++11 -stdlib=libc++ mwe.cpp -o mwe
Or:
g++ -Wall -std=c++11 mwe.cpp -o mwe
Valgrind output (from clang):
$ valgrind --track-origins=yes --leak-check=full ./mwe
==231996== Memcheck, a memory error detector
==231996== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==231996== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==231996== Command: ./mwe
==231996==
Known areas before total 0 of 5
Total area is 3.500000
Known areas after total 5 of 5
Splitting
Known areas before total 4 of 6
Total area is 3.500000
Known areas after total 6 of 6
==231996==
==231996== HEAP SUMMARY:
==231996== in use at exit: 8 bytes in 1 blocks
==231996== total heap usage: 14 allocs, 13 frees, 1,800 bytes allocated
==231996==
==231996== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1
==231996== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==231996== by 0x48E3BA7: operator new(unsigned long) (in /usr/lib/llvm-10/lib/libc++.so.1.0)
==231996== by 0x4028A8: tri::Area() const (in /home/ramcdona/Desktop/mwe)
==231996== by 0x401E57: mesh::Area() const (in /home/ramcdona/Desktop/mwe)
==231996== by 0x4017A9: main (in /home/ramcdona/Desktop/mwe)
==231996==
==231996== LEAK SUMMARY:
==231996== definitely lost: 8 bytes in 1 blocks
==231996== indirectly lost: 0 bytes in 0 blocks
==231996== possibly lost: 0 bytes in 0 blocks
==231996== still reachable: 0 bytes in 0 blocks
==231996== suppressed: 0 bytes in 0 blocks
==231996==
==231996== For lists of detected and suppressed errors, rerun with: -s
==231996== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Built with gcc, the Valgrind output was essentially the same.
As pointed out by #Jarod42, the assignment operator as written is the source of the leak.
The cache and mutable are all working as expected.
The corrected code should read:
tri & operator=( const tri & t )
{
if ( this != &t )
{
m_P1 = t.m_P1;
m_P2 = t.m_P2;
m_P3 = t.m_P3;
delete m_Area;
if ( t.m_Area )
{
m_Area = new double( *(t.m_Area) );
}
else
{
m_Area = NULL;
}
}
return *this;
}
The approach suggested by #TedLyngmo would also work. In fact, it would avoid these sorts of problems entirely. However, I was looking to understand why the existing code did not work.
One way to avoid making it mutable is to make it always point at the the data cache, which could be a std::optional<double>.
You'd then create and store a std::unique_ptr<std::optional<double>> that you keep for the tri object's lifetime.
Example:
#include <memory> // std::unique_ptr / std::make_unique
#include <optional> // std::optional
class tri {
public:
using cache_type = std::optional<double>;
tri() : m_Area(std::make_unique<cache_type>()) {} // create the cache
tri(const tri& rhs) : // copy constructor
m_Area(std::make_unique<cache_type>(*rhs.m_Area)),
m_P1(rhs.m_P1), m_P2(rhs.m_P2), m_P3(rhs.m_P3)
{}
tri(tri&&) noexcept = default; // move constructor
tri& operator=(const tri& rhs) { // copy assignment
m_Area = std::make_unique<cache_type>(*rhs.m_Area);
m_P1 = rhs.m_P1;
m_P2 = rhs.m_P2;
m_P3 = rhs.m_P3;
return *this;
}
tri& operator=(tri&& rhs) noexcept = default; // move assignment
// no user-defined destructor needed
void SetPts(const point& p1, const point& p2, const point& p3) {
m_P1 = p1;
m_P2 = p2;
m_P3 = p3;
m_Area->reset(); // the cache is not up to date anymore
}
double Area() const {
if(!*m_Area) *m_Area = CalcArea(); // set the cached value
return m_Area->value(); // return the stored value
}
private:
std::unique_ptr<cache_type> m_Area; // mutable not needed
point m_P1;
point m_P2;
point m_P3;
double CalcArea() const {
// the calculation
}
};

How to convert mouse coordinate on screen to 3D coordinate

I'm creating a 3D application using GLUT in C++.
Now, I want to implement a method similar to this:
Vector3* MyClass::get3DObjectfromMouse(int mouseX, int mouseY);
How can I implement this method?
As it was commented by Andon M. Coleman, one way you can achieve this is by doing a ray/object intersection test, with unprojected screen coordinates. This technique is commonly known as picking.
A pseudo-C++ code for picking:
Assume we have a 3D object type/class:
class Object3D { ... };
A 3D picking function would return a list of all objects that are intersected by a line going from the given 2D point in the near plane to the same point in the far plane.
struct LineSegment
{
Vector3 start;
Vector3 end;
};
Object3D[] Pick(float x, float y)
{
LineSegment lineSeg;
Object3D[] intersectedObjs;
// Do both un-projections for z-near (0) and z-far (1).
// This produces a line segment going from z-near to far.
UnProject(x, y, /* z = */ 0.0, modelViewMatrix, projectionMatrix, viewport, lineSeg.start);
UnProject(x, y, /* z = */ 1.0, modelViewMatrix, projectionMatrix, viewport, lineSeg.end);
// Iterate all object in the scene or in the current view:
for (Object3D obj : scene)
{
if (TestLineIntersection(obj, lineSeg))
{
// This object is crossed by the picking line.
intersectedObjs.Add(obj);
}
}
// Optionally you might want sort them from distance
// to the camera/viewer before returning the intersections.
return intersectedObjs;
}
And the UnProject() function would look like this:
bool UnProject(float winX, float winY, float winZ,
const Matrix4 & modelView, const Matrix4 & projection,
const ScreenRect viewport, Vector3 & worldCoordinates)
{
// Compute (projection x modelView) ^ -1:
const Matrix4 m = inverse(projection * modelView);
// Need to invert Y since screen Y-origin point down,
// while 3D Y-origin points up (this is an OpenGL only requirement):
winY = viewport.Height() - winY;
// Transformation of normalized coordinates between -1 and 1:
Vector4 in;
in[0] = (winX - viewport.X()) / viewport.Width() * 2.0 - 1.0;
in[1] = (winY - viewport.Y()) / viewport.Height() * 2.0 - 1.0;
in[2] = 2.0 * winZ - 1.0;
in[3] = 1.0;
// To world coordinates:
Vector4 out(m * in);
if (out[3] == 0.0) // Avoid a division by zero
{
worldCoordinates = Vector3Zero;
return false;
}
out[3] = 1.0 / out[3];
worldCoordinates[0] = out[0] * out[3];
worldCoordinates[1] = out[1] * out[3];
worldCoordinates[2] = out[2] * out[3];
return true;
}
To clarify, TestLineIntersection() does a line vs AABB intersection test. The bounding box should be transformed to world-space, since it is usually expressed as a set of points in local model-space.
bool TestLineIntersection(const Object3D & obj, const LineSegment & lineSeg)
{
AABB aabb = obj.GetAABB();
aabb.TransformBy(obj.modelMatrix);
return aabb.LineIntersection(lineSeg.start, lineSeg.end);
}
// AABB.cpp:
bool AABB::LineIntersection(const Vector3 & start, const Vector3 & end) const
{
const Vector3 center = (mins + maxs) * 0.5;
const Vector3 extents = maxs - center;
const Vector3 lineDir = 0.5 * (end - start);
const Vector3 lineCenter = start + lineDir;
const Vector3 dir = lineCenter - center;
const float ld0 = Mathf::Abs(lineDir[0]);
if (Mathf::Abs(dir[0]) > (extents[0] + ld0))
{
return false;
}
const float ld1 = Mathf::Abs(lineDir[1]);
if (Mathf::Abs(dir[1]) > (extents[1] + ld1))
{
return false;
}
const float ld2 = Mathf::Abs(lineDir[2]);
if (Mathf::Abs(dir[2]) > (extents[2] + ld2))
{
return false;
}
const Vector3 vCross = cross(lineDir, dir);
if (Mathf::Abs(vCross[0]) > (extents[1] * ld2 + extents[2] * ld1))
{
return false;
}
if (Mathf::Abs(vCross[1]) > (extents[0] * ld2 + extents[2] * ld0))
{
return false;
}
if (Mathf::Abs(vCross[2]) > (extents[0] * ld1 + extents[1] * ld0))
{
return false;
}
return true;
}

Function is ambiguos in c++

I'm trying to use a library I found for triangulations, and I'm getting a strange error. When I compile everything, I get the following errors:
'_trytoadd' is ambiguous ' Candidates are: void _trytoadd(Se<SeDcdtVertex,SeDcdtEdge,SeDcdtFace>
*, Se<SeDcdtVertex,SeDcdtEdge,SeDcdtFace> *, int, const GsVec2 &, const GsVec2 &, const GsVec2
&) '
I'm getting this error for _cantpass, _try to add and _ptreeaddent functions, but they are all defined and called properly in this .cpp file. Any idea what's wrong? Here's the code for the class in question:
/*=======================================================================
Copyright 2010 Marcelo Kallmann. All Rights Reserved.
This software is distributed for noncommercial use only, without
any warranties, and provided that all copies contain the full copyright
notice licence.txt located at the base folder of the distribution.
=======================================================================*/
# include <math.h>
# include <stdlib.h>
# include "gs_geo2.h"
# include "se_lct.h"
# include "se_triangulator_internal.h"
# define GS_TRACE_ONLY_LINES
//# define GS_USE_TRACE1 // main search method
//# define GS_USE_TRACE2 // search expansion
//# define GS_USE_TRACE3
# include "gs_trace.h"
//================================================================================
//========================== search path tree ====================================
//================================================================================
// nen/nex are the entrance/exit edges of the node being expanded, which is already in the search tree
// en/ex are the entrance/exit edge of the current traversal being evaluated for expansion
// (p1,p2) are en coordinates
bool SeLct::_canpass ( SeDcdtSymEdge* nen, SeDcdtSymEdge* nex, SeDcdtSymEdge* en, SeDcdtSymEdge* ex,
const GsPnt2& p1, const GsPnt2& p2, const GsPnt2& p3, float r, float d2 )
{
// check if can traverse en/ex traversal:
if ( _man->is_constrained(ex->edg()) ) return false;
// test if next triangle being tested has been already visited:
if ( _mesh->marked(en->fac()) ) return false;
if ( en->nxt()==ex ) // bot
{
if ( _pre_clearance )
{ float cl = ex->edg()->cl(ex);
if ( cl<d2 ) return false;
}
else
{ if ( dist2(p2,p3)<d2 ) return false;
if ( !_sector_clear(ex->nxt(),d2,p2,p3,p1) ) return false;
}
if ( nex->fac()==_fi && nex->nxt()==nen ) // top->bot departure transition
{ if ( !_local_transition_free(ex,en,d2,_xi,_yi) ) return false;
}
}
else // top
{
if ( _pre_clearance )
{ float cl = en->edg()->cl(en);
if ( cl<d2 ) return false;
}
else
{ if ( dist2(p3,p1)<d2 ) return false;
if ( !_sector_clear(en->nxt(),d2,p1,p2,p3) ) return false;
}
if ( nex->fac()==_fi && nen->nxt()==nex ) // bot->top departure transition
{ if ( !_local_transition_free(ex,en,d2,_xi,_yi) ) return false;
}
}
return true;
}
// en is the entrance edge, ex the exit edge, (p1,p2) are en coordinates
void SeLct::_trytoadd ( SeDcdtSymEdge* en, SeDcdtSymEdge* ex, int mi, const GsPnt2& p1, const GsPnt2& p2, const GsPnt2& p3 )
{
// verify if it is passable:
PathNode& n = _ptree->nodes[mi];
if ( !_canpass ( (SeDcdtSymEdge*)n.en, (SeDcdtSymEdge*)n.ex, en, ex, p1, p2, p3, _ptree->radius, _ptree->diam2 ) ) return;
// ok it is passable, compute cost:
double x, y;
if ( en->nxt()==ex )
_getcostpoint ( &n, n.x, n.y, p2.x, p2.y, p3.x, p3.y, x, y, _ptree->radius ); // bot
else
_getcostpoint ( &n, n.x, n.y, p3.x, p3.y, p1.x, p1.y, x, y, _ptree->radius ); // top
// insert:
# define PTDIST(a,b,c,d) float(sqrt(gs_dist2(a,b,c,d)))
_ptree->add_child ( mi, en, ex, n.ncost+PTDIST(n.x,n.y,x,y), PTDIST(x,y,_xg,_yg), x,y ); // A* heuristic
# undef PTDIST
}
# define ExpansionNotFinished -1
# define ExpansionBlocked -2
int SeLct::_expand_lowest_cost_leaf ()
{
int min_i;
if ( _ptree->leafs.size()>_maxfronts ) _maxfronts=_ptree->leafs.size();
min_i = _ptree->lowest_cost_leaf ();
GS_TRACE2 ( "Expanding leaf: "<<min_i );
if ( min_i<0 ) return ExpansionBlocked; // no more leafs: path could not be found!
// attention: array references may be invalidated due array reallocation during insertion
SeDcdtSymEdge* s = (SeDcdtSymEdge*) _ptree->nodes[min_i].ex->sym();
SeDcdtSymEdge* sn = s->nxt();
SeDcdtSymEdge* sp = sn->nxt();
const GsPnt2& p1 = s->vtx()->p; // note: s is in the triangle to expand
const GsPnt2& p2 = sn->vtx()->p;
const GsPnt2& p3 = sp->vtx()->p;
float d2 = _ptree->diam2;
// test if next triangle contains goal point:
if ( gs_in_triangle(p1.x,p1.y,p2.x,p2.y,p3.x,p3.y,_xg,_yg) ) // reached goal triangle !
{ GS_TRACE1 ( "Goal triangle reached..." );
double r = _ptree->radius;
if ( !pt2circfree(this,s,_xg,_yg,r) ) // we do not know if the goal location is valid, so test it now
{ GS_TRACE1 ( "Goal location is invalid." );
return ExpansionBlocked;
}
GS_TRACE1 ( "Goal location valid." );
GS_TRACE1 ( "Analyzing arrival..." );
SeDcdtSymEdge* nen = (SeDcdtSymEdge*)_ptree->nodes[min_i].en;
SeDcdtSymEdge* nex = (SeDcdtSymEdge*)_ptree->nodes[min_i].ex;
_analyze_arrival ( s, 3, r, d2, nen, nex );
if ( _ent[3].type==EntBlocked )
{ if ( (sn->edg()->is_constrained()||dist2(p2,p3)<d2) &&
(sp->edg()->is_constrained()||dist2(p3,p1)<d2) )
{ GS_TRACE1 ( "Arrival blocked from all possible entries." );
return ExpansionBlocked;
}
else
{ GS_TRACE1 ( "Arrival is blocked, but search can continue..." );
// at this point the arrival is not valid but the search will continue, and
// note that the arrival triangle may still be used as passage so we let
// the expansion tests in _trytoadd() proceed.
}
}
else
{ GS_TRACE1 ( "Arrival tests passed." );
GS_TRACE1 ( "Arrival is valid " << (_ent[3].type==EntTrivial?"and trivial.":"but non trivial.") );
return min_i; // FOUND!
}
}
int nsize = _ptree->nodes.size();
_trytoadd ( s, sn, min_i, p1, p2, p3 ); // bot
_trytoadd ( s, sp, min_i, p1, p2, p3 ); // top
if ( _ptree->nodes.size()>nsize ) _mesh->mark ( s->fac() ); // only mark traversed faces
if (_searchcb) _searchcb(_sudata);
return ExpansionNotFinished; // continue the expansion
}
void SeLct::_ptreeaddent ( SeDcdtSymEdge* s, bool top, bool edge )
{
const GsPnt2& p1 = s->vtx()->p;
const GsPnt2& p2 = s->nvtx()->p;
double x, y;
if ( edge )
{ x=_xi; y=_yi; }
else
{ _getcostpoint ( 0, _xi, _yi, p1.x, p1.y, p2.x, p2.y, x, y, _ptree->radius ); }
// insert:
# define PTDIST(a,b,c,d) (float)sqrt(gs_dist2(a,b,c,d))
_ptree->add_child ( -1, top? s->nxt():s->nxn(), s, PTDIST(_xi,_yi,x,y), PTDIST(x,y,_xg,_yg), x, y );
# undef PTDIST
}
//================================================================================
//============================== search path =====================================
//================================================================================
/* - This is the A* algorithm that takes O(nf), f is the faces in the "expansion frontier". */
bool SeLct::_search_channel ( double x1, double y1, double x2, double y2, float radius, const SeFace* iniface )
{
GS_TRACE1 ( "Starting Search Path..." );
if ( !_ptree ) _ptree = new PathTree;
_clear_path(); // clear data from previous query and set _path_result to NoPath
_channel.size(0);
_xi=x1; _yi=y1; _xg=x2; _yg=y2;
if ( !iniface ) return false;
// Even if p1 is on an edge, locate_point will return in s a face that
// can be considered to contain p1 (p1 would be invalid if in a vertex)
SeBase *s;
LocateResult res=locate_point ( iniface, x1, y1, s );
if ( res==NotFound )
{ GS_TRACE1 ( "Could not locate first point!" );
_path_result=NoPath;
return false;
}
_fi = s->fac(); // save initial face
if ( !pt1circfree(this,s,x1,y1,radius) ) { _path_result=NoPath; return false; }
// Check if we are to solve trivial or local paths, testing if both points are in the same triangle:
if ( _man->in_triangle(s->vtx(),s->nxt()->vtx(),s->nxn()->vtx(),x2,y2) )
{ GS_TRACE1 ( "Both points are in the same triangle..." );
if ( radius==0 )
{ GS_TRACE1 ( "Trivial path returned." );
_path_result=TrivialPath; return true; // this is it
}
if ( !pt2circfree(this,s,x2,y2,radius) )
{ GS_TRACE1 ( "Goal point in same triangle invalid. No path returned." );
_path_result=NoPath; return false;
}
_path_result = _analyze_local_path ( s, radius );
if ( _path_result==TrivialPath )
{ GS_TRACE1 ( "Capsule free. Trivial path returned." );
return true; // path exists
}
else if ( _path_result==LocalPath )
{ GS_TRACE1 ( "Deformable capsule is passable. Local path returned." );
return true; // path exists
}
// at this point the result may be a GlobalPath or a NoPath
GS_TRACE1 ( "Deformable capsule is not passable." );
// we then let the normal entrance analysis and search to proceed.
// the entrance that blocked the capsule will also be blocked but
// there may be a global path to get there so we just do not
// mark the initial face as visited, allowing it to be found by the global search.
}
GS_TRACE1 ( "Searching for a global path..." );
GS_TRACE1 ( "Analyzing entrances..." );
_analyze_entrances ( s, _xi, _yi, radius );
GS_TRACE1 ( "Entrance 0: "<<(_ent[0].type==EntBlocked?"blocked":_ent[0].type==EntTrivial?"trivial":"not trivial") );
GS_TRACE1 ( "Entrance 1: "<<(_ent[1].type==EntBlocked?"blocked":_ent[1].type==EntTrivial?"trivial":"not trivial") );
GS_TRACE1 ( "Entrance 2: "<<(_ent[2].type==EntBlocked?"blocked":_ent[2].type==EntTrivial?"trivial":"not trivial") );
GS_TRACE1 ( "Initializing A* search..." );
_mesh->begin_marking ();
_ptree->init ( radius );
if ( _ent[0].type!=EntBlocked ) _ptreeaddent ( _ent[0].s, _ent[0].top, res==EdgeFound? true:false );
if ( _ent[1].type!=EntBlocked ) _ptreeaddent ( _ent[1].s, _ent[1].top, false );
if ( _ent[2].type!=EntBlocked ) _ptreeaddent ( _ent[2].s, _ent[2].top, false );
if (_searchcb) _searchcb(_sudata);
GS_TRACE1 ( "Expanding leafs..." );
int found = ExpansionNotFinished;
while ( found==ExpansionNotFinished )
found = _expand_lowest_cost_leaf();
_mesh->end_marking ();
if ( found==ExpansionBlocked )
{ GS_TRACE1 ( "Points are not connectable!" );
_path_result = NoPath;
return false;
}
_finalsearchnode = found;
int n = found; // the starting leaf
s = _ptree->nodes[n].ex->sym();
do { _channel.push() = _ptree->nodes[n].ex;
n = _ptree->nodes[n].parent;
} while ( n!=-1 );
_channel.revert();
GS_TRACE1 ( "Path crosses "<<_channel.size()<<" edges." );
_path_result = GlobalPath;
return true;
}
void SeLct::get_search_nodes ( GsArray<SeBase*>& e )
{
e.size ( 0 );
for ( int i=0; i<_ptree->nodes.size(); i++ )
{ e.push() = _ptree->nodes[i].en;
e.push() = _ptree->nodes[i].ex;
}
}
int SeLct::get_search_nodes () const
{
return _ptree? _ptree->nodes.size() : 0;
}
void SeLct::get_search_metric ( GsArray<GsPnt2>& pnts )
{
pnts.size ( 0 );
if ( !_ptree ) return;
for ( int i=0; i<_ptree->nodes.size(); i++ )
{
PathNode& n = _ptree->nodes[i];
if ( n.parent<0 )
{ pnts.push().set ( _xi, _yi ); }
else
{ PathNode& np = _ptree->nodes[n.parent];
pnts.push().set ( np.x, np.y );
}
pnts.push().set ( n.x, n.y );
}
}
void SeLct::get_search_front ( GsArray<SeBase*>& e )
{
e.size ( 0 );
for ( int i=0; i<_ptree->leafs.size(); i++ )
{ e.push() = _ptree->nodes[ _ptree->leafs.elem(i) ].ex;
}
}
//============================ End of File =================================

Creating arrays using for loops in OpenGL

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.

How to fix weird camera rotation while moving camera with sdl, opengl in c++

I have a camera object that I have put together from reading on the net that handles moving forward and backward, strafe left and right and even look around with the mouse. But when I move in any direction plus try to look around it jumps all over the place, but when I don't move and look around its fine.
I'm hoping someone can help me work out why I can move and look around at the same time?
main.h
#include "SDL/SDL.h"
#include "SDL/SDL_opengl.h"
#include <cmath>
#define CAMERASPEED 0.03f // The Camera Speed
struct tVector3 // Extended 3D Vector Struct
{
tVector3() {} // Struct Constructor
tVector3 (float new_x, float new_y, float new_z) // Init Constructor
{ x = new_x; y = new_y; z = new_z; }
// overload + operator
tVector3 operator+(tVector3 vVector) {return tVector3(vVector.x+x, vVector.y+y, vVector.z+z);}
// overload - operator
tVector3 operator-(tVector3 vVector) {return tVector3(x-vVector.x, y-vVector.y, z-vVector.z);}
// overload * operator
tVector3 operator*(float number) {return tVector3(x*number, y*number, z*number);}
// overload / operator
tVector3 operator/(float number) {return tVector3(x/number, y/number, z/number);}
float x, y, z; // 3D vector coordinates
};
class CCamera
{
public:
tVector3 mPos;
tVector3 mView;
tVector3 mUp;
void Strafe_Camera(float speed);
void Move_Camera(float speed);
void Rotate_View(float speed);
void Position_Camera(float pos_x, float pos_y,float pos_z,
float view_x, float view_y, float view_z,
float up_x, float up_y, float up_z);
};
void Draw_Grid();
camera.cpp
#include "main.h"
void CCamera::Position_Camera(float pos_x, float pos_y, float pos_z,
float view_x, float view_y, float view_z,
float up_x, float up_y, float up_z)
{
mPos = tVector3(pos_x, pos_y, pos_z);
mView = tVector3(view_x, view_y, view_z);
mUp = tVector3(up_x, up_y, up_z);
}
void CCamera::Move_Camera(float speed)
{
tVector3 vVector = mView - mPos;
mPos.x = mPos.x + vVector.x * speed;
mPos.z = mPos.z + vVector.z * speed;
mView.x = mView.x + vVector.x * speed;
mView.z = mView.z + vVector.z * speed;
}
void CCamera::Strafe_Camera(float speed)
{
tVector3 vVector = mView - mPos;
tVector3 vOrthoVector;
vOrthoVector.x = -vVector.z;
vOrthoVector.z = vVector.x;
mPos.x = mPos.x + vOrthoVector.x * speed;
mPos.z = mPos.z + vOrthoVector.z * speed;
mView.x = mView.x + vOrthoVector.x * speed;
mView.z = mView.z + vOrthoVector.z * speed;
}
void CCamera::Rotate_View(float speed)
{
tVector3 vVector = mView - mPos;
tVector3 vOrthoVector;
vOrthoVector.x = -vVector.z;
vOrthoVector.z = vVector.x;
mView.z = (float)(mPos.z + sin(speed)*vVector.x + cos(speed)*vVector.z);
mView.x = (float)(mPos.x + cos(speed)*vVector.x - sin(speed)*vVector.z);
}
and the mousemotion code
void processEvents()
{
int mid_x = screen_width >> 1;
int mid_y = screen_height >> 1;
int mpx = event.motion.x;
int mpy = event.motion.y;
float angle_y = 0.0f;
float angle_z = 0.0f;
while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_MOUSEMOTION:
if( (mpx == mid_x) && (mpy == mid_y) ) return;
// Get the direction from the mouse cursor, set a resonable maneuvering speed
angle_y = (float)( (mid_x - mpx) ) / 1000; //1000
angle_z = (float)( (mid_y - mpy) ) / 1000; //1000
// The higher the value is the faster the camera looks around.
objCamera.mView.y += angle_z * 2;
// limit the rotation around the x-axis
if((objCamera.mView.y - objCamera.mPos.y) > 8) objCamera.mView.y = objCamera.mPos.y + 8;
if((objCamera.mView.y - objCamera.mPos.y) <-8) objCamera.mView.y = objCamera.mPos.y - 8;
objCamera.Rotate_View(-angle_y);
SDL_WarpMouse(mid_x, mid_y);
break;
case SDL_KEYUP:
objKeyb.handleKeyboardEvent(event,true);
break;
case SDL_KEYDOWN:
objKeyb.handleKeyboardEvent(event,false);
break;
case SDL_QUIT:
quit = true;
break;
case SDL_VIDEORESIZE:
screen = SDL_SetVideoMode( event.resize.w, event.resize.h, screen_bpp, SDL_OPENGL | SDL_HWSURFACE | SDL_RESIZABLE | SDL_GL_DOUBLEBUFFER | SDL_HWPALETTE );
screen_width = event.resize.w;
screen_height = event.resize.h;
init_opengl();
std::cout << "Resized to width: " << event.resize.w << " height: " << event.resize.h << std::endl;
break;
default:
break;
}
}
}
I'm not entirely sure what you are doing above.
Personally I would just allow a simple 4x4 matrix. Any implementation will do. To rotate you, simply, need to rotate using the change of mouse x and y as euler inputs for rotation around the y and x axes. There is lots of code available all over the internet that will do this for you.
Some of those matrix libraries won't provide you with a "MoveForward()" function. If this is the case its ok, moving forward is pretty easy. The third column (or row if you are using row major matrices) is your forward vector. Extract it. Normalise it (It really should be normalised anyway so this step may not be needed). Multiply it by how much you wish to move forward and then add it to the position (the 4th column/row).
Now here is the odd part. A view matrix is a special type of matrix. The matrix above defines the view space. If you multiply your current model matrix by this matrix you will not get the answer you expect. Because you wish to transform it such that the camera is at the origin. As such you need to, effectively, undo the camera transformation to re-orient things to the view defined above. To do this you multiply your model matrix by the inverse of the view matrix.
You now have an object defined in the correct view space.
This is my very simple camera class. It does not handle the functionality you describe but hopefully will give you a few ideas on how to set up the class (Be warned, I use row major, ie DirectX style, matrices).
BaseCamera.h:
#ifndef BASE_CAMERA_H_
#define BASE_CAMERA_H_
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
#include "Maths/Vector4.h"
#include "Maths/Matrix4x4.h"
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
class BaseCamera
{
protected:
bool mDirty;
MathsLib::Matrix4x4 mCameraMat;
MathsLib::Matrix4x4 mViewMat;
public:
BaseCamera();
BaseCamera( const BaseCamera& camera );
BaseCamera( const MathsLib::Vector4& vPos, const MathsLib::Vector4& vLookAt );
BaseCamera( const MathsLib::Matrix4x4& matCamera );
bool IsDirty() const;
void SetDirty();
MathsLib::Matrix4x4& GetOrientationMatrix();
const MathsLib::Matrix4x4& GetOrientationMatrix() const;
MathsLib::Matrix4x4& GetViewMatrix();
};
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline MathsLib::Matrix4x4& BaseCamera::GetOrientationMatrix()
{
return mCameraMat;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline const MathsLib::Matrix4x4& BaseCamera::GetOrientationMatrix() const
{
return mCameraMat;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline bool BaseCamera::IsDirty() const
{
return mDirty;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
inline void BaseCamera::SetDirty()
{
mDirty = true;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
#endif
BaseCamera.cpp:
#include "Render/stdafx.h"
#include "BaseCamera.h"
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
BaseCamera::BaseCamera() :
mDirty( true )
{
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
BaseCamera::BaseCamera( const BaseCamera& camera ) :
mDirty( camera.mDirty ),
mCameraMat( camera.mCameraMat ),
mViewMat( camera.mViewMat )
{
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
BaseCamera::BaseCamera( const MathsLib::Vector4& vPos, const MathsLib::Vector4& vLookAt ) :
mDirty( true )
{
MathsLib::Vector4 vDir = (vLookAt - vPos).Normalise();
MathsLib::Vector4 vLat = MathsLib::CrossProduct( MathsLib::Vector4( 0.0f, 1.0f, 0.0f ), vDir ).Normalise();
MathsLib::Vector4 vUp = MathsLib::CrossProduct( vDir, vLat );//.Normalise();
mCameraMat.Set( vLat, vUp, vDir, vPos );
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
BaseCamera::BaseCamera( const MathsLib::Matrix4x4& matCamera ) :
mDirty( true ),
mCameraMat( matCamera )
{
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
MathsLib::Matrix4x4& BaseCamera::GetViewMatrix()
{
if ( IsDirty() )
{
mViewMat = mCameraMat.Inverse();
mDirty = false;
}
return mViewMat;
}
/*+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+*/
I agree with Goz. You need to use homegenous 4x4 matrices if you want to represent affine transformations such as rotate + translate
Assuming row major representation then if there is no scaling or shearing, your 4x4 matrix represents the following:
Rows 0 to 2 : The three basis vectors of your local co-ordinate system ( i.e x,y,z )
Row 3 : the current translation from the origin
So to move along your local x vector, as Goz says, because you can assume it's a unit vector
if there is no scale/shear you just multiply it by the move step ( +ve or -ve ) then add the resultant vector onto Row 4 in the matrix
So taking a simple example of starting at the origin with your local frame set to world frame then your matrix would look something like this
1 0 0 0 <--- x unit vector
0 1 0 0 <--- y unit vector
0 0 1 0 <--- z unit vector
0 0 0 1 <--- translation vector
In terms of a way most game cameras work then the axes map like this:
x axis <=> Camera Pan Left/Right
y axis <=> Camera Pan Up/Down
z axis <=> Camera Zoom In/Out
So if I rotate my entire frame of reference to say look at a new point LookAt then as Goz puts in his BaseCamera overloaded constructor code, you then construct a new local co-ordinate system and set this into your matrix ( all mCameraMat.Set( vLat, vUp, vDir, vPos ) does typically is set those four rows of the matrix i.e VLat would be row 0, vUp row 1, vDir row 2 and vPos row 3 )
Then to zoom in/out would just become row 3 = row 2 * stepval
Again as Goz, rightly points out, you then need to transform this back into world-space and this is done by multiplying by the inverse of the view matrix