3D GM:S. Implementing Rubik's Cube rotations.

C

Chubb1337

Guest
Hi there!

The goal in a nutshell
I wish to create a digital 3D Rubik's Cube program with a variable cube size (think 2x2x2, 3x3x3, 4x4x4, 5x5x5 etc.. ). In this program I wish to try out several cube sizes, maybe even 2x2x4. Implement solving algorithms ans possibly image input which will make the digital cube match the physical scrambled cube.

Current situation
Currently I have 3x3 cube, semi-hard coded (not what I want), with the possibility to rotate the camera around this cube. It is possible to rotate the Up layer, Equator layer and Bottom layer.
I'm using an array (called workingArray) with the following information:

//0 = current position index, 1 = axis (0=centroid 1=x+ 2=y+ 3=z+ 4=x- 5=y- 6=z- 7=x0 8=y0 9=z0), 2 = angle (around the axis), 3 = radius, 4 = roll (x-axis), 5 = pitch (y-axis), 6 = yaw (z-axis)

solvedArray[i, 6] = 0;
solvedArray[i, 5] = 0;
solvedArray[i, 4] = 0;
solvedArray[i, 3] = radius;
solvedArray[i, 2] = angle;
solvedArray[i, 1] = axis;
solvedArray[i, 0] = i;

Current situation that works (up layer, middle and down also work):

(Depending on axis, angle and radius; pitch, roll and yaw, each of the 27 cubies is drawn individually)

When pressing the 1, 2 or 3 key they rotate the different layers. However trying to implement this in the vertical direction fails (1, 2 and 3 rotate around the Z axis, trying to implement this for the X and Y axis creates unexpected behaviour).

The problem
There are actually several problems, first of all, the current situation is not adaptable in size (2x2, 3x3, 4x4, etc..etc..). I am uncertain of what would be the best way to store the needed data, which will allow for an N by N cube to rotate all layers (X, Y and Z layers) in clockwise and counter-clockwise directions, my current attempt of using arrays feels mismatched.
The second problem consists of the actual rotating of the layers. This means the information needs to move around within the (to be specified) data structure and then be drawn accordingly.

Any help with choosing a data structure capable for this task, help on how to manipulate this data structure accordingly and making an adaptable cube size would be greatly appreciated.

If more information is required to help me out, I will gladly provide.

-Chub
 

DukeSoft

Member
How about ds_grids ?
Maybe wrap a list around it to make a 3d kind of grid?

Or just a ds_grid of aXa for every side?
 
C

Chubb1337

Guest
How about ds_grids ?
Maybe wrap a list around it to make a 3d kind of grid?

Or just a ds_grid of aXa for every side?
Hmm, okey so a ds_grid. How would help with the permutations though (the rotation of layers and slices, etc.. ) and how do you mean to wrap a list around it?
Like have a list of ds_grids you say, each ranging from 0,0 to n,n and then n amount of ds_grids in the ds_list? How is a ds_grid different from a 2D array?
 

DukeSoft

Member
Actually you're right, a ds_grid is basically the same as an 2d array.

At this point i've been staring at my screen for 2 minutes and nothing is coming up. I'm thinking how to make good looking animations and stuff for the rubics cube but thats hard... Let me come back with a better answer later ;p Interesting matter.
 

TheouAegis

Member
width = min(2,width);
height = min(2,height);
for(var k=width * height - 1; k>-1; k++)
for(var i=0; i<6; i++) face[i,k] = i;


That's just what popped into my head. The range of i will always be {0,5} as a cube will have 6 faces. So the first index is the face of the cube and k is the slot. You'd make sure the cube's width and height are both at least 2 (so you could make a Rubik's hyperrectangle. All the faces would be generated with the same color.

For shuffling the Rubik's hyperrectangle, you'd either run a series of individual transformations (the humanoid approach) or just randomize all the colors and then count the number of inversions, then adjust accordingly. Considering I have no idea how to check for and adjust inversions in three dimensions, I'd take the easy route and do the humanoid shuffle.

The tricky part is keeping track of the order of faces for when you rotate. Since you're working in a free-moving 3D space, horizontal and vertical rotations would depend on the orientation in space. A horizontal rotation when the cube has been rotated 90 degrees in space would be a vertical rotation to the player but still a horizontal rotation to the current face. If you restrict your movements to a 2D mechanic while letting the visuals exist in 3D, then it's pretty doable, but handling which face each value translates into would probably be tricky. It might be doable with an algorithm, but I'd probably just use a series of specific scripts.
 
C

Chubb1337

Guest
Actually you're right, a ds_grid is basically the same as an 2d array.

At this point i've been staring at my screen for 2 minutes and nothing is coming up. I'm thinking how to make good looking animations and stuff for the rubics cube but thats hard... Let me come back with a better answer later ;p Interesting matter.
@DukeSoft I agree, it is interesting matter :)

width = max(2,width);
height = max(2,height);
for(var k=width * height - 1; k>-1; k--)
for(var i=0; i<6; i++) face[i,k] = i;
Some minor fixes in your code ;)
Just wondering, what do you mean with 'Rubik's hyperrectangle' ?

The humanoid approach is what I will use for shuffling.

What I'm currently using is an 1D array of size 'cube_size' (so for example 2, 3, 4, 5 etc..). Inside this 1D array, each index is a ds_grid with width and height of the 'cube_size'.
This results in the cube being divided into layers from top to bottom, each being represented in a ds_grid. Each cell of these grids contains a string "000000000" to (3 times 000, where the first section represents the roll, the second the pitch and the third the yaw).

To rotate the layers, I increment the last section of this string by a certain degrees (90, 180, 270) and then mod it by 360.
I can now have any cube size I want, I can rotate any layer (In fact I can also rotate the slices), but the problem now is that the 3D cubies misbehave. The code I use to draw the cube is as follows:

Code:
for (i=0; i<cube_size; i++){
    for (j=0; j<cube_size; j++){
        for (k=0; k<cube_size; k++){
            // Text representation
            if (floor(cubit_num/(cube_size*cube_size)) != i || i*cube_size*cube_size+j*cube_size+k==cubit_num){
                draw_set_colour(make_colour_hsv(round(255/cube_size)*i, 200, 200));
                d3d_transform_set_identity();
                d3d_transform_set_translation(ceil((cubit_size*cube_size)/2)+2, ceil((cubit_size*cube_size)/2)+2, -cube_size*16+i*16);
                draw_text(k*20,j*20,string(ds_grid_get(gridArray[i], k, j)));
                d3d_transform_set_identity();
                draw_set_colour(c_white);
            }
           
            // Cubies
            // Outsides only
            if ((k==0 || j==0 || i==0 || k==cube_size-1 || j==cube_size-1 || i==cube_size-1) && (i*cube_size*cube_size+j*cube_size+k!=cubit_num)){
                var xx, yy, zz, cube_hsize, rx, ry, rz;
                xx = k*cubit_size;
                yy = j*cubit_size;
                zz = i*cubit_size;
                if (ds_grid_get(gridArray[i], k, j) != "000000000"){
                    rx = real(string_copy(ds_grid_get(gridArray[i], k, j), 1, 3));
                    ry = real(string_copy(ds_grid_get(gridArray[i], k, j), 4, 3));
                    rz = real(string_copy(ds_grid_get(gridArray[i], k, j), 7, 3));
                }else{
                    rx = 0;
                    ry = 0;
                    rz = 0;
                }
                //rx = roll;
                //ry = pitch;
                //rz = yaw;
                cube_hsize = cube_size*cubit_hsize;
                d3d_transform_set_identity();
                d3d_transform_add_rotation_x(rx);
                d3d_transform_add_rotation_y(ry);
                d3d_transform_add_rotation_z(rz);
                d3d_draw_box(x-cubit_size-xx+cube_hsize, y-yy+cube_hsize, z-zz+cube_hsize, x-xx+cube_hsize, y-cubit_size-yy+cube_hsize, z-cubit_size-zz+cube_hsize, txW, txG, txO, txB, txR, txY, 1, 1);
                d3d_transform_set_identity();
            }
        }
    }
}

Rotating only layers works, and only slices works, but when rotating both layers and slices, some cubies rotate which should, while others which should, don't.

There aren't any animations in there for now, once all layers, slices and panel rotations work instantly, I'll start worrying about the animation, maybe using the lerp() function.
 

dphsw

Member
@DukeSoft Rotating only layers works, and only slices works, but when rotating both layers and slices, some cubies rotate which should, while others which should, don't.
Yes - it's complicated to decide how pitch, yaw and roll should combine. That's why I prefer to represent 3D rotations with matrices.

I explain how to do this manually, but I found an easier way. I'll leave the original explanation here for anyone who's interested.
Though certainly that's easier in an object oriented language that just has some 'matrix' objects that you can multiply. Still, it shouldn't be too bad. Instead of having nine-character strings in your arrays, have 3x3 ds_grids. These ds_grids will be the rotation matrices of your cubies. Start each one off with the identity matrix:
Code:
1 0 0
0 1 0
0 0 1
Then to rotate a cubie 90 degrees around the X axis, Y axis and Z axis respectively, multiply them by the following matrices, which you'll just store in another ds_grid somewhere:
Code:
1   0   0
0   0  -1
0   1   0

0   0   1
0   1   0
-1  0   0

0   -1  0
1   0   0
0   0   1

To multiply them, use some code like this:
Code:
var r,c;
for(r=0;r<3;r++){
    for(c=0;c<3;c++){
        // Don't change the cubie rotation matrix until you've finished using it for calculation - use a different ds_grid for calculating
        cubie_new_rotation[# r,c] = rotation[# r,0]*cubie_old_rotation[# 0,c] + rotation[# r,1]*cubie_old_rotation[# 1,c] + rotation[# r,2]*cubie_old_rotation[# 2,c];
    }
}
I see GameMaker doesn't allow you to apply a 3D matrix when drawing, but it does allow you to work with a 4D matrix using an array and matrix_set. (To turn any of these 3x3 matrices into the equivalent 4x4 matrix, just add zeros below and to the right, and a 1 at the bottom corner.)

So instead of 9-character strings, I'd say set the contents of your cubie rotation array to hold a whole bunch of arrays initialised with matrix_build(0,0,0,0,0,0,1,1,1) and initialise your rotation matrices with stuff like matrix_build(0,0,0,90,0,0,1,1,1), then just use matrix_multiply to rotate all the cubies to which a rotation should apply, and matrix_set in your drawing routine to set the rotation before drawing each one.

(You could even put the position of your cubies in their matrices when intialising them so you don't need to put that code in your drawing routine - it will make things a lot easier when you come to animate them since they will automatically move round with the rotation (and you'll see the effect easily by making the rotation matrix rotate by a few degrees a frame instead of the whole 90 degrees at once), although it will make things harder in the short term since the cubies will actually be moving around rather than just rotating, so you'll no longer be making it harder to keep track of which ones should rotate when a face rotates - the ones in the top layer of the array will no longer necessarily be the ones on top of the cube since they'll have moved around, so you'll need to check their position - indices 12-14 in the cubies' matrices will probably let you keep track of their position.)
 

TheouAegis

Member
A hyperrectangle is rectangular on all 6 faces but not all faces are the same size like a cube. A Rubik's hyperrectangle is just a theoretical idea that would only work in abstract digital representations, so don't worry about it.
 
C

Chubb1337

Guest
A hyperrectangle is rectangular on all 6 faces but not all faces are the same size like a cube. A Rubik's hyperrectangle is just a theoretical idea that would only work in abstract digital representations, so don't worry about it.
Ah, so something like a Rubik's tower: https://eu.rubiks.com/store/cubes/rubiks-tower ?

@dphsw I'm afraid I don't quite follow with this matrix thing, if it is not too much trouble, would you mind creating an small example?

Current situation
Layer rotation (around the z-axis): Check
Slice rotation (around the x-axis): Check
Combination: Sort of, the cubies that already got a rotation, do not wholly follow along.
This is different from before as then they also moved about to places they did not belong. See below:

On the left. Up layer, clockwise rotation. On the right, Right slice, counter-clockwise rotation (red rotates to the bottom, white to the front, orange to the top).
As you can see, for some reason, white also moved to the front, and green stayed there, while the red line at the top should have gone rotated along with the right slice aswell. I can't seem to wrap my head around how to go about this.
CubeRotationProblems_strip2.png

-Chub
 

dphsw

Member
On the left. Up layer, clockwise rotation. On the right, Right slice, counter-clockwise rotation (red rotates to the bottom, white to the front, orange to the top).
As you can see, for some reason, white also moved to the front, and green stayed there, while the red line at the top should have gone rotated along with the right slice aswell. I can't seem to wrap my head around how to go about this.
-Chub
I'm not sure if the cubies are really moving about, or if they just rotate on the spot, which would explain a lot. So in the left picture, if you take, say the cubie we see right in the middle, imagine that is still the cubie that was right there all along. It's just that before it rotated, you were seeing its white red and blue faces - but now that it's rotated 90 degrees, you can see its green face, and it's blue face is facing the back, so you think it's the cubie from round the corner (the one that had a white green and red face), but actually it's the same cubie that was always in that position. If it's rotation was animated, you'd see the nine cubies rotating without moving from their individual spots.
Then you rotate the blue face, and because it's just the cubies rotating (the face isn't actually rotating - the cubies don't move around, as I've said) the cubies at the bottom naturally still show their blue face - they are just the cubies that were always at the bottom, and still are. The only difference is that they've rotated on the spot so that their white faces (that were on top, and so concealed by the cubies above them) are now visible.
That only leaves the question of the cubies along the sort-of top-right row. They have undergone both rotations. The problem is, they haven't undergone them both in the right order. You've rotated the white face, then the blue face. What the middle cubie is showing is what you would expect if you had rotated the blue face, then the white face. Part of the problem with the way you store the angles is that it clearly doesn't account for the order the rotations happen in. This is where matrices (or quaternions, but they're more complicated) are better. If you have two matrices representing a rotation around the X axis and a rotation around the Y axis, multiply them one way round and multiply them the other way round, you'd get different answers, accurately representing the fact that when you rotate a cube, it matters what order you rotate it in.

@dphsw I'm afraid I don't quite follow with this matrix thing, if it is not too much trouble, would you mind creating an small example?
Sure. But I'd recommend learning more about the subject - you're especially likely to use them if you're ever programming shaders. This is a good free course on them (and on a lot of mathematics in general). The main part of interest is under 'Matrices as Transformations', but the earlier parts will be necessary to understand it.
KhanAcademy - Matrices
Code:
// initialise cube
numcubies = cube_size*cube_size*cube_size;
var i,j,k;
var first_coord = -0.5*(cube_size-1); // cubie centres at -1 to 1 for 3x3x3 cube, -2 to 2 for 5x5x5 cube
for(i=0;i<cube_size;i++){
    for(j=0;j<cube_size;j++){
        for(k=0;k<cube_size;k++){
            cubie[(i*cube_size*cube_size)+(j*cube_size)+k] = matrix_build(i+first_coord,j+first_coord,k+first_coord,0,0,0,1,1,1);
        }
    }
}



// example rotation code - rotating 1 slice around x-axis

var axis = 12; // this indicates x-axis - it would be 13 for y-axis, 14 for z-axis.  These numbers are the position on a matrix where the
               // coordinates are stored.  Unless I've mixed up rows and columns, in which case it's 3, 7 and 11 you want.
              // Look at https://msdn.microsoft.com/en-us/library/windows/desktop/bb206269(v=vs.85).aspx under the subheading 'Translate'
              // You want the positions of the Tx,Ty and Tz, and the matrix is being stored here as an array going along the rows and columns.

var rotation = matrix_build(0,0,0,90,0,0,1,1,1); // Rotate 90 degrees around x-axis - the two variables after that control the y and z axis.
// (Try setting the rotation to 30 degrees and pressing it 3 times to make sure it's working as expected)

var coord = 1; // This is to say which slice to rotate - I'm going to rotate any cubie that has an x-coordinate of 1
               // (The axis variable above just controls which coordinate I'm checking the value of to see if it matches this.)
          
var temp; // this is just a temporary variable to access an element of the array inside the array - according to the forums, the inner elements can't be accessed any other way in GML.
for(var c=0;c<numcubies;c++){
    temp = cubie[c];
    if(temp[axis] == coord) cubie[c] = matrix_multiply(rotation,temp);
    // N.B. Over time, a coordinate supposed to equal 1 might equal 0.99999, so you might need to adjust math_set_epsilon to make this work reliably.
}




// drawing code
// replace these lines:
                d3d_transform_add_rotation_x(rx);
                d3d_transform_add_rotation_y(ry);
                d3d_transform_add_rotation_z(rz);
               d3d_draw_box(x-cubit_size-xx+cube_hsize, y-yy+cube_hsize, z-zz+cube_hsize, x-xx+cube_hsize, y-cubit_size-yy+cube_hsize, z-cubit_size-zz+cube_hsize, txW, txG, txO, txB, txR, txY, 1, 1);
// with these
    matrix_set(matrix_world, cubie[c]);
    d3d_draw_box(0.5, 0.5, 0.5, -0.5, -0.5, -0.5, txW, txG, txO, txB, txR, txY, 1, 1);
    // I can't find this function in the manual, but I'm inferring these are the coordinates and colours of the cubie.
    // The position is now part of the matrix, so we just need the size - I set them all 1 apart in the initialization,
    // so I've given them a size of 1 and a centre of zero.
By the way, it's occurred to me that an interesting variant of the game would be if you had one of larger cubes (like a 5x5x5), and kept all those middle cubies (the invisible ones) there, made it so that you could see through layers to look at the insides, and it only counted as solved when the inside cubies were aligned correctly as well as the outside cubies. I'm not sure if that's been done before. Oh, that reminds me - to check if the cubies are aligned correctly, you just need to check whether they all have 1's in positions 0,5 and 10 of their arrays.

Also the Rubik's Hypercube can be found here.
 
Last edited:
C

Chubb1337

Guest
Sure. But I'd recommend learning more about the subject - you're especially likely to use them if you're ever programming shaders. This is a good free course on them (and on a lot of mathematics in general). The main part of interest is under 'Matrices as Transformations', but the earlier parts will be necessary to understand it.
KhanAcademy - Matrices
Thank you so much for that KhanAcademy url. Last time I did any linear algebra it was in college a few years back in and it required some dusting off. After working my way through it all, I now have a clearer understanding of what it is you're doing in your code. I do however have a question which I could not answer by looking in the manual.

According to the manual, it is a 4x4 matrix, but using 'matrix_build' clearly shows only 9 entries, so shouldn't it be a 3x3 matrix?

I have implemented the code you have given me, and the results are somewhat unexpected.

oCube Create Event:
Code:
//      ### start of dphsw's code ###
// initialise cube
numcubies = cube_size*cube_size*cube_size;
var i,j,k;
var first_coord = -0.5*(cube_size-1); // cubie centres at -1 to 1 for 3x3x3 cube, -2 to 2 for 5x5x5 cube
for(i=0;i<cube_size;i++){
    for(j=0;j<cube_size;j++){
        for(k=0;k<cube_size;k++){
            //cubie[(i*cube_size*cube_size)+(j*cube_size)+k] = matrix_build((i+first_coord)*cubit_size,(j+first_coord)*cubit_size,(k+first_coord)*cubit_size,0,0,0,cubit_size,cubit_size,cubit_size); //Scaled up version of the cube.
            cubie[(i*cube_size*cube_size)+(j*cube_size)+k] = matrix_build((i+first_coord),(j+first_coord),(k+first_coord),0,0,0,1,1,1);
        }
    }
}
//      ### end of dphsw's code ###
oCube Step Event:
Code:
if keyboard_check_released(ord("4")){   //L
    //cube_slice_rotation(0,90);
  
    //      ### start of dphsw's code ###

    // example rotation code - rotating 1 slice around x-axis
  
    var axis = 12; // this indicates x-axis - it would be 13 for y-axis, 14 for z-axis.  These numbers are the position on a matrix where the
                   // coordinates are stored.  Unless I've mixed up rows and columns, in which case it's 3, 7 and 11 you want.
                  // Look at https://msdn.microsoft.com/en-us/library/windows/desktop/bb206269(v=vs.85).aspx under the subheading 'Translate'
                  // You want the positions of the Tx,Ty and Tz, and the matrix is being stored here as an array going along the rows and columns.
  
    var rotation = matrix_build(0,0,0,30,0,0,1,1,1); // Rotate 90 degrees around x-axis - the two variables after that control the y and z axis.
    //var rotation = matrix_build(0,0,0,30,0,0,cubit_size,cubit_size,cubit_size);   Scaled up test, does not seem to work.
  
    // (Try setting the rotation to 30 degrees and pressing it 3 times to make sure it's working as expected)
  
    var coord = 1; // This is to say which slice to rotate - I'm going to rotate any cubie that has an x-coordinate of 1
                   // (The axis variable above just controls which coordinate I'm checking the value of to see if it matches this.)
            
    var temp; // this is just a temporary variable to access an element of the array inside the array - according to the forums, the inner elements can't be accessed any other way in GML.
    for(var c=0;c<numcubies;c++){
        temp = cubie[c];
        if(temp[axis] == coord) cubie[c] = matrix_multiply(rotation,temp);
        // N.B. Over time, a coordinate supposed to equal 1 might equal 0.99999, so you might need to adjust math_set_epsilon to make this work reliably. <- So far have not had any problems, might set the machine epsilon to 0.0001 or something if ever needed.
    }
  
    //      ### end of dphsw's code ###
oCube Draw Event:
Code:
//      ### start of dphsw's code ###
for(var c=0;c<numcubies;c++){
    matrix_set(matrix_world, cubie[c]);
    d3d_draw_box(0.5, 0.5, 0.5, -0.5, -0.5, -0.5, txW, txB, txO, txG, txR, txY, 1, 1);
    // I can't find this function in the manual, but I'm inferring these are the coordinates and colours of the cubie.
    // The position is now part of the matrix, so we just need the size - I set them all 1 apart in the initialization,
    // so I've given them a size of 1 and a centre of zero.
}
d3d_transform_set_identity();
//      ### end of dphsw's code ###
The d3d_draw_box() is a script I made:
Code:
///d3d_draw_box(x1, y1, z1, x2, y2, z2, tex1, tex2, tex3, tex4, tex5, tex6, hrep, vrep);

// Arguments
var x1, x2, y1, y2, z1, z2;
x1 = argument0;
y1 = argument1;
z1 = argument2;
x2 = argument3;
y2 = argument4;
z2 = argument5;

var tex1, tex2, tex3, tex4, tex5, tex6;
tex1 = argument6;
tex2 = argument7;
tex3 = argument8;
tex4 = argument9;
tex5 = argument10;
tex6 = argument11;

var hrep, vrep;
hrep = argument12;
vrep = argument13;

// Draw box
d3d_draw_floor(x1, y1, z1, x2, y2, z1, tex1, hrep, vrep);
d3d_draw_floor(x1, y2, z2, x2, y1, z2, tex6, hrep, vrep);
d3d_draw_wall(x1, y2, z1, x2, y2, z2, tex3, hrep, vrep);
d3d_draw_wall(x1, y1, z1, x1, y2, z2, tex2, hrep, vrep);
d3d_draw_wall(x2, y2, z1, x2, y1, z2, tex4, hrep, vrep);
d3d_draw_wall(x2, y1, z1, x1, y1, z2, tex5, hrep, vrep);

Now, what happens without the scaling is as follow:
A very very tiny cube. When '4' is pressed the cubies in the left slice (positive x-axis) are rotated around on the spot (individual x-axis, not the cubes x-axis).

With the scaling included:
A normal sized cube. When '4' is pressed, nothing happens.

So we are still left with the problem of the cubies rotating on the spot, but now we are also left with either a very tiny cube, or a normal sized not responding cube. I tried several things to get the scaled up cube to behave like the tiny cube, to no avail. :(

Also the Rubik's Hypercube can be found here.
Oh dear, please no! Haha!

By the way, it's occurred to me that an interesting variant of the game would be if you had one of larger cubes (like a 5x5x5), and kept all those middle cubies (the invisible ones) there, made it so that you could see through layers to look at the insides, and it only counted as solved when the inside cubies were aligned correctly as well as the outside cubies. I'm not sure if that's been done before.
Hmm interesting thought, however a bit to advanced at this given point ;)

P.S. Is there some way to colour syntax the code in the forums? I tried [GML] [/GML] but that was not supported.
 

dphsw

Member
According to the manual, it is a 4x4 matrix, but using 'matrix_build' clearly shows only 9 entries, so shouldn't it be a 3x3 matrix?
matrix_build doesn't take the actual numbers that go into the matrix - it takes arguments about whether you want the matrix to make a rotation or translation or scaling. For example, the bottom right element of the 4x4 matrix (element 15 in the array) will always be 1, no matter what you arguments you put into matrix_build. It's the top-left 3x3 square of elements (in the array, elements 0-2,4-6 and 8-10) that make up the 3x3 matrix more commonly dealt with outside the realm of computer programming. There's stuff online about how to make a 3x3 matrix for rotating (at wikipedia for instance) and if we ask matrix_build to make a matrix that rotates by a certain angle, it works out a bunch of sines and cosines and puts them into the relevant elements. So one argument can affect more than one element of the matrix - just by specifying rotations, you can set nine elements of the matrix, and the position affects a few more. The right column is, I think, always 0,0,0,1 in our matrix, but the camera matrix can be more complicated. The link I reference in a comment in the rotation code has some examples of what these 4x4 matrices look like, and how the translations rotations and scale affect them.

With the scaling included:
A normal sized cube. When '4' is pressed, nothing happens.
That's because the cubies now have x-coordinates greater than 1, but the rotation code is still only trying to rotate those with an x-coordinate equal to 1.
Code:
   var coord = 1; // This is to say which slice to rotate - I'm going to rotate any cubie that has an x-coordinate of 1
You'll want to multiply that coord by cubit_size.

When '4' is pressed the cubies in the left slice (positive x-axis) are rotated around on the spot (individual x-axis, not the cubes x-axis).
Hmm. I don't have a lot of time today to experiment with the math, but try changing:
Code:
cubie[c] = matrix_multiply(rotation,temp);
to:
Code:
cubie[c] = matrix_multiply(temp,rotation);
In my head, that seems plausible.
 
Last edited:
C

Chubb1337

Guest
matrix_build doesn't take the actual numbers that go into the matrix - it takes arguments about whether you want the matrix to make a rotation or translation or scaling. For example, the bottom right element of the 4x4 matrix (element 15 in the array) will always be 1, no matter what you arguments you put into matrix_build. It's the top-left 3x3 square of elements (in the array, elements 0-2,4-6 and 8-10) that make up the 3x3 matrix more commonly dealt with outside the realm of computer programming. There's stuff online about how to make a 3x3 matrix for rotating (at wikipedia for instance) and if we ask matrix_build to make a matrix that rotates by a certain angle, it works out a bunch of sines and cosines and puts them into the relevant elements. So one argument can affect more than one element of the matrix - just by specifying rotations, you can set nine elements of the matrix, and the position affects a few more. The right column is, I think, always 0,0,0,1 in our matrix, but the camera matrix can be more complicated. The link I reference in a comment in the rotation code has some examples of what these 4x4 matrices look like, and how the translations rotations and scale affect them.
Ah-hah! I see, so what is entered in the function isn't directly what is entered in the matrix itself, that does explain it.

That's because the cubies now have x-coordinates greater than 1, but the rotation code is still only trying to rotate those with an x-coordinate equal to 1.
Code:
   var coord = 1; // This is to say which slice to rotate - I'm going to rotate any cubie that has an x-coordinate of 1
You'll want to multiply that coord by cubit_size.
Yep, that fixed it! 3x3 full scale does take input now :) Now that I know, feels totally logical..

Hmm. I don't have a lot of time today to experiment with the math, but try changing:
Code:
cubie[c] = matrix_multiply(rotation,temp);
to:
Code:
cubie[c] = matrix_multiply(temp,rotation);
In my head, that seems plausible.
Don't worry, you have been helping me so much in understanding and explaining, infact, I tried this, and it works! This shows again that order of matrix multiplication does indeed matter I guess ;)

So, the current situation is:
I can rotate a 3x3 (full scale) cube around the z-axis and x-axis sides, and it behaves like an actual 3x3 would! Next up is defining the other layers; counter-clockwise/clockwise rotation; making the not outside facing sides black; animations for rotating; devising a way to adjust the coord var to automatically fit to the cube_size (now only 3x3 works, or 5x5 next to outer-layers).

I should be capable of that. If not (or if other problems arise), I'll post here again :)

Thank you all so much and especially @dphsw for all the help you've given me already!
-Chub

P.S. Just a fun fact, the max sized cube I can handle with a solid centre is 5x5, at sizes 6x6 or higher I get this message:
___________________________________________
############################################################################################
FATAL ERROR in
action number 1
of Draw Event
for object oCube:

Fatal Error: Can not create vertex buffer of size 147456 bytes
4148 vertex buffers allocated with total size of 922224 KB
at gml_Script_d3d_draw_box (line 26) - d3d_draw_floor(x1, y2, z2, x2, y1, z2, tex6, hrep, vrep);
############################################################################################
--------------------------------------------------------------------------------------------
stack frame is
gml_Script_d3d_draw_box (line 26)
called from - gml_Object_oCube_DrawEvent_1 (line 129) - d3d_draw_box(0.5, 0.5, 0.5, -0.5, -0.5, -0.5, txW, txB, txO, txG, txR, txY, 1, 1);

But if I make the centre hollow, I get the message at cube size of 7x7 :D (still smaller than I'd had hoped, is there a way to avoid this error?)
 
Last edited by a moderator:

dphsw

Member
P.S. Just a fun fact, the max sized cube I can handle with a solid centre is 5x5, at sizes 6x6 or higher I get this message: ... But if I make the centre hollow, I get the message at cube size of 7x7 :D (still smaller than I'd had hoped, is there a way to avoid this error?)
Googling for this error, I see a few people having it when they try to draw a lot of stuff, including people who aren't doing any 3D stuff, just normal 2D draw functions. It's using up too much memory because of the amount of stuff it's trying to draw, although the amount of cubes shouldn't really require as much memory as it's using - GameMaker seems to allocate this memory quite inefficiently.
One way to improve the problem might be to put all the cubes into a single vertex buffer using the 'draw primitive' functions. It would be very complicated though, and it's only a guess as to whether it would actually help.
The only other thing is to reduce the amount of stuff being drawn, which is why making the cubes hollow helps. Also, is it possible the loop that I wrote that draws all the cubies is inside the loop that you wrote first? If it's going through one loop inside another, then for 6x6x6 or 216 cubies, it'll be be trying to draw 216 cubies 216 times, or 46656 cubies. If that's happening, then changing that would certainly help! (Even though it should really be able to handle 40000 cubies without running out of memory, but as I say, GameMaker seems to be bad at this.)
Otherwise, you might be able to reduce the amount of stuff you need to draw by thinking about what's facing the camera - as well as the cubies on the inside, any cubies that are currently facing away from the camera don't really need to be drawn either. Neither do the back three faces of the front cubies. Neither of those solutions are simple to implement though. They're possible solutions if nothing else works, but hopefully it'll turn out it was a loop-in-a-loop, and changing that will make it all happy!

BTW, I'm beginning to suspect a reason for this. To draw a wall or floor as you do in your draw_box function, I imagine a reasonable vertex buffer size would be about 144 bytes (4 vertices, 9 pieces of information (usual GameMaker format is x,y,z coords, x,y texture coords, and RGBA colour info even though you aren't using them) and each piece of information is a 4-byte floating-point number - 4x9x4 = 144 bytes ). GameMaker is complaining that it "Can not create vertex buffer of size 147456 bytes". 144*1024=147456. I think some internal function in GameMaker is supposed to allocate 144 bytes, but is actually trying to allocate 144 Kilobytes. Doing that 6 times for every cubie will eat up nearly a Megabyte of memory for every cubie, and will soon consume your RAM for a large Rubiks cube. I'll try to report this suspicion to the GameMaker team.

(I've submitted a report. I mentioned in there that maybe the buffer is bigger to allow batching, and yet it clearly isn't happening. It occurs to me now that maybe the batching isn't happening because you're changing the texture in between every face you draw. Try changing your box-drawing function to draw all faces using the same color - if that allows you to draw much bigger cubes, we'll know that's the problem, and there will be some different solutions to that.)
 
Last edited:
C

Chubb1337

Guest
Also, is it possible the loop that I wrote that draws all the cubies is inside the loop that you wrote first? If it's going through one loop inside another, then for 6x6x6 or 216 cubies, it'll be be trying to draw 216 cubies 216 times, or 46656 cubies. If that's happening, then changing that would certainly help!
This is not the case. It was when I first tried your code, but this wouldn't even allow me to draw the 3x3, I soon figured out what the problem was and got rid of the other loop. In fact, I've gotten rid off all my other code except for the bits that are required with the current way of using matrices. So unfortunately this is not the easy fix ;)

Otherwise, you might be able to reduce the amount of stuff you need to draw by thinking about what's facing the camera - as well as the cubies on the inside, any cubies that are currently facing away from the camera don't really need to be drawn either. Neither do the back three faces of the front cubies. Neither of those solutions are simple to implement though. They're possible solutions if nothing else works, but hopefully it'll turn out it was a loop-in-a-loop, and changing that will make it all happy!
I've already cut down on the amount of drawing by making the cube truly hollow (only the outside faces are being drawn, not even whole cubies). This now allows me to go up to about a 13x13 cube, although it does start dropping the FPS at that point. A downside to getting rid of all but the outside faces is that, during the rotation animation, you can now look inside the cube, so I should most likely draw some 'sliding faces' during rotating, my plan is to encase the rotating layer with a few dark coloured textured vertices, and one above and below (or left and right) in the static layers.

As for the not drawing of the faces that are not facing the camera, I had the same idea, I however have not tried this as of yet.

BTW, I'm beginning to suspect a reason for this. To draw a wall or floor as you do in your draw_box function, I imagine a reasonable vertex buffer size would be about 144 bytes (4 vertices, 9 pieces of information (usual GameMaker format is x,y,z coords, x,y texture coords, and RGBA colour info even though you aren't using them) and each piece of information is a 4-byte floating-point number - 4x9x4 = 144 bytes ). GameMaker is complaining that it "Can not create vertex buffer of size 147456 bytes". 144*1024=147456. I think some internal function in GameMaker is supposed to allocate 144 bytes, but is actually trying to allocate 144 Kilobytes.
Hmm that would be sloppy. Would it help if, instead of using 'draw_floor', I'd use d3d_vertex_texture?

Try changing your box-drawing function to draw all faces using the same color - if that allows you to draw much bigger cubes, we'll know that's the problem, and there will be some different solutions to that.)
Changing the function to draw an all white faces cube got me up to 15x15 where it crashed (worked my way up from 3, 9, 10, 11, 12, 13, 14 (lagging), 15 (crash).
Using the function to draw a normal six colour faces cube, it also got me up to 15x15 (3, 9, 10, 11, 12, 13, 14 (lagging again), 15 (crashing again). So either the way I changed it was incorrect or it makes no difference.

I'll try to delve into the d3d_vertex_texture a bit and see if that helps or not.


EDIT: I have added the d3d_vertex_texture, the d3d_draw_box function now looks like this:

Code:
///d3d_draw_box(x1, y1, z1, x2, y2, z2, tex1, tex2, tex3, tex4, tex5, tex6, hrep, vrep, texture_array, cubie_num);

// Arguments
var x1, x2, y1, y2, z1, z2;
x1 = argument0;
y1 = argument1;
z1 = argument2;
x2 = argument3;
y2 = argument4;
z2 = argument5;

var tex1, tex2, tex3, tex4, tex5, tex6;
tex1 = argument6;
tex2 = argument7;
tex3 = argument8;
tex4 = argument9;
tex5 = argument10;
tex6 = argument11;

var hrep, vrep;
hrep = argument12;
vrep = argument13;

var temp_array;
temp_array = argument14;

var cubie_num;
cubie_num = argument15;

// Draw box
if (temp_array[cubie_num, 2]){
    //d3d_draw_floor(x1, y1, z1, x2, y2, z1, tex1, hrep, vrep);
    d3d_primitive_begin_texture(pr_trianglefan, tex1)
    d3d_vertex_texture(x1, y1, z1, 0, 0);
    d3d_vertex_texture(x2, y1, z1, 1, 0);
    d3d_vertex_texture(x2, y2, z1, 1, 1);
    d3d_vertex_texture(x1, y2, z1, 0, 1);
    d3d_primitive_end();
}
if (temp_array[cubie_num, 5]){
    //d3d_draw_floor(x1, y2, z2, x2, y1, z2, tex6, hrep, vrep);
    d3d_primitive_begin_texture(pr_trianglefan, tex6)
    d3d_vertex_texture(x1, y2, z2, 0, 0);
    d3d_vertex_texture(x2, y2, z2, 1, 0);
    d3d_vertex_texture(x2, y1, z2, 1, 1);
    d3d_vertex_texture(x1, y1, z2, 0, 1);
    d3d_primitive_end();
}
if (temp_array[cubie_num, 4]){
    //d3d_draw_wall(x1, y2, z1, x2, y2, z2, tex3, hrep, vrep);
    d3d_primitive_begin_texture(pr_trianglefan, tex3);
    d3d_vertex_texture(x1, y2, z1, 0, 0);
    d3d_vertex_texture(x2, y2, z1, 1, 0);
    d3d_vertex_texture(x2, y2, z2, 1, 1);
    d3d_vertex_texture(x1, y2, z2, 0, 1);
    d3d_primitive_end();
}
if (temp_array[cubie_num, 0]){
    //d3d_draw_wall(x1, y1, z1, x1, y2, z2, tex2, hrep, vrep);
    d3d_primitive_begin_texture(pr_trianglefan, tex2);
    d3d_vertex_texture(x1, y1, z1, 0, 0);
    d3d_vertex_texture(x1, y2, z1, 1, 0);
    d3d_vertex_texture(x1, y2, z2, 1, 1);
    d3d_vertex_texture(x1, y1, z2, 0, 1);
    d3d_primitive_end();
}
if (temp_array[cubie_num, 3]){
    //d3d_draw_wall(x2, y2, z1, x2, y1, z2, tex4, hrep, vrep);
    d3d_primitive_begin_texture(pr_trianglefan, tex4);
    d3d_vertex_texture(x2, y2, z1, 0, 0);
    d3d_vertex_texture(x2, y1, z1, 1, 0);
    d3d_vertex_texture(x2, y1, z2, 1, 1);
    d3d_vertex_texture(x2, y2, z2, 0, 1);
    d3d_primitive_end();
}
if (temp_array[cubie_num, 1]){
    //d3d_draw_wall(x2, y1, z1, x1, y1, z2, tex5, hrep, vrep);
    d3d_primitive_begin_texture(pr_trianglefan, tex5);
    d3d_vertex_texture(x2, y1, z1, 0, 0);
    d3d_vertex_texture(x1, y1, z1, 1, 0);
    d3d_vertex_texture(x1, y1, z2, 1, 1);
    d3d_vertex_texture(x2, y1, z2, 0, 1);
    d3d_primitive_end();
}
The result however is unchanged, going from 3 to 9, 10, 11, 12, 13, 14 (lagg), 15 (crash).
 
Last edited by a moderator:
Top