-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathblog_transforms_transforms.html
110 lines (87 loc) · 8.87 KB
/
blog_transforms_transforms.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Gabor Makes Games</title>
<meta name="author" content="Gabor Szauer">
<link rel="stylesheet" type="text/css" href="css/shared.css"><link rel="stylesheet" type="text/css" href="css/navigation.css"><link rel="stylesheet" type="text/css" href="css/font-raleway.css"><link rel="stylesheet" type="text/css" href="css/font-oxygen.css"><link rel="stylesheet" type="text/css" href="css/font-worksans.css"><link rel="stylesheet" type="text/css" href="css/codepretty/skins/desert.css"><script type="text/javascript" src="js/codepretty/prettify.js"></script><script type="text/javascript" src="js/navigation.js"></script><!-- Global site tag (gtag.js) - Google Analytics --><script async src="https://www.googletagmanager.com/gtag/js?id=UA-96941899-3"></script><script>window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'UA-96941899-3');</script> <link rel="stylesheet" type="text/css" href="css/blog.css">
<script type="text/javascript" src="js/blog_transforms_transforms.js"></script>
</head>
<body onload="MainNavOnLoad();PR.prettyPrint();Init();"> <div class="nav"> <ul class="menu"> <li class="logo"><a href="https://gabormakesgames.com">Gabor Makes Games</a></li> <li class="item"><a id="main-nav-active" href="blog.html">Blog</a></li> <li class="item"><a href="books.html">Books</a></li> <li class="item"><a href="https://github.com/gszauer/">Github</a></li> <li class="item"><a href="https://twitter.com/gszauer">@gszauer</a></li> <li class="toggle"><a href="#">Open Menu</a></li> </ul></div>
<div id="blog">
<div id="sidebar"><a class="sidebar-item sidebar-first sidebar-gap" href="blog.html">Back to blog</a></li><a class="sidebar-item sidebar-first" href="transforms.html">Transform Hierarchies</a></li><a class="sidebar-item" href="blog_transforms_matrices.html">Combining Matrices</a><a class="sidebar-item sidebar-tab" href="blog_transforms_matrix_getters.html">World Space Getters</a></li><a class="sidebar-item sidebar-tab" href="blog_transforms_matrix_setters.html">World Space Setters</a></li><a class="sidebar-item sidebar-active " href="blog_transforms_transforms.html">Combining Transforms</a><a class="sidebar-item sidebar-tab" href="blog_transforms_transform_world.html">World Space</a></li><a class="sidebar-item" href="blog_transforms_summary.html">Summary</a></li></div>
<div id="content">
<h1>Accumulating Transforms</h1>
<p>Instead of accumulating matrices, it's possible to accumulate transforms by combining their rotation / scale / position in the hierarchy directly. This way, instead of finding the matrix of every transform we accumulate the final world transform, then find the matrix of only that world transform. Engines like <a href="https://github.com/EpicGames/UnrealEngine/blob/08ee319f80ef47dbf0988e14b546b65214838ec4/Engine/Source/Runtime/Core/Public/Math/TransformNonVectorized.h#L1273">UE4</a> and <a href="https://github.com/ehsan/ogre/blob/069a43c4c4fcb5264c995fca65a28acd3154b230/OgreMain/src/OgreNode.cpp#L240" target="_blank">Ogre 3D</a> work this way.</p>
<p>When combining two transforms, <i>a</i> and <i>b</i> both the final position and scale is obtained by directly multiplying the scale and rotation of each transform together. The final position is obtained by scaling the position of <i>b</i> by the scale of <i>a</i>, then rotating that point by the rotation of <i>a</i> and adding the result to the position of <i>a</i>. The following code demonstrates how to do this:</p>
<pre class="prettyprint linenums">// a = parent transform, b = child (or current) transform
Transform CombineTransforms(Transform a, Transform b) {
Transform out;
out.scale = a.scale * b.scale; // vec3 * vec3, parent scale times child scale
out.rotation = b.rotation * a.rotation; // quat * quat, Quaternions multiply in reverse, this is parent times child
// parent scale times child position, rotated by parent rotation:
out.position = (a.scale * b.position) * a.rotation; // (vec3 * vec3) * quat, quaternions multiply in reverse
out.position = a.position + out.position; // ve3 + vec3, combine positions
return out;
}</pre>
<p>To use the above code in a hierarchy, the first argument (<i>a</i>) should be the parent transform and the second argument (<i>b</i>) should be the child transform. The code below shows how you can accumulate all transforms in a hierarchy and create a world matrix from the final combined transform.</p>
<pre class="prettyprint linenums">Transform GetWorldTransform(Transform transform) {
Transform worldTransform = transform; // This is acopy, not a reference
if (transform.parent != NULL) {
Transform worldParent = GetWorldTransform(transform.parent);
// Accumulate scale, Vector * Vector
worldTransform.scale = worldParent.scale * worldTransform.scale;
// Accumulate rotation, Quaternion * Quaternion
// Remember, quaternions multiply in reverse order! So:
// parent times child is written as: child * parent
worldTransform.rotation = worldTransform.rotation * worldParent.rotation;
// Accumulate position: scale first, Vector * vector
worldTransform.position = worldParent.scale * worldTransform.position;
// Accumulate position: rotate next, vector * Quaternion (quats rotate right to left)
worldTransform.position = worldTransform.position * worldParent.rotation;
// Accumulate position: transform last, Vector + Vector
worldTransform.position = worldParent.position + worldTransform.position;
}
return worldTransform;
}
Matrix GetWorldMatrix(Transform transform) {
Transform worldSpaceTransform = GetWorldTransform(transform);
return ToMatrix(worldSpaceTransform);
}</pre>
<p>Accumulating transformations this way does not suffer from the same skewing artifact that accumulating matrices does. The below gif shows the rotation of game objects where one of them does not have a uniform scaled parent. The <a href="blog_transforms_matrices.html">skew artifact</a> is not present.</p>
<img src="images/blog_transform_transform_unreal.gif" alt="Unreal rotation NO artifact sample" />
<p>Even tough the skewing artifact was fixed, a new artifact was introduced. You may notice from the above code that all scaling is accumulated in the same space. This prevents the final matrix from having a non-orthogonal basis (no skew is introduced), but scaling happens along the local axis of the transform, not relative to its parent like we would expect.</p>
<p>The interactive example below shows this artifact. The basis vectors of the world are rendered in by red / green / blue rods. You can affect the scale of the piston with the sliderbar, notice how the "Accumulate Matrices" option looks more natural (because it scales length along the x axis as expected).</p>
<canvas id="polar_2d_canvas" width="800" height="80">
Canvas support required
</canvas>
<canvas id="polar_3d_canvas" width="800" height="400">
WebGL support required
</canvas>
<p>The cylinders in the above demo are oriented to the z axis. The piston looking object is made up of the following hierarchy (values are updated live with slider):</p>
<ul>
<li><b>Root Transform</b> (not visible)</li>
<li>position: <span id="root_pos">(2.50, 3.00, 3.00)</span></li>
<li>rotation: <span id="root_rot">(1.00, 0.00, 0.00, 0.00)</span></li>
<li>scale: <span id="root_scl">(1.00, 1.00, 1.00)</span></li>
<li>
<ul>
<li><b>Transform A</b> (magenta cylinder)</li>
<li>position: <span id="parent_pos">(0.00, 0.00, 1.50)</span></li>
<li>rotation: <span id="parent_rot">(0.71, 0.00, 0.71, 0.00)</span></li>
<li>scale: <span id="parent_scl">(0.25, 0.25, 2.00)</span></li>
<li>
<ul>
<li><b>Transform B</b> (cyan cylinder)</li>
<li>position: <span id="child_pos">(0.00, 0.00, 0.50)</span></li>
<li>rotation: <span id="child_rot">(1.00, 0.00, 0.00, 0.00)</span></li>
<li>scale: <span id="child_scl">(4.00, 4.00, 0.50)</span></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</div>
</body>
</html>