Geeks With Blogs

News


Timmy Kokke's Blog

↑ Grab this Headline Animator

Timmy Kokke at Blogged
Timmy Kokke …just sorting my bubbles…

Introduction:

When I read about the Mix09 10k Challenge I immediately thought of doing something with 3d. I’ve started programming simple, rotating 3d applications years ago, in the MS-DOS era. In those days, basic 3d was used a lot in demos and intros. And, because Silverlight doesn’t support 3d natively (yet), I made it my own challenge to see if I use my experience with 3d to fit a 3d engine in 10k.

Instead of using the original Rubik’s Cube colors, I chose to use the Mix09 colors. The colors of the cube are taken from the Mix09 site.

I thought long and hard about the user interaction. I decided to made only 1 side of the cube turn at a time and not rotate every single slice of the cube. This would have made the cube easier to operate, but made it impossible to shrink it down to less than 10k. The only mistake I made in this was that I didn’t change the cursor in the surrounding areas of the cube. Only a few people discovered that the whole cube can rotate, and that it’s actual 3D and not a smart animation trick.

I wrote the entire application with possible optimizations in mind. This caused a lot of bad practices and strange constructions in the unoptimized version of the application. For example the Cvs class, which inherits from Canvas. The only use for this class is so that I don’t have to write “Canvas” every time I need a Canvas. Later I renamed it “C”. This saves 5 bytes every time I use Canvas.

Design Of The Application:

A default Silverlight application uses two xaml files with code behinds, app.xaml and page.xaml. This creates a lot of code, that it useful in a “real” appilcation, but isn’t necessary in this case. So I made the first optimizate decision right at the start of the project. I didn’t use any xaml files. I wrote an article for CodeProject about not using xaml in a Silvelight application, in case you want to know more about this.

The entire application is build around 6 classes:

ClassDiagram1

The App class the main entrypoint of the application. It is responsible for the generation of the cube, the event handling for user interaction and for updating the cube and texts on screen.

The Poly3D class is responsible for one little square in the cube. It calculates the positions of each corner of that square and is able to render it to a 2D canvas. In this class also contains a map of the cube in which the colors are stored, for optimization reasons it’s in here and not in another class. The same goes for the actual colors itself, with corresponding RGB values.

The Face class hold information about one of the six sides of the cube and the way the cube needs to be updated after rotating.

The Digit class is responsible for rendering numbers to the 7-led type font.

The Vector3D and Matrix3D classes are necessary for the 3d calculations.

Generating The Cube:

The first thing the application does is generating the cube, in it’s solved form.

public void GenerateCube()
        {
            CreateP(GenerateFrontBack, -.93,
                           new int[] { 3, 4, 5, 15, 16, 17, 27, 28, 29 }, 0);
            Faces.Add(new Face()
            {
                Faces = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 
                                   19, 21, 25, 28, 30, 34, 37, 
                                   39, 43, 46, 48, 52 },
                Becomes = new int[] { 0, 7, 3, 5, 8, 6, 2, 4, 1, 
                                     43, 39, 37, 52, 48, 46, 
                                     28, 30, 34, 19, 21, 25 },
                RotationVector = NewVec(0, 0, -1)
            });
    ...
};

The CreateP method creates 1 side of the cube, which is made out of 9 squares. It’s calles 6 times with different parameters. The first parameter is a delegate to one of three methods to generate the sides. Because on each side of the cube only 2 of the 3 dimensions are used, I pass the Z as a parameter. 3 Different mothods are used as delegates.

Poly3D GenerateFrontBack(Vector3D vector1, Vector3D vector2, int color, int face){ ... }
Poly3D GenerateLeftRight(Vector3D vector1, Vector3D vector2, int color, int face){ ... }
Poly3D GenerateTopBottom(Vector3D vector1, Vector3D vector2, int color, int face){ ... }

The first method generates the squares on the cube for all different X- and Y-position with Z-positions of 1 or –1, thus the frond and back. The second method does the same for all different X- and Z-postions with Y-positons of 1 and –1, the left and right. And the thirs, as you might guess, generates the squares for every Y- and Z-postions with X-postition of 1 and –1, the top and bottom.

The CreateP methods also takes an array of integer as a parameter. This array contains the locations of the colors in the colormap for the squares on that side of the cube.

All CreateP does is calling the delegate 9 times, 1 for each square, with different parameters.

Because every square must react to MouseEnter, MouseLeave and Click events, I create one method that is called when instanciating a Poly3D to add the event handlers. This method takes a Poly3D and return a Poly3D. The CreatePolyEvents method is called from each of the 3 delegate functions and adds the same event handlers for everything.

Poly3D CreatePolyEvents(Poly3D poly){ ... }

For every square of the cube two a new Vector3D must be instanciated, the top-left and the bottom-right. To be able to write this with less characters in the optimized version I created a method that take 3 doubles, X, Y and Z and returns a new Vector3D.

Vector3D NewVec(double x, double y, double z) { return new Vector3D(x, y, z); }

After generating the squares, an instance of Face is added to the list of faces. It takes a list of integers which represents the little squares influenced by that face. This is needed to make that side, including the neighboring squares to grow and rotate. The second list of integers hold the positions in the color map which every square needs to become after rotating. I will go into more detail about this when explaining the animations. The last parameter is just to initialize the rotation and to prevent exceptions.

Directly after generating the cube, and before rendering, I scamble the cube. I wanted to make absolutely sure that the cube is solvable, without the need to make my application capable of solving the cube. Therefor I randomly pick a side and rotate that. Just as you would with a normal cube. And repeat this 25 times. I use the same code that is used when playing with the cube. Because it’s not in the game-loop, it’s fast and invible.

Updating,Animating,Rendering:

The game-loop is responsible for updating the screen, animating the cube and rendering the graphics. It is attached to the Tick event of a DispatcherTimer and is called at very small interval. All that does is calling the Update method and looping thru all 54 polygons and render those.

The first thing the Update method does is checking if the user is dragging the mouse across the canvas. If it does, the cube needs to be rotated. The mouse can only move in 2 directions, so only 2 axis of cube can be rotated. Due to optimizations to fit the 10k, I had to restict the axis of rotation. That’s why the cube seems to mirror the mouse movement when it’s upside down. I adjust one of the axis in the rotation to compensate this a bit.

Next, the Update method checks if there is a Face that needs to be animated. If there is, the animation is calculated. I use a counter that counts down the animation. It starts at 90 degrees and counts down to 0. Every time the update method is called, this counter is lowered a certain amount. The growing and shrinking of the rotating section of the cube, uses this same counter also.

If this counter reaches 0, the counter is resetted to 90, the moves-counter is incremented, the colormap is updated by using the “becomes”-array of the Face and the rotations are set back to 0. This is not noticible and doesn’t require a lot of code. By doing it this way, I can use the same rotation over and over without getting complexity in the polygons. They do not move to a new position on the cube, only the coloring of the cube changes.

Last thing the Update method does is rendering the time played and the number of moves to the screen using the Digit class.

The polygons rendered by calculating the 2D screen positon of all 4 3D cornerposition and than update a basic Silverlight Poly on a Canvas. When the Poly3D class is constructed, it add a Silverlight Polygon to a Canvas. Every time the Poly3D.Render method is called, the Polygon on the Canvas is updated. Because every corner has a Z-Position, I take the average and use that as Z-Index of the Polygon. That way the Polygons are sorted automaticly by Silverlight. The transparency comes as a gift from Silverlight. Where transparency is tough to do in “real” 3d, in this case it comes for free with the Silverlight Polygon and adds a lot of coolness to the game.

Conclusion:

With every decision I made programming SilverRubix I kept in mind that I needed to be able to shrink it down. Where maintainability normaly is important, size is all that mattered in this case. After uploading my entry to the contest I realized I had made a few mistakes. The biggest is the discoverability of the rotation of the cube. Almost no one saw that. All I had to do was changing the mousecursor and it was fixed.

I added the unoptimized code the this article, feel free to have a look at it or use it in your own projects. I have added picture of the optimized code below.

I had great time writing the cube. It’s nice to use bad practices for good once in a while.

I want to thank everybody who voted in this contest and for all great responses.

Thank you!

10K

Click Source of SilverRubix to get the original unoptimized version.

Posted on Monday, February 23, 2009 1:32 PM | Back to top


Comments on this post: SilverRubix: The Details

# re: SilverRubix: The Details
Requesting Gravatar...
A long time ago (about a year) I wrote such application for MSDN. Please check
http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=WpfRubiks&ReleaseId=555

Best regards
Agha Khan
Left by Agha on Feb 23, 2009 8:53 PM

Your comment:
 (will show your gravatar)


Copyright © Timmy Kokke | Powered by: GeeksWithBlogs.net