/
TransformComponent.cs
387 lines (337 loc) · 14.7 KB
/
TransformComponent.cs
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
// Copyright (c) Stride contributors (https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using Stride.Core;
using Stride.Core.Collections;
using Stride.Core.Mathematics;
using Stride.Core.Serialization;
using Stride.Engine.Design;
using Stride.Engine.Processors;
namespace Stride.Engine
{
/// <summary>
/// Defines Position, Rotation and Scale of its <see cref="Entity"/>.
/// </summary>
[DataContract("TransformComponent")]
[DataSerializerGlobal(null, typeof(FastCollection<TransformComponent>))]
[DefaultEntityComponentProcessor(typeof(TransformProcessor))]
[Display("Transform", Expand = ExpandRule.Once)]
[ComponentOrder(0)]
public sealed class TransformComponent : EntityComponent //, IEnumerable<TransformComponent> Check why this is not working
{
private static readonly TransformOperation[] EmptyTransformOperations = new TransformOperation[0];
// When false, transformation should be computed in TransformProcessor (no dependencies).
// When true, transformation is computed later by another system.
// This is useful for scenario such as binding a node to a bone, where it first need to run TransformProcessor for the hierarchy,
// run MeshProcessor to update ModelViewHierarchy, copy Node/Bone transformation to another Entity with special root and then update its children transformations.
private bool useTRS = true;
private TransformComponent parent;
private readonly TransformChildrenCollection children;
internal bool IsMovingInsideRootScene;
/// <summary>
/// This is where we can register some custom work to be done after world matrix has been computed, such as updating model node hierarchy or physics for local node.
/// </summary>
[DataMemberIgnore]
public FastListStruct<TransformOperation> PostOperations = new FastListStruct<TransformOperation>(EmptyTransformOperations);
/// <summary>
/// The world matrix.
/// Its value is automatically recomputed at each frame from the local and the parent matrices.
/// One can use <see cref="UpdateWorldMatrix"/> to force the update to happen before next frame.
/// </summary>
/// <remarks>The setter should not be used and is accessible only for performance purposes.</remarks>
[DataMemberIgnore]
public Matrix WorldMatrix = Matrix.Identity;
/// <summary>
/// The local matrix.
/// Its value is automatically recomputed at each frame from the position, rotation and scale.
/// One can use <see cref="UpdateLocalMatrix"/> to force the update to happen before next frame.
/// </summary>
/// <remarks>The setter should not be used and is accessible only for performance purposes.</remarks>
[DataMemberIgnore]
public Matrix LocalMatrix = Matrix.Identity;
/// <summary>
/// The translation relative to the parent transformation.
/// </summary>
/// <userdoc>The translation of the entity with regard to its parent</userdoc>
[DataMember(10)]
public Vector3 Position;
/// <summary>
/// The rotation relative to the parent transformation.
/// </summary>
/// <userdoc>The rotation of the entity with regard to its parent</userdoc>
[DataMember(20)]
public Quaternion Rotation;
/// <summary>
/// The scaling relative to the parent transformation.
/// </summary>
/// <userdoc>The scale of the entity with regard to its parent</userdoc>
[DataMember(30)]
public Vector3 Scale;
[DataMemberIgnore]
public TransformLink TransformLink;
/// <summary>
/// Initializes a new instance of the <see cref="TransformComponent" /> class.
/// </summary>
public TransformComponent()
{
children = new TransformChildrenCollection(this);
UseTRS = true;
Scale = Vector3.One;
Rotation = Quaternion.Identity;
}
/// <summary>
/// Gets or sets a value indicating whether to use the Translation/Rotation/Scale.
/// </summary>
/// <value><c>true</c> if [use TRS]; otherwise, <c>false</c>.</value>
[DataMemberIgnore]
[Display(Browsable = false)]
[DefaultValue(true)]
public bool UseTRS
{
get { return useTRS; }
set { useTRS = value; }
}
/// <summary>
/// Gets the children of this <see cref="TransformComponent"/>.
/// </summary>
public FastCollection<TransformComponent> Children => children;
/// <summary>
/// Gets or sets the euler rotation, with XYZ order.
/// Not stable: setting value and getting it again might return different value as it is internally encoded as a <see cref="Quaternion"/> in <see cref="Rotation"/>.
/// </summary>
/// <value>
/// The euler rotation.
/// </value>
[DataMemberIgnore]
public Vector3 RotationEulerXYZ
{
// Unfortunately it is not possible to factorize the following code with Quaternion.RotationYawPitchRoll because Z axis direction is inversed
get
{
var rotation = Rotation;
Vector3 rotationEuler;
// Equivalent to:
// Matrix rotationMatrix;
// Matrix.Rotation(ref cachedRotation, out rotationMatrix);
// rotationMatrix.DecomposeXYZ(out rotationEuler);
float xx = rotation.X * rotation.X;
float yy = rotation.Y * rotation.Y;
float zz = rotation.Z * rotation.Z;
float xy = rotation.X * rotation.Y;
float zw = rotation.Z * rotation.W;
float zx = rotation.Z * rotation.X;
float yw = rotation.Y * rotation.W;
float yz = rotation.Y * rotation.Z;
float xw = rotation.X * rotation.W;
rotationEuler.Y = (float)Math.Asin(2.0f * (yw - zx));
double test = Math.Cos(rotationEuler.Y);
if (test > 1e-6f)
{
rotationEuler.Z = (float)Math.Atan2(2.0f * (xy + zw), 1.0f - (2.0f * (yy + zz)));
rotationEuler.X = (float)Math.Atan2(2.0f * (yz + xw), 1.0f - (2.0f * (yy + xx)));
}
else
{
rotationEuler.Z = (float)Math.Atan2(2.0f * (zw - xy), 2.0f * (zx + yw));
rotationEuler.X = 0.0f;
}
return rotationEuler;
}
set
{
// Equilvalent to:
// Quaternion quatX, quatY, quatZ;
//
// Quaternion.RotationX(value.X, out quatX);
// Quaternion.RotationY(value.Y, out quatY);
// Quaternion.RotationZ(value.Z, out quatZ);
//
// rotation = quatX * quatY * quatZ;
var halfAngles = value * 0.5f;
var fSinX = (float)Math.Sin(halfAngles.X);
var fCosX = (float)Math.Cos(halfAngles.X);
var fSinY = (float)Math.Sin(halfAngles.Y);
var fCosY = (float)Math.Cos(halfAngles.Y);
var fSinZ = (float)Math.Sin(halfAngles.Z);
var fCosZ = (float)Math.Cos(halfAngles.Z);
var fCosXY = fCosX * fCosY;
var fSinXY = fSinX * fSinY;
Rotation.X = fSinX * fCosY * fCosZ - fSinZ * fSinY * fCosX;
Rotation.Y = fSinY * fCosX * fCosZ + fSinZ * fSinX * fCosY;
Rotation.Z = fSinZ * fCosXY - fSinXY * fCosZ;
Rotation.W = fCosZ * fCosXY + fSinXY * fSinZ;
}
}
/// <summary>
/// Gets or sets the parent of this <see cref="TransformComponent"/>.
/// </summary>
/// <value>
/// The parent.
/// </value>
[DataMemberIgnore]
public TransformComponent Parent
{
get { return parent; }
set
{
var oldParent = Parent;
if (oldParent == value)
return;
// SceneValue must be null if we have a parent
if( Entity.SceneValue != null )
Entity.Scene = null;
var previousScene = oldParent?.Entity?.Scene;
var newScene = value?.Entity?.Scene;
// Get to root scene
while (previousScene?.Parent != null)
previousScene = previousScene.Parent;
while (newScene?.Parent != null)
newScene = newScene.Parent;
// Check if root scene didn't change
bool moving = (newScene != null && newScene == previousScene);
if (moving)
IsMovingInsideRootScene = true;
// Add/Remove
oldParent?.Children.Remove(this);
value?.Children.Add(this);
if (moving)
IsMovingInsideRootScene = false;
}
}
/// <summary>
/// Updates the local matrix.
/// If <see cref="UseTRS"/> is true, <see cref="LocalMatrix"/> will be updated from <see cref="Position"/>, <see cref="Rotation"/> and <see cref="Scale"/>.
/// </summary>
public void UpdateLocalMatrix()
{
if (UseTRS)
{
Matrix.Transformation(ref Scale, ref Rotation, ref Position, out LocalMatrix);
}
}
/// <summary>
/// Updates the local matrix based on the world matrix and the parent entity's or containing scene's world matrix.
/// </summary>
public void UpdateLocalFromWorld()
{
if (Parent == null)
{
var scene = Entity?.Scene;
if (scene != null)
{
Matrix.Invert(ref scene.WorldMatrix, out var inverseSceneTransform);
Matrix.Multiply(ref WorldMatrix, ref inverseSceneTransform, out LocalMatrix);
}
else
{
LocalMatrix = WorldMatrix;
}
}
else
{
//We are not root so we need to derive the local matrix as well
Matrix.Invert(ref Parent.WorldMatrix, out var inverseParent);
Matrix.Multiply(ref WorldMatrix, ref inverseParent, out LocalMatrix);
}
}
/// <summary>
/// Updates the world matrix.
/// It will first call <see cref="UpdateLocalMatrix"/> on self, and <see cref="UpdateWorldMatrix"/> on <see cref="Parent"/> if not null.
/// Then <see cref="WorldMatrix"/> will be updated by multiplying <see cref="LocalMatrix"/> and parent <see cref="WorldMatrix"/> (if any).
/// </summary>
public void UpdateWorldMatrix()
{
UpdateLocalMatrix();
UpdateWorldMatrixInternal(true);
}
internal void UpdateWorldMatrixInternal(bool recursive)
{
if (TransformLink != null)
{
Matrix linkMatrix;
TransformLink.ComputeMatrix(recursive, out linkMatrix);
Matrix.Multiply(ref LocalMatrix, ref linkMatrix, out WorldMatrix);
}
else if (Parent != null)
{
if (recursive)
Parent.UpdateWorldMatrix();
Matrix.Multiply(ref LocalMatrix, ref Parent.WorldMatrix, out WorldMatrix);
}
else
{
var scene = Entity?.Scene;
if (scene != null)
{
if (recursive)
{
scene.UpdateWorldMatrix();
}
Matrix.Multiply(ref LocalMatrix, ref scene.WorldMatrix, out WorldMatrix);
}
else
{
WorldMatrix = LocalMatrix;
}
}
foreach (var transformOperation in PostOperations)
{
transformOperation.Process(this);
}
}
[DataContract]
public class TransformChildrenCollection : FastCollection<TransformComponent>
{
TransformComponent transform;
Entity Entity => transform.Entity;
public TransformChildrenCollection(TransformComponent transformParam)
{
transform = transformParam;
}
private void OnTransformAdded(TransformComponent item)
{
if (item.Parent != null)
throw new InvalidOperationException("This TransformComponent already has a Parent, detach it first.");
item.parent = transform;
Entity?.EntityManager?.OnHierarchyChanged(item.Entity);
Entity?.EntityManager?.GetProcessor<TransformProcessor>().NotifyChildrenCollectionChanged(item, true);
}
private void OnTransformRemoved(TransformComponent item)
{
if (item.Parent != transform)
throw new InvalidOperationException("This TransformComponent's parent is not the expected value.");
item.parent = null;
Entity?.EntityManager?.OnHierarchyChanged(item.Entity);
Entity?.EntityManager?.GetProcessor<TransformProcessor>().NotifyChildrenCollectionChanged(item, false);
}
/// <inheritdoc/>
protected override void InsertItem(int index, TransformComponent item)
{
base.InsertItem(index, item);
OnTransformAdded(item);
}
/// <inheritdoc/>
protected override void RemoveItem(int index)
{
OnTransformRemoved(this[index]);
base.RemoveItem(index);
}
/// <inheritdoc/>
protected override void ClearItems()
{
for (var i = Count - 1; i >= 0; --i)
OnTransformRemoved(this[i]);
base.ClearItems();
}
/// <inheritdoc/>
protected override void SetItem(int index, TransformComponent item)
{
OnTransformRemoved(this[index]);
base.SetItem(index, item);
OnTransformAdded(this[index]);
}
}
}
}