Hello, again.
I created a class for a special "effect". It's not a true .fx effect ... but rather an object that generates a series of points in world space, translates them to screen space, and connects them with the Basic2D command: dbLine.
My problem is that, every so often, a point will be generated outside the view frustum and in a position where the resulting screen space representation yields a line artifact spanning the entire screen.
So, I created a Frustum class designed to cull any points generated outside the view frustum. But it is not working. Everything gets culled or--if I swap the test condition result--nothing gets culled. I think it must be an issue with how I acquired my frustum planes. Can anyone confirm this? ... or offer a better solution?
Here is my frustum code:
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Copyright (C) 2010 - P. Andrew LeRoy
//
// 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, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//_________________________________________________________________________
#ifndef _PAL_LIB_Frustum_hpp_
#define _PAL_LIB_Frustum_hpp_
#include "Vector3.hpp"
#include <vector>
namespace PAL_LIB
{
class Plane;
class Frustum
{
public:
Frustum(void);
~Frustum(void);
public:
void updateFrustum(void);
bool testPoint(Vector3 const& point);
void clipPoints(std::vector<Vector3> &points);
protected:
Plane *planes_;
}; // class Frustum
} // namespace PAL_LIB
#endif // _PAL_LIB_Frustum_hpp_
// -----------------------------------------------------------
// Frustum.cpp
// -----------------------------------------------------------
#include "Frustum.hpp"
#include "Vector3.hpp"
#include "Plane.hpp"
#include "DarkGDK.h"
#include <vector>
namespace PAL_LIB
{
Frustum::Frustum(void)
{
planes_ = new Plane[6];
}
Frustum::~Frustum(void)
{
delete[] planes_;
}
bool Frustum::testPoint(Vector3 const& point)
{
for (int i = 0; i < 6; ++i)
{
if (planes_[i].test(point) < 0.0f)
{
return false;
}
}
return true;
}
void Frustum::clipPoints(std::vector<Vector3> &points)
{
for (std::vector<Vector3>::iterator it = points.begin(); it != points.end(); ++it)
{
if (!testPoint(*it))
{
points.erase(it);
}
}
}
void Frustum::updateFrustum(void)
{
float length;
D3DXMATRIX clip, view, proj;
D3DXMatrixIdentity(&clip);
view = dbGetViewMatrix(0);
proj = dbGetProjectionMatrix(0);
D3DXMatrixMultiply(&clip, &view, &proj);
// left frustrum plane
planes_[0].normal_.x = clip.m[0][3] + clip.m[0][0]; //-(clip.m[0][3] + clip.m[0][0]);
planes_[0].normal_.y = clip.m[1][3] + clip.m[1][0]; //-(clip.m[1][3] + clip.m[1][0]);
planes_[0].normal_.x = clip.m[2][3] + clip.m[2][0]; //-(clip.m[2][3] + clip.m[2][0]);
planes_[0].offset_ = clip.m[3][3] + clip.m[3][0]; //-(clip.m[3][3] + clip.m[3][0]);
// right frustrum plane
planes_[1].normal_.x = clip.m[0][3] - clip.m[0][0]; //-(clip.m[0][3] - clip.m[0][0]);
planes_[1].normal_.y = clip.m[1][3] - clip.m[1][0]; //-(clip.m[1][3] - clip.m[1][0]);
planes_[1].normal_.z = clip.m[2][3] - clip.m[2][0]; //-(clip.m[2][3] - clip.m[2][0]);
planes_[1].offset_ = clip.m[3][3] - clip.m[3][0]; //-(clip.m[3][3] - clip.m[3][0]);
// bottom frustrum plane
planes_[2].normal_.x = clip.m[0][3] + clip.m[0][1]; //-(clip.m[0][3] - clip.m[0][1]);
planes_[2].normal_.y = clip.m[1][3] + clip.m[1][1]; //-(clip.m[1][3] - clip.m[1][1]);
planes_[2].normal_.z = clip.m[2][3] + clip.m[2][1]; //-(clip.m[2][3] - clip.m[2][1]);
planes_[2].offset_ = clip.m[3][3] + clip.m[3][1]; //-(clip.m[3][3] - clip.m[3][1]);
// top frustrum plane
planes_[3].normal_.x = clip.m[0][3] - clip.m[0][1]; //-(clip.m[0][3] + clip.m[0][1]);
planes_[3].normal_.y = clip.m[1][3] - clip.m[1][1]; //-(clip.m[1][3] + clip.m[1][1]);
planes_[3].normal_.z = clip.m[2][3] - clip.m[2][1]; //-(clip.m[2][3] + clip.m[2][1]);
planes_[3].offset_ = clip.m[3][3] - clip.m[3][1]; //-(clip.m[3][3] + clip.m[3][1]);
// near frustrum plane
planes_[4].normal_.x = clip.m[0][3] + clip.m[0][2]; //-clip.m[0][2];
planes_[4].normal_.y = clip.m[1][3] + clip.m[1][2]; //-clip.m[1][2];
planes_[4].normal_.z = clip.m[2][3] + clip.m[2][2]; //-clip.m[2][2];
planes_[4].offset_ = clip.m[3][3] + clip.m[3][2]; //-clip.m[3][2];
// far frustrum plane
planes_[5].normal_.x = clip.m[0][3] - clip.m[0][2]; //-(clip.m[0][3] - clip.m[0][2]);
planes_[5].normal_.y = clip.m[1][3] - clip.m[1][2]; //-(clip.m[1][3] - clip.m[1][2]);
planes_[5].normal_.z = clip.m[2][3] - clip.m[2][2]; //-(clip.m[2][3] - clip.m[2][2]);
planes_[5].offset_ = clip.m[3][3] - clip.m[3][2]; //-(clip.m[3][3] - clip.m[3][2]);
for (int i = 0; i < 6; ++i)
{
length = planes_[i].normal_.length();
planes_[i].normal_.normalize();
planes_[i].offset_ /= length;
}
}
} // namespace PAL_LIB
and the Plane code:
#ifndef _PAL_LIB_Plane_hpp_
#define _PAL_LIB_Plane_hpp_
#include "Vector3.hpp"
#include "Math.hpp"
namespace PAL_LIB
{
class Plane
{
public:
Plane();
Plane( float a, float b, float c, float d);
Plane(Vector3 const& p0, Vector3 const& p1, Vector3 const& p2);
~Plane(void);
// copy operations
Plane(Plane const& other);
Plane& operator=(Plane const& other);
// comparison
bool operator==(Plane const& ray) const;
bool operator!=(Plane const& ray) const;
public:
void set(float a, float b, float c, float d);
void set(Vector3 const& n, float d);
void set(Vector3 const& p0, Vector3 const& p1, Vector3 const& p2);
inline friend float distance(Plane const& plane, Vector3 const& point)
{
return Abs(plane.test(point));
}
float test(Vector3 const& point) const;
Vector3 getNearestPoint(Vector3 const& point) const;
void get(Vector3 &normal, float &direction) const;
Vector3 getNormal(void) const;
float getOffset(void) const;
public:
float offset_;
Vector3 normal_;
};
inline Plane::~Plane(void) { }
inline Vector3 Plane::getNormal() const { return normal_; }
inline float Plane::getOffset() const { return offset_; }
inline void Plane::set(Vector3 const& n, float d)
{
set(n.x, n.y, n.z, d);
}
inline float Plane::test(Vector3 const& point) const
{
return normal_.dot(point) + offset_;
}
} // namespace PAL_LIB
#endif // _PAL_LIB_Plane_hpp_
// ------------------------------------------------------
// Plane.cpp
// ------------------------------------------------------
#include "Plane.hpp"
#include "Math.hpp"
#include "Vector3.hpp"
#include "assert.hpp"
namespace PAL_LIB
{
Plane::Plane() : normal_(1.0f, 0.0f, 0.0f), offset_(0.0f)
{ }
Plane::Plane(float a, float b, float c, float d)
{
set(a, b, c, d);
}
Plane::Plane(Vector3 const& p0, Vector3 const& p1, Vector3 const& p2)
{
set(p0, p1, p2);
}
Plane::Plane(Plane const& other) : normal_( other.normal_ ), offset_( other.offset_ )
{ }
Plane& Plane::operator=(Plane const& other)
{
if (this == &other)
{ return *this; }
normal_ = other.normal_;
offset_ = other.offset_;
return *this;
}
bool Plane::operator==(Plane const& plane) const
{
return (plane.normal_ == normal_ && plane.offset_ == offset_);
}
bool Plane::operator!=(Plane const& plane) const
{
return !(plane.normal_ == normal_ && plane.offset_ == offset_);
}
void Plane::get(Vector3& normal, float& offset) const
{
normal = normal_;
offset = offset_;
}
void Plane::set(float a, float b, float c, float d)
{
float lensq = a*a + b*b + c*c;
ASSERT( !IsZero(lensq) );
if (IsZero(lensq))
{
normal_ = Vector3::xAxis;
offset_ = 0.0f;
}
else
{
float recip = InvSqrt(lensq);
normal_.set(a*recip, b*recip, c*recip);
offset_ = d*recip;
}
}
void Plane::set(Vector3 const& p0, Vector3 const& p1, Vector3 const& p2)
{
Vector3 u = p1 - p0;
Vector3 v = p2 - p0;
Vector3 w = u.cross(v);
float lensq = w.x*w.x + w.y*w.y + w.z*w.z;
ASSERT( !IsZero(lensq) );
if (IsZero(lensq))
{
normal_ = Vector3::xAxis;
offset_ = 0.0f;
}
else
{
float recip = 1.0f/lensq;
normal_.set( w.x*recip, w.y*recip, w.z*recip );
offset_ = -normal_.dot(p0);
}
}
Vector3 Plane::getNearestPoint(Vector3 const& point) const
{
return point - test(point) * normal_;
}
} // namespace PAL_LIB
and finally, the effect code (which generates the points and owns the frustum object to check each point)
#ifndef _PAL_LIB_BoltEmitter_hpp_
#define _PAL_LIB_BoltEmitter_hpp_
#include "DarkGDK.h"
namespace PAL_LIB
{
class Frustum;
class BoltEmitter
{
private:
typedef std::vector<Vector3> path_list;
typedef std::vector<Vector3>::iterator path_iter;
public:
BoltEmitter(void);
~BoltEmitter(void);
public:
void update(void);
void begin(void);
void set(Vector3 origin, Vector3 target, int duration);
protected:
void createPath(path_list &path);
protected:
int ID_P0_WORLD;
int ID_P1_WORLD;
int ID_P0_SCREEN;
int ID_P1_SCREEN;
int ID_POST_BMP;
int framesLeft_;
Frustum *frustum_;
D3DXMATRIX jitterMatrix_;
D3DXMATRIX jitterRotate_;
Vector3 origin_;
Vector3 target_;
Vector3 direction_;
};
} // namespace PAL_LIB
#endif //_PAL_LIB_BoltEmitter_hpp_
// ----------------------------------------------------
// BoltEmitter.cpp
// ----------------------------------------------------
//¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// GAME 2342 - Lab03
// P. Andrew LeRoy
//_________________________________________________________________________
#include "BoltEmitter.hpp"
#include "Vector3.hpp"
#include "Functions.hpp"
#include "Frustum.hpp"
#include "Constants.hpp"
#include "DarkGDK.h"
using namespace std;
namespace PAL_LIB
{
BoltEmitter::BoltEmitter()
: origin_(0.0f, 0.0f, 0.0f),
target_(0.0f, 0.0f, 0.0f),
direction_(0.0f, 0.0f, 0.0f)
{
ID_P0_WORLD = 10;
ID_P1_WORLD = 11;
ID_P0_SCREEN = 12;
ID_P1_SCREEN = 13;
frustum_ = new Frustum();
dbMakeVector3(ID_P0_WORLD);
dbMakeVector3(ID_P1_WORLD);
dbMakeVector3(ID_P0_SCREEN);
dbMakeVector3(ID_P1_SCREEN);
}
BoltEmitter::~BoltEmitter(void)
{
if (frustum_) { delete frustum_; }
dbDeleteVector3(ID_P0_WORLD);
dbDeleteVector3(ID_P1_WORLD);
dbDeleteVector3(ID_P0_SCREEN);
dbDeleteVector3(ID_P1_SCREEN);
}
void BoltEmitter::update(void)
{
if (--framesLeft_ > 0)
{
path_list path;
createPath(path);
if (!path.empty())
{
dbInk(dbRGB(255 - dbRND(15), 255 - dbRND(15), 255 - dbRND(15)), 0);
dbViewMatrix4 (ID_VIEW_MATRIX);
dbProjectionMatrix4 (ID_PROJ_MATRIX);
dbWorldMatrix4 (ID_WRLD_MATRIX);
path_iter p0 = path.begin();
path_iter p1 = path.begin();
for (++p1; p1 != path.end(); ++p1)
{
dbSetVector3(ID_P0_WORLD, p0->x, p0->y, p0->z);
dbSetVector3(ID_P1_WORLD, p1->x, p1->y, p1->z);
dbProjectVector3(ID_P0_SCREEN, ID_P0_WORLD, ID_PROJ_MATRIX, ID_VIEW_MATRIX, ID_WRLD_MATRIX);
dbProjectVector3(ID_P1_SCREEN, ID_P1_WORLD, ID_PROJ_MATRIX, ID_VIEW_MATRIX, ID_WRLD_MATRIX);
dbLine(dbXVector3(ID_P0_SCREEN), dbYVector3(ID_P0_SCREEN),
dbXVector3(ID_P1_SCREEN), dbYVector3(ID_P1_SCREEN));
++p0;
}
path.clear();
}
}
}
void BoltEmitter::createPath(BoltEmitter::path_list &path)
{
Vector3 point(origin_);
Vector3 v;
D3DXVECTOR3 relativePos;
D3DXVECTOR3 targetPos;
float d;
bool drawing = true;
while (drawing)
{
D3DXMatrixMultiply(&jitterMatrix_, &jitterMatrix_, &jitterRotate_);
v = direction_ * 2.0f;
d = distance(point, target_);
if (d > 20.0f)
{
point += v;
if (dbRND(20) % 2 == 0)
{
direction_ = target_ - point;
direction_.normalize();
point += 1.5f * v;
}
relativePos.x = 0.0f;
relativePos.y = 0.0f;
relativePos.z = dbRND(11.0) - 5.0f;
D3DXVec3TransformCoord(&targetPos, &relativePos, &jitterMatrix_);
Vector3 w(targetPos.x, targetPos.y, targetPos.z);
point += w;
if (frustum_->testPoint(point))
{
path.push_back(point);
}
}
else
{
drawing = false;
if (frustum_->testPoint(target_))
{
path.push_back(target_);
}
}
}
}
void BoltEmitter::begin(void)
{
frustum_->updateFrustum();
}
void BoltEmitter::set(Vector3 origin, Vector3 target, int duration)
{
origin_ = origin;
target_ = target;
direction_ = target_ - origin_;
framesLeft_ = duration;
direction_.normalize();
D3DXMatrixIdentity(&jitterMatrix_);
D3DXMatrixIdentity(&jitterRotate_);
D3DXMatrixTranslation(&jitterMatrix_, 0.0f, 1.0f, 0.0f);
D3DXMatrixRotationY(&jitterRotate_, 35.0f * toRADIANS);
}
} // namespace PAL_LIB
The #includes not included in this post are likely not relevant to the question, but I'll post any that may lead to a resolution.
"Contants.hpp" - contains constants global to my project (namespace PAL_LIB), namely IDs for DarkGDK functions.
"Vector3.hpp" - is a simple object-oriented approach to 3D vectors.
"Math.hpp" - contains basic math, trig, and 3D util type functions, namely (IsZero is used in the plane class. It looks like this:
#define kEpsilon 1.0e-6f
inline bool IsZero(float a)
{
return (fabsf(a) < kEpsilon);
} // IsZero()
)
Thanks in advance,
~Andrew~