Fair Strides has asked for the wisdom of the Perl Monks concerning the following question:

Hello again. It's been a long time since I last logged on, but I'm in another bit of trouble over small coding errors...

I'm building an application to help mod a Star Wars game, and the game stores an object's directional facing as a form of a quaternion, with four numbers: the first is the X-orientation, the fourth the Y-orientation, and the two in the middle are always 0.

I want to allow the users to enter the facing in degrees, calculate the quaternion values from the degrees, and then eventually put that into the file. Currently, the program reads the info from the file correctly, but upon my editing the info, writing it back to the file, and then re-reading it, the numbers aren't right...

The game has a built-in scripting language, and the code to get the Quaternion X and Y goes like this:

float fOrient = GetFacing( GetFirstPC() -90.0 ); float fX = cos( fOrient/2 ); float fY = sin( fOrient/2);

The reason for the -90.0 from the Facing is that the Quaternion values are used for rotation, with the game using East as the 0.0 rotation, and the GetFacing function using North as the 0.0 rotation.

Just a heads-up, using the code below, I operate off of the degree returned by converting the radian value for the Quaternion's Y-orientation. When I set the degrees to 10 in the spinbox, and then re-read the information from the file after writing, it's 9.98 and then some more numbers. When I set the degrees to 55, the new value read in is 52.91 and then some numbers, so the margin for error is growing...

Here's the code I have for reading the info from a hash-of-hashes I already populated when I processed the file:

# The below section should do the math right. my $pi = 3.14159265358979; #my $pi = 4*atan2(1,1); sub deg_to_rad { ($_[0]/180) * $pi;} sub rad_to_deg { ($_[0]/$pi) * 180; } sub calc_quatcam { my $camx = shift; my $camy = shift; print "\nRad X:Y\t $camx : $camy\n"; # my $qx = rad_to_deg(acos($camx)); # my $qy = rad_to_deg(asin($camy)); my $qx = rad_to_deg($camx); my $qy = rad_to_deg($camy); print "\nRadian X:Y\t $qx : $qy\n"; print "Total: " . ($qx + $qy) . "\n"; $qx = ($qx * 2);# + 90.0; $qy = ($qy * 2);# + 90.0; my @calc_cam = ($qx, $qy); # print "\nQuat X:Y\t $qx : $qy\n"; return @calc_cam; } # Read in from the Hash. The function that reads from # the file returns an anon Array, so I have to access # as a scalar, then turn it into an Array. my $can_ori = $gff_files{Cameras}{$num}{Orientation}; @cam_ori = @$can_ori; my $cam_ori1 = @cam_ori[0]; # X-orientation my $cam_ori2 = @cam_ori[1]; # Always 0 my $cam_ori3 = @cam_ori[2]; # Always 0 my $cam_ori4 = @cam_ori[3]; # Y-orientation # Debugging print-out print "Orientation:\n\t$cam_ori1\n\t$cam_ori2\n\t$cam_ori3\n\t$cam +_ori4\n\n"; # The orientation values are stored as radians, so # calc_quatcam returns the degree equivalent, or should... my @cam_quats = calc_quatcam($cam_ori1, $cam_ori4); my $qx = @cam_quats[0]; my $qy = @cam_quats[1];

and the code that takes the measure of degrees from a spinbox widget and writes back the quaternion values in radians...:

my $ori_deglab = $ori_framey->Label(-text=>"Facing (in Degrees):") +->pack(-side=>left); my $ori_deg = $ori_framey->Spinbox(-from=>0.0, -to=>360.0, -increm +ent=>0.5, -width=>12, -command=>sub { my $deg = shift; my $degg = $deg; my $fl = $degg/2; my $flo = deg_to_rad($fl); print "Order:\n\t$deg\n\t$f +l\n\t$flo\n"; $quatx = cos($fl); $quaty = sin($fl); my @cal = calc_quatcam($quatx, $quaty); print "The deg is: $deg\n"; $ori_xlab->configure(-text=>"X Quaternion Value: $quat +x"); $ori_ylab->configure(-text=>"Y Quaternion Value: $quat +y");})->pack(-side=>right); $ori_deg->set($qy); $ori_xlab = $ori_framey1->Label(-text=>"X Quaternion Value: $quatx +", -font=>[-family=>'fixed', -size=>10], -width=>30)->pack(-expand=>1 +, -fill=>x); $ori_ylab = $ori_framey2->Label(-text=>"Y Quaternion Value: $quaty +", -font=>[-family=>'fixed', -size=>10], -width=>30)->pack(-expand=>1 +, -fill=>x);

Now, I found How can I get sine, cosine, and tangent to return values in degrees? and applied the info there, but I am still not getting the right values from my equations...

I fear I'm not being very clear, so I'll restate my problem: How do I get the PROPER Quaternion values from a given degree?

Replies are listed 'Best First'.
Re: Perl and Quaternions
by kcott (Archbishop) on May 08, 2014 at 11:37 UTC

    G'day Fair Strides,

    Some sample input data, actual results and expected results would have helped. A short script that reproduced your problem would have helped even more: we could have run it, tweaked it and suggested modifications. The guidelines in "How do I post a question effectively?" explain how to do this.

    "Here's the code I have for reading the info from a hash-of-hashes ..."

    As I already alluded to, you provide no indication of the structure of this "hash-of-hashes" nor what data populates it. In the code, you have variables with no indication of how they were declared or what values they might hold. In order to troubleshoot this, we'd need to make many guesses regarding data, context and the value of variables: guessing is not a good way to proceed.

    "and the code that takes the measure of degrees ..."

    Much the same comments apply here also. It looks like Tk code but you don't specify this. It contains non-essential, distracting clutter like font families and sizes; but important information, such as variable declarations, is missing.

    Here's a few comments on what you have provided:

    • Use the strict pragma in all your scripts; I do! It will alert you to many little mistakes that you often won't notice: typos in variable names; missing or incorrect sigils; missing or duplicate declarations; and so on. In the code you posted, there are multiple places where such alerts would be raised.
    • Use the warnings pragma in all your scripts; I do! This will alert you to a huge number of potential problems. I can see four places in your posted code where you'll not only receive a warning but also actual code to replace what you've written (e.g. "your_code" better written as "better_code").
    • Was there a reason you commented out "my $pi = 4*atan2(1,1)" and used a hard-coded value instead?
    • "# Read in from the Hash. The function that reads from the file returns an anon Array, so I have to access as a scalar, then turn it into an Array.": Comments like this don't help at all! There's no function, no file, no filehandle and an intermediary variable is unnecessary: just use my @arrayname = @{ expression_evaluating_to_arrayref };.
    • Learn about Slices. Use @arrayname[@indexes] for an array slice and $arrayname[$single_index] for a single array element.
    • When you have an array and want to access single elements, just do it. Don't assign array elements to $element1, $element2, ..., $elementN.

    See also: Math::Trig (for pi constants and functions such as deg2rad); Math::Quaternion (for the creation and manipulation of quaternions); SecondLife::Rotation (for an example of how to subclass Math::Quaternion, should you need that).

    "The game has a built-in scripting language, ..."

    [The following (in the spoiler) has nothing at all to do with Perl; it's about NWScript. Of interest to the OP, but of little or no interest to others.]

    Firstly, the code you posted looks like NWScript (again, something else unspecified). I'm assuming you're writing a mod for one of BioWare's "Stars Wars" titles. If this is a wrong assumption, don't bother reading any more.

    [Disclaimer: While I know about NWScript, I've never played, or written mods for, any of BioWare's "Stars Wars" titles.]

    This is incorrect syntax:

    float fOrient = GetFacing( GetFirstPC() -90.0 );

    This is correct syntax; although, I don't think it's doing what you want:

    float fOrient = GetFacing( GetFirstPC() ) - 90.0;

    In-game orientation starts at zero for East and moves anticlockwise: E=0; N=90; W=180; S=270.

    Normal compass orientation starts at zero for North and moves clockwise: N=0; E=90; S=180; W=270.

    Therefore, I believe you want logic more like this Perl code:

    my %game = qw{E 0.0 N 90.0 W 180.0 S 270.0}; for (qw{N E S W}) { my $real = (360 + 90 - $game{$_}) % 360; printf "game: %s = %5.1f; real: %5.1f\n" => $_, $game{$_}, $real; }

    Output:

    game: N = 90.0; real: 0.0 game: E = 0.0; real: 90.0 game: S = 270.0; real: 180.0 game: W = 180.0; real: 270.0

    However, NWScript only allows integers as operands for its modulo (%) operator, so you'll need to do some casting like this:

    float fOrient = IntToFloat( (360 + 90 - FloatToInt(GetFacing(GetFirstPC()))) % 360 );

    Note that GetFacing() returns a floating point number BUT it's rounded to the nearest whole degree. This has two implications:

    1. Casting from float to int and back again will not change the value as you're only dealing with whole numbers.
    2. Attempting to change the orientation in half degree increments (...Spinbox(... -increment=>0.5 ...)) is not valid, inasmuch as subsequent reporting of the changed orientation will be rounded to a whole number.

    The reference I used for the above information can be downloaded from NWN Lexicon v1.69.

    -- Ken

      I apologize for the terrible formatting of the question, everyone.

      I'm not used to posting here, and actually am more used to people knowing where my code is coming from(this familiarity coming from my time at Holowan Labs, at LucasForums). This is not an excuse, merely a statement about my own ignorance.

      To address everything in your post, Ken, and hopefully everything else, let me try this again.

      I'm trying to convert between radians and degrees from a given degree, for use in a quaternion format, particularly in translation between Quaternion values and Euler angles. I took another look at how the games I mod, Star Wars Knights of the Old Republic it's sequel, use Quaternions. And it looks like I messed up:

      Z = sin (angle/2) Y = 0.0 X = 0.0 Q = cos (angle/2)

      In my eyes, it was a simple mistake, though you guys might judge differently. I had always assumed that the values I needed were X and Y because that was how the game referenced them in it's files.

      So, some example code was asked for. This code can be run from the commandline and will ask for an input value. Be sure to input it in degrees.

      use strict; use warnings; use Math::Complex; my $pi = 4*atan2(1,1); sub deg_to_rad { ($_[0]/180) * $pi; } sub rad_to_deg { ($_[0]/$pi) * 180; } print "Enter a value: "; my $value = <STDIN>; my $radvalue = deg_to_rad($value); my $degvalue = rad_to_deg($radvalue); my $quat1 = sin($radvalue); my $quat1_deg = rad_to_deg($quat1); my $quat2 = cos($radvalue); my $quat2_deg = rad_to_deg($quat2); print "Original value: $value\n"; print "Radian value: $radvalue\n"; print "Degree value: $degvalue\n\n"; print "Quaternion first value: \t$quat1\n"; print "Quaternion first value(in degrees): \t$quat1_deg\n"; print "Quaternion fourth value: \t$quat2\n"; print "Quaternion fourth value(in degrees): \t$quat2_deg\n";

      Inputting 90 degrees should bring up the radian value, 1.570796 blahblahblah, but it doesn't. I'm using the Math::Complex module, so that might be the difference between the expected results, but I'm not sure...

      About the example code I posted that the game uses, yes it is NWScript. And I'm known for being accurate when I script in it, but on this occasion I didn't copy-and-paste correctly, so now feel kinda stupid for that. Also, I posted it for the sole reason of showing anybody who might care to know how the game calculates the values, which might help me do the opposite and calculate the facing from the values.

      I admit that the spinbox widget isn't really optimal for this; it was more of a test on that part. It's easy enough to switch it to an entry widget with a binding to the Enter key, though, so I'm not too worried about it. However, thanks for pointing it out, Ken.

      I tend to not use the strict pragma because it interferes with the TK code, in that when it forces me to put quotes around certain text, the options aren't accepted by TK... However, warnings should definitely have been used. Sorry about that, Ken.

      I alternated between either value of Pi to see if there was any functional difference in the conversion subroutines.

      Sorry about the clutter in the code samples. I was trying to give you guys enough info without chucking the whole script in, and without leaving out something that might be important and then get me flamed for not adding it in when it was so essential for the people answering to know that ahead of time...

      Also, could you please be more specific about what you meant when you mentioned Array slices? You mentioned them, but never where in my code samples they might be used...

      And I found Math::Quaternion myself while doing research, but I have no clue how to interpret it's 3x3 or 4x4 output, given that I just need four values...

      I apologize, Ken, and wish I could elaborate more and get some more help, but I need to head off to school now. I'll be checking back in at lunch, see if anything's new. Thanks for the helpful comments, Ken!

        "So, some example code was asked for. This code can be run from the commandline and will ask for an input value. Be sure to input it in degrees."

        Thanks. That looks like a much better sample that we can work with.

        "Inputting 90 degrees should bring up the radian value, 1.570796 blahblahblah, but it doesn't."

        Actually, it does when I run it.

        Enter a value: 90 Original value: 90 Radian value: 1.5707963267949 Degree value: 90 Quaternion first value: 1 Quaternion first value(in degrees): 57.2957795130823 Quaternion fourth value: 6.12323399573677e-17 Quaternion fourth value(in degrees): 3.50835464926744e-15

        And, just to confirm that's the correct result:

        $ perl -E 'say(4*atan2(1,1) / 2)' 1.5707963267949

        I suggest you run this again and check the output. Also check that you're running exactly the same code that you posted.

        "I'm using the Math::Complex module, so that might be the difference between the expected results, but I'm not sure..."

        Although you've loaded the Math::Complex module, I don't see any code that uses its functionality. In fact, if I comment out "use Math::Complex;", the output remains exactly the same.

        However, even if you were using Math::Complex's constants or functions, I can't see how you'd get anything different. Here's some fragments from its source code:

        ... sub pi () { 4 * CORE::atan2(1, 1) } ... sub cos { my ($z) = @_ ? @_ : $_; return CORE::cos($z) unless ref $z; ... } ... sub sin { my ($z) = @_ ? @_ : $_; return CORE::sin($z) unless ref $z; ... } ...

        And repeating the confirmation using pi from Math::Complex:

        $ perl -MMath::Complex -E 'say(pi / 2)' 1.5707963267949
        "I tend to not use the strict pragma because it interferes with the TK code, in that when it forces me to put quotes around certain text, the options aren't accepted by TK..."

        Frankly, that's a poor reason to remove all strictures from your entire script. If quoting right and x in

        ...->pack(-side=>right); ... ...->pack(-expand=>1, -fill=>x);

        is really such a burden, write them like this:

        ...->pack(qw{-side right}); ... ...->pack(qw{-expand 1 -fill x});

        On the rare occasions when you really do need to turn off a stricture, turn off just one and do it the smallest scope possible. For example

        use strict; ... # all strictures on here { no strict 'refs'; # 'vars' and 'subs' strictures still on here ...; # <--- minimal code requiring "no strict 'refs'" } # all strictures on here ...
        "However, warnings should definitely have been used."

        I'm not certain whether you're saying they weren't used or weren't shown. Had they been used, you would have received multiple messages like this:

        $ perl -Mwarnings -E 'my @x = qw{1 2 3}; my $y = @x[1]' Scalar value @x[1] better written as $x[1] at -e line 1.

        This is what I was referring to in my previous post with: "I can see four places in your posted code where you'll not only receive a warning but also actual code to replace what you've written (e.g. "your_code" better written as "better_code")."; although, I've now spotted two more.

        "Also, could you please be more specific about what you meant when you mentioned Array slices? You mentioned them, but never where in my code samples they might be used..."

        Don't use slices like this (as per my previous point):

        my $qx = @cam_quats[0]; my $qy = @cam_quats[1];

        You didn't show how you were intending to use $qx or $qy. If they were just for their values, you don't need them at all: you can use $cam_quats[0] and $cam_quats[1]. If, on the other hand, you wanted to modify these values without changing @cam_quats, you could have used a slice like this:

        my ($qx, $qy) = @cam_quats[0,1];

        However, when I originally made that point, I was thinking more about this code:

        my $cam_ori1 = @cam_ori[0]; # X-orientation my $cam_ori2 = @cam_ori[1]; # Always 0 my $cam_ori3 = @cam_ori[2]; # Always 0 my $cam_ori4 = @cam_ori[3]; # Y-orientation # Debugging print-out print "Orientation:\n\t$cam_ori1\n\t$cam_ori2\n\t$cam_ori3\n\t$cam +_ori4\n\n"; # The orientation values are stored as radians, so # calc_quatcam returns the degree equivalent, or should... my @cam_quats = calc_quatcam($cam_ori1, $cam_ori4);

        which you could've written like this using slices:

        # Debugging print-out print join("\n\t", 'Orientation:', @cam_ori[0..3]), "\n\n"; # The orientation values are stored as radians, so # calc_quatcam returns the degree equivalent, or should... my @cam_quats = calc_quatcam(@cam_ori[0,3]);

        As you can see, you no longer need any of: $cam_ori1, $cam_ori2, $cam_ori3 or $cam_ori4. Refer back to the dot point following the one about slices.

        Regarding "am more used to people knowing where my code is coming from", "Sorry about the clutter in the code samples." (and a few other related points), take a look at "I know what I mean. Why don't you?" which should address most of these.

        This is only your second post so don't concern yourself too much about mistakes you've made. Mistakes are good: we all make them and, hopefully, all learn from them. :-)

        -- Ken

Re: Perl and Quaternions
by salva (Canon) on May 08, 2014 at 08:03 UTC
    Uff, that's not the usual quaternion!

    The best way to do trigonometric operations with a computer is to convert between degrees and radians on the input/output layer and internally, on your program, use always radians.

    Also, in order to manipulate 2D coordinates, using vector arithmetic would greatly simplify everything. For instance, check Math::Vector::Real.

      Uff, that's not the usual quaternion!

      Actually, it is; quaternions are often used to represent rotations.

        Oh, ok, I got confused because of the X, Y names. 2D and 3D coordinates are promoted to quaternions as {0, x, y, z} and seeing {x, 0, 0, y} didn't feel right!

        In any case, for 2D rotations, quaternions are overkill. You can just use complex numbers.

Re: Perl and Quaternions
by RichardK (Parson) on May 08, 2014 at 10:20 UTC

    Math::Trig provides lots of useful trig functions. Maybe cylindrical_to_cartesian() will give you the right answer?

    Why not post an example of your expected results? It would make testing any suggestions easier.