View Full Version : Two Sided 3D Clip
Chimay
December 17th, 2008, 11:19 AM
For some reason I expected that with the new z property, rotationX, rotationY, rotationZ, Matrix3D etc... that they would have supported some sort of two-sided movieClip... something like:
MovieClip.front = 1;
MovieClip.back = 2;
// where front and back are the frames from the movieClip to use
Anyway... this code will give you a two sided MovieClip where frame 1 of your clip is the front and frame 2 is the back:
VIEW THE SWF (http://shapevent.com/source/two_sided.html)
DOWNLOAD FLA (http://shapevent.com/source/two_sided.zip)
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
box.rotationX = 200 - mouseY;
box.rotationY = mouseX;
makeTwoSided(box);
}
// assumes that frame 1 of the movieClip is the front and 2 the is back
function makeTwoSided(mc:MovieClip):void {
box.gotoAndStop(1);
var rx:Number=Math.abs(mc.rotationX%360);
var ry:Number=Math.abs(mc.rotationY%360);
var turnX:Boolean = (rx > 90 && rx < 270);
var turnY:Boolean = (ry > 90 && ry < 270);
if (turnX != turnY) {
box.gotoAndStop(2);
// make sure the "b" isn't backwards most of the time
box.scaleX = -1;
}
}
senocular
December 17th, 2008, 11:47 AM
That's not quite going to cut it.
Making a two-sided object with the new 3D properties is not exactly easy. In fact I haven't found a way to do it using the new 3D APIs - if it's even possible. The problem mostly relies in the perspective projection and how there is no way to tell what side of an object in 3D space is going to visible until the projection is applied to bring it into the 2D realm.
One thing you can do is to use 3 globaled points inside the object and check the winding of those points for the side. That make-2-sided function would look something like this:
function makeTwoSided(mc:MovieClip):void {
var p1:Point = mc.localToGlobal(new Point(0,0));
var p2:Point = mc.localToGlobal(new Point(100,0));
var p3:Point = mc.localToGlobal(new Point(0,100));
mc.gotoAndStop(((p2.x-p1.x)*(p3.y-p1.y) - (p2.y-p1.y)*(p3.x-p1.x)) < 0 ? 1 : 2);
}
biznuge
December 17th, 2008, 04:20 PM
what would happen in this case if the clip in question, or indeed the camera, were off centre? Not sure this would work for anything but the most simple of tests... :2c:
senocular
December 17th, 2008, 05:35 PM
what would happen in this case if the clip in question, or indeed the camera, were off centre? Not sure this would work for anything but the most simple of tests... :2c:
That's exactly why the original version doesn't hold up. The solution I provided accounts for this. Ex:
http://www.senocular.com/pub/kirupa/2sided3DMC.html
You can see with the original version, and the mc on the middle right, the frame (square, circle) changes before it fully rotates at that perspective. The bottom couple change individually when expected.
Chimay
December 17th, 2008, 07:07 PM
Thanks for the info senocular - not sure I fully grasp the winding method without playing with it for a few mins, but looks very cool.
Too bad its such a tricky thing to implement using movieClip properties, it seems to be one of the most common uses for this type of 3D.
cheers...
senocular
December 17th, 2008, 08:40 PM
I'm about to leave work in a little bit, but I'll try to provide a useful description of why that works when I get home :)
senocular
December 18th, 2008, 12:49 PM
(Sorry I didn't get this up last night; took longer than I expected ;))
Winding and Sides
A way to determine what side of a 3D object is currently being viewed is through path winding. Winding represents the direction in which a closed polygon shape is drawn. For winding to be determined, you need at minimum 3 points that, when drawn create a triangle. The winding for such a shape (L, M, N) is the direction in which that shape is drawn as you progress from point L to point M to point N (and effectively back to point L again).
A positively wound shape is a shape that is drawn in a clockwise direction. A negatively wound shape is a shape that is drawn in a counterclockwise direction.
http://www.senocular.com/pub/kirupa/twosidedplane_files/shape_winding_lmn.png
If we place a positively wound triangular shape on a 3D plane and rotate that plane in any way that shows it's reverse side, the winding of that shape from the perspective of the viewer, or as that shape is viewed in 2D space, will be reversed and become negative. This is natural since anything flipped over is the reverse of what it was originally.
http://www.senocular.com/pub/kirupa/twosidedplane_files/rotated_3d_lmn.png
The key here, is that this flipping occurs from the perspective of the viewer - as the 3D shape is being viewed in 2D space.
Now, if there was only a way to determine the winding direction of such a path in a 3D plane (as seen in 2D), you would be able to determine what side of that plane is currently visible. Luckily for us, there is a way to do exactly that. In fact, there are a number of ways (one of which, not the best, I describe in the 3D tutorial on kirupa.com). The standard way is to use the cross-product of vectors that make up 2 of a triangular path's sides and check the resulting z value. This is the very same technique used internally in Flash Player for the culling property in the new enhanced Drawing API in Flash Player 10 (Graphics.drawTriangles and GraphicsTrianglePath).
Determining Winding with Cross-product
A cross-product is an operation between two 3D vectors to produce another vector. In this context, vector represents a directional line projected into space - not to be confused with the Vector data type which is a typed Array. The vector resulting from a cross-product is perpendicular to the two original vectors.
http://www.senocular.com/pub/kirupa/twosidedplane_files/vectors_cross_product.png
The length, or magnitude of the cross-product is proportional to the magnitudes of the original vectors and the angle between them. As the angle gets smaller, so does the magnitude of the cross-product. If/when the vectors cross, the magnitude will be negative.
http://www.senocular.com/pub/kirupa/twosidedplane_files/magnitude_negative.png
The angle here is especially important as we apply this towards determining winding. Consider that winding triangle path in a 3D plane. That path is representative of 2 vectors in that plane. At any point where the plane flips to reveal its back side, those vectors, as they are perceived in 2D space, cross creating a negative magnitude.
What this means is that winding direction is inline with the magnitude of the cross-product from the vectors that define the path; when the cross-product magnitude is negative, so is the winding.
With that in mind, we can use three arbitrary points within a plane that define a triangular path, define vectors based on those points as they exist in 2D space from the perspective of the user's view, get their cross-product and check its magnitude to determine winding and in turn what side of the plane we can see.
Specifically, in terms of Flash, this means specifying points in a movie clip that will represent a winding path/two vectors, getting their global 2D positions with localToGlobal, and then performing the cross-product math to determine a resulting magnitude. For the cross-product, the basic applied formula for a cross-product C from vectors A and B (A×B) is:
Cx = AyBz - AzBy
Cy = AzBx - AxBz
Cz = AxBy - AyBx
Remember that a cross-product is between 3D vectors. The vectors we are obtaining from the movie clip are actually 2D based on global locations of the screen. This gives them x and y values, but not a z. For the cross-product then, z for these vectors is 0.
The fact that we're working from source vectors with a 0 z value is important for two reasons. For one, it greatly reduces our operations since in the formulas listed above, we are left with:
Cx = Ay*0 - 0*By = 0
Cy = 0*Bx - Ax*0 = 0
Cz = AxBy - AyBx
Which leaves only z to be calculated. As a result, and this is the second reason, magnitude is equal to the value of z. No extra calculations are required to obtain the magnitude since it lies directly on the axis. This gives us the following formula:
magnitude = Cz = AxBy - AyBx
And since winding is negative when magnitude is, we can determine our winding, and hence our side, by checking magnitude > 0.
So where do vectors A and B come from? We start with points in the 2D coordinate space of the movie clip being transformed in 3D. Those are brought out to global coordinates using localToGlobal which accounts for all transformations 3D and otherwise so we get those points as the user would see them on the screen. Its from these points that the vectors are derived. Vectors, however, are projections from the origin of a 3D coordinate space. For this to work with the points we've obtained from the movie clip, one of the points needs to represent the origin, and the other two the vectors.
http://www.senocular.com/pub/kirupa/twosidedplane_files/vectors_on_plane.png
So given points L, M, N, assuming L to be our origin, we get:
Ax = Mx - Lx
Ay = My - Ly
Bx = Nx - Lx
By = Ny - Ly
And of course for z, we have 0. But z isn't important in our calculations so it is being omitted. Plugging this into our magnitude formula we get
magnitude = (Mx - Lx)*(Ny - Ly) - (My - Ly)*(Nx - Lx)
And that is exactly what was used to determine the frame required to show in the updated makeTwoSided function, just with a little ternary trickery to do it all inline :)
var p1:Point = mc.localToGlobal(new Point(0,0));
var p2:Point = mc.localToGlobal(new Point(100,0));
var p3:Point = mc.localToGlobal(new Point(0,100));
var frontFacing:Boolean = ((p2.x-p1.x)*(p3.y-p1.y) - (p2.y-p1.y)*(p3.x-p1.x) > 0);
By checking whether or not that calculation returns a positive or negative number will let you know whether or not your movie clip is showing its front face or its back face. From that you can change the movie clip to show whatever you want, such as by changing its frame, or even behave in a different manner if you'd like.
Chimay
December 18th, 2008, 01:07 PM
While I was writing the below you posted the description :)
Played with it a bit this morning and read up on winding. I think I've got my head wrapped around it. But a description would be great. This is what I didn't grasp at first....
(p2.x-p1.x)*(p3.y-p1.y) - (p2.y-p1.y)*(p3.x-p1.x)
While googling around I learned that if the result of that calculation is greater than 0 the winding is counter clockwise, if it's less than zero its clockwise....
I also learned that this could be used to determine if point p3 is to the left of or to the right of an infinite line that passes through p1 and p2.... which actually helped to clarify my understanding of the winding...
Chimay
December 18th, 2008, 03:08 PM
I created a quick demo that uses senocular's winding technique along with z-sorting:
View Demo (http://shapevent.com/source/nav3d.html)
Download Source (http://shapevent.com/source/nav3d.fla.zip)
I learned the z-sorting technique here. (http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS18334A17-3F85-4d5a-ADB4-F5BF6196774C.html)
senocular
December 18th, 2008, 04:44 PM
I'm currently getting an error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at nav3d_fla::MainTimeline/zSort()
at nav3d_fla::MainTimeline/onRunNav()
Chimay
December 18th, 2008, 06:16 PM
That's odd. Not getting that error, maybe the source I zipped up was unsaved.... re-uploaded:
download source (http://shapevent.com/source/nav3d.fla.zip)
Thanks again for the in depth post about winding... it's easier to understand than the stuff I found via google.
senocular
December 18th, 2008, 06:56 PM
still getting the error. Are you testing with the debugger player? Thats how I can tell the errors are occuring.
Chimay
December 18th, 2008, 08:25 PM
I wasn't using the debug player...
(now that I am) There was a small bug... I was using this:
pInfo[i].plane.transform.getRelativeMatrix3D(this).positio n.z;
before altering the z property of any of the planes. I just set the planes z to -1 by default and that solved this issue. I've updated the source files. :)
View Demo (http://shapevent.com/source/nav3d.html)
Download Source (http://shapevent.com/source/nav3d.fla.zip)
senocular
December 18th, 2008, 08:32 PM
much better :)
And well done :D
birdwing
December 18th, 2008, 09:44 PM
Nice Chimay... although I have to read seno's description again :eek:
it was a bit over my head :worried:
But I shold understand it after reading it a few.... lot more times.
saxx
December 18th, 2008, 09:53 PM
That was really interesting to read, thanks sen.
Chimay
December 19th, 2008, 12:22 PM
much better :)
And well done :D
Nice Chimay...
Thanks :) ...
kirupa
December 21st, 2008, 02:14 AM
This is one of the best explanations of winding I've read as well! Really cool sen and chimay :)
senocular
December 21st, 2008, 12:04 PM
This is one of the best explanations of winding I've read as well! Really cool sen and chimay :)
Thanks! :pleased:
I'm going to go ahead and move it to Best Of.
soulwire
January 9th, 2009, 01:59 PM
Thanks for the explanation Sen. As ever it’s extremely useful and clear.
For convenience, I packaged your technique into a two sided Sprite class. The ideas is you give it a front and a back DisplayObject (like Chimay described) and it will take care of aligning them and setting their visibility (using the technique you described) as their position and rotation properties are updated.
It uses stage.invalidate() to manage the updates, so it’s pretty light on resources.
In case it proves useful to anyone who’s read Senocular’s explanation: Two sided 3D Sprite
(http://blog.soulwire.co.uk/flash/actionscript-3/two-sided-planes-in-flash-player-10/)
Thanks again for the tidy solution Sen... :)
Stratboy
January 14th, 2009, 04:13 PM
Hi! Thank you!!! REALLY important post this...
I've got 2 little questions.
1. Is there a special reson for what you choose that points? I mean 0,0; 0,100; 100,0;
why not for example 00; 0,50; 50,0? You know, I'm not a math man at all.. :(
2. Have you seen this post?
http://flashartofwar.com/2008/11/16/two-sided-plane-fp-10/#comment-391
Is it basically the same thing but with a lot more code? What do you think about it?
Thanks! :)
senocular
January 14th, 2009, 04:34 PM
1. The points are arbitrary. However, the larger the numbers, the more accuracy you'll have. 100,100 seemed well enough, and at a 90 degree angle, they are between the two straight-line angles of 0 and 180.
2. No. It seems to be doing something similar but under different, what appears to be more constrained circumstances. It's solution also isn't as robust as the one I posted.
And you're welcome ;)
Angel_Ice
February 12th, 2009, 11:56 AM
Thanks so much 4 the explanation, I'm a designer approaching the As 3.0 and all of this is really hard for me to understand guyz, you're really fukin genius guys... anyway great job!
Anogar
March 19th, 2009, 02:58 AM
What does it say about me that the nice plain English explanation went directly over my head but the ActionScript snippet made perfect sense to me? :huh:
senocular
March 19th, 2009, 11:39 AM
What does it say about me that the nice plain English explanation went directly over my head but the ActionScript snippet made perfect sense to me? :huh:
That makes you French.
SrihariCh
April 4th, 2009, 04:37 AM
For some reason I expected that with the new z property, rotationX, rotationY, rotationZ, Matrix3D etc... that they would have supported some sort of two-sided movieClip... something like:
MovieClip.front = 1;
MovieClip.back = 2;
// where front and back are the frames from the movieClip to use
Anyway... this code will give you a two sided MovieClip where frame 1 of your clip is the front and frame 2 is the back:
VIEW THE SWF (http://shapevent.com/source/two_sided.html)
DOWNLOAD FLA (http://shapevent.com/source/two_sided.zip)
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
box.rotationX = 200 - mouseY;
box.rotationY = mouseX;
makeTwoSided(box);
}
// assumes that frame 1 of the movieClip is the front and 2 the is back
function makeTwoSided(mc:MovieClip):void {
box.gotoAndStop(1);
var rx:Number=Math.abs(mc.rotationX%360);
var ry:Number=Math.abs(mc.rotationY%360);
var turnX:Boolean = (rx > 90 && rx < 270);
var turnY:Boolean = (ry > 90 && ry < 270);
if (turnX != turnY) {
box.gotoAndStop(2);
// make sure the "b" isn't backwards most of the time
box.scaleX = -1;
}
}
Hi all,
Here the hyperlink "DOWNLOAD FLA" doesn't work..But, "VIEW THE SWF" works well.
Please can anybody help me in this context.
Thanks in advance....
Srihari.Ch
ddennis
January 15th, 2010, 06:12 AM
Hi
I have 2 questions which is also about having a displayobject with a front and back side.
-- 1. When using drawtriangles is it possible to draw a different bitmap to each triangle, how is the bitmap applied.
-- 2. What would be the best way to loop through the vector arrays to change.
I have made a little test where i change the bitmap when the triangle flips over, using the "getWinding function" from here (http://www.senocular.com/flash/tutorials/flash10drawingapi/)
the test is here (http://ddennis.dk/labs/imageDistort/fliptriangles.html) - it works fine for 1 triangle, but when using more than one the bitmap fill changes on both.
how do i loop through each triangle, get the winding and assign a 1 bit map material to the forward facing triangles and another one to the backward facing triangles.
I imagine it would be possible to spilt each triangle into it's own, but if i have understood it correcly the power of draw triangles is the ability to reuse point between 2 triangles.
any help would be much appreciated.
Thanks Dennis
Powered by vBulletin® Version 4.1.10 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.