Merge branch 'master' of https://github.com/FlaxEngine/FlaxEngine into feature/1151-play-mode-actions

This commit is contained in:
envision3d
2023-07-20 02:00:53 -05:00
128 changed files with 1679 additions and 608 deletions

Binary file not shown.

BIN
Content/Editor/Gizmo/Material.flax (Stored with Git LFS)

Binary file not shown.

BIN
Content/Editor/TexturePreviewMaterial.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -3,7 +3,7 @@
"Version": {
"Major": 1,
"Minor": 6,
"Build": 6342
"Build": 6344
},
"Company": "Flax",
"Copyright": "Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.",

View File

@@ -29,7 +29,7 @@ namespace FlaxEditor.Content
if (asset)
{
var source = Editor.GetShaderSourceCode(asset);
Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source", item.RootWindow.Window);
Utilities.Utils.ShowSourceCodeWindow(source, "Shader Source", item.RootWindow?.Window);
}
return null;
}

View File

@@ -3,7 +3,6 @@
using FlaxEditor.CustomEditors;
using FlaxEditor.Windows;
using FlaxEngine.GUI;
using FlaxEngine;
using DockState = FlaxEditor.GUI.Docking.DockState;
namespace FlaxEditor
@@ -86,8 +85,12 @@ namespace FlaxEditor
if (!FlaxEngine.Scripting.IsTypeFromGameScripts(type))
return;
Editor.Instance.Windows.AddToRestore(this);
if (!Window.IsHidden)
{
Editor.Instance.Windows.AddToRestore(this);
}
Window.Close();
Window.Dispose();
}
/// <summary>

View File

@@ -167,7 +167,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
Presenter.Undo?.AddAction(new MultiUndoAction(actions));
// Build ragdoll
SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll);
AnimatedModelNode.BuildRagdoll(animatedModel, options, ragdoll);
}
private void OnRebuildBone(Button button)
@@ -191,7 +191,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
// Build ragdoll
SceneGraph.Actors.AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name);
AnimatedModelNode.BuildRagdoll(animatedModel, new AnimatedModelNode.RebuildOptions(), ragdoll, name);
}
private void OnRemoveBone(Button button)

View File

@@ -33,7 +33,12 @@ namespace FlaxEditor.CustomEditors.Editors
[CustomEditor(typeof(Asset)), DefaultEditor]
public class AssetRefEditor : CustomEditor
{
private AssetPicker _picker;
/// <summary>
/// The asset picker used to get a reference to an asset.
/// </summary>
public AssetPicker Picker;
private bool _isRefreshing;
private ScriptType _valueType;
/// <inheritdoc />
@@ -44,7 +49,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
if (HasDifferentTypes)
return;
_picker = layout.Custom<AssetPicker>().CustomControl;
Picker = layout.Custom<AssetPicker>().CustomControl;
_valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]);
var assetType = _valueType;
@@ -66,7 +71,7 @@ namespace FlaxEditor.CustomEditors.Editors
{
// Generic file picker
assetType = ScriptType.Null;
_picker.FileExtension = assetReference.TypeName;
Picker.FileExtension = assetReference.TypeName;
}
else
{
@@ -78,23 +83,25 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
_picker.AssetType = assetType;
_picker.Height = height;
_picker.SelectedItemChanged += OnSelectedItemChanged;
Picker.AssetType = assetType;
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
private void OnSelectedItemChanged()
{
if (_isRefreshing)
return;
if (typeof(AssetItem).IsAssignableFrom(_valueType.Type))
SetValue(_picker.SelectedItem);
SetValue(Picker.SelectedItem);
else if (_valueType.Type == typeof(Guid))
SetValue(_picker.SelectedID);
SetValue(Picker.SelectedID);
else if (_valueType.Type == typeof(SceneReference))
SetValue(new SceneReference(_picker.SelectedID));
SetValue(new SceneReference(Picker.SelectedID));
else if (_valueType.Type == typeof(string))
SetValue(_picker.SelectedPath);
SetValue(Picker.SelectedPath);
else
SetValue(_picker.SelectedAsset);
SetValue(Picker.SelectedAsset);
}
/// <inheritdoc />
@@ -104,16 +111,18 @@ namespace FlaxEditor.CustomEditors.Editors
if (!HasDifferentValues)
{
_isRefreshing = true;
if (Values[0] is AssetItem assetItem)
_picker.SelectedItem = assetItem;
Picker.SelectedItem = assetItem;
else if (Values[0] is Guid guid)
_picker.SelectedID = guid;
Picker.SelectedID = guid;
else if (Values[0] is SceneReference sceneAsset)
_picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
Picker.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID);
else if (Values[0] is string path)
_picker.SelectedPath = path;
Picker.SelectedPath = path;
else
_picker.SelectedAsset = Values[0] as Asset;
Picker.SelectedAsset = Values[0] as Asset;
_isRefreshing = false;
}
}
}

View File

@@ -41,7 +41,17 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
element.CustomControl.Value = (Color)Values[0];
var value = Values[0];
if (value is Color asColor)
element.CustomControl.Value = asColor;
else if (value is Color32 asColor32)
element.CustomControl.Value = asColor32;
else if (value is Float4 asFloat4)
element.CustomControl.Value = asFloat4;
else if (value is Double4 asDouble4)
element.CustomControl.Value = (Float4)asDouble4;
else if (value is Vector4 asVector4)
element.CustomControl.Value = asVector4;
}
}
}

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.CustomEditors.Elements;
using FlaxEditor.CustomEditors.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
namespace FlaxEditor.CustomEditors.Editors
@@ -13,6 +15,11 @@ namespace FlaxEditor.CustomEditors.Editors
{
private GroupElement _group;
private bool _updateName;
private int _entryIndex;
private bool _isRefreshing;
private MaterialBase _material;
private ModelInstanceActor _modelInstance;
private AssetRefEditor _materialEditor;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
@@ -21,47 +28,122 @@ namespace FlaxEditor.CustomEditors.Editors
var group = layout.Group("Entry");
_group = group;
if (ParentEditor == null)
return;
var entry = (ModelInstanceEntry)Values[0];
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
var materialLabel = new PropertyNameLabel("Material");
materialLabel.TooltipText = "The mesh surface material used for the rendering.";
if (ParentEditor.ParentEditor?.Values[0] is ModelInstanceActor modelInstance)
{
_entryIndex = entryIndex;
_modelInstance = modelInstance;
var slots = modelInstance.MaterialSlots;
if (entry.Material == slots[entryIndex].Material)
{
// Ensure that entry with default material set is set back to null
modelInstance.SetMaterial(entryIndex, null);
}
_material = modelInstance.GetMaterial(entryIndex);
var defaultValue = GPUDevice.Instance.DefaultMaterial;
if (slots[entryIndex].Material)
{
// Use default value set on asset (eg. Model Asset)
defaultValue = slots[entryIndex].Material;
}
// Create material picker
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
materialEditor.Values.SetDefaultValue(defaultValue);
materialEditor.RefreshDefaultValue();
materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged;
_materialEditor = materialEditor;
}
base.Initialize(group);
}
private void OnSelectedMaterialChanged()
{
if (_isRefreshing)
return;
_isRefreshing = true;
var slots = _modelInstance.MaterialSlots;
var material = _materialEditor.Picker.SelectedAsset as MaterialBase;
var defaultMaterial = GPUDevice.Instance.DefaultMaterial;
var value = (ModelInstanceEntry)Values[0];
var prevMaterial = value.Material;
if (!material)
{
// Fallback to default material
_materialEditor.Picker.SelectedAsset = defaultMaterial;
value.Material = defaultMaterial;
}
else if (material == slots[_entryIndex].Material)
{
// Asset default material
value.Material = null;
}
else if (material == defaultMaterial && !slots[_entryIndex].Material)
{
// Default material while asset has no set as well
value.Material = null;
}
else
{
// Custom material
value.Material = material;
}
if (prevMaterial != value.Material)
SetValue(value);
_isRefreshing = false;
}
/// <inheritdoc />
protected override void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
{
// Skip material member as it is overridden
if (item.Info.Name == "Material" && _materialEditor != null)
return;
base.SpawnProperty(itemLayout, itemValues, item);
}
/// <inheritdoc />
public override void Refresh()
{
// Update panel title to match material slot name
if (_updateName &&
_group != null &&
ParentEditor?.ParentEditor != null &&
ParentEditor.ParentEditor.Values.Count > 0)
{
var entryIndex = ParentEditor.ChildrenEditors.IndexOf(this);
if (ParentEditor.ParentEditor.Values[0] is StaticModel staticModel)
if (ParentEditor.ParentEditor.Values[0] is ModelInstanceActor modelInstance)
{
var model = staticModel.Model;
if (model && model.IsLoaded)
var slots = modelInstance.MaterialSlots;
if (slots != null && slots.Length > entryIndex)
{
var slots = model.MaterialSlots;
if (slots != null && slots.Length > entryIndex)
{
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
_updateName = false;
}
}
}
else if (ParentEditor.ParentEditor.Values[0] is AnimatedModel animatedModel)
{
var model = animatedModel.SkinnedModel;
if (model && model.IsLoaded)
{
var slots = model.MaterialSlots;
if (slots != null && slots.Length > entryIndex)
{
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
_updateName = false;
}
_updateName = false;
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
}
}
}
// Refresh currently selected material
_material = _modelInstance.GetMaterial(_entryIndex);
base.Refresh();
}
/// <inheritdoc />
protected override void Deinitialize()
{
_material = null;
_modelInstance = null;
_materialEditor = null;
base.Deinitialize();
}
}
}

View File

@@ -390,7 +390,6 @@ namespace FlaxEditor.CustomEditors.Editors
if (addTagDropPanel.IsClosed)
{
Debug.Log("Hit");
nameTextBox.BorderColor = Color.Transparent;
nameTextBox.BorderSelectedColor = FlaxEngine.GUI.Style.Current.BackgroundSelected;
return;

View File

@@ -37,7 +37,8 @@ public class Editor : EditorModule
{
base.Setup(options);
options.ScriptingAPI.SystemReferences.Add("System.Private.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml");
options.ScriptingAPI.SystemReferences.Add("System.Xml.ReaderWriter");
options.ScriptingAPI.SystemReferences.Add("System.Text.RegularExpressions");
options.ScriptingAPI.SystemReferences.Add("System.ComponentModel.TypeConverter");
@@ -101,5 +102,7 @@ public class Editor : EditorModule
files.Add(Path.Combine(FolderPath, "Cooker/GameCooker.h"));
files.Add(Path.Combine(FolderPath, "Cooker/PlatformTools.h"));
files.Add(Path.Combine(FolderPath, "Cooker/Steps/CookAssetsStep.h"));
files.Add(Path.Combine(FolderPath, "Utilities/ScreenUtilities.h"));
files.Add(Path.Combine(FolderPath, "Utilities/ViewportIconsRenderer.h"));
}
}

View File

@@ -27,6 +27,17 @@ namespace FlaxEditor.GUI
/// </summary>
public ScriptType Type => _type;
/// <summary>
/// Initializes a new instance of the <see cref="TypeItemView"/> class.
/// </summary>
public TypeItemView()
{
_type = ScriptType.Null;
Name = "<null>";
TooltipText = "Unset value.";
Tag = _type;
}
/// <summary>
/// Initializes a new instance of the <see cref="TypeItemView"/> class.
/// </summary>
@@ -83,6 +94,7 @@ namespace FlaxEditor.GUI
// TODO: use async thread to search types without UI stall
var allTypes = Editor.Instance.CodeEditing.All.Get();
AddItem(new TypeItemView());
for (int i = 0; i < allTypes.Count; i++)
{
var type = allTypes[i];

View File

@@ -178,6 +178,8 @@ namespace FlaxEditor.Gizmo
if (selection[i] is ActorNode actorNode && actorNode.Actor != null)
CollectActors(actorNode.Actor);
}
if (_actors.Count == 0)
return;
// Render selected objects depth
Renderer.DrawSceneDepth(context, task, customDepth, _actors);

View File

@@ -20,7 +20,7 @@ namespace FlaxEditor
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
@@ -44,7 +44,7 @@ namespace FlaxEditor
/// <param name="existingValue">The existing property value of the JSON that is being converted.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{

View File

@@ -112,6 +112,7 @@ public:
{
Version = ::Version(1, 0);
DefaultSceneSpawn = Ray(Vector3::Zero, Vector3::Forward);
DefaultScene = Guid::Empty;
}
/// <summary>

View File

@@ -613,6 +613,28 @@ bool ScriptsBuilderService::Init()
const String targetOutput = Globals::ProjectFolder / TEXT("Binaries") / target / platform / architecture / configuration;
Array<String> files;
FileSystem::DirectoryGetFiles(files, targetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly);
for (const auto& reference : Editor::Project->References)
{
if (reference.Project->Name == TEXT("Flax"))
continue;
String referenceTarget;
if (reference.Project->EditorTarget.HasChars())
{
referenceTarget = reference.Project->EditorTarget.Get();
}
else if (reference.Project->GameTarget.HasChars())
{
referenceTarget = reference.Project->GameTarget.Get();
}
if (referenceTarget.IsEmpty())
continue;
const String referenceTargetOutput = reference.Project->ProjectFolderPath / TEXT("Binaries") / referenceTarget / platform / architecture / configuration;
FileSystem::DirectoryGetFiles(files, referenceTargetOutput, TEXT("*.HotReload.*"), DirectorySearchOption::TopDirectoryOnly);
}
if (files.HasItems())
LOG(Info, "Removing {0} files from previous Editor run hot-reloads", files.Count());
for (auto& file : files)

View File

@@ -363,7 +363,6 @@ namespace FlaxEditor.Surface.ContextMenu
Profiler.BeginEvent("VisjectCM.RemoveGroup");
if (group.Archetypes.Count == 0)
{
Debug.Log("Remove");
_groups.RemoveAt(i);
group.Dispose();
}

View File

@@ -292,7 +292,6 @@ void FoliageTools::Paint(Foliage* foliage, Span<int32> foliageTypesIndices, cons
{
PROFILE_CPU_NAMED("Place Instances");
Matrix matrix;
FoliageInstance instance;
Quaternion tmp;
Matrix world;

View File

@@ -1538,6 +1538,7 @@ namespace FlaxEditor.Viewport
new ViewFlagOptions(ViewFlags.MotionBlur, "Motion Blur"),
new ViewFlagOptions(ViewFlags.ContactShadows, "Contact Shadows"),
new ViewFlagOptions(ViewFlags.PhysicsDebug, "Physics Debug"),
new ViewFlagOptions(ViewFlags.LightsDebug, "Lights Debug"),
new ViewFlagOptions(ViewFlags.DebugDraw, "Debug Draw"),
};

View File

@@ -3,8 +3,6 @@
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
using FlaxEditor.GUI.Input;
using FlaxEngine.GUI;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -15,11 +13,10 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="AssetPreview" />
public class AnimatedModelPreview : AssetPreview
{
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private bool _showNodes, _showBounds, _showFloor, _showCurrentLOD, _showNodesNames;
private AnimatedModel _previewModel;
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private bool _showNodes, _showBounds, _showFloor, _showNodesNames;
private StaticModel _floorModel;
private ContextMenuButton _showCurrentLODButton;
private bool _playAnimation, _playAnimationOnce;
private float _playSpeed = 1.0f;
@@ -188,8 +185,8 @@ namespace FlaxEditor.Viewport.Previews
{
UseTimeScale = false,
UpdateWhenOffscreen = true,
//_previewModel.BoundsScale = 1000.0f;
UpdateMode = AnimatedModel.AnimationUpdateMode.Manual
BoundsScale = 100.0f,
UpdateMode = AnimatedModel.AnimationUpdateMode.Manual,
};
Task.AddCustomActor(_previewModel);
@@ -207,26 +204,6 @@ namespace FlaxEditor.Viewport.Previews
// Show Floor
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
_showFloorButton.IndexInParent = 1;
// Show Current LOD
_showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button =>
{
_showCurrentLOD = !_showCurrentLOD;
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
});
_showCurrentLODButton.IndexInParent = 2;
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
}
}
// Enable shadows
@@ -339,44 +316,6 @@ namespace FlaxEditor.Viewport.Previews
_previewModel.ResetAnimation();
}
private int ComputeLODIndex(SkinnedModel model)
{
if (PreviewActor.ForcedLOD != -1)
return PreviewActor.ForcedLOD;
// Based on RenderTools::ComputeModelLOD
CreateProjectionMatrix(out var projectionMatrix);
float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22);
var sphere = PreviewActor.Sphere;
var viewOrigin = ViewPosition;
var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin);
var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr);
// Check if model is being culled
if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared)
return -1;
// Skip if no need to calculate LOD
if (model.LoadedLODs == 0)
return -1;
var lods = model.LODs;
if (lods.Length == 0)
return -1;
if (lods.Length == 1)
return 0;
// Iterate backwards and return the first matching LOD
for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--)
{
if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
{
return lodIndex + PreviewActor.LODBias;
}
}
return 0;
}
/// <inheritdoc />
protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
{
@@ -440,45 +379,6 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var skinnedModel = _previewModel.SkinnedModel;
if (skinnedModel == null || !skinnedModel.IsLoaded)
return;
var lods = skinnedModel.LODs;
if (lods.Length == 0)
{
// Force show skeleton for models without geometry
ShowNodes = true;
return;
}
if (_showCurrentLOD)
{
var lodIndex = ComputeLODIndex(skinnedModel);
string text = string.Format("Current LOD: {0}", lodIndex);
if (lodIndex != -1)
{
lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1);
var lod = lods[lodIndex];
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++)
{
var mesh = lod.Meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount);
}
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
}
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
@@ -498,14 +398,21 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(_previewModel.Box);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(_previewModel.Box);
ResetCamera();
return true;
case KeyboardKeys.Spacebar:
PlayAnimation = !PlayAnimation;
@@ -525,7 +432,6 @@ namespace FlaxEditor.Viewport.Previews
_showNodesButton = null;
_showBoundsButton = null;
_showFloorButton = null;
_showCurrentLODButton = null;
_showNodesNamesButton = null;
base.OnDestroy();

View File

@@ -4,6 +4,8 @@ using System;
using FlaxEditor.Surface;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
using FlaxEditor.GUI.ContextMenu;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -46,6 +48,7 @@ namespace FlaxEditor.Viewport.Previews
private int _selectedModelIndex;
private Image _guiMaterialControl;
private readonly MaterialBase[] _postFxMaterialsCache = new MaterialBase[1];
private ContextMenu _modelWidgetButtonMenu;
/// <summary>
/// Gets or sets the material asset to preview. It can be <see cref="FlaxEngine.Material"/> or <see cref="FlaxEngine.MaterialInstance"/>.
@@ -95,20 +98,33 @@ namespace FlaxEditor.Viewport.Previews
Task.AddCustomActor(_previewModel);
// Create context menu for primitive switching
if (useWidgets && ViewWidgetButtonMenu != null)
if (useWidgets)
{
ViewWidgetButtonMenu.AddSeparator();
var modelSelect = ViewWidgetButtonMenu.AddChildMenu("Model").ContextMenu;
// Fill out all models
for (int i = 0; i < Models.Length; i++)
// Model mode widget
var modelMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_modelWidgetButtonMenu = new ContextMenu();
_modelWidgetButtonMenu.VisibleChanged += control =>
{
var button = modelSelect.AddButton(Models[i]);
button.Tag = i;
}
if (!control.Visible)
return;
_modelWidgetButtonMenu.ItemsContainer.DisposeChildren();
// Link the action
modelSelect.ButtonClicked += (button) => SelectedModelIndex = (int)button.Tag;
// Fill out all models
for (int i = 0; i < Models.Length; i++)
{
var index = i;
var button = _modelWidgetButtonMenu.AddButton(Models[index]);
button.ButtonClicked += _ => SelectedModelIndex = index;
button.Checked = SelectedModelIndex == index;
button.Tag = index;
}
};
new ViewportWidgetButton("Model", SpriteHandle.Invalid, _modelWidgetButtonMenu)
{
TooltipText = "Change material model",
Parent = modelMode,
};
modelMode.Parent = this;
}
}

View File

@@ -1,6 +1,5 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.Input;
using FlaxEngine;
using Object = FlaxEngine.Object;
@@ -56,25 +55,14 @@ namespace FlaxEditor.Viewport.Previews
// Link actors for rendering
Task.AddCustomActor(StaticModel);
Task.AddCustomActor(AnimatedModel);
}
if (useWidgets)
{
// Preview LOD
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () =>
{
StaticModel.ForcedLOD = previewLODValue.Value;
AnimatedModel.ForcedLOD = previewLODValue.Value;
};
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = StaticModel.ForcedLOD;
}
}
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
}
private void OnBegin(RenderTask task, GPUContext context)
@@ -103,8 +91,7 @@ namespace FlaxEditor.Viewport.Previews
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(StaticModel.Model != null ? StaticModel.Box : AnimatedModel.Box);
ResetCamera();
break;
}
return base.OnKeyDown(key);

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
using FlaxEditor.Viewport.Widgets;
using Object = FlaxEngine.Object;
namespace FlaxEditor.Viewport.Previews
@@ -16,10 +16,27 @@ namespace FlaxEditor.Viewport.Previews
public class ModelPreview : AssetPreview
{
private ContextMenuButton _showBoundsButton, _showCurrentLODButton, _showNormalsButton, _showTangentsButton, _showBitangentsButton, _showFloorButton;
private ContextMenu _previewLODsWidgetButtonMenu;
private StaticModel _previewModel, _floorModel;
private bool _showBounds, _showCurrentLOD, _showNormals, _showTangents, _showBitangents, _showFloor;
private MeshDataCache _meshDatas;
/// <summary>
/// Gets or sets a value that shows LOD statistics
/// </summary>
public bool ShowCurrentLOD
{
get => _showCurrentLOD;
set
{
if (_showCurrentLOD == value)
return;
_showCurrentLOD = value;
if (_showCurrentLODButton != null)
_showCurrentLODButton.Checked = value;
}
}
/// <summary>
/// Gets or sets the model asset to preview.
/// </summary>
@@ -198,17 +215,36 @@ namespace FlaxEditor.Viewport.Previews
});
_showCurrentLODButton.IndexInParent = 2;
// Preview LOD
// Preview LODs mode widget
var PreviewLODsMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{
var previewLOD = ViewWidgetButtonMenu.AddButton("Preview LOD");
previewLOD.CloseMenuOnClick = false;
var previewLODValue = new IntValueBox(-1, 90, 2, 70.0f, -1, 10, 0.02f)
if (!control.Visible)
return;
var model = _previewModel.Model;
if (model && !model.WaitForLoaded())
{
Parent = previewLOD
};
previewLODValue.ValueChanged += () => _previewModel.ForcedLOD = previewLODValue.Value;
ViewWidgetButtonMenu.VisibleChanged += control => previewLODValue.Value = _previewModel.ForcedLOD;
}
_previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren();
var lods = model.LODs.Length;
for (int i = -1; i < lods; i++)
{
var index = i;
var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index));
button.ButtonClicked += _ => _previewModel.ForcedLOD = index;
button.Checked = _previewModel.ForcedLOD == index;
button.Tag = index;
if (lods <= 1)
break;
}
}
};
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
Parent = PreviewLODsMode,
};
PreviewLODsMode.Parent = this;
}
}
@@ -312,7 +348,7 @@ namespace FlaxEditor.Viewport.Previews
var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin);
var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr);
screenSize = Mathf.Sqrt((float)screenRadiusSquared) * 2.0f;
// Check if model is being culled
if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared)
return -1;
@@ -346,8 +382,11 @@ namespace FlaxEditor.Viewport.Previews
if (_showCurrentLOD)
{
var asset = Model;
var lodIndex = ComputeLODIndex(asset, out var screenSize);
string text = string.Format("Current LOD: {0}\nScreen Size: {1:F2}", lodIndex, screenSize);
var lodIndex = ComputeLODIndex(asset, out var screenSize);
var auto = _previewModel.ForcedLOD == -1;
string text = auto ? "LOD Automatic" : "";
text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : "";
text += string.Format("\nCurrent LOD: {0}", lodIndex);
if (lodIndex != -1)
{
var lods = asset.LODs;
@@ -369,14 +408,21 @@ namespace FlaxEditor.Viewport.Previews
}
}
/// <summary>
/// Resets the camera to focus on a object.
/// </summary>
public void ResetCamera()
{
ViewportCamera.SetArcBallView(_previewModel.Box);
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.F:
// Pay respect..
ViewportCamera.SetArcBallView(_previewModel.Box);
ResetCamera();
break;
}
return base.OnKeyDown(key);
@@ -389,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews
Object.Destroy(ref _previewModel);
_showBoundsButton = null;
_showCurrentLODButton = null;
_previewLODsWidgetButtonMenu = null;
_showNormalsButton = null;
_showTangentsButton = null;
_showBitangentsButton = null;

View File

@@ -0,0 +1,177 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEditor.Viewport.Widgets;
namespace FlaxEditor.Viewport.Previews
{
/// <summary>
/// Animation asset preview editor viewport.
/// </summary>
/// <seealso cref="AnimatedModelPreview" />
public class SkinnedModelPreview : AnimatedModelPreview
{
private bool _showCurrentLOD;
private ContextMenuButton _showCurrentLODButton;
private ContextMenu _previewLODsWidgetButtonMenu;
/// <summary>
/// Gets or sets a value that shows LOD statistics
/// </summary>
public bool ShowCurrentLOD
{
get => _showCurrentLOD;
set
{
if (_showCurrentLOD == value)
return;
_showCurrentLOD = value;
if (_showCurrentLODButton != null)
_showCurrentLODButton.Checked = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SkinnedModelPreview"/> class.
/// </summary>
/// <param name="useWidgets">if set to <c>true</c> use widgets.</param>
public SkinnedModelPreview(bool useWidgets)
: base(useWidgets)
{
if (useWidgets)
{
// Show Current LOD
_showCurrentLODButton = ViewWidgetShowMenu.AddButton("Current LOD", button =>
{
_showCurrentLOD = !_showCurrentLOD;
_showCurrentLODButton.Icon = _showCurrentLOD ? Style.Current.CheckBoxTick : SpriteHandle.Invalid;
});
_showCurrentLODButton.IndexInParent = 2;
// PreviewLODS mode widget
var PreviewLODSMode = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight);
_previewLODsWidgetButtonMenu = new ContextMenu();
_previewLODsWidgetButtonMenu.VisibleChanged += control =>
{
if (!control.Visible)
return;
var skinned = PreviewActor.SkinnedModel;
if (skinned && !skinned.WaitForLoaded())
{
_previewLODsWidgetButtonMenu.ItemsContainer.DisposeChildren();
var lods = skinned.LODs.Length;
for (int i = -1; i < lods; i++)
{
var index = i;
var button = _previewLODsWidgetButtonMenu.AddButton("LOD " + (index == -1 ? "Auto" : index));
button.ButtonClicked += (button) => PreviewActor.ForcedLOD = index;
button.Checked = PreviewActor.ForcedLOD == index;
button.Tag = index;
if (lods <= 1)
break;
}
}
};
new ViewportWidgetButton("Preview LOD", SpriteHandle.Invalid, _previewLODsWidgetButtonMenu)
{
TooltipText = "Preview LOD properties",
Parent = PreviewLODSMode,
};
PreviewLODSMode.Parent = this;
}
}
private int ComputeLODIndex(SkinnedModel model, out float screenSize)
{
screenSize = 1.0f;
if (PreviewActor.ForcedLOD != -1)
return PreviewActor.ForcedLOD;
// Based on RenderTools::ComputeModelLOD
CreateProjectionMatrix(out var projectionMatrix);
float screenMultiple = 0.5f * Mathf.Max(projectionMatrix.M11, projectionMatrix.M22);
var sphere = PreviewActor.Sphere;
var viewOrigin = ViewPosition;
var distSqr = Vector3.DistanceSquared(ref sphere.Center, ref viewOrigin);
var screenRadiusSquared = Mathf.Square(screenMultiple * sphere.Radius) / Mathf.Max(1.0f, distSqr);
screenSize = Mathf.Sqrt((float)screenRadiusSquared) * 2.0f;
// Check if model is being culled
if (Mathf.Square(model.MinScreenSize * 0.5f) > screenRadiusSquared)
return -1;
// Skip if no need to calculate LOD
if (model.LoadedLODs == 0)
return -1;
var lods = model.LODs;
if (lods.Length == 0)
return -1;
if (lods.Length == 1)
return 0;
// Iterate backwards and return the first matching LOD
for (int lodIndex = lods.Length - 1; lodIndex >= 0; lodIndex--)
{
if (Mathf.Square(lods[lodIndex].ScreenSize * 0.5f) >= screenRadiusSquared)
{
return lodIndex + PreviewActor.LODBias;
}
}
return 0;
}
/// <inheritdoc />
public override void Draw()
{
base.Draw();
var skinnedModel = PreviewActor.SkinnedModel;
if (skinnedModel == null || !skinnedModel.IsLoaded)
return;
var lods = skinnedModel.LODs;
if (lods.Length == 0)
{
// Force show skeleton for models without geometry
ShowNodes = true;
return;
}
if (_showCurrentLOD)
{
var lodIndex = ComputeLODIndex(skinnedModel, out var screenSize);
var auto = PreviewActor.ForcedLOD == -1;
string text = auto ? "LOD Automatic" : "";
text += auto ? string.Format("\nScreen Size: {0:F2}", screenSize) : "";
text += string.Format("\nCurrent LOD: {0}", lodIndex);
if (lodIndex != -1)
{
lodIndex = Mathf.Clamp(lodIndex + PreviewActor.LODBias, 0, lods.Length - 1);
var lod = lods[lodIndex];
int triangleCount = 0, vertexCount = 0;
for (int meshIndex = 0; meshIndex < lod.Meshes.Length; meshIndex++)
{
var mesh = lod.Meshes[meshIndex];
triangleCount += mesh.TriangleCount;
vertexCount += mesh.VertexCount;
}
text += string.Format("\nTriangles: {0:N0}\nVertices: {1:N0}", triangleCount, vertexCount);
}
var font = Style.Current.FontMedium;
var pos = new Float2(10, 50);
Render2D.DrawText(font, text, new Rectangle(pos + Float2.One, Size), Color.Black);
Render2D.DrawText(font, text, new Rectangle(pos, Size), Color.White);
}
}
/// <inheritdoc />
public override void OnDestroy()
{
_showCurrentLODButton = null;
_previewLODsWidgetButtonMenu = null;
base.OnDestroy();
}
}
}

View File

@@ -182,6 +182,7 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole collision");
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/physics/colliders/collision-data.html")).LinkTooltip("See documentation to learn more");
// Split Panel

View File

@@ -779,6 +779,7 @@ namespace FlaxEditor.Windows.Assets
private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings();
private float _backfacesThreshold = 0.6f;
private ToolStripButton _showCurrentLODButton;
/// <inheritdoc />
public ModelWindow(Editor editor, AssetItem item)
@@ -786,6 +787,9 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/graphics/models/index.html")).LinkTooltip("See documentation to learn more");
// Model preview
@@ -869,6 +873,8 @@ namespace FlaxEditor.Windows.Assets
}
}
_showCurrentLODButton.Checked = _preview.ShowCurrentLOD;
base.Update(deltaTime);
}
@@ -946,6 +952,7 @@ namespace FlaxEditor.Windows.Assets
base.OnDestroy();
Object.Destroy(ref _highlightActor);
_showCurrentLODButton = null;
}
}
}

View File

@@ -31,7 +31,7 @@ namespace FlaxEditor.Windows.Assets
/// <seealso cref="FlaxEditor.Windows.Assets.AssetEditorWindow" />
public sealed class SkinnedModelWindow : ModelBaseWindow<SkinnedModel, SkinnedModelWindow>
{
private sealed class Preview : AnimatedModelPreview
private sealed class Preview : SkinnedModelPreview
{
private readonly SkinnedModelWindow _window;
@@ -1105,6 +1105,7 @@ namespace FlaxEditor.Windows.Assets
private Preview _preview;
private AnimatedModel _highlightActor;
private ToolStripButton _showNodesButton;
private ToolStripButton _showCurrentLODButton;
private MeshData[][] _meshDatas;
private bool _meshDatasInProgress;
@@ -1116,7 +1117,9 @@ namespace FlaxEditor.Windows.Assets
{
// Toolstrip
_toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");
_showNodesButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Bone64, () => _preview.ShowNodes = !_preview.ShowNodes).LinkTooltip("Show animated model nodes debug view");
_toolstrip.AddButton(editor.Icons.CenterView64, () => _preview.ResetCamera()).LinkTooltip("Show whole model");
_toolstrip.AddSeparator();
_toolstrip.AddButton(editor.Icons.Docs64, () => Platform.OpenUrl(Utilities.Constants.DocsUrl + "manual/animation/skinned-model/index.html")).LinkTooltip("See documentation to learn more");
@@ -1265,6 +1268,7 @@ namespace FlaxEditor.Windows.Assets
}
}
_showCurrentLODButton.Checked = _preview.ShowCurrentLOD;
_showNodesButton.Checked = _preview.ShowNodes;
base.Update(deltaTime);
@@ -1349,6 +1353,7 @@ namespace FlaxEditor.Windows.Assets
Object.Destroy(ref _highlightActor);
_preview = null;
_showNodesButton = null;
_showCurrentLODButton = null;
}
}
}

View File

@@ -80,6 +80,7 @@ namespace FlaxEditor.Windows
public LogGroup Group;
public LogEntryDescription Desc;
public SpriteHandle Icon;
public int LogCount = 1;
public LogEntry(DebugLogWindow window, ref LogEntryDescription desc)
: base(0, 0, 120, DefaultHeight)
@@ -137,7 +138,14 @@ namespace FlaxEditor.Windows
// Title
var textRect = new Rectangle(38, 2, clientRect.Width - 40, clientRect.Height - 10);
Render2D.PushClip(ref clientRect);
Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
if (LogCount == 1)
{
Render2D.DrawText(style.FontMedium, Desc.Title, textRect, style.Foreground);
}
else if (LogCount > 1)
{
Render2D.DrawText(style.FontMedium, $"{Desc.Title} ({LogCount})", textRect, style.Foreground);
}
Render2D.PopClip();
}
@@ -289,6 +297,7 @@ namespace FlaxEditor.Windows
private readonly List<LogEntry> _pendingEntries = new List<LogEntry>(32);
private readonly ToolStripButton _clearOnPlayButton;
private readonly ToolStripButton _collapseLogsButton;
private readonly ToolStripButton _pauseOnErrorButton;
private readonly ToolStripButton[] _groupButtons = new ToolStripButton[3];
@@ -316,6 +325,7 @@ namespace FlaxEditor.Windows
};
toolstrip.AddButton("Clear", Clear).LinkTooltip("Clears all log entries");
_clearOnPlayButton = (ToolStripButton)toolstrip.AddButton("Clear on Play").SetAutoCheck(true).SetChecked(true).LinkTooltip("Clears all log entries on enter playmode");
_collapseLogsButton = (ToolStripButton)toolstrip.AddButton("Collapse").SetAutoCheck(true).SetChecked(true).LinkTooltip("Collapses similar logs.");
_pauseOnErrorButton = (ToolStripButton)toolstrip.AddButton("Pause on Error").SetAutoCheck(true).LinkTooltip("Performs auto pause on error");
toolstrip.AddSeparator();
_groupButtons[0] = (ToolStripButton)toolstrip.AddButton(editor.Icons.Error32, () => UpdateLogTypeVisibility(LogGroup.Error, _groupButtons[0].Checked)).SetAutoCheck(true).SetChecked(true).LinkTooltip("Shows/hides error messages");
@@ -612,6 +622,30 @@ namespace FlaxEditor.Windows
var top = _entriesPanel.Children.Count != 0 ? _entriesPanel.Children[_entriesPanel.Children.Count - 1].Bottom + spacing : margin.Top;
for (int i = 0; i < _pendingEntries.Count; i++)
{
if (_collapseLogsButton.Checked)
{
bool logExists = false;
foreach (var child in _entriesPanel.Children)
{
if (child is LogEntry entry)
{
var pendingEntry = _pendingEntries[i];
if (string.Equals(entry.Desc.Title, pendingEntry.Desc.Title, StringComparison.Ordinal) &&
string.Equals(entry.Desc.LocationFile, pendingEntry.Desc.LocationFile, StringComparison.Ordinal) &&
entry.Desc.Level == pendingEntry.Desc.Level &&
string.Equals(entry.Desc.Description, pendingEntry.Desc.Description, StringComparison.Ordinal) &&
entry.Desc.LocationLine == pendingEntry.Desc.LocationLine)
{
entry.LogCount += 1;
newEntry = entry;
logExists = true;
break;
}
}
}
if (logExists)
continue;
}
newEntry = _pendingEntries[i];
newEntry.Visible = _groupButtons[(int)newEntry.Group].Checked;
anyVisible |= newEntry.Visible;

View File

@@ -217,6 +217,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
// Evaluate nested animations
bool hasNested = false;
if (anim->NestedAnims.Count() != 0)
{
for (auto& e : anim->NestedAnims)
@@ -239,6 +240,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
GetAnimSamplePos(nestedAnim.Loop, nestedAnimLength, nestedAnim.StartTime, nestedAnimPrevPos, nestedAnimPos, nestedAnimPos, nestedAnimPrevPos);
ProcessAnimation(nodes, node, true, nestedAnimLength, nestedAnimPos, nestedAnimPrevPos, nestedAnim.Anim, 1.0f, weight, mode);
hasNested = true;
}
}
}
@@ -291,7 +293,7 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
dstNode.Scale = srcNode.Scale * weight;
dstNode.Orientation = srcNode.Orientation * weight;
}
else
else if (!hasNested)
{
dstNode = srcNode;
}

View File

@@ -27,7 +27,6 @@ public:
/// <summary>
/// Returns true if material is an material instance.
/// </summary>
/// <returns>True if it's a material instance, otherwise false.</returns>
virtual bool IsMaterialInstance() const = 0;
public:

View File

@@ -618,7 +618,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Return
case 5:
{
auto& scope = ThreadStacks.Get().Stack->Scope;
auto scope = ThreadStacks.Get().Stack->Scope;
scope->FunctionReturn = tryGetValue(node->GetBox(1), Value::Zero);
break;
}
@@ -634,7 +634,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
else
{
// Evaluate method parameter value from the current scope
auto& scope = ThreadStacks.Get().Stack->Scope;
auto scope = ThreadStacks.Get().Stack->Scope;
int32 index = boxBase->ID - 1;
if (index < scope->Parameters.Length())
value = scope->Parameters.Get()[index];
@@ -1138,7 +1138,7 @@ void VisualScriptExecutor::ProcessGroupFlow(Box* boxBase, Node* node, Value& val
break;
}
int32 arrayIndex = 0;
for (; iteratorIndex < scope->ReturnedValues.Count(); arrayIndex++)
for (; arrayIndex < scope->ReturnedValues.Count(); arrayIndex++)
{
const auto& e = scope->ReturnedValues[arrayIndex];
if (e.NodeId == node->ID && e.BoxId == 1)

View File

@@ -37,6 +37,9 @@ public class Content : EngineModule
files.AddRange(Directory.GetFiles(FolderPath, "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Assets"), "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Cache"), "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Factories"), "*.h", SearchOption.TopDirectoryOnly));
files.AddRange(Directory.GetFiles(Path.Combine(FolderPath, "Storage"), "*.h", SearchOption.TopDirectoryOnly));
files.Add(Path.Combine(FolderPath, "Upgraders/BinaryAssetUpgrader.h"));
files.Add(Path.Combine(FolderPath, "Upgraders/IAssetUpgrader.h"));
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using Flax.Build;
using Flax.Build.NativeCpp;
@@ -23,5 +24,7 @@ public class ContentExporters : EngineModule
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
files.Add(Path.Combine(FolderPath, "AssetsExportingManager.h"));
files.Add(Path.Combine(FolderPath, "Types.h"));
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.IO;
using Flax.Build;
using Flax.Build.NativeCpp;
@@ -31,5 +32,7 @@ public class ContentImporters : EngineModule
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
files.Add(Path.Combine(FolderPath, "AssetsImportingManager.h"));
files.Add(Path.Combine(FolderPath, "Types.h"));
}
}

View File

@@ -357,8 +357,8 @@ public:
{
for (Iterator i = Begin(); i.IsNotEnd(); ++i)
{
if (i->Value)
::Delete(i->Value);
if (i->Item)
::Delete(i->Item);
}
Clear();
}

View File

@@ -38,9 +38,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
BoundingBox()
{
}
BoundingBox() = default;
/// <summary>
/// Initializes a new instance of the <see cref="BoundingBox"/> struct.

View File

@@ -12,6 +12,7 @@
/// </summary>
API_STRUCT(InBuild) struct FLAXENGINE_API BoundingFrustum
{
friend CollisionsHelper;
private:
Matrix _matrix;
@@ -33,9 +34,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
BoundingFrustum()
{
}
BoundingFrustum() = default;
/// <summary>
/// Initializes a new instance of the <see cref="BoundingFrustum"/> struct.

View File

@@ -34,9 +34,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
BoundingSphere()
{
}
BoundingSphere() = default;
/// <summary>
/// Initializes a new instance of the <see cref="BoundingSphere"/> struct.

View File

@@ -939,7 +939,6 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
normal = Vector3::Up;
return false;
}
const Vector3 point = ray.Position + ray.Direction * distance;
normal = Vector3::Normalize(point - sphere.Center);
return true;
@@ -953,22 +952,17 @@ bool CollisionsHelper::RayIntersectsSphere(const Ray& ray, const BoundingSphere&
point = Vector3::Zero;
return false;
}
point = ray.Position + ray.Direction * distance;
return true;
}
PlaneIntersectionType CollisionsHelper::PlaneIntersectsPoint(const Plane& plane, const Vector3& point)
{
Real distance = Vector3::Dot(plane.Normal, point);
distance += plane.D;
const Real distance = Vector3::Dot(plane.Normal, point) + plane.D;
if (distance > Plane::DistanceEpsilon)
return PlaneIntersectionType::Front;
if (distance < Plane::DistanceEpsilon)
return PlaneIntersectionType::Back;
return PlaneIntersectionType::Intersecting;
}
@@ -1169,7 +1163,6 @@ ContainmentType CollisionsHelper::SphereContainsPoint(const BoundingSphere& sphe
{
if (Vector3::DistanceSquared(point, sphere.Center) <= sphere.Radius * sphere.Radius)
return ContainmentType::Contains;
return ContainmentType::Disjoint;
}
@@ -1254,13 +1247,10 @@ ContainmentType CollisionsHelper::SphereContainsBox(const BoundingSphere& sphere
ContainmentType CollisionsHelper::SphereContainsSphere(const BoundingSphere& sphere1, const BoundingSphere& sphere2)
{
const Real distance = Vector3::Distance(sphere1.Center, sphere2.Center);
if (sphere1.Radius + sphere2.Radius < distance)
return ContainmentType::Disjoint;
if (sphere1.Radius - sphere2.Radius < distance)
return ContainmentType::Intersects;
return ContainmentType::Contains;
}
@@ -1274,7 +1264,8 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
auto result = ContainmentType::Contains;
for (int32 i = 0; i < 6; i++)
{
Plane plane = frustum.GetPlane(i);
Plane plane = frustum._planes[i];
Vector3 p = box.Minimum;
if (plane.Normal.X >= 0)
p.X = box.Maximum.X;
@@ -1282,7 +1273,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Maximum.Y;
if (plane.Normal.Z >= 0)
p.Z = box.Maximum.Z;
if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back)
if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
return ContainmentType::Disjoint;
p = box.Maximum;
@@ -1292,7 +1283,7 @@ ContainmentType CollisionsHelper::FrustumContainsBox(const BoundingFrustum& frus
p.Y = box.Minimum.Y;
if (plane.Normal.Z >= 0)
p.Z = box.Minimum.Z;
if (PlaneIntersectsPoint(plane, p) == PlaneIntersectionType::Back)
if (Vector3::Dot(plane.Normal, p) + plane.D < Plane::DistanceEpsilon)
result = ContainmentType::Intersects;
}
return result;

View File

@@ -50,9 +50,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Color()
{
}
Color() = default;
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.

View File

@@ -53,9 +53,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Color32()
{
}
Color32() = default;
/// <summary>
/// Constructs a new Color32 with given r, g, b, a components.

View File

@@ -121,9 +121,7 @@ public:
/// <summary>
/// Default constructor
/// </summary>
Half2()
{
}
Half2() = default;
/// <summary>
/// Init
@@ -185,9 +183,7 @@ public:
Half Z;
public:
Half3()
{
}
Half3() = default;
Half3(Half x, Half y, Half z)
: X(x)
@@ -242,9 +238,7 @@ public:
Half W;
public:
Half4()
{
}
Half4() = default;
Half4(Half x, Half y, Half z, Half w)
: X(x)

View File

@@ -83,9 +83,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Matrix()
{
}
Matrix() = default;
/// <summary>
/// Initializes a new instance of the <see cref="Matrix"/> struct.

View File

@@ -63,9 +63,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Matrix3x3()
{
}
Matrix3x3() = default;
/// <summary>
/// Initializes a new instance of the <see cref="Matrix3x3"/> struct.

View File

@@ -30,9 +30,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Plane()
{
}
Plane() = default;
/// <summary>
/// Init

View File

@@ -67,9 +67,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Quaternion()
{
}
Quaternion() = default;
/// <summary>
/// Init

View File

@@ -35,9 +35,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Ray()
{
}
Ray() = default;
/// <summary>
/// Initializes a new instance of the <see cref="Ray"/> struct.

View File

@@ -31,9 +31,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Rectangle()
{
}
Rectangle() = default;
// Init
// @param x Rectangle location X coordinate

View File

@@ -40,9 +40,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Transform()
{
}
Transform() = default;
/// <summary>
/// Initializes a new instance of the <see cref="Transform"/> struct.

View File

@@ -30,9 +30,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Triangle()
{
}
Triangle() = default;
/// <summary>
/// Initializes a new instance of the <see cref="Triangle"/> struct.

View File

@@ -60,9 +60,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Vector2Base()
{
}
Vector2Base() = default;
FORCE_INLINE Vector2Base(T xy)
: X(xy)

View File

@@ -89,9 +89,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Vector3Base()
{
}
Vector3Base() = default;
FORCE_INLINE Vector3Base(T xyz)
: X(xyz)

View File

@@ -76,9 +76,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Vector4Base()
{
}
Vector4Base() = default;
FORCE_INLINE Vector4Base(T xyzw)
: X(xyzw)

View File

@@ -52,9 +52,7 @@ public:
/// <summary>
/// Empty constructor.
/// </summary>
Viewport()
{
}
Viewport() = default;
// Init
// @param x The x coordinate of the upper-left corner of the viewport in pixels

View File

@@ -36,6 +36,32 @@ namespace AllocatorExt
return result;
}
/// <summary>
/// Reallocates block of the memory.
/// </summary>
/// </summary>
/// <param name="ptr">A pointer to the memory block to reallocate.</param>
/// <param name="newSize">The size of the new allocation (in bytes).</param>
/// <param name="alignment">The memory alignment (in bytes). Must be an integer power of 2.</param>
/// <returns>The pointer to the allocated chunk of the memory. The pointer is a multiple of alignment.</returns>
inline void* ReallocAligned(void* ptr, uint64 newSize, uint64 alignment)
{
if (newSize == 0)
{
Allocator::Free(ptr);
return nullptr;
}
if (!ptr)
return Allocator::Allocate(newSize, alignment);
void* result = Allocator::Allocate(newSize, alignment);
if (result)
{
Platform::MemoryCopy(result, ptr, newSize);
Allocator::Free(ptr);
}
return result;
}
/// <summary>
/// Reallocates block of the memory.
/// </summary>

View File

@@ -114,3 +114,20 @@ inline Span<T> ToSpan(const T* ptr, int32 length)
{
return Span<T>(ptr, length);
}
template<typename T, typename U = T, typename AllocationType = HeapAllocation>
inline Span<U> ToSpan(const Array<T, AllocationType>& data)
{
return Span<U>((U*)data.Get(), data.Count());
}
template<typename T>
inline bool SpanContains(const Span<T> span, const T& value)
{
for (int32 i = 0; i < span.Length(); i++)
{
if (span.Get()[i] == value)
return true;
}
return false;
}

View File

@@ -73,7 +73,7 @@ void String::Set(const char* chars, int32 length)
_length = length;
}
if (chars)
StringUtils::ConvertANSI2UTF16(chars, _data, length);
StringUtils::ConvertANSI2UTF16(chars, _data, length, _length);
}
void String::SetUTF8(const char* chars, int32 length)
@@ -112,7 +112,8 @@ void String::Append(const char* chars, int32 count)
_data = (Char*)Platform::Allocate((_length + 1) * sizeof(Char), 16);
Platform::MemoryCopy(_data, oldData, oldLength * sizeof(Char));
StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count * sizeof(Char));
StringUtils::ConvertANSI2UTF16(chars, _data + oldLength, count, _length);
_length += oldLength;
_data[_length] = 0;
Platform::Free(oldData);

View File

@@ -125,7 +125,8 @@ public:
const int32 length = str && *str ? StringUtils::Length(str) : 0;
const int32 prevCnt = _data.Count();
_data.AddDefault(length);
StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length);
int32 tmp;
StringUtils::ConvertANSI2UTF16(str, _data.Get() + prevCnt, length, tmp);
return *this;
}

View File

@@ -660,7 +660,8 @@ Variant::Variant(const StringAnsiView& v)
const int32 length = v.Length() * sizeof(Char) + 2;
AsBlob.Data = Allocator::Allocate(length);
AsBlob.Length = length;
StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length());
int32 tmp;
StringUtils::ConvertANSI2UTF16(v.Get(), (Char*)AsBlob.Data, v.Length(), tmp);
((Char*)AsBlob.Data)[v.Length()] = 0;
}
else
@@ -2578,7 +2579,8 @@ void Variant::SetString(const StringAnsiView& str)
AsBlob.Data = Allocator::Allocate(length);
AsBlob.Length = length;
}
StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length());
int32 tmp;
StringUtils::ConvertANSI2UTF16(str.Get(), (Char*)AsBlob.Data, str.Length(), tmp);
((Char*)AsBlob.Data)[str.Length()] = 0;
}
@@ -2794,6 +2796,122 @@ String Variant::ToString() const
}
}
void Variant::Inline()
{
VariantType::Types type = VariantType::Null;
byte data[sizeof(Matrix)];
if (Type.Type == VariantType::Structure && AsBlob.Data && AsBlob.Length <= sizeof(Matrix))
{
for (int32 i = 2; i < VariantType::MAX; i++)
{
if (StringUtils::Compare(Type.TypeName, InBuiltTypesTypeNames[i]) == 0)
{
type = (VariantType::Types)i;
break;
}
}
if (type == VariantType::Null)
{
// Aliases
if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector2") == 0)
type = VariantType::Types::Vector2;
else if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector3") == 0)
type = VariantType::Types::Vector3;
else if (StringUtils::Compare(Type.TypeName, "FlaxEngine.Vector4") == 0)
type = VariantType::Types::Vector4;
}
if (type != VariantType::Null)
Platform::MemoryCopy(data, AsBlob.Data, AsBlob.Length);
}
if (type != VariantType::Null)
{
switch (type)
{
case VariantType::Bool:
*this = *(bool*)data;
break;
case VariantType::Int:
*this = *(int32*)data;
break;
case VariantType::Uint:
*this = *(uint32*)data;
break;
case VariantType::Int64:
*this = *(int64*)data;
break;
case VariantType::Uint64:
*this = *(uint64*)data;
break;
case VariantType::Float:
*this = *(float*)data;
break;
case VariantType::Double:
*this = *(double*)data;
break;
case VariantType::Float2:
*this = *(Float2*)data;
break;
case VariantType::Float3:
*this = *(Float3*)data;
break;
case VariantType::Float4:
*this = *(Float4*)data;
break;
case VariantType::Color:
*this = *(Color*)data;
break;
case VariantType::Guid:
*this = *(Guid*)data;
break;
case VariantType::BoundingBox:
*this = Variant(*(BoundingBox*)data);
break;
case VariantType::BoundingSphere:
*this = *(BoundingSphere*)data;
break;
case VariantType::Quaternion:
*this = *(Quaternion*)data;
break;
case VariantType::Transform:
*this = Variant(*(Transform*)data);
break;
case VariantType::Rectangle:
*this = *(Rectangle*)data;
break;
case VariantType::Ray:
*this = Variant(*(Ray*)data);
break;
case VariantType::Matrix:
*this = Variant(*(Matrix*)data);
break;
case VariantType::Int2:
*this = *(Int2*)data;
break;
case VariantType::Int3:
*this = *(Int3*)data;
break;
case VariantType::Int4:
*this = *(Int4*)data;
break;
case VariantType::Int16:
*this = *(int16*)data;
break;
case VariantType::Uint16:
*this = *(uint16*)data;
break;
case VariantType::Double2:
*this = *(Double2*)data;
break;
case VariantType::Double3:
*this = *(Double3*)data;
break;
case VariantType::Double4:
*this = *(Double4*)data;
break;
}
}
}
bool Variant::CanCast(const Variant& v, const VariantType& to)
{
if (v.Type == to)
@@ -3680,6 +3798,7 @@ void Variant::AllocStructure()
const ScriptingType& type = typeHandle.GetType();
AsBlob.Length = type.Size;
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
Platform::MemoryClear(AsBlob.Data, AsBlob.Length);
type.Struct.Ctor(AsBlob.Data);
}
else if (typeName == "System.Int16" || typeName == "System.UInt16")

View File

@@ -359,6 +359,9 @@ public:
void SetAsset(Asset* asset);
String ToString() const;
// Inlines potential value type into in-built format (eg. Vector3 stored as Structure, or String stored as ManagedObject).
void Inline();
FORCE_INLINE Variant Cast(const VariantType& to) const
{
return Cast(*this, to);

View File

@@ -50,13 +50,13 @@ namespace FlaxEngine.Interop
/// <remarks>The resources must be released by calling FreePooled() instead of Free()-method.</remarks>
public static ManagedArray WrapPooledArray(Array arr, Type arrayType)
{
ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * Marshal.SizeOf(arr.GetType().GetElementType()));
ManagedArray managedArray = ManagedArrayPool.Get(arr.Length * NativeInterop.GetTypeSize(arr.GetType().GetElementType()));
managedArray.WrapArray(arr, arrayType);
return managedArray;
}
internal static ManagedArray AllocateNewArray(int length, Type arrayType, Type elementType)
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, Marshal.SizeOf(elementType)), length, arrayType, elementType);
=> new ManagedArray((IntPtr)NativeInterop.NativeAlloc(length, NativeInterop.GetTypeSize(elementType)), length, arrayType, elementType);
internal static ManagedArray AllocateNewArray(IntPtr ptr, int length, Type arrayType, Type elementType)
=> new ManagedArray(ptr, length, arrayType, elementType);
@@ -86,7 +86,7 @@ namespace FlaxEngine.Interop
_length = arr.Length;
_arrayType = arrayType;
_elementType = arr.GetType().GetElementType();
_elementSize = Marshal.SizeOf(_elementType);
_elementSize = NativeInterop.GetTypeSize(_elementType);
}
internal void Allocate<T>(int length) where T : unmanaged
@@ -117,7 +117,7 @@ namespace FlaxEngine.Interop
_length = length;
_arrayType = arrayType;
_elementType = elementType;
_elementSize = Marshal.SizeOf(elementType);
_elementSize = NativeInterop.GetTypeSize(_elementType);
}
~ManagedArray()

View File

@@ -350,14 +350,14 @@ namespace FlaxEngine.Interop
#endif
public static class NativeToManaged
{
public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements)
public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements)
{
if (unmanaged is null)
return null;
return new T[numElements];
}
public static Span<T> GetManagedValuesDestination(T[]? managed) => managed;
public static Span<T> GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{
@@ -390,7 +390,7 @@ namespace FlaxEngine.Interop
#endif
public static class ManagedToNative
{
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements)
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
{
if (managed is null)
{
@@ -399,10 +399,10 @@ namespace FlaxEngine.Interop
}
numElements = managed.Length;
ManagedArray managedArray = ManagedArray.AllocatePooledArray<TUnmanagedElement>(managed.Length);
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Weak);
return (TUnmanagedElement*)ManagedHandle.ToIntPtr(managedArray, GCHandleType.Normal);
}
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed;
public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{
@@ -431,7 +431,7 @@ namespace FlaxEngine.Interop
ManagedArray unmanagedArray;
ManagedHandle handle;
public void FromManaged(T[]? managed)
public void FromManaged(T[] managed)
{
if (managed == null)
return;
@@ -476,7 +476,7 @@ namespace FlaxEngine.Interop
}
}
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[]? managed, out int numElements)
public static TUnmanagedElement* AllocateContainerForUnmanagedElements(T[] managed, out int numElements)
{
if (managed is null)
{
@@ -489,7 +489,7 @@ namespace FlaxEngine.Interop
return (TUnmanagedElement*)handle;
}
public static ReadOnlySpan<T> GetManagedValuesSource(T[]? managed) => managed;
public static ReadOnlySpan<T> GetManagedValuesSource(T[] managed) => managed;
public static Span<TUnmanagedElement> GetUnmanagedValuesDestination(TUnmanagedElement* unmanaged, int numElements)
{
@@ -499,9 +499,9 @@ namespace FlaxEngine.Interop
return unmanagedArray.ToSpan<TUnmanagedElement>();
}
public static T[]? AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements];
public static T[] AllocateContainerForManagedElements(TUnmanagedElement* unmanaged, int numElements) => unmanaged is null ? null : new T[numElements];
public static Span<T> GetManagedValuesDestination(T[]? managed) => managed;
public static Span<T> GetManagedValuesDestination(T[] managed) => managed;
public static ReadOnlySpan<TUnmanagedElement> GetUnmanagedValuesSource(TUnmanagedElement* unmanaged, int numElements)
{

View File

@@ -244,7 +244,25 @@ namespace FlaxEngine.Interop
@namespace = NativeAllocStringAnsi(type.Namespace ?? ""),
typeAttributes = (uint)type.Attributes,
};
*assemblyHandle = GetAssemblyHandle(type.Assembly);
Assembly assembly = null;
if (type.IsGenericType && !type.Assembly.IsCollectible)
{
// The owning assembly of a generic type with type arguments referencing
// collectible assemblies must be one of the collectible assemblies.
foreach (var genericType in type.GetGenericArguments())
{
if (genericType.Assembly.IsCollectible)
{
assembly = genericType.Assembly;
break;
}
}
}
if (assembly == null)
assembly = type.Assembly;
*assemblyHandle = GetAssemblyHandle(assembly);
}
[UnmanagedCallersOnly]
@@ -770,6 +788,13 @@ namespace FlaxEngine.Interop
field.field.SetValue(fieldOwner, value);
}
[UnmanagedCallersOnly]
internal static int FieldGetOffset(ManagedHandle fieldHandle)
{
FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
return (int)Marshal.OffsetOf(field.field.DeclaringType, field.field.Name);
}
[UnmanagedCallersOnly]
internal static void FieldGetValue(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle, IntPtr valuePtr)
{
@@ -778,6 +803,15 @@ namespace FlaxEngine.Interop
field.toNativeMarshaller(field.field, fieldOwner, valuePtr, out int fieldOffset);
}
[UnmanagedCallersOnly]
internal static IntPtr FieldGetValueBoxed(ManagedHandle fieldOwnerHandle, ManagedHandle fieldHandle)
{
object fieldOwner = fieldOwnerHandle.Target;
FieldHolder field = Unsafe.As<FieldHolder>(fieldHandle.Target);
object fieldValue = field.field.GetValue(fieldOwner);
return Invoker.MarshalReturnValueGeneric(field.field.FieldType, fieldValue);
}
[UnmanagedCallersOnly]
internal static void WriteArrayReference(ManagedHandle arrayHandle, IntPtr valueHandle, int index)
{
@@ -885,6 +919,7 @@ namespace FlaxEngine.Interop
handle.Free();
fieldHandleCacheCollectible.Clear();
#endif
_typeSizeCache.Clear();
foreach (var pair in classAttributesCacheCollectible)
pair.Value.Free();
@@ -923,7 +958,7 @@ namespace FlaxEngine.Interop
if (nativeType.IsClass)
size = sizeof(IntPtr);
else
size = Marshal.SizeOf(nativeType);
size = GetTypeSize(nativeType);
return size;
}

View File

@@ -46,6 +46,7 @@ namespace FlaxEngine.Interop
#endif
private static Dictionary<object, ManagedHandle> classAttributesCacheCollectible = new();
private static Dictionary<Assembly, ManagedHandle> assemblyHandles = new();
private static Dictionary<Type, int> _typeSizeCache = new();
private static Dictionary<string, IntPtr> loadedNativeLibraries = new();
internal static Dictionary<string, string> nativeLibraryPaths = new();
@@ -96,14 +97,17 @@ namespace FlaxEngine.Interop
}
#if FLAX_EDITOR
private static Assembly? OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
private static Assembly OnScriptingAssemblyLoadContextResolving(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName)
{
// FIXME: There should be a better way to resolve the path to EditorTargetPath where the dependencies are stored
string editorTargetPath = Path.GetDirectoryName(nativeLibraryPaths.Keys.First(x => x != "FlaxEngine"));
foreach (string nativeLibraryPath in nativeLibraryPaths.Values)
{
string editorTargetPath = Path.GetDirectoryName(nativeLibraryPath);
var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll");
if (File.Exists(assemblyPath))
return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
var assemblyPath = Path.Combine(editorTargetPath, assemblyName.Name + ".dll");
if (File.Exists(assemblyPath))
return assemblyLoadContext.LoadFromAssemblyPath(assemblyPath);
}
return null;
}
#endif
@@ -581,7 +585,7 @@ namespace FlaxEngine.Interop
else if (fieldType.IsClass || fieldType.IsPointer)
fieldAlignment = IntPtr.Size;
else
fieldAlignment = Marshal.SizeOf(fieldType);
fieldAlignment = GetTypeSize(fieldType);
}
internal static void ToManagedField(FieldInfo field, ref T fieldOwner, IntPtr fieldPtr, out int fieldOffset)
@@ -1085,6 +1089,29 @@ namespace FlaxEngine.Interop
return handle;
}
internal static int GetTypeSize(Type type)
{
if (!_typeSizeCache.TryGetValue(type, out var size))
{
try
{
var marshalType = type;
if (type.IsEnum)
marshalType = type.GetEnumUnderlyingType();
size = Marshal.SizeOf(marshalType);
}
catch
{
// Workaround the issue where structure defined within generic type instance (eg. MyType<int>.MyStruct) fails to get size
// https://github.com/dotnet/runtime/issues/46426
var obj = Activator.CreateInstance(type);
size = Marshal.SizeOf(obj);
}
_typeSizeCache.Add(type, size);
}
return size;
}
private static class DelegateHelpers
{
#if USE_AOT

View File

@@ -1031,6 +1031,11 @@ API_ENUM(Attributes="Flags") enum class ViewFlags : uint64
/// </summary>
Sky = 1 << 26,
/// <summary>
/// Shows/hides light debug shapes.
/// </summary>
LightsDebug = 1 << 27,
/// <summary>
/// Default flags for Game.
/// </summary>

View File

@@ -238,7 +238,7 @@ public:
/// <summary>
/// Gets the default material.
/// </summary>
MaterialBase* GetDefaultMaterial() const;
API_PROPERTY() MaterialBase* GetDefaultMaterial() const;
/// <summary>
/// Gets the default material (Deformable domain).

View File

@@ -145,7 +145,7 @@ bool GPUShader::Create(MemoryReadStream& stream)
// Create CB
#if GPU_ENABLE_RESOURCE_NAMING
String name = ToString() + TEXT(".CB") + i;
String name = String::Format(TEXT("{}.CB{}"), ToString(), i);
#else
String name;
#endif

View File

@@ -1337,6 +1337,20 @@ Actor* Actor::FindActor(const MClass* type, const StringView& name) const
return nullptr;
}
Actor* Actor::FindActor(const MClass* type, const Tag& tag) const
{
CHECK_RETURN(type, nullptr);
if (GetClass()->IsSubClassOf(type) && HasTag(tag))
return const_cast<Actor*>(this);
for (auto child : Children)
{
const auto actor = child->FindActor(type, tag);
if (actor)
return actor;
}
return nullptr;
}
Script* Actor::FindScript(const MClass* type) const
{
CHECK_RETURN(type, nullptr);

View File

@@ -269,6 +269,17 @@ namespace FlaxEngine
{
return FindActor(typeof(T), name) as T;
}
/// <summary>
/// Tries to find actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy).
/// </summary>
/// <param name="tag">A tag on the object.</param>
/// <typeparam name="T">Type of the object.</typeparam>
/// <returns>Actor instance if found, null otherwise.</returns>
public T FindActor<T>(Tag tag) where T : Actor
{
return FindActor(typeof(T), tag) as T;
}
/// <summary>
/// Searches for all actors of a specific type in this actor children list.

View File

@@ -739,6 +739,14 @@ public:
/// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name) const;
/// <summary>
/// Tries to find the actor of the given type and tag in this actor hierarchy.
/// </summary>
/// <param name="type">Type of the actor to search for. Includes any actors derived from the type.</param>
/// <param name="tag">The tag of the actor to search for.</param>
/// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag) const;
/// <summary>
/// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy).
/// </summary>
@@ -759,6 +767,17 @@ public:
{
return (T*)FindActor(T::GetStaticClass(), name);
}
/// <summary>
/// Tries to find the actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <returns>Actor instance if found, null otherwise.</returns>
template<typename T>
FORCE_INLINE T* FindActor(const Tag& tag) const
{
return (T*)FindActor(T::GetStaticClass(), tag);
}
/// <summary>
/// Tries to find the script of the given type in this actor hierarchy (checks this actor and all children hierarchy).

View File

@@ -144,7 +144,7 @@ void AnimatedModel::SetCurrentPose(const Array<Matrix>& nodesTransformation, boo
Matrix invWorld;
Matrix::Invert(world, invWorld);
for (auto& m : GraphInstance.NodesPose)
m = invWorld * m;
m = m * invWorld;
}
OnAnimationUpdated();
}
@@ -603,7 +603,13 @@ void AnimatedModel::OnAnimationUpdated_Sync()
// Update synchronous stuff
UpdateSockets();
ApplyRootMotion(GraphInstance.RootMotion);
AnimationUpdated();
if (!_isDuringUpdateEvent)
{
// Prevent stack-overflow when gameplay modifies the pose within the event
_isDuringUpdateEvent = true;
AnimationUpdated();
_isDuringUpdateEvent = false;
}
}
void AnimatedModel::OnAnimationUpdated()
@@ -892,6 +898,31 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DrawModes |= DrawPass::GlobalSurfaceAtlas;
}
const Span<MaterialSlot> AnimatedModel::GetMaterialSlots() const
{
const auto model = SkinnedModel.Get();
if (model && !model->WaitForLoaded())
return ToSpan(model->MaterialSlots);
return Span<MaterialSlot>();
}
MaterialBase* AnimatedModel::GetMaterial(int32 entryIndex)
{
if (SkinnedModel)
SkinnedModel->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material)
{
material = SkinnedModel->MaterialSlots[entryIndex].Material.Get();
if (!material)
material = GPUDevice::Instance->GetDefaultMaterial();
}
return material;
}
bool AnimatedModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal)
{
auto model = SkinnedModel.Get();

View File

@@ -60,6 +60,7 @@ private:
AnimationUpdateMode _actualMode;
uint32 _counter;
Real _lastMinDstSqr;
bool _isDuringUpdateEvent = false;
uint64 _lastUpdateFrame;
BlendShapesInstance _blendShapes;
ScriptingObjectReference<AnimatedModel> _masterPose;
@@ -372,6 +373,8 @@ public:
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
const Span<MaterialSlot> GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;
void OnDeleteObject() override;

View File

@@ -26,6 +26,7 @@ void Light::OnEnable()
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
#if USE_EDITOR
GetSceneRendering()->AddViewportIcon(this);
GetSceneRendering()->AddLightsDebug<Light, &Light::DrawLightsDebug>(this);
#endif
// Base
@@ -36,6 +37,7 @@ void Light::OnDisable()
{
#if USE_EDITOR
GetSceneRendering()->RemoveViewportIcon(this);
GetSceneRendering()->RemoveLightsDebug<Light, &Light::DrawLightsDebug>(this);
#endif
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
@@ -43,6 +45,12 @@ void Light::OnDisable()
Actor::OnDisable();
}
#if USE_EDITOR
void Light::DrawLightsDebug(RenderView& view)
{
}
#endif
void Light::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base

View File

@@ -66,6 +66,8 @@ public:
const Vector3 size(50);
return BoundingBox(_transform.Translation - size, _transform.Translation + size);
}
virtual void DrawLightsDebug(RenderView& view);
#endif
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -39,10 +39,9 @@ void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material)
MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex)
{
WaitForModelLoad();
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
auto material = Entries[entryIndex].Material.Get();
MaterialBase* material = GetMaterial(entryIndex);
CHECK_RETURN(material && !material->WaitForLoaded(), nullptr);
const auto result = material->CreateVirtualInstance();
MaterialInstance* result = material->CreateVirtualInstance();
Entries[entryIndex].Material = result;
if (_sceneRenderingKey != -1)
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);

View File

@@ -35,6 +35,17 @@ public:
/// </summary>
API_PROPERTY() void SetEntries(const Array<ModelInstanceEntry>& value);
/// <summary>
/// Gets the material slots array set on the asset (eg. model or skinned model asset).
/// </summary>
API_PROPERTY(Sealed) virtual const Span<class MaterialSlot> GetMaterialSlots() const = 0;
/// <summary>
/// Gets the material used to draw the meshes which are assigned to that slot (set in Entries or model's default).
/// </summary>
/// <param name="entryIndex">The material slot entry index.</param>
API_FUNCTION(Sealed) virtual MaterialBase* GetMaterial(int32 entryIndex) = 0;
/// <summary>
/// Sets the material to the entry slot. Can be used to override the material of the meshes using this slot.
/// </summary>

View File

@@ -143,6 +143,16 @@ void PointLight::OnDebugDrawSelected()
LightWithShadow::OnDebugDrawSelected();
}
void PointLight::DrawLightsDebug(RenderView& view)
{
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
if (!view.CullingFrustum.Intersects(sphere) || !EnumHasAnyFlags(view.Flags, ViewFlags::PointLights))
return;
// Draw influence range
DEBUG_DRAW_WIRE_SPHERE(_sphere, Color::Yellow, 0, true);
}
#endif
void PointLight::OnLayerChanged()

View File

@@ -95,6 +95,7 @@ public:
#if USE_EDITOR
void OnDebugDraw() override;
void OnDebugDrawSelected() override;
void DrawLightsDebug(RenderView& view) override;
#endif
void OnLayerChanged() override;
void Serialize(SerializeStream& stream, const void* otherObj) override;

View File

@@ -341,6 +341,31 @@ void SplineModel::OnParentChanged()
OnSplineUpdated();
}
const Span<MaterialSlot> SplineModel::GetMaterialSlots() const
{
const auto model = Model.Get();
if (model && !model->WaitForLoaded())
return ToSpan(model->MaterialSlots);
return Span<MaterialSlot>();
}
MaterialBase* SplineModel::GetMaterial(int32 entryIndex)
{
if (Model)
Model->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material)
{
material = Model->MaterialSlots[entryIndex].Material.Get();
if (!material)
material = GPUDevice::Instance->GetDefaultDeformableMaterial();
}
return material;
}
bool SplineModel::HasContentLoaded() const
{
return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded();

View File

@@ -115,6 +115,8 @@ public:
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
void OnParentChanged() override;
const Span<MaterialSlot> GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
protected:
// [ModelInstanceActor]

View File

@@ -203,6 +203,11 @@ void SpotLight::OnDebugDrawSelected()
DEBUG_DRAW_LINE(position, position + forward * radius + right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, discRadius, color, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, falloffDiscRadius, color * 0.6f, 0, true);
@@ -210,6 +215,34 @@ void SpotLight::OnDebugDrawSelected()
LightWithShadow::OnDebugDrawSelected();
}
void SpotLight::DrawLightsDebug(RenderView& view)
{
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
if (!view.CullingFrustum.Intersects(sphere) || !EnumHasAnyFlags(view.Flags, ViewFlags::SpotLights))
return;
const auto color = Color::Yellow;
Vector3 right = _transform.GetRight();
Vector3 up = _transform.GetUp();
Vector3 forward = GetDirection();
float radius = GetScaledRadius();
float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians);
float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians);
Vector3 position = GetPosition();
DEBUG_DRAW_LINE(position, position + forward * radius + up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, discRadius, color, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, falloffDiscRadius, color * 0.6f, 0, true);
}
#endif
void SpotLight::Serialize(SerializeStream& stream, const void* otherObj)

View File

@@ -127,6 +127,7 @@ public:
#if USE_EDITOR
void OnDebugDraw() override;
void OnDebugDrawSelected() override;
void DrawLightsDebug(RenderView& view) override;
#endif
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;

View File

@@ -535,6 +535,31 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
}
}
const Span<MaterialSlot> StaticModel::GetMaterialSlots() const
{
const auto model = Model.Get();
if (model && !model->WaitForLoaded())
return ToSpan(model->MaterialSlots);
return Span<MaterialSlot>();
}
MaterialBase* StaticModel::GetMaterial(int32 entryIndex)
{
if (Model)
Model->WaitForLoaded();
else
return nullptr;
CHECK_RETURN(entryIndex >= 0 && entryIndex < Entries.Count(), nullptr);
MaterialBase* material = Entries[entryIndex].Material.Get();
if (!material)
{
material = Model->MaterialSlots[entryIndex].Material.Get();
if (!material)
material = GPUDevice::Instance->GetDefaultMaterial();
}
return material;
}
bool StaticModel::IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal)
{
auto model = Model.Get();

View File

@@ -121,7 +121,7 @@ public:
/// <param name="meshIndex">The zero-based mesh index.</param>
/// <param name="lodIndex">The LOD index.</param>
/// <returns>Material or null if not assigned.</returns>
API_FUNCTION() MaterialBase* GetMaterial(int32 meshIndex, int32 lodIndex = 0) const;
API_FUNCTION() MaterialBase* GetMaterial(int32 meshIndex, int32 lodIndex) const;
/// <summary>
/// Gets the color of the painter vertex (this model instance).
@@ -166,6 +166,8 @@ public:
bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
const Span<MaterialSlot> GetMaterialSlots() const override;
MaterialBase* GetMaterial(int32 entryIndex) override;
bool IntersectsEntry(int32 entryIndex, const Ray& ray, Real& distance, Vector3& normal) override;
bool IntersectsEntry(const Ray& ray, Real& distance, Vector3& normal, int32& entryIndex) override;

View File

@@ -726,116 +726,6 @@ int32 Level::GetLayerIndex(const StringView& layer)
return result;
}
Actor* FindActorRecursive(Actor* node, const Tag& tag)
{
if (node->HasTag(tag))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursive(child, tag);
if (result)
break;
}
return result;
}
void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorsRecursive(child, tag, activeOnly, result);
}
void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
for (Tag tag : tags)
{
if (node->HasTag(tag))
{
result.Add(node);
break;
}
}
for (Actor* child : node->Children)
FindActorsRecursiveByParentTags(child, tags, activeOnly, result);
}
Actor* Level::FindActor(const Tag& tag, Actor* root)
{
PROFILE_CPU();
if (root)
return FindActorRecursive(root, tag);
Actor* result = nullptr;
for (Scene* scene : Scenes)
{
result = FindActorRecursive(scene, tag);
if (result)
break;
}
return result;
}
void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
{
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorRecursive(child, tag, result);
}
Array<Actor*> Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
if (root)
{
FindActorsRecursive(root, tag, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursive(scene, tag, activeOnly, result);
}
return result;
}
Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
const Array<Tag> subTags = Tags::GetSubTags(parentTag);
if (subTags.Count() == 0)
{
return result;
}
if (subTags.Count() == 1)
{
result = FindActors(subTags[0], activeOnly, root);
return result;
}
if (root)
{
FindActorsRecursiveByParentTags(root, subTags, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result);
}
return result;
}
void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b)
{
PROFILE_CPU();
@@ -1505,6 +1395,143 @@ Actor* Level::FindActor(const MClass* type, const StringView& name)
return result;
}
Actor* FindActorRecursive(Actor* node, const Tag& tag)
{
if (node->HasTag(tag))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursive(child, tag);
if (result)
break;
}
return result;
}
Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag)
{
CHECK_RETURN(type, nullptr);
if (node->HasTag(tag) && node->GetClass()->IsSubClassOf(type))
return node;
Actor* result = nullptr;
for (Actor* child : node->Children)
{
result = FindActorRecursiveByType(child, type, tag);
if (result)
break;
}
return result;
}
void FindActorsRecursive(Actor* node, const Tag& tag, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorsRecursive(child, tag, activeOnly, result);
}
void FindActorsRecursiveByParentTags(Actor* node, const Array<Tag>& tags, const bool activeOnly, Array<Actor*>& result)
{
if (activeOnly && !node->GetIsActive())
return;
for (Tag tag : tags)
{
if (node->HasTag(tag))
{
result.Add(node);
break;
}
}
for (Actor* child : node->Children)
FindActorsRecursiveByParentTags(child, tags, activeOnly, result);
}
Actor* Level::FindActor(const Tag& tag, Actor* root)
{
PROFILE_CPU();
if (root)
return FindActorRecursive(root, tag);
Actor* result = nullptr;
for (Scene* scene : Scenes)
{
result = FindActorRecursive(scene, tag);
if (result)
break;
}
return result;
}
Actor* Level::FindActor(const MClass* type, const Tag& tag, Actor* root)
{
CHECK_RETURN(type, nullptr);
if (root)
return FindActorRecursiveByType(root, type, tag);
Actor* result = nullptr;
ScopeLock lock(ScenesLock);
for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++)
result = Scenes[i]->FindActor(type, tag);
return result;
}
void FindActorRecursive(Actor* node, const Tag& tag, Array<Actor*>& result)
{
if (node->HasTag(tag))
result.Add(node);
for (Actor* child : node->Children)
FindActorRecursive(child, tag, result);
}
Array<Actor*> Level::FindActors(const Tag& tag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
if (root)
{
FindActorsRecursive(root, tag, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursive(scene, tag, activeOnly, result);
}
return result;
}
Array<Actor*> Level::FindActorsByParentTag(const Tag& parentTag, const bool activeOnly, Actor* root)
{
PROFILE_CPU();
Array<Actor*> result;
const Array<Tag> subTags = Tags::GetSubTags(parentTag);
if (subTags.Count() == 0)
{
return result;
}
if (subTags.Count() == 1)
{
result = FindActors(subTags[0], activeOnly, root);
return result;
}
if (root)
{
FindActorsRecursiveByParentTags(root, subTags, activeOnly, result);
}
else
{
ScopeLock lock(ScenesLock);
for (Scene* scene : Scenes)
FindActorsRecursiveByParentTags(scene, subTags, activeOnly, result);
}
return result;
}
Script* Level::FindScript(const MClass* type)
{
CHECK_RETURN(type, nullptr);

View File

@@ -77,6 +77,18 @@ namespace FlaxEngine
{
return FindActor(typeof(T), name) as T;
}
/// <summary>
/// Tries to find actor of the given type and tag in a root actor or all loaded scenes.
/// </summary>
/// <param name="tag">A tag on the object.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <typeparam name="T">Type of the object.</typeparam>
/// <returns>Found actor or null.</returns>
public static T FindActor<T>(Tag tag, Actor root = null) where T : Actor
{
return FindActor(typeof(T), tag, root) as T;
}
/// <summary>
/// Tries to find actor with the given ID in all loaded scenes. It's very fast O(1) lookup.

View File

@@ -371,6 +371,41 @@ public:
/// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const StringView& name);
/// <summary>
/// Tries to find the actor with the given tag (returns the first one found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actor or null.</returns>
API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr);
/// <summary>
/// Tries to find the actor of the given type and tag in all the loaded scenes.
/// </summary>
/// <param name="type">Type of the actor to search for. Includes any actors derived from the type.</param>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Actor instance if found, null otherwise.</returns>
API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, Actor* root = nullptr);
/// <summary>
/// Tries to find the actors with the given tag (returns all found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actors or empty if none.</returns>
API_FUNCTION() static Array<Actor*> FindActors(const Tag& tag, const bool activeOnly = false, Actor* root = nullptr);
/// <summary>
/// Search actors using a parent parentTag.
/// </summary>
/// <param name="parentTag">The tag to search actors with subtags belonging to this tag</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Returns all actors that have subtags belonging to the given parent parentTag</returns>
API_FUNCTION() static Array<Actor*> FindActorsByParentTag(const Tag& parentTag, const bool activeOnly = false, Actor* root = nullptr);
/// <summary>
/// Tries to find the actor of the given type in all the loaded scenes.
/// </summary>
@@ -392,6 +427,18 @@ public:
return (T*)FindActor(T::GetStaticClass(), name);
}
/// <summary>
/// Tries to find the actor of the given type and tag in a root actor or all the loaded scenes.
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// </summary>
/// <returns>Actor instance if found, null otherwise.</returns>
template<typename T>
FORCE_INLINE static T* FindActor(const Tag& tag, Actor* root = nullptr)
{
return (T*)FindActor(T::GetStaticClass(), tag, root);
}
/// <summary>
/// Tries to find the script of the given type in all the loaded scenes.
/// </summary>
@@ -481,33 +528,6 @@ public:
/// </summary>
API_FUNCTION() static int32 GetLayerIndex(const StringView& layer);
public:
/// <summary>
/// Tries to find the actor with the given tag (returns the first one found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actor or null.</returns>
API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr);
/// <summary>
/// Tries to find the actors with the given tag (returns all found).
/// </summary>
/// <param name="tag">The tag of the actor to search for.</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Found actors or empty if none.</returns>
API_FUNCTION() static Array<Actor*> FindActors(const Tag& tag, const bool activeOnly = false, Actor* root = nullptr);
/// <summary>
/// Search actors using a parent parentTag.
/// </summary>
/// <param name="parentTag">The tag to search actors with subtags belonging to this tag</param>
/// <param name="activeOnly">Find only active actors.</param>
/// <param name="root">The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes.</param>
/// <returns>Returns all actors that have subtags belonging to the given parent parentTag</returns>
API_FUNCTION() static Array<Actor*> FindActorsByParentTag(const Tag& parentTag, const bool activeOnly = false, Actor* root = nullptr);
private:
// Actor API
enum class ActorEventType

View File

@@ -96,6 +96,16 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c
physicsDebugData[i](view);
}
}
// Draw light shapes
if (EnumHasAnyFlags(view.Flags, ViewFlags::LightsDebug))
{
const LightsDebugCallback* lightsDebugData = LightsDebug.Get();
for (int32 i = 0; i < LightsDebug.Count(); i++)
{
lightsDebugData[i](view);
}
}
}
#endif
}
@@ -157,14 +167,16 @@ void SceneRendering::UpdateActor(Actor* a, int32& key)
const int32 category = a->_drawCategory;
ScopeLock lock(Locker);
auto& list = Actors[category];
if (list.IsEmpty())
if (list.Count() <= key) // Ignore invalid key softly
return;
auto& e = list[key];
ASSERT_LOW_LAYER(a == e.Actor);
for (auto* listener : _listeners)
listener->OnSceneRenderingUpdateActor(a, e.Bounds);
e.LayerMask = a->GetLayerMask();
e.Bounds = a->GetSphere();
if (e.Actor == a)
{
for (auto* listener : _listeners)
listener->OnSceneRenderingUpdateActor(a, e.Bounds);
e.LayerMask = a->GetLayerMask();
e.Bounds = a->GetSphere();
}
}
void SceneRendering::RemoveActor(Actor* a, int32& key)
@@ -172,14 +184,16 @@ void SceneRendering::RemoveActor(Actor* a, int32& key)
const int32 category = a->_drawCategory;
ScopeLock lock(Locker);
auto& list = Actors[category];
if (list.HasItems())
if (list.Count() > key) // Ignore invalid key softly (eg. list after batch clear during scene unload)
{
auto& e = list[key];
ASSERT_LOW_LAYER(a == e.Actor);
for (auto* listener : _listeners)
listener->OnSceneRenderingRemoveActor(a);
e.Actor = nullptr;
e.LayerMask = 0;
auto& e = list.Get()[key];
if (e.Actor == a)
{
for (auto* listener : _listeners)
listener->OnSceneRenderingRemoveActor(a);
e.Actor = nullptr;
e.LayerMask = 0;
}
}
key = -1;
}

View File

@@ -65,6 +65,7 @@ class FLAXENGINE_API SceneRendering
{
#if USE_EDITOR
typedef Function<void(RenderView&)> PhysicsDebugCallback;
typedef Function<void(RenderView&)> LightsDebugCallback;
friend class ViewportIconsRendererService;
#endif
public:
@@ -95,6 +96,7 @@ public:
private:
#if USE_EDITOR
Array<PhysicsDebugCallback> PhysicsDebug;
Array<LightsDebugCallback> LightsDebug;
Array<Actor*> ViewportIcons;
#endif
@@ -153,6 +155,22 @@ public:
PhysicsDebug.Remove(f);
}
template<class T, void(T::*Method)(RenderView&)>
FORCE_INLINE void AddLightsDebug(T* obj)
{
LightsDebugCallback f;
f.Bind<T, Method>(obj);
LightsDebug.Add(f);
}
template<class T, void(T::*Method)(RenderView&)>
void RemoveLightsDebug(T* obj)
{
LightsDebugCallback f;
f.Bind<T, Method>(obj);
LightsDebug.Remove(f);
}
FORCE_INLINE void AddViewportIcon(Actor* obj)
{
ViewportIcons.Add(obj);

View File

@@ -23,8 +23,8 @@ namespace FlaxEngine
if (_keyframes == null || _keyframes.Length != count)
_keyframes = new BezierCurve<Transform>.Keyframe[count];
#if !BUILD_RELEASE
if (Marshal.SizeOf(typeof(BezierCurve<Transform>.Keyframe)) != Transform.SizeInBytes * 3 + sizeof(float))
throw new Exception("Invalid size of BezierCurve keyframe " + Marshal.SizeOf(typeof(BezierCurve<Transform>.Keyframe)) + " bytes.");
if (System.Runtime.CompilerServices.Unsafe.SizeOf<BezierCurve<Transform>.Keyframe>() != Transform.SizeInBytes * 3 + sizeof(float))
throw new Exception("Invalid size of BezierCurve keyframe " + System.Runtime.CompilerServices.Unsafe.SizeOf<BezierCurve<Transform>.Keyframe>() + " bytes.");
#endif
Internal_GetKeyframes(__unmanagedPtr, _keyframes);
return _keyframes;

View File

@@ -23,7 +23,7 @@ void NetworkReplicationHierarchyUpdateResult::Init()
{
_clientsHaveLocation = false;
_clients.Resize(NetworkManager::Clients.Count());
_clientsMask = NetworkClientsMask();
_clientsMask = NetworkManager::Mode == NetworkManagerMode::Client ? NetworkClientsMask::All : NetworkClientsMask();
for (int32 i = 0; i < _clients.Count(); i++)
_clientsMask.SetBit(i);
_entries.Clear();
@@ -63,6 +63,17 @@ bool NetworkReplicationNode::RemoveObject(ScriptingObject* obj)
return !Objects.Remove(obj);
}
bool NetworkReplicationNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result)
{
const int32 index = Objects.Find(obj);
if (index != -1)
{
result = Objects[index];
return true;
}
return false;
}
bool NetworkReplicationNode::DirtyObject(ScriptingObject* obj)
{
const int32 index = Objects.Find(obj);
@@ -156,6 +167,7 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
cell->MinCullDistance = obj.CullDistance;
}
cell->Node->AddObject(obj);
_objectToCell[obj.Object] = coord;
// Cache minimum culling distance for a whole cell to skip it at once
cell->MinCullDistance = Math::Min(cell->MinCullDistance, obj.CullDistance);
@@ -163,14 +175,35 @@ void NetworkReplicationGridNode::AddObject(NetworkReplicationHierarchyObject obj
bool NetworkReplicationGridNode::RemoveObject(ScriptingObject* obj)
{
for (const auto& e : _children)
Int3 coord;
if (!_objectToCell.TryGet(obj, coord))
{
if (e.Value.Node->RemoveObject(obj))
{
// TODO: remove empty cells?
// TODO: update MinCullDistance for cell?
return true;
}
return false;
}
if (_children[coord].Node->RemoveObject(obj))
{
_objectToCell.Remove(obj);
// TODO: remove empty cells?
// TODO: update MinCullDistance for cell?
return true;
}
return false;
}
bool NetworkReplicationGridNode::GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result)
{
Int3 coord;
if (!_objectToCell.TryGet(obj, coord))
{
return false;
}
if (_children[coord].Node->GetObject(obj, result))
{
return true;
}
return false;
}

View File

@@ -200,6 +200,14 @@ API_CLASS(Abstract, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API Ne
/// <returns>True on successful removal, otherwise false.</returns>
API_FUNCTION() virtual bool RemoveObject(ScriptingObject* obj);
/// <summary>
/// Gets object from the hierarchy.
/// </summary>
/// <param name="obj">The object to get.</param>
/// <param name="result">The hierarchy object to retrieve.</param>
/// <returns>True on successful retrieval, otherwise false.</returns>
API_FUNCTION() virtual bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result);
/// <summary>
/// Force replicates the object during the next update. Resets any internal tracking state to force the synchronization.
/// </summary>
@@ -238,6 +246,7 @@ private:
};
Dictionary<Int3, Cell> _children;
Dictionary<ScriptingObject*, Int3> _objectToCell;
public:
/// <summary>
@@ -247,6 +256,7 @@ public:
void AddObject(NetworkReplicationHierarchyObject obj) override;
bool RemoveObject(ScriptingObject* obj) override;
bool GetObject(ScriptingObject* obj, NetworkReplicationHierarchyObject& result) override;
void Update(NetworkReplicationHierarchyUpdateResult* result) override;
};

View File

@@ -700,9 +700,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri
NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo;
}
void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds)
bool NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds)
{
EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan<uint32>(targetIds));
return EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan<uint32>(targetIds));
}
StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name)
@@ -1113,12 +1113,12 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC()
return CachedWriteStream;
}
void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds)
bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds)
{
Scripting::ObjectsLookupIdMapping.Set(nullptr);
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name));
if (!info || !obj || NetworkManager::IsOffline())
return;
return false;
ObjectsLock.Lock();
auto& rpc = RpcQueue.AddOne();
rpc.Object = obj;
@@ -1135,12 +1135,23 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa
}
#endif
ObjectsLock.Unlock();
// Check if skip local execution (eg. server rpc called from client or client rpc with specific targets)
const NetworkManagerMode networkMode = NetworkManager::Mode;
if (info->Server && networkMode == NetworkManagerMode::Client)
return true;
if (info->Client && networkMode == NetworkManagerMode::Server)
return true;
if (info->Client && networkMode == NetworkManagerMode::Host && targetIds.IsValid() && !SpanContains(targetIds, NetworkManager::LocalClientId))
return true;
return false;
}
void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
{
ScopeLock lock(ObjectsLock);
NewClients.Add(client);
ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients
}
void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client)
@@ -1193,6 +1204,7 @@ void NetworkInternal::NetworkReplicatorClear()
Objects.Remove(it);
}
}
Objects.Clear();
RpcQueue.Clear();
SpawnQueue.Clear();
DespawnQueue.Clear();

View File

@@ -120,10 +120,11 @@ namespace FlaxEngine.Networking
/// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
/// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
/// <returns>True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).</returns>
[Unmanaged]
public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null)
public static bool EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null)
{
Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds);
return Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds);
}
/// <summary>

View File

@@ -195,13 +195,14 @@ public:
/// <param name="name">The RPC name.</param>
/// <param name="argsStream">The RPC serialized arguments stream returned from BeginInvokeRPC.</param>
/// <param name="targetIds">Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs.</param>
static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds = Span<uint32>());
/// <returns>True if RPC cannot be executed locally, false if execute it locally too (checks RPC mode and target client ids).</returns>
static bool EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span<uint32> targetIds = Span<uint32>());
private:
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize);
API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function<void(void*, void*)>& execute, bool isServer, bool isClient, NetworkChannelType channel);
API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds);
API_FUNCTION(NoProxy) static bool CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MArray* targetIds);
static StringAnsiView GetCSharpCachedName(const StringAnsiView& name);
#endif
};

View File

@@ -41,7 +41,7 @@ struct FLAXENGINE_API NetworkRpcInfo
uint8 Client : 1;
uint8 Channel : 4;
void (*Execute)(ScriptingObject* obj, NetworkStream* stream, void* tag);
void (*Invoke)(ScriptingObject* obj, void** args);
bool (*Invoke)(ScriptingObject* obj, void** args);
void* Tag;
/// <summary>
@@ -83,10 +83,7 @@ FORCE_INLINE void NetworkRpcInitArg(Array<void*, FixedAllocation<16>>& args, con
{ \
Array<void*, FixedAllocation<16>> args; \
NetworkRpcInitArg(args, __VA_ARGS__); \
rpcInfo.Invoke(this, args.Get()); \
if (rpcInfo.Server && networkMode == NetworkManagerMode::Client) \
return; \
if (rpcInfo.Client && networkMode == NetworkManagerMode::Server) \
if (rpcInfo.Invoke(this, args.Get())) \
return; \
} \
}

View File

@@ -189,7 +189,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va
const Matrix viewProjection = context.ViewTask ? context.ViewTask->View.PrevViewProjection : Matrix::Identity;
const Float3 position = (Float3)TryGetValue(node->GetBox(0), Value::Zero);
Float4 projPos;
Float3::Transform(position, viewProjection);
Float3::Transform(position, viewProjection, projPos);
projPos /= projPos.W;
value = Float2(projPos.X * 0.5f + 0.5f, projPos.Y * 0.5f + 0.5f);
break;

View File

@@ -230,10 +230,11 @@ void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
{
const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(basePos, wheel.Radius * 0.07f), Color::Blue * 0.3f, 0, true);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, true);
DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, true);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true);
if (!data.State.IsInAir)
{
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.TireContactPoint, 5.0f), Color::Green, 0, true);
@@ -260,6 +261,7 @@ void WheeledVehicle::OnDebugDrawSelected()
{
const Vector3 currentPos = wheel.Collider->GetPosition();
const Vector3 basePos = currentPos - Vector3(0, data.State.SuspensionOffset, 0);
const Quaternion wheelDebugOrientation = GetOrientation() * Quaternion::Euler(-data.State.RotationAngle, data.State.SteerAngle, 0) * Quaternion::Euler(90, 0, 90);
Transform actorPose = Transform::Identity, shapePose = Transform::Identity;
PhysicsBackend::GetRigidActorPose(_actor, actorPose.Translation, actorPose.Orientation);
PhysicsBackend::GetShapeLocalPose(wheel.Collider->GetPhysicsShape(), shapePose.Translation, shapePose.Orientation);
@@ -267,7 +269,7 @@ void WheeledVehicle::OnDebugDrawSelected()
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(currentPos, wheel.Radius * 0.08f), Color::Blue * 0.8f, 0, false);
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(actorPose.LocalToWorld(shapePose.Translation), wheel.Radius * 0.11f), Color::OrangeRed * 0.8f, 0, false);
DEBUG_DRAW_LINE(basePos, currentPos, Color::Blue, 0, false);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false);
DEBUG_DRAW_WIRE_CYLINDER(currentPos, wheelDebugOrientation, wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false);
if (!data.State.SuspensionTraceStart.IsZero())
{
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(data.State.SuspensionTraceStart, 5.0f), Color::AliceBlue, 0, false);

View File

@@ -125,6 +125,7 @@ WindowBase::~WindowBase()
{
ASSERT(!RenderTask);
ASSERT(!_swapChain);
WindowsManager::Unregister((Window*)this);
}
bool WindowBase::IsMain() const

Some files were not shown because too many files have changed in this diff Show More