The search for the perfect bezier tweening syntax

原文地址:http://zehfernando.com/2007/the-search-for-the-perfect-bezier-tweening-solution/
The search for the perfect bezier tweening syntax

Undoubtedly, the biggest challenge when implementing some feature on Tweener is deciding on the syntax to be used – that is, doing the syntax design itself. It can’t be too complex, or else it’ll be too convoluted to use, but it also can’t be too simple, or it won’t be able to do everything it could; it has to be flexible, but still be straightforward. The funny thing is that once the syntax is decided upon, the implementation follows easily; I’ve spent literally months deciding on how some features should work, then once the “Eureka!” moment arrives, it’s just a matter of a couple of hours until it’s implememented.

The most recent of such challenges was implementing beziér curves on tweenings. At a simplistic level, this feature allows for an object to slide on screen by following a curved path; at a more complex level, it allows any number of properties of a object to follow a complex ‘path’ of values until it reaches its destination.

 

In the most basic sense, this could easily be achieved with, say, a special property. Using Tweener, it could work like this, for a sliding based on a quadratic bezier curve path:

Tweener.addTween(myMC, {_x:10, _y:10, _controlX:20, _controlY:0, time:1, transition:"easeoutquad"});

That is, the _controlX and _controlY properties actually define the location of the control point of the bezier curve, as one would expect when using, say, the curveTo method from the actionscript drawing API.

At close analysis, however, this seemingly natural syntax poses two significant problems. For one, it uses special, separate properties for control points that are hardcoded into the engine – they should know in advance which properties should be modified: _x and _y, on this case. What would happen if, instead of tweening properties of a MovieClip instance, I was tweening properties of a custom StageWindow instance, with respective cameraX and cameraY properties? I’d need to create a new hardcoded special property just for my own custom classes.

One alternative solution would be to make a guess on what should be tweened: for example, “the first two properties to be defined on the tweening parameter object will be respectively considered to be x and y”. That would work on the above case and in many other cases where bezier tweening was needed on two dimensions. It would create new syntax rules which are not exactly healthy, though — on Tweener, the parameters order itself doesn’t matter.

The second problem is that it’s limited to one control point. Paths based on bezier curves can, instead, use several different control points, allowing a more complex path to be taken. For that, the above solution would need separate arrays, or additional control point variables, and it could become quite clumsy.

Hence why a more flexible solution is needed. I spent a good deal of thought on this matter, and I honestly believe I’ve reached a good compromise. So the final syntax for bezier transitions on Tweener is as such, just as an example:

Tweener.addTween(myMC, {_x:10, _y:10, _bezier:{_x:20, _y:20}, time:1, transition:"easeoutquad"});

That is, the tweening or sliding is done the same way it’s always done – by using _x and _y properties, or whatever’s the norm at that specific language version or Class – and a bezier point can be defined by the special property named _bezier, which contains one object with the properties that define its position.

Here’s the first good point: by using the destination property names themselves when defining the control point, the bezier code already knows what to tween and how. You don’t need any other special parameter rule when writing the code – you could tackle an _alpha tweening there on the same object and it would still work – and, specially, you can use whichever property you need when doing a bezier tweening. So, for example, if you had my hypothetical StageWindow instance there, you can still tween it by using this syntax:

Tweener.addTween(myStageWindow, {cameraX:10, cameraY:10, _bezier:{cameraX:20, cameraY:20}, time:1, transition:"easeoutquad"});

That is, you just need to define your points based on the properties you actually want to tween.

The second good point is that, because of the way the _bezier special property is used – it’s only one property – it’s easier to allow several different points to be used. This is done, obviously enough, by allowing this special property to use an array of objects as its parameter, instead of just an object. So to make an object do a path following two bezier curves (or a curve defined by two quadratic bezier curves), it would be like so:

Tweener.addTween(myMC, {_x:10, _y:10, _bezier:[{_x:20, _y:20}, {_x:30, _y:30}], time:1, transition:"easeoutquad"});

Obviously this can be split into several different lines, it’s the same thing.

var myPath:Array = new Array();myPath.push({_x:20, _y:20});myPath.push({_x:30, _y:30});Tweener.addTween(myMC, {_x:10, _y:10, _bezier:myPath, time:1, transition:"easeoutquad"});

Anyhow, I probably write too many boring details though; it’s better to show it working. So check it out, the Tweener bezier example, appropriately named Bezier Maker:

Download the source here (Flash 8, AS2) or here (Flash CS3, AS3).

To operate it, click and drag each point to change the path origin, destination, or bezier control points, and to better understand how it works. Clicking on “Add Bezier Point” adds a point next to the currently selected point; clicking on “Remove Point” removes the currently selected point. For demonstration purposes, the code one would use for such animation is generated at the bottom. The animation time is set to 1 second. Try adding more points and noticing how the path adapts to the control points, and how the ball travels through the path.

From this example, it’s also possible to notice one quasi-caveat of quadratic bezier curves: depending on how you setup your control points, or how your curves are laid out, it will change the speed of travel an object could have when sliding through the path. You can notice this happens by looking at the circles distributed among the curves, when you create a very acute curve: if the circles are too close to each other on a specific point of the path, that means a point of deceleration. Code wise, there are ways to avoid that kind of behavior and provide a normalized travel speed over the curved path; this will probably show up on Tweener as an optional bezier setting on the future. Right now, however, this kind of bezier-driven speed is the way it’s supposed to work by default.

And at last, but not least, the third and final good point of this bezier syntax is that by allowing a loose object to define the bezier control points, the code is freed from the obligation to do a bezier on two properties (or dimensions) and can instead apply a bezier to one, two, three, or more dimensions. So what this means is that the same code that can be used to slide a MovieClip on screen can be used to change its _rotation value only, or even change the X, Y and Z properties of a Camera object on a 3d projection. So considering the previous bezier tweening syntax, the syntax for such task would be simple enough:

Tweener.addTween(myCamera, {x:10, y:10, z:10, _bezier:{x:20, y:20, z:30}, time:1, transition:"easeoutquad"});

This is almost accidental in design – the syntax wasn’t built with any specific class or rendering engine in mind – but shows how a flexible syntax approach can just happen to fit well into other classes that make use of numeric properties. Once again, this is better shown than explained, so check this out, another example using Papervision3D:

Download the source here (Flash CS3, AS3) or here (Flash 9 alpha preview, AS3). But again, it does not contain the Papervision3D source, as it’s not publicly released; more information on how to get the beta here.

This example operates similarly to the previous one; the difference is that it has to use three different properties for tweening, and as such, it has a few more viewports to allow setting each of the points on a 3d world. Also notice you can not only drag the points around on the bottom viewports, but also the cone objects themselves. It’s also a bit crude as it’s just a quick example, so you’ll notice you can drag elements outside of the viewports, and there are other small user interface issues.

On the question of cameras, however, one interesting point must be brought up. Moving the camera around the space is just one of the steps needed for this kind of animation; moving the camera target around is another story. On 3d views, this can usually be done by aligning the rotation of the camera according to the path traveled (on all axis) and thus achieving the desired effect. This requires some more math, though, so on the above example, a little trick is used: instead of trying to calculate the angles of the camera in real time in a way that makes it stay aligned to the path, what is done is that the camera target itself is also tweened, but ahead of the camera in time. This means that both the camera and the target are running on the same rail, but because the camera is delayed, the effect of alignment is achieved because the camera is always “looking” a little bit further its own path (this is also what causes the slight movement delay when the travel starts). Animating the target with a second, different path would allow better movement control, but I’ll let that open as a suggestion for people who download the code. Anyhow, to test the difference between the two types of camera target, click the “toggle target type” button on the above example; it toggles between a fixed target (on the center of the world) and a target that follows the path a little bit ahead in time. The generated code is also updated to match these options.

To be honest, I think that for real, professional, tight, super-controlled camera control on 3d extensions such as Papervision3D, another solution must be created; something based on curves copied straight out of a 3d editing package, maybe something that parses a collada spline to create all needed points and then tween the position of a camera; or maybe a maxscript (or similar) script that outputs a number of points from a spline in the form of points that allow interpolation. As a quick solution for many cases, however, this simple bezier tweening will do.

Is this the perfect tweening syntax? It’s difficult to say. There are other mathematical solutions to the path problem which could be based on cubic (or higher level) bezier curves instead of the default quadratic ones; different approaches should be taken if the travel speed is to be maintained the same on the entire path, as it is dependent of all properties being tweened on any given path (the default quadratic bezier, on the other hand, allows all properties to be calculated separately); and there are different usage patterns that surely make for different syntactical solutions, as are almost most features on these tweening engines and classes. For me, however, this is the perfect boat to sail through curved streams, and it might also fit other people’s needs, so there it is.

Lastly, please notice this bezier feature has been added on the latest Tweener version, 1.25.53, only available through the subversion repository. This means the zipped “stable” download version is a bit older (1.24.51), and will be like that for a few days, until the new version has been tested enough. This version is now stable and can be downloaded from the regular download page.

posted @ 2010-06-11 10:46  rob_2010  阅读(209)  评论(0编辑  收藏  举报