[comp.graphics] 2 3D Vectors: Get a 3rd perp. to 1st?

jcs@crash.cts.com (John Schultz) (12/31/90)

  Given two 3D vectors, compute a third vector normal to the first
and in the plane of both. The two given vectors are unit length, and
the result must be unit length. I've come up with two solutions:

  Given unit vectors A and B, result is C:

  1. C = normalize(A cross (A cross B)), [15 muls, 8 add/subs, 1 sqrt,
                                          and 3 divs].

  2. C = (A*(A dot B) - B) / (sin(acos(A dot B))), [6 muls, 5 add/subs,
                                                    2 table lookups,
Here's what it looks like:                          and 3 divs].

      C
      |    B 
      |   /
      |  /
      | /
      |/_____A

  Method two is much faster, and produces correct results with error
increasing as the angle between A and B decreases. I've tried both
methods in double precision floating point and 32/64 bit integer, and
get similar results. Method one has much less error. Is there a faster
way to do this, or with less error?


  John

levine@well.sf.ca.us (Ron Levine) (01/01/91)

Newsgroups: comp.graphics
Subject: Re: 2 3D Vectors: Get a 3rd perp. to 1st?
Summary: 
Expires: 
References: <6613@crash.cts.com>
Sender: 
Followup-To: 
Distribution: 
Organization: Whole Earth 'Lectronic Link, Sausalito, CA
Keywords: 

In article <6613@crash.cts.com> jcs@crash.cts.com (John Schultz) writes:
>  Given two 3D vectors, compute a third vector normal to the first
>and in the plane of both. The two given vectors are unit length, and
>the result must be unit length. I've come up with two solutions:
>
>  Given unit vectors A and B, result is C:
>
>  1. C = normalize(A cross (A cross B)), [15 muls, 8 add/subs, 1 sqrt,
>                                          and 3 divs].
>
>  2. C = (A*(A dot B) - B) / (sin(acos(A dot B))), [6 muls, 5 add/subs,
>                                                    2 table lookups,
>  Method two is much faster, and produces correct results with error
>increasing as the angle between A and B decreases. ...
>... Method one has much less error. Is there a faster
>way to do this, or with less error?
>
>
The source of your errors is probably in the table look ups
for the trig functions.  Why not just use

    C =  normalize(B - (A dot B)A)

It requires 9 multiplies, 7 adds, 1 sqrt and 3 divides.  

This is well-known as the "Gram-Schmidt procedure".

jcs@crash.cts.com (John Schultz) (01/02/91)

In <22365@well.sf.ca.us> levine@well.sf.ca.us (Ron Levine) writes:
>In article <6613@crash.cts.com> jcs@crash.cts.com (John Schultz) writes:
>>  Given two 3D vectors, compute a third vector normal to the first
>>and in the plane of both. The two given vectors are unit length, and
>>the result must be unit length. I've come up with two solutions:
>>
>>  Given unit vectors A and B, result is C:
>>
>>  1. C = normalize(A cross (A cross B)), [15 muls, 8 add/subs, 1 sqrt,
>>                                          and 3 divs].
>>
>>  2. C = (A*(A dot B) - B) / (sin(acos(A dot B))), [6 muls, 5 add/subs,
>>                                                    2 table lookups,
>>  Method two is much faster, and produces correct results with error
>>increasing as the angle between A and B decreases. ...
>>... Method one has much less error. Is there a faster
>>way to do this, or with less error?

>The source of your errors is probably in the table look ups
>for the trig functions.  Why not just use

  Nope. Tried it using the 68882 and still got large errors, although
slightly lower than the tabled version (but not much). Double precision
floats were used. When drawn on paper, you can see that as A dot B
approaches 1.0, the length of the new vector (C) becomes shorter, and
sin(acos(A dot B)) grows smaller. It appear that the errors are due to
the accumulation of error of the short vector being scaled by a length
derived using transcendentals, which only has so many bits of accuracy. 

>    C =  normalize(B - (A dot B)A)
 
  I tried that as well, before deriving the non-sqrt version. On a machine
where muls.l is 44 cycles and sqrt is (60+107+100) 267 cycles, sqrt is
to be avoided! I just now looked up Gram-Schmidt (process), of which the above
is a small part. There should be a book titled "Well Known Procedures for
Computer Graphics", subtitled "so you don't have to re-invent the wheel".
(Kind of like Graphics Gems, only all procedures. All of them known to man
at the time of printing. A phone book of tools, with an intelligent index
making the knowledge the reader seeks easy to find).
But, deriving the stuff is fun... 

>It requires 9 multiplies, 7 adds, 1 sqrt and 3 divides.  

  3 extra muls, 2 extra adds, and 1 sqrt. Thats about 415 extra cycles,
which is too expensive (my work is real-time).

>This is well-known as the "Gram-Schmidt procedure".

  Yup, I figured what I derived was probably well-known... But they
didn't use the trig-projection trick (which is probably obvious).

  The best solution then, looks like a two part heuristic: When A dot B
is large, use the normalization method, else use the much faster table
lookup method.


  John

ccplumb@rose.uwaterloo.ca (Colin Plumb) (01/16/91)

jcs@crash.cts.com (John Schultz) wrote:
>In <22365@well.sf.ca.us> levine@well.sf.ca.us (Ron Levine) writes:
>>In article <6613@crash.cts.com> jcs@crash.cts.com (John Schultz) writes:
>>>  Given two 3D vectors, compute a third vector normal to the first
>>>and in the plane of both. The two given vectors are unit length, and
>>>the result must be unit length. I've come up with two solutions:
>>>
>>>  Given unit vectors A and B, result is C:
>>>
>>>  2. C = (A*(A dot B) - B) / (sin(acos(A dot B))), [6 muls, 5 add/subs,
>>>                                                    2 table lookups,
>>>  Method two is much faster, and produces correct results with error
>>>increasing as the angle between A and B decreases. ...
>>>... Method one has much less error. Is there a faster
>>>way to do this, or with less error?

Why two table lookups?  sin(acos(x)) = sqrt(1-x^2) and the
division can be done in one lookup or, if you prefer,
near x=0, 1/sqrt(1-x^2) =

            2        4         6    35  8    63  10    231  12    429  14
   1 + 1/2 x  + 3/8 x  + 5/16 x  + --- x  + --- x   + ---- x   + ---- x
                                   128      256       1024       2048

           6435  16   12155  18    46189  20    88179  22    676039  24
        + ----- x   + ----- x   + ------ x   + ------ x   + ------- x
          32768       65536       262144       524288       4194304

          1300075  26    5014575  28      30
        + ------- x   + -------- x   + O(x  )
          8388608       33554432

This is similar to a problem I'm trying to solve.  I want to do very
fast (real-time animation) incremental rotation of an orthonormal basis
in 3-space.  E.g. rotate 1/20 degree up, then 1/20 degree right, then
1/20 degree right, etc.  The user has a joystick, and is flying over a
surface I'm texture-mapping.  The step size is always the same (1/20
degree is about right) but just multiplying by the matrix doesn't work
if you're composing it 10,000+ times.  I have to correct so the vectors
stay orthonormal.

In my application, accuracy is not important, as long as I don't get
sudden jumps in any of the vectors, and they never drift too far from
perpendicular.  Does anyone know a recurrence that takes an almost
orthonormal basis and returns a more nearly orthonormal basis that I
can execute once per step or something?  (If it matters, I'm using
scaled integers, not floating point.)

Your help is greatly appreciated.
-- 
	-Colin

falk@peregrine.Sun.COM (Ed Falk) (01/19/91)

In article <7012@crash.cts.com> jcs@crash.cts.com (John Schultz) writes:
>In <1991Jan15.233218.7646@watdragon.waterloo.edu> ccplumb@rose.uwaterloo.ca (Colin Plumb) writes:
>
>  Yuck! First, I do two table lookups because I have sin and asin tabled
>already for all possible cases. Can't do that for sqrt...

Sure you can.  Just have a table of all your sqrts from sqrt(1) to
sqrt(4) at any resolution you want.  Then:

	i = 0 ;
	while(x < 1.)
	  x *= 4. , ++i ;
	while(x > 4.)
	  x *= .25 , --i ;
	x = sqrt_table_lookup(x) ;
	while( i > 0 )
	  x *= .5 ;
	while( i < 0 )
	  x *= 2. ;


(Optimization left as an excercise to the student.  A smart compiler
should be able to convert the multiplies into increments or decrements
of the exponent.)

		-ed falk, sun microsystems
		 sun!falk, falk@sun.com
To be loyal to rags, to shout for rags, to worship rags, to die for rags 
-- that is a loyalty of unreason, it is pure animal (Mark Twain).