Terragen Euler Order

Started by WAS, April 21, 2022, 11:21:59 PM

Previous topic - Next topic


So maybe you could help here @Matt , is Terragen's Euler order not XYZ? I know +Y is UP and +Z is forward, etc, but Euler order for Blender/FBX and Three.JS don't match TG when in XYZ. It has to be YXZ, and then I need to add 90d to Y to get orientation right.

Is there an orientation offset as well?

I make sure Blender Euler is XYZ, I export XYZ, and +Y UP / +Z Forward but I gotta change the Euler order and apply an offset to match TG.

No one from Three.js / blender / stack overflow can answer this and as puzzled as I am with only clues to go on which got me the right order and offset.


TG's internal rotation order is bank, pitch, heading (ZXY): https://planetside.co.uk/forums/index.php/topic,13730.0.html.

Edit: Pretty sure there's a +90 degree heading offset between TG and Blender, but I'd have to check that. Also Z and Y axes are swapped.

I have some notes about this somewhere. Let me dig those up.


And yet, to get the right orientation, must be reordered to YXZ xD for the rotations to be applied. Even more confusion.


OK, math is not my strong suit but I played around with it for a bit, and here's what I learned (and found in my notes).

Terragen is left-handed (rotation is clockwise) with +Y vertical axis.
Blender is right-handed (rotation is counterclockwise) with +Z vertical axis.
Zero orientation is +Z for Terragen, +Y for Blender.
Terragen rotation order = ZXY
Blender's default rotation = XYZ but it can be set for individual objects. So Blender's corresponding rotation order = YXZ.
So for any object position:

Blender tx = Terragen tx
Blender ty = Terragen tz
Blender tz = Terragen ty
Blender rx = Terragen rx
Blender ry = Terragen rz
Blender rz = Terragen -ry

The problem with camera positions is that the default orientation of Blender's camera is -z (straight down). This can be corrected by adding 90 to its x rotation. But this quickly causes problems when converting camera rotations from TG to Blender that are related (I think) to gimbal lock. I corrected this by setting the x rotation of Blender's camera to 90, then parenting it to another object with zero rotation. If you do that and position the parent object, the above relationships work as long as the object's rotation order in Blender is set to YXZ.

This is a hack, but I'm guessing the true solution is to convert the TG camera's rotation values to quaternion, and using those in Blender. (And vice versa.) But that's way above my pay grade.

Maybe @digitalguru can shed some more light on this.


The quaternion value I am getting seems modulated by Three.js, and I couldn't figure out where. Rotations weren't even resembling what they should have been. Though I honestly didn't pay it much attention. For example, the FBX provides transform matrix data, to apply to scales and rotations. But since I want people to just use all local unit scales (basically unitless) it makes things a lot more straight forward than applying transforms and detecting units.

In the new version of my application, I have allowed the user to select a Euler order, and a rotation offset. So they should be able to overcome any issues with other DCC.

I also noticed I swapped X and Z for XYZ output tot TG, so my Euler order used wasn't accurate cause the end result was being flipped for TG. So I have reset that. But I am finishing wrapping up point lights and spot lights before testing in TG so I can just test the whole camera/light rig exported from Blender.

PS I am terrible with math as well. Heck, my crater generator / light populator tools wouldn't have been possible without Matt giving me some pointers on unnecessary stuff that introduced waaay more complication then I needed. Not to mention one of the most crucial features, I couldn't get to work without his help.


I admire your patience and determination. Good luck with this. Looking forward to seeing the final result.


Thank you.

Turns out I never did solve the rotation issue. The spotlight I import has a totally random rotation, it looks like. I'm lost here. I have the whole thing finished but this darn rotation issue. LOL

Here is the rotation/position info for an object, say the SpotLight here:

matrix: Object { elements: (16) [...] }
elements: Array(16) [ 76.48769037572993, -5.518904820176932, 64.18079861276502, ... ]
<prototype>: Object { isMatrix4: true, extractPosition: extractPosition(m), flattenToArrayOffset: flattenToArrayOffset(array, offset)
, ... }
matrixAutoUpdate: true
matrixWorld: Object { elements: (16) [...] }
elements: Array(16) [ 76.48769037572993, -5.518904820176932, 64.18079861276502, ... ]
<prototype>: Object { isMatrix4: true, extractPosition: extractPosition(m), flattenToArrayOffset: flattenToArrayOffset(array, offset)
, ... }
matrixWorldNeedsUpdate: true
modelViewMatrix: Object { elements: (16) [...] }
elements: Array(16) [ 1, 0, 0, ... ]
0: 1
1: 0
2: 0
3: 0
4: 0
5: 1
6: 0
7: 0
8: 0
9: 0
10: 1
11: 0
12: 0
13: 0
14: 0
15: 1
length: 16
<prototype>: Array []
<prototype>: Object { isMatrix4: true, extractPosition: extractPosition(m), flattenToArrayOffset: flattenToArrayOffset(array, offset)
, ... }
name: "Light001"
normalMatrix: Object { elements: (9) [...] }
elements: Array(9) [ 1, 0, 0, ... ]
<prototype>: Object { isMatrix3: true, flattenToArrayOffset: flattenToArrayOffset(array, offset), multiplyVector3: multiplyVector3(vector)
, ... }
position: Object { x: 292.28216346634, y: 579.4672635697015, z: -293.5565281843084 }
quaternion: Object { _x: -0.2911366906664989, _y: -0.3156872961277598, _z: -0.1338023885619925, ... }
_onChangeCallback: function onQuaternionChange()
_w: 0.8931281426684577
_x: -0.2911366906664989
_y: -0.3156872961277598
_z: -0.1338023885619925
<prototype>: Object { isQuaternion: true, multiplyVector3: multiplyVector3(vector), inverse: inverse()
, ... }
rotation: Object { _x: -0.6491683985586032, _y: -0.6561729208785894, _z: -0.06933744711029033, ... }
_onChangeCallback: function onRotationChange()
_order: "YXZ"
_x: -0.6491683985586032
_y: -0.6561729208785894
_z: -0.06933744711029033
<prototype>: Object { isEuler: true, toVector3: toVector3()
, ... }
_onChange: function _onChange(callback)
_onChangeCallback: function _onChangeCallback()
clone: function clone()
constructor: class Euler { constructor(x, y, z, order) }
copy: function copy(euler)
equals: function equals(euler)
fromArray: function fromArray(array)
isEuler: true
reorder: function reorder(newOrder)
set: function set(x, y, z, order)
setFromQuaternion: function setFromQuaternion(q, order, update)
setFromRotationMatrix: function setFromRotationMatrix(m, order, update)
setFromVector3: function setFromVector3(v, order)
toArray: function toArray(array, offset)
toVector3: function toVector3()
<get order()>: function order()
<set order()>: function order(value)
<get x()>: function x()
<set x()>: function x(value)
<get y()>: function y()
<set y()>: function y(value)
<get z()>: function z()
<set z()>: function z(value)
<prototype>: Object { ... }

If anyone notices anything, let me know. Gah. Info kinda looks bad here on the forums. The tool online now shows all the information for each object (just open the console): https://nwdagroup.com/terragen-tools/fbx2terragen/

Then I apply the rotation conversions to rotation after reordering the euler:

// Camera Rotation in XYZ Degrees
obj.rotation.reorder( eulerOrderString );
let rotation = ( Math.round( THREE.Math.radToDeg( obj.rotation.x ) + rotationOffset
 % 360 ) ) + ' ' + ( Math.round( THREE.Math.radToDeg( obj.rotation.y ) ) + rotationOffset[1] % 360 )
    + ' ' + ( Math.round( THREE.Math.radToDeg( obj.rotation.z ) + rotationOffset[2] % 360 ) );