3D Coordinates from 2D Simulation

in away3d and isgl3d

Just a quick recap, on the best ways to render a 3D game based on 2D collision logic. And what I mean by this is that your whole game logic concerns itself with a 2D simulation, and only render the scene in 3D. One simulation can easily “talk” to the other one, so that for instance if the user picks a 3D object with the mouse and moves it, this movement can be translated back to the 2D simulation. It’s just a matter of planning to find the best possible process.

Don’t Shoot Yourself on the Foot

Make sure all your objects, especially those in the 2D simulation, have their origin point in their center. So no upper left registration point.

The Y Means Y Method

This used to suck before OpenGl support. Now it’s cool again. In this method, you simply use the X and Y coordinates from the 2D simulation, and then either adjust your camera or rotate the container object to show your scene in 3D.

You still need to offset the elements so you can have a centered origin point.

However, remember this: if you want camera movement in your scene, use the next method. Making a camera follow an object in 3D space using this method may not work properly.

The YZ Switcheroo Method

With this method Y means Z. So the Y from the 2D simulation is translated to Z in the 3D simulation. This also will require camera adjustment, or rotation of the container object. As you will see in a moment.

This method does have the added problem of the “flipped image”. Take a look at the following:

The image in 2D space which I want to represent in 3D

However, because of the sign in the Z axis, the image would appear upside down if I used the same value from the 2D Y axis.

The Example

I have this image in 2D:

And here is the code to create it. One square is rotated 45 degrees.

var point1:Point = new Point (0, 0);
var point2:Point = new Point (300, 200);
var point3:Point = new Point (500, 300);
var point4:Point = new Point (500, 100);
 
var side1:int = 100;
var side2:int = 80;
var side3:int = 50;
var side4:int = 60;
 
_container2d = new Sprite();
_container2d.graphics.beginFill(0x666666);
_container2d.graphics.drawRect(0, 0, 600, 400);
_container2d.graphics.beginFill(0xFF9900);
//reference point is at the center of the squares!!!
_container2d.graphics.drawRect(point1.x - side1 * 0.5, point1.y - side1 * 0.5, side1, side1);
_container2d.graphics.drawRect(point2.x - side2 * 0.5, point2.y - side2 * 0.5, side2, side2);
_container2d.graphics.drawRect(point3.x - side3 * 0.5, point3.y - side3 * 0.5, side3, side3);
 
var rotatedSquare:Sprite = new Sprite();
rotatedSquare.graphics.beginFill(0xFF0000);
rotatedSquare.graphics.drawRect(-side4 * 0.5, -side4 * 0.5, side4, side4);
rotatedSquare.rotation = 45;
rotatedSquare.x = point4.x;
rotatedSquare.y = point4.y;
_container2d.addChild(rotatedSquare);

Now I want to bring this to 3D using cubes. So I create a Container object.

_container3d = new ObjectContainer3D();
scene.addChild(_container3d);

I add the cubes (I’ll use the Y means Y method first). And I offset the coordinates using the Width and Height of the screen.

// create cubes
var cubeMesh:Mesh;
var cube:CubeGeometry;
var w:int = 600;
var h:int = 400;
 
cube = new CubeGeometry (side1, side1, side1);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point1.x - w * 0.5;
cubeMesh.y = h * 0.5 - point1.y;
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side2, side2, side2);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point2.x - w * 0.5;
cubeMesh.y = h * 0.5 - point2.y;
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side3, side3, side3);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point3.x - w * 0.5;
cubeMesh.y = h * 0.5 - point3.y;
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side4, side4, side4);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF0000));
cubeMesh.x = point4.x - w * 0.5;
cubeMesh.y = h * 0.5 - point4.y;
cubeMesh.rotationZ = 45;
_container3d.addChild(cubeMesh);

Notice the formula for the offset, and the rotation of the last cube. In this method the Z axis is the one I need to apply the rotation to.

This causes the scene to shift to the center, like this:

And this is an equivalent way to manage the sign inversion.

cube = new CubeGeometry (side1, side1, side1);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point1.x - w * 0.5;
cubeMesh.y = -1 * (point1.y - h * 0.5);
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side2, side2, side2);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point2.x - w * 0.5;
cubeMesh.y = -1 *(point2.y - h * 0.5);
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side3, side3, side3);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point3.x - w * 0.5;
cubeMesh.y = -1 * (point3.y - h * 0.5);
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side4, side4, side4);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF0000));
cubeMesh.x = point4.x - w * 0.5;
cubeMesh.y = -1 * (point4.y - h * 0.5);
cubeMesh.rotationZ = 45;
_container3d.addChild(cubeMesh);

So with the Y = Y method, I end up with the same 2D picture which I can then rotate in 3D space.

In the Y = Z method I would do this:

// create cubes
var cubeMesh:Mesh;
var cube:CubeGeometry;
var w:int = 600;
var h:int = 400;
 
cube = new CubeGeometry (side1, side1, side1);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point1.x - w * 0.5;
cubeMesh.z = h * 0.5 - point1.y;
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side2, side2, side2);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point2.x - w * 0.5;
cubeMesh.z = h * 0.5 - point2.y;
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side3, side3, side3);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point3.x - w * 0.5;
cubeMesh.z = h * 0.5 - point3.y;
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side4, side4, side4);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF0000));
cubeMesh.x = point4.x - w * 0.5;
cubeMesh.z = h * 0.5 - point4.y;
cubeMesh.rotationY = 45;
_container3d.addChild(cubeMesh);
 
_container3d.rotationX = -90;

This is equivalent:

cube = new CubeGeometry (side1, side1, side1);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point1.x - w * 0.5;
cubeMesh.z = -1 * (point1.y - h * 0.5);
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side2, side2, side2);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point2.x - w * 0.5;
cubeMesh.z = -1 *(point2.y - h * 0.5);
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side3, side3, side3);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF9900));
cubeMesh.x = point3.x - w * 0.5;
cubeMesh.z = -1 * (point3.y - h * 0.5);
_container3d.addChild(cubeMesh);
 
cube = new CubeGeometry (side4, side4, side4);
cubeMesh = new Mesh (cube, new ColorMaterial(0xFF0000));
cubeMesh.x = point4.x - w * 0.5;
cubeMesh.z = -1 * (point4.y - h * 0.5);
cubeMesh.rotationY = 45;
_container3d.addChild(cubeMesh);
 
_container3d.rotationX = -90;

The formula for the offset is the same. But now I rotate the last cube on the Y axis and in order to see the scene from the top (as I do in the 2D simulation) I need to rotate the container object ninety degrees.

In both cases of course I need to pull back the camera on the Z axis. For this scene I use a Z value of -345 to fill up the screen with the elements.

In ISGL3D

Isgl3D has the same screen origin as Away3D, unlike other 3D engines for iOS. However the Z axis is positive towards the viewer and negative deeper into screen space. In other words, the opposite to Away3D.

The Y = Y method in Objective-C

CGPoint point1 = CGPointMake(0, 0);
CGPoint point2 = CGPointMake(30, 20);
CGPoint point3 = CGPointMake(50, 30);
CGPoint point4 = CGPointMake(50, 10);
 
int side1 = 10;
int side2 = 8;
int side3 = 5;
int side4 = 6;
 
Isgl3dNode * _container = [self.scene createNode];
 
int w = 48;
int h = 32;
 
Isgl3dGLMesh * geometry;
Isgl3dMeshNode * cube;
Isgl3dColorMaterial * material;
 
material = [[Isgl3dColorMaterial alloc] initWithHexColors:@"0xFF9900" diffuse:@"0xFF9900" specular:@"0xFF9900" shininess:0.0];
 
geometry = [Isgl3dCube meshWithGeometry:side1 height:side1 depth:side1 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.x = point1.x - w * 0.5;
cube.y = h * 0.5 - point1.y;
[_container addChild:cube];
 
geometry = [Isgl3dCube meshWithGeometry:side2 height:side2 depth:side2 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.x = point2.x - w * 0.5;
cube.y = h * 0.5 - point2.y;
[_container addChild:cube];
 
geometry = [Isgl3dCube meshWithGeometry:side3 height:side3 depth:side3 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.x = point3.x - w * 0.5;
cube.y = h * 0.5 - point3.y;
[_container addChild:cube];
 
material = [[Isgl3dColorMaterial alloc] initWithHexColors:@"0xFF0000" diffuse:@"0xFF0000" specular:@"0xFF0000" shininess:0.0];
 
geometry = [Isgl3dCube meshWithGeometry:side4 height:side4 depth:side4 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.rotationZ = 45;
cube.x = point4.x - w * 0.5;
cube.y = h * 0.5 - point4.y;
[_container addChild:cube];

The same formula and the same rotation on the Z axis. Only now I use a 10:1 scale for all magnitudes. Which is not necessary, as you can adjust the camera instead, but I prefer doing things this way.

And now the Y = Z method in Objective-c.

geometry = [Isgl3dCube meshWithGeometry:side1 height:side1 depth:side1 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.x = point1.x - w * 0.5;
cube.z =  point1.y - h * 0.5;
[_container addChild:cube];
 
geometry = [Isgl3dCube meshWithGeometry:side2 height:side2 depth:side2 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.x = point2.x - w * 0.5;
cube.z =  point2.y - h * 0.5;
[_container addChild:cube];
 
geometry = [Isgl3dCube meshWithGeometry:side3 height:side3 depth:side3 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.x = point3.x - w * 0.5;
cube.z =  point3.y - h * 0.5;
[_container addChild:cube];
 
material = [[Isgl3dColorMaterial alloc] initWithHexColors:@"0xFF0000" diffuse:@"0xFF0000" specular:@"0xFF0000" shininess:0.0];
 
geometry = [Isgl3dCube meshWithGeometry:side4 height:side4 depth:side4 nx:2 ny:2];
cube = [[Isgl3dMeshNode alloc] initWithMesh:geometry andMaterial:material];
cube.rotationY = 45;
cube.x = point4.x - w * 0.5;
cube.z =  point4.y -  h * 0.5;
[_container addChild:cube];
 
_container.rotationX = 90;

Notice the change in the Z formula and the sign in rotationX. This is because of the inverted Z sign in Isgl3D. As a matter of fact you can still use the same formula as before, but this will cause the movie to be seen from bellow. (It’s a good idea to use a plane bellow your primitives to test if you are not seeing things from bellow, in that case the plane would disappear if it’s not set to be double sided.)

Conclusion

The number of options I guess is what can make this process so confusing. I mean, you can make the same variations by changing the camera, or applying rotation on two axis on the container object instead of just one axis. In the end it’s just a matter of preference. But the problem is always connected to the origin point and the sign of the Z axis.

So when coding your games, pick your favorite way, and do a bunch of tests. But don’t forget to deal as much with the 2D simulation as you can, especially for movement and collision detection, and only then translate all to 3D using the offset formulas I’ve shown here.

Leave a Comment