Dynamic Animated Tile Layer in Bing Maps

I attempted to create an animated weather map in Bing Maps by using a custom tilelayer that pointed to a set of dynamically-generated animated GIF image tiles. Note that the tilesource itself never changed – it was the tile images which the tilelayer pointed to that were animated, rather than the tilesource itself. There are a number of limitations with this approach, since there is no way in the client application to control the animation – it just repeats in a fixed loop and, because each tile is loaded and animated independently, there is a risk for the animations frames to become out-of-sync.

In order to have control over the animation, we don’t want the frames to be hard-baked into an animated GIF, but rather we want to be able to refresh a tile layer programmatically, updating the underlying image source based on some client event or after a specified time interval. This is what I’ll look at it this post.

Updating the Tile Source of an Existing Tile Layer?

To change the properties of an existing tile layer, you use the setOptions() method of the tile layer, passing in the same TileLayerOptions structure as you do when constructing a new layer:

tilelayer.setOptions(tileLayerOptions);

The problem is that, although the TileLayerOptions object defines a tilesource property that can be passed to the setOptions() method (and there is nothing in the documentation to suggest otherwise), it seems that you can’t change the tilesource of an existing tilelayer – any tilesource specified in the TileLayerOptions supplied to setOptions() is simply ignored. You can verify this quite easily using the following code (which also demonstrates the undocumented getVisible() method of the TileLayer class):

// Declare a set of tile layer options
var tileLayerOptions = {
  mercator: new Microsoft.Maps.TileSource({
    uriConstructor: 'http://mapsys.info/wp-content/uploads/2011/05/25b2588fcbtile1.jpg.jpg'
  }),
  opacity: 0.1,
  visible: false,
  zIndex: 20
}
// Declare another set of tile layer options
var tileLayerOptions2 = {
  mercator: new Microsoft.Maps.TileSource({
    uriConstructor: 'http://mapsys.info/wp-content/uploads/2011/05/940347c14ftile2.jpg.jpg'
  }),
  opacity: 0.2,
  visible: true,
  zIndex: 25
};
// Create a tile layer based on the first options
var tilelayer = new Microsoft.Maps.TileLayer(tileLayerOptions);
// Change the options of the tile layer
tilelayer.setOptions(tileLayerOptions2);
// Check the current options of the tile layer
alert(
  'TileSource: ' + tilelayer.getTileSource('mercator').getUriConstructor() + 'n' +
  'Opacity: ' + tilelayer.getOpacity() + 'n' +
  'Visible: ' + tilelayer.getVisible() + 'n' +
  'ZIndex: ' + tilelayer.getZIndex()
);

The results of the previous code listing will demonstrate that the opacity, visibility, and z-index properties of the tilelayer have all been updated to reflect the changed options passed to the setOptions() method, but the tilesource property is still the original, unchanged rand_tile1.jpg file.

So, if the tilesource can only be defined at the point the tile layer is first created, we can’t animate an existing tilelayer. Instead, we have to create a new tile layer every time we want to display a new frame in the animation.

Approach #1 : Dropping and recreating a Tile Layer

A first approach at animating between frames might simply drop the existing tilelayer and then immediately create a new tilelayer in its place, advancing the source image to the next frame of the animation, something like this:

var frames = ['frame1.png', 'frame2.png', 'frame3.png', 'frame4.png'];
for(var i = 0; i < frames.length; i++) {
  DisplayFrame(frames[i]);
}

function DisplayFrame(frame) {
  // Remove the existing frame
  map.entities.clear();

  // Create a tilelayer showing the next frame
  var tilelayer = new Microsoft.Maps.TileLayer({
    mercator: new Microsoft.Maps.TileSource({
      uriConstructor: frame
    })
  });

  // Display the new frame on the map
  map.entities.push(tilelayer);
}

The problem here is that, because the current frame is removed before the next frame is generated, there will be a gap and/or probably some fairly horrible flickering between frames.

Approach #2 : Cycling through an array of tilelayers

You might be tempted to go to the other extreme and preload all of the frames into separate tilelayers before starting the animation. Suppose you had a 30 frame animation – you could create 30 different tile layers when the map first loads, with the tile source of each layer pointing to the tile images for each individual frames of the animation. Once all the layers had loaded, you could selectively show only one layer (visible: true) while hiding the others (by setting visible: false) to create a smooth animation between the frames. Something like this:

// Add tilelayers for each frame in the animation
var frames = ['frame1.png', 'frame2.png', 'frame3.png', 'frame4.png'];
for (var i = 0; i < frames.length; i++) {
  var tilelayer = new Microsoft.Maps.TileLayer({
    mercator: new Microsoft.Maps.TileSource({
      uriConstructor: frames[i]
    }),
    visible: false
  });
  map.entities.push(tilelayer);
}

// Make chosen frame visible
function DisplayFrame(frame) {
  for (var i = 0; i < map.entities.getLength(); i++) {
    if (i == frame) {
      map.entities.get(i).setOptions({ visible: true });
    }
    else {
      map.entities.get(i).setOptions({ visible: false });
    }
  }
}

The problem with this second approach is that each new tile layer leads to many additional elements being placed in the DOM, many more image tiles being simultaneously downloaded, and having that many tile layers created at the same time would almost certainly choke the browser (as confirmed in a recent post on the MSDN forums, in which 10 simultaneous tile layers ceased a network).

Approach #3 : Buffering frames onto a queue of tile layers

The solution I went for was to try to create a “happy medium” somewhere in the middle of the previous two approaches – by creating a buffer of tile layers that queued up the next one or two frames of animation in advance, but didn’t preload all the frames.  Assuming the animation speed was kept relatively constant, I should be able to animate the frames that had been queued more smoothly than simply dropping and recreating a single layer, yet without requiring the resources to preload every frame in a separate layer.

To create my queue, rather than simply create an array of tilelayers, I decided to use aMicrosoft.Maps.EntityCollection structure. The first tilelayer in the entity collection would be set to visible: true and represent the frame of animation currently shown on the map. Subsequent visible:false tilelayers would be added for the immediate upcoming frames and pushed onto the end of the collection. When I wanted to display the next frame, I’d remove the first element from the collection and shuffle any other frames down the queue, setting the visible:true property on the new lowest frame. So my queue structure looked a bit like this:

var frames = ['frame1.png', 'frame2.png', 'frame3.png', 'frame4.png'];
var currentFrame = 2;
// Create the tile queue
var tileQueue = new Microsoft.Maps.EntityCollection();
// Add the current tile as the first (visible) entity in the queue
var currentTileLayer = new Microsoft.Maps.TileLayer({
mercator: new Microsoft.Maps.TileSource({
uriConstructor: frames[i]
}),
visible: true
});
map.entities.push(currentTileLayer);
// Queue up the next few tile layers
for (var i = 1; i < 3; i++) {
var nextTileLayer = new Microsoft.Maps.TileLayer({
mercator: new Microsoft.Maps.TileSource({
uriConstructor: frames[currentFrame + i]
}),
visible: false
});
map.entities.push(tilelayer);
}

Obviously, preloading subsequent frames onto the queue implies that the frames of animation will generally be being played in consistent, sequential order and I’m able to predict what the frame displayed in 2 or 3 frames time will be, in order to add it to the queue. If the user decided to jump to arbitrary points along the timeline, I wouldn’t have been able to queue the necessary frame in advance, and you’d get much the same behaviour from the queue as you would with a single tilelayer. Still, I decided that, for the most part, the queue would probably offer a benefit for the general use case of “playing” an animation.

I hoped that, by using the EntityCollection object rather than a generic array, I’d be able to make use of some of the Bing Maps events and methods to help with my animation handling, but more on that in a minute.

Wait for it… Wait for it…

The javascript setInterval() method repeatedly calls a specified function at a chosen interval in time, specified in milliseconds. So to automatically animate through my queue of frames my first thought was to use setInterval(x) to schedule a function to execute every x milliseconds, which would remove the current tile layer from the queue (by tileQueue.removeAt(0); ), and immediately display the next element from the queue in its place. I’d also create and push a new tilelayer onto the end of the queue, to maintain a minimum number of ‘lookahead’ frames – ready-created tilelayers that were ready to display.

However, simply because the tilelayers pointing at the next frames of animation had been created and added onto the entitycollection, it doesn’t necessarily mean that the tile images for that frame had finished downloading. If you set a fast frame rate, it’s perfectly possible that the function called by setInterval would churn through the tilelayers in the queue faster than they were ready to display. Although you’d still get some benefit from the fact that the tiles had been requested slightly earlier than they were required to be displayed, it would be nice to only advance to the next frame if we knew it was fully ready to be displayed.

In order to find out if the next frame in the queue was ready, I thought I could make use of the tiledownloadcomplete event, as described at http://msdn.microsoft.com/en-us/library/gg427609.aspx. My idea was that, rather than simply wait for a predefined time to have elapsed before setInterval() would display the next frame, I would also monitor the tiledownloadcomplete event to know that the images for the next frame were completely ready. That would mean that animating between frames should be seamless, requiring nothing more than to swap the opacity of the current foreground tilelayer and its replacement. This sounded good on paper but, after considerable time spent wrangling this into a working solution, I encountered a number of practical problems with the implementation:

  • Firstly, the tiledownloadcomplete event only fires on the map object itself, not on an individual tilelayer. Therefore, when trying to queue up more than frame in advance, I couldn’t separately identify that the next frame’s tiles had finished downloading, only that all tiles queued had finished downloading. This means that I’d have to limit my buffer to only look one frame ahead at any one time. Then, when the event fires, I’d know that both the current frame being displayed and the next frame were fully loaded. I could then safely start queuing the next tilelayer in the animation.
  • The second problem, which was more significant, is that the AJAX map control only downloads tiles that are actually visible on the map. Objectively speaking, this sounds like a perfectly sensible design decision – why would you want your browser to unnecessarily download image tiles for a tilelayer that you couldn’t even see? However, this presented a problem for the implementation of my “background” tilelayers – setting the properties of the queued tilelayers to either visible:false or opacity:0 meant that the tiles weren’t downloaded, and consequently the tiledownloadcomplete event never fired. To get round this, I set the opacity of my queued tilelayers to be nearly transparent (opacity: 0.01) and then, when they were to be displayed, turned the opacity of the new current frame to full opacity: 1.0.
  • The final, killer, problem with relying on the tiledownloadcomplete event is that, as its name suggests, it is a single event that fires once at the point that tiles required to display the current map image have finished downloading. However, I was trying to use it as a indicator that the tiles for a given frame of animation were ready to display at any given point in time. “Finished downloading” is not the same as “Ready to display”. This problem surfaced when I started to test looped animations – my queued frames would run beautifully through the first cycle, only changing when the next frame was fully ready to be rendered, as designed. Then, on the second loop, the tiledownloadevent would never fire, so my animation never advanced to the next  frame. This was, of course, because the tiles for the next frame in the queue had already been downloaded and cached, so were not being requested again when each tile was shown for the second time. I could have perhaps solved this problem by appending an incremental string onto the end of each tile request so that, when requested on each successive occasion, each tile would have a slightly different URL. However, to do so would negate the possibly of caching altogether (caching is, for the most part, a good thing of course – and there would be no reason for me not to cache tiles that were being re-used again and again on a short animation cycle).

Reluctantly, I couldn’t find a way to make tiledownloadcomplete work for my purposes to reliably tell me that the next frame in my queue was ready, so I decided to go back to Plan A and make do with simply waiting a chosen amount of time before changing frames, in the hope that the time spent on the queue will have provided enough opportunity for the needed tile images to be fully downloaded.

Android 3.0 Hardware Acceleration


One of the biggest changes we made to Android in this release is the addition of a new rendering pipeline so that applications can benefit from hardware accelerated 2D graphics. Hardware accelerated graphics is nothing new to the Android platform, it has always been used for windows composition or OpenGL games for instance, but with this new rendering pipeline applications can benefit from an extra boost in performance. On a Motorola Xoom device, all the standard applications like Browser and Calendar use hardware-accelerated 2D graphics.

In this article, I will show you how to enable the hardware accelerated 2D graphics pipeline in your application and give you a few tips on how to use it properly.

Go faster

To enable the hardware accelerated 2D graphics, open your AndroidManifest.xml file and add the following attribute to the tag:

    android:hardwareAccelerated="true"

If your application uses only standard widgets and drawables, this should be all you need to do. Once hardware acceleration is enabled, all drawing operations performed on a View’s Canvas are performed using the GPU.

If you have custom drawing code you might need to do a bit more, which is in part why hardware acceleration is not enabled by default. And it’s why you might want to read the rest of this article, to understand some of the important details of acceleration.

Controlling hardware acceleration

Because of the characteristics of the new rendering pipeline, you might run into issues with your application. Problems usually manifest themselves as invisible elements, exceptions or different-looking pixels. To help you, Android gives you 4 different ways to control hardware acceleration. You can enable or disable it on the following elements:

  • Application
  • Activity
  • Window
  • View

To enable or disable hardware acceleration at the application or activity level, use the XML attribute mentioned earlier. The following snippet enables hardware acceleration for the entire application but disables it for one activity:

    

    

If you need more fine-grained control, you can enable hardware acceleration for a given window at runtime:

    getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

Note that you currently cannot disable hardware acceleration at the window level. Finally, hardware acceleration can be disabled on individual views:

    view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

Layer types have many other usages that will be described later.

Am I hardware accelerated?

It is sometimes useful for an application, or more likely a custom view, to know whether it currently is hardware accelerated. This is particularly useful if your application does a lot of custom drawing and not all operations are properly supported by the new rendering pipeline.

There are two different ways to check whether the application is hardware accelerated:

If you must do this check in your drawing code, it is highly recommended to use Canvas.isHardwareAccelerated() instead of View.isHardwareAccelerated(). Indeed, even when a View is attached to a hardware accelerated window, it can be drawn using a non-hardware accelerated Canvas. This happens for instance when drawing a View into a bitmap for caching purpose.

What drawing operations are supported?

The current hardware accelerated 2D pipeline supports the most commonly used Canvas operations, and then some. We implemented all the operations needed to render the built-in applications, all the default widgets and layouts, and common advanced visual effects (reflections, tiled textures, etc.) There are however a few operations that are currently not supported, but might be in a future version of Android:

  • Canvas
    • clipPath
    • clipRegion
    • drawPicture
    • drawPoints
    • drawPosText
    • drawTextOnPath
    • drawVertices
  • Paint
    • setLinearText
    • setMaskFilter
    • setRasterizer

In addition, some operations behave differently when hardware acceleration enabled:

  • Canvas
    • clipRect: XOR, Difference and ReverseDifference clip modes are ignored; 3D transforms do not apply to the clip rectangle
    • drawBitmapMesh: colors array is ignored
    • drawLines: anti-aliasing is not supported
    • setDrawFilter: can be set, but ignored
  • Paint
    • setDither: ignored
    • setFilterBitmap: filtering is always on
    • setShadowLayer: works with text only
  • ComposeShader
    • A ComposeShader can only contain shaders of different types (a BitmapShader and a LinearGradientShader for instance, but not two instances of BitmapShader)
    • A ComposeShader cannot contain a ComposeShader

If drawing code in one of your views is affected by any of the missing features or limitations, you don’t have to miss out on the advantages of hardware acceleration for your overall application. Instead, consider rendering the problematic view into a bitmap or setting its layer type to LAYER_TYPE_SOFTWARE. In both cases, you will switch back to the software rendering pipeline.

Dos and don’ts

Switching to hardware accelerated 2D graphics is a great way to get smoother animations and faster rendering in your application but it is by no means a magic bullet. Your application should be designed and implemented to be GPU friendly. It is easier than you might think if you follow these recommendations:

  • Reduce the number of Views in your application: the more Views the system has to draw, the slower it will be. This applies to the software pipeline as well; it is one of the easiest ways to optimize your UI.
  • Avoid overdraw: always make sure that you are not drawing too many layers on top of each other. In particular, make sure to remove any Views that are completely obscured by other opaque views on top of it. If you need to draw several layers blended on top of each other consider merging them into a single one. A good rule of thumb with current hardware is to not draw more than 2.5 times the number of pixels on screen per frame (and transparent pixels in a bitmap count!)
  • Don’t create render objects in draw methods: a common mistake is to create a new Paint, or a new Path, every time a rendering method is invoked. This is not only wasteful, forcing the system to run the GC more often, it also bypasses caches and optimizations in the hardware pipeline.
  • Don’t modify shapes too often: complex shapes, paths and circles for instance, are rendered using texture masks. Every time you create or modify a Path, the hardware pipeline must create a new mask, which can be expensive.
  • Don’t modify bitmaps too often: every time you change the content of a bitmap, it needs to be uploaded again as a GPU texture the next time you draw it.
  • Use alpha with care: when a View is made translucent using View.setAlpha(), an AlphaAnimation or an ObjectAnimator animating the “alpha” property, it is rendered in an off-screen buffer which doubles the required fill-rate. When applying alpha on very large views, consider setting the View’s layer type to LAYER_TYPE_HARDWARE.

View layers

Since Android 1.0, Views have had the ability to render into off-screen buffers, either by using a View’s drawing cache, or by using Canvas.saveLayer(). Off-screen buffers, or layers, have several interesting usages. They can be used to get better performance when animating complex Views or to apply composition effects. For instance, fade effects are implemented by using Canvas.saveLayer() to temporarily render a View into a layer and then compositing it back on screen with an opacity factor.

Because layers are so useful, Android 3.0 gives you more control on how and when to use them. To to so, we have introduced a new API called View.setLayerType(int type, Paint p). This API takes two parameters: the type of layer you want to use and an optional Paint that describes how the layer should be composited. The paint parameter may be used to apply color filters, special blending modes or opacity to a layer. A View can use one of 3 layer types:

  • LAYER_TYPE_NONE: the View is rendered normally, and is not backed by an off-screen buffer.
  • LAYER_TYPE_HARDWARE: the View is rendered in hardware into a hardware texture if the application is hardware accelerated. If the application is not hardware accelerated, this layer type behaves the same as LAYER_TYPE_SOFTWARE.
  • LAYER_TYPE_SOFTWARE: the View is rendered in software into a bitmap

The type of layer you will use depends on your goal:

  • Performance: use a hardware layer type to render a View into a hardware texture. Once a View is rendered into a layer, its drawing code does not have to be executed until the View calls invalidate(). Some animations, for instance alpha animations, can then be applied directly onto the layer, which is very efficient for the GPU to do.
  • Visual effects: use a hardware or software layer type and a Paint to apply special visual treatments to a View. For instance, you can draw a View in black and white using a ColorMatrixColorFilter.
  • Compatibility: use a software layer type to force a View to be rendered in software. This is an easy way to work around limitations of the hardware rendering pipeline.

Layers and animations

Hardware-accelerated 2D graphics help deliver a faster and smoother user experience, especially when it comes to animations. Running an animation at 60 frames per second is not always possible when animating complex views that issue a lot of drawing operations. If you are running an animation in your application and do not obtain the smooth results you want, consider enabling hardware layers on your animated views.

When a View is backed by a hardware layer, some of its properties are handled by the way the layer is composited on screen. Setting these properties will be efficient because they do not require the view to be invalidated and redrawn. Here is the list of properties that will affect the way the layer is composited; calling the setter for any of these properties will result in optimal invalidation and no redraw of the targeted View:

  • alpha: to change the layer’s opacity
  • x, y, translationX, translationY: to change the layer’s position
  • scaleX, scaleY: to change the layer’s size
  • rotation, rotationX, rotationY: to change the layer’s orientation in 3D space
  • pivotX, pivotY: to change the layer’s transformations origin

These properties are the names used when animating a View with an ObjectAnimator. If you want to set/get these properties, call the appropriate setter or getter. For instance, to modify the alpha property, call setAlpha(). The following code snippet shows the most efficient way to rotate a View in 3D around the Y axis:

    view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    ObjectAnimator.ofFloat(view, "rotationY", 180).start();

Since hardware layers consume video memory, it is highly recommended you enable them only for the duration of the animation. This can be achieved with animation listeners:

    view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    ObjectAnimator animator = ObjectAnimator.ofFloat(
         view, "rotationY", 180);
    animator.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            view.setLayerType(View.LAYER_TYPE_NONE, null);
        }
    });
    animator.start();

New drawing model

Along with hardware-accelerated 2D graphics, Android 3.0 introduces another major change in the UI toolkit’s drawing model: display lists, which are only enabled when hardware acceleration is turned on. To fully understand display lists and how they may affect your application it is important to also understand how Views are drawn.

Whenever an application needs to update a part of its UI, it invokes invalidate() (or one of its variants) on any View whose content has changed. The invalidation messages are propagated all the way up the view hierarchy to compute the dirty region; the region of the screen that needs to be redrawn. The system then draws any View in the hierarchy that intersects with the dirty region. The drawing model is therefore made of two stages:

  1. Invalidate the hierarchy
  2. Draw the hierarchy

There are unfortunately two drawbacks to this approach. First, this drawing model requires execution of a lot of code on every draw pass. Imagine for instance your application calls invalidate() on a button and that button sits on top of a more complex View like a MapView. When it comes time to draw, the drawing code of the MapView will be executed even though the MapView itself has not changed.

The second issue with that approach is that it can hide bugs in your application. Since views are redrawn anytime they intersect with the dirty region, a View whose content you changed might be redrawn even though invalidate() was not called on it. When this happens, you are relying on another View getting invalidated to obtain the proper behavior. Needless to say, this behavior can change every time you modify your application ever so slightly. Remember this rule: always call invalidate() on a View whenever you modify data or state that affects this View’s drawing code. This applies only to custom code since setting standard properties, like the background color or the text in a TextView, will cause invalidate() to be called properly internally.

Android 3.0 still relies on invalidate() to request screen updates and draw() to render views. The difference is in how the drawing code is handled internally. Rather than executing the drawing commands immediately, the UI toolkit now records them inside display lists. This means that display lists do not contain any logic, but rather the output of the view hierarchy’s drawing code. Another interesting optimization is that the system only needs to record/update display lists for views marked dirty by an invalidate() call; views that have not been invalidated can be redrawn simply by re-issuing the previously recorded display list. The new drawing model now contains 3 stages:

  1. Invalidate the hierarchy
  2. Record/update display lists
  3. Draw the display lists

With this model, you cannot rely on a View intersecting the dirty region to have its draw() method executed anymore: to ensure that a View’s display list will be recorded, you must call invalidate(). This kind of bug becomes very obvious with hardware acceleration turned on and is easy to fix: you would see the previous content of a View after changing it.

Using display lists also benefits animation performance. In the previous section, we saw that setting specific properties (alpha, rotation, etc.) does not require invalidating the targeted View. This optimization also applies to views with display lists (any View when your application is hardware accelerated.) Let’s imagine you want to change the opacity of a ListView embedded inside a LinearLayout, above a Button. Here is what the (simplified) display list of the LinearLayout looks like before changing the list’s opacity:

    DrawDisplayList(ListView)
    DrawDisplayList(Button)

After invoking listView.setAlpha(0.5f) the display list now contains this:

    SaveLayerAlpha(0.5)
    DrawDisplayList(ListView)
    Restore
    DrawDisplayList(Button)

The complex drawing code of ListView was not executed. Instead the system only updated the display list of the much simpler LinearLayout. In previous versions of Android, or in an application without hardware acceleration enabled, the drawing code of both the list and its parent would have to be executed again.

It’s your turn

Enabling hardware accelerated 2D graphics in your application is easy, particularly if you rely solely on standard views and drawables. Just keep in mind the few limitations and potential issues described in this document and make sure to thoroughly test your application!

3ds Max 2011 One Project from Start to Finish

3ds Max is a uniquely powerful modelling program, yet one that can catch out both new comers and even advanced users out with its intricacies and workflows. Many tutorial style books are fundamentally flawed by assuming the reader is already familiar with various aspects of the software. Indeed, we remember well a book that stated ‘now complete this easy step’ only to leave us both frustrated and annoyed as we failed to grasp the instructions.

What therefore is needed is a book that illustrates the process of creating a 3D visualization project step by step – and we are pleased to say there finally is a book worthy of its title ‘3ds Max 2011, One Project from Start to Finish’.

The movie below details the model produced over 9 chapters and the good news is we have three copies to give away, full details at the end of the post:

The book has been designed to be useful for readers at all skill levels. The material is presented in a way that will engage advanced users while still being explicatory enough for beginners – which is great to see.

Covering 2D-3D Modelling, Terrain Creation, Tree Creation, Water Elements, Animation, Lighting, Rending, Particles Systems and more the book provides a fully structured guide.

If your only going to buy one 3D modelling book this year then 3ds Max 2011 – One Project from Start to Finish is simply the best option.

The publishers 3DATS have kindly provided us with 3 copies to give away to readers – simply retweet this post and we will pick 3 winners at random from the Twitter feed. Competition ends 28th February, so you have a week to enter.

The retweet button is at the top of the post, good luck, books will be shipped Tuesday 29th, each valued at $99.95.