stravant
|
  |
 |
| Joined: 22 Oct 2007 |
| Total Posts: 2893 |
|
|
| 15 Dec 2011 12:17 AM |
This is a brief introduction on how to do some more advanced math using CFrames than simply translating stuff by a rotation or an offset.
Note that what we're dealing with here is really Matricies,every CFrame is just a rotation matrix + a position, but I'll be calling them CFrames for simplicity.
You're already familiar with the fact that for every mathematical operation there is a special quantity which is "neutral with respect to" that operation. For example: For addition that number is 0, that is, every number plus zero will still be that number. For multiplication that number is 1, that is, every number times one will just be that number.
Well, CFrames as you might already know, have multiplication defined on them in a very special way. However, this special form of multiplication also has a neutral element just like addition and multiplication of normal numbers. CFrame.new() happens to return that value when you give it no arguments. And that makes sense, right? You wouldn't want to get back some arbitrary CFrame from .new(), you want a CFrame that does nothing when you apply it to other CFrames. In the case of a Part's CFrame that would mean not moving the part anywhere for example.
That is, we have the following identity for any CFrame "cf": cf*CFrame.new() == CFrame.new()*cf == cf
Now, what about inverse operations? Most mathematical operations have some sort of inverse. For example, the inverse with respect to addition is taking the negative of a number, and the inverse with respect to multiplication is taking 1/the value. But what exactly is an inverse operation doing? What any inverse is really doing when applied to an element "x" is satisfying the following identity: x OPERATION inverse_with_respect_to_operation(x) == the neutral element That is to say, if you apply the operation to an element and it's inverse with respect to that operation, you get the neutral element with respect to that operation. For example, with addition ans multiplication: x + (-x) == 0 x * (1/x) == 1
But what if you apply this same logic to CFrames? There is a slight complication with the fact that multiplication of CFrames is "not commutative", that is to say if you take two frames "a" and "b", "a*b" won't necessarily be the same thing as "b*a", that is, it matters what order you do the CFrame multiplication in. However, aside from needing to be more careful in keeping our order constant, that doesn't change the way inverses work for CFrames: CFrame:inverse() will still give you the inverse of a CFrame. That value you get back from inverse will satisfy the same identity as before. If you take a CFrame "cf": cf * cf:inverse() == cf:inverse() * cf == CFrame.new()
Note that here commutativity does hold, the multiplication of the CFrames does work both ways for CFrames and their inverses, but ONLY IN THIS CASE, normally it won't give you the same result if you switch the order of a CFrame multiplication.
So, now, what if we apply that to a common situation: Finding out what C0 or C1 you need for a weld to make it work. It turns out all you need to know is a bit of basic algebra, and this understanding of CFrame multiplication to do it. The first thing you need to know is that all joints in Roblox satisfy the following identity: Part1.CFrame * C1 == Part0.CFrame * C0 * joint_effect()
Where "joint_effect()" is whatever the joint needs to do, for example in the case of welds it does nothing at all, but for motors it's equal to an extra rotation CFrame around a specific axis. Now suppose we know where to parts are, and we set the C0 to something specific, but now we want to find out what C1 to use to not move the parts, how do we do it? It turns out not to be that hard. We just start with the identity (I've just called Part0/1.CFrame Part0/1 here for brevity):
Part1*C1 == Part0*C0
Now, just like you would with normal algebra, we're going to multiply something to both sides of the equation. The only thing to be careful of we have to multiply the thing to the _same side_ of each half o the equation since order matters for CFrames. Remember, we're trying to isolate the "C1" variable since that's what we want to find. To do this we can use Part1:inverse():
Part1:inverse() * Part1*C1 == Part1:inverse() * Part0*C0
Now, remember what we said about inverses? If you multiply a CFrame and it's inverse then you get CFrame.new() back every time, so we can replace "Part1:inverse()*Part1" with just "CFrame.new()":
CFrame.new() *C1 == Part1:inverse() * Part0*C0
But then also remember what we said about the "neutral element" with respect to any operation. CFrame.new() is the neutral element, so multiplying C1 by it should have no effect whatsoever. That means we can remove it without breaking the equality:
C1 == Part1:inverse() * Part0 * C0
And we've successfully isolated the required C1 just like that. You can now plug the known values for Part1, Part0 and C0 into the multiplication to get out the C1 that you need.
And it's really as simple as that. By moving values around using inverse you can easily perform whatever algebra you need in order to find CFrames. For example, rotating a bunch of parts around an origin becomes easy with this knowledge:
We have an origin, we need to know how far the part is displaced from the origin, so: part.CFrame = origin * displacement so: displacement = origin:inverse()*part.CFrame and then we just need to apply the rotation to the origin before moving the part back: part.CFrame = origin * rotation * displacement |
|
|
| Report Abuse |
|
|
smurf279
|
  |
| Joined: 15 Mar 2010 |
| Total Posts: 6871 |
|
|
| 15 Dec 2011 12:34 AM |
Thanks :D I saw it in Scripters but nice to have a copy here. Probably a bit too much for most of the people that aren't regulars here though :/
*Tracking |
|
|
| Report Abuse |
|
|
tallpie
|
  |
| Joined: 09 Feb 2010 |
| Total Posts: 1378 |
|
| |
|
stravant
|
  |
 |
| Joined: 22 Oct 2007 |
| Total Posts: 2893 |
|
| |
|
|
| 15 Dec 2011 12:37 PM |
Starvant, Can't you just make this a Pinned Post?
~Bikerking200 :P~ |
|
|
| Report Abuse |
|
|
Trioxide
|
  |
| Joined: 29 Mar 2011 |
| Total Posts: 32902 |
|
| |
|
| |
|
stravant
|
  |
 |
| Joined: 22 Oct 2007 |
| Total Posts: 2893 |
|
| |
|
xvgigakid
|
  |
| Joined: 22 Jun 2008 |
| Total Posts: 4407 |
|
|
| 15 Dec 2011 04:16 PM |
@Starvant
It looks a bit squished on the forum. Perhaps it would be a better idea to post it on the wiki where you can organize your paragraphs.
But it is a good tutorial :/
~Dark Sky Gift of Hoarders Lover |
|
|
| Report Abuse |
|
|
|
| 15 Dec 2011 04:20 PM |
Very interesting. Though, do you know anything regarding the rotation matrices themselves? I'd like to know how to calculate some things with the rotation components of the CFrame. |
|
|
| Report Abuse |
|
|
|
| 15 Dec 2011 04:28 PM |
STRAVANT. NOT STARVANT.
It's not very 'advanced', really.
Advanced would be manipulating tons of welds at once, but with your tutorial you could do that.
Good job. |
|
|
| Report Abuse |
|
|
stravant
|
  |
 |
| Joined: 22 Oct 2007 |
| Total Posts: 2893 |
|
|
| 15 Dec 2011 04:29 PM |
Doing anything to do with the individual components that you can't do with just the normal multiply / add / subtract operations requires far more mathematical knowledge than I can possibly divulge in a tutorial or even a series of tutorials. And even if I could, I doubt that you really need to decompose a CFrame to do whatever it is you're interested in doing.
If you post some examples of what you would want to do I can probably show you how to solve them using the normal CFrame operations. |
|
|
| Report Abuse |
|
|
|
| 15 Dec 2011 04:31 PM |
| I've been working with cameras, and using the toEulerAnglesXYZ function is not very accurate for a frame-by-frame transition from one location and focus to another. I'm wondering how I can smooth out this transition using pure math instead of archiving fixed positions/angles. |
|
|
| Report Abuse |
|
|
| |
|
stravant
|
  |
 |
| Joined: 22 Oct 2007 |
| Total Posts: 2893 |
|
|
| 15 Dec 2011 04:45 PM |
@AgentFirefox That's going to be pretty tricky, and would be one of the legitimate cases. Normally how you do this is using what's called "quaternions" to represent the rotation-part since they can be easily interpolated unlike CFrames. That would look something like this:
local quat1 = toQuaternion(cf[n]) local quat2 = toQuaternion(cf[n+1]) local quat = slerp(quat1, quat2, fraction) local newCF = toCFrame(quaternion)
(Although if you really want to try: cf:components() will give you a tuple of all of the components that make up the rotation-matrix / position of a CFrame)
That's going to take a lot of reading and implementing formulas to do, so you could always do something like this instead. It wont' be as good but it will work better than decomposing to angles: Try taking the two CFrame's position parts, and a position say 10 studs in-front of the cameras. Now, take the directions from those offset positions, in the direction from one end-point to the other. Use those two position/direction pairs offset from the movement endpoints as the control points for a 2pointed "spline" (it's easy to implement, you can just read the Wikipedia article). Now, for every point along the line of the camera's movement, construct the CFrame at that point, and pointing at the corresponding point on the spline that you generated. That will give you decent movement and the math isn't too hard. |
|
|
| Report Abuse |
|
|
smurf279
|
  |
| Joined: 15 Mar 2010 |
| Total Posts: 6871 |
|
|
| 18 Dec 2011 10:25 PM |
"Thanks :D I saw it in Scripters but nice to have a copy here. Probably a bit too much for most of the people that aren't regulars here though :/
*Tracking"
________________________________________________________
Nevermind, didn't notice you posted this in SH not BH :/. . . bumpp |
|
|
| Report Abuse |
|
|
| |
|
AxeOfMen
|
  |
| Joined: 14 Dec 2011 |
| Total Posts: 196 |
|
|
| 03 Jan 2012 10:56 AM |
Thank you, stravant. There is definitely a need for more advanced tutorials of this nature. I have been struggling the past few days with writing a function that I can call from script to roll a block onto its side. I have a set of four buttons with arrows pointing North, East, South, West. When a player clicks a button, I want the block to roll in that direction. The player ought to be able to click multiple buttons and have the block roll multiple times. Find my channel (username: PoisonousLlama) on YouTube to see a video depicting precisely what I am trying to accomplish.
I am attempting to create a function with the following signature: function RollBlock(Block, Direction) where Block is the block that will be rolled and Direction is "North", "South", "East" or "West" (these are fairly arbitrary in a x,y,z axis based world, I realize, but it's meant as a convenience for the script user)
I have been using the CFrame components to try to determine which way the block is facing and use that information to determine what vector to use to multiply with the block's CFrame.
This seems like it ought to be simple to implement and I'm overcomplicating the issue due to my lack of understanding but I could sure use some help figuring out what I'm doing wrong. Can I do this without examining the individual CFrame components to figure out how to
Here is a link to the script I have been working on: http://www.roblox.com/Block-Roll-Script-item?id=69263373 |
|
|
| Report Abuse |
|
|
Miro034
|
  |
| Joined: 07 Oct 2009 |
| Total Posts: 6568 |
|
|
| 03 Jan 2012 11:27 AM |
| I guess stravant is smarter than AgentFireFox in Lua. Thanks stavant by the way. |
|
|
| Report Abuse |
|
|
AxeOfMen
|
  |
| Joined: 14 Dec 2011 |
| Total Posts: 196 |
|
|
| 03 Jan 2012 01:05 PM |
It looks like my video is difficult to find on you tube's search. The id is
g66pWuepWQQ |
|
|
| Report Abuse |
|
|
stravant
|
  |
 |
| Joined: 22 Oct 2007 |
| Total Posts: 2893 |
|
|
| 03 Jan 2012 02:04 PM |
@Axe.
It's actually fairly simple, the trouble is probably that you're trying to do it something like this: part.CFrame = part.CFrame*CFrame.Angles(...)
When that's fundamentally the wrong way to do it, because what that does is apply the transformation to the local coordinate system of the part, as opposed to applying it around the global axis. What you want to do is: part.CFrame = CFrame.Angles(...)*part.CFrame
That will apply the current transformation of the part to the rotation, resulting in a rotation around the global axis rather than the local axis. Of course, it will also rotate the position of the part, so you'll have to fix that afterwards, or subtract away the position before you do the rotation, and then add it back later: local pos = part.CFrame.p part.CFrame = part.CFrame - pos part.CFrame = CFrame.Angles(...)*part.CFrame part.CFrame = part.CFrame + pos |
|
|
| Report Abuse |
|
|
AxeOfMen
|
  |
| Joined: 14 Dec 2011 |
| Total Posts: 196 |
|
|
| 03 Jan 2012 06:24 PM |
@Stravant
You pinpointed my problem precisely. I appreciate your attention to my concern. My final function is significantly less convoluted (not to mention that it actually works!) thanks to your guidance.
I'm very grateful |
|
|
| Report Abuse |
|
|
SCS
|
  |
 |
| Joined: 24 Jun 2008 |
| Total Posts: 10075 |
|
|
| 19 Apr 2012 09:20 PM |
This is fascinating. I am looking forward to rereading this entire post numerous times in the future, until I really understand what you are saying. My problem is that, though I am familiar with (or, at least recognize) the mathematics behind this, I am no where near as familiar with it in the context of scripting. However, hopefully through time and practice, I will be able to understand this completely.
Oh, and I was also very glad to finally learn what "CFrame.new()" is and why it is multiplied by the CFrame.
Thank you for making this post, stravant. |
|
|
| Report Abuse |
|
|
SDuke524
|
  |
| Joined: 29 Jul 2008 |
| Total Posts: 6267 |
|
|
| 19 Apr 2012 09:36 PM |
@stravant
Why does using quaternion's make the rotation better? |
|
|
| Report Abuse |
|
|
Wowgnomes
|
  |
| Joined: 27 Sep 2009 |
| Total Posts: 26255 |
|
| |
|