1209 lines
33 KiB
C++
1209 lines
33 KiB
C++
/*
|
|
Copyright (C) 2001-2006, William Joseph.
|
|
All Rights Reserved.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant 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 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant 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 GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#if !defined( INCLUDED_RENDER_H )
|
|
#define INCLUDED_RENDER_H
|
|
|
|
/// \file
|
|
/// \brief High-level constructs for efficient OpenGL rendering.
|
|
|
|
#include "irender.h"
|
|
#include "igl.h"
|
|
|
|
#include "container/array.h"
|
|
#include "math/vector.h"
|
|
#include "math/pi.h"
|
|
|
|
#include <vector>
|
|
|
|
typedef unsigned int RenderIndex;
|
|
const GLenum RenderIndexTypeID = GL_UNSIGNED_INT;
|
|
|
|
/// \brief A resizable buffer of indices.
|
|
class IndexBuffer
|
|
{
|
|
typedef std::vector<RenderIndex> Indices;
|
|
Indices m_data;
|
|
public:
|
|
typedef Indices::iterator iterator;
|
|
typedef Indices::const_iterator const_iterator;
|
|
|
|
iterator begin(){
|
|
return m_data.begin();
|
|
}
|
|
const_iterator begin() const {
|
|
return m_data.begin();
|
|
}
|
|
iterator end(){
|
|
return m_data.end();
|
|
}
|
|
const_iterator end() const {
|
|
return m_data.end();
|
|
}
|
|
|
|
bool empty() const {
|
|
return m_data.empty();
|
|
}
|
|
std::size_t size() const {
|
|
return m_data.size();
|
|
}
|
|
const RenderIndex* data() const {
|
|
return &( *m_data.begin() );
|
|
}
|
|
RenderIndex& operator[]( std::size_t index ){
|
|
return m_data[index];
|
|
}
|
|
const RenderIndex& operator[]( std::size_t index ) const {
|
|
return m_data[index];
|
|
}
|
|
void clear(){
|
|
m_data.clear();
|
|
}
|
|
void reserve( std::size_t max_indices ){
|
|
m_data.reserve( max_indices );
|
|
}
|
|
void insert( RenderIndex index ){
|
|
m_data.push_back( index );
|
|
}
|
|
void swap( IndexBuffer& other ){
|
|
std::swap( m_data, m_data );
|
|
}
|
|
};
|
|
|
|
namespace std
|
|
{
|
|
/// \brief Swaps the values of \p self and \p other.
|
|
/// Overloads std::swap.
|
|
inline void swap( IndexBuffer& self, IndexBuffer& other ){
|
|
self.swap( other );
|
|
}
|
|
}
|
|
|
|
/// \brief A resizable buffer of vertices.
|
|
/// \param Vertex The vertex data type.
|
|
template<typename Vertex>
|
|
class VertexBuffer
|
|
{
|
|
typedef typename std::vector<Vertex> Vertices;
|
|
Vertices m_data;
|
|
public:
|
|
typedef typename Vertices::iterator iterator;
|
|
typedef typename Vertices::const_iterator const_iterator;
|
|
|
|
iterator begin(){
|
|
return m_data.begin();
|
|
}
|
|
iterator end(){
|
|
return m_data.end();
|
|
}
|
|
const_iterator begin() const {
|
|
return m_data.begin();
|
|
}
|
|
const_iterator end() const {
|
|
return m_data.end();
|
|
}
|
|
|
|
bool empty() const {
|
|
return m_data.empty();
|
|
}
|
|
RenderIndex size() const {
|
|
return RenderIndex( m_data.size() );
|
|
}
|
|
const Vertex* data() const {
|
|
return &( *m_data.begin() );
|
|
}
|
|
Vertex& operator[]( std::size_t index ){
|
|
return m_data[index];
|
|
}
|
|
const Vertex& operator[]( std::size_t index ) const {
|
|
return m_data[index];
|
|
}
|
|
|
|
void clear(){
|
|
m_data.clear();
|
|
}
|
|
void reserve( std::size_t max_vertices ){
|
|
m_data.reserve( max_vertices );
|
|
}
|
|
void push_back( const Vertex& vertex ){
|
|
m_data.push_back( vertex );
|
|
}
|
|
};
|
|
|
|
/// \brief A wrapper around a VertexBuffer which inserts only vertices which have not already been inserted.
|
|
/// \param Vertex The vertex data type. Must support operator<, operator== and operator!=.
|
|
/// For best performance, quantise vertices before inserting them.
|
|
template<typename Vertex>
|
|
class UniqueVertexBuffer
|
|
{
|
|
typedef VertexBuffer<Vertex> Vertices;
|
|
Vertices& m_data;
|
|
|
|
struct bnode
|
|
{
|
|
bnode()
|
|
: m_left( 0 ), m_right( 0 ){
|
|
}
|
|
RenderIndex m_left;
|
|
RenderIndex m_right;
|
|
};
|
|
|
|
std::vector<bnode> m_btree;
|
|
RenderIndex m_prev0;
|
|
RenderIndex m_prev1;
|
|
RenderIndex m_prev2;
|
|
|
|
RenderIndex find_or_insert( const Vertex& vertex ){
|
|
RenderIndex index = 0;
|
|
|
|
while ( 1 )
|
|
{
|
|
if ( vertex < m_data[index] ) {
|
|
bnode& node = m_btree[index];
|
|
if ( node.m_left != 0 ) {
|
|
index = node.m_left;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
node.m_left = RenderIndex( m_btree.size() );
|
|
m_btree.push_back( bnode() );
|
|
m_data.push_back( vertex );
|
|
return RenderIndex( m_btree.size() - 1 );
|
|
}
|
|
}
|
|
if ( m_data[index] < vertex ) {
|
|
bnode& node = m_btree[index];
|
|
if ( node.m_right != 0 ) {
|
|
index = node.m_right;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
node.m_right = RenderIndex( m_btree.size() );
|
|
m_btree.push_back( bnode() );
|
|
m_data.push_back( vertex );
|
|
return RenderIndex( m_btree.size() - 1 );
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
}
|
|
public:
|
|
UniqueVertexBuffer( Vertices& data )
|
|
: m_data( data ), m_prev0( 0 ), m_prev1( 0 ), m_prev2( 0 ){
|
|
}
|
|
|
|
typedef typename Vertices::const_iterator iterator;
|
|
|
|
iterator begin() const {
|
|
return m_data.begin();
|
|
}
|
|
iterator end() const {
|
|
return m_data.end();
|
|
}
|
|
|
|
std::size_t size() const {
|
|
return m_data.size();
|
|
}
|
|
const Vertex* data() const {
|
|
return &( *m_data.begin() );
|
|
}
|
|
Vertex& operator[]( std::size_t index ){
|
|
return m_data[index];
|
|
}
|
|
const Vertex& operator[]( std::size_t index ) const {
|
|
return m_data[index];
|
|
}
|
|
|
|
void clear(){
|
|
m_prev0 = 0;
|
|
m_prev1 = 0;
|
|
m_prev2 = 0;
|
|
m_data.clear();
|
|
m_btree.clear();
|
|
}
|
|
void reserve( std::size_t max_vertices ){
|
|
m_data.reserve( max_vertices );
|
|
m_btree.reserve( max_vertices );
|
|
}
|
|
/// \brief Returns the index of the element equal to \p vertex.
|
|
RenderIndex insert( const Vertex& vertex ){
|
|
if ( m_data.empty() ) {
|
|
m_data.push_back( vertex );
|
|
m_btree.push_back( bnode() );
|
|
return 0;
|
|
}
|
|
|
|
if ( m_data[m_prev0] == vertex ) {
|
|
return m_prev0;
|
|
}
|
|
if ( m_prev1 != m_prev0 && m_data[m_prev1] == vertex ) {
|
|
return m_prev1;
|
|
}
|
|
if ( m_prev2 != m_prev0 && m_prev2 != m_prev1 && m_data[m_prev2] == vertex ) {
|
|
return m_prev2;
|
|
}
|
|
|
|
m_prev2 = m_prev1;
|
|
m_prev1 = m_prev0;
|
|
m_prev0 = find_or_insert( vertex );
|
|
|
|
return m_prev0;
|
|
}
|
|
};
|
|
|
|
|
|
/// \brief A 4-byte colour.
|
|
struct Colour4b
|
|
{
|
|
unsigned char r, g, b, a;
|
|
|
|
Colour4b(){
|
|
}
|
|
|
|
Colour4b( unsigned char _r, unsigned char _g, unsigned char _b, unsigned char _a )
|
|
: r( _r ), g( _g ), b( _b ), a( _a ){
|
|
}
|
|
};
|
|
|
|
const Colour4b colour_vertex( 0, 255, 0, 255 );
|
|
const Colour4b colour_selected( 0, 0, 255, 255 );
|
|
|
|
inline bool operator<( const Colour4b& self, const Colour4b& other ){
|
|
if ( self.r != other.r ) {
|
|
return self.r < other.r;
|
|
}
|
|
if ( self.g != other.g ) {
|
|
return self.g < other.g;
|
|
}
|
|
if ( self.b != other.b ) {
|
|
return self.b < other.b;
|
|
}
|
|
if ( self.a != other.a ) {
|
|
return self.a < other.a;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool operator==( const Colour4b& self, const Colour4b& other ){
|
|
return self.r == other.r && self.g == other.g && self.b == other.b && self.a == other.a;
|
|
}
|
|
|
|
inline bool operator!=( const Colour4b& self, const Colour4b& other ){
|
|
return !operator==( self, other );
|
|
}
|
|
|
|
/// \brief A 3-float vertex.
|
|
struct Vertex3f : public Vector3
|
|
{
|
|
Vertex3f(){
|
|
}
|
|
|
|
Vertex3f( float _x, float _y, float _z )
|
|
: Vector3( _x, _y, _z ){
|
|
}
|
|
};
|
|
|
|
inline bool operator<( const Vertex3f& self, const Vertex3f& other ){
|
|
if ( self.x() != other.x() ) {
|
|
return self.x() < other.x();
|
|
}
|
|
if ( self.y() != other.y() ) {
|
|
return self.y() < other.y();
|
|
}
|
|
if ( self.z() != other.z() ) {
|
|
return self.z() < other.z();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool operator==( const Vertex3f& self, const Vertex3f& other ){
|
|
return self.x() == other.x() && self.y() == other.y() && self.z() == other.z();
|
|
}
|
|
|
|
inline bool operator!=( const Vertex3f& self, const Vertex3f& other ){
|
|
return !operator==( self, other );
|
|
}
|
|
|
|
|
|
inline Vertex3f vertex3f_from_array( const float* array ){
|
|
return Vertex3f( array[0], array[1], array[2] );
|
|
}
|
|
|
|
inline float* vertex3f_to_array( Vertex3f& vertex ){
|
|
return reinterpret_cast<float*>( &vertex );
|
|
}
|
|
|
|
inline const float* vertex3f_to_array( const Vertex3f& vertex ){
|
|
return reinterpret_cast<const float*>( &vertex );
|
|
}
|
|
|
|
const Vertex3f vertex3f_identity( 0, 0, 0 );
|
|
|
|
inline Vertex3f vertex3f_for_vector3( const Vector3& vector3 ){
|
|
return Vertex3f( vector3.x(), vector3.y(), vector3.z() );
|
|
}
|
|
|
|
inline const Vector3& vertex3f_to_vector3( const Vertex3f& vertex ){
|
|
return vertex;
|
|
}
|
|
|
|
inline Vector3& vertex3f_to_vector3( Vertex3f& vertex ){
|
|
return vertex;
|
|
}
|
|
|
|
|
|
/// \brief A 3-float normal.
|
|
struct Normal3f : public Vector3
|
|
{
|
|
Normal3f(){
|
|
}
|
|
|
|
Normal3f( float _x, float _y, float _z )
|
|
: Vector3( _x, _y, _z ){
|
|
}
|
|
};
|
|
|
|
inline bool operator<( const Normal3f& self, const Normal3f& other ){
|
|
if ( self.x() != other.x() ) {
|
|
return self.x() < other.x();
|
|
}
|
|
if ( self.y() != other.y() ) {
|
|
return self.y() < other.y();
|
|
}
|
|
if ( self.z() != other.z() ) {
|
|
return self.z() < other.z();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool operator==( const Normal3f& self, const Normal3f& other ){
|
|
return self.x() == other.x() && self.y() == other.y() && self.z() == other.z();
|
|
}
|
|
|
|
inline bool operator!=( const Normal3f& self, const Normal3f& other ){
|
|
return !operator==( self, other );
|
|
}
|
|
|
|
|
|
inline Normal3f normal3f_from_array( const float* array ){
|
|
return Normal3f( array[0], array[1], array[2] );
|
|
}
|
|
|
|
inline float* normal3f_to_array( Normal3f& normal ){
|
|
return reinterpret_cast<float*>( &normal );
|
|
}
|
|
|
|
inline const float* normal3f_to_array( const Normal3f& normal ){
|
|
return reinterpret_cast<const float*>( &normal );
|
|
}
|
|
|
|
inline Normal3f normal3f_for_vector3( const Vector3& vector3 ){
|
|
return Normal3f( vector3.x(), vector3.y(), vector3.z() );
|
|
}
|
|
|
|
inline const Vector3& normal3f_to_vector3( const Normal3f& normal ){
|
|
return normal;
|
|
}
|
|
|
|
inline Vector3& normal3f_to_vector3( Normal3f& normal ){
|
|
return normal;
|
|
}
|
|
|
|
|
|
/// \brief A 2-float texture-coordinate set.
|
|
struct TexCoord2f : public Vector2
|
|
{
|
|
TexCoord2f(){
|
|
}
|
|
|
|
TexCoord2f( float _s, float _t )
|
|
: Vector2( _s, _t ){
|
|
}
|
|
|
|
float& s(){
|
|
return x();
|
|
}
|
|
const float& s() const {
|
|
return x();
|
|
}
|
|
float& t(){
|
|
return y();
|
|
}
|
|
const float& t() const {
|
|
return y();
|
|
}
|
|
};
|
|
|
|
inline bool operator<( const TexCoord2f& self, const TexCoord2f& other ){
|
|
if ( self.s() != other.s() ) {
|
|
return self.s() < other.s();
|
|
}
|
|
if ( self.t() != other.t() ) {
|
|
return self.t() < other.t();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool operator==( const TexCoord2f& self, const TexCoord2f& other ){
|
|
return self.s() == other.s() && self.t() == other.t();
|
|
}
|
|
|
|
inline bool operator!=( const TexCoord2f& self, const TexCoord2f& other ){
|
|
return !operator==( self, other );
|
|
}
|
|
|
|
|
|
inline float* texcoord2f_to_array( TexCoord2f& texcoord ){
|
|
return reinterpret_cast<float*>( &texcoord );
|
|
}
|
|
|
|
inline const float* texcoord2f_to_array( const TexCoord2f& texcoord ){
|
|
return reinterpret_cast<const float*>( &texcoord );
|
|
}
|
|
|
|
inline const TexCoord2f& texcoord2f_from_array( const float* array ){
|
|
return *reinterpret_cast<const TexCoord2f*>( array );
|
|
}
|
|
|
|
inline TexCoord2f texcoord2f_for_vector2( const Vector2& vector2 ){
|
|
return TexCoord2f( vector2.x(), vector2.y() );
|
|
}
|
|
|
|
inline Vector4 colour4f_for_vector4( const Vector4& vector4 ){
|
|
return vector4;
|
|
}
|
|
|
|
inline const Vector2& texcoord2f_to_vector2( const TexCoord2f& vertex ){
|
|
return vertex;
|
|
}
|
|
|
|
inline Vector2& texcoord2f_to_vector2( TexCoord2f& vertex ){
|
|
return vertex;
|
|
}
|
|
inline Vector4& colour4f_to_vector4( Vector4& vertex ){
|
|
return vertex;
|
|
}
|
|
|
|
/// \brief Returns \p normal rescaled to be unit-length.
|
|
inline Normal3f normal3f_normalised( const Normal3f& normal ){
|
|
return normal3f_for_vector3( vector3_normalised( normal3f_to_vector3( normal ) ) );
|
|
}
|
|
|
|
enum UnitSphereOctant
|
|
{
|
|
UNITSPHEREOCTANT_000 = 0 << 0 | 0 << 1 | 0 << 2,
|
|
UNITSPHEREOCTANT_001 = 0 << 0 | 0 << 1 | 1 << 2,
|
|
UNITSPHEREOCTANT_010 = 0 << 0 | 1 << 1 | 0 << 2,
|
|
UNITSPHEREOCTANT_011 = 0 << 0 | 1 << 1 | 1 << 2,
|
|
UNITSPHEREOCTANT_100 = 1 << 0 | 0 << 1 | 0 << 2,
|
|
UNITSPHEREOCTANT_101 = 1 << 0 | 0 << 1 | 1 << 2,
|
|
UNITSPHEREOCTANT_110 = 1 << 0 | 1 << 1 | 0 << 2,
|
|
UNITSPHEREOCTANT_111 = 1 << 0 | 1 << 1 | 1 << 2,
|
|
};
|
|
|
|
/// \brief Returns the octant for \p normal indicating the sign of the region of unit-sphere space it lies within.
|
|
inline UnitSphereOctant normal3f_classify_octant( const Normal3f& normal ){
|
|
return static_cast<UnitSphereOctant>(
|
|
( ( normal.x() > 0 ) << 0 ) | ( ( normal.y() > 0 ) << 1 ) | ( ( normal.z() > 0 ) << 2 )
|
|
);
|
|
}
|
|
|
|
/// \brief Returns \p normal with its components signs made positive based on \p octant.
|
|
inline Normal3f normal3f_fold_octant( const Normal3f& normal, UnitSphereOctant octant ){
|
|
switch ( octant )
|
|
{
|
|
case UNITSPHEREOCTANT_000:
|
|
return Normal3f( -normal.x(), -normal.y(), -normal.z() );
|
|
case UNITSPHEREOCTANT_001:
|
|
return Normal3f( normal.x(), -normal.y(), -normal.z() );
|
|
case UNITSPHEREOCTANT_010:
|
|
return Normal3f( -normal.x(), normal.y(), -normal.z() );
|
|
case UNITSPHEREOCTANT_011:
|
|
return Normal3f( normal.x(), normal.y(), -normal.z() );
|
|
case UNITSPHEREOCTANT_100:
|
|
return Normal3f( -normal.x(), -normal.y(), normal.z() );
|
|
case UNITSPHEREOCTANT_101:
|
|
return Normal3f( normal.x(), -normal.y(), normal.z() );
|
|
case UNITSPHEREOCTANT_110:
|
|
return Normal3f( -normal.x(), normal.y(), normal.z() );
|
|
case UNITSPHEREOCTANT_111:
|
|
return Normal3f( normal.x(), normal.y(), normal.z() );
|
|
}
|
|
return Normal3f();
|
|
}
|
|
|
|
/// \brief Reverses the effect of normal3f_fold_octant() on \p normal with \p octant.
|
|
/// \p normal must have been obtained with normal3f_fold_octant().
|
|
/// \p octant must have been obtained with normal3f_classify_octant().
|
|
inline Normal3f normal3f_unfold_octant( const Normal3f& normal, UnitSphereOctant octant ){
|
|
return normal3f_fold_octant( normal, octant );
|
|
}
|
|
|
|
enum UnitSphereSextant
|
|
{
|
|
UNITSPHERESEXTANT_XYZ = 0,
|
|
UNITSPHERESEXTANT_XZY = 1,
|
|
UNITSPHERESEXTANT_YXZ = 2,
|
|
UNITSPHERESEXTANT_YZX = 3,
|
|
UNITSPHERESEXTANT_ZXY = 4,
|
|
UNITSPHERESEXTANT_ZYX = 5,
|
|
};
|
|
|
|
/// \brief Returns the sextant for \p normal indicating how to sort its components so that x > y > z.
|
|
/// All components of \p normal must be positive.
|
|
/// \p normal must be normalised.
|
|
inline UnitSphereSextant normal3f_classify_sextant( const Normal3f& normal ){
|
|
return
|
|
normal.x() >= normal.y()
|
|
? normal.x() >= normal.z()
|
|
? normal.y() >= normal.z()
|
|
? UNITSPHERESEXTANT_XYZ
|
|
: UNITSPHERESEXTANT_XZY
|
|
: UNITSPHERESEXTANT_ZXY
|
|
: normal.y() >= normal.z()
|
|
? normal.x() >= normal.z()
|
|
? UNITSPHERESEXTANT_YXZ
|
|
: UNITSPHERESEXTANT_YZX
|
|
: UNITSPHERESEXTANT_ZYX;
|
|
}
|
|
|
|
/// \brief Returns \p normal with its components sorted so that x > y > z based on \p sextant.
|
|
/// All components of \p normal must be positive.
|
|
/// \p normal must be normalised.
|
|
inline Normal3f normal3f_fold_sextant( const Normal3f& normal, UnitSphereSextant sextant ){
|
|
switch ( sextant )
|
|
{
|
|
case UNITSPHERESEXTANT_XYZ:
|
|
return Normal3f( normal.x(), normal.y(), normal.z() );
|
|
case UNITSPHERESEXTANT_XZY:
|
|
return Normal3f( normal.x(), normal.z(), normal.y() );
|
|
case UNITSPHERESEXTANT_YXZ:
|
|
return Normal3f( normal.y(), normal.x(), normal.z() );
|
|
case UNITSPHERESEXTANT_YZX:
|
|
return Normal3f( normal.y(), normal.z(), normal.x() );
|
|
case UNITSPHERESEXTANT_ZXY:
|
|
return Normal3f( normal.z(), normal.x(), normal.y() );
|
|
case UNITSPHERESEXTANT_ZYX:
|
|
return Normal3f( normal.z(), normal.y(), normal.x() );
|
|
}
|
|
return Normal3f();
|
|
}
|
|
|
|
/// \brief Reverses the effect of normal3f_fold_sextant() on \p normal with \p sextant.
|
|
/// \p normal must have been obtained with normal3f_fold_sextant().
|
|
/// \p sextant must have been obtained with normal3f_classify_sextant().
|
|
inline Normal3f normal3f_unfold_sextant( const Normal3f& normal, UnitSphereSextant sextant ){
|
|
return normal3f_fold_sextant( normal, sextant );
|
|
}
|
|
|
|
const std::size_t c_quantise_normal = 1 << 6;
|
|
|
|
/// \brief All the components of \p folded must be positive and sorted so that x > y > z.
|
|
inline Normal3f normal3f_folded_quantised( const Normal3f& folded ){
|
|
// compress
|
|
double scale = static_cast<float>( c_quantise_normal ) / ( folded.x() + folded.y() + folded.z() );
|
|
unsigned int zbits = static_cast<unsigned int>( folded.z() * scale );
|
|
unsigned int ybits = static_cast<unsigned int>( folded.y() * scale );
|
|
|
|
// decompress
|
|
return normal3f_normalised( Normal3f(
|
|
static_cast<float>( c_quantise_normal - zbits - ybits ),
|
|
static_cast<float>( ybits ),
|
|
static_cast<float>( zbits )
|
|
) );
|
|
}
|
|
|
|
/// \brief Returns \p normal quantised by compressing and then decompressing its representation.
|
|
inline Normal3f normal3f_quantised_custom( const Normal3f& normal ){
|
|
UnitSphereOctant octant = normal3f_classify_octant( normal );
|
|
Normal3f folded = normal3f_fold_octant( normal, octant );
|
|
UnitSphereSextant sextant = normal3f_classify_sextant( folded );
|
|
folded = normal3f_fold_sextant( folded, sextant );
|
|
return normal3f_unfold_octant( normal3f_unfold_sextant( normal3f_folded_quantised( folded ), sextant ), octant );
|
|
}
|
|
|
|
|
|
|
|
struct spherical_t
|
|
{
|
|
double longditude, latitude;
|
|
|
|
spherical_t( double _longditude, double _latitude )
|
|
: longditude( _longditude ), latitude( _latitude ){
|
|
}
|
|
};
|
|
|
|
/*
|
|
{
|
|
theta = 2pi * U;
|
|
phi = acos((2 * V) - 1);
|
|
|
|
U = theta / 2pi;
|
|
V = (cos(phi) + 1) / 2;
|
|
}
|
|
|
|
longitude = atan(y / x);
|
|
latitude = acos(z);
|
|
*/
|
|
struct uniformspherical_t
|
|
{
|
|
double U, V;
|
|
|
|
uniformspherical_t( double U_, double V_ )
|
|
: U( U_ ), V( V_ ){
|
|
}
|
|
};
|
|
|
|
|
|
inline spherical_t spherical_from_normal3f( const Normal3f& normal ){
|
|
return spherical_t( normal.x() == 0 ? c_pi / 2 : normal.x() > 0 ? atan( normal.y() / normal.x() ) : atan( normal.y() / normal.x() ) + c_pi, acos( normal.z() ) );
|
|
}
|
|
|
|
inline Normal3f normal3f_from_spherical( const spherical_t& spherical ){
|
|
return Normal3f(
|
|
static_cast<float>( cos( spherical.longditude ) * sin( spherical.latitude ) ),
|
|
static_cast<float>( sin( spherical.longditude ) * sin( spherical.latitude ) ),
|
|
static_cast<float>( cos( spherical.latitude ) )
|
|
);
|
|
}
|
|
|
|
inline uniformspherical_t uniformspherical_from_spherical( const spherical_t& spherical ){
|
|
return uniformspherical_t( spherical.longditude * c_inv_2pi, ( cos( spherical.latitude ) + 1 ) * 0.5 );
|
|
}
|
|
|
|
inline spherical_t spherical_from_uniformspherical( const uniformspherical_t& uniformspherical ){
|
|
return spherical_t( c_2pi * uniformspherical.U, acos( ( 2 * uniformspherical.V ) - 1 ) );
|
|
}
|
|
|
|
inline uniformspherical_t uniformspherical_from_normal3f( const Normal3f& normal ){
|
|
return uniformspherical_from_spherical( spherical_from_normal3f( normal ) );
|
|
//return uniformspherical_t(atan2(normal.y / normal.x) * c_inv_2pi, (normal.z + 1) * 0.5);
|
|
}
|
|
|
|
inline Normal3f normal3f_from_uniformspherical( const uniformspherical_t& uniformspherical ){
|
|
return normal3f_from_spherical( spherical_from_uniformspherical( uniformspherical ) );
|
|
}
|
|
|
|
/// \brief Returns a single-precision \p component quantised to \p precision.
|
|
inline float float_quantise( float component, float precision ){
|
|
return float_snapped( component, precision );
|
|
}
|
|
|
|
/// \brief Returns a double-precision \p component quantised to \p precision.
|
|
inline double double_quantise( double component, double precision ){
|
|
return float_snapped( component, precision );
|
|
}
|
|
|
|
inline spherical_t spherical_quantised( const spherical_t& spherical, float snap ){
|
|
return spherical_t( double_quantise( spherical.longditude, snap ), double_quantise( spherical.latitude, snap ) );
|
|
}
|
|
|
|
inline uniformspherical_t uniformspherical_quantised( const uniformspherical_t& uniformspherical, float snap ){
|
|
return uniformspherical_t( double_quantise( uniformspherical.U, snap ), double_quantise( uniformspherical.V, snap ) );
|
|
}
|
|
|
|
/// \brief Returns a \p vertex quantised to \p precision.
|
|
inline Vertex3f vertex3f_quantised( const Vertex3f& vertex, float precision ){
|
|
return Vertex3f( float_quantise( vertex.x(), precision ), float_quantise( vertex.y(), precision ), float_quantise( vertex.z(), precision ) );
|
|
}
|
|
|
|
/// \brief Returns a \p normal quantised to a fixed precision.
|
|
inline Normal3f normal3f_quantised( const Normal3f& normal ){
|
|
return normal3f_quantised_custom( normal );
|
|
//return normal3f_from_spherical(spherical_quantised(spherical_from_normal3f(normal), snap));
|
|
//return normal3f_from_uniformspherical(uniformspherical_quantised(uniformspherical_from_normal3f(normal), snap));
|
|
// float_quantise(normal.x, snap), float_quantise(normal.y, snap), float_quantise(normal.y, snap));
|
|
}
|
|
|
|
/// \brief Returns a \p texcoord quantised to \p precision.
|
|
inline TexCoord2f texcoord2f_quantised( const TexCoord2f& texcoord, float precision ){
|
|
return TexCoord2f( float_quantise( texcoord.s(), precision ), float_quantise( texcoord.t(), precision ) );
|
|
}
|
|
|
|
/// \brief Standard vertex type for lines and points.
|
|
struct PointVertex
|
|
{
|
|
Colour4b colour;
|
|
Vertex3f vertex;
|
|
|
|
PointVertex(){
|
|
}
|
|
PointVertex( Vertex3f _vertex )
|
|
: colour( Colour4b( 255, 255, 255, 255 ) ), vertex( _vertex ){
|
|
}
|
|
PointVertex( Vertex3f _vertex, Colour4b _colour )
|
|
: colour( _colour ), vertex( _vertex ){
|
|
}
|
|
};
|
|
|
|
inline bool operator<( const PointVertex& self, const PointVertex& other ){
|
|
if ( self.vertex != other.vertex ) {
|
|
return self.vertex < other.vertex;
|
|
}
|
|
if ( self.colour != other.colour ) {
|
|
return self.colour < other.colour;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool operator==( const PointVertex& self, const PointVertex& other ){
|
|
return self.colour == other.colour && self.vertex == other.vertex;
|
|
}
|
|
|
|
inline bool operator!=( const PointVertex& self, const PointVertex& other ){
|
|
return !operator==( self, other );
|
|
}
|
|
|
|
/// \brief Standard vertex type for lit/textured meshes.
|
|
struct ArbitraryMeshVertex
|
|
{
|
|
TexCoord2f texcoord;
|
|
Normal3f normal;
|
|
Vertex3f vertex;
|
|
Normal3f tangent;
|
|
Normal3f bitangent;
|
|
Vector4 colour;
|
|
|
|
ArbitraryMeshVertex() : tangent( 0, 0, 0 ), bitangent( 0, 0, 0 ), colour( 1, 1, 1, 1 ){
|
|
}
|
|
ArbitraryMeshVertex( Vertex3f _vertex, Normal3f _normal, TexCoord2f _texcoord )
|
|
: texcoord( _texcoord ), normal( _normal ), vertex( _vertex ), tangent( 0, 0, 0 ), bitangent( 0, 0, 0 ), colour( 1, 1, 1, 1 ){
|
|
}
|
|
};
|
|
|
|
inline bool operator<( const ArbitraryMeshVertex& self, const ArbitraryMeshVertex& other ){
|
|
if ( self.texcoord != other.texcoord ) {
|
|
return self.texcoord < other.texcoord;
|
|
}
|
|
if ( self.normal != other.normal ) {
|
|
return self.normal < other.normal;
|
|
}
|
|
if ( self.vertex != other.vertex ) {
|
|
return self.vertex < other.vertex;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool operator==( const ArbitraryMeshVertex& self, const ArbitraryMeshVertex& other ){
|
|
return self.texcoord == other.texcoord && self.normal == other.normal && self.vertex == other.vertex;
|
|
}
|
|
|
|
inline bool operator!=( const ArbitraryMeshVertex& self, const ArbitraryMeshVertex& other ){
|
|
return !operator==( self, other );
|
|
}
|
|
|
|
const float c_quantise_vertex = 1.f / static_cast<float>( 1 << 3 );
|
|
|
|
/// \brief Returns \p v with vertex quantised to a fixed precision.
|
|
inline PointVertex pointvertex_quantised( const PointVertex& v ){
|
|
return PointVertex( vertex3f_quantised( v.vertex, c_quantise_vertex ), v.colour );
|
|
}
|
|
|
|
const float c_quantise_texcoord = 1.f / static_cast<float>( 1 << 8 );
|
|
|
|
/// \brief Returns \p v with vertex, normal and texcoord quantised to a fixed precision.
|
|
inline ArbitraryMeshVertex arbitrarymeshvertex_quantised( const ArbitraryMeshVertex& v ){
|
|
return ArbitraryMeshVertex( vertex3f_quantised( v.vertex, c_quantise_vertex ), normal3f_quantised( v.normal ), texcoord2f_quantised( v.texcoord, c_quantise_texcoord ) );
|
|
}
|
|
|
|
|
|
/// \brief Sets up the OpenGL colour and vertex arrays for \p array.
|
|
inline void pointvertex_gl_array( const PointVertex* array ){
|
|
glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( PointVertex ), &array->colour );
|
|
glVertexPointer( 3, GL_FLOAT, sizeof( PointVertex ), &array->vertex );
|
|
}
|
|
|
|
class RenderablePointArray : public OpenGLRenderable
|
|
{
|
|
const Array<PointVertex>& m_array;
|
|
const GLenum m_mode;
|
|
public:
|
|
RenderablePointArray( const Array<PointVertex>& array, GLenum mode )
|
|
: m_array( array ), m_mode( mode ){
|
|
}
|
|
void render( RenderStateFlags state ) const {
|
|
#define NV_DRIVER_BUG 1
|
|
#if NV_DRIVER_BUG
|
|
glColorPointer( 4, GL_UNSIGNED_BYTE, 0, 0 );
|
|
glVertexPointer( 3, GL_FLOAT, 0, 0 );
|
|
glDrawArrays( GL_TRIANGLE_FAN, 0, 0 );
|
|
#endif
|
|
pointvertex_gl_array( m_array.data() );
|
|
glDrawArrays( m_mode, 0, GLsizei( m_array.size() ) );
|
|
}
|
|
};
|
|
|
|
class RenderablePointVector : public OpenGLRenderable
|
|
{
|
|
std::vector<PointVertex> m_vector;
|
|
const GLenum m_mode;
|
|
public:
|
|
RenderablePointVector( GLenum mode )
|
|
: m_mode( mode ){
|
|
}
|
|
|
|
void render( RenderStateFlags state ) const {
|
|
pointvertex_gl_array( &m_vector.front() );
|
|
glDrawArrays( m_mode, 0, GLsizei( m_vector.size() ) );
|
|
}
|
|
|
|
std::size_t size() const {
|
|
return m_vector.size();
|
|
}
|
|
bool empty() const {
|
|
return m_vector.empty();
|
|
}
|
|
void clear(){
|
|
m_vector.clear();
|
|
}
|
|
void reserve( std::size_t size ){
|
|
m_vector.reserve( size );
|
|
}
|
|
void push_back( const PointVertex& point ){
|
|
m_vector.push_back( point );
|
|
}
|
|
};
|
|
|
|
|
|
class RenderableVertexBuffer : public OpenGLRenderable
|
|
{
|
|
const GLenum m_mode;
|
|
const VertexBuffer<PointVertex>& m_vertices;
|
|
public:
|
|
RenderableVertexBuffer( GLenum mode, const VertexBuffer<PointVertex>& vertices )
|
|
: m_mode( mode ), m_vertices( vertices ){
|
|
}
|
|
|
|
void render( RenderStateFlags state ) const {
|
|
pointvertex_gl_array( m_vertices.data() );
|
|
glDrawArrays( m_mode, 0, m_vertices.size() );
|
|
}
|
|
};
|
|
|
|
class RenderableIndexBuffer : public OpenGLRenderable
|
|
{
|
|
const GLenum m_mode;
|
|
const IndexBuffer& m_indices;
|
|
const VertexBuffer<PointVertex>& m_vertices;
|
|
public:
|
|
RenderableIndexBuffer( GLenum mode, const IndexBuffer& indices, const VertexBuffer<PointVertex>& vertices )
|
|
: m_mode( mode ), m_indices( indices ), m_vertices( vertices ){
|
|
}
|
|
|
|
void render( RenderStateFlags state ) const {
|
|
#if 1
|
|
pointvertex_gl_array( m_vertices.data() );
|
|
glDrawElements( m_mode, GLsizei( m_indices.size() ), RenderIndexTypeID, m_indices.data() );
|
|
#else
|
|
glBegin( m_mode );
|
|
if ( state & RENDER_COLOURARRAY != 0 ) {
|
|
for ( std::size_t i = 0; i < m_indices.size(); ++i )
|
|
{
|
|
glColor4ubv( &m_vertices[m_indices[i]].colour.r );
|
|
glVertex3fv( &m_vertices[m_indices[i]].vertex.x );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( std::size_t i = 0; i < m_indices.size(); ++i )
|
|
{
|
|
glVertex3fv( &m_vertices[m_indices[i]].vertex.x );
|
|
}
|
|
}
|
|
glEnd();
|
|
#endif
|
|
}
|
|
};
|
|
|
|
|
|
class RemapXYZ
|
|
{
|
|
public:
|
|
static void set( Vertex3f& vertex, float x, float y, float z ){
|
|
vertex.x() = x;
|
|
vertex.y() = y;
|
|
vertex.z() = z;
|
|
}
|
|
};
|
|
|
|
class RemapYZX
|
|
{
|
|
public:
|
|
static void set( Vertex3f& vertex, float x, float y, float z ){
|
|
vertex.x() = z;
|
|
vertex.y() = x;
|
|
vertex.z() = y;
|
|
}
|
|
};
|
|
|
|
class RemapZXY
|
|
{
|
|
public:
|
|
static void set( Vertex3f& vertex, float x, float y, float z ){
|
|
vertex.x() = y;
|
|
vertex.y() = z;
|
|
vertex.z() = x;
|
|
}
|
|
};
|
|
|
|
template<typename remap_policy>
|
|
inline void draw_circle( const std::size_t segments, const float radius, PointVertex* vertices, remap_policy remap ){
|
|
const double increment = c_pi / double(segments << 2);
|
|
|
|
std::size_t count = 0;
|
|
float x = radius;
|
|
float y = 0;
|
|
while ( count < segments )
|
|
{
|
|
PointVertex* i = vertices + count;
|
|
PointVertex* j = vertices + ( ( segments << 1 ) - ( count + 1 ) );
|
|
|
|
PointVertex* k = i + ( segments << 1 );
|
|
PointVertex* l = j + ( segments << 1 );
|
|
|
|
PointVertex* m = i + ( segments << 2 );
|
|
PointVertex* n = j + ( segments << 2 );
|
|
PointVertex* o = k + ( segments << 2 );
|
|
PointVertex* p = l + ( segments << 2 );
|
|
|
|
remap_policy::set( i->vertex, x,-y, 0 );
|
|
remap_policy::set( k->vertex,-y,-x, 0 );
|
|
remap_policy::set( m->vertex,-x, y, 0 );
|
|
remap_policy::set( o->vertex, y, x, 0 );
|
|
|
|
++count;
|
|
|
|
{
|
|
const double theta = increment * count;
|
|
x = static_cast<float>( radius * cos( theta ) );
|
|
y = static_cast<float>( radius * sin( theta ) );
|
|
}
|
|
|
|
remap_policy::set( j->vertex, y,-x, 0 );
|
|
remap_policy::set( l->vertex,-x,-y, 0 );
|
|
remap_policy::set( n->vertex,-y, x, 0 );
|
|
remap_policy::set( p->vertex, x, y, 0 );
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
class PointVertexArrayIterator
|
|
{
|
|
PointVertex* m_point;
|
|
public:
|
|
PointVertexArrayIterator( PointVertex* point )
|
|
: m_point( point ){
|
|
}
|
|
PointVertexArrayIterator& operator++(){
|
|
++m_point;
|
|
return *this;
|
|
}
|
|
PointVertexArrayIterator operator++( int ){
|
|
PointVertexArrayIterator tmp( *this );
|
|
++m_point;
|
|
return tmp;
|
|
}
|
|
Vertex3f& operator*(){
|
|
return m_point.vertex;
|
|
}
|
|
Vertex3f* operator->(){
|
|
return &( operator*() );
|
|
}
|
|
}
|
|
|
|
template<typename remap_policy, typename iterator_type
|
|
inline void draw_circle( const std::size_t segments, const float radius, iterator_type start, remap_policy remap ){
|
|
const float increment = c_pi / (double)( segments << 2 );
|
|
|
|
std::size_t count = 0;
|
|
iterator_type pxpy( start );
|
|
iterator_type pypx( pxpy + ( segments << 1 ) );
|
|
iterator_type pynx( pxpy + ( segments << 1 ) );
|
|
iterator_type nxpy( pypx + ( segments << 1 ) );
|
|
iterator_type nxny( pypx + ( segments << 1 ) );
|
|
iterator_type nynx( nxpy + ( segments << 1 ) );
|
|
iterator_type nypx( nxpy + ( segments << 1 ) );
|
|
iterator_type pxny( start );
|
|
while ( count < segments )
|
|
{
|
|
const float theta = increment * count;
|
|
const float x = radius * cos( theta );
|
|
const float y = radius * sin( theta );
|
|
|
|
remap_policy::set( ( *pxpy ), x, y, 0 );
|
|
remap_policy::set( ( *pxny ), x,-y, 0 );
|
|
remap_policy::set( ( *nxpy ),-x, y, 0 );
|
|
remap_policy::set( ( *nxny ),-x,-y, 0 );
|
|
|
|
remap_policy::set( ( *pypx ), y, x, 0 );
|
|
remap_policy::set( ( *pynx ), y,-x, 0 );
|
|
remap_policy::set( ( *nypx ),-y, x, 0 );
|
|
remap_policy::set( ( *nynx ),-y,-x, 0 );
|
|
}
|
|
}
|
|
|
|
template<typename remap_policy, typename iterator_type
|
|
inline void draw_semicircle( const std::size_t segments, const float radius, iterator_type start, remap_policy remap )
|
|
{
|
|
const float increment = c_pi / (double)( segments << 2 );
|
|
|
|
std::size_t count = 0;
|
|
iterator_type pxpy( start );
|
|
iterator_type pypx( pxpy + ( segments << 1 ) );
|
|
iterator_type pynx( pxpy + ( segments << 1 ) );
|
|
iterator_type nxpy( pypx + ( segments << 1 ) );
|
|
iterator_type nxny( pypx + ( segments << 1 ) );
|
|
iterator_type nynx( nxpy + ( segments << 1 ) );
|
|
iterator_type nypx( nxpy + ( segments << 1 ) );
|
|
iterator_type pxny( start );
|
|
while ( count < segments )
|
|
{
|
|
const float theta = increment * count;
|
|
const float x = radius * cos( theta );
|
|
const float y = radius * sin( theta );
|
|
|
|
remap_policy::set( ( *pxpy ), x, y, 0 );
|
|
remap_policy::set( ( *pxny ), x,-y, 0 );
|
|
remap_policy::set( ( *nxpy ),-x, y, 0 );
|
|
remap_policy::set( ( *nxny ),-x,-y, 0 );
|
|
|
|
//remap_policy::set((*pypx), y, x, 0);
|
|
//remap_policy::set((*pynx), y,-x, 0);
|
|
//remap_policy::set((*nypx),-y, x, 0);
|
|
//remap_policy::set((*nynx),-y,-x, 0);
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
inline void draw_quad( const float radius, PointVertex* quad ){
|
|
( *quad++ ).vertex = Vertex3f( -radius, radius, 0 );
|
|
( *quad++ ).vertex = Vertex3f( radius, radius, 0 );
|
|
( *quad++ ).vertex = Vertex3f( radius, -radius, 0 );
|
|
( *quad++ ).vertex = Vertex3f( -radius, -radius, 0 );
|
|
}
|
|
|
|
inline void draw_cube( const float radius, PointVertex* cube ){
|
|
( *cube++ ).vertex = Vertex3f( -radius, -radius, -radius );
|
|
( *cube++ ).vertex = Vertex3f( radius, -radius, -radius );
|
|
( *cube++ ).vertex = Vertex3f( -radius, radius, -radius );
|
|
( *cube++ ).vertex = Vertex3f( radius, radius, -radius );
|
|
( *cube++ ).vertex = Vertex3f( -radius, -radius, radius );
|
|
( *cube++ ).vertex = Vertex3f( radius, -radius, radius );
|
|
( *cube++ ).vertex = Vertex3f( -radius, radius, radius );
|
|
( *cube++ ).vertex = Vertex3f( radius, radius, radius );
|
|
}
|
|
|
|
|
|
/// \brief Calculates the tangent vectors for a triangle \p a, \p b, \p c and stores the tangent in \p s and the bitangent in \p t.
|
|
inline void ArbitraryMeshTriangle_calcTangents( const ArbitraryMeshVertex& a, const ArbitraryMeshVertex& b, const ArbitraryMeshVertex& c, Vector3& s, Vector3& t ){
|
|
s = Vector3( 0, 0, 0 );
|
|
t = Vector3( 0, 0, 0 );
|
|
{
|
|
Vector3 cross(
|
|
vector3_cross(
|
|
vector3_subtracted(
|
|
Vector3( b.vertex.x(), b.texcoord.s(), b.texcoord.t() ),
|
|
Vector3( a.vertex.x(), a.texcoord.s(), a.texcoord.t() )
|
|
),
|
|
vector3_subtracted(
|
|
Vector3( c.vertex.x(), c.texcoord.s(), c.texcoord.t() ),
|
|
Vector3( a.vertex.x(), a.texcoord.s(), a.texcoord.t() )
|
|
)
|
|
)
|
|
);
|
|
|
|
if ( fabs( cross.x() ) > 0.000001f ) {
|
|
s.x() = -cross.y() / cross.x();
|
|
}
|
|
|
|
if ( fabs( cross.x() ) > 0.000001f ) {
|
|
t.x() = -cross.z() / cross.x();
|
|
}
|
|
}
|
|
|
|
{
|
|
Vector3 cross(
|
|
vector3_cross(
|
|
vector3_subtracted(
|
|
Vector3( b.vertex.y(), b.texcoord.s(), b.texcoord.t() ),
|
|
Vector3( a.vertex.y(), a.texcoord.s(), a.texcoord.t() )
|
|
),
|
|
vector3_subtracted(
|
|
Vector3( c.vertex.y(), c.texcoord.s(), c.texcoord.t() ),
|
|
Vector3( a.vertex.y(), a.texcoord.s(), a.texcoord.t() )
|
|
)
|
|
)
|
|
);
|
|
|
|
if ( fabs( cross.x() ) > 0.000001f ) {
|
|
s.y() = -cross.y() / cross.x();
|
|
}
|
|
|
|
if ( fabs( cross.x() ) > 0.000001f ) {
|
|
t.y() = -cross.z() / cross.x();
|
|
}
|
|
}
|
|
|
|
{
|
|
Vector3 cross(
|
|
vector3_cross(
|
|
vector3_subtracted(
|
|
Vector3( b.vertex.z(), b.texcoord.s(), b.texcoord.t() ),
|
|
Vector3( a.vertex.z(), a.texcoord.s(), a.texcoord.t() )
|
|
),
|
|
vector3_subtracted(
|
|
Vector3( c.vertex.z(), c.texcoord.s(), c.texcoord.t() ),
|
|
Vector3( a.vertex.z(), a.texcoord.s(), a.texcoord.t() )
|
|
)
|
|
)
|
|
);
|
|
|
|
if ( fabs( cross.x() ) > 0.000001f ) {
|
|
s.z() = -cross.y() / cross.x();
|
|
}
|
|
|
|
if ( fabs( cross.x() ) > 0.000001f ) {
|
|
t.z() = -cross.z() / cross.x();
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void ArbitraryMeshTriangle_sumTangents( ArbitraryMeshVertex& a, ArbitraryMeshVertex& b, ArbitraryMeshVertex& c ){
|
|
Vector3 s, t;
|
|
|
|
ArbitraryMeshTriangle_calcTangents( a, b, c, s, t );
|
|
|
|
reinterpret_cast<Vector3&>( a.tangent ) += s;
|
|
reinterpret_cast<Vector3&>( b.tangent ) += s;
|
|
reinterpret_cast<Vector3&>( c.tangent ) += s;
|
|
|
|
reinterpret_cast<Vector3&>( a.bitangent ) += t;
|
|
reinterpret_cast<Vector3&>( b.bitangent ) += t;
|
|
reinterpret_cast<Vector3&>( c.bitangent ) += t;
|
|
}
|
|
|
|
|
|
#endif
|