Jump to content
  • Advertisement
Sign in to follow this  
_DarkWIng_

[.net] Unions, arrays and safe structs (C# )

This topic is 5080 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

Is here any way to have something like this in C#:
struct Matrix4 {
	union {	
		float m[ 16 ];
		float e[ 4 ][ 4 ];
		struct {
			float m11; float m12; float m13; float m14;
			float m21; float m22; float m23; float m24;
			float m31; float m32; float m33; float m34;
			float m41; float m42; float m43; float m44;			
		};		
	};
        // ...all the rest...
}

I would just like to access elements of a matrix by index or by name and this worked like a charm in C++. I found a way in C# that works with classes or with unsafe structs ( using [StructLayout(LayoutKind.Explicit)] & [System.Runtime.InteropServices.FieldOffset( 0 )] ) but I would perfer using safe structs. ps1: I'm using C# 2005 and .NET 2.0 ps2: Does anyone have a nice 4x4 matrix class so I could look at it. I've already found the one form Sharp engine.

Share this post


Link to post
Share on other sites
Advertisement
Here's a simple class for a square matrix of arbitrary size, with row-major addressing. To get column-major addressing, just switch the placement of row and col in the indexer. I leave the matrix operations you want to implement as an exercise to the reader.


[CLSCompliant(true)]
public struct TestMatrix
{
private int m_MatrixSize;

public float[] Values;

public float this[int row, int col]
{
get
{
return this.Values[row * m_MatrixSize + col];
}
set
{
this.Values[row * m_MatrixSize + col] = value;
}
}

public float this[int entry]
{
get
{
return this.Values[entry];
}
set
{
this.Values[entry] = value;
}
}

public TestMatrix(int order)
{
this.m_MatrixSize = order;
this.Values = new float[m_MatrixSize * m_MatrixSize];
}

// Include desired matrix operations here.
}



Because unions don't exist in C#, you probably can't do exactly what you want. But since a union is just a different way of looking at exactly the same data, and since an indexer does the same thing, indexers make a nice substitute for unions in safe code.

HTH,

Share this post


Link to post
Share on other sites
Here's my matrix class. Please note that it is currently being re-optimized, so alot of the math functions aren't as fast as they could be.


// Matrix.cs
// Halfling Game Engine
// Walt Destler

using System;
using System.Drawing;
using FlyingFuzz.Halfling.Graphics;
using M = System.Math;

namespace FlyingFuzz.Halfling.Geometry
{
/// <summary>
/// Represents a 4x4 transformation matrix.
/// </summary>
public struct Matrix : ITransform
{
#region Public Static Constants

public static readonly Matrix Empty = new Matrix();
public static readonly Matrix Identity = new Matrix(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);

#endregion
#region Public Fields

public float XX, XY, XZ, XW;
public float YX, YY, YZ, YW;
public float ZX, ZY, ZZ, ZW;
public float WX, WY, WZ, WW;

#endregion
#region Constructors

/// <summary>
/// Creates a new Matrix with the specified initial values.
/// </summary>
public Matrix(
float XX, float XY, float XZ, float XW,
float YX, float YY, float YZ, float YW,
float ZX, float ZY, float ZZ, float ZW,
float WX, float WY, float WZ, float WW)
{
this.XX = XX;
this.XY = XY;
this.XZ = XZ;
this.XW = XW;

this.YX = YX;
this.YY = YY;
this.YZ = YZ;
this.YW = YW;

this.ZX = ZX;
this.ZY = ZY;
this.ZZ = ZZ;
this.ZW = ZW;

this.WX = WX;
this.WY = WY;
this.WZ = WZ;
this.WW = WW;
}

/// <summary>
/// Creates a new Matrix whose values are initialized to those in the specified array.
/// </summary>
/// <param name="array"></param>
public Matrix(float[,] array)
{
this.XX = array[0,0];
this.XY = array[0,1];
this.XZ = array[0,2];
this.XW = array[0,3];

this.YX = array[1,0];
this.YY = array[1,1];
this.YZ = array[1,2];
this.YW = array[1,3];

this.ZX = array[2,0];
this.ZY = array[2,1];
this.ZZ = array[2,2];
this.ZW = array[2,3];

this.WX = array[3,0];
this.WY = array[3,1];
this.WZ = array[3,2];
this.WW = array[3,3];
}

#endregion
#region Public Methods

/// <summary>
/// Transforms the passed Vector3D using this Matrix.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public Vector3D TransformVector(Vector3D v)
{
Vector3D ret = new Vector3D();

ret.X = XX*v.X + XY*v.Y + XZ*v.Z + XW*1;
ret.Y = YX*v.X + YY*v.Y + YZ*v.Z + YW*1;
ret.Z = ZX*v.X + ZY*v.Y + ZZ*v.Z + ZW*1;
float w = WX*v.X + WY*v.Y + WZ*v.Z + WW*1;
ret.X /= w;
ret.Y /= w;

return ret;
}

/// <summary>
/// Untransforms the passed Vector3D using this Matrix.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public Vector3D UntransformVector(Vector3D v)
{
return this.Invert().TransformVector(v);
}

/// <summary>
/// Converts this Matrix to a 4x4 array of floats.
/// </summary>
/// <returns></returns>
public float[,] ToArray()
{
return new float[4,4]{
{XX, XY, XZ, XW},
{YX, YY, YZ, YW},
{ZX, ZY, ZZ, ZW},
{WX, WY, WZ, WW}};
}

/// <summary>
/// Appends the passed Transform to this Matrix.
/// </summary>
/// <param name="t"></param>
/// <returns>The resulting Matrix.</returns>
public Matrix AppendTransform(ITransform t)
{
this = t.GetMatrix() * this;
return this;
}

/// <summary>
/// Pushes the passed Transform on top of this Matrix.
/// </summary>
/// <param name="t"></param>
/// <returns>The resulting Matrix.</returns>
public Matrix PushTransform(ITransform t)
{
this = this * t.GetMatrix();
return this;
}

/// <summary>
/// Transposes this Matrix and returns the resulting Matrix.
/// </summary>
/// <returns></returns>
public Matrix Transpose()
{
return new Matrix(getTranspose(ToArray()));
}

/// <summary>
/// Inverts this Matrix and returns the resulting Matrix.
/// </summary>
/// <returns></returns>
public Matrix Invert()
{
float[,] m = ToArray();
float[,] ret = new float[4,4];
float d = Determinant;

// Make sure determinant is not 0.
if(d == 0)
throw new HalflingException(this, "Cannot invert this Matrix because its derterminant is 0.");

// Loop through each row and column.
for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
// Create new smaller matrix from excluded row and column.
float[,] nm = new float[3,3];
for(int k = 0; k < 3; k++)
{
for(int l = 0; l < 3; l++)
{
nm[k,l] = m[(k>=i)?(k+1):(k),(l>=j)?(l+1):(l)];
}
}

// Calc determinant of this new matrix and calc value of this cell.
ret[i,j] = getDeterminant(nm) / d * (float)Math.Pow(-1, i + j);
}
}

// Transpose the matrix and return it.
return new Matrix(getTranspose(ret));
}

/// <summary>
/// Gets the matrix that is created by this transform.
/// </summary>
/// <returns></returns>
Matrix ITransform.GetMatrix()
{
return this;
}

/// <summary>
/// Gets the determinant for this Matrix.
/// </summary>
public float Determinant
{
get{return getDeterminant(ToArray());}
}

/// <summary>
/// Gets or sets the value of the component at the specified indices.
/// </summary>
public float this[int i, int j]
{
get
{
switch(i)
{
case 0:
switch(j)
{
case 0: return XX;
case 1: return XY;
case 2: return XZ;
case 3: return XW;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
case 1:
switch(j)
{
case 0: return YX;
case 1: return YY;
case 2: return YZ;
case 3: return YW;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
case 2:
switch(j)
{
case 0: return ZX;
case 1: return ZY;
case 2: return ZZ;
case 3: return ZW;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
case 3:
switch(j)
{
case 0: return WX;
case 1: return WY;
case 2: return WZ;
case 3: return WW;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
default:
throw new HalflingException(this, "Invalid dimension-one array index: " + i);
}
}
set
{
switch(i)
{
case 0:
switch(j)
{
case 0: XX = value; break;
case 1: XY = value; break;
case 2: XZ = value; break;
case 3: XW = value; break;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
break;
case 1:
switch(j)
{
case 0: YX = value; break;
case 1: YY = value; break;
case 2: YZ = value; break;
case 3: YW = value; break;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
break;
case 2:
switch(j)
{
case 0: ZX = value; break;
case 1: ZY = value; break;
case 2: ZZ = value; break;
case 3: ZW = value; break;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
break;
case 3:
switch(j)
{
case 0: WX = value; break;
case 1: WY = value; break;
case 2: WZ = value; break;
case 3: WW = value; break;
default: throw new HalflingException(this, "Invalid dimension-two array index: " + j);
}
break;
default:
throw new HalflingException(this, "Invalid dimension-one array index: " + i);
}
}
}

#endregion
#region Public Static Methods

/// <summary>
/// Calculates the sum of two matrices and returns the result.
/// </summary>
/// <param name="m1"></param>
/// <param name="m2"></param>
/// <returns></returns>
public static Matrix Sum(Matrix m1, Matrix m2)
{
float[,]ret = new float[4,4];

for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ret[i,j] = m1[i,j] + m2[i,j];
}
}

return new Matrix(ret);
}

/// <summary>
/// Calculates the difference of m2 from m1 and returns the result.
/// </summary>
/// <param name="m1"></param>
/// <param name="m2"></param>
/// <returns></returns>
public static Matrix Difference(Matrix m1, Matrix m2)
{
float[,]ret = new float[4,4];

for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ret[i,j] = m2[i,j] - m1[i,j];
}
}

return new Matrix(ret);
}

/// <summary>
/// Performs a matrix multiplication on m2 by m1 and returns the result.
/// </summary>
/// <param name="m1"></param>
/// <param name="m2"></param>
/// <returns></returns>
public static Matrix Product(Matrix m1, Matrix m2)
{
float[,] a1 = m1.ToArray();
float[,] a2 = m2.ToArray();
float[,] ret = new float[4,4];

for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ret[j,i] = a1[j,0]*a2[0,i] + a1[j,1]*a2[1,i] + a1[j,2]*a2[2,i] + a1[j,3]*a2[3,i];
}
}

return new Matrix(ret);
}

/// <summary>
/// Performs a scalar multiplication on m1 by the specified scalar value.
/// </summary>
/// <param name="m1"></param>
/// <param name="scalar"></param>
/// <returns></returns>
public static Matrix ScalarProduct(Matrix m1, float scalar)
{
float[,]ret = new float[4,4];

for(int i = 0; i < 4; i++)
{
for(int j = 0; j < 4; j++)
{
ret[i,j] = m1[i,j] * scalar;
}
}

return new Matrix(ret);
}

/// <summary>
/// Creates a translation matrix using the passed offsets.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
public static Matrix Translation(float x, float y, float z)
{
return new Matrix(
1, 0, 0, x,
0, 1, 0, y,
0, 0, 1, z,
0, 0, 0, 1);
}

/// <summary>
/// Creates a translation matrix using the passed Vector3D.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public static Matrix Translation(Vector3D v)
{
return Translation(v.X, v.Y, v.Z);
}

/// <summary>
/// Creates a scaling matrix using the passed scales.
/// </summary>
/// <param name="w"></param>
/// <param name="h"></param>
/// <param name="d"></param>
/// <returns></returns>
public static Matrix Scaling(float w, float h, float d)
{
return new Matrix(
w, 0, 0, 0,
0, h, 0, 0,
0, 0, d, 0,
0, 0, 0, 1);
}

/// <summary>
/// Creates a scaling matrix using the passed Vector3D.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public static Matrix Scaling(Vector3D v)
{
return Scaling(v.X, v.Y, v.Z);
}

/// <summary>
/// Creates an XY rotation matrix using the passed angle.
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
public static Matrix RotationXY(Angle a)
{
return new Matrix(
(float)M.Cos(a), -(float)M.Sin(a), 0, 0,
(float)M.Sin(a), (float)M.Cos(a), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
}

/// <summary>
/// Creates an XZ rotation matrix using the passed angle.
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
public static Matrix RotationXZ(Angle a)
{
return new Matrix(
(float)M.Cos(a), 0, (float)M.Sin(a), 0,
0, 1, 0, 0,
-(float)M.Sin(a), 0, (float)M.Cos(a), 0,
0, 0, 0, 1);
}

/// <summary>
/// Creates a YZ rotation matrix using the passed angle.
/// </summary>
/// <param name="a"></param>
/// <returns></returns>
public static Matrix RotationYZ(Angle a)
{
return new Matrix(
1, 0, 0, 0,
0, (float)M.Cos(a), -(float)M.Sin(a), 0,
0, (float)M.Sin(a), (float)M.Cos(a), 0,
0, 0, 0, 1);
}

/// <summary>
/// Creates a rotation matrix using the passed Vector3D.
/// </summary>
/// <param name="v"></param>
/// <returns></returns>
public static Matrix Rotation(Vector3D v)
{
Matrix m = Matrix.Identity;
m.AppendTransform(Matrix.RotationYZ(v.X));
m.AppendTransform(Matrix.RotationXZ(v.Y));
m.AppendTransform(Matrix.RotationXY(v.Z));
return m;
}

/// <summary>
/// Creates a perspective matrix using the passed distance.
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
public static Matrix Perspective(float d)
{
return new Matrix(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, (d == float.PositiveInfinity || d == float.NegativeInfinity)?(0):(1/d), 1);
}

/// <summary>
/// Creates a transformation matrix using the passed transforms.
/// </summary>
/// <param name="scaling">Factor by which coordinates will be scaled.</param>
/// <param name="rotationCenter">Center around which coordinates will be rotated.</param>
/// <param name="rotation">Angle by which coordinates will be rotated.</param>
/// <param name="translation">Distance by which coordinates will be translated.</param>
/// <returns>The resulting Matrix.</returns>
public static Matrix Transformation(Vector3D scaling, Vector3D rotationCenter, Vector3D rotation, Vector3D translation)
{
Matrix m = Matrix.Identity;
m.AppendTransform(Matrix.Scaling(scaling));
m.AppendTransform(Matrix.Translation(-rotationCenter));
m.AppendTransform(Matrix.Rotation(rotation));
m.AppendTransform(Matrix.Translation(rotationCenter));
m.AppendTransform(Matrix.Translation(translation));
return m;
}

/// <summary>
/// Creates a view matrix using the passed transforms.
/// </summary>
/// <param name="rotation"></param>
/// <param name="translation"></param>
/// <returns></returns>
public static Matrix View(Vector3D rotation, Vector3D translation)
{
Matrix m = Matrix.Identity;
m.AppendTransform(Matrix.Translation(-translation));
m.AppendTransform(Matrix.Rotation(-rotation));
return m;
}

/// <summary>
/// Creates a perspective projection matrix.
/// </summary>
/// <param name="width">Width of the viewing area.</param>
/// <param name="height">Height of the viewing area.</param>
/// <param name="scale">Scale of the viewing area.</param>
/// <param name="depth">Depth of the viewing area, already scaled.</param>
/// <param name="perspective">Perspective distance.</param>
/// <returns>The resulting Matrix.</returns>
public static Matrix ProjectionPerspective(float width, float height, float scale, float depth, float perspective)
{
Matrix m = Matrix.Identity;
if(width > height)
m.AppendTransform(Matrix.Scaling(scale, width / height * scale, depth));
else
m.AppendTransform(Matrix.Scaling(height / width * scale, scale, depth));
m.AppendTransform(Matrix.Perspective(perspective));
return m;
}

/// <summary>
/// Creates an orthogonal projection matrix.
/// </summary>
/// <param name="width">Width of the viewing area.</param>
/// <param name="height">Height of the viewing area.</param>
/// <param name="depth">Depth of the viewing area.</param>
/// <returns></returns>
public static Matrix ProjectionOrthogonal(float width, float height, float depth)
{
Matrix m = Matrix.Identity;
m.AppendTransform(Matrix.Scaling(1/(width/2), -1/(height/2), 1/depth));
m.AppendTransform(Matrix.Translation(-1, 1, 0));
return m;
}

/// <summary>
/// Creates an orthogonal projection matrix.
/// </summary>
/// <param name="dis">Display device from which to retrieve width and height parameters.</param>
/// <returns></returns>
public static Matrix ProjectionOrthogonal(Display dis)
{
return ProjectionOrthogonal(dis.ScreenSize.Width, dis.ScreenSize.Height, 1f);
}

/// <summary>
/// Overloaded + operator.
/// </summary>
/// <param name="m1"></param>
/// <param name="m2"></param>
/// <returns></returns>
public static Matrix operator + (Matrix m1, Matrix m2)
{
return Matrix.Sum(m1, m2);
}

/// <summary>
/// Overloaded - operator.
/// </summary>
/// <param name="m1"></param>
/// <param name="m2"></param>
/// <returns></returns>
public static Matrix operator - (Matrix m1, Matrix m2)
{
return Matrix.Difference(m1, m2);
}

/// <summary>
/// Overloaded * operator.
/// </summary>
/// <param name="m1"></param>
/// <param name="m2"></param>
/// <returns></returns>
public static Matrix operator * (Matrix m1, Matrix m2)
{
return Matrix.Product(m1, m2);
}

/// <summary>
/// Overloaded * operator.
/// </summary>
/// <param name="m1"></param>
/// <param name="scalar"></param>
/// <returns></returns>
public static Matrix operator * (Matrix m1, float scalar)
{
return Matrix.ScalarProduct(m1, scalar);
}

/// <summary>
/// Overloaded * operator.
/// </summary>
/// <param name="m1"></param>
/// <param name="scalar"></param>
/// <returns></returns>
public static Matrix operator * (float scalar, Matrix m1)
{
return Matrix.ScalarProduct(m1, scalar);
}

/// <summary>
/// Overloaded / operator.
/// </summary>
/// <param name="m1"></param>
/// <param name="scalar"></param>
/// <returns></returns>
public static Matrix operator / (Matrix m1, float scalar)
{
return Matrix.ScalarProduct(m1, 1 / scalar);
}

#endregion
#region Private Static Methods

/// <summary>
/// Gets the transpose of a given matrix.
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
private static float[,] getTranspose(float[,] m)
{
float[,] ret = new float[m.GetLength(1), m.GetLength(0)];

for(int i = 0; i < m.GetLength(0); i++)
{
for(int j = 0; j < m.GetLength(1); j++)
{
ret[j,i] = m[i,j];
}
}

return ret;
}

/// <summary>
/// Recursively calculates the determinant of a square matrix of arbitrary size.
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
private static float getDeterminant(float[,] m)
{
// Make sure matrix is square.
if(m.GetLength(0) != m.GetLength(1))
throw new HalflingException("Determinants can only be calculated for square matrices.");

// BASE CASE: Matrix is 1x1.
if(m.GetLength(0) == 1)
{
return m[0,0];
}

// BASE CASE: Matrix is any other size.
else
{
// Create a new matrix that is one unit smaller on each dimension.
float[,] nm = new float[m.GetLength(0) - 1, m.GetLength(1) - 1];
float sum = 0;

// Remove each column.
for(int i = 0; i < m.GetLength(1); i++)
{
// Calculate new matrix.
for(int j = 0; j < nm.GetLength(0); j++)
{
for(int k = 0; k < nm.GetLength(1); k++)
{
nm[k,j] = m[(k>=i)?(k+1):(k), j+1];
}
}

// Get its determinant, multiply by value in top row, and add it to the sum.
sum += getDeterminant(nm) * m[i,0] * (float)Math.Pow(-1, i);
}

// Return the sum.
return sum;
}
}

#endregion
}
}

Share this post


Link to post
Share on other sites
Is the reassignment of this really valid?

public Matrix PushTransform(ITransform t)
{
this = this * t.GetMatrix(); // Is this valid?
return this;
}

Share this post


Link to post
Share on other sites
Quote:
Original post by dalleboy
Is the reassignment of this really valid?

public Matrix PushTransform(ITransform t)
{
this = this * t.GetMatrix(); // Is this valid?
return this;
}
NO!

Share this post


Link to post
Share on other sites
I don't see why not if the * operator is defined for the matrix class, as the 'this' keyword just provides the current class/struct.
I have never used that construct myself, but if it is not possible, I think it should be allowed

Share this post


Link to post
Share on other sites
Quote:
Original post by capn_midnight
Quote:
Original post by dalleboy
Is the reassignment of this really valid?

public Matrix PushTransform(ITransform t)
{
this = this * t.GetMatrix(); // Is this valid?
return this;
}
NO!

WRONG!

In C# this isn't a pointer, it's a reference. That line will simply call the assignment operator for whatever type this is (which in this case is Matrix).

Share this post


Link to post
Share on other sites
Quote:
Original post by capn_midnight
Quote:
Original post by dalleboy
Is the reassignment of this really valid?

public Matrix PushTransform(ITransform t)
{
this = this * t.GetMatrix(); // Is this valid?
return this;
}
NO!


"this = this * t.GetMatrix();" calls the method


public static Matrix operator * (Matrix m1, Matrix m2)
{
return Matrix.Product(m1, m2);
}


with parameters m1 = {this}, m2 = {the result of t.GetMatrix()}. (Member access has higher precedence than the * operator.)

Share this post


Link to post
Share on other sites
Quote:
Original post by kSquared
"this = this * t.GetMatrix();" calls the method


public static Matrix operator * (Matrix m1, Matrix m2)
{
return Matrix.Product(m1, m2);
}


with parameters m1 = {this}, m2 = {the result of t.GetMatrix()}. (Member access has higher precedence than the * operator.)

Partially correct.

"this * t.GetMatrix()" calls the method "public static Matrix operator * (Matrix m1, Matrix m2)" with the parameters m1 = {this}, m2 = {the result of t.GetMatrix()} that returns a new Matrix (lets call it tempMatrix).

"this = tempMatrix" is the illegal part as you cannot overload the operator = in C#, and assignments of 'this' are illegal.

Share this post


Link to post
Share on other sites
Quote:
Original post by dalleboy
"this = tempMatrix" is the illegal part as you cannot overload the operator = in C#, and assignments of 'this' are illegal.

Assignments to this are only illegal for reference types, i.e. classes. For structs it performs a shallow copy.


struct Test {

public Test(int value) {
this.value = value;
}

private int value;

public int Value {
get { return value; }
}

public void Assign(Test a) {
this = a;
}
}


class Program {

static void Main() {
Test t1 = new Test(10);
Test t2 = new Test(20);
t1.Assign(t2);
Console.WriteLine(t1.Value); // prints 20
}
}



Change "struct" to "class" and it won't compile anymore.

Regards,
Andre

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.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!