Jump to content
  • Advertisement
Sign in to follow this  
Eckos

Matrix question

This topic is 3810 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Okay, I have my own Matrix class which I will be using with OpenGL. (not opengl question) I have scale and translation.
//! Scale the matrix
/*!
 \param _x X axis
 \param _y Y axis
 \param _z Z axis
 \return A Matrix 4x4
 */
MAT_INLINE Mat4& scale(const _type _x, const _type _y, const _type _z)
{
	//*this = identity();
	mat[0] = _x;
	mat[5] = _y;
	mat[10] = _z;
	return *this;
}


Do I need to set the Matrix Identity? And if I don't have to unless I call it manually when the time is needed. Do I take for example mat[0] and multiply it again the current one like mat[0] *= _x;? Same with translate and rotation? [Edited by - Eckos on June 14, 2008 3:48:49 AM]

Share this post


Link to post
Share on other sites
Advertisement
All of the matrix elements will need to be initialized at some point. There are many places this could be done - the constructor, an 'identity' function, etc. - but typically, you would do this as part of the transform function itself (scale() in your example).

I'm not sure what you're getting at with multiplying individual matrix elements by the scale factors, but typically you combine transforms by multiplying several matrices together (where each matrix represents a single transform).

Also, I would strongly suggest wrapping element access so that you can use 2-d indexing.

Share this post


Link to post
Share on other sites
Initialization:
you need to set
mat[15]=1
and set all other elements to 0

Quote:
Original post by jyk

Also, I would strongly suggest wrapping element access so that you can use 2-d indexing.

Seconded.
You can make .at(i,j) accessor.

Share this post


Link to post
Share on other sites
Quote:
Original post by Dmytry
Initialization:
you need to set
mat[15]=1
and set all other elements to 0


I would expect a matrix to be initialized to the identity matrix, not the zero matrix. But, that's just me.

Share this post


Link to post
Share on other sites
Okay I uncomment *this = identity(); in scale and in translation.

if I perform

Mat4d m = Mat4d(....);

m.rotateXAxis(180.0);
m.scale(3.0, 3.0, 3.0);
m.translate(-4.5, 3.4, 1.0);

I wanna perform it that way. But calling scale resets the matrix and makes rotateXAxis useless, and when translate gets called it overrides the scale. And that's why I was ponding if I needed to do that. But even if I comment it it will still be overridden the current matrix.

I want try and make it perform that way. And I that was why I was asking if I do mat[0] *= _x;. Since it multiplies the current matrix value with _x then saves it back into it, and returns with *this.

Here's the full matrix class.


#ifndef MATRIX_H_
#define MATRIX_H_

#include <iostream>
#include <tr1/array>
#include <cassert>
#include <algorithm>
#include "Vector.h"
#include "Math.h"

#define MAT_INLINE __attribute__((always_inline))

template<typename _type> class Vec2;
template<typename _type> class Vec3;
template<typename _type> class Vec4;
template<typename _type> class Math;
template<typename _type>
//! A Matrix 4x4 math class
class Mat4
{
public:
//! Anonymous union
union
{
//! Anonymous struct
struct
{
_type m11, m12, m13, m14,
m21, m22, m23, m24,
m31, m32, m33, m34,
m41, m42, m43, m44;
};
//! Matrix array
/*!
Holds the matrix data in this array for easy access. Sharing data with the m11 ... m44 variables.
*/

std::tr1::array<_type, 16> mat;
};

//! Explicit default constructor
explicit Mat4()
{
// Set to identity
*this = identity();
}

//! Explicit constructor
/*!
Follows the OpenGL matrix style.
\f$ \left[\begin{array}{cccc}
m11 & m21 & m31 & m41\ m12 & m22 & m32 & m42\ m13 & m23 & m33 & m43\ m14 & m24 & m34 & m44\end{array} \right] \f$
\param m11 mat[0]
\param m21 mat[4]
\param m31 mat[8]
\param m41 mat[12]
\param m12 mat[1]
\param m22 mat[5]
\param m32 mat[9]
\param m42 mat[13]
\param m13 mat[2]
\param m23 mat[6]
\param m33 mat[10]
\param m34 mat[14]
\param m14 mat[3]
\param m24 mat[7]
\param m34 mat[11]
\param m44 mat[15]
*/

explicit Mat4(_type m11, _type m21, _type m31, _type m41,
_type m12, _type m22, _type m32, _type m42,
_type m13, _type m23, _type m33, _type m43,
_type m14, _type m24, _type m34, _type m44)
{
mat[0] = m11; mat[4] = m21; mat[8] = m31; mat[12] = m41;
mat[1] = m12; mat[5] = m22; mat[9] = m32; mat[13] = m42;
mat[2] = m13; mat[6] = m23; mat[10] = m33; mat[14] = m43;
mat[3] = m14; mat[7] = m24; mat[11] = m34; mat[15] = m44;
}

//! Copy constructor
/*!
\param m A Matrix 4x4
*/

Mat4(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = m.mat;
}

MAT_INLINE Mat4& operator=(const _type* t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = t;
return *this;
}

//! Overloaded operator=
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator=(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = m.mat;
return *this;
}

//! Overloaded operator=
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator=(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = t;
return *this;
}

//! Overloaded operator+
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator+(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = mat + m.mat;
return *this;
}

//! Overloaded operator+
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator+(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = mat + t;
return *this;
}

//! Overloaded operator+=
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator+=(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat += m.mat;
return *this;
}

//! Overloaded operator+=
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator+=(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat += t;
return *this;
}

//! Overloaded operator-
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator-(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = mat - m.mat;
return *this;
}

//! Overloaded operator-
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator-(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = mat - t;
return *this;
}

//! Overloaded operator-=
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator-=(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat -= m.mat;
return *this;
}

//! Overloaded operator-=
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator-=(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat -= t;
return *this;
}

//! Overloaded operator*
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4 operator*(const Mat4& m)
{
return Mat4(mat[0] * m.mat[0] + mat[4] * m.mat[1] + mat[8] * m.mat[2] + mat[12] * m.mat[3],
mat[1] * m.mat[0] + mat[5] * m.mat[1] + mat[9] * m.mat[2] + mat[13] * m.mat[3],
mat[2] * m.mat[0] + mat[6] * m.mat[1] + mat[10] * m.mat[2] + mat[14] * m.mat[3],
mat[3] * m.mat[0] + mat[7] * m.mat[1] + mat[11] * m.mat[2] + mat[15] * m.mat[3],
mat[0] * m.mat[4] + mat[4] * m.mat[5] + mat[8] * m.mat[6] + mat[12] * m.mat[7],
mat[1] * m.mat[4] + mat[5] * m.mat[5] + mat[9] * m.mat[6] + mat[13] * m.mat[7],
mat[2] * m.mat[4] + mat[6] * m.mat[5] + mat[10] * m.mat[6] + mat[14] * m.mat[7],
mat[3] * m.mat[4] + mat[7] * m.mat[5] + mat[11] * m.mat[6] + mat[15] * m.mat[7],
mat[0] * m.mat[8] + mat[4] * m.mat[9] + mat[8] * m.mat[10] + mat[12] * m.mat[11],
mat[1] * m.mat[8] + mat[5] * m.mat[9] + mat[9] * m.mat[10] + mat[13] * m.mat[11],
mat[2] * m.mat[8] + mat[6] * m.mat[9] + mat[10] * m.mat[10] + mat[14] * m.mat[11],
mat[3] * m.mat[8] + mat[7] * m.mat[9] + mat[11] * m.mat[10] + mat[15] * m.mat[11],
mat[0] * m.mat[12] + mat[4] * m.mat[13] + mat[8] * m.mat[14] + mat[12] * m.mat[15],
mat[1] * m.mat[12] + mat[5] * m.mat[13] + mat[9] * m.mat[14] + mat[13] * m.mat[15],
mat[2] * m.mat[12] + mat[6] * m.mat[13] + mat[10] * m.mat[14] + mat[14] * m.mat[15],
mat[3] * m.mat[12] + mat[7] * m.mat[13] + mat[11] * m.mat[14] + mat[15] * m.mat[15]);
}

//! Overloaded operator*
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator*(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat = mat * t;
return *this;
}

//! Overloaded operator*
/*!
\param v A Vector 3d
\return A Vector 3d
*/

MAT_INLINE Vec3<_type> operator*(const Vec3<_type>& v)
{
Vec3<_type> t;
t.x = v.x * mat[0] + v.y * mat[4] + v.z * mat[8] + mat[12];
t.y = v.x * mat[1] + v.y * mat[5] + v.z * mat[9] + mat[13];
t.z = v.y * mat[2] + v.y * mat[6] + v.z * mat[10] + mat[14];
return t;
}

//! Overloaded operator*
/*!
\param v A Vector 4d
\return A Vector 4d
*/

MAT_INLINE Vec4<_type> operator*(const Vec4<_type>& v)
{
Vec4<_type> t;
t.x = v.x * mat[0] + v.y * mat[4] + v.z * mat[8] + v.w * mat[12];
t.y = v.x * mat[1] + v.y * mat[5] + v.z * mat[9] + v.w * mat[13];
t.z = v.x * mat[2] + v.y * mat[6] + v.z * mat[10] + v.w * mat[14];
t.w = v.x * mat[3] + v.y * mat[7] + v.z * mat[11] + v.w * mat[15];
return t;
}

//! Overloaded operator*=
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator*=(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat *= m.mat;
return *this;
}

//! Overloaded operator*=
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator*=(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat *= t;
return *this;
}

//! Overloaded operator/
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4 operator/(const Mat4& m)
{
return Mat4(mat[0] / m.mat[0] + mat[4] / m.mat[1] + mat[8] / m.mat[2] + mat[12] / m.mat[3],
mat[1] / m.mat[0] + mat[5] / m.mat[1] + mat[9] / m.mat[2] + mat[13] / m.mat[3],
mat[2] / m.mat[0] + mat[6] / m.mat[1] + mat[10] / m.mat[2] + mat[14] / m.mat[3],
mat[3] / m.mat[0] + mat[7] / m.mat[1] + mat[11] / m.mat[2] + mat[15] / m.mat[3],
mat[0] / m.mat[4] + mat[4] / m.mat[5] + mat[8] / m.mat[6] + mat[12] / m.mat[7],
mat[1] / m.mat[4] + mat[5] / m.mat[5] + mat[9] / m.mat[6] + mat[13] / m.mat[7],
mat[2] / m.mat[4] + mat[6] / m.mat[5] + mat[10] / m.mat[6] + mat[14] / m.mat[7],
mat[3] / m.mat[4] + mat[7] / m.mat[5] + mat[11] / m.mat[6] + mat[15] / m.mat[7],
mat[0] / m.mat[8] + mat[4] / m.mat[9] + mat[8] / m.mat[10] + mat[12] / m.mat[11],
mat[1] / m.mat[8] + mat[5] / m.mat[9] + mat[9] / m.mat[10] + mat[13] / m.mat[11],
mat[2] / m.mat[8] + mat[6] / m.mat[9] + mat[10] / m.mat[10] + mat[14] / m.mat[11],
mat[3] / m.mat[8] + mat[7] / m.mat[9] + mat[11] / m.mat[10] + mat[15] / m.mat[11],
mat[0] / m.mat[12] + mat[4] / m.mat[13] + mat[8] / m.mat[14] + mat[12] / m.mat[15],
mat[1] / m.mat[12] + mat[5] / m.mat[13] + mat[9] / m.mat[14] + mat[13] / m.mat[15],
mat[2] / m.mat[12] + mat[6] / m.mat[13] + mat[10] / m.mat[14] + mat[14] / m.mat[15],
mat[3] / m.mat[12] + mat[7] / m.mat[13] + mat[11] / m.mat[14] + mat[15] / m.mat[15]);
}

//! Overloaded operator/
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator/(const _type t)
{
_type tmp;
if(t == 0.0)
{
t = 1.0;
}

tmp = 1.0 / t;

for(unsigned i = 0; i < mat.size(); ++i)
mat = mat * tmp;
return *this;
}

//! Overloaded operator/=
/*!
\param m A Matrix 4x4
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator/=(const Mat4& m)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat /= m.mat;
return *this;
}

//! Overloaded operator/=
/*!
\param t A Matrix scalar
\return A Matrix 4x4
*/

MAT_INLINE Mat4& operator/=(const _type t)
{
for(unsigned i = 0; i < mat.size(); ++i)
mat /= t;
return *this;
}

//! Sets the matrix identity
/*!
\f$ \left[\begin{array}{cccc}
1.0 & 0.0 & 0.0 & 0.0\ 0.0 & 1.0 & 0.0 & 0.0\ 0.0 & 0.0 & 1.0 & 0.0\ 0.0 & 0.0 & 0.0 & 1.0\end{array} \right] \f$
\return A Matrix 4x4
*/

MAT_INLINE static Mat4 identity()
{
return Mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
}

//! Set the matrix zero
/*!
\f$ \left[\begin{array}{cccc}
0.0 & 0.0 & 0.0 & 0.0\ 0.0 & 0.0 & 0.0 & 0.0\ 0.0 & 0.0 & 0.0 & 0.0\ 0.0 & 0.0 & 0.0 & 0.0\end{array}\right] \f$
\return A Matrix 4x4
*/

MAT_INLINE static Mat4 zero()
{
return Mat4(0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0);
}

//! Transpose a Matrix by swapping its columns and rows
MAT_INLINE void transpose()
{
std::swap(mat[1], mat[4]);
std::swap(mat[2], mat[8]);
std::swap(mat[3], mat[12]);
std::swap(mat[6], mat[9]);
std::swap(mat[7], mat[13]);
std::swap(mat[11], mat[14]);
}

//! Rotate the matrix around the X axis
/*!
\param angle Angle of degrees which to rotate
\return A Matrix 4x4
*/

MAT_INLINE Mat4& rotateXAxis(_type angle)
{
_type cos = Math<_type>::Cosine(angle);
_type sin = Math<_type>::Sine(angle);

mat[5] = cos;
mat[6] = -sin;
mat[9] = sin;
mat[10] = cos;
return *this;
}

//! Rotate the matrix around the Y axis
/*!
\param angle Angle of degrees which to rotate
\return A Matrix 4x4
*/

MAT_INLINE Mat4& rotateYAxis(_type angle)
{
_type cos = Math<_type>::Cosine(angle);
_type sin = Math<_type>::Sine(angle);

mat[0] = cos;
mat[2] = sin;
mat[8] = -sin;
mat[10] = cos;
return *this;
}

//! Rotate the matrix around the Z axis
/*!
\param angle Angle of degrees which to rotate
\return A Matrix 4x4
*/

MAT_INLINE Mat4& rotateZAxis(_type angle)
{
_type cos = Math<_type>::Cosine(angle);
_type sin = Math<_type>::Sine(angle);

mat[0] = cos;
mat[1] = -sin;
mat[4] = sin;
mat[5] = cos;
return *this;
}

//! Translate the matrix across the axis'
/*!
\param _x X axis
\param _y Y axis
\param _z Z axis
\return A Matrix 4x4
*/

MAT_INLINE Mat4& translate(const _type _x, const _type _y, const _type _z)
{
//*this = identity();
mat[12] *= _x;
mat[13] *= _y;
mat[14] *= _z;
return *this;
}

//! Translate the matrix across the axis'
/*!
\param v A Vertex 3d to translate
\return A Matrix 4x4
*/

MAT_INLINE Mat4& translate(const Vec3<_type>& v)
{
translate(v.x, v.y, v.z);
return *this;
}

//! Scale the matrix
/*!
\param _x X axis
\param _y Y axis
\param _z Z axis
\return A Matrix 4x4
*/

MAT_INLINE Mat4& scale(const _type _x, const _type _y, const _type _z)
{
//*this = identity();
mat[0] = _x;
mat[5] = _y;
mat[10] = _z;
return *this;
}

//! Scale the matrix
/*!
\param v A Vertex 3d
\return A Matrix 4x4
*/

MAT_INLINE Mat4& scale(const Vec3<_type> v)
{
scale(v.x, v.y, v.z);
return *this;
}

MAT_INLINE void orthoProjection(const Vec2<_type>& leftRight,
const Vec2<_type>& bottomTop,
const Vec2<_type>& zNearFar)
{
*this = zero();
mat[0] = 2.0 / (leftRight.y - leftRight.x);
mat[5] = 2.0 / (bottomTop.y - bottomTop.x);
mat[10] = -2.0 / (zNearFar.y - zNearFar.x);
mat[12] = -((leftRight.y + leftRight.x) / (leftRight.y - leftRight.x));
mat[13] = -((bottomTop.y + bottomTop.x) / (bottomTop.y - bottomTop.x));
mat[14] = -((zNearFar.y + zNearFar.x) / (zNearFar.y - zNearFar.x));
mat[15] = 1.0;
}

//! Print the Matrix for debugging purposes
MAT_INLINE void printMat4()
{
std::cout << "[ " << m11 << ", " << m21 << ", " << m31 << ", " << m41 << " ]" << std::endl;
std::cout << "[ " << m12 << ", " << m22 << ", " << m32 << ", " << m42 << " ]" << std::endl;
std::cout << "[ " << m13 << ", " << m23 << ", " << m33 << ", " << m43 << " ]" << std::endl;
std::cout << "[ " << m14 << ", " << m24 << ", " << m34 << ", " << m44 << " ]\n" << std::endl;
}
};

typedef Mat4<double> Mat4d;
typedef Mat4<float> Mat4f;

#endif /*MATRIX_H_*/




[Edited by - Eckos on June 14, 2008 3:08:26 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Driv3MeFar
Quote:
Original post by Dmytry
Initialization:
you need to set
mat[15]=1
and set all other elements to 0


I would expect a matrix to be initialized to the identity matrix, not the zero matrix. But, that's just me.
I think Dmytry was referring to the OP's posted example, where the first three diagonal elements have already been set to the scale factors (leaving element 4,4 as the only element that needs to be set to '1').

Share this post


Link to post
Share on other sites
There are a number of things wrong with your implementation. I might be able to address them in another post, but if I don't I'm sure someone else will. Meanwhile:
Quote:
if I perform

Mat4d m = Mat4d(....);

m.rotateXAxis(180.0);
m.scale(3.0, 3.0, 3.0);
m.translate(-4.5, 3.4, 1.0);

I wanna perform it that way. But calling scale resets the matrix and makes rotateXAxis useless, and when translate gets called it overrides the scale. And that's why I was ponding if I needed to do that. But even if I comment it it will still be overridden the current matrix.

I want try and make it perform that way. And I that was why I was asking if I do mat[0] *= _x;. Since it multiplies the current matrix value with _x then saves it back into it, and returns with *this.
Again, matrix transform functions don't usually modify the existing matrix as in your example (at least I haven't observed this to be the case). Instead, you create each transform matrix individually, and then multiply them together, e.g.:
matrix s, r, t;
s.scale(...);
r.rotate_x(...);
t.translate(...);
matrix m = t * r * s;

// Or, if you have functions available that create and return transform matrices:
matrix m = translate(...) * rotate_x(...) * scale(...);
Also, you generally don't combine transforms by simply multiplying against specific matrix elements (e.g. m[5] *= scale_y). In order to understand why this is so you need to have some knowledge of linear algebra, but meanwhile, just understand that the typical way to combine two transforms is to multiply together the matrices that represent them.

And again, you should really switch over to 2-d indexing.

Share this post


Link to post
Share on other sites
Quote:
Original post by jyk
There are a number of things wrong with your implementation. I might be able to address them in another post, but if I don't I'm sure someone else will. Meanwhile:
Quote:
if I perform

Mat4d m = Mat4d(....);

m.rotateXAxis(180.0);
m.scale(3.0, 3.0, 3.0);
m.translate(-4.5, 3.4, 1.0);

I wanna perform it that way. But calling scale resets the matrix and makes rotateXAxis useless, and when translate gets called it overrides the scale. And that's why I was ponding if I needed to do that. But even if I comment it it will still be overridden the current matrix.

I want try and make it perform that way. And I that was why I was asking if I do mat[0] *= _x;. Since it multiplies the current matrix value with _x then saves it back into it, and returns with *this.
Again, matrix transform functions don't usually modify the existing matrix as in your example (at least I haven't observed this to be the case). Instead, you create each transform matrix individually, and then multiply them together, e.g.:
matrix s, r, t;
s.scale(...);
r.rotate_x(...);
t.translate(...);
matrix m = t * r * s;

// Or, if you have functions available that create and return transform matrices:
matrix m = translate(...) * rotate_x(...) * scale(...);
Also, you generally don't combine transforms by simply multiplying against specific matrix elements (e.g. m[5] *= scale_y). In order to understand why this is so you need to have some knowledge of linear algebra, but meanwhile, just understand that the typical way to combine two transforms is to multiply together the matrices that represent them.

And again, you should really switch over to 2-d indexing.


Yeah I was thinking that it didn't blend wit the existing matrix but that was 10% correct in my head. Yeah I'm learning Linear Algebra with 3D Math Primer.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!