Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
PickingSystem.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Linq;
5 using System.Threading.Tasks;
6 using SiliconStudio.Paradox;
7 using SiliconStudio.Paradox.DataModel;
8 using SiliconStudio.Paradox.Effects;
9 using SiliconStudio.Paradox.Effects.Modules;
10 using SiliconStudio.Paradox.Engine;
11 using SiliconStudio.Paradox.EntityModel;
12 using SiliconStudio.Paradox.Games;
13 using SiliconStudio.Paradox.Games.Collections;
14 using SiliconStudio.Core.Extensions;
15 using SiliconStudio.Paradox.Games.Mathematics;
16 using SiliconStudio.Paradox.Input;
17 using SiliconStudio.Shaders;
19 
20 namespace ScriptTest
21 {
23  {
24  private class SelectedEntity
25  {
26  public Entity Entity;
27  public Vector3 PickingObjectOrigin;
28 
29  public SelectedEntity(Entity entity)
30  {
31  Entity = entity;
32  }
33  }
34 
35  private Entity translationGizmo;
36  private Entity rotationGizmo;
37  private EntitySystem editorEntitySystem;
38  private SelectedEntity[] selectedEntities;
39  private GizmoAction currentActiveGizmoActionMode;
40  private Entity currentActiveGizmoEntity;
41 
42  private static readonly PropertyKey<GizmoAction> GizmoActionKey = new PropertyKey<GizmoAction>("GizmoAction", typeof(ScriptDebug));
43  private static readonly PropertyKey<Color> GizmoColorKey = new PropertyKey<Color>("GizmoColor", typeof(ScriptDebug));
44 
45  public PickingSystem()
46  {
47  ActiveGizmoActionMode = GizmoAction.Translation;
48  SelectedEntities = new Entity[0];
49  }
50 
51  public event PropertyChangedEventHandler PropertyChanged;
52 
53  /// <summary>Gets or sets currently active picking mode (valid choices: None for selection, Translation, Rotation).</summary>
54  public GizmoAction ActiveGizmoActionMode { get; set; }
55 
56  /// <summary>Gets or sets currently selected entity.</summary>
57  public Entity[] SelectedEntities { get; set; }
58 
60  {
61  var entity = new Entity("RotationGizmo");
62  entity.Set(TransformationComponent.Key, new TransformationComponent());
63 
64  // TODO: Factorize some of this code with GenerateTranslationGizmo?
65  var gizmoActions = new[] { GizmoAction.RotationX, GizmoAction.RotationY, GizmoAction.RotationZ };
66  var orientationMatrices = new[] { Matrix.Identity, Matrix.RotationZ((float)Math.PI * 0.5f), Matrix.RotationY(-(float)Math.PI * 0.5f) };
67  var colors = new[] { Color.Green, Color.Blue, Color.Red };
68 
69  var albedoMaterial = new ShaderMixinSource()
70  {
71  "AlbedoDiffuseBase",
72  "AlbedoSpecularBase",
73  new ShaderComposition("albedoDiffuse", new ShaderClassSource("ComputeColorFixed", MaterialKeys.DiffuseColor)),
74  new ShaderComposition("albedoSpecular", new ShaderClassSource("ComputeColor")), // TODO: Default values!
75  };
76 
77  for (int axis = 0; axis < 3; ++axis)
78  {
79  // Rendering
80  var circleEffectMeshData = new EffectMeshData();
81  circleEffectMeshData.Parameters = new ParameterCollection();
82  circleEffectMeshData.MeshData = MeshDataHelper.CreateCircle(20.0f, 32, colors[axis]);
83  circleEffectMeshData.EffectData = new EffectData("Gizmo") { AlbedoMaterial = albedoMaterial };
84 
85  var circleEntity = new Entity("ArrowCone");
86  circleEntity.GetOrCreate(ModelComponent.Key).SubMeshes.Add(circleEffectMeshData);
87  circleEntity.Set(TransformationComponent.Key, TransformationMatrix.CreateComponent(orientationMatrices[axis]));
88 
89  circleEntity.Set(GizmoColorKey, colors[axis]);
90  circleEntity.Set(GizmoActionKey, gizmoActions[axis]);
91 
92  entity.GetOrCreate(TransformationComponent.Key).Children.Add(circleEntity.GetOrCreate(TransformationComponent.Key));
93  }
94 
95  return entity;
96  }
97 
99  {
100  var entity = new Entity("TranslationGizmo");
101  entity.Set(TransformationComponent.Key, new TransformationComponent());
102 
103  var gizmoActions = new[] { GizmoAction.TranslationX, GizmoAction.TranslationY, GizmoAction.TranslationZ };
104  var colors = new[] { Color.Green, Color.Blue, Color.Red };
105  var orientationMatrices = new[] { Matrix.Identity, Matrix.RotationZ((float)Math.PI * 0.5f), Matrix.RotationY(-(float)Math.PI * 0.5f) };
106 
107  var albedoMaterial = new ShaderMixinSource()
108  {
109  "AlbedoDiffuseBase",
110  "AlbedoSpecularBase",
111  new ShaderComposition("albedoDiffuse", new ShaderClassSource("ComputeColorFixed", MaterialKeys.DiffuseColor)),
112  new ShaderComposition("albedoSpecular", new ShaderClassSource("ComputeColor")), // TODO: Default values!
113  };
114 
115  for (int axis = 0; axis < 3; ++axis)
116  {
117  // Rendering
118  var lineEffectMeshData = new EffectMeshData();
119  lineEffectMeshData.Parameters = new ParameterCollection();
120  lineEffectMeshData.MeshData = MeshDataHelper.CreateLine(20.0f, colors[axis]);
121  lineEffectMeshData.EffectData = new EffectData("Gizmo") { AlbedoMaterial = albedoMaterial };
122 
123  var lineEntity = new Entity("ArrowCone");
124  lineEntity.GetOrCreate(ModelComponent.Key).SubMeshes.Add(lineEffectMeshData);
125  lineEntity.Set(TransformationComponent.Key, TransformationMatrix.CreateComponent(orientationMatrices[axis]));
126 
127  var coneEffectMeshData = new EffectMeshData();
128  coneEffectMeshData.Parameters = new ParameterCollection();
129  coneEffectMeshData.MeshData = MeshDataHelper.CreateCone(2.5f, 10.0f, 10, colors[axis]);
130  coneEffectMeshData.EffectData = new EffectData("Gizmo") { AlbedoMaterial = albedoMaterial };
131 
132  var coneEntity = new Entity("ArrowBody");
133  coneEntity.GetOrCreate(ModelComponent.Key).SubMeshes.Add(coneEffectMeshData);
134  coneEntity.Set(TransformationComponent.Key, TransformationMatrix.CreateComponent(Matrix.Translation(20.0f, 0.0f, 0.0f) * orientationMatrices[axis]));
135 
136  lineEntity.Set(GizmoColorKey, colors[axis]);
137  coneEntity.Set(GizmoColorKey, colors[axis]);
138 
139  lineEntity.Set(GizmoActionKey, gizmoActions[axis]);
140  coneEntity.Set(GizmoActionKey, gizmoActions[axis]);
141 
142  entity.Transformation.Children.Add(lineEntity.Transformation);
143  entity.Transformation.Children.Add(coneEntity.Transformation);
144  }
145 
146  return entity;
147  }
148 
149  private void RefreshGizmos(GizmoAction currentGizmoAction)
150  {
151  var renderingSetup = RenderingSetup.Singleton;
152 
153  // Delay gizmo change until current action is finished
154  if (currentActiveGizmoActionMode != ActiveGizmoActionMode
155  && currentGizmoAction == GizmoAction.None)
156  {
157  if (currentActiveGizmoEntity != null)
158  editorEntitySystem.Entities.Remove(currentActiveGizmoEntity);
159 
160  if (ActiveGizmoActionMode == GizmoAction.Translation)
161  {
162  currentActiveGizmoEntity = translationGizmo;
163  }
164  else if (ActiveGizmoActionMode == GizmoAction.Rotation)
165  {
166  currentActiveGizmoEntity = rotationGizmo;
167  }
168  else
169  {
170  currentActiveGizmoEntity = null;
171  }
172  currentActiveGizmoActionMode = ActiveGizmoActionMode;
173  }
174 
175  if (currentActiveGizmoEntity == null)
176  return;
177 
178  TransformationComponent[] selectedTransformations;
179 
180  if (selectedEntities != null && (selectedTransformations = selectedEntities.Select(x => x.Entity.Transformation).Where(x => x != null && x.Value is TransformationTRS).ToArray()).Length > 0)
181  {
182  if (!editorEntitySystem.Entities.Contains(currentActiveGizmoEntity))
183  editorEntitySystem.Add(currentActiveGizmoEntity);
184 
185  // TODO: Only act on "current" gizmo?
186  var gizmoTransformation = (TransformationTRS)currentActiveGizmoEntity.Transformation.Value;
187  var translationSum = Vector3.Zero;
188 
189  // Make average of selected objects origin to position gizmo
190  foreach (var selectedTransformation in selectedTransformations)
191  {
192  // Compensate scaling
193  // TODO: Negative/zero scaling?
194  var parentMatrix = selectedTransformation.WorldMatrix;
195  translationSum += parentMatrix.TranslationVector;
196  }
197 
198  gizmoTransformation.Translation = translationSum / selectedTransformations.Length;
199 
200  // Make gizmo size constant (screen-space) -- only if not currently being used
201  // TODO: Currently FOV-dependent
202  if (currentGizmoAction == GizmoAction.None)
203  {
204  var view = renderingSetup.MainPlugin.ViewParameters.Get(TransformationKeys.View);
205  var worldView = view;
206  var position = Vector3.TransformCoordinate(gizmoTransformation.Translation, worldView);
207  gizmoTransformation.Scaling = new Vector3(position.Z * 0.01f);
208  }
209  }
210  else
211  {
212  if (editorEntitySystem.Entities.Contains(currentActiveGizmoEntity))
213  editorEntitySystem.Entities.Remove(currentActiveGizmoEntity);
214  }
215  }
216 
217  private void UpdateSelectedEntities(EngineContext engineContext, Entity nextSelectedEntity)
218  {
219  if (engineContext.InputManager.IsKeyDown(Keys.LeftShift))
220  {
221  // Shift pressed: Add or remove entity to current selection
222  if (!selectedEntities.Any(x => x.Entity == nextSelectedEntity))
223  selectedEntities = selectedEntities.Concat(new[] { new SelectedEntity(nextSelectedEntity) }).ToArray();
224  else
225  selectedEntities = selectedEntities.Where(x => x.Entity != nextSelectedEntity).ToArray();
226  }
227  else
228  {
229  // Shift not pressed: Replace selection
230  selectedEntities = nextSelectedEntity != null ? new[] { new SelectedEntity(nextSelectedEntity) } : new SelectedEntity[0];
231  }
232 
233  SelectedEntities = selectedEntities.Select(x => x.Entity).ToArray();
234  }
235 
236  public async Task ProcessGizmoAndPicking(EngineContext engineContext)
237  {
238  var gizmoTargetPlugin = (RenderTargetsPlugin)engineContext.DataContext.RenderPassPlugins.TryGetValue("GizmoTargetPlugin");
239  if (gizmoTargetPlugin == null)
240  return;
241 
242  var pickingResults = new Queue<PickingAction>();
243 
244  var effectGizmo = engineContext.RenderContext.BuildEffect("Gizmo")
245  .Using(
246  new BasicShaderPlugin(
247  new ShaderMixinSource()
248  {
249  "ShaderBase",
250  "TransformationWVP",
251  "AlbedoFlatShading",
252  }) { RenderPassPlugin = gizmoTargetPlugin })
253  .Using(new MaterialShaderPlugin() { RenderPassPlugin = gizmoTargetPlugin })
254  //.Using(new LightingShaderPlugin() { RenderPassPlugin = renderingSetup.LightingPlugin })
255  ;
256 
257  effectGizmo.PickingPassMainPlugin = gizmoTargetPlugin;
258 
259  //effectGizmo.Permutations.Set(LightingPermutation.Key, new LightingPermutation { Lights = new Light[] { new DirectionalLight { LightColor = new Color3(1.0f), LightDirection = new R32G32B32_Float(-1.0f, -1.0f, 1.0f) } } });
260 
261  engineContext.RenderContext.Effects.Add(effectGizmo);
262 
263  editorEntitySystem = new EntitySystem();
264  editorEntitySystem.Processors.Add(new MeshProcessor(engineContext.RenderContext, engineContext.AssetManager));
265  editorEntitySystem.Processors.Add(new HierarchicalProcessor());
266  editorEntitySystem.Processors.Add(new TransformationProcessor());
267  editorEntitySystem.Processors.Add(new TransformationUpdateProcessor());
268 
269  // Prepare gizmo entities
270  translationGizmo = GenerateTranslationGizmo();
271  rotationGizmo = GenerateRotationGizmo();
272  UpdateGizmoHighlighting(GizmoAction.None);
273 
274  RenderPassPlugin renderPassPlugin;
275  engineContext.DataContext.RenderPassPlugins.TryGetValue("PickingPlugin", out renderPassPlugin);
276  var pickingPlugin = renderPassPlugin as PickingPlugin;
277  engineContext.DataContext.RenderPassPlugins.TryGetValue("MouseOverPickingPlugin", out renderPassPlugin);
278  var mouseOverPickingPlugin = renderPassPlugin as PickingPlugin;
279 
280  // States
281  var pickingGizmoOrigin = new Vector3();
282  var previousMouseLocation = new Vector2();
283  var currentGizmoAction = GizmoAction.None;
284  var nextGizmoAction = GizmoAction.None;
285  Entity nextSelectedEntity = null;
286 
287  var previousMousePosition = Vector2.Zero;
288 
289  while (true)
290  {
291  await engineContext.Scheduler.NextFrame();
292 
293  lock (pickingResults)
294  {
295  var mousePosition = engineContext.InputManager.MousePosition;
296  if (engineContext.InputManager.IsMouseButtonPressed(MouseButton.Left))
297  {
298  pickingResults.Enqueue(new PickingAction
299  {
300  Type = PickingActionType.MouseDown,
301  MouseLocation = mousePosition,
302  PickingResult = pickingPlugin.Pick(engineContext.InputManager.MousePosition),
303  });
304  }
305  else if (engineContext.InputManager.IsMouseButtonReleased(MouseButton.Left))
306  {
307  pickingResults.Enqueue(new PickingAction
308  {
309  Type = PickingActionType.MouseUp,
310  MouseLocation = mousePosition,
311  });
312  }
313 
314  if (engineContext.InputManager.IsMouseButtonDown(MouseButton.Left) && previousMousePosition != mousePosition)
315  {
316  pickingResults.Enqueue(new PickingAction
317  {
318  Type = PickingActionType.MouseMove,
319  MouseLocation = mousePosition,
320  });
321  }
322 
323  pickingResults.Enqueue(new PickingAction
324  {
325  Type = PickingActionType.MouseOver,
326  MouseLocation = mousePosition,
327  PickingResult = mouseOverPickingPlugin.Pick(mousePosition)
328  });
329 
330  previousMousePosition = mousePosition;
331 
332  while (pickingResults.Count > 0 && (pickingResults.Peek().PickingResult == null || pickingResults.Peek().PickingResult.IsCompleted))
333  {
334  // State machine handling mouse down/move/over. Everything work in async deferred (picking results only comes after a few frames due to GPU asynchronism).
335  // Possible improvements:
336  // - If user do a mouse down and no gizmo action active, it should either be gizmo action if selected item didn't change, or instant picking (no wait for mouse up) if picking changed.
337  // Gizmo action could probably start on new entity if MouseMove happens during the same sequence.
338  var pickingAction = pickingResults.Dequeue();
339  var pickedEntity = pickingAction.PickingResult != null ? GetPickedEntity(engineContext, pickingAction.PickingResult.Result.EffectMesh) : null;
340  switch (pickingAction.Type)
341  {
342  case PickingActionType.MouseOver:
343  if (currentGizmoAction == GizmoAction.None)
344  {
345  // Mouse over or click on gizmo: highlight the appropriate parts (yellow)
346  nextGizmoAction = pickedEntity != null ? pickedEntity.Get(GizmoActionKey) : GizmoAction.None;
347  UpdateGizmoHighlighting(nextGizmoAction);
348  if (pickedEntity != null)
349  pickingGizmoOrigin = pickingAction.PickingResult.Result.Position;
350  }
351  break;
352  case PickingActionType.MouseDown:
353  nextSelectedEntity = pickedEntity;
354  previousMouseLocation = pickingAction.MouseLocation;
355 
356  // User isn't around a "mouse over gizmo" so it is a picking selection for sure, doesn't wait for mouse move or mouse up
357  if (nextGizmoAction == GizmoAction.None)
358  {
359  UpdateSelectedEntities(engineContext, nextSelectedEntity);
360 
361  // Force gizmo refresh (otherwise it won't happen as we enforce gizmo action right away, however we don't want it to be highlighted until used)
362  RefreshGizmos(GizmoAction.None);
363  UpdateGizmoHighlighting(currentGizmoAction);
364 
365  // Engage default action
366  if (currentActiveGizmoActionMode == GizmoAction.Translation)
367  {
368  currentGizmoAction = GizmoAction.TranslationXY;
369  if (pickedEntity != null)
370  pickingGizmoOrigin = pickingAction.PickingResult.Result.Position;
371  }
372  else if (currentGizmoAction == GizmoAction.Rotation)
373  {
374  currentGizmoAction = GizmoAction.RotationZ;
375  }
376  }
377 
378  if (selectedEntities != null && selectedEntities.Length > 0)
379  {
380  // Save aside picking object origin in case it turns out to be a translation
381  foreach (var selectedEntity in selectedEntities)
382  {
383  var transformationComponent = selectedEntity.Entity.Transformation.Value as TransformationTRS;
384  selectedEntity.PickingObjectOrigin = transformationComponent != null ? transformationComponent.Translation : Vector3.Zero;
385  }
386  }
387  break;
388  case PickingActionType.MouseMove:
389  // Gizmo action just started?
390  if (currentGizmoAction == GizmoAction.None)
391  currentGizmoAction = nextGizmoAction;
392  UpdateGizmoHighlighting(currentGizmoAction);
393  if (selectedEntities != null && selectedEntities.Length > 0)
394  {
395  // Performs translation
396  if ((currentGizmoAction & GizmoAction.Translation) != GizmoAction.None)
397  {
398  // Translation is computed from origin position during mouse down => mouse delta is not reset
399  foreach (var selectedEntity in selectedEntities)
400  {
401  MoveEntity(engineContext, selectedEntity.Entity, currentGizmoAction, pickingGizmoOrigin, selectedEntity.PickingObjectOrigin, pickingAction.MouseLocation - previousMouseLocation);
402  }
403  }
404  else if ((currentGizmoAction & GizmoAction.Rotation) != GizmoAction.None)
405  {
406  foreach (var selectedEntity in selectedEntities)
407  {
408  RotateEntity(engineContext, selectedEntity.Entity, currentGizmoAction, pickingAction.MouseLocation - previousMouseLocation);
409  }
410  // Rotation is using incremental => reset delta
411  previousMouseLocation = pickingAction.MouseLocation;
412  }
413  }
414  break;
415  case PickingActionType.MouseUp:
416  if (currentGizmoAction == GizmoAction.None)
417  {
418  // Selection
419  UpdateSelectedEntities(engineContext, nextSelectedEntity);
420  }
421 
422  // Reset states
423  currentGizmoAction = GizmoAction.None;
424  nextGizmoAction = GizmoAction.None;
425  UpdateGizmoHighlighting(nextGizmoAction);
426  nextSelectedEntity = null;
427  break;
428  }
429  }
430  }
431 
432  if (currentGizmoAction == GizmoAction.None)
433  {
434  if (!EnumerableExtensions.Equals(SelectedEntities, selectedEntities != null ? selectedEntities.Select(x => x.Entity) : null))
435  selectedEntities = SelectedEntities.Select(x => new SelectedEntity(x)).ToArray();
436  }
437 
438  RefreshGizmos(currentGizmoAction);
439  editorEntitySystem.Update();
440  }
441  }
442 
443  private void MoveEntity(EngineContext engineContext, Entity entity, GizmoAction currentGizmoAction, Vector3 pickingGizmoOrigin, Vector3 pickingObjectOrigin, Vector2 delta)
444  {
445  var renderingSetup = RenderingSetup.Singleton;
446 
447  // Get current transformation component
448  var transformationComponent = entity != null ? entity.Transformation : null;
449  if (delta == Vector2.Zero || transformationComponent == null)
450  return;
451 
452  var transformationComponentValues = transformationComponent.Value as TransformationTRS;
453  if (transformationComponentValues == null)
454  return;
455 
456  var viewParameters = renderingSetup.MainPlugin.ViewParameters;
457 
458  transformationComponentValues.Translation = pickingObjectOrigin;
459 
460  if ((currentGizmoAction & GizmoAction.Translation) != GizmoAction.None)
461  {
462  var axes = new Vector3[3];
463  int axisCount = 0;
464  if ((currentGizmoAction & GizmoAction.TranslationX) != GizmoAction.None)
465  axes[axisCount++] = Vector3.UnitX;
466  if ((currentGizmoAction & GizmoAction.TranslationY) != GizmoAction.None)
467  axes[axisCount++] = Vector3.UnitY;
468  if ((currentGizmoAction & GizmoAction.TranslationZ) != GizmoAction.None)
469  axes[axisCount++] = Vector3.UnitZ;
470 
471  if (axisCount == 3)
472  throw new NotImplementedException("Translation should only act on two axes.");
473 
474  var viewProj = viewParameters.Get(TransformationKeys.View) * viewParameters.Get(TransformationKeys.Projection);
475 
476  // Only one axis, we should find the best second "helper" axis to build our plan.
477  // Currently, it looks for another unit axis to build that plan (the one that is the most "perpendicular" to current axis in screenspace).
478  if (axisCount == 1)
479  {
480  Vector3 projectedAxis;
481  Vector3.TransformNormal(ref axes[0], ref viewProj, out projectedAxis);
482  var unitX = Vector3.TransformNormal(Vector3.UnitX, viewProj);
483  var unitY = Vector3.TransformNormal(Vector3.UnitY, viewProj);
484  var unitZ = Vector3.TransformNormal(Vector3.UnitZ, viewProj);
485 
486  // Ignore Z axis (depth)
487  projectedAxis.Z = 0.0f;
488  unitX.Z = 0.0f;
489  unitY.Z = 0.0f;
490  unitZ.Z = 0.0f;
491 
492  // Normalize
493  projectedAxis.Normalize();
494  unitX.Normalize();
495  unitY.Normalize();
496  unitZ.Normalize();
497 
498  var dotX = Math.Abs(Vector3.Dot(unitX, projectedAxis));
499  var dotY = Math.Abs(Vector3.Dot(unitY, projectedAxis));
500  var dotZ = Math.Abs(Vector3.Dot(unitZ, projectedAxis));
501 
502  if (dotX < dotY && dotX < dotZ)
503  axes[1] = Vector3.UnitX;
504  else if (dotY < dotZ)
505  axes[1] = Vector3.UnitY;
506  else
507  axes[1] = Vector3.UnitZ;
508  }
509 
510  var parentMatrix = transformationComponent.Parent != null ? transformationComponent.Parent.WorldMatrix : Matrix.Identity;
511  parentMatrix.Invert();
512 
513  transformationComponentValues.Translation += Vector3.TransformNormal(ComputePickingDelta(engineContext, axes[0], axes[1], ref viewProj, pickingGizmoOrigin, pickingObjectOrigin, delta), parentMatrix);
514  if (axisCount == 2)
515  transformationComponentValues.Translation += Vector3.TransformNormal(ComputePickingDelta(engineContext, axes[1], axes[0], ref viewProj, pickingGizmoOrigin, pickingObjectOrigin, delta), parentMatrix);
516  }
517  }
518 
519  private void RotateEntity(EngineContext engineContext, Entity entity, GizmoAction currentGizmoAction, Vector2 delta)
520  {
521  // Get current transformation component
522  var transformationComponent = entity != null ? entity.Transformation : null;
523 
524  //transformationComponent.Rotation = pickingObjectRotation;
525 
526  if (delta == Vector2.Zero || transformationComponent == null)
527  return;
528 
529  var transformationComponentValues = transformationComponent.Value as TransformationTRS;
530  if (transformationComponentValues == null)
531  return;
532 
533  Matrix currentRotationMatrix;
534 
535  Vector3 pickingGizmoRotationLocal;
536  transformationComponentValues.LocalMatrix.DecomposeXYZ(out pickingGizmoRotationLocal);
537 
538  var currentRotationMatrixLocal = Matrix.RotationX(pickingGizmoRotationLocal.X)
539  * Matrix.RotationY(pickingGizmoRotationLocal.Y)
540  * Matrix.RotationZ(pickingGizmoRotationLocal.Z);
541 
542  var parentMatrix = transformationComponent.Parent != null ? transformationComponent.Parent.WorldMatrix : Matrix.Identity;
543  var parentMatrixInverse = Matrix.Invert(parentMatrix);
544 
545  float deltaRotation = (delta.X + delta.Y) * 0.01f;
546 
547  Vector3 rotationBefore;
548  currentRotationMatrixLocal.DecomposeXYZ(out rotationBefore);
549 
550  // Apply the rotation in parent local space
551  if (currentGizmoAction == GizmoAction.RotationX)
552  {
553  currentRotationMatrix = currentRotationMatrixLocal * parentMatrixInverse * Matrix.RotationX(deltaRotation) * parentMatrix;
554  }
555  else if (currentGizmoAction == GizmoAction.RotationY)
556  {
557  currentRotationMatrix = currentRotationMatrixLocal * parentMatrixInverse * Matrix.RotationY(deltaRotation) * parentMatrix;
558  }
559  else if (currentGizmoAction == GizmoAction.RotationZ)
560  {
561  currentRotationMatrix = currentRotationMatrixLocal * parentMatrixInverse * Matrix.RotationZ(deltaRotation) * parentMatrix;
562  }
563  else
564  {
565  throw new NotImplementedException();
566  }
567 
568  Vector3 rotationAfter;
569  currentRotationMatrix.DecomposeXYZ(out rotationAfter);
570 
571  transformationComponentValues.RotationEuler += rotationAfter - rotationBefore;
572  }
573 
574  private enum PickingActionType
575  {
576  MouseOver = 0,
577  MouseDown = 1,
578  MouseMove = 2,
579  MouseUp = 3,
580  }
581 
582  private struct PickingAction
583  {
584  public PickingActionType Type;
585  public Vector2 MouseLocation;
586  public Task<PickingPlugin.Result> PickingResult;
587  }
588 
589  [Flags]
590  public enum GizmoAction
591  {
592  None = 0,
593  TranslationX = 1,
594  TranslationY = 2,
595  TranslationZ = 4,
600  RotationX = 8,
601  RotationY = 16,
602  RotationZ = 32,
603  Rotation = RotationX | RotationY | RotationZ,
604  }
605 
606  private Entity GetPickedEntity(EngineContext engineContext, EffectMesh pickedEffectMesh)
607  {
608  if (pickedEffectMesh == null)
609  return null;
610 
611  // Find Entity from EffectMesh
612  var pickedEntity = pickedEffectMesh.Get(PickingPlugin.AssociatedEntity);
613  if (pickedEntity == null)
614  {
615  // Iterate over both scene entities and editor entities (i.e. gizmo)
616  var entities = engineContext.EntityManager.Entities.AsEnumerable();
617  if (editorEntitySystem != null)
618  entities = entities.Concat(editorEntitySystem.Entities);
619  foreach (var entity in entities)
620  {
621  var meshComponent = entity.Get(ModelComponent.Key);
622  if (meshComponent == null)
623  continue;
624 
625  if (!meshComponent.InstantiatedSubMeshes.Any(x => x.Value == pickedEffectMesh))
626  continue;
627 
628  pickedEntity = entity;
629  break;
630  }
631  }
632  return pickedEntity;
633  }
634 
635  private void UpdateGizmoHighlighting(GizmoAction gizmoAction)
636  {
637  if (currentActiveGizmoEntity == null)
638  return;
639 
640  var gizmoTranslationHierarchicalParent = currentActiveGizmoEntity.Transformation;
641 
642  foreach (var child in gizmoTranslationHierarchicalParent.Children)
643  {
644  var childGizmoAction = child.Entity.Get(GizmoActionKey);
645  bool isActiveGizmo = ((childGizmoAction & gizmoAction) == childGizmoAction);
646  child.Entity.Get(ModelComponent.Key).MeshParameters.Set(MaterialKeys.DiffuseColor, isActiveGizmo ? Color.Yellow : child.Entity.Get(GizmoColorKey));
647  }
648  }
649 
650  private Vector3 ComputePickingDelta(EngineContext engineContext, Vector3 axis1, Vector3 axis2, ref Matrix viewProj, Vector3 pickingGizmoOrigin, Vector3 pickingObjectOrigin, Vector2 delta)
651  {
652  // Build plane along which object will move
653  var plane = new Plane(pickingGizmoOrigin, pickingGizmoOrigin + axis1, pickingGizmoOrigin + axis2);
654  plane.Normalize();
655 
656  // Position difference in screen space to move one unit
657  var pickingStartTranslationScreenSpace = Vector3.TransformCoordinate(pickingGizmoOrigin, viewProj);
658 
659  // Build mouse picking ray
660  var mouseDelta = new Vector3(delta.X / engineContext.RenderContext.Width * 2.0f,
661  -delta.Y / engineContext.RenderContext.Height * 2.0f,
662  0.0f);
663  var mousePickingPosition = pickingStartTranslationScreenSpace + mouseDelta;
664  var invViewProj = Matrix.Invert(viewProj);
665  mousePickingPosition.Z = 0.0f;
666  var picking1 = Vector3.TransformCoordinate(mousePickingPosition, invViewProj);
667  mousePickingPosition.Z = 0.1f;
668  var picking2 = Vector3.TransformCoordinate(mousePickingPosition, invViewProj);
669 
670  // Intersect moving plane with mouse picking ray
671  var ray = new Ray(picking1, Vector3.Normalize(picking2 - picking1));
672  Vector3 pickingDelta;
673  plane.Intersects(ref ray, out pickingDelta);
674 
675  // Project result along movement axis
676  pickingDelta = Vector3.Dot(axis1, pickingDelta - pickingGizmoOrigin) * axis1;
677 
678  return pickingDelta;
679  }
680  }
681 }
SiliconStudio.Paradox.Games.Mathematics.Vector2 Vector2
PropertyChangedEventHandler PropertyChanged
Entity[] SelectedEntities
Gets or sets currently selected entity.
MouseButton
Mouse buttons.
Definition: MouseButton.cs:10
Game entity. It usually aggregates multiple EntityComponent
Definition: Entity.cs:28
Keys
Enumeration for keys.
Definition: Keys.cs:8
Defines Position, Rotation and Scale of its Entity.
SiliconStudio.Paradox.Engine.MeshProcessor MeshProcessor
Definition: Game.cs:25
SiliconStudio.Paradox.Input.Keys Keys
GizmoAction ActiveGizmoActionMode
Gets or sets currently active picking mode (valid choices: None for selection, Translation, Rotation).
TransformationComponent Transformation
Gets or sets the Transformation associated to this entity. Added for convenience over usual Get/Set m...
Definition: Entity.cs:74
This processor will take care of adding/removing children of every Entity added/removed in the Entity...
static readonly RenderingSetup Singleton
Flags
Enumeration of the new Assimp's flags.
SiliconStudio.Core.Mathematics.Color Color
Definition: ColorPicker.cs:14
Manage a collection of entities.
Definition: EntitySystem.cs:22
void Remove(Entity entity)
Removes the entity from the EntitySystem. It works weither entity has a parent or not...
Level10 render pass using a depth buffer and a render target.
Updates TransformationComponent.WorldMatrix of entities.
SiliconStudio.Core.Mathematics.Vector3 Vector3
void Add(Entity entity)
Adds the entity. If the Entity has a parent, its parent should be added (or TransformationComponent.Children) should be used.
Definition: EntitySystem.cs:90
static readonly ParameterKey< Matrix > Projection
static PropertyKey< TransformationComponent > Key
bool Contains(Entity item)
Determines whether this instance contains the specified entity.
A container to handle a hierarchical collection of effect variables.
async Task ProcessGizmoAndPicking(EngineContext engineContext)