homogeneous coordinate math

Started by
7 comments, last by cignox1 16 years, 6 months ago
Hi all, I have a couple of questions for those who are used with homogeneous coordinates. I'm rewriting my math library and due to sse optimizations I want to directly store the w component in the vector4. I'm not really sure how operations should be performed though: -Should I use different classes for vectors and point? If not, where should I keep attention about which one I'm using from inside the methods? If yes, should I use a third class for normals? -Should Addition, Multiplication etc operate on the w component? Are there constraints on how these operators should behave? What about Lenght() or Normalize()? -Since operators may change the w components, when should I 'restore' the vector? Is everything guaranteed to work out correctly if my code does not use vectors and points when they aer not supposed to be used? i.e, if I never add a point to another point, or multiply a point to a scalar? When is w allowed to change? Only after projection matrices? I suppose that as long as I never mix points with vectors in the wrong way, the w component will always be 1 for points and 0 for vectors (except for some transformations). Am I right? I apologize if these questions sound stupid or the like...I *think* to know some of the answers, but I wouldn't like to discover only too late that I did it wrong. thank you.
Advertisement
The sense of using homogeneous co-ordinates makes the class distinction in position and direction vectors senseless. The w component already _does_ the distinction. For almost all cases w is either 0 (what means you have a direction vector) or else 1 (what means you have a position vector). In fact, every w other than 0 and 1 denote a "de-normalized" position vector that can (and should) be re-normalized by diving by w (yielding in w==1 again). Such de-normalized positions occur sometimes, e.g. in projections, but also by naively applying standard vector math (some eamples follow below).

Now, there are some rules that apply:
a position (w=1) + a direction (w=0) =: a position (w=1)
a position (w=1) - a position (w=1) =: a direction (w=0)
a direction (w=0)+ a direction (w=0) =: a direction (w=0)
a direction (w=0) - a direction (w=0) =: a direction (w=0)

As you can see here, the w component will always be correctly computed by standard vector math. Now look at these cases:

a position (w=1) + a position (w=1) =: what? (w=2)
Here you have to "normalize" the w component by dividing the whole vector by 2. Interpreting the result, you can see that adding 2 position vectors means to compute their barycenter (is it named so? I assume).

norm( a direction ) works as usual, and hence will make a direction vector having unit length.

norm( a position ) divides by the length, so that the w component becomes 1/norm; now, remember to re-normalize the vector, so that w==1, and voila, the vector is the same as before (therefore making a position unit length has no sense at all; think a bit over it, and you'll see its truth; what you actually mean with the "length of a position vector" is its distance from the origin, what actually is the length of a difference and hence direction vector).

Say, homogeneous co-ordinates allow the math to directly show that position and direction vectors are semantically different.
Thank you, I was confused by the fact that someone (i.e. the book "Physically Based Rendering) actually use two (well, three, counting normals) different classes...

EDIT: well, they don't use 4 components vectors though :-)
I don't think it ever really makes sense to add two normalized position vectors. For instance, using (P1 + P2) to find the mid-point/barycenter doesn't feel right at all since that's not the correct formula. You would use (P1 + P2)/2. The sum yields a vector with W = 2, which is then divided out by the factor of 1/2 (which incidentally has the same effect of normalization, but only because we're finding the mid-point). But I could just have easily written it as (P1)/2 + (P2)/2, where each point is first multiplied by 1/2 to become de-normalized with W = 1/2, and then two de-normalized vectors are added together to get one with W = 1.

I've actually been thinking a bit about it, and I've come up with a set of rules that seem to unify addition between vectors and positions. They are:
1a) There must always be at least one de-normalized coordinate in the addition.
1b) A coordinate with W != 1 is considered de-normalized.
2) The sum of the W components must be either 0 (for vectors/directions) or 1 (for points).

This seems to take care of all the cases:

Vector + Vector := (two denormalized vectors, result W = 0)
Vector - Vector := (two denormalized vectors, result W = 0)
Vector + Normalized Position := (at least one denormalized vector, result W = 1)
Vector - Normalized Position = Vector + (-Normalized Vector) := (at least one denormalized vector, but result W = -1, thus *undefined*)
Normalized Position + Normalized Position (no denormalized vectors, *undefined*)
Normalized Position - Normalized Position = Normalized Position + (-Normalized Position) := (at least one denormalized vector with W = -1, result has W = 0)
Normalized Position + Vector := (at least one denormalized vector, result W = 1)
Normalized Position - Vector = Normalized Vector + (-Vector) := (at least one denormalized vector, result W = 1)

And the general cases:

Denormalized Position + Denormalized Position := (at least one denormalized vector, defined if result W = 0/1)
Denormalized Position - Denormalized Position = Denormalized Position + (-Denormalized Position) := (at least one denormalized vector, defined if result W = 0/1)
Denormalized Position + (Vector/Normalized Position) := (at least one denormalized vector, defined if result W = 0/1)
Denormalized Position - (Vector/Normalized Position) = Denormalized Position + (-Vector/Normalized Position) := (at least one denormalized vector, defined if result W = 0/1)
(Vector/Normalized Position) + Denormalized Position := (at least one denormalized vector, defined if result W = 0/1)
(Vector/Normalized Position) - Denormalized Position = (Vector/Normalized Position) + (-Denormalized Position) := (at least one denormalized vector, defined if result W = 0/1)

The only issue is that these may not be the best way to implement operations in code, i.e. you wouldn't do (P1)/2 + (P2)/2 because that's two divisions, you'd do (P1 + P2)/2. Also, the above rules only demonstrate the binary operations which would be used in code, however clearly for the rules to apply they need to be extended to all N-ary addition operators.

There may be something I missed, but these feel like a better set of rules to me [smile]

EDIT: So I've been thinking of a few cases where the rules might not make too much sense, for instance during the projection matrix transformation you get a W that isn't necessarily equal to 1 even though matrix transformation is a linear combination of vectors. I suppose you could get around that by pre-diving the matrix by Z, but that just seems like too much work just to get the rules to work out nicely. So maybe they don't apply cleanly in all cases, but just a lot of them.

[Edited by - Zipster on October 1, 2007 5:59:23 PM]
Thank you Zipster, I've already thought about a few of these rules. The reason why I posted those questions was that I could manage to handle arithmetic operators, but I couldn't think to all the operations that I would want to perform on a vector, so I wasn't sure every operation would have had a meaning. Another example that comes to mind is the Dot product: I think I should do it with all the 4 components, but with points it would break. So I either ignore the w component (I've seen in a lib I've found on internet that someone implemented both the Dot and the Dot3 operators) or I use the w component but trust in the caller that if he wanted a dot3 equivalent then he would have passed in vectors and not points.
I suppose that I should take the same decision for Length also (ie. Length3 and Length vs. Length alone) and for other methods....

EDIT: I think I will let the user to 'Restore' the vector on-demand. If arithmetic operators don't lead to 'wrong' w values, then the only places where this can happen is after a transformation: the caller will call 'Restore' if he thinks there is (or checks) a bad w value (i.e. after a projection matrix, where the w holds the z value IIRC).

Thank you again!
Quote:Original post by cignox1
-Should I use different classes for vectors and point?
No! It is tempting to do so, and you can even argue the separation from mathematical grounds, but it is pragmatically completely the wrong thing to do.

Trust me, in the long run you will be much happier with just a generic Vector4 that operates uniformly on all four SIMD elements at once. Where needed, implement e.g. both Dot3() and Dot4(), and Length3() and Length4() functions over these Vector4's; have your Cross() ignore the w-components of the input arguments (and potentially zero the w-component of the result), etc. Your life will be so much more simpler if you do things this way.
Quote:Original post by Christer Ericson
Quote:Original post by cignox1
-Should I use different classes for vectors and point?
No! It is tempting to do so, and you can even argue the separation from mathematical grounds, but it is pragmatically completely the wrong thing to do.

Trust me, in the long run you will be much happier with just a generic Vector4 that operates uniformly on all four SIMD elements at once. Where needed, implement e.g. both Dot3() and Dot4(), and Length3() and Length4() functions over these Vector4's; have your Cross() ignore the w-components of the input arguments (and potentially zero the w-component of the result), etc. Your life will be so much more simpler if you do things this way.


I will trust your word :-) Actually, I'm already doing that, and was thinking to implement a few methods specialized in homogeneous coordinates (i.e. that treat the w coordinate in special ways) and 3d methods like you suggested.
Since I already have aworking vector class I wrote last month, I just have to change the code to work with templates.
Thank you again!
I just wanted to point out that strictly speaking, a 4-vector with homogeneous coordinates whose w component is 0 is *not* the same thing as 3-dimensional vector. The diference is that [1:0:0:0] and [2:0:0:0] are the exact same 4-vector with homogeneous coordinates, but (1,0,0) and (2,0,0) are different 3-dimensional vectors.

A 4-vector with homogeneous coordinates represents a point at infinity.

This doesn't mean that there is anything wrong with Christer Ericson's advice. I have always kept vector and point classes separated and I have never had a problem, but he probably has more experience than I do.

Quote:Original post by alvaro
I just wanted to point out that strictly speaking, a 4-vector with homogeneous coordinates whose w component is 0 is *not* the same thing as 3-dimensional vector. The diference is that [1:0:0:0] and [2:0:0:0] are the exact same 4-vector with homogeneous coordinates, but (1,0,0) and (2,0,0) are different 3-dimensional vectors.

A 4-vector with homogeneous coordinates represents a point at infinity.

This doesn't mean that there is anything wrong with Christer Ericson's advice. I have always kept vector and point classes separated and I have never had a problem, but he probably has more experience than I do.


Yes, I thought that too. I don't think it will be a problem though, since a w=0 vector represents a direction and most of the time will be normalized anyway... I hope, at least :-)

This topic is closed to new replies.

Advertisement