Merge remote-tracking branch 'origin/master' into 1.8
This commit is contained in:
@@ -12,13 +12,14 @@ namespace FlaxEngine.Tools
|
||||
{
|
||||
partial struct Options
|
||||
{
|
||||
private bool ShowGeometry => Type == ModelTool.ModelType.Model || Type == ModelTool.ModelType.SkinnedModel;
|
||||
private bool ShowModel => Type == ModelTool.ModelType.Model;
|
||||
private bool ShowSkinnedModel => Type == ModelTool.ModelType.SkinnedModel;
|
||||
private bool ShowAnimation => Type == ModelTool.ModelType.Animation;
|
||||
private bool ShowGeometry => Type == ModelType.Model || Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
|
||||
private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab;
|
||||
private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
|
||||
private bool ShowAnimation => Type == ModelType.Animation || Type == ModelType.Prefab;
|
||||
private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
|
||||
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
|
||||
private bool ShowFramesRange => ShowAnimation && Duration == ModelTool.AnimationDuration.Custom;
|
||||
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
|
||||
private bool ShowSplitting => Type != ModelType.Prefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,10 @@ namespace FlaxEditor.Content
|
||||
{
|
||||
if (_preview == null)
|
||||
{
|
||||
_preview = new ModelPreview(false);
|
||||
_preview = new ModelPreview(false)
|
||||
{
|
||||
ScaleToFit = false,
|
||||
};
|
||||
InitAssetPreview(_preview);
|
||||
}
|
||||
|
||||
@@ -91,6 +94,7 @@ namespace FlaxEditor.Content
|
||||
_preview.Model = (Model)request.Asset;
|
||||
_preview.Parent = guiRoot;
|
||||
_preview.SyncBackbufferSize();
|
||||
_preview.ViewportCamera.SetArcBallView(_preview.Model.GetBox());
|
||||
|
||||
_preview.Task.OnDraw();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ namespace FlaxEditor.Content
|
||||
/// </summary>
|
||||
protected ContentFolder _folder;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node can be deleted.
|
||||
/// </summary>
|
||||
public virtual bool CanDelete => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this node can be duplicated.
|
||||
/// </summary>
|
||||
public virtual bool CanDuplicate => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content folder item.
|
||||
/// </summary>
|
||||
@@ -301,7 +311,7 @@ namespace FlaxEditor.Content
|
||||
StartRenaming();
|
||||
return true;
|
||||
case KeyboardKeys.Delete:
|
||||
if (Folder.Exists)
|
||||
if (Folder.Exists && CanDelete)
|
||||
Editor.Instance.Windows.ContentWin.Delete(Folder);
|
||||
return true;
|
||||
}
|
||||
@@ -310,7 +320,7 @@ namespace FlaxEditor.Content
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.D:
|
||||
if (Folder.Exists)
|
||||
if (Folder.Exists && CanDuplicate)
|
||||
Editor.Instance.Windows.ContentWin.Duplicate(Folder);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ namespace FlaxEditor.Content
|
||||
{
|
||||
private FileSystemWatcher _watcher;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanDelete => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanDuplicate => false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MainContentTreeNode"/> class.
|
||||
/// </summary>
|
||||
|
||||
@@ -1270,7 +1270,7 @@ bool CookAssetsStep::Perform(CookingData& data)
|
||||
{
|
||||
Array<CookingData::AssetTypeStatistics> assetTypes;
|
||||
data.Stats.AssetStats.GetValues(assetTypes);
|
||||
Sorting::QuickSort(assetTypes.Get(), assetTypes.Count());
|
||||
Sorting::QuickSort(assetTypes);
|
||||
|
||||
LOG(Info, "");
|
||||
LOG(Info, "Top assets types stats:");
|
||||
|
||||
@@ -119,7 +119,7 @@ bool DeployDataStep::Perform(CookingData& data)
|
||||
if (!version.StartsWith(TEXT("8."))) // Check for major part of 8.0
|
||||
version.Clear();
|
||||
}
|
||||
Sorting::QuickSort(versions.Get(), versions.Count());
|
||||
Sorting::QuickSort(versions);
|
||||
const String version = versions.Last();
|
||||
FileSystem::NormalizePath(srcDotnet);
|
||||
LOG(Info, "Using .Net Runtime {} at {}", version, srcDotnet);
|
||||
|
||||
79
Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs
Normal file
79
Source/Editor/CustomEditors/Dedicated/ModelPrefabEditor.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using FlaxEditor.Content;
|
||||
using FlaxEditor.CustomEditors.Editors;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
using FlaxEngine.Tools;
|
||||
|
||||
namespace FlaxEditor.CustomEditors.Dedicated;
|
||||
|
||||
/// <summary>
|
||||
/// The missing script editor.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ModelPrefab)), DefaultEditor]
|
||||
public class ModelPrefabEditor : GenericEditor
|
||||
{
|
||||
private Guid _prefabId;
|
||||
private Button _reimportButton;
|
||||
private string _importPath;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize(LayoutElementsContainer layout)
|
||||
{
|
||||
base.Initialize(layout);
|
||||
|
||||
var modelPrefab = Values[0] as ModelPrefab;
|
||||
if (modelPrefab == null)
|
||||
return;
|
||||
_prefabId = modelPrefab.PrefabID;
|
||||
while (true)
|
||||
{
|
||||
var prefab = FlaxEngine.Content.Load<Prefab>(_prefabId);
|
||||
if (prefab)
|
||||
{
|
||||
var prefabObjectId = modelPrefab.PrefabObjectID;
|
||||
var prefabObject = prefab.GetDefaultInstance(ref prefabObjectId);
|
||||
if (prefabObject.PrefabID == _prefabId)
|
||||
break;
|
||||
_prefabId = prefabObject.PrefabID;
|
||||
}
|
||||
}
|
||||
|
||||
var button = layout.Button("Reimport", "Reimports the source asset as prefab.");
|
||||
_reimportButton = button.Button;
|
||||
_reimportButton.Clicked += OnReimport;
|
||||
}
|
||||
|
||||
private void OnReimport()
|
||||
{
|
||||
var prefab = FlaxEngine.Content.Load<Prefab>(_prefabId);
|
||||
var modelPrefab = (ModelPrefab)Values[0];
|
||||
var importPath = modelPrefab.ImportPath;
|
||||
var editor = Editor.Instance;
|
||||
if (editor.ContentImporting.GetReimportPath("Model Prefab", ref importPath))
|
||||
return;
|
||||
var folder = editor.ContentDatabase.Find(Path.GetDirectoryName(prefab.Path)) as ContentFolder;
|
||||
if (folder == null)
|
||||
return;
|
||||
var importOptions = modelPrefab.ImportOptions;
|
||||
importOptions.Type = ModelTool.ModelType.Prefab;
|
||||
_importPath = importPath;
|
||||
_reimportButton.Enabled = false;
|
||||
editor.ContentImporting.ImportFileEnd += OnImportFileEnd;
|
||||
editor.ContentImporting.Import(importPath, folder, true, importOptions);
|
||||
}
|
||||
|
||||
private void OnImportFileEnd(IFileEntryAction entry, bool failed)
|
||||
{
|
||||
if (entry.SourceUrl == _importPath)
|
||||
{
|
||||
// Restore button
|
||||
_importPath = null;
|
||||
_reimportButton.Enabled = true;
|
||||
Editor.Instance.ContentImporting.ImportFileEnd -= OnImportFileEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +246,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var multiAction = new MultiUndoAction(actions);
|
||||
multiAction.Do();
|
||||
var presenter = ScriptsEditor.Presenter;
|
||||
ScriptsEditor.ParentEditor?.RebuildLayout();
|
||||
if (presenter != null)
|
||||
{
|
||||
presenter.Undo.AddAction(multiAction);
|
||||
|
||||
@@ -73,7 +73,6 @@ namespace FlaxEditor.CustomEditors
|
||||
{
|
||||
if (instanceValues == null || instanceValues.Count != Count)
|
||||
throw new ArgumentException();
|
||||
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var v = instanceValues[i];
|
||||
|
||||
@@ -201,7 +201,21 @@ namespace FlaxEditor.Gizmo
|
||||
ActorNode prefabRoot = GetPrefabRootInParent(actorNode);
|
||||
if (prefabRoot != null && actorNode != prefabRoot)
|
||||
{
|
||||
hit = WalkUpAndFindActorNodeBeforeSelection(actorNode, prefabRoot);
|
||||
bool isPrefabInSelection = false;
|
||||
foreach (var e in sceneEditing.Selection)
|
||||
{
|
||||
if (e is ActorNode ae && GetPrefabRootInParent(ae) == prefabRoot)
|
||||
{
|
||||
isPrefabInSelection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip selecting prefab root if we already had object from that prefab selected
|
||||
if (!isPrefabInSelection)
|
||||
{
|
||||
hit = WalkUpAndFindActorNodeBeforeSelection(actorNode, prefabRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -769,7 +769,7 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String
|
||||
|
||||
// Get options from model
|
||||
FileSystem::NormalizePath(assetPath);
|
||||
return ImportModelFile::TryGetImportOptions(assetPath, options);
|
||||
return ImportModel::TryGetImportOptions(assetPath, options);
|
||||
}
|
||||
|
||||
bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options)
|
||||
|
||||
@@ -126,29 +126,35 @@ namespace FlaxEditor.Modules
|
||||
{
|
||||
if (item != null && !item.GetImportPath(out string importPath))
|
||||
{
|
||||
// Check if input file is missing
|
||||
if (!System.IO.File.Exists(importPath))
|
||||
{
|
||||
Editor.LogWarning(string.Format("Cannot reimport asset \'{0}\'. File \'{1}\' does not exist.", item.Path, importPath));
|
||||
if (skipSettingsDialog)
|
||||
return;
|
||||
|
||||
// Ask user to select new file location
|
||||
var title = string.Format("Please find missing \'{0}\' file for asset \'{1}\'", importPath, item.ShortName);
|
||||
if (FileSystem.ShowOpenFileDialog(Editor.Windows.MainWindow, null, "All files (*.*)\0*.*\0", false, title, out var files))
|
||||
return;
|
||||
if (files != null && files.Length > 0)
|
||||
importPath = files[0];
|
||||
|
||||
// Validate file path again
|
||||
if (!System.IO.File.Exists(importPath))
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetReimportPath(item.ShortName, ref importPath, skipSettingsDialog))
|
||||
return;
|
||||
Import(importPath, item.Path, true, skipSettingsDialog, settings);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool GetReimportPath(string contextName, ref string importPath, bool skipSettingsDialog = false)
|
||||
{
|
||||
// Check if input file is missing
|
||||
if (!System.IO.File.Exists(importPath))
|
||||
{
|
||||
Editor.LogWarning(string.Format("Cannot reimport asset \'{0}\'. File \'{1}\' does not exist.", contextName, importPath));
|
||||
if (skipSettingsDialog)
|
||||
return true;
|
||||
|
||||
// Ask user to select new file location
|
||||
var title = string.Format("Please find missing \'{0}\' file for asset \'{1}\'", importPath, contextName);
|
||||
if (FileSystem.ShowOpenFileDialog(Editor.Windows.MainWindow, null, "All files (*.*)\0*.*\0", false, title, out var files))
|
||||
return true;
|
||||
if (files != null && files.Length > 0)
|
||||
importPath = files[0];
|
||||
|
||||
// Validate file path again
|
||||
if (!System.IO.File.Exists(importPath))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports the specified files.
|
||||
/// </summary>
|
||||
|
||||
@@ -31,13 +31,26 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
|
||||
// Rotate to match the space (GUI uses upper left corner as a root)
|
||||
Actor.LocalOrientation = Quaternion.Euler(0, -180, -180);
|
||||
var uiControl = new UIControl
|
||||
bool canSpawn = true;
|
||||
foreach (var uiControl in Actor.GetChildren<UIControl>())
|
||||
{
|
||||
Name = "Canvas Scalar",
|
||||
Transform = Actor.Transform,
|
||||
Control = new CanvasScaler()
|
||||
};
|
||||
Root.Spawn(uiControl, Actor);
|
||||
if (uiControl.Get<CanvasScaler>() == null)
|
||||
continue;
|
||||
canSpawn = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (canSpawn)
|
||||
{
|
||||
var uiControl = new UIControl
|
||||
{
|
||||
Name = "Canvas Scalar",
|
||||
Transform = Actor.Transform,
|
||||
Control = new CanvasScaler()
|
||||
};
|
||||
Root.Spawn(uiControl, Actor);
|
||||
}
|
||||
_treeNode.Expand();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -85,12 +85,20 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
{
|
||||
if (Parent is ActorTreeNode parent)
|
||||
{
|
||||
for (int i = 0; i < parent.ChildrenCount; i++)
|
||||
var anyChanged = false;
|
||||
var children = parent.Children;
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
if (parent.Children[i] is ActorTreeNode child && child.Actor)
|
||||
child._orderInParent = child.Actor.OrderInParent;
|
||||
if (children[i] is ActorTreeNode child && child.Actor)
|
||||
{
|
||||
var orderInParent = child.Actor.OrderInParent;
|
||||
anyChanged |= child._orderInParent != orderInParent;
|
||||
if (anyChanged)
|
||||
child._orderInParent = orderInParent;
|
||||
}
|
||||
}
|
||||
parent.SortChildren();
|
||||
if (anyChanged)
|
||||
parent.SortChildren();
|
||||
}
|
||||
else if (Actor)
|
||||
{
|
||||
|
||||
@@ -1335,6 +1335,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Surface?.AddBatchedUndoAction(action);
|
||||
action.Do();
|
||||
Surface?.OnNodesConnected(this, other);
|
||||
Surface?.MarkAsEdited();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1911,6 +1912,7 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
var action = new StateMachineStateBase.AddRemoveTransitionAction(this);
|
||||
SourceState.Surface?.AddBatchedUndoAction(action);
|
||||
SourceState.Surface?.MarkAsEdited();
|
||||
action.Do();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,10 @@ namespace FlaxEditor.Surface.Elements
|
||||
Archetype = archetype;
|
||||
|
||||
ParentNode.ValuesChanged += OnNodeValuesChanged;
|
||||
|
||||
// Disable slider if surface doesn't allow it
|
||||
if (!ParentNode.Surface.CanLivePreviewValueChanges)
|
||||
_slideSpeed = 0.0f;
|
||||
}
|
||||
|
||||
private void OnNodeValuesChanged()
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace FlaxEditor.Surface
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanLivePreviewValueChanges => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string GetTypeName(ScriptType type)
|
||||
{
|
||||
|
||||
@@ -533,6 +533,7 @@ namespace FlaxEditor.Surface
|
||||
UpdateSelectionRectangle();
|
||||
}
|
||||
}
|
||||
bool showPrimaryMenu = false;
|
||||
if (_rightMouseDown && button == MouseButton.Right)
|
||||
{
|
||||
_rightMouseDown = false;
|
||||
@@ -546,8 +547,7 @@ namespace FlaxEditor.Surface
|
||||
_cmStartPos = location;
|
||||
if (controlUnderMouse == null)
|
||||
{
|
||||
// Show primary context menu
|
||||
ShowPrimaryMenu(_cmStartPos);
|
||||
showPrimaryMenu = true;
|
||||
}
|
||||
}
|
||||
_mouseMoveAmount = 0;
|
||||
@@ -573,8 +573,13 @@ namespace FlaxEditor.Surface
|
||||
return true;
|
||||
}
|
||||
|
||||
// If none of the child controls handled this show the primary context menu
|
||||
if (showPrimaryMenu)
|
||||
{
|
||||
ShowPrimaryMenu(_cmStartPos);
|
||||
}
|
||||
// Letting go of a connection or right clicking while creating a connection
|
||||
if (!_isMovingSelection && _connectionInstigator != null && !IsPrimaryMenuOpened)
|
||||
else if (!_isMovingSelection && _connectionInstigator != null && !IsPrimaryMenuOpened)
|
||||
{
|
||||
_cmStartPos = location;
|
||||
Cursor = CursorType.Default;
|
||||
|
||||
@@ -534,6 +534,11 @@ namespace FlaxEditor.Surface
|
||||
/// </summary>
|
||||
public virtual bool CanSetParameters => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether surface supports/allows live previewing graph modifications due to value sliders and color pickers. True by default but disabled for shader surfaces that generate and compile shader source at flight.
|
||||
/// </summary>
|
||||
public virtual bool CanLivePreviewValueChanges => true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified node archetype can be used in the surface.
|
||||
/// </summary>
|
||||
|
||||
@@ -1374,8 +1374,8 @@ namespace FlaxEditor.Viewport
|
||||
ivp.Invert();
|
||||
|
||||
// Create near and far points
|
||||
var nearPoint = new Vector3(mousePosition, 0.0f);
|
||||
var farPoint = new Vector3(mousePosition, 1.0f);
|
||||
var nearPoint = new Vector3(mousePosition, _nearPlane);
|
||||
var farPoint = new Vector3(mousePosition, _farPlane);
|
||||
viewport.Unproject(ref nearPoint, ref ivp, out nearPoint);
|
||||
viewport.Unproject(ref farPoint, ref ivp, out farPoint);
|
||||
|
||||
|
||||
@@ -428,11 +428,9 @@ namespace FlaxEditor.Windows.Assets
|
||||
|
||||
private void Update(ActorNode actorNode)
|
||||
{
|
||||
if (actorNode.Actor)
|
||||
{
|
||||
actorNode.TreeNode.UpdateText();
|
||||
actorNode.TreeNode.OnOrderInParentChanged();
|
||||
}
|
||||
actorNode.TreeNode.UpdateText();
|
||||
if (actorNode.TreeNode.IsCollapsed)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < actorNode.ChildNodes.Count; i++)
|
||||
{
|
||||
|
||||
@@ -440,6 +440,7 @@ namespace FlaxEditor.Windows.Assets
|
||||
{
|
||||
try
|
||||
{
|
||||
FlaxEngine.Profiler.BeginEvent("PrefabWindow.Update");
|
||||
if (Graph.Main != null)
|
||||
{
|
||||
// Due to fact that actors in prefab editor are only created but not added to gameplay
|
||||
@@ -468,6 +469,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
Graph.Root.TreeNode.ExpandAll(true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
FlaxEngine.Profiler.EndEvent();
|
||||
}
|
||||
|
||||
// Auto fit
|
||||
if (_focusCamera && _viewport.Task.FrameCount > 1)
|
||||
|
||||
@@ -114,18 +114,32 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
}
|
||||
|
||||
cm.AddButton("Delete", () => Delete(item));
|
||||
if (isFolder && folder.Node is MainContentTreeNode)
|
||||
{
|
||||
cm.AddSeparator();
|
||||
}
|
||||
else
|
||||
{
|
||||
cm.AddButton("Delete", () => Delete(item));
|
||||
|
||||
cm.AddSeparator();
|
||||
cm.AddSeparator();
|
||||
|
||||
cm.AddButton("Duplicate", _view.Duplicate);
|
||||
cm.AddButton("Duplicate", _view.Duplicate);
|
||||
|
||||
cm.AddButton("Copy", _view.Copy);
|
||||
cm.AddButton("Copy", _view.Copy);
|
||||
}
|
||||
|
||||
b = cm.AddButton("Paste", _view.Paste);
|
||||
b.Enabled = _view.CanPaste();
|
||||
|
||||
cm.AddButton("Rename", () => Rename(item));
|
||||
if (isFolder && folder.Node is MainContentTreeNode)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
cm.AddButton("Rename", () => Rename(item));
|
||||
}
|
||||
|
||||
// Custom options
|
||||
ContextMenuShow?.Invoke(cm, item);
|
||||
|
||||
@@ -391,7 +391,7 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
|
||||
Editor.Log("Plugin project has been cloned.");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Start git submodule clone
|
||||
@@ -412,24 +412,28 @@ namespace FlaxEditor.Windows
|
||||
}
|
||||
|
||||
// Find project config file. Could be different then what the user named the folder.
|
||||
var files = Directory.GetFiles(clonePath);
|
||||
string pluginProjectName = "";
|
||||
foreach (var file in files)
|
||||
foreach (var file in Directory.GetFiles(clonePath))
|
||||
{
|
||||
if (file.Contains(".flaxproj", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
pluginProjectName = Path.GetFileNameWithoutExtension(file);
|
||||
Debug.Log(pluginProjectName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(pluginProjectName))
|
||||
Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually.");
|
||||
else
|
||||
{
|
||||
await AddReferenceToProject(pluginName, pluginProjectName);
|
||||
MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
|
||||
Editor.LogError("Failed to find plugin project file to add to Project config. Please add manually.");
|
||||
return;
|
||||
}
|
||||
|
||||
await AddModuleReferencesInGameModule(clonePath);
|
||||
await AddReferenceToProject(pluginName, pluginProjectName);
|
||||
|
||||
if (Editor.Options.Options.SourceCode.AutoGenerateScriptsProjectFiles)
|
||||
Editor.ProgressReporting.GenerateScriptsProjectFiles.RunAsync();
|
||||
|
||||
MessageBox.Show($"{pluginName} has been successfully cloned. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
|
||||
}
|
||||
|
||||
private void OnAddButtonClicked()
|
||||
@@ -749,6 +753,37 @@ namespace FlaxEditor.Windows
|
||||
MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
|
||||
}
|
||||
|
||||
private async Task AddModuleReferencesInGameModule(string pluginFolderPath)
|
||||
{
|
||||
// Common game build script location
|
||||
var gameScript = Path.Combine(Globals.ProjectFolder, "Source/Game/Game.Build.cs");
|
||||
if (File.Exists(gameScript))
|
||||
{
|
||||
var gameScriptContents = await File.ReadAllTextAsync(gameScript);
|
||||
var insertLocation = gameScriptContents.IndexOf("base.Setup(options);", StringComparison.Ordinal);
|
||||
if (insertLocation != -1)
|
||||
{
|
||||
insertLocation += 20;
|
||||
var modifiedAny = false;
|
||||
|
||||
// Find all code modules in a plugin to auto-reference them in game build script
|
||||
foreach (var subDir in Directory.GetDirectories(Path.Combine(pluginFolderPath, "Source")))
|
||||
{
|
||||
var pluginModuleName = Path.GetFileName(subDir);
|
||||
var pluginModuleScriptPath = Path.Combine(subDir, pluginModuleName + ".Build.cs");
|
||||
if (File.Exists(pluginModuleScriptPath))
|
||||
{
|
||||
gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");");
|
||||
modifiedAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modifiedAny)
|
||||
await File.WriteAllTextAsync(gameScript, gameScriptContents, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddReferenceToProject(string pluginFolderName, string pluginName)
|
||||
{
|
||||
// Project flax config file
|
||||
|
||||
@@ -624,8 +624,10 @@ void BehaviorTreeLoopDecorator::PostUpdate(const BehaviorUpdateContext& context,
|
||||
if (result == BehaviorUpdateResult::Success)
|
||||
{
|
||||
auto state = GetState<State>(context.Memory);
|
||||
state->Loops--;
|
||||
if (state->Loops > 0)
|
||||
if (!InfiniteLoop)
|
||||
state->Loops--;
|
||||
|
||||
if (state->Loops > 0 || InfiniteLoop)
|
||||
{
|
||||
// Keep running in a loop but reset node's state (preserve self state)
|
||||
result = BehaviorUpdateResult::Running;
|
||||
|
||||
@@ -305,12 +305,16 @@ API_CLASS(Sealed) class FLAXENGINE_API BehaviorTreeLoopDecorator : public Behavi
|
||||
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(BehaviorTreeLoopDecorator, BehaviorTreeDecorator);
|
||||
API_AUTO_SERIALIZATION();
|
||||
|
||||
// Amount of times to execute the node. Unused if LoopCountSelector is used.
|
||||
API_FIELD(Attributes="EditorOrder(10), Limit(0)")
|
||||
// Is the loop infinite (until failed)?
|
||||
API_FIELD(Attributes = "EditorOrder(10)")
|
||||
bool InfiniteLoop = false;
|
||||
|
||||
// Amount of times to execute the node. Unused if LoopCountSelector is used or if InfiniteLoop is used.
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(0), VisibleIf(nameof(InfiniteLoop), true)")
|
||||
int32 LoopCount = 3;
|
||||
|
||||
// Amount of times to execute the node from behavior's knowledge (blackboard, goal or sensor). If set, overrides LoopCount.
|
||||
API_FIELD(Attributes="EditorOrder(20)")
|
||||
// Amount of times to execute the node from behavior's knowledge (blackboard, goal or sensor). If set, overrides LoopCount. Unused if InfiniteLoop is used.
|
||||
API_FIELD(Attributes="EditorOrder(30), VisibleIf(nameof(InfiniteLoop), true)")
|
||||
BehaviorKnowledgeSelector<int32> LoopCountSelector;
|
||||
|
||||
public:
|
||||
|
||||
@@ -17,6 +17,11 @@ API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public SerializableScriptin
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE(AnimEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the event can be executed in async from a thread that updates the animated model. Otherwise, event execution will be delayed until the sync point of the animated model and called from the main thread. Async events need to precisely handle data access, especially when it comes to editing scene objects with multi-threading.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="HideInEditor, NoSerialize") bool Async = false;
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Event display color in the Editor.
|
||||
|
||||
@@ -98,7 +98,6 @@ public:
|
||||
/// </summary>
|
||||
struct AnimationData
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// The duration of the animation (in frames).
|
||||
/// </summary>
|
||||
@@ -114,6 +113,11 @@ public:
|
||||
/// </summary>
|
||||
bool EnableRootMotion = false;
|
||||
|
||||
/// <summary>
|
||||
/// The animation name.
|
||||
/// </summary>
|
||||
String Name;
|
||||
|
||||
/// <summary>
|
||||
/// The custom node name to be used as a root motion source. If not specified the actual root node will be used.
|
||||
/// </summary>
|
||||
@@ -131,14 +135,14 @@ public:
|
||||
FORCE_INLINE float GetLength() const
|
||||
{
|
||||
#if BUILD_DEBUG
|
||||
ASSERT(FramesPerSecond != 0);
|
||||
ASSERT(FramesPerSecond > ZeroTolerance);
|
||||
#endif
|
||||
return static_cast<float>(Duration / FramesPerSecond);
|
||||
}
|
||||
|
||||
uint64 GetMemoryUsage() const
|
||||
{
|
||||
uint64 result = RootNodeName.Length() * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
|
||||
uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
|
||||
for (const auto& e : Channels)
|
||||
result += e.GetMemoryUsage();
|
||||
return result;
|
||||
@@ -151,9 +155,7 @@ public:
|
||||
{
|
||||
int32 result = 0;
|
||||
for (int32 i = 0; i < Channels.Count(); i++)
|
||||
{
|
||||
result += Channels[i].GetKeyframesCount();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -166,6 +168,7 @@ public:
|
||||
::Swap(Duration, other.Duration);
|
||||
::Swap(FramesPerSecond, other.FramesPerSecond);
|
||||
::Swap(EnableRootMotion, other.EnableRootMotion);
|
||||
::Swap(Name, other.Name);
|
||||
::Swap(RootNodeName, other.RootNodeName);
|
||||
Channels.Swap(other.Channels);
|
||||
}
|
||||
@@ -175,6 +178,7 @@ public:
|
||||
/// </summary>
|
||||
void Dispose()
|
||||
{
|
||||
Name.Clear();
|
||||
Duration = 0.0;
|
||||
FramesPerSecond = 0.0;
|
||||
RootNodeName.Clear();
|
||||
|
||||
@@ -146,6 +146,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
|
||||
auto animatedModel = AnimationManagerInstance.UpdateList[index];
|
||||
if (CanUpdateModel(animatedModel))
|
||||
{
|
||||
animatedModel->GraphInstance.InvokeAnimEvents();
|
||||
animatedModel->OnAnimationUpdated_Sync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,22 +38,16 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32
|
||||
|
||||
void AnimGraphInstanceData::Clear()
|
||||
{
|
||||
Version = 0;
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
RootTransform = Transform::Identity;
|
||||
RootMotion = Transform::Identity;
|
||||
ClearState();
|
||||
Parameters.Resize(0);
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
Slots.Resize(0);
|
||||
for (const auto& e : Events)
|
||||
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
|
||||
Events.Resize(0);
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::ClearState()
|
||||
{
|
||||
for (const auto& e : ActiveEvents)
|
||||
OutgoingEvents.Add(e.End((AnimatedModel*)Object));
|
||||
ActiveEvents.Clear();
|
||||
InvokeAnimEvents();
|
||||
Version = 0;
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
@@ -62,9 +56,6 @@ void AnimGraphInstanceData::ClearState()
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
Slots.Clear();
|
||||
for (const auto& e : Events)
|
||||
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f);
|
||||
Events.Clear();
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::Invalidate()
|
||||
@@ -73,6 +64,43 @@ void AnimGraphInstanceData::Invalidate()
|
||||
CurrentFrame = 0;
|
||||
}
|
||||
|
||||
void AnimGraphInstanceData::InvokeAnimEvents()
|
||||
{
|
||||
const bool all = IsInMainThread();
|
||||
for (int32 i = 0; i < OutgoingEvents.Count(); i++)
|
||||
{
|
||||
const OutgoingEvent e = OutgoingEvents[i];
|
||||
if (all || e.Instance->Async)
|
||||
{
|
||||
OutgoingEvents.RemoveAtKeepOrder(i);
|
||||
switch (e.Type)
|
||||
{
|
||||
case OutgoingEvent::OnEvent:
|
||||
e.Instance->OnEvent(e.Actor, e.Anim, e.Time, e.DeltaTime);
|
||||
break;
|
||||
case OutgoingEvent::OnBegin:
|
||||
((AnimContinuousEvent*)e.Instance)->OnBegin(e.Actor, e.Anim, e.Time, e.DeltaTime);
|
||||
break;
|
||||
case OutgoingEvent::OnEnd:
|
||||
((AnimContinuousEvent*)e.Instance)->OnEnd(e.Actor, e.Anim, e.Time, e.DeltaTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(AnimatedModel* actor) const
|
||||
{
|
||||
OutgoingEvent out;
|
||||
out.Instance = Instance;
|
||||
out.Actor = actor;
|
||||
out.Anim = Anim;
|
||||
out.Time = 0.0f;
|
||||
out.DeltaTime = 0.0f;
|
||||
out.Type = OutgoingEvent::OnEnd;
|
||||
return out;
|
||||
}
|
||||
|
||||
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
|
||||
{
|
||||
auto& context = AnimGraphExecutor::Context.Get();
|
||||
@@ -208,7 +236,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
// Initialize buckets
|
||||
ResetBuckets(context, &_graph);
|
||||
}
|
||||
for (auto& e : data.Events)
|
||||
for (auto& e : data.ActiveEvents)
|
||||
e.Hit = false;
|
||||
|
||||
// Init empty nodes data
|
||||
@@ -240,16 +268,17 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
if (animResult == nullptr)
|
||||
animResult = GetEmptyNodes();
|
||||
}
|
||||
if (data.Events.Count() != 0)
|
||||
if (data.ActiveEvents.Count() != 0)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Events");
|
||||
for (int32 i = data.Events.Count() - 1; i >= 0; i--)
|
||||
for (int32 i = data.ActiveEvents.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
const auto& e = data.Events[i];
|
||||
const auto& e = data.ActiveEvents[i];
|
||||
if (!e.Hit)
|
||||
{
|
||||
((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)context.Data->Object, e.Anim, 0.0f, 0.0f);
|
||||
data.Events.RemoveAt(i);
|
||||
// Remove active event that was not hit during this frame (eg. animation using it was not used in blending)
|
||||
data.OutgoingEvents.Add(e.End((AnimatedModel*)context.Data->Object));
|
||||
data.ActiveEvents.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +313,6 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i);
|
||||
targetNodes[i] = node;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +347,9 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
data.RootMotion = animResult->RootMotion;
|
||||
}
|
||||
|
||||
// Invoke any async anim events
|
||||
context.Data->InvokeAnimEvents();
|
||||
|
||||
// Cleanup
|
||||
context.Data = nullptr;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ class AnimSubGraph;
|
||||
class AnimGraphBase;
|
||||
class AnimGraphNode;
|
||||
class AnimGraphExecutor;
|
||||
class AnimatedModel;
|
||||
class AnimEvent;
|
||||
class AnimContinuousEvent;
|
||||
class SkinnedModel;
|
||||
class SkeletonData;
|
||||
|
||||
@@ -349,16 +352,40 @@ public:
|
||||
/// </summary>
|
||||
void Invalidate();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes any outgoing AnimEvent and AnimContinuousEvent collected during the last animation update. When called from non-main thread only Async events will be invoked.
|
||||
/// </summary>
|
||||
void InvokeAnimEvents();
|
||||
|
||||
private:
|
||||
struct Event
|
||||
struct OutgoingEvent
|
||||
{
|
||||
enum Types
|
||||
{
|
||||
OnEvent,
|
||||
OnBegin,
|
||||
OnEnd,
|
||||
};
|
||||
|
||||
AnimEvent* Instance;
|
||||
AnimatedModel* Actor;
|
||||
Animation* Anim;
|
||||
float Time, DeltaTime;
|
||||
Types Type;
|
||||
};
|
||||
|
||||
struct ActiveEvent
|
||||
{
|
||||
AnimContinuousEvent* Instance;
|
||||
Animation* Anim;
|
||||
AnimGraphNode* Node;
|
||||
bool Hit;
|
||||
|
||||
OutgoingEvent End(AnimatedModel* actor) const;
|
||||
};
|
||||
|
||||
Array<Event, InlinedAllocation<8>> Events;
|
||||
Array<ActiveEvent, InlinedAllocation<8>> ActiveEvents;
|
||||
Array<OutgoingEvent, InlinedAllocation<8>> OutgoingEvents;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -441,7 +468,7 @@ public:
|
||||
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
|
||||
/// </summary>
|
||||
const static uint16 InvalidTransitionIndex = MAX_uint16;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
|
||||
/// </summary>
|
||||
|
||||
@@ -100,48 +100,50 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float
|
||||
if (!k.Value.Instance)
|
||||
continue;
|
||||
const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f;
|
||||
#define ADD_OUTGOING_EVENT(type) context.Data->OutgoingEvents.Add({ k.Value.Instance, (AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime, AnimGraphInstanceData::OutgoingEvent::type })
|
||||
if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration)
|
||||
{
|
||||
int32 stateIndex = -1;
|
||||
if (duration > 1)
|
||||
{
|
||||
// Begin for continuous event
|
||||
for (stateIndex = 0; stateIndex < context.Data->Events.Count(); stateIndex++)
|
||||
for (stateIndex = 0; stateIndex < context.Data->ActiveEvents.Count(); stateIndex++)
|
||||
{
|
||||
const auto& e = context.Data->Events[stateIndex];
|
||||
const auto& e = context.Data->ActiveEvents[stateIndex];
|
||||
if (e.Instance == k.Value.Instance && e.Node == node)
|
||||
break;
|
||||
}
|
||||
if (stateIndex == context.Data->Events.Count())
|
||||
if (stateIndex == context.Data->ActiveEvents.Count())
|
||||
{
|
||||
auto& e = context.Data->Events.AddOne();
|
||||
e.Instance = k.Value.Instance;
|
||||
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
|
||||
auto& e = context.Data->ActiveEvents.AddOne();
|
||||
e.Instance = (AnimContinuousEvent*)k.Value.Instance;
|
||||
e.Anim = anim;
|
||||
e.Node = node;
|
||||
ASSERT(k.Value.Instance->Is<AnimContinuousEvent>());
|
||||
((AnimContinuousEvent*)k.Value.Instance)->OnBegin((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||
ADD_OUTGOING_EVENT(OnBegin);
|
||||
}
|
||||
}
|
||||
|
||||
// Event
|
||||
k.Value.Instance->OnEvent((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||
ADD_OUTGOING_EVENT(OnEvent);
|
||||
if (stateIndex != -1)
|
||||
context.Data->Events[stateIndex].Hit = true;
|
||||
context.Data->ActiveEvents[stateIndex].Hit = true;
|
||||
}
|
||||
else if (duration > 1)
|
||||
{
|
||||
// End for continuous event
|
||||
for (int32 i = 0; i < context.Data->Events.Count(); i++)
|
||||
for (int32 i = 0; i < context.Data->ActiveEvents.Count(); i++)
|
||||
{
|
||||
const auto& e = context.Data->Events[i];
|
||||
const auto& e = context.Data->ActiveEvents[i];
|
||||
if (e.Instance == k.Value.Instance && e.Node == node)
|
||||
{
|
||||
((AnimContinuousEvent*)k.Value.Instance)->OnEnd((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime);
|
||||
context.Data->Events.RemoveAt(i);
|
||||
ADD_OUTGOING_EVENT(OnEnd);
|
||||
context.Data->ActiveEvents.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#undef ADD_OUTGOING_EVENT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1070,15 +1072,33 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
const auto nodes = node->GetNodes(this);
|
||||
const auto nodesA = static_cast<AnimGraphImpulse*>(valueA.AsPointer);
|
||||
const auto nodesB = static_cast<AnimGraphImpulse*>(valueB.AsPointer);
|
||||
const auto& baseNodes = _graph.BaseModel.Get()->GetNodes();
|
||||
Transform t, tA, tB;
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
tA = nodesA->Nodes[i];
|
||||
tB = nodesB->Nodes[i];
|
||||
t.Translation = tA.Translation + tB.Translation;
|
||||
t.Orientation = tA.Orientation * tB.Orientation;
|
||||
t.Scale = tA.Scale * tB.Scale;
|
||||
const auto& baseNode = baseNodes[i];
|
||||
|
||||
t.Translation = tA.Translation + (tB.Translation - baseNode.LocalTransform.Translation) * alpha;
|
||||
|
||||
//auto baseOrientation = tA.Orientation;
|
||||
//Quaternion additiveOrientation = alpha * (tB.Orientation - baseNode.LocalTransform.Orientation);
|
||||
//t.Orientation = baseOrientation + additiveOrientation;
|
||||
auto m1 = Matrix::RotationQuaternion(tA.Orientation);
|
||||
auto m2 = Matrix::RotationQuaternion(alpha * tB.Orientation);
|
||||
auto m3 = Matrix::RotationQuaternion(alpha * baseNode.LocalTransform.Orientation);
|
||||
Matrix m4;
|
||||
Matrix::Subtract(m2, m3, m4);
|
||||
Matrix m5;
|
||||
Matrix::Add(m1, m4, m5);
|
||||
|
||||
t.SetRotation(m5);
|
||||
t.Orientation.Normalize();
|
||||
|
||||
t.Scale = tA.Scale * tB.Scale;
|
||||
|
||||
//nodes->Nodes[i] = t;
|
||||
Transform::Lerp(tA, t, alpha, nodes->Nodes[i]);
|
||||
}
|
||||
Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#define CHECK_INVALID_BUFFER(model, buffer) \
|
||||
if (buffer->IsValidFor(model) == false) \
|
||||
{ \
|
||||
LOG(Warning, "Invalid Model Instance Buffer size {0} for Model {1}. It should be {2}. Manual update to proper size.", buffer->Count(), model->ToString(), model->MaterialSlots.Count()); \
|
||||
buffer->Setup(model); \
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#define CHECK_INVALID_BUFFER(model, buffer) \
|
||||
if (buffer->IsValidFor(model) == false) \
|
||||
{ \
|
||||
LOG(Warning, "Invalid Skinned Model Instance Buffer size {0} for Skinned Model {1}. It should be {2}. Manual update to proper size.", buffer->Count(), model->ToString(), model->MaterialSlots.Count()); \
|
||||
buffer->Setup(model); \
|
||||
}
|
||||
|
||||
|
||||
@@ -562,6 +562,7 @@ bool FlaxStorage::Reload()
|
||||
{
|
||||
if (!IsLoaded())
|
||||
return false;
|
||||
PROFILE_CPU();
|
||||
|
||||
OnReloading(this);
|
||||
|
||||
@@ -728,7 +729,11 @@ bool FlaxStorage::ChangeAssetID(Entry& e, const Guid& newId)
|
||||
}
|
||||
|
||||
// Close file
|
||||
CloseFileHandles();
|
||||
if (CloseFileHandles())
|
||||
{
|
||||
LOG(Error, "Cannot close file access for '{}'", _path);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Change ID
|
||||
// TODO: here we could extend it and load assets from the storage and call asset ID change event to change references
|
||||
@@ -776,6 +781,8 @@ FlaxChunk* FlaxStorage::AllocateChunk()
|
||||
|
||||
bool FlaxStorage::Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode, const CustomData* customData)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ZoneText(*path, path.Length());
|
||||
LOG(Info, "Creating package at \'{0}\'. Silent Mode: {1}", path, silentMode);
|
||||
|
||||
// Prepare to have access to the file
|
||||
@@ -1296,7 +1303,7 @@ FileReadStream* FlaxStorage::OpenFile()
|
||||
return stream;
|
||||
}
|
||||
|
||||
void FlaxStorage::CloseFileHandles()
|
||||
bool FlaxStorage::CloseFileHandles()
|
||||
{
|
||||
// Note: this is usually called by the content manager when this file is not used or on exit
|
||||
// In those situations all the async tasks using this storage should be cancelled externally
|
||||
@@ -1323,10 +1330,12 @@ void FlaxStorage::CloseFileHandles()
|
||||
waitTime = 100;
|
||||
while (Platform::AtomicRead(&_chunksLock) != 0 && waitTime-- > 0)
|
||||
Platform::Sleep(1);
|
||||
ASSERT(_chunksLock == 0);
|
||||
if (Platform::AtomicRead(&_chunksLock) != 0)
|
||||
return true; // Failed, someone is still accessing the file
|
||||
|
||||
// Close file handles (from all threads)
|
||||
_file.DeleteAll();
|
||||
return false;
|
||||
}
|
||||
|
||||
void FlaxStorage::Dispose()
|
||||
@@ -1335,7 +1344,10 @@ void FlaxStorage::Dispose()
|
||||
return;
|
||||
|
||||
// Close file
|
||||
CloseFileHandles();
|
||||
if (CloseFileHandles())
|
||||
{
|
||||
LOG(Error, "Cannot close file access for '{}'", _path);
|
||||
}
|
||||
|
||||
// Release data
|
||||
_chunks.ClearDelete();
|
||||
|
||||
@@ -405,7 +405,7 @@ public:
|
||||
/// <summary>
|
||||
/// Closes the file handles (it can be modified from the outside).
|
||||
/// </summary>
|
||||
void CloseFileHandles();
|
||||
bool CloseFileHandles();
|
||||
|
||||
/// <summary>
|
||||
/// Releases storage resources and closes handle to the file.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#include "Engine/Engine/Globals.h"
|
||||
#include "ImportTexture.h"
|
||||
#include "ImportModelFile.h"
|
||||
#include "ImportModel.h"
|
||||
#include "ImportAudio.h"
|
||||
#include "ImportShader.h"
|
||||
#include "ImportFont.h"
|
||||
@@ -165,20 +165,7 @@ bool CreateAssetContext::AllocateChunk(int32 index)
|
||||
void CreateAssetContext::AddMeta(JsonWriter& writer) const
|
||||
{
|
||||
writer.JKEY("ImportPath");
|
||||
if (AssetsImportingManager::UseImportPathRelative && !FileSystem::IsRelative(InputPath)
|
||||
#if PLATFORM_WINDOWS
|
||||
// Import path from other drive should be stored as absolute on Windows to prevent issues
|
||||
&& InputPath.Length() > 2 && Globals::ProjectFolder.Length() > 2 && InputPath[0] == Globals::ProjectFolder[0]
|
||||
#endif
|
||||
)
|
||||
{
|
||||
const String relativePath = FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, InputPath);
|
||||
writer.String(relativePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.String(InputPath);
|
||||
}
|
||||
writer.String(AssetsImportingManager::GetImportPath(InputPath));
|
||||
writer.JKEY("ImportUsername");
|
||||
writer.String(Platform::GetUserName());
|
||||
}
|
||||
@@ -189,7 +176,12 @@ void CreateAssetContext::ApplyChanges()
|
||||
auto storage = ContentStorageManager::TryGetStorage(TargetAssetPath);
|
||||
if (storage && storage->IsLoaded())
|
||||
{
|
||||
storage->CloseFileHandles();
|
||||
if (storage->CloseFileHandles())
|
||||
{
|
||||
LOG(Error, "Cannot close file access for '{}'", TargetAssetPath);
|
||||
_applyChangesResult = CreateAssetResult::CannotSaveFile;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Move file
|
||||
@@ -304,8 +296,24 @@ bool AssetsImportingManager::ImportIfEdited(const StringView& inputPath, const S
|
||||
return false;
|
||||
}
|
||||
|
||||
String AssetsImportingManager::GetImportPath(const String& path)
|
||||
{
|
||||
if (UseImportPathRelative && !FileSystem::IsRelative(path)
|
||||
#if PLATFORM_WINDOWS
|
||||
// Import path from other drive should be stored as absolute on Windows to prevent issues
|
||||
&& path.Length() > 2 && Globals::ProjectFolder.Length() > 2 && path[0] == Globals::ProjectFolder[0]
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return FileSystem::ConvertAbsolutePathToRelative(Globals::ProjectFolder, path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAssetContext&)>& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ZoneText(*outputPath, outputPath.Length());
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
// Pick ID if not specified
|
||||
@@ -369,7 +377,7 @@ bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAsset
|
||||
if (result == CreateAssetResult::Ok)
|
||||
{
|
||||
// Register asset
|
||||
Content::GetRegistry()->RegisterAsset(context.Data.Header, outputPath);
|
||||
Content::GetRegistry()->RegisterAsset(context.Data.Header, context.TargetAssetPath);
|
||||
|
||||
// Done
|
||||
const auto endTime = Platform::GetTimeSeconds();
|
||||
@@ -380,7 +388,7 @@ bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAsset
|
||||
// Do nothing
|
||||
return true;
|
||||
}
|
||||
else
|
||||
else if (result != CreateAssetResult::Skip)
|
||||
{
|
||||
LOG(Error, "Cannot import file '{0}'! Result: {1}", inputPath, ::ToString(result));
|
||||
return true;
|
||||
@@ -425,37 +433,37 @@ bool AssetsImportingManagerService::Init()
|
||||
{ TEXT("otf"), ASSET_FILES_EXTENSION, ImportFont::Import },
|
||||
|
||||
// Models
|
||||
{ TEXT("obj"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("x"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("dae"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("glb"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("obj"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("x"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("dae"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("glb"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
|
||||
// gettext PO files
|
||||
{ TEXT("po"), TEXT("json"), CreateJson::ImportPo },
|
||||
|
||||
// Models (untested formats - may fail :/)
|
||||
{ TEXT("blend"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("ase"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("ply"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("nff"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("smd"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("vta"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("md2"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("md3"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("ac"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("stl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("lws"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
|
||||
{ TEXT("blend"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("ase"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("ply"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("nff"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("smd"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("vta"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("md2"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("md3"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("ac"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("stl"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("lws"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
{ TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModel::Import },
|
||||
};
|
||||
AssetsImportingManager::Importers.Add(InBuildImporters, ARRAY_COUNT(InBuildImporters));
|
||||
|
||||
@@ -473,7 +481,7 @@ bool AssetsImportingManagerService::Init()
|
||||
{ AssetsImportingManager::CreateMaterialInstanceTag, CreateMaterialInstance::Create },
|
||||
|
||||
// Models
|
||||
{ AssetsImportingManager::CreateModelTag, ImportModelFile::Create },
|
||||
{ AssetsImportingManager::CreateModelTag, ImportModel::Create },
|
||||
|
||||
// Other
|
||||
{ AssetsImportingManager::CreateRawDataTag, CreateRawData::Create },
|
||||
|
||||
@@ -236,6 +236,9 @@ public:
|
||||
return ImportIfEdited(inputPath, outputPath, id, arg);
|
||||
}
|
||||
|
||||
// Converts source files path into the relative format if enabled by the project settings. Result path can be stored in asset for reimports.
|
||||
static String GetImportPath(const String& path);
|
||||
|
||||
private:
|
||||
static bool Create(const CreateAssetFunction& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg);
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ bool CreateJson::Create(const StringView& path, const StringAnsiView& data, cons
|
||||
{
|
||||
if (FileSystem::CreateDirectory(directory))
|
||||
{
|
||||
LOG(Warning, "Failed to create directory");
|
||||
LOG(Warning, "Failed to create directory '{}'", directory);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
743
Source/Engine/ContentImporters/ImportModel.cpp
Normal file
743
Source/Engine/ContentImporters/ImportModel.cpp
Normal file
@@ -0,0 +1,743 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ImportModel.h"
|
||||
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Core/Collections/ArrayExtensions.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
#include "Engine/Content/Storage/ContentStorageManager.h"
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Level/Actors/EmptyActor.h"
|
||||
#include "Engine/Level/Actors/StaticModel.h"
|
||||
#include "Engine/Level/Prefabs/Prefab.h"
|
||||
#include "Engine/Level/Prefabs/PrefabManager.h"
|
||||
#include "Engine/Level/Scripts/ModelPrefab.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Utilities/RectPack.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "AssetsImportingManager.h"
|
||||
|
||||
bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
|
||||
{
|
||||
if (FileSystem::FileExists(path))
|
||||
{
|
||||
// Try to load asset file and asset info
|
||||
auto tmpFile = ContentStorageManager::GetStorage(path);
|
||||
AssetInitData data;
|
||||
if (tmpFile
|
||||
&& tmpFile->GetEntriesCount() == 1
|
||||
&& (
|
||||
(tmpFile->GetEntry(0).TypeName == Model::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 4)
|
||||
||
|
||||
(tmpFile->GetEntry(0).TypeName == SkinnedModel::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
|
||||
||
|
||||
(tmpFile->GetEntry(0).TypeName == Animation::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
|
||||
))
|
||||
{
|
||||
// Check import meta
|
||||
rapidjson_flax::Document metadata;
|
||||
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
|
||||
if (metadata.HasParseError() == false)
|
||||
{
|
||||
options.Deserialize(metadata, nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct PrefabObject
|
||||
{
|
||||
int32 NodeIndex;
|
||||
String Name;
|
||||
String AssetPath;
|
||||
};
|
||||
|
||||
void RepackMeshLightmapUVs(ModelData& data)
|
||||
{
|
||||
// Use weight-based coordinates space placement and rect-pack to allocate more space for bigger meshes in the model lightmap chart
|
||||
int32 lodIndex = 0;
|
||||
auto& lod = data.LODs[lodIndex];
|
||||
|
||||
// Build list of meshes with their area
|
||||
struct LightmapUVsPack : RectPack<LightmapUVsPack, float>
|
||||
{
|
||||
LightmapUVsPack(float x, float y, float width, float height)
|
||||
: RectPack<LightmapUVsPack, float>(x, y, width, height)
|
||||
{
|
||||
}
|
||||
|
||||
void OnInsert()
|
||||
{
|
||||
}
|
||||
};
|
||||
struct MeshEntry
|
||||
{
|
||||
MeshData* Mesh;
|
||||
float Area;
|
||||
float Size;
|
||||
LightmapUVsPack* Slot;
|
||||
};
|
||||
Array<MeshEntry> entries;
|
||||
entries.Resize(lod.Meshes.Count());
|
||||
float areaSum = 0;
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
auto& entry = entries[meshIndex];
|
||||
entry.Mesh = lod.Meshes[meshIndex];
|
||||
entry.Area = entry.Mesh->CalculateTrianglesArea();
|
||||
entry.Size = Math::Sqrt(entry.Area);
|
||||
areaSum += entry.Area;
|
||||
}
|
||||
|
||||
if (areaSum > ZeroTolerance)
|
||||
{
|
||||
// Pack all surfaces into atlas
|
||||
float atlasSize = Math::Sqrt(areaSum) * 1.02f;
|
||||
int32 triesLeft = 10;
|
||||
while (triesLeft--)
|
||||
{
|
||||
bool failed = false;
|
||||
const float chartsPadding = (4.0f / 256.0f) * atlasSize;
|
||||
LightmapUVsPack root(chartsPadding, chartsPadding, atlasSize - chartsPadding, atlasSize - chartsPadding);
|
||||
for (auto& entry : entries)
|
||||
{
|
||||
entry.Slot = root.Insert(entry.Size, entry.Size, chartsPadding);
|
||||
if (entry.Slot == nullptr)
|
||||
{
|
||||
// Failed to insert surface, increase atlas size and try again
|
||||
atlasSize *= 1.5f;
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failed)
|
||||
{
|
||||
// Transform meshes lightmap UVs into the slots in the whole atlas
|
||||
const float atlasSizeInv = 1.0f / atlasSize;
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv);
|
||||
Float2 uvScale((entry.Slot->Width - chartsPadding) * atlasSizeInv, (entry.Slot->Height - chartsPadding) * atlasSizeInv);
|
||||
// TODO: SIMD
|
||||
for (auto& uv : entry.Mesh->LightmapUVs)
|
||||
{
|
||||
uv = uv * uvScale + uvOffset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
|
||||
{
|
||||
// Skip if file is missing
|
||||
if (!FileSystem::FileExists(context.TargetAssetPath))
|
||||
return;
|
||||
|
||||
// Try to load asset that gets reimported
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset == nullptr)
|
||||
return;
|
||||
if (asset->WaitForLoaded())
|
||||
return;
|
||||
|
||||
// Get model object
|
||||
ModelBase* model = nullptr;
|
||||
if (asset.Get()->GetTypeName() == Model::TypeName)
|
||||
{
|
||||
model = ((Model*)asset.Get());
|
||||
}
|
||||
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
|
||||
{
|
||||
model = ((SkinnedModel*)asset.Get());
|
||||
}
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
// Peek materials
|
||||
for (int32 i = 0; i < modelData.Materials.Count(); i++)
|
||||
{
|
||||
auto& dstSlot = modelData.Materials[i];
|
||||
|
||||
if (model->MaterialSlots.Count() > i)
|
||||
{
|
||||
auto& srcSlot = model->MaterialSlots[i];
|
||||
|
||||
dstSlot.Name = srcSlot.Name;
|
||||
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
|
||||
dstSlot.AssetID = srcSlot.Material.GetID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetupMaterialSlots(ModelData& data, const Array<MaterialSlotEntry>& materials)
|
||||
{
|
||||
Array<int32> materialSlotsTable;
|
||||
materialSlotsTable.Resize(materials.Count());
|
||||
materialSlotsTable.SetAll(-1);
|
||||
for (auto& lod : data.LODs)
|
||||
{
|
||||
for (MeshData* mesh : lod.Meshes)
|
||||
{
|
||||
int32 newSlotIndex = materialSlotsTable[mesh->MaterialSlotIndex];
|
||||
if (newSlotIndex == -1)
|
||||
{
|
||||
newSlotIndex = data.Materials.Count();
|
||||
data.Materials.AddOne() = materials[mesh->MaterialSlotIndex];
|
||||
}
|
||||
mesh->MaterialSlotIndex = newSlotIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SortMeshGroups(IGrouping<StringView, MeshData*> const& i1, IGrouping<StringView, MeshData*> const& i2)
|
||||
{
|
||||
return i1.GetKey().Compare(i2.GetKey()) < 0;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
{
|
||||
// Get import options
|
||||
Options options;
|
||||
if (context.CustomArg != nullptr)
|
||||
{
|
||||
// Copy import options from argument
|
||||
options = *static_cast<Options*>(context.CustomArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore the previous settings or use default ones
|
||||
if (!TryGetImportOptions(context.TargetAssetPath, options))
|
||||
{
|
||||
LOG(Warning, "Missing model import options. Using default values.");
|
||||
}
|
||||
}
|
||||
|
||||
// Import model file
|
||||
ModelData* data = options.Cached ? options.Cached->Data : nullptr;
|
||||
ModelData dataThis;
|
||||
Array<IGrouping<StringView, MeshData*>>* meshesByNamePtr = options.Cached ? (Array<IGrouping<StringView, MeshData*>>*)options.Cached->MeshesByName : nullptr;
|
||||
Array<IGrouping<StringView, MeshData*>> meshesByNameThis;
|
||||
String autoImportOutput;
|
||||
if (!data)
|
||||
{
|
||||
String errorMsg;
|
||||
autoImportOutput = StringUtils::GetDirectoryName(context.TargetAssetPath);
|
||||
autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
|
||||
if (ModelTool::ImportModel(context.InputPath, dataThis, options, errorMsg, autoImportOutput))
|
||||
{
|
||||
LOG(Error, "Cannot import model file. {0}", errorMsg);
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
data = &dataThis;
|
||||
|
||||
// Group meshes by the name (the same mesh name can be used by multiple meshes that use different materials)
|
||||
if (data->LODs.Count() != 0)
|
||||
{
|
||||
const Function<StringView(MeshData* const&)> f = [](MeshData* const& x) -> StringView
|
||||
{
|
||||
return x->Name;
|
||||
};
|
||||
ArrayExtensions::GroupBy(data->LODs[0].Meshes, f, meshesByNameThis);
|
||||
Sorting::QuickSort(meshesByNameThis.Get(), meshesByNameThis.Count(), &SortMeshGroups);
|
||||
}
|
||||
meshesByNamePtr = &meshesByNameThis;
|
||||
}
|
||||
Array<IGrouping<StringView, MeshData*>>& meshesByName = *meshesByNamePtr;
|
||||
|
||||
// Import objects from file separately
|
||||
ModelTool::Options::CachedData cached = { data, (void*)meshesByNamePtr };
|
||||
Array<PrefabObject> prefabObjects;
|
||||
if (options.Type == ModelTool::ModelType::Prefab)
|
||||
{
|
||||
// Normalize options
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = -1;
|
||||
|
||||
// Import all of the objects recursive but use current model data to skip loading file again
|
||||
options.Cached = &cached;
|
||||
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath)> splitImport = [&context, &autoImportOutput](Options& splitOptions, const StringView& objectName, String& outputPath)
|
||||
{
|
||||
// Recursive importing of the split object
|
||||
String postFix = objectName;
|
||||
const int32 splitPos = postFix.FindLast(TEXT('|'));
|
||||
if (splitPos != -1)
|
||||
postFix = postFix.Substring(splitPos + 1);
|
||||
// TODO: check for name collisions with material/texture assets
|
||||
outputPath = autoImportOutput / String(StringUtils::GetFileNameWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
|
||||
splitOptions.SubAssetFolder = TEXT(" "); // Use the same folder as asset as they all are imported to the subdir for the prefab (see SubAssetFolder usage above)
|
||||
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
|
||||
};
|
||||
auto splitOptions = options;
|
||||
LOG(Info, "Splitting imported {0} meshes", meshesByName.Count());
|
||||
PrefabObject prefabObject;
|
||||
for (int32 groupIndex = 0; groupIndex < meshesByName.Count(); groupIndex++)
|
||||
{
|
||||
auto& group = meshesByName[groupIndex];
|
||||
|
||||
// Cache object options (nested sub-object import removes the meshes)
|
||||
prefabObject.NodeIndex = group.First()->NodeIndex;
|
||||
prefabObject.Name = group.First()->Name;
|
||||
|
||||
splitOptions.Type = ModelTool::ModelType::Model;
|
||||
splitOptions.ObjectIndex = groupIndex;
|
||||
if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath))
|
||||
{
|
||||
prefabObjects.Add(prefabObject);
|
||||
}
|
||||
}
|
||||
LOG(Info, "Splitting imported {0} animations", data->Animations.Count());
|
||||
for (int32 i = 0; i < data->Animations.Count(); i++)
|
||||
{
|
||||
auto& animation = data->Animations[i];
|
||||
splitOptions.Type = ModelTool::ModelType::Animation;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitImport(splitOptions, animation.Name, prefabObject.AssetPath);
|
||||
}
|
||||
}
|
||||
else if (options.SplitObjects)
|
||||
{
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
// Import rest of the objects recursive but use current model data to skip loading file again
|
||||
options.Cached = &cached;
|
||||
Function<bool(Options& splitOptions, const StringView& objectName)> splitImport;
|
||||
splitImport.Bind([&context](Options& splitOptions, const StringView& objectName)
|
||||
{
|
||||
// Recursive importing of the split object
|
||||
String postFix = objectName;
|
||||
const int32 splitPos = postFix.FindLast(TEXT('|'));
|
||||
if (splitPos != -1)
|
||||
postFix = postFix.Substring(splitPos + 1);
|
||||
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
|
||||
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
|
||||
});
|
||||
auto splitOptions = options;
|
||||
switch (options.Type)
|
||||
{
|
||||
case ModelTool::ModelType::Model:
|
||||
case ModelTool::ModelType::SkinnedModel:
|
||||
LOG(Info, "Splitting imported {0} meshes", meshesByName.Count());
|
||||
for (int32 groupIndex = 1; groupIndex < meshesByName.Count(); groupIndex++)
|
||||
{
|
||||
auto& group = meshesByName[groupIndex];
|
||||
splitOptions.ObjectIndex = groupIndex;
|
||||
splitImport(splitOptions, group.GetKey());
|
||||
}
|
||||
break;
|
||||
case ModelTool::ModelType::Animation:
|
||||
LOG(Info, "Splitting imported {0} animations", data->Animations.Count());
|
||||
for (int32 i = 1; i < data->Animations.Count(); i++)
|
||||
{
|
||||
auto& animation = data->Animations[i];
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitImport(splitOptions, animation.Name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// When importing a single object as model asset then select a specific mesh group
|
||||
Array<MeshData*> meshesToDelete;
|
||||
if (options.ObjectIndex >= 0 &&
|
||||
options.ObjectIndex < meshesByName.Count() &&
|
||||
(options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel))
|
||||
{
|
||||
auto& group = meshesByName[options.ObjectIndex];
|
||||
if (&dataThis == data)
|
||||
{
|
||||
// Use meshes only from the the grouping (others will be removed manually)
|
||||
{
|
||||
auto& lod = dataThis.LODs[0];
|
||||
meshesToDelete.Add(lod.Meshes);
|
||||
lod.Meshes.Clear();
|
||||
for (MeshData* mesh : group)
|
||||
{
|
||||
lod.Meshes.Add(mesh);
|
||||
meshesToDelete.Remove(mesh);
|
||||
}
|
||||
}
|
||||
for (int32 lodIndex = 1; lodIndex < dataThis.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = dataThis.LODs[lodIndex];
|
||||
Array<MeshData*> lodMeshes = lod.Meshes;
|
||||
lod.Meshes.Clear();
|
||||
for (MeshData* lodMesh : lodMeshes)
|
||||
{
|
||||
if (lodMesh->Name == group.GetKey())
|
||||
lod.Meshes.Add(lodMesh);
|
||||
else
|
||||
meshesToDelete.Add(lodMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Use only materials references by meshes from the first grouping
|
||||
{
|
||||
auto materials = dataThis.Materials;
|
||||
dataThis.Materials.Clear();
|
||||
SetupMaterialSlots(dataThis, materials);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy data from others data
|
||||
dataThis.Skeleton = data->Skeleton;
|
||||
dataThis.Nodes = data->Nodes;
|
||||
|
||||
// Move meshes from this group (including any LODs of them)
|
||||
{
|
||||
auto& lod = dataThis.LODs.AddOne();
|
||||
lod.ScreenSize = data->LODs[0].ScreenSize;
|
||||
lod.Meshes.Add(group);
|
||||
for (MeshData* mesh : group)
|
||||
data->LODs[0].Meshes.Remove(mesh);
|
||||
}
|
||||
for (int32 lodIndex = 1; lodIndex < data->LODs.Count(); lodIndex++)
|
||||
{
|
||||
Array<MeshData*> lodMeshes = data->LODs[lodIndex].Meshes;
|
||||
for (int32 i = lodMeshes.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
MeshData* lodMesh = lodMeshes[i];
|
||||
if (lodMesh->Name == group.GetKey())
|
||||
data->LODs[lodIndex].Meshes.Remove(lodMesh);
|
||||
else
|
||||
lodMeshes.RemoveAtKeepOrder(i);
|
||||
}
|
||||
if (lodMeshes.Count() == 0)
|
||||
break; // No meshes of that name in this LOD so skip further ones
|
||||
auto& lod = dataThis.LODs.AddOne();
|
||||
lod.ScreenSize = data->LODs[lodIndex].ScreenSize;
|
||||
lod.Meshes.Add(lodMeshes);
|
||||
}
|
||||
|
||||
// Copy materials used by the meshes
|
||||
SetupMaterialSlots(dataThis, data->Materials);
|
||||
}
|
||||
data = &dataThis;
|
||||
}
|
||||
|
||||
// Check if restore materials on model reimport
|
||||
if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
|
||||
{
|
||||
TryRestoreMaterials(context, *data);
|
||||
}
|
||||
|
||||
// When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space
|
||||
// Model importer generates UVs in [0-1] space for each mesh so now we need to pack them inside the whole model (only when using multiple meshes)
|
||||
if (options.Type == ModelTool::ModelType::Model && options.LightmapUVsSource == ModelLightmapUVsSource::Generate && data->LODs.HasItems() && data->LODs[0].Meshes.Count() > 1)
|
||||
{
|
||||
RepackMeshLightmapUVs(*data);
|
||||
}
|
||||
|
||||
// Create destination asset type
|
||||
CreateAssetResult result = CreateAssetResult::InvalidTypeID;
|
||||
switch (options.Type)
|
||||
{
|
||||
case ModelTool::ModelType::Model:
|
||||
result = CreateModel(context, *data, &options);
|
||||
break;
|
||||
case ModelTool::ModelType::SkinnedModel:
|
||||
result = CreateSkinnedModel(context, *data, &options);
|
||||
break;
|
||||
case ModelTool::ModelType::Animation:
|
||||
result = CreateAnimation(context, *data, &options);
|
||||
break;
|
||||
case ModelTool::ModelType::Prefab:
|
||||
result = CreatePrefab(context, *data, options, prefabObjects);
|
||||
break;
|
||||
}
|
||||
for (auto mesh : meshesToDelete)
|
||||
Delete(mesh);
|
||||
if (result != CreateAssetResult::Ok)
|
||||
return result;
|
||||
|
||||
// Create json with import context
|
||||
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
|
||||
importOptionsMetaBuffer.Reserve(256);
|
||||
CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer);
|
||||
JsonWriter& importOptionsMeta = importOptionsMetaObj;
|
||||
importOptionsMeta.StartObject();
|
||||
{
|
||||
context.AddMeta(importOptionsMeta);
|
||||
options.Serialize(importOptionsMeta, nullptr);
|
||||
}
|
||||
importOptionsMeta.EndObject();
|
||||
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::Create(CreateAssetContext& context)
|
||||
{
|
||||
ASSERT(context.CustomArg != nullptr);
|
||||
auto& modelData = *(ModelData*)context.CustomArg;
|
||||
|
||||
// Ensure model has any meshes
|
||||
if ((modelData.LODs.IsEmpty() || modelData.LODs[0].Meshes.IsEmpty()))
|
||||
{
|
||||
LOG(Warning, "Models has no valid meshes");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
|
||||
// Auto calculate LODs transition settings
|
||||
modelData.CalculateLODsScreenSizes();
|
||||
|
||||
return CreateModel(context, modelData);
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
IMPORT_SETUP(Model, Model::SerializedVersion);
|
||||
|
||||
// Save model header
|
||||
MemoryWriteStream stream(4096);
|
||||
if (modelData.Pack2ModelHeader(&stream))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
// Pack model LODs data
|
||||
const auto lodCount = modelData.GetLODsCount();
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
|
||||
// Pack meshes
|
||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
if (meshes[meshIndex]->Pack2Model(&stream))
|
||||
{
|
||||
LOG(Warning, "Cannot pack mesh.");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
}
|
||||
|
||||
// Generate SDF
|
||||
if (options && options->GenerateSDF)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath))
|
||||
{
|
||||
if (context.AllocateChunk(15))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
|
||||
|
||||
// Save skinned model header
|
||||
MemoryWriteStream stream(4096);
|
||||
if (modelData.Pack2SkinnedModelHeader(&stream))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
// Pack model LODs data
|
||||
const auto lodCount = modelData.GetLODsCount();
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
|
||||
// Mesh Data Version
|
||||
stream.WriteByte(1);
|
||||
|
||||
// Pack meshes
|
||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
if (meshes[meshIndex]->Pack2SkinnedModel(&stream))
|
||||
{
|
||||
LOG(Warning, "Cannot pack mesh.");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
}
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
IMPORT_SETUP(Animation, Animation::SerializedVersion);
|
||||
|
||||
// Save animation data
|
||||
MemoryWriteStream stream(8182);
|
||||
const int32 animIndex = options && options->ObjectIndex != -1 ? options->ObjectIndex : 0; // Single animation per asset
|
||||
if (modelData.Pack2AnimationHeader(&stream, animIndex))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<PrefabObject>& prefabObjects)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
if (data.Nodes.Count() == 0)
|
||||
return CreateAssetResult::Error;
|
||||
|
||||
// If that prefab already exists then we need to use it as base to preserve object IDs and local changes applied by user
|
||||
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + DEFAULT_PREFAB_EXTENSION_DOT;
|
||||
auto* prefab = FileSystem::FileExists(outputPath) ? Content::Load<Prefab>(outputPath) : nullptr;
|
||||
if (prefab)
|
||||
{
|
||||
// Ensure that prefab has Default Instance so ObjectsCache is valid (used below)
|
||||
prefab->GetDefaultInstance();
|
||||
}
|
||||
|
||||
// Create prefab structure
|
||||
Dictionary<int32, Actor*> nodeToActor;
|
||||
Array<Actor*> nodeActors;
|
||||
Actor* rootActor = nullptr;
|
||||
for (int32 nodeIndex = 0; nodeIndex < data.Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
const auto& node = data.Nodes[nodeIndex];
|
||||
|
||||
// Create actor(s) for this node
|
||||
nodeActors.Clear();
|
||||
for (const PrefabObject& e : prefabObjects)
|
||||
{
|
||||
if (e.NodeIndex == nodeIndex)
|
||||
{
|
||||
auto* actor = New<StaticModel>();
|
||||
actor->SetName(e.Name);
|
||||
if (auto* model = Content::LoadAsync<Model>(e.AssetPath))
|
||||
{
|
||||
actor->Model = model;
|
||||
}
|
||||
nodeActors.Add(actor);
|
||||
}
|
||||
}
|
||||
Actor* nodeActor = nodeActors.Count() == 1 ? nodeActors[0] : New<EmptyActor>();
|
||||
if (nodeActors.Count() > 1)
|
||||
{
|
||||
for (Actor* e : nodeActors)
|
||||
{
|
||||
e->SetParent(nodeActor);
|
||||
}
|
||||
}
|
||||
if (nodeActors.Count() != 1)
|
||||
{
|
||||
// Include default actor to iterate over it properly in code below
|
||||
nodeActors.Add(nodeActor);
|
||||
}
|
||||
|
||||
// Setup node in hierarchy
|
||||
nodeToActor.Add(nodeIndex, nodeActor);
|
||||
nodeActor->SetName(node.Name);
|
||||
nodeActor->SetLocalTransform(node.LocalTransform);
|
||||
if (nodeIndex == 0)
|
||||
{
|
||||
// Special case for root actor to link any unlinked nodes
|
||||
nodeToActor.Add(-1, nodeActor);
|
||||
rootActor = nodeActor;
|
||||
}
|
||||
else
|
||||
{
|
||||
Actor* parentActor;
|
||||
if (nodeToActor.TryGet(node.ParentIndex, parentActor))
|
||||
nodeActor->SetParent(parentActor);
|
||||
}
|
||||
|
||||
// Link with object from prefab (if reimporting)
|
||||
if (prefab)
|
||||
{
|
||||
for (Actor* a : nodeActors)
|
||||
{
|
||||
for (const auto& i : prefab->ObjectsCache)
|
||||
{
|
||||
if (i.Value->GetTypeHandle() != a->GetTypeHandle()) // Type match
|
||||
continue;
|
||||
auto* o = (Actor*)i.Value;
|
||||
if (o->GetName() != a->GetName()) // Name match
|
||||
continue;
|
||||
|
||||
// Mark as this object already exists in prefab so will be preserved when updating it
|
||||
a->LinkPrefab(o->GetPrefabID(), o->GetPrefabObjectID());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_LOW_LAYER(rootActor);
|
||||
{
|
||||
// Add script with import options
|
||||
auto* modelPrefabScript = New<ModelPrefab>();
|
||||
modelPrefabScript->SetParent(rootActor);
|
||||
modelPrefabScript->ImportPath = AssetsImportingManager::GetImportPath(context.InputPath);
|
||||
modelPrefabScript->ImportOptions = options;
|
||||
|
||||
// Link with existing prefab instance
|
||||
if (prefab)
|
||||
{
|
||||
for (const auto& i : prefab->ObjectsCache)
|
||||
{
|
||||
if (i.Value->GetTypeHandle() == modelPrefabScript->GetTypeHandle())
|
||||
{
|
||||
modelPrefabScript->LinkPrefab(i.Value->GetPrefabID(), i.Value->GetPrefabObjectID());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create prefab instead of native asset
|
||||
bool failed;
|
||||
if (prefab)
|
||||
{
|
||||
failed = prefab->ApplyAll(rootActor);
|
||||
}
|
||||
else
|
||||
{
|
||||
failed = PrefabManager::CreatePrefab(rootActor, outputPath, false);
|
||||
}
|
||||
|
||||
// Cleanup objects from memory
|
||||
rootActor->DeleteObjectNow();
|
||||
|
||||
if (failed)
|
||||
return CreateAssetResult::Error;
|
||||
return CreateAssetResult::Skip;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -8,15 +8,10 @@
|
||||
|
||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable caching model import options
|
||||
/// </summary>
|
||||
#define IMPORT_MODEL_CACHE_OPTIONS 1
|
||||
|
||||
/// <summary>
|
||||
/// Importing models utility
|
||||
/// </summary>
|
||||
class ImportModelFile
|
||||
class ImportModel
|
||||
{
|
||||
public:
|
||||
typedef ModelTool::Options Options;
|
||||
@@ -45,9 +40,10 @@ public:
|
||||
static CreateAssetResult Create(CreateAssetContext& context);
|
||||
|
||||
private:
|
||||
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
|
||||
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
|
||||
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
|
||||
static CreateAssetResult CreateModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreateAnimation(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
|
||||
static CreateAssetResult CreatePrefab(CreateAssetContext& context, ModelData& data, const Options& options, const Array<struct PrefabObject>& prefabObjects);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ImportModel.h"
|
||||
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
#include "Engine/Content/Storage/ContentStorageManager.h"
|
||||
#include "Engine/Content/Assets/Animation.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "AssetsImportingManager.h"
|
||||
|
||||
bool ImportModelFile::TryGetImportOptions(const StringView& path, Options& options)
|
||||
{
|
||||
#if IMPORT_MODEL_CACHE_OPTIONS
|
||||
if (FileSystem::FileExists(path))
|
||||
{
|
||||
// Try to load asset file and asset info
|
||||
auto tmpFile = ContentStorageManager::GetStorage(path);
|
||||
AssetInitData data;
|
||||
if (tmpFile
|
||||
&& tmpFile->GetEntriesCount() == 1
|
||||
&& (
|
||||
(tmpFile->GetEntry(0).TypeName == Model::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 4)
|
||||
||
|
||||
(tmpFile->GetEntry(0).TypeName == SkinnedModel::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
|
||||
||
|
||||
(tmpFile->GetEntry(0).TypeName == Animation::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1)
|
||||
))
|
||||
{
|
||||
// Check import meta
|
||||
rapidjson_flax::Document metadata;
|
||||
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
|
||||
if (metadata.HasParseError() == false)
|
||||
{
|
||||
options.Deserialize(metadata, nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
|
||||
{
|
||||
// Skip if file is missing
|
||||
if (!FileSystem::FileExists(context.TargetAssetPath))
|
||||
return;
|
||||
|
||||
// Try to load asset that gets reimported
|
||||
AssetReference<Asset> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
|
||||
if (asset == nullptr)
|
||||
return;
|
||||
if (asset->WaitForLoaded())
|
||||
return;
|
||||
|
||||
// Get model object
|
||||
ModelBase* model = nullptr;
|
||||
if (asset.Get()->GetTypeName() == Model::TypeName)
|
||||
{
|
||||
model = ((Model*)asset.Get());
|
||||
}
|
||||
else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName)
|
||||
{
|
||||
model = ((SkinnedModel*)asset.Get());
|
||||
}
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
// Peek materials
|
||||
for (int32 i = 0; i < modelData.Materials.Count(); i++)
|
||||
{
|
||||
auto& dstSlot = modelData.Materials[i];
|
||||
|
||||
if (model->MaterialSlots.Count() > i)
|
||||
{
|
||||
auto& srcSlot = model->MaterialSlots[i];
|
||||
|
||||
dstSlot.Name = srcSlot.Name;
|
||||
dstSlot.ShadowsMode = srcSlot.ShadowsMode;
|
||||
dstSlot.AssetID = srcSlot.Material.GetID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
|
||||
{
|
||||
// Get import options
|
||||
Options options;
|
||||
if (context.CustomArg != nullptr)
|
||||
{
|
||||
// Copy import options from argument
|
||||
options = *static_cast<Options*>(context.CustomArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore the previous settings or use default ones
|
||||
if (!TryGetImportOptions(context.TargetAssetPath, options))
|
||||
{
|
||||
LOG(Warning, "Missing model import options. Using default values.");
|
||||
}
|
||||
}
|
||||
if (options.SplitObjects)
|
||||
{
|
||||
options.OnSplitImport.Bind([&context](Options& splitOptions, const String& objectName)
|
||||
{
|
||||
// Recursive importing of the split object
|
||||
String postFix = objectName;
|
||||
const int32 splitPos = postFix.FindLast(TEXT('|'));
|
||||
if (splitPos != -1)
|
||||
postFix = postFix.Substring(splitPos + 1);
|
||||
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
|
||||
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
|
||||
});
|
||||
}
|
||||
|
||||
// Import model file
|
||||
ModelData modelData;
|
||||
String errorMsg;
|
||||
String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
|
||||
autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
|
||||
if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
|
||||
{
|
||||
LOG(Error, "Cannot import model file. {0}", errorMsg);
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
|
||||
// Check if restore materials on model reimport
|
||||
if (options.RestoreMaterialsOnReimport && modelData.Materials.HasItems())
|
||||
{
|
||||
TryRestoreMaterials(context, modelData);
|
||||
}
|
||||
|
||||
// Auto calculate LODs transition settings
|
||||
modelData.CalculateLODsScreenSizes();
|
||||
|
||||
// Create destination asset type
|
||||
CreateAssetResult result = CreateAssetResult::InvalidTypeID;
|
||||
switch (options.Type)
|
||||
{
|
||||
case ModelTool::ModelType::Model:
|
||||
result = ImportModel(context, modelData, &options);
|
||||
break;
|
||||
case ModelTool::ModelType::SkinnedModel:
|
||||
result = ImportSkinnedModel(context, modelData, &options);
|
||||
break;
|
||||
case ModelTool::ModelType::Animation:
|
||||
result = ImportAnimation(context, modelData, &options);
|
||||
break;
|
||||
}
|
||||
if (result != CreateAssetResult::Ok)
|
||||
return result;
|
||||
|
||||
#if IMPORT_MODEL_CACHE_OPTIONS
|
||||
// Create json with import context
|
||||
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
|
||||
importOptionsMetaBuffer.Reserve(256);
|
||||
CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer);
|
||||
JsonWriter& importOptionsMeta = importOptionsMetaObj;
|
||||
importOptionsMeta.StartObject();
|
||||
{
|
||||
context.AddMeta(importOptionsMeta);
|
||||
options.Serialize(importOptionsMeta, nullptr);
|
||||
}
|
||||
importOptionsMeta.EndObject();
|
||||
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
|
||||
#endif
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModelFile::Create(CreateAssetContext& context)
|
||||
{
|
||||
ASSERT(context.CustomArg != nullptr);
|
||||
auto& modelData = *(ModelData*)context.CustomArg;
|
||||
|
||||
// Ensure model has any meshes
|
||||
if ((modelData.LODs.IsEmpty() || modelData.LODs[0].Meshes.IsEmpty()))
|
||||
{
|
||||
LOG(Warning, "Models has no valid meshes");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
|
||||
// Auto calculate LODs transition settings
|
||||
modelData.CalculateLODsScreenSizes();
|
||||
|
||||
// Import
|
||||
return ImportModel(context, modelData);
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
{
|
||||
// Base
|
||||
IMPORT_SETUP(Model, Model::SerializedVersion);
|
||||
|
||||
// Save model header
|
||||
MemoryWriteStream stream(4096);
|
||||
if (modelData.Pack2ModelHeader(&stream))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
// Pack model LODs data
|
||||
const auto lodCount = modelData.GetLODsCount();
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
|
||||
// Pack meshes
|
||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
if (meshes[meshIndex]->Pack2Model(&stream))
|
||||
{
|
||||
LOG(Warning, "Cannot pack mesh.");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
}
|
||||
|
||||
// Generate SDF
|
||||
if (options && options->GenerateSDF)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
if (!ModelTool::GenerateModelSDF(nullptr, &modelData, options->SDFResolution, lodCount - 1, nullptr, &stream, context.TargetAssetPath))
|
||||
{
|
||||
if (context.AllocateChunk(15))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
{
|
||||
// Base
|
||||
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
|
||||
|
||||
// Save skinned model header
|
||||
MemoryWriteStream stream(4096);
|
||||
if (modelData.Pack2SkinnedModelHeader(&stream))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
// Pack model LODs data
|
||||
const auto lodCount = modelData.GetLODsCount();
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
|
||||
// Mesh Data Version
|
||||
stream.WriteByte(1);
|
||||
|
||||
// Pack meshes
|
||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
if (meshes[meshIndex]->Pack2SkinnedModel(&stream))
|
||||
{
|
||||
LOG(Warning, "Cannot pack mesh.");
|
||||
return CreateAssetResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
}
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
|
||||
{
|
||||
// Base
|
||||
IMPORT_SETUP(Animation, Animation::SerializedVersion);
|
||||
|
||||
// Save animation data
|
||||
MemoryWriteStream stream(8182);
|
||||
if (modelData.Pack2AnimationHeader(&stream))
|
||||
return CreateAssetResult::Error;
|
||||
if (context.AllocateChunk(0))
|
||||
return CreateAssetResult::CannotAllocateChunk;
|
||||
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
|
||||
|
||||
return CreateAssetResult::Ok;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
|
||||
#include "Engine/Content/Assets/Model.h"
|
||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||
|
||||
/// <summary>
|
||||
/// Enable/disable caching model import options
|
||||
/// </summary>
|
||||
#define IMPORT_MODEL_CACHE_OPTIONS 1
|
||||
|
||||
/// <summary>
|
||||
/// Importing models utility
|
||||
/// </summary>
|
||||
class ImportModelFile
|
||||
{
|
||||
public:
|
||||
typedef ModelTool::Options Options;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Tries the get model import options from the target location asset.
|
||||
/// </summary>
|
||||
/// <param name="path">The asset path.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>True if success, otherwise false.</returns>
|
||||
static bool TryGetImportOptions(String path, Options& options);
|
||||
|
||||
/// <summary>
|
||||
/// Imports the model file.
|
||||
/// </summary>
|
||||
/// <param name="context">The importing context.</param>
|
||||
/// <returns>Result.</returns>
|
||||
static CreateAssetResult Import(CreateAssetContext& context);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the model asset from the ModelData storage (input argument should be pointer to ModelData).
|
||||
/// </summary>
|
||||
/// <param name="context">The importing context.</param>
|
||||
/// <returns>Result.</returns>
|
||||
static CreateAssetResult Create(CreateAssetContext& context);
|
||||
|
||||
private:
|
||||
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData);
|
||||
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData);
|
||||
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -18,7 +18,7 @@ class CreateAssetContext;
|
||||
/// <summary>
|
||||
/// Create/Import new asset callback result
|
||||
/// </summary>
|
||||
DECLARE_ENUM_7(CreateAssetResult, Ok, Abort, Error, CannotSaveFile, InvalidPath, CannotAllocateChunk, InvalidTypeID);
|
||||
DECLARE_ENUM_8(CreateAssetResult, Ok, Abort, Error, CannotSaveFile, InvalidPath, CannotAllocateChunk, InvalidTypeID, Skip);
|
||||
|
||||
/// <summary>
|
||||
/// Create/Import new asset callback function
|
||||
|
||||
@@ -25,6 +25,19 @@ private:
|
||||
int32 _capacity;
|
||||
AllocationData _allocation;
|
||||
|
||||
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromCount, int32 fromCapacity)
|
||||
{
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
to.Swap(from);
|
||||
else
|
||||
{
|
||||
to.Allocate(fromCapacity);
|
||||
Memory::MoveItems(to.Get(), from.Get(), fromCount);
|
||||
Memory::DestructItems(from.Get(), fromCount);
|
||||
from.Free();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Array"/> class.
|
||||
@@ -134,7 +147,7 @@ public:
|
||||
_capacity = other._capacity;
|
||||
other._count = 0;
|
||||
other._capacity = 0;
|
||||
_allocation.Swap(other._allocation);
|
||||
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -191,7 +204,7 @@ public:
|
||||
_capacity = other._capacity;
|
||||
other._count = 0;
|
||||
other._capacity = 0;
|
||||
_allocation.Swap(other._allocation);
|
||||
MoveToEmpty(_allocation, other._allocation, _count, _capacity);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -713,9 +726,18 @@ public:
|
||||
/// <param name="other">The other collection.</param>
|
||||
void Swap(Array& other)
|
||||
{
|
||||
::Swap(_count, other._count);
|
||||
::Swap(_capacity, other._capacity);
|
||||
_allocation.Swap(other._allocation);
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
{
|
||||
_allocation.Swap(other._allocation);
|
||||
::Swap(_count, other._count);
|
||||
::Swap(_capacity, other._capacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array tmp = MoveTemp(other);
|
||||
other = *this;
|
||||
*this = MoveTemp(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -726,9 +748,7 @@ public:
|
||||
T* data = _allocation.Get();
|
||||
const int32 count = _count / 2;
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
::Swap(data[i], data[_count - i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -55,9 +55,7 @@ public:
|
||||
for (int32 i = 0; i < obj.Count(); i++)
|
||||
{
|
||||
if (predicate(obj[i]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INVALID_INDEX;
|
||||
}
|
||||
@@ -74,9 +72,7 @@ public:
|
||||
for (int32 i = 0; i < obj.Count(); i++)
|
||||
{
|
||||
if (predicate(obj[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -93,13 +89,101 @@ public:
|
||||
for (int32 i = 0; i < obj.Count(); i++)
|
||||
{
|
||||
if (!predicate(obj[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters a sequence of values based on a predicate.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target collection.</param>
|
||||
/// <param name="predicate">The prediction function. Return true for elements that should be included in result list.</param>
|
||||
/// <param name="result">The result list with items that passed the predicate.</param>
|
||||
template<typename T, typename AllocationType>
|
||||
static void Where(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate, Array<T, AllocationType>& result)
|
||||
{
|
||||
for (const T& i : obj)
|
||||
{
|
||||
if (predicate(i))
|
||||
result.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters a sequence of values based on a predicate.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target collection.</param>
|
||||
/// <param name="predicate">The prediction function. Return true for elements that should be included in result list.</param>
|
||||
/// <returns>The result list with items that passed the predicate.</returns>
|
||||
template<typename T, typename AllocationType>
|
||||
static Array<T, AllocationType> Where(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
|
||||
{
|
||||
Array<T, AllocationType> result;
|
||||
Where(obj, predicate, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a new form.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target collection.</param>
|
||||
/// <param name="selector">A transform function to apply to each source element; the second parameter of the function represents the index of the source element.</param>
|
||||
/// <param name="result">The result list whose elements are the result of invoking the transform function on each element of source.</param>
|
||||
template<typename TResult, typename TSource, typename AllocationType>
|
||||
static void Select(const Array<TSource, AllocationType>& obj, const Function<TResult(const TSource&)>& selector, Array<TResult, AllocationType>& result)
|
||||
{
|
||||
for (const TSource& i : obj)
|
||||
result.Add(MoveTemp(selector(i)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Projects each element of a sequence into a new form.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target collection.</param>
|
||||
/// <param name="selector">A transform function to apply to each source element; the second parameter of the function represents the index of the source element.</param>
|
||||
/// <returns>The result list whose elements are the result of invoking the transform function on each element of source.</returns>
|
||||
template<typename TResult, typename TSource, typename AllocationType>
|
||||
static Array<TResult, AllocationType> Select(const Array<TSource, AllocationType>& obj, const Function<TResult(const TSource&)>& selector)
|
||||
{
|
||||
Array<TResult, AllocationType> result;
|
||||
Select(obj, selector, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all the elements that match the conditions defined by the specified predicate.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target collection to modify.</param>
|
||||
/// <param name="predicate">A transform function that defines the conditions of the elements to remove.</param>
|
||||
template<typename T, typename AllocationType>
|
||||
static void RemoveAll(Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
|
||||
{
|
||||
for (int32 i = obj.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
if (predicate(obj[i]))
|
||||
obj.RemoveAtKeepOrder(i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all the elements that match the conditions defined by the specified predicate.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target collection to process.</param>
|
||||
/// <param name="predicate">A transform function that defines the conditions of the elements to remove.</param>
|
||||
/// <returns>The result list whose elements are the result of invoking the transform function on each element of source.</returns>
|
||||
template<typename T, typename AllocationType>
|
||||
static Array<T, AllocationType> RemoveAll(const Array<T, AllocationType>& obj, const Function<bool(const T&)>& predicate)
|
||||
{
|
||||
Array<T, AllocationType> result;
|
||||
for (const T& i : obj)
|
||||
{
|
||||
if (!predicate(i))
|
||||
result.Ass(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Groups the elements of a sequence according to a specified key selector function.
|
||||
/// </summary>
|
||||
@@ -109,7 +193,7 @@ public:
|
||||
template<typename TSource, typename TKey, typename AllocationType>
|
||||
static void GroupBy(const Array<TSource, AllocationType>& obj, const Function<TKey(TSource const&)>& keySelector, Array<IGrouping<TKey, TSource>, AllocationType>& result)
|
||||
{
|
||||
Dictionary<TKey, IGrouping<TKey, TSource>> data(static_cast<int32>(obj.Count() * 3.0f));
|
||||
Dictionary<TKey, IGrouping<TKey, TSource>> data;
|
||||
for (int32 i = 0; i < obj.Count(); i++)
|
||||
{
|
||||
const TKey key = keySelector(obj[i]);
|
||||
|
||||
@@ -110,6 +110,33 @@ private:
|
||||
int32 _size = 0;
|
||||
AllocationData _allocation;
|
||||
|
||||
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
|
||||
{
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
to.Swap(from);
|
||||
else
|
||||
{
|
||||
to.Allocate(fromSize);
|
||||
Bucket* toData = to.Get();
|
||||
Bucket* fromData = from.Get();
|
||||
for (int32 i = 0; i < fromSize; i++)
|
||||
{
|
||||
Bucket& fromBucket = fromData[i];
|
||||
if (fromBucket.IsOccupied())
|
||||
{
|
||||
Bucket& toBucket = toData[i];
|
||||
Memory::MoveItems(&toBucket.Key, &fromBucket.Key, 1);
|
||||
Memory::MoveItems(&toBucket.Value, &fromBucket.Value, 1);
|
||||
toBucket._state = Bucket::Occupied;
|
||||
Memory::DestructItem(&fromBucket.Key);
|
||||
Memory::DestructItem(&fromBucket.Value);
|
||||
fromBucket._state = Bucket::Empty;
|
||||
}
|
||||
}
|
||||
from.Free();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Dictionary"/> class.
|
||||
@@ -139,7 +166,7 @@ public:
|
||||
other._elementsCount = 0;
|
||||
other._deletedCount = 0;
|
||||
other._size = 0;
|
||||
_allocation.Swap(other._allocation);
|
||||
MoveToEmpty(_allocation, other._allocation, _size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -180,7 +207,7 @@ public:
|
||||
other._elementsCount = 0;
|
||||
other._deletedCount = 0;
|
||||
other._size = 0;
|
||||
_allocation.Swap(other._allocation);
|
||||
MoveToEmpty(_allocation, other._allocation, _size);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -510,7 +537,7 @@ public:
|
||||
return;
|
||||
ASSERT(capacity >= 0);
|
||||
AllocationData oldAllocation;
|
||||
oldAllocation.Swap(_allocation);
|
||||
MoveToEmpty(oldAllocation, _allocation, _size);
|
||||
const int32 oldSize = _size;
|
||||
const int32 oldElementsCount = _elementsCount;
|
||||
_deletedCount = _elementsCount = 0;
|
||||
@@ -580,10 +607,19 @@ public:
|
||||
/// <param name="other">The other collection.</param>
|
||||
void Swap(Dictionary& other)
|
||||
{
|
||||
::Swap(_elementsCount, other._elementsCount);
|
||||
::Swap(_deletedCount, other._deletedCount);
|
||||
::Swap(_size, other._size);
|
||||
_allocation.Swap(other._allocation);
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
{
|
||||
::Swap(_elementsCount, other._elementsCount);
|
||||
::Swap(_deletedCount, other._deletedCount);
|
||||
::Swap(_size, other._size);
|
||||
_allocation.Swap(other._allocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary tmp = MoveTemp(other);
|
||||
other = *this;
|
||||
*this = MoveTemp(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -930,7 +966,7 @@ private:
|
||||
{
|
||||
// Rebuild entire table completely
|
||||
AllocationData oldAllocation;
|
||||
oldAllocation.Swap(_allocation);
|
||||
MoveToEmpty(oldAllocation, _allocation, _size);
|
||||
_allocation.Allocate(_size);
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
|
||||
@@ -93,6 +93,31 @@ private:
|
||||
int32 _size = 0;
|
||||
AllocationData _allocation;
|
||||
|
||||
FORCE_INLINE static void MoveToEmpty(AllocationData& to, AllocationData& from, int32 fromSize)
|
||||
{
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
to.Swap(from);
|
||||
else
|
||||
{
|
||||
to.Allocate(fromSize);
|
||||
Bucket* toData = to.Get();
|
||||
Bucket* fromData = from.Get();
|
||||
for (int32 i = 0; i < fromSize; i++)
|
||||
{
|
||||
Bucket& fromBucket = fromData[i];
|
||||
if (fromBucket.IsOccupied())
|
||||
{
|
||||
Bucket& toBucket = toData[i];
|
||||
Memory::MoveItems(&toBucket.Item, &fromBucket.Item, 1);
|
||||
toBucket._state = Bucket::Occupied;
|
||||
Memory::DestructItem(&fromBucket.Item);
|
||||
fromBucket._state = Bucket::Empty;
|
||||
}
|
||||
}
|
||||
from.Free();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HashSet"/> class.
|
||||
@@ -122,7 +147,7 @@ public:
|
||||
other._elementsCount = 0;
|
||||
other._deletedCount = 0;
|
||||
other._size = 0;
|
||||
_allocation.Swap(other._allocation);
|
||||
MoveToEmpty(_allocation, other._allocation, _size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -163,7 +188,7 @@ public:
|
||||
other._elementsCount = 0;
|
||||
other._deletedCount = 0;
|
||||
other._size = 0;
|
||||
_allocation.Swap(other._allocation);
|
||||
MoveToEmpty(_allocation, other._allocation, _size);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -389,7 +414,7 @@ public:
|
||||
return;
|
||||
ASSERT(capacity >= 0);
|
||||
AllocationData oldAllocation;
|
||||
oldAllocation.Swap(_allocation);
|
||||
MoveToEmpty(oldAllocation, _allocation, _size);
|
||||
const int32 oldSize = _size;
|
||||
const int32 oldElementsCount = _elementsCount;
|
||||
_deletedCount = _elementsCount = 0;
|
||||
@@ -458,10 +483,19 @@ public:
|
||||
/// <param name="other">The other collection.</param>
|
||||
void Swap(HashSet& other)
|
||||
{
|
||||
::Swap(_elementsCount, other._elementsCount);
|
||||
::Swap(_deletedCount, other._deletedCount);
|
||||
::Swap(_size, other._size);
|
||||
_allocation.Swap(other._allocation);
|
||||
if IF_CONSTEXPR (AllocationType::HasSwap)
|
||||
{
|
||||
::Swap(_elementsCount, other._elementsCount);
|
||||
::Swap(_deletedCount, other._deletedCount);
|
||||
::Swap(_size, other._size);
|
||||
_allocation.Swap(other._allocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
HashSet tmp = MoveTemp(other);
|
||||
other = *this;
|
||||
*this = MoveTemp(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -726,7 +760,7 @@ private:
|
||||
{
|
||||
// Rebuild entire table completely
|
||||
AllocationData oldAllocation;
|
||||
oldAllocation.Swap(_allocation);
|
||||
MoveToEmpty(oldAllocation, _allocation, _size);
|
||||
_allocation.Allocate(_size);
|
||||
Bucket* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; i++)
|
||||
|
||||
@@ -45,6 +45,16 @@ public:
|
||||
};
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
|
||||
/// </summary>
|
||||
/// <param name="data">The data container.</param>
|
||||
template<typename T, typename AllocationType = HeapAllocation>
|
||||
FORCE_INLINE static void QuickSort(Array<T, AllocationType>& data)
|
||||
{
|
||||
QuickSort(data.Get(), data.Count());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the linear data array using Quick Sort algorithm (non recursive version, uses temporary stack collection).
|
||||
/// </summary>
|
||||
|
||||
@@ -93,3 +93,10 @@
|
||||
#endif
|
||||
|
||||
#define PACK_STRUCT(__Declaration__) PACK_BEGIN() __Declaration__ PACK_END()
|
||||
|
||||
// C++ 17
|
||||
#if __cplusplus >= 201703L
|
||||
#define IF_CONSTEXPR constexpr
|
||||
#else
|
||||
#define IF_CONSTEXPR
|
||||
#endif
|
||||
|
||||
@@ -58,7 +58,7 @@ bool Log::Logger::Init()
|
||||
int32 remaining = oldLogs.Count() - maxLogFiles + 1;
|
||||
if (remaining > 0)
|
||||
{
|
||||
Sorting::QuickSort(oldLogs.Get(), oldLogs.Count());
|
||||
Sorting::QuickSort(oldLogs);
|
||||
|
||||
// Delete the oldest logs
|
||||
int32 i = 0;
|
||||
|
||||
@@ -15,6 +15,8 @@ const Float2 Float2::Zero(0.0f);
|
||||
template<>
|
||||
const Float2 Float2::One(1.0f);
|
||||
template<>
|
||||
const Float2 Float2::Half(0.5f);
|
||||
template<>
|
||||
const Float2 Float2::UnitX(1.0f, 0.0f);
|
||||
template<>
|
||||
const Float2 Float2::UnitY(0.0f, 1.0f);
|
||||
|
||||
@@ -44,6 +44,9 @@ public:
|
||||
// Vector with all components equal 1
|
||||
static FLAXENGINE_API const Vector2Base<T> One;
|
||||
|
||||
// Vector with all components equal 0.5
|
||||
static FLAXENGINE_API const Vector2Base<T> Half;
|
||||
|
||||
// Vector X=1, Y=0
|
||||
static FLAXENGINE_API const Vector2Base<T> UnitX;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ const Float4 Float4::Zero(0.0f);
|
||||
template<>
|
||||
const Float4 Float4::One(1.0f);
|
||||
template<>
|
||||
const Float4 Float4::Half(0.5f);
|
||||
template<>
|
||||
const Float4 Float4::UnitX(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
template<>
|
||||
const Float4 Float4::UnitY(0.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
@@ -54,6 +54,9 @@ public:
|
||||
// Vector with all components equal 1
|
||||
static FLAXENGINE_API const Vector4Base<T> One;
|
||||
|
||||
// Vector with all components equal 0.5
|
||||
static FLAXENGINE_API const Vector4Base<T> Half;
|
||||
|
||||
// Vector X=1, Y=0, Z=0, W=0
|
||||
static FLAXENGINE_API const Vector4Base<T> UnitX;
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ template<int Capacity>
|
||||
class FixedAllocation
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = false };
|
||||
|
||||
template<typename T>
|
||||
class Data
|
||||
{
|
||||
@@ -61,12 +63,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
FORCE_INLINE void Swap(Data& other)
|
||||
void Swap(Data& other)
|
||||
{
|
||||
byte tmp[Capacity * sizeof(T)];
|
||||
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
|
||||
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
|
||||
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
|
||||
// Not supported
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -77,6 +76,8 @@ public:
|
||||
class HeapAllocation
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = true };
|
||||
|
||||
template<typename T>
|
||||
class Data
|
||||
{
|
||||
@@ -179,6 +180,8 @@ template<int Capacity, typename OtherAllocator = HeapAllocation>
|
||||
class InlinedAllocation
|
||||
{
|
||||
public:
|
||||
enum { HasSwap = false };
|
||||
|
||||
template<typename T>
|
||||
class Data
|
||||
{
|
||||
@@ -267,14 +270,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
FORCE_INLINE void Swap(Data& other)
|
||||
void Swap(Data& other)
|
||||
{
|
||||
byte tmp[Capacity * sizeof(T)];
|
||||
Platform::MemoryCopy(tmp, _data, Capacity * sizeof(T));
|
||||
Platform::MemoryCopy(_data, other._data, Capacity * sizeof(T));
|
||||
Platform::MemoryCopy(other._data, tmp, Capacity * sizeof(T));
|
||||
::Swap(_useOther, other._useOther);
|
||||
_other.Swap(other._other);
|
||||
// Not supported
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -522,7 +522,13 @@ void EngineImpl::InitLog()
|
||||
LOG(Info, "Compiled for Dev Environment");
|
||||
#endif
|
||||
LOG(Info, "Version " FLAXENGINE_VERSION_TEXT);
|
||||
LOG(Info, "Compiled: {0} {1}", TEXT(__DATE__), TEXT(__TIME__));
|
||||
const Char* cpp = TEXT("?");
|
||||
if (__cplusplus == 202101L) cpp = TEXT("C++23");
|
||||
else if (__cplusplus == 202002L) cpp = TEXT("C++20");
|
||||
else if (__cplusplus == 201703L) cpp = TEXT("C++17");
|
||||
else if (__cplusplus == 201402L) cpp = TEXT("C++14");
|
||||
else if (__cplusplus == 201103L) cpp = TEXT("C++11");
|
||||
LOG(Info, "Compiled: {0} {1} {2}", TEXT(__DATE__), TEXT(__TIME__), cpp);
|
||||
#ifdef _MSC_VER
|
||||
const String mcsVer = StringUtils::ToString(_MSC_FULL_VER);
|
||||
LOG(Info, "Compiled with Visual C++ {0}.{1}.{2}.{3:0^2d}", mcsVer.Substring(0, 2), mcsVer.Substring(2, 2), mcsVer.Substring(4, 5), _MSC_BUILD);
|
||||
|
||||
@@ -1271,6 +1271,9 @@ namespace FlaxEngine.Interop
|
||||
case Type _ when type == typeof(IntPtr):
|
||||
monoType = MTypes.Ptr;
|
||||
break;
|
||||
case Type _ when type.IsPointer:
|
||||
monoType = MTypes.Ptr;
|
||||
break;
|
||||
case Type _ when type.IsEnum:
|
||||
monoType = MTypes.Enum;
|
||||
break;
|
||||
|
||||
@@ -1135,7 +1135,7 @@ namespace FlaxEngine.Interop
|
||||
marshallers[i](fields[i], offsets[i], ref managedValue, fieldPtr, out int fieldSize);
|
||||
fieldPtr += fieldSize;
|
||||
}
|
||||
Assert.IsTrue((fieldPtr - nativePtr) <= Unsafe.SizeOf<T>());
|
||||
//Assert.IsTrue((fieldPtr - nativePtr) <= GetTypeSize(typeof(T)));
|
||||
}
|
||||
|
||||
internal static void ToManaged(ref T managedValue, IntPtr nativePtr, bool byRef)
|
||||
@@ -1182,7 +1182,7 @@ namespace FlaxEngine.Interop
|
||||
marshallers[i](fields[i], offsets[i], ref managedValue, nativePtr, out int fieldSize);
|
||||
nativePtr += fieldSize;
|
||||
}
|
||||
Assert.IsTrue((nativePtr - fieldPtr) <= Unsafe.SizeOf<T>());
|
||||
//Assert.IsTrue((nativePtr - fieldPtr) <= GetTypeSize(typeof(T)));
|
||||
}
|
||||
|
||||
internal static void ToNative(ref T managedValue, IntPtr nativePtr)
|
||||
@@ -1331,39 +1331,50 @@ namespace FlaxEngine.Interop
|
||||
// Skip using in-built delegate for value types (eg. Transform) to properly handle instance value passing to method
|
||||
if (invokeDelegate == null && !method.DeclaringType.IsValueType)
|
||||
{
|
||||
List<Type> methodTypes = new List<Type>();
|
||||
if (!method.IsStatic)
|
||||
methodTypes.Add(method.DeclaringType);
|
||||
if (returnType != typeof(void))
|
||||
methodTypes.Add(returnType);
|
||||
methodTypes.AddRange(parameterTypes);
|
||||
|
||||
List<Type> genericParamTypes = new List<Type>();
|
||||
foreach (var type in methodTypes)
|
||||
// Thread-safe creation
|
||||
lock (typeCache)
|
||||
{
|
||||
if (type.IsByRef)
|
||||
genericParamTypes.Add(type.GetElementType());
|
||||
else if (type.IsPointer)
|
||||
genericParamTypes.Add(typeof(IntPtr));
|
||||
else
|
||||
genericParamTypes.Add(type);
|
||||
}
|
||||
|
||||
string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(returnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}";
|
||||
Type invokerType = Type.GetType(invokerTypeName);
|
||||
if (invokerType != null)
|
||||
{
|
||||
if (genericParamTypes.Count != 0)
|
||||
invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray());
|
||||
invokeDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.MarshalAndInvoke), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Invoker.MarshalAndInvokeDelegate>();
|
||||
delegInvoke = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method });
|
||||
if (invokeDelegate == null)
|
||||
{
|
||||
TryCreateDelegate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outDeleg = invokeDelegate;
|
||||
outDelegInvoke = delegInvoke;
|
||||
return outDeleg != null;
|
||||
}
|
||||
|
||||
private void TryCreateDelegate()
|
||||
{
|
||||
var methodTypes = new List<Type>();
|
||||
if (!method.IsStatic)
|
||||
methodTypes.Add(method.DeclaringType);
|
||||
if (returnType != typeof(void))
|
||||
methodTypes.Add(returnType);
|
||||
methodTypes.AddRange(parameterTypes);
|
||||
|
||||
var genericParamTypes = new List<Type>();
|
||||
foreach (var type in methodTypes)
|
||||
{
|
||||
if (type.IsByRef)
|
||||
genericParamTypes.Add(type.GetElementType());
|
||||
else if (type.IsPointer)
|
||||
genericParamTypes.Add(typeof(IntPtr));
|
||||
else
|
||||
genericParamTypes.Add(type);
|
||||
}
|
||||
|
||||
string invokerTypeName = $"{typeof(Invoker).FullName}+Invoker{(method.IsStatic ? "Static" : "")}{(returnType != typeof(void) ? "Ret" : "NoRet")}{parameterTypes.Length}{(genericParamTypes.Count > 0 ? "`" + genericParamTypes.Count : "")}";
|
||||
Type invokerType = Type.GetType(invokerTypeName);
|
||||
if (invokerType != null)
|
||||
{
|
||||
if (genericParamTypes.Count != 0)
|
||||
invokerType = invokerType.MakeGenericType(genericParamTypes.ToArray());
|
||||
delegInvoke = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.CreateDelegate), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { method });
|
||||
invokeDelegate = invokerType.GetMethod(nameof(Invoker.InvokerStaticNoRet0.MarshalAndInvoke), BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate<Invoker.MarshalAndInvokeDelegate>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1580,7 +1591,7 @@ namespace FlaxEngine.Interop
|
||||
private static IntPtr PinValue<T>(T value) where T : struct
|
||||
{
|
||||
// Store the converted value in unmanaged memory so it will not be relocated by the garbage collector.
|
||||
int size = Unsafe.SizeOf<T>();
|
||||
int size = GetTypeSize(typeof(T));
|
||||
uint index = Interlocked.Increment(ref pinnedAllocationsPointer) % (uint)pinnedAllocations.Length;
|
||||
ref (IntPtr ptr, int size) alloc = ref pinnedAllocations[index];
|
||||
if (alloc.size < size)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "Engine/Core/Collections/BitArray.h"
|
||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||
#include "Engine/Tools/ModelTool/VertexTriangleAdjacency.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Platform/Platform.h"
|
||||
#define USE_MIKKTSPACE 1
|
||||
#include "ThirdParty/MikkTSpace/mikktspace.h"
|
||||
@@ -78,6 +79,7 @@ void RemapArrayHelper(Array<T>& target, const std::vector<uint32_t>& remap)
|
||||
|
||||
bool MeshData::GenerateLightmapUVs()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
#if PLATFORM_WINDOWS
|
||||
// Prepare
|
||||
HRESULT hr;
|
||||
@@ -235,6 +237,7 @@ void RemapBuffer(Array<T>& src, Array<T>& dst, const Array<int32>& mapping, int3
|
||||
|
||||
void MeshData::BuildIndexBuffer()
|
||||
{
|
||||
PROFILE_CPU();
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
const int32 vertexCount = Positions.Count();
|
||||
@@ -341,6 +344,7 @@ bool MeshData::GenerateNormals(float smoothingAngle)
|
||||
LOG(Warning, "Missing vertex or index data to generate normals.");
|
||||
return true;
|
||||
}
|
||||
PROFILE_CPU();
|
||||
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
@@ -520,6 +524,7 @@ bool MeshData::GenerateTangents(float smoothingAngle)
|
||||
LOG(Warning, "Missing normals or texcoors data to generate tangents.");
|
||||
return true;
|
||||
}
|
||||
PROFILE_CPU();
|
||||
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
const int32 vertexCount = Positions.Count();
|
||||
@@ -706,6 +711,7 @@ void MeshData::ImproveCacheLocality()
|
||||
|
||||
if (Positions.IsEmpty() || Indices.IsEmpty() || Positions.Count() <= VertexCacheSize)
|
||||
return;
|
||||
PROFILE_CPU();
|
||||
|
||||
const auto startTime = Platform::GetTimeSeconds();
|
||||
|
||||
@@ -886,6 +892,7 @@ void MeshData::ImproveCacheLocality()
|
||||
|
||||
float MeshData::CalculateTrianglesArea() const
|
||||
{
|
||||
PROFILE_CPU();
|
||||
float sum = 0;
|
||||
// TODO: use SIMD
|
||||
for (int32 i = 0; i + 2 < Indices.Count(); i += 3)
|
||||
|
||||
@@ -625,6 +625,11 @@ bool MaterialSlotEntry::UsesProperties() const
|
||||
Normals.TextureIndex != -1;
|
||||
}
|
||||
|
||||
ModelLodData::~ModelLodData()
|
||||
{
|
||||
Meshes.ClearDelete();
|
||||
}
|
||||
|
||||
BoundingBox ModelLodData::GetBox() const
|
||||
{
|
||||
if (Meshes.IsEmpty())
|
||||
@@ -644,11 +649,9 @@ void ModelData::CalculateLODsScreenSizes()
|
||||
{
|
||||
const float autoComputeLodPowerBase = 0.5f;
|
||||
const int32 lodCount = LODs.Count();
|
||||
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
auto& lod = LODs[lodIndex];
|
||||
|
||||
if (lodIndex == 0)
|
||||
{
|
||||
lod.ScreenSize = 1.0f;
|
||||
@@ -675,6 +678,8 @@ void ModelData::TransformBuffer(const Matrix& matrix)
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelData::Pack2ModelHeader(WriteStream* stream) const
|
||||
{
|
||||
// Validate input
|
||||
@@ -724,7 +729,12 @@ bool ModelData::Pack2ModelHeader(WriteStream* stream) const
|
||||
|
||||
// Amount of meshes
|
||||
const int32 meshes = lod.Meshes.Count();
|
||||
if (meshes == 0 || meshes > MODEL_MAX_MESHES)
|
||||
if (meshes == 0)
|
||||
{
|
||||
LOG(Warning, "Empty LOD.");
|
||||
return true;
|
||||
}
|
||||
if (meshes > MODEL_MAX_MESHES)
|
||||
{
|
||||
LOG(Warning, "Too many meshes per LOD.");
|
||||
return true;
|
||||
@@ -880,20 +890,21 @@ bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelData::Pack2AnimationHeader(WriteStream* stream) const
|
||||
bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
|
||||
{
|
||||
// Validate input
|
||||
if (stream == nullptr)
|
||||
if (stream == nullptr || animIndex < 0 || animIndex >= Animations.Count())
|
||||
{
|
||||
Log::ArgumentNullException();
|
||||
return true;
|
||||
}
|
||||
if (Animation.Duration <= ZeroTolerance || Animation.FramesPerSecond <= ZeroTolerance)
|
||||
auto& anim = Animations.Get()[animIndex];
|
||||
if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance)
|
||||
{
|
||||
Log::InvalidOperationException(TEXT("Invalid animation duration."));
|
||||
return true;
|
||||
}
|
||||
if (Animation.Channels.IsEmpty())
|
||||
if (anim.Channels.IsEmpty())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
|
||||
return true;
|
||||
@@ -901,22 +912,23 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream) const
|
||||
|
||||
// Info
|
||||
stream->WriteInt32(100); // Header version (for fast version upgrades without serialization format change)
|
||||
stream->WriteDouble(Animation.Duration);
|
||||
stream->WriteDouble(Animation.FramesPerSecond);
|
||||
stream->WriteBool(Animation.EnableRootMotion);
|
||||
stream->WriteString(Animation.RootNodeName, 13);
|
||||
stream->WriteDouble(anim.Duration);
|
||||
stream->WriteDouble(anim.FramesPerSecond);
|
||||
stream->WriteBool(anim.EnableRootMotion);
|
||||
stream->WriteString(anim.RootNodeName, 13);
|
||||
|
||||
// Animation channels
|
||||
stream->WriteInt32(Animation.Channels.Count());
|
||||
for (int32 i = 0; i < Animation.Channels.Count(); i++)
|
||||
stream->WriteInt32(anim.Channels.Count());
|
||||
for (int32 i = 0; i < anim.Channels.Count(); i++)
|
||||
{
|
||||
auto& anim = Animation.Channels[i];
|
||||
|
||||
stream->WriteString(anim.NodeName, 172);
|
||||
Serialization::Serialize(*stream, anim.Position);
|
||||
Serialization::Serialize(*stream, anim.Rotation);
|
||||
Serialization::Serialize(*stream, anim.Scale);
|
||||
auto& channel = anim.Channels[i];
|
||||
stream->WriteString(channel.NodeName, 172);
|
||||
Serialization::Serialize(*stream, channel.Position);
|
||||
Serialization::Serialize(*stream, channel.Rotation);
|
||||
Serialization::Serialize(*stream, channel.Scale);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -366,12 +366,32 @@ struct FLAXENGINE_API MaterialSlotEntry
|
||||
bool UsesProperties() const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data container for model hierarchy node.
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API ModelDataNode
|
||||
{
|
||||
/// <summary>
|
||||
/// The parent node index. The root node uses value -1.
|
||||
/// </summary>
|
||||
int32 ParentIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The local transformation of the node, relative to the parent node.
|
||||
/// </summary>
|
||||
Transform LocalTransform;
|
||||
|
||||
/// <summary>
|
||||
/// The name of this node.
|
||||
/// </summary>
|
||||
String Name;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Data container for LOD metadata and sub meshes.
|
||||
/// </summary>
|
||||
class FLAXENGINE_API ModelLodData
|
||||
struct FLAXENGINE_API ModelLodData
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// The screen size to switch LODs. Bottom limit of the model screen size to render this LOD.
|
||||
/// </summary>
|
||||
@@ -382,21 +402,10 @@ public:
|
||||
/// </summary>
|
||||
Array<MeshData*> Meshes;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelLodData"/> class.
|
||||
/// </summary>
|
||||
ModelLodData()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="ModelLodData"/> class.
|
||||
/// </summary>
|
||||
~ModelLodData()
|
||||
{
|
||||
Meshes.ClearDelete();
|
||||
}
|
||||
~ModelLodData();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bounding box combined for all meshes in this model LOD.
|
||||
@@ -426,7 +435,7 @@ public:
|
||||
Array<MaterialSlotEntry> Materials;
|
||||
|
||||
/// <summary>
|
||||
/// Array with all LODs. The first element is the top most LOD0 followed by the LOD1, LOD2, etc.
|
||||
/// Array with all Level Of Details that contain meshes. The first element is the top most LOD0 followed by the LOD1, LOD2, etc.
|
||||
/// </summary>
|
||||
Array<ModelLodData> LODs;
|
||||
|
||||
@@ -435,24 +444,20 @@ public:
|
||||
/// </summary>
|
||||
SkeletonData Skeleton;
|
||||
|
||||
/// <summary>
|
||||
/// The scene nodes (in hierarchy).
|
||||
/// </summary>
|
||||
Array<ModelDataNode> Nodes;
|
||||
|
||||
/// <summary>
|
||||
/// The node animations.
|
||||
/// </summary>
|
||||
AnimationData Animation;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModelData"/> class.
|
||||
/// </summary>
|
||||
ModelData()
|
||||
{
|
||||
}
|
||||
Array<AnimationData> Animations;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the valid level of details count.
|
||||
/// </summary>
|
||||
/// <returns>The LOD count.</returns>
|
||||
FORCE_INLINE int32 GetLODsCount() const
|
||||
{
|
||||
return LODs.Count();
|
||||
@@ -461,7 +466,6 @@ public:
|
||||
/// <summary>
|
||||
/// Determines whether this instance has valid skeleton structure.
|
||||
/// </summary>
|
||||
/// <returns>True if has skeleton, otherwise false.</returns>
|
||||
FORCE_INLINE bool HasSkeleton() const
|
||||
{
|
||||
return Skeleton.Bones.HasItems();
|
||||
@@ -479,6 +483,7 @@ public:
|
||||
/// <param name="matrix">The matrix to use for the transformation.</param>
|
||||
void TransformBuffer(const Matrix& matrix);
|
||||
|
||||
#if USE_EDITOR
|
||||
public:
|
||||
/// <summary>
|
||||
/// Pack mesh data to the header stream
|
||||
@@ -498,6 +503,8 @@ public:
|
||||
/// Pack animation data to the header stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Output stream</param>
|
||||
/// <param name="animIndex">Index of animation.</param>
|
||||
/// <returns>True if cannot save data, otherwise false</returns>
|
||||
bool Pack2AnimationHeader(WriteStream* stream) const;
|
||||
bool Pack2AnimationHeader(WriteStream* stream, int32 animIndex = 0) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="sourceSkeleton">The source model skeleton.</param>
|
||||
/// <param name="targetSkeleton">The target skeleton. May be null to disable nodes mapping.</param>
|
||||
SkeletonMapping(Items& sourceSkeleton, Items* targetSkeleton)
|
||||
SkeletonMapping(const Items& sourceSkeleton, const Items* targetSkeleton)
|
||||
{
|
||||
Size = sourceSkeleton.Count();
|
||||
SourceToTarget.Resize(Size); // model => skeleton mapping
|
||||
|
||||
@@ -63,7 +63,6 @@ StreamingTexture::~StreamingTexture()
|
||||
{
|
||||
UnloadTexture();
|
||||
SAFE_DELETE(_texture);
|
||||
ASSERT(_streamingTasks.Count() == 0);
|
||||
}
|
||||
|
||||
Float2 StreamingTexture::Size() const
|
||||
@@ -134,11 +133,9 @@ bool StreamingTexture::Create(const TextureHeader& header)
|
||||
void StreamingTexture::UnloadTexture()
|
||||
{
|
||||
ScopeLock lock(_owner->GetOwnerLocker());
|
||||
|
||||
// Release
|
||||
CancelStreamingTasks();
|
||||
_texture->ReleaseGPU();
|
||||
_header.MipLevels = 0;
|
||||
|
||||
ASSERT(_streamingTasks.Count() == 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ void GPUDeviceVulkan::GetInstanceLayersAndExtensions(Array<const char*>& outInst
|
||||
if (foundUniqueLayers.HasItems())
|
||||
{
|
||||
LOG(Info, "Found instance layers:");
|
||||
Sorting::QuickSort(foundUniqueLayers.Get(), foundUniqueLayers.Count());
|
||||
Sorting::QuickSort(foundUniqueLayers);
|
||||
for (const StringAnsi& name : foundUniqueLayers)
|
||||
{
|
||||
LOG(Info, "- {0}", String(name));
|
||||
@@ -257,7 +257,7 @@ void GPUDeviceVulkan::GetInstanceLayersAndExtensions(Array<const char*>& outInst
|
||||
if (foundUniqueExtensions.HasItems())
|
||||
{
|
||||
LOG(Info, "Found instance extensions:");
|
||||
Sorting::QuickSort(foundUniqueExtensions.Get(), foundUniqueExtensions.Count());
|
||||
Sorting::QuickSort(foundUniqueExtensions);
|
||||
for (const StringAnsi& name : foundUniqueExtensions)
|
||||
{
|
||||
LOG(Info, "- {0}", String(name));
|
||||
@@ -455,7 +455,7 @@ void GPUDeviceVulkan::GetDeviceExtensionsAndLayers(VkPhysicalDevice gpu, Array<c
|
||||
if (foundUniqueLayers.HasItems())
|
||||
{
|
||||
LOG(Info, "Found device layers:");
|
||||
Sorting::QuickSort(foundUniqueLayers.Get(), foundUniqueLayers.Count());
|
||||
Sorting::QuickSort(foundUniqueLayers);
|
||||
for (const StringAnsi& name : foundUniqueLayers)
|
||||
{
|
||||
LOG(Info, "- {0}", String(name));
|
||||
@@ -465,7 +465,7 @@ void GPUDeviceVulkan::GetDeviceExtensionsAndLayers(VkPhysicalDevice gpu, Array<c
|
||||
if (foundUniqueExtensions.HasItems())
|
||||
{
|
||||
LOG(Info, "Found device extensions:");
|
||||
Sorting::QuickSort(foundUniqueExtensions.Get(), foundUniqueExtensions.Count());
|
||||
Sorting::QuickSort(foundUniqueExtensions);
|
||||
for (const StringAnsi& name : foundUniqueExtensions)
|
||||
{
|
||||
LOG(Info, "- {0}", String(name));
|
||||
|
||||
@@ -239,14 +239,8 @@ const Guid& Actor::GetSceneObjectId() const
|
||||
|
||||
void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefabLink)
|
||||
{
|
||||
// Check if value won't change
|
||||
if (_parent == value)
|
||||
return;
|
||||
if (IsDuringPlay() && !IsInMainThread())
|
||||
{
|
||||
LOG(Error, "Editing scene hierarchy is only allowed on a main thread.");
|
||||
return;
|
||||
}
|
||||
#if USE_EDITOR || !BUILD_RELEASE
|
||||
if (Is<Scene>())
|
||||
{
|
||||
@@ -265,6 +259,13 @@ void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefa
|
||||
// Detect it actor is not in a game but new parent is already in a game (we should spawn it)
|
||||
const bool isBeingSpawned = !IsDuringPlay() && newScene && value->IsDuringPlay();
|
||||
|
||||
// Actors system doesn't support editing scene hierarchy from multiple threads
|
||||
if (!IsInMainThread() && (IsDuringPlay() || isBeingSpawned))
|
||||
{
|
||||
LOG(Error, "Editing scene hierarchy is only allowed on a main thread.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle changing scene (unregister from it)
|
||||
const bool isSceneChanging = prevScene != newScene;
|
||||
if (prevScene && isSceneChanging && wasActiveInTree)
|
||||
|
||||
@@ -99,6 +99,8 @@ void SkyLight::UpdateBounds()
|
||||
{
|
||||
_sphere = BoundingSphere(GetPosition(), GetScaledRadius());
|
||||
BoundingBox::FromSphere(_sphere, _box);
|
||||
if (_sceneRenderingKey != -1)
|
||||
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);
|
||||
}
|
||||
|
||||
void SkyLight::Draw(RenderContext& renderContext)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Engine/ContentImporters/CreateJson.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#include "Editor/Editor.h"
|
||||
|
||||
// Apply flow:
|
||||
@@ -174,6 +175,12 @@ public:
|
||||
/// <param name="newObjectIds">Collection with ids of the objects (actors and scripts) from the prefab after changes apply. Used to find new objects or old objects and use this information during changes sync (eg. generate ids for the new objects to prevent ids collisions).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
static bool SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds);
|
||||
|
||||
static void DeletePrefabObject(SceneObject* obj)
|
||||
{
|
||||
obj->SetParent(nullptr);
|
||||
obj->DeleteObject();
|
||||
}
|
||||
};
|
||||
|
||||
void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor)
|
||||
@@ -302,14 +309,10 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
{
|
||||
// Remove object
|
||||
LOG(Info, "Removing object {0} from instance {1} (prefab: {2})", obj->GetSceneObjectId(), instance.TargetActor->ToString(), prefabId);
|
||||
|
||||
obj->DeleteObject();
|
||||
obj->SetParent(nullptr);
|
||||
|
||||
DeletePrefabObject(obj);
|
||||
sceneObjects.Value->RemoveAtKeepOrder(i);
|
||||
existingObjectsCount--;
|
||||
i--;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -358,10 +361,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabI
|
||||
{
|
||||
// Remove object removed from the prefab
|
||||
LOG(Info, "Removing prefab instance object {0} from instance {1} (prefab object: {2}, prefab: {3})", obj->GetSceneObjectId(), instance.TargetActor->ToString(), obj->GetPrefabObjectID(), prefabId);
|
||||
|
||||
obj->DeleteObject();
|
||||
obj->SetParent(nullptr);
|
||||
|
||||
DeletePrefabObject(obj);
|
||||
sceneObjects.Value->RemoveAtKeepOrder(i);
|
||||
deserializeSceneObjectIndex--;
|
||||
existingObjectsCount--;
|
||||
@@ -633,6 +633,19 @@ bool Prefab::ApplyAll(Actor* targetActor)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!IsInMainThread())
|
||||
{
|
||||
// Prefabs cannot be updated on async thread so sync it with a Main Thread
|
||||
bool result = true;
|
||||
Function<void()> action = [&]
|
||||
{
|
||||
result = ApplyAll(targetActor);
|
||||
};
|
||||
const auto task = Task::StartNew(New<MainThreadActionTask>(action));
|
||||
if (task->Wait(TimeSpan::FromSeconds(10)))
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Prevent cyclic references
|
||||
{
|
||||
@@ -921,9 +934,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
||||
{
|
||||
// Remove object removed from the prefab
|
||||
LOG(Info, "Removing object {0} from prefab default instance", obj->GetSceneObjectId());
|
||||
|
||||
obj->DeleteObject();
|
||||
obj->SetParent(nullptr);
|
||||
PrefabInstanceData::DeletePrefabObject(obj);
|
||||
sceneObjects->At(i) = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +90,11 @@ SceneObject* Prefab::GetDefaultInstance(const Guid& objectId)
|
||||
|
||||
if (objectId.IsValid())
|
||||
{
|
||||
const void* object;
|
||||
SceneObject* object;
|
||||
if (ObjectsCache.TryGet(objectId, object))
|
||||
{
|
||||
// Actor or Script
|
||||
return (SceneObject*)object;
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
/// <summary>
|
||||
/// The objects cache maps the id of the object contained in the prefab asset (actor or script) to the default instance deserialized from prefab data. Valid only if asset is loaded and GetDefaultInstance was called.
|
||||
/// </summary>
|
||||
Dictionary<Guid, const void*> ObjectsCache;
|
||||
Dictionary<Guid, SceneObject*> ObjectsCache;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
||||
@@ -76,12 +76,12 @@ Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent)
|
||||
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, nullptr);
|
||||
}
|
||||
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, const void*>* objectsCache, bool withSynchronization)
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*>* objectsCache, bool withSynchronization)
|
||||
{
|
||||
return SpawnPrefab(prefab, Transform(Vector3::Minimum), parent, objectsCache, withSynchronization);
|
||||
}
|
||||
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, const void*>* objectsCache, bool withSynchronization)
|
||||
Actor* PrefabManager::SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*>* objectsCache, bool withSynchronization)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Prefab.Spawn");
|
||||
if (prefab == nullptr)
|
||||
|
||||
@@ -89,7 +89,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager
|
||||
/// <param name="objectsCache">The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script).</param>
|
||||
/// <param name="withSynchronization">True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications.</param>
|
||||
/// <returns>The created actor (root) or null if failed.</returns>
|
||||
static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, const void*, HeapAllocation>* objectsCache, bool withSynchronization = false);
|
||||
static Actor* SpawnPrefab(Prefab* prefab, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = false);
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the instance of the prefab objects. If parent actor is specified then created actors are fully initialized (OnLoad event and BeginPlay is called if parent actor is already during gameplay).
|
||||
@@ -100,7 +100,7 @@ API_CLASS(Static) class FLAXENGINE_API PrefabManager
|
||||
/// <param name="objectsCache">The options output objects cache that can be filled with prefab object id mapping to deserialized object (actor or script).</param>
|
||||
/// <param name="withSynchronization">True if perform prefab changes synchronization for the spawned objects. It will check if need to add new objects due to nested prefab modifications.</param>
|
||||
/// <returns>The created actor (root) or null if failed.</returns>
|
||||
static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, const void*, HeapAllocation>* objectsCache, bool withSynchronization = false);
|
||||
static Actor* SpawnPrefab(Prefab* prefab, const Transform& transform, Actor* parent, Dictionary<Guid, SceneObject*, HeapAllocation>* objectsCache, bool withSynchronization = false);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
|
||||
@@ -11,29 +11,6 @@ String SceneInfo::ToString() const
|
||||
return TEXT("SceneInfo");
|
||||
}
|
||||
|
||||
const int32 lightmapAtlasSizes[] =
|
||||
{
|
||||
32,
|
||||
64,
|
||||
128,
|
||||
256,
|
||||
512,
|
||||
1024,
|
||||
2048,
|
||||
4096
|
||||
};
|
||||
DECLARE_ENUM_8(LightmapAtlasSize, _32, _64, _128, _256, _512, _1024, _2048, _4096);
|
||||
|
||||
LightmapAtlasSize getLightmapAtlasSize(int32 size)
|
||||
{
|
||||
for (int32 i = 0; i < LightmapAtlasSize_Count; i++)
|
||||
{
|
||||
if (lightmapAtlasSizes[i] == size)
|
||||
return (LightmapAtlasSize)i;
|
||||
}
|
||||
return LightmapAtlasSize::_1024;
|
||||
}
|
||||
|
||||
void SceneInfo::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
SERIALIZE_GET_OTHER_OBJ(SceneInfo);
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
#if !BUILD_RELEASE || USE_EDITOR
|
||||
#include "Engine/Level/Level.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Level/Components/MissingScript.h"
|
||||
#include "Engine/Level/Scripts/MissingScript.h"
|
||||
#endif
|
||||
#include "Engine/Level/Scripts/ModelPrefab.h"
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
@@ -46,6 +47,11 @@ void MissingScript::SetReferenceScript(const ScriptingObjectReference<Script>& v
|
||||
|
||||
#endif
|
||||
|
||||
ModelPrefab::ModelPrefab(const SpawnParams& params)
|
||||
: Script(params)
|
||||
{
|
||||
}
|
||||
|
||||
SceneObjectsFactory::Context::Context(ISerializeModifier* modifier)
|
||||
: Modifier(modifier)
|
||||
{
|
||||
@@ -166,7 +172,6 @@ SceneObject* SceneObjectsFactory::Spawn(Context& context, const ISerializable::D
|
||||
return nullptr;
|
||||
}
|
||||
const StringAnsiView typeName(typeNameMember->value.GetStringAnsiView());
|
||||
|
||||
const ScriptingTypeHandle type = Scripting::FindScriptingType(typeName);
|
||||
if (type)
|
||||
{
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
#include "Engine/Core/Cache.h"
|
||||
#include "Engine/Scripting/Script.h"
|
||||
#include "Engine/Scripting/ScriptingObjectReference.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
|
||||
/// <summary>
|
||||
/// Actor script component that represents missing script.
|
||||
29
Source/Engine/Level/Scripts/ModelPrefab.h
Normal file
29
Source/Engine/Level/Scripts/ModelPrefab.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Scripting/Script.h"
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Tools/ModelTool/ModelTool.h"
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Actor script component that handled model prefabs importing and setup.
|
||||
/// </summary>
|
||||
API_CLASS(Attributes="HideInEditor") class FLAXENGINE_API ModelPrefab : public Script
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCRIPTING_TYPE(ModelPrefab);
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Source model file path (absolute or relative to the project).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="ReadOnly") String ImportPath;
|
||||
|
||||
/// <summary>
|
||||
/// Model file import settings.
|
||||
/// </summary>
|
||||
API_FIELD() ModelTool::Options ImportOptions;
|
||||
#endif
|
||||
};
|
||||
@@ -162,7 +162,7 @@ void ENetDriver::Disconnect(const NetworkConnection& connection)
|
||||
}
|
||||
}
|
||||
|
||||
bool ENetDriver::PopEvent(NetworkEvent* eventPtr)
|
||||
bool ENetDriver::PopEvent(NetworkEvent& eventPtr)
|
||||
{
|
||||
ASSERT(_host);
|
||||
ENetEvent event;
|
||||
@@ -173,30 +173,30 @@ bool ENetDriver::PopEvent(NetworkEvent* eventPtr)
|
||||
{
|
||||
// Copy sender data
|
||||
const uint32 connectionId = enet_peer_get_id(event.peer);
|
||||
eventPtr->Sender.ConnectionId = connectionId;
|
||||
eventPtr.Sender.ConnectionId = connectionId;
|
||||
|
||||
switch (event.type)
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
eventPtr->EventType = NetworkEventType::Connected;
|
||||
eventPtr.EventType = NetworkEventType::Connected;
|
||||
if (IsServer())
|
||||
_peerMap.Add(connectionId, event.peer);
|
||||
break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
eventPtr->EventType = NetworkEventType::Disconnected;
|
||||
eventPtr.EventType = NetworkEventType::Disconnected;
|
||||
if (IsServer())
|
||||
_peerMap.Remove(connectionId);
|
||||
break;
|
||||
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
|
||||
eventPtr->EventType = NetworkEventType::Timeout;
|
||||
eventPtr.EventType = NetworkEventType::Timeout;
|
||||
if (IsServer())
|
||||
_peerMap.Remove(connectionId);
|
||||
break;
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
eventPtr->EventType = NetworkEventType::Message;
|
||||
eventPtr->Message = _networkHost->CreateMessage();
|
||||
eventPtr->Message.Length = event.packet->dataLength;
|
||||
Platform::MemoryCopy(eventPtr->Message.Buffer, event.packet->data, event.packet->dataLength);
|
||||
eventPtr.EventType = NetworkEventType::Message;
|
||||
eventPtr.Message = _networkHost->CreateMessage();
|
||||
eventPtr.Message.Length = event.packet->dataLength;
|
||||
Platform::MemoryCopy(eventPtr.Message.Buffer, event.packet->data, event.packet->dataLength);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
bool Connect() override;
|
||||
void Disconnect() override;
|
||||
void Disconnect(const NetworkConnection& connection) override;
|
||||
bool PopEvent(NetworkEvent* eventPtr) override;
|
||||
bool PopEvent(NetworkEvent& eventPtr) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, NetworkConnection target) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets) override;
|
||||
|
||||
@@ -92,7 +92,7 @@ void NetworkLagDriver::Disconnect(const NetworkConnection& connection)
|
||||
_driver->Disconnect(connection);
|
||||
}
|
||||
|
||||
bool NetworkLagDriver::PopEvent(NetworkEvent* eventPtr)
|
||||
bool NetworkLagDriver::PopEvent(NetworkEvent& eventPtr)
|
||||
{
|
||||
if (!_driver)
|
||||
return false;
|
||||
@@ -104,7 +104,7 @@ bool NetworkLagDriver::PopEvent(NetworkEvent* eventPtr)
|
||||
if (e.Lag > 0.0)
|
||||
continue;
|
||||
|
||||
*eventPtr = e.Event;
|
||||
eventPtr = e.Event;
|
||||
_events.RemoveAtKeepOrder(i);
|
||||
return true;
|
||||
}
|
||||
@@ -117,7 +117,7 @@ bool NetworkLagDriver::PopEvent(NetworkEvent* eventPtr)
|
||||
|
||||
auto& e = _events.AddOne();
|
||||
e.Lag = (double)Lag;
|
||||
e.Event = *eventPtr;
|
||||
e.Event = eventPtr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
bool Connect() override;
|
||||
void Disconnect() override;
|
||||
void Disconnect(const NetworkConnection& connection) override;
|
||||
bool PopEvent(NetworkEvent* eventPtr) override;
|
||||
bool PopEvent(NetworkEvent& eventPtr) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, NetworkConnection target) override;
|
||||
void SendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets) override;
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="eventPtr">The pointer to event structure.</param>
|
||||
/// <returns>True when succeeded and the event can be processed.</returns>
|
||||
API_FUNCTION() virtual bool PopEvent(NetworkEvent* eventPtr) = 0;
|
||||
API_FUNCTION() virtual bool PopEvent(API_PARAM(Out) NetworkEvent& eventPtr) = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the server.
|
||||
|
||||
@@ -10,13 +10,19 @@
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConnection
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkConnection);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The identifier of the connection.
|
||||
/// </summary>
|
||||
/// <remarks>Used by network driver implementations.</remarks>
|
||||
API_FIELD()
|
||||
uint32 ConnectionId;
|
||||
API_FIELD() uint32 ConnectionId;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<NetworkConnection>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
inline bool operator==(const NetworkConnection& a, const NetworkConnection& b)
|
||||
|
||||
@@ -43,24 +43,28 @@ API_ENUM(Namespace="FlaxEngine.Networking") enum class NetworkEventType
|
||||
API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkEvent
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkEvent);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The type of the received event.
|
||||
/// </summary>
|
||||
API_FIELD();
|
||||
NetworkEventType EventType;
|
||||
API_FIELD() NetworkEventType EventType;
|
||||
|
||||
/// <summary>
|
||||
/// The message when this event is an "message" event - not valid in any other cases.
|
||||
/// If this is an message-event, make sure to return the message using RecycleMessage function of the peer after processing it!
|
||||
/// </summary>
|
||||
API_FIELD();
|
||||
NetworkMessage Message;
|
||||
API_FIELD() NetworkMessage Message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected of the client that has sent message, connected, disconnected or got a timeout.
|
||||
/// </summary>
|
||||
/// <remarks>Only valid when event has been received on server-peer.</remarks>
|
||||
API_FIELD();
|
||||
NetworkConnection Sender;
|
||||
API_FIELD() NetworkConnection Sender;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TIsPODType<NetworkEvent>
|
||||
{
|
||||
enum { Value = true };
|
||||
};
|
||||
|
||||
@@ -382,7 +382,8 @@ void NetworkManagerService::Update()
|
||||
|
||||
// Process network messages
|
||||
NetworkEvent event;
|
||||
while (peer->PopEvent(event))
|
||||
bool eventIsValid = true;
|
||||
while (peer->PopEvent(event) && eventIsValid)
|
||||
{
|
||||
switch (event.EventType)
|
||||
{
|
||||
@@ -472,6 +473,9 @@ void NetworkManagerService::Update()
|
||||
}
|
||||
peer->RecycleMessage(event.Message);
|
||||
break;
|
||||
default:
|
||||
eventIsValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ void NetworkPeer::Disconnect(const NetworkConnection& connection)
|
||||
bool NetworkPeer::PopEvent(NetworkEvent& eventRef)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
return NetworkDriver->PopEvent(&eventRef);
|
||||
return NetworkDriver->PopEvent(eventRef);
|
||||
}
|
||||
|
||||
NetworkMessage NetworkPeer::CreateMessage()
|
||||
|
||||
@@ -37,30 +37,26 @@ public:
|
||||
/// Once this is called, this peer becomes a server.
|
||||
/// </summary>
|
||||
/// <returns>True when succeeded.</returns>
|
||||
API_FUNCTION()
|
||||
bool Listen();
|
||||
API_FUNCTION() bool Listen();
|
||||
|
||||
/// <summary>
|
||||
/// Starts connection handshake with the end point specified in the <seealso cref="NetworkConfig"/> structure.
|
||||
/// Once this is called, this peer becomes a client.
|
||||
/// </summary>
|
||||
/// <returns>True when succeeded.</returns>
|
||||
API_FUNCTION()
|
||||
bool Connect();
|
||||
API_FUNCTION() bool Connect();
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects from the server.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used only by the client!</remarks>
|
||||
API_FUNCTION()
|
||||
void Disconnect();
|
||||
API_FUNCTION() void Disconnect();
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects given connection from the server.
|
||||
/// </summary>
|
||||
/// <remarks>Can be used only by the server!</remarks>
|
||||
API_FUNCTION()
|
||||
void Disconnect(const NetworkConnection& connection);
|
||||
API_FUNCTION() void Disconnect(const NetworkConnection& connection);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to pop an network event from the queue.
|
||||
@@ -68,8 +64,7 @@ public:
|
||||
/// <param name="eventRef">The reference to event structure.</param>
|
||||
/// <returns>True when succeeded and the event can be processed.</returns>
|
||||
/// <remarks>If this returns message event, make sure to recycle the message using <see cref="RecycleMessage"/> function after processing it!</remarks>
|
||||
API_FUNCTION()
|
||||
bool PopEvent(API_PARAM(out) NetworkEvent& eventRef);
|
||||
API_FUNCTION() bool PopEvent(API_PARAM(Out) NetworkEvent& eventRef);
|
||||
|
||||
/// <summary>
|
||||
/// Acquires new message from the pool.
|
||||
@@ -77,29 +72,25 @@ public:
|
||||
/// </summary>
|
||||
/// <returns>The acquired message.</returns>
|
||||
/// <remarks>Make sure to recycle the message to this peer once it is no longer needed!</remarks>
|
||||
API_FUNCTION()
|
||||
NetworkMessage CreateMessage();
|
||||
API_FUNCTION() NetworkMessage CreateMessage();
|
||||
|
||||
/// <summary>
|
||||
/// Returns given message to the pool.
|
||||
/// </summary>
|
||||
/// <remarks>Make sure that this message belongs to the peer and has not been recycled already (debug build checks for this)!</remarks>
|
||||
API_FUNCTION()
|
||||
void RecycleMessage(const NetworkMessage& message);
|
||||
API_FUNCTION() void RecycleMessage(const NetworkMessage& message);
|
||||
|
||||
/// <summary>
|
||||
/// Acquires new message from the pool and setups it for sending.
|
||||
/// </summary>
|
||||
/// <returns>The acquired message.</returns>
|
||||
API_FUNCTION()
|
||||
NetworkMessage BeginSendMessage();
|
||||
API_FUNCTION() NetworkMessage BeginSendMessage();
|
||||
|
||||
/// <summary>
|
||||
/// Aborts given message send. This effectively deinitializes the message and returns it to the pool.
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
API_FUNCTION()
|
||||
void AbortSendMessage(const NetworkMessage& message);
|
||||
API_FUNCTION() void AbortSendMessage(const NetworkMessage& message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the server.
|
||||
@@ -111,8 +102,7 @@ public:
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
API_FUNCTION()
|
||||
bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message);
|
||||
API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the given client connection (target).
|
||||
@@ -125,8 +115,7 @@ public:
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
API_FUNCTION()
|
||||
bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const NetworkConnection& target);
|
||||
API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const NetworkConnection& target);
|
||||
|
||||
/// <summary>
|
||||
/// Sends given message over specified channel to the given client connection (target).
|
||||
@@ -139,8 +128,7 @@ public:
|
||||
/// Do not recycle the message after calling this.
|
||||
/// This function automatically recycles the message.
|
||||
/// </remarks>
|
||||
API_FUNCTION()
|
||||
bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets);
|
||||
API_FUNCTION() bool EndSendMessage(NetworkChannelType channelType, const NetworkMessage& message, const Array<NetworkConnection, HeapAllocation>& targets);
|
||||
|
||||
/// <summary>
|
||||
/// Creates new peer using given configuration.
|
||||
@@ -148,15 +136,13 @@ public:
|
||||
/// <param name="config">The configuration to create and setup new peer.</param>
|
||||
/// <returns>The peer.</returns>
|
||||
/// <remarks>Peer should be destroyed using <see cref="ShutdownPeer"/> once it is no longer in use. Returns null if failed to create a peer (eg. config is invalid).</remarks>
|
||||
API_FUNCTION()
|
||||
static NetworkPeer* CreatePeer(const NetworkConfig& config);
|
||||
API_FUNCTION() static NetworkPeer* CreatePeer(const NetworkConfig& config);
|
||||
|
||||
/// <summary>
|
||||
/// Shutdowns and destroys given peer.
|
||||
/// </summary>
|
||||
/// <param name="peer">The peer to destroy.</param>
|
||||
API_FUNCTION()
|
||||
static void ShutdownPeer(NetworkPeer* peer);
|
||||
API_FUNCTION() static void ShutdownPeer(NetworkPeer* peer);
|
||||
|
||||
public:
|
||||
bool IsValid() const
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "BoxCollider.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/PhysicsBackend.h"
|
||||
|
||||
BoxCollider::BoxCollider(const SpawnParams& params)
|
||||
@@ -116,24 +115,6 @@ bool BoxCollider::IntersectsItself(const Ray& ray, Real& distance, Vector3& norm
|
||||
return _bounds.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void BoxCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(BoxCollider);
|
||||
|
||||
SERIALIZE_MEMBER(Size, _size);
|
||||
}
|
||||
|
||||
void BoxCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(Size, _size);
|
||||
}
|
||||
|
||||
void BoxCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Colliders/Box Collider\"), ActorToolbox(\"Physics\")")
|
||||
class FLAXENGINE_API BoxCollider : public Collider
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCENE_OBJECT(BoxCollider);
|
||||
private:
|
||||
Float3 _size;
|
||||
@@ -49,8 +50,6 @@ public:
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
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;
|
||||
|
||||
protected:
|
||||
// [Collider]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "CapsuleCollider.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
CapsuleCollider::CapsuleCollider(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
@@ -81,26 +80,6 @@ bool CapsuleCollider::IntersectsItself(const Ray& ray, Real& distance, Vector3&
|
||||
return _orientedBox.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void CapsuleCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(CapsuleCollider);
|
||||
|
||||
SERIALIZE_MEMBER(Radius, _radius);
|
||||
SERIALIZE_MEMBER(Height, _height);
|
||||
}
|
||||
|
||||
void CapsuleCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(Radius, _radius);
|
||||
DESERIALIZE_MEMBER(Height, _height);
|
||||
}
|
||||
|
||||
void CapsuleCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Colliders/Capsule Collider\"), ActorToolbox(\"Physics\")")
|
||||
class FLAXENGINE_API CapsuleCollider : public Collider
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCENE_OBJECT(CapsuleCollider);
|
||||
private:
|
||||
float _radius;
|
||||
@@ -58,8 +59,6 @@ public:
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
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;
|
||||
|
||||
protected:
|
||||
// [Collider]
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Physics/PhysicsBackend.h"
|
||||
#include "Engine/Physics/PhysicsScene.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Engine/Time.h"
|
||||
|
||||
#define CC_MIN_SIZE 0.001f
|
||||
@@ -387,33 +386,3 @@ void CharacterController::OnPhysicsSceneChanged(PhysicsScene* previous)
|
||||
DeleteController();
|
||||
CreateController();
|
||||
}
|
||||
|
||||
void CharacterController::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(CharacterController);
|
||||
|
||||
SERIALIZE_MEMBER(StepOffset, _stepOffset);
|
||||
SERIALIZE_MEMBER(SlopeLimit, _slopeLimit);
|
||||
SERIALIZE_MEMBER(NonWalkableMode, _nonWalkableMode);
|
||||
SERIALIZE_MEMBER(Radius, _radius);
|
||||
SERIALIZE_MEMBER(Height, _height);
|
||||
SERIALIZE_MEMBER(MinMoveDistance, _minMoveDistance);
|
||||
SERIALIZE_MEMBER(UpDirection, _upDirection);
|
||||
}
|
||||
|
||||
void CharacterController::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(StepOffset, _stepOffset);
|
||||
DESERIALIZE_MEMBER(SlopeLimit, _slopeLimit);
|
||||
DESERIALIZE_MEMBER(NonWalkableMode, _nonWalkableMode);
|
||||
DESERIALIZE_MEMBER(Radius, _radius);
|
||||
DESERIALIZE_MEMBER(Height, _height);
|
||||
DESERIALIZE_MEMBER(MinMoveDistance, _minMoveDistance);
|
||||
DESERIALIZE_MEMBER(UpDirection, _upDirection);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Character Controller\"), ActorToolbox(\"Physics\")")
|
||||
class FLAXENGINE_API CharacterController : public Collider, public IPhysicsActor
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCENE_OBJECT(CharacterController);
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -198,8 +199,6 @@ public:
|
||||
#if USE_EDITOR
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
void CreateShape() override;
|
||||
void UpdateBounds() override;
|
||||
void AddMovement(const Vector3& translation, const Quaternion& rotation) override;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Level/Scene/SceneRendering.h"
|
||||
#endif
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/PhysicsSettings.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Physics/PhysicsBackend.h"
|
||||
@@ -292,30 +291,6 @@ void Collider::OnMaterialChanged()
|
||||
PhysicsBackend::SetShapeMaterial(_shape, Material.Get());
|
||||
}
|
||||
|
||||
void Collider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(Collider);
|
||||
|
||||
SERIALIZE_MEMBER(IsTrigger, _isTrigger);
|
||||
SERIALIZE_MEMBER(Center, _center);
|
||||
SERIALIZE_MEMBER(ContactOffset, _contactOffset);
|
||||
SERIALIZE(Material);
|
||||
}
|
||||
|
||||
void Collider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
PhysicsColliderActor::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(IsTrigger, _isTrigger);
|
||||
DESERIALIZE_MEMBER(Center, _center);
|
||||
DESERIALIZE_MEMBER(ContactOffset, _contactOffset);
|
||||
DESERIALIZE(Material);
|
||||
}
|
||||
|
||||
void Collider::BeginPlay(SceneBeginData* data)
|
||||
{
|
||||
// Check if has no shape created (it means no rigidbody requested it but also collider may be spawned at runtime)
|
||||
|
||||
@@ -17,6 +17,7 @@ class RigidBody;
|
||||
/// <seealso cref="PhysicsColliderActor" />
|
||||
API_CLASS(Abstract) class FLAXENGINE_API Collider : public PhysicsColliderActor
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCENE_OBJECT_ABSTRACT(Collider);
|
||||
protected:
|
||||
Vector3 _center;
|
||||
@@ -196,8 +197,6 @@ private:
|
||||
|
||||
public:
|
||||
// [PhysicsColliderActor]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
RigidBody* GetAttachedRigidBody() const override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "MeshCollider.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Physics/PhysicsScene.h"
|
||||
#if USE_EDITOR || !BUILD_RELEASE
|
||||
@@ -117,24 +116,6 @@ bool MeshCollider::IntersectsItself(const Ray& ray, Real& distance, Vector3& nor
|
||||
return _box.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void MeshCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(MeshCollider);
|
||||
|
||||
SERIALIZE(CollisionData);
|
||||
}
|
||||
|
||||
void MeshCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE(CollisionData);
|
||||
}
|
||||
|
||||
void MeshCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Colliders/Mesh Collider\"), ActorToolbox(\"Physics\")")
|
||||
class FLAXENGINE_API MeshCollider : public Collider
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCENE_OBJECT(MeshCollider);
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -33,8 +34,6 @@ public:
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
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;
|
||||
|
||||
protected:
|
||||
// [Collider]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SphereCollider.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
SphereCollider::SphereCollider(const SpawnParams& params)
|
||||
: Collider(params)
|
||||
@@ -58,24 +57,6 @@ bool SphereCollider::IntersectsItself(const Ray& ray, Real& distance, Vector3& n
|
||||
return _sphere.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void SphereCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(SphereCollider);
|
||||
|
||||
SERIALIZE_MEMBER(Radius, _radius);
|
||||
}
|
||||
|
||||
void SphereCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE_MEMBER(Radius, _radius);
|
||||
}
|
||||
|
||||
void SphereCollider::UpdateBounds()
|
||||
{
|
||||
// Cache bounds
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
API_CLASS(Attributes="ActorContextMenu(\"New/Physics/Colliders/Sphere Collider\"), ActorToolbox(\"Physics\")")
|
||||
class FLAXENGINE_API SphereCollider : public Collider
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCENE_OBJECT(SphereCollider);
|
||||
private:
|
||||
float _radius;
|
||||
@@ -38,8 +39,6 @@ public:
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
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;
|
||||
|
||||
protected:
|
||||
// [Collider]
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Math/Ray.h"
|
||||
#include "Engine/Level/Actors/Spline.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Physics/PhysicsBackend.h"
|
||||
#include "Engine/Physics/PhysicsScene.h"
|
||||
@@ -124,26 +123,6 @@ bool SplineCollider::IntersectsItself(const Ray& ray, Real& distance, Vector3& n
|
||||
return _box.Intersects(ray, distance, normal);
|
||||
}
|
||||
|
||||
void SplineCollider::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
{
|
||||
// Base
|
||||
Collider::Serialize(stream, otherObj);
|
||||
|
||||
SERIALIZE_GET_OTHER_OBJ(SplineCollider);
|
||||
|
||||
SERIALIZE(CollisionData);
|
||||
SERIALIZE_MEMBER(PreTransform, _preTransform)
|
||||
}
|
||||
|
||||
void SplineCollider::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
{
|
||||
// Base
|
||||
Collider::Deserialize(stream, modifier);
|
||||
|
||||
DESERIALIZE(CollisionData);
|
||||
DESERIALIZE_MEMBER(PreTransform, _preTransform);
|
||||
}
|
||||
|
||||
void SplineCollider::OnParentChanged()
|
||||
{
|
||||
if (_spline)
|
||||
|
||||
@@ -15,6 +15,7 @@ class Spline;
|
||||
/// <seealso cref="Spline" />
|
||||
API_CLASS() class FLAXENGINE_API SplineCollider : public Collider
|
||||
{
|
||||
API_AUTO_SERIALIZATION();
|
||||
DECLARE_SCENE_OBJECT(SplineCollider);
|
||||
private:
|
||||
Spline* _spline = nullptr;
|
||||
@@ -61,8 +62,6 @@ public:
|
||||
void OnDebugDrawSelected() override;
|
||||
#endif
|
||||
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;
|
||||
void OnParentChanged() override;
|
||||
void EndPlay() override;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user