Merge remote-tracking branch 'origin/master' into 1.6
# Conflicts: # .github/workflows/tests.yml # Source/Engine/Content/JsonAsset.cs
This commit is contained in:
BIN
Content/Shaders/DebugDraw.flax
(Stored with Git LFS)
BIN
Content/Shaders/DebugDraw.flax
(Stored with Git LFS)
Binary file not shown.
@@ -45,7 +45,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
var upperRightCell = new VerticalPanel
|
||||
{
|
||||
ClipChildren = false,
|
||||
Pivot = new Float2(0.0f, 0.0f),
|
||||
Pivot = new Float2(0.00001f, 0.0f),
|
||||
Offset = new Float2(-labelsWidth, 0),
|
||||
Rotation = -90,
|
||||
Spacing = 0,
|
||||
|
||||
@@ -145,7 +145,10 @@ namespace FlaxEditor.CustomEditors.Dedicated
|
||||
else
|
||||
{
|
||||
// No localization so initialize with empty table
|
||||
var path = Path.Combine(Path.Combine(Path.GetDirectoryName(GameSettings.Load().Localization.Path), "Localization", culture.Name + ".json"));
|
||||
var folder = Path.Combine(Path.GetDirectoryName(GameSettings.Load().Localization.Path), "Localization");
|
||||
if (!Directory.Exists(folder))
|
||||
Directory.CreateDirectory(folder);
|
||||
var path = Path.Combine(Path.Combine(folder, culture.Name + ".json"));
|
||||
var table = FlaxEngine.Content.CreateVirtualAsset<LocalizedStringTable>();
|
||||
table.Locale = culture.Name;
|
||||
if (!table.Save(path))
|
||||
|
||||
@@ -10,7 +10,6 @@ using FlaxEditor.Content;
|
||||
using FlaxEditor.Content.Import;
|
||||
using FlaxEditor.Content.Settings;
|
||||
using FlaxEditor.Content.Thumbnails;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.Modules;
|
||||
using FlaxEditor.Modules.SourceCodeEditing;
|
||||
using FlaxEditor.Options;
|
||||
@@ -314,6 +313,7 @@ namespace FlaxEditor
|
||||
_areModulesInited = true;
|
||||
|
||||
// Preload initial scene asset
|
||||
try
|
||||
{
|
||||
var startupSceneMode = Options.Options.General.StartupSceneMode;
|
||||
if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene))
|
||||
@@ -330,12 +330,23 @@ namespace FlaxEditor
|
||||
}
|
||||
case GeneralOptions.StartupSceneModes.LastOpened:
|
||||
{
|
||||
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId))
|
||||
Internal_LoadAsset(ref lastSceneId);
|
||||
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName))
|
||||
{
|
||||
var lastScenes = JsonSerializer.Deserialize<Guid[]>(lastSceneIdName);
|
||||
foreach (var scene in lastScenes)
|
||||
{
|
||||
var lastScene = scene;
|
||||
Internal_LoadAsset(ref lastScene);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
InitializationStart?.Invoke();
|
||||
|
||||
@@ -408,58 +419,69 @@ namespace FlaxEditor
|
||||
}
|
||||
|
||||
// Load scene
|
||||
|
||||
// scene cmd line argument
|
||||
var scene = ContentDatabase.Find(_startupSceneCmdLine);
|
||||
if (scene is SceneItem)
|
||||
try
|
||||
{
|
||||
Editor.Log("Loading scene specified in command line");
|
||||
Scene.OpenScene(_startupSceneCmdLine);
|
||||
return;
|
||||
}
|
||||
|
||||
// if no scene cmd line argument is provided
|
||||
var startupSceneMode = Options.Options.General.StartupSceneMode;
|
||||
if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene))
|
||||
{
|
||||
// Fallback to default project scene if nothing saved in the cache
|
||||
startupSceneMode = GeneralOptions.StartupSceneModes.ProjectDefault;
|
||||
}
|
||||
switch (startupSceneMode)
|
||||
{
|
||||
case GeneralOptions.StartupSceneModes.ProjectDefault:
|
||||
{
|
||||
if (string.IsNullOrEmpty(GameProject.DefaultScene))
|
||||
break;
|
||||
JsonSerializer.ParseID(GameProject.DefaultScene, out var defaultSceneId);
|
||||
var defaultScene = ContentDatabase.Find(defaultSceneId);
|
||||
if (defaultScene is SceneItem)
|
||||
// Scene cmd line argument
|
||||
var scene = ContentDatabase.Find(_startupSceneCmdLine);
|
||||
if (scene is SceneItem)
|
||||
{
|
||||
Editor.Log("Loading default project scene");
|
||||
Scene.OpenScene(defaultSceneId);
|
||||
|
||||
// Use spawn point
|
||||
Windows.EditWin.Viewport.ViewRay = GameProject.DefaultSceneSpawn;
|
||||
Editor.Log("Loading scene specified in command line");
|
||||
Scene.OpenScene(_startupSceneCmdLine);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GeneralOptions.StartupSceneModes.LastOpened:
|
||||
{
|
||||
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName) && Guid.TryParse(lastSceneIdName, out var lastSceneId))
|
||||
var startupSceneMode = Options.Options.General.StartupSceneMode;
|
||||
if (startupSceneMode == GeneralOptions.StartupSceneModes.LastOpened && !ProjectCache.HasCustomData(ProjectDataLastScene))
|
||||
{
|
||||
var lastScene = ContentDatabase.Find(lastSceneId);
|
||||
if (lastScene is SceneItem)
|
||||
// Fallback to default project scene if nothing saved in the cache
|
||||
startupSceneMode = GeneralOptions.StartupSceneModes.ProjectDefault;
|
||||
}
|
||||
switch (startupSceneMode)
|
||||
{
|
||||
case GeneralOptions.StartupSceneModes.ProjectDefault:
|
||||
{
|
||||
if (string.IsNullOrEmpty(GameProject.DefaultScene))
|
||||
break;
|
||||
JsonSerializer.ParseID(GameProject.DefaultScene, out var defaultSceneId);
|
||||
var defaultScene = ContentDatabase.Find(defaultSceneId);
|
||||
if (defaultScene is SceneItem)
|
||||
{
|
||||
Editor.Log("Loading last opened scene");
|
||||
Scene.OpenScene(lastSceneId);
|
||||
Editor.Log("Loading default project scene");
|
||||
Scene.OpenScene(defaultSceneId);
|
||||
|
||||
// Use spawn point
|
||||
Windows.EditWin.Viewport.ViewRay = GameProject.DefaultSceneSpawn;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GeneralOptions.StartupSceneModes.LastOpened:
|
||||
{
|
||||
if (ProjectCache.TryGetCustomData(ProjectDataLastScene, out var lastSceneIdName))
|
||||
{
|
||||
var lastScenes = JsonSerializer.Deserialize<Guid[]>(lastSceneIdName);
|
||||
foreach (var sceneId in lastScenes)
|
||||
{
|
||||
var lastScene = ContentDatabase.Find(sceneId);
|
||||
if (!(lastScene is SceneItem))
|
||||
continue;
|
||||
|
||||
Editor.Log($"Loading last opened scene: {lastScene.ShortName}");
|
||||
if (sceneId == lastScenes[0])
|
||||
Scene.OpenScene(sceneId);
|
||||
else
|
||||
Level.LoadSceneAsync(sceneId);
|
||||
}
|
||||
|
||||
// Restore view
|
||||
if (ProjectCache.TryGetCustomData(ProjectDataLastSceneSpawn, out var lastSceneSpawnName))
|
||||
Windows.EditWin.Viewport.ViewRay = JsonSerializer.Deserialize<Ray>(lastSceneSpawnName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,11 +691,14 @@ namespace FlaxEditor
|
||||
// Start exit
|
||||
StateMachine.GoToState<ClosingState>();
|
||||
|
||||
// Cache last opened scene
|
||||
// Cache last opened scenes
|
||||
{
|
||||
var lastSceneId = Level.ScenesCount > 0 ? Level.Scenes[0].ID : Guid.Empty;
|
||||
var lastScenes = Level.Scenes;
|
||||
var lastSceneIds = new Guid[lastScenes.Length];
|
||||
for (int i = 0; i < lastScenes.Length; i++)
|
||||
lastSceneIds[i] = lastScenes[i].ID;
|
||||
var lastSceneSpawn = Windows.EditWin.Viewport.ViewRay;
|
||||
ProjectCache.SetCustomData(ProjectDataLastScene, lastSceneId.ToString());
|
||||
ProjectCache.SetCustomData(ProjectDataLastScene, JsonSerializer.Serialize(lastSceneIds));
|
||||
ProjectCache.SetCustomData(ProjectDataLastSceneSpawn, JsonSerializer.Serialize(lastSceneSpawn));
|
||||
}
|
||||
|
||||
|
||||
@@ -626,7 +626,16 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
{
|
||||
var item = _dragAssets.Objects[i];
|
||||
var actor = item.OnEditorDrop(this);
|
||||
actor.StaticFlags = spawnParent.StaticFlags;
|
||||
if (spawnParent.GetType() != typeof(Scene))
|
||||
{
|
||||
// Set all Actors static flags to match parents
|
||||
List<Actor> childActors = new List<Actor>();
|
||||
GetActorsTree(childActors, actor);
|
||||
foreach (var child in childActors)
|
||||
{
|
||||
child.StaticFlags = spawnParent.StaticFlags;
|
||||
}
|
||||
}
|
||||
actor.Name = item.ShortName;
|
||||
actor.Transform = spawnParent.Transform;
|
||||
ActorNode.Root.Spawn(actor, spawnParent);
|
||||
@@ -667,6 +676,16 @@ namespace FlaxEditor.SceneGraph.GUI
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void GetActorsTree(List<Actor> list, Actor a)
|
||||
{
|
||||
list.Add(a);
|
||||
int cnt = a.ChildrenCount;
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
GetActorsTree(list, a.GetChild(i));
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateDragActor(ActorNode actorNode)
|
||||
{
|
||||
|
||||
@@ -730,12 +730,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Float(30, 0, 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Float(30, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Max", true, typeof(float), 2, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -753,14 +749,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Vector_X(30, 0, 0),
|
||||
NodeElementArchetype.Factory.Vector_Y(83, 0, 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Vector_X(30, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Vector_Y(83, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float2), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float2), 2, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -778,16 +768,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Vector_X(30, 0, 0),
|
||||
NodeElementArchetype.Factory.Vector_Y(83, 0, 0),
|
||||
NodeElementArchetype.Factory.Vector_Z(136, 0, 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Vector_X(30, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Vector_Y(83, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Vector_Z(136, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float3), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float3), 2, 1),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -805,18 +787,8 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float4), 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, 0, "Min", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Vector_X(30, 0, 0),
|
||||
NodeElementArchetype.Factory.Vector_Y(83, 0, 0),
|
||||
NodeElementArchetype.Factory.Vector_Z(136, 0, 0),
|
||||
NodeElementArchetype.Factory.Vector_W(189, 0, 0),
|
||||
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY, "Max", 30.0f, 18.0f),
|
||||
NodeElementArchetype.Factory.Vector_X(30, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Vector_Y(83, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Vector_Z(136, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Vector_W(189, Surface.Constants.LayoutOffsetY, 1),
|
||||
NodeElementArchetype.Factory.Input(0, "Min", true, typeof(Float4), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Max", true, typeof(Float4), 2, 1),
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ AssetReference<MaterialInstance> CustomTextureMaterial;
|
||||
ModelInstanceEntries InstanceBuffers[static_cast<int32>(IconTypes::MAX)];
|
||||
Dictionary<ScriptingTypeHandle, IconTypes> ActorTypeToIconType;
|
||||
Dictionary<ScriptingTypeHandle, AssetReference<Texture>> ActorTypeToTexture;
|
||||
Dictionary<ScriptingObjectReference<Actor>, AssetReference<Texture>> ActorToTexture;
|
||||
Dictionary<AssetReference<Texture>, AssetReference<MaterialBase>> TextureToMaterial;
|
||||
|
||||
class ViewportIconsRendererService : public EngineService
|
||||
{
|
||||
@@ -103,10 +105,18 @@ void ViewportIconsRenderer::AddActor(Actor* actor)
|
||||
actor->GetSceneRendering()->AddViewportIcon(actor);
|
||||
}
|
||||
|
||||
void ViewportIconsRenderer::AddActorWithTexture(Actor* actor, Texture* iconTexture)
|
||||
{
|
||||
CHECK(actor && actor->GetScene() && iconTexture);
|
||||
ActorToTexture[actor] = iconTexture;
|
||||
actor->GetSceneRendering()->AddViewportIcon(actor);
|
||||
}
|
||||
|
||||
void ViewportIconsRenderer::RemoveActor(Actor* actor)
|
||||
{
|
||||
CHECK(actor && actor->GetScene());
|
||||
actor->GetSceneRendering()->RemoveViewportIcon(actor);
|
||||
ActorToTexture.Remove(actor);
|
||||
}
|
||||
|
||||
void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene* scene, Mesh::DrawInfo& draw)
|
||||
@@ -128,7 +138,7 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
|
||||
ScriptingTypeHandle typeHandle = icon->GetTypeHandle();
|
||||
draw.Buffer = nullptr;
|
||||
|
||||
if (ActorTypeToTexture.TryGet(typeHandle, texture))
|
||||
if (ActorToTexture.TryGet(icon, texture) || ActorTypeToTexture.TryGet(typeHandle, texture))
|
||||
{
|
||||
// Use custom texture
|
||||
draw.Buffer = &InstanceBuffers[static_cast<int32>(IconTypes::CustomTexture)];
|
||||
@@ -137,9 +147,20 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Scene
|
||||
// Lazy-init (use in-built icon material with custom texture)
|
||||
draw.Buffer->Setup(1);
|
||||
draw.Buffer->At(0).ReceiveDecals = false;
|
||||
draw.Buffer->At(0).Material = InstanceBuffers[0][0].Material->CreateVirtualInstance();
|
||||
draw.Buffer->At(0).ShadowsMode = ShadowsCastingMode::None;
|
||||
}
|
||||
draw.Buffer->At(0).Material->SetParameterValue(TEXT("Image"), Variant(texture));
|
||||
|
||||
AssetReference<MaterialBase> material;
|
||||
|
||||
if (!TextureToMaterial.TryGet(texture, material))
|
||||
{
|
||||
// Create custom material per custom texture
|
||||
TextureToMaterial[texture] = InstanceBuffers[0][0].Material->CreateVirtualInstance();
|
||||
TextureToMaterial[texture]->SetParameterValue(TEXT("Image"), Variant(texture));
|
||||
material = TextureToMaterial[texture];
|
||||
}
|
||||
|
||||
draw.Buffer->At(0).Material = material;
|
||||
}
|
||||
else if (ActorTypeToIconType.TryGet(typeHandle, iconType))
|
||||
{
|
||||
@@ -170,6 +191,8 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
|
||||
Matrix m1, m2, world;
|
||||
BoundingSphere sphere(actor->GetPosition() - renderContext.View.Origin, ICON_RADIUS);
|
||||
IconTypes iconType;
|
||||
AssetReference<Texture> texture;
|
||||
|
||||
if (frustum.Intersects(sphere) && ActorTypeToIconType.TryGet(actor->GetTypeHandle(), iconType))
|
||||
{
|
||||
// Create world matrix
|
||||
@@ -182,7 +205,39 @@ void ViewportIconsRendererService::DrawIcons(RenderContext& renderContext, Actor
|
||||
// Draw icon
|
||||
GeometryDrawStateData drawState;
|
||||
draw.DrawState = &drawState;
|
||||
draw.Buffer = &InstanceBuffers[static_cast<int32>(iconType)];
|
||||
|
||||
// Support custom icons through types, but not onces that were added through actors,
|
||||
// since they cant register while in prefab view anyway
|
||||
if (ActorTypeToTexture.TryGet(actor->GetTypeHandle(), texture))
|
||||
{
|
||||
// Use custom texture
|
||||
draw.Buffer = &InstanceBuffers[static_cast<int32>(IconTypes::CustomTexture)];
|
||||
if (draw.Buffer->Count() == 0)
|
||||
{
|
||||
// Lazy-init (use in-built icon material with custom texture)
|
||||
draw.Buffer->Setup(1);
|
||||
draw.Buffer->At(0).ReceiveDecals = false;
|
||||
draw.Buffer->At(0).ShadowsMode = ShadowsCastingMode::None;
|
||||
}
|
||||
|
||||
AssetReference<MaterialBase> material;
|
||||
|
||||
if (!TextureToMaterial.TryGet(texture, material))
|
||||
{
|
||||
// Create custom material per custom texture
|
||||
TextureToMaterial[texture] = InstanceBuffers[0][0].Material->CreateVirtualInstance();
|
||||
TextureToMaterial[texture]->SetParameterValue(TEXT("Image"), Variant(texture));
|
||||
material = TextureToMaterial[texture];
|
||||
}
|
||||
|
||||
draw.Buffer->At(0).Material = material;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use predefined material
|
||||
draw.Buffer = &InstanceBuffers[static_cast<int32>(iconType)];
|
||||
}
|
||||
|
||||
draw.World = &world;
|
||||
draw.Bounds = sphere;
|
||||
QuadModel->Draw(renderContext, draw);
|
||||
@@ -196,9 +251,10 @@ bool ViewportIconsRendererService::Init()
|
||||
{
|
||||
QuadModel = Content::LoadAsyncInternal<Model>(TEXT("Engine/Models/Quad"));
|
||||
#define INIT(type, path) \
|
||||
InstanceBuffers[static_cast<int32>(IconTypes::type)].Setup(1); \
|
||||
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].ReceiveDecals = false; \
|
||||
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].Material = Content::LoadAsyncInternal<MaterialInstance>(TEXT(path))
|
||||
InstanceBuffers[static_cast<int32>(IconTypes::type)].Setup(1); \
|
||||
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].ReceiveDecals = false; \
|
||||
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].ShadowsMode = ShadowsCastingMode::None; \
|
||||
InstanceBuffers[static_cast<int32>(IconTypes::type)][0].Material = Content::LoadAsyncInternal<MaterialInstance>(TEXT(path))
|
||||
INIT(PointLight, "Editor/Icons/PointLight");
|
||||
INIT(DirectionalLight, "Editor/Icons/DirectionalLight");
|
||||
INIT(EnvironmentProbe, "Editor/Icons/EnvironmentProbe");
|
||||
@@ -236,4 +292,6 @@ void ViewportIconsRendererService::Dispose()
|
||||
for (int32 i = 0; i < ARRAY_COUNT(InstanceBuffers); i++)
|
||||
InstanceBuffers[i].Release();
|
||||
ActorTypeToIconType.Clear();
|
||||
ActorToTexture.Clear();
|
||||
TextureToMaterial.Clear();
|
||||
}
|
||||
|
||||
@@ -37,6 +37,13 @@ public:
|
||||
/// <param name="actor">The actor to register for icon drawing.</param>
|
||||
API_FUNCTION() static void AddActor(Actor* actor);
|
||||
|
||||
/// <summary>
|
||||
/// Adds actor to the viewport icon rendering.
|
||||
/// </summary>
|
||||
/// <param name="actor">The actor to register for icon drawing.</param>
|
||||
/// <param name="iconTexture">The icon texture to draw.</param>
|
||||
API_FUNCTION() static void AddActorWithTexture(Actor* actor, Texture* iconTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Removes actor from the viewport icon rendering.
|
||||
/// </summary>
|
||||
|
||||
@@ -126,6 +126,10 @@ namespace FlaxEditor.Windows.Assets
|
||||
_presenter.NoSelectionText = "Failed to load asset. See log for more. " + ex.Message.Replace('\n', ' ');
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(dataTypeName))
|
||||
{
|
||||
_presenter.NoSelectionText = "Empty data type.";
|
||||
}
|
||||
else
|
||||
{
|
||||
_presenter.NoSelectionText = string.Format("Missing type '{0}'.", dataTypeName);
|
||||
|
||||
@@ -489,7 +489,10 @@ namespace FlaxEditor.Windows
|
||||
/// <param name="item">The item to delete.</param>
|
||||
public void Delete(ContentItem item)
|
||||
{
|
||||
Delete(Editor.Instance.Windows.ContentWin.View.Selection);
|
||||
var items = View.Selection;
|
||||
if (items.Count == 0)
|
||||
items = new List<ContentItem>() { item };
|
||||
Delete(items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -657,6 +660,11 @@ namespace FlaxEditor.Windows
|
||||
throw new ArgumentNullException(nameof(proxy));
|
||||
|
||||
string name = initialName ?? proxy.NewItemName;
|
||||
|
||||
// If the proxy can not be created in the current folder, then navigate to the content folder
|
||||
if (!proxy.CanCreate(CurrentViewFolder))
|
||||
Navigate(Editor.Instance.ContentDatabase.Game.Content);
|
||||
|
||||
ContentFolder parentFolder = CurrentViewFolder;
|
||||
string parentFolderPath = parentFolder.Path;
|
||||
|
||||
|
||||
@@ -1002,17 +1002,12 @@ Asset* Content::load(const Guid& id, const ScriptingTypeHandle& type, AssetInfo&
|
||||
}
|
||||
|
||||
#if ASSETS_LOADING_EXTRA_VERIFICATION
|
||||
|
||||
// Ensure we have valid asset info
|
||||
ASSERT(assetInfo.TypeName.HasChars() && assetInfo.Path.HasChars());
|
||||
|
||||
// Check if file exists
|
||||
if (!FileSystem::FileExists(assetInfo.Path))
|
||||
{
|
||||
LOG(Error, "Cannot find file '{0}'", assetInfo.Path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Find asset factory based in its type
|
||||
|
||||
@@ -35,6 +35,11 @@ namespace FlaxEngine
|
||||
return null;
|
||||
|
||||
var dataTypeName = DataTypeName;
|
||||
if (string.IsNullOrEmpty(dataTypeName))
|
||||
{
|
||||
Debug.LogError(string.Format("Missing typename of data in Json asset '{0}'.", Path), this);
|
||||
return null;
|
||||
}
|
||||
var assemblies = Utils.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
|
||||
@@ -637,9 +637,10 @@ void DebugDrawService::Update()
|
||||
desc.VS = shader->GetVS("VS");
|
||||
|
||||
// Default
|
||||
desc.PS = shader->GetPS("PS");
|
||||
desc.PS = shader->GetPS("PS", 0);
|
||||
desc.PrimitiveTopologyType = PrimitiveTopologyType::Line;
|
||||
failed |= DebugDrawPsLinesDefault.Create(desc);
|
||||
desc.PS = shader->GetPS("PS", 1);
|
||||
desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle;
|
||||
failed |= DebugDrawPsTrianglesDefault.Create(desc);
|
||||
desc.Wireframe = true;
|
||||
@@ -647,9 +648,10 @@ void DebugDrawService::Update()
|
||||
|
||||
// Depth Test
|
||||
desc.Wireframe = false;
|
||||
desc.PS = shader->GetPS("PS_DepthTest");
|
||||
desc.PS = shader->GetPS("PS", 2);
|
||||
desc.PrimitiveTopologyType = PrimitiveTopologyType::Line;
|
||||
failed |= DebugDrawPsLinesDepthTest.Create(desc);
|
||||
desc.PS = shader->GetPS("PS", 3);
|
||||
desc.PrimitiveTopologyType = PrimitiveTopologyType::Triangle;
|
||||
failed |= DebugDrawPsTrianglesDepthTest.Create(desc);
|
||||
desc.Wireframe = true;
|
||||
|
||||
@@ -903,12 +903,11 @@ bool LevelImpl::unloadScene(Scene* scene)
|
||||
bool LevelImpl::unloadScenes()
|
||||
{
|
||||
auto scenes = Level::Scenes;
|
||||
for (int32 i = 0; i < scenes.Count(); i++)
|
||||
for (int32 i = scenes.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
if (unloadScene(scenes[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1210,6 +1209,15 @@ bool LevelImpl::saveScene(Scene* scene, const String& path)
|
||||
|
||||
LOG(Info, "Scene saved! Time {0} ms", Math::CeilToInt((float)(DateTime::NowUTC() - startTime).GetTotalMilliseconds()));
|
||||
|
||||
#if USE_EDITOR
|
||||
// Reload asset at the target location if is loaded
|
||||
Asset* asset = Content::GetAsset(sceneId);
|
||||
if (!asset)
|
||||
asset = Content::GetAsset(path);
|
||||
if (asset)
|
||||
asset->Reload();
|
||||
#endif
|
||||
|
||||
// Fire event
|
||||
CallSceneEvent(SceneEventType::OnSceneSaved, scene, sceneId);
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ public:
|
||||
Dictionary<Guid, int32> PrefabInstanceIdToDataIndex;
|
||||
|
||||
public:
|
||||
typedef Array<PrefabInstanceData> PrefabInstancesData;
|
||||
|
||||
/// <summary>
|
||||
/// Collects all the valid prefab instances to update on prefab data synchronization.
|
||||
/// </summary>
|
||||
@@ -138,15 +140,15 @@ public:
|
||||
/// <param name="prefabId">The prefab asset identifier.</param>
|
||||
/// <param name="defaultInstance">The default instance (prefab internal, can be null).</param>
|
||||
/// <param name="targetActor">The target actor (optional actor to skip for counting, can be null).</param>
|
||||
static void CollectPrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor);
|
||||
static void CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor);
|
||||
|
||||
/// <summary>
|
||||
/// Serializes all the prefab instances local changes to restore on prefab data synchronization.
|
||||
/// </summary>
|
||||
/// <param name="prefabInstancesData">The prefab instances data.</param>
|
||||
/// <param name="tmpBuffer">The temporary json buffer (cleared before and after usage).</param>
|
||||
/// <param name="sceneObjects">The scene objects collection cache (cleared before and after usage).</param>
|
||||
static void SerializePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, SceneObjectsListCacheType& sceneObjects);
|
||||
/// <param name="prefab">Source prefab.</param>
|
||||
static void SerializePrefabInstances(PrefabInstancesData& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, const Prefab* prefab);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances.
|
||||
@@ -158,7 +160,7 @@ public:
|
||||
/// <param name="prefabObjectIdToDiffData">The hash table that maps the prefab object id to json data for the given prefab object.</param>
|
||||
/// <param name="newPrefabObjectIds">The collection of the new prefab objects ids added to prefab during changes synchronization or modifications apply.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
static bool SynchronizePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds);
|
||||
static bool SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds);
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes the prefab instances by applying changes from the diff data and restoring the local changes captured by SerializePrefabInstances.
|
||||
@@ -171,10 +173,10 @@ public:
|
||||
/// <param name="oldObjectsIds">Collection with ids of the objects (actors and scripts) from the prefab before 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>
|
||||
/// <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(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds);
|
||||
static bool SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds);
|
||||
};
|
||||
|
||||
void PrefabInstanceData::CollectPrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor)
|
||||
void PrefabInstanceData::CollectPrefabInstances(PrefabInstancesData& prefabInstancesData, const Guid& prefabId, Actor* defaultInstance, Actor* targetActor)
|
||||
{
|
||||
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
|
||||
if (PrefabManager::PrefabsReferences.ContainsKey(prefabId))
|
||||
@@ -207,11 +209,12 @@ void PrefabInstanceData::CollectPrefabInstances(Array<PrefabInstanceData>& prefa
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabInstanceData::SerializePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, SceneObjectsListCacheType& sceneObjects)
|
||||
void PrefabInstanceData::SerializePrefabInstances(PrefabInstancesData& prefabInstancesData, rapidjson_flax::StringBuffer& tmpBuffer, const Prefab* prefab)
|
||||
{
|
||||
if (prefabInstancesData.IsEmpty())
|
||||
return;
|
||||
|
||||
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
|
||||
sceneObjects->EnsureCapacity(prefab->ObjectsCount * 4);
|
||||
for (int32 dataIndex = 0; dataIndex < prefabInstancesData.Count(); dataIndex++)
|
||||
{
|
||||
auto& instance = prefabInstancesData[dataIndex];
|
||||
@@ -253,12 +256,10 @@ void PrefabInstanceData::SerializePrefabInstances(Array<PrefabInstanceData>& pre
|
||||
instance.PrefabInstanceIdToDataIndex.Add(obj->GetSceneObjectId(), i);
|
||||
}
|
||||
}
|
||||
|
||||
sceneObjects->Clear();
|
||||
tmpBuffer.Clear();
|
||||
}
|
||||
|
||||
bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds)
|
||||
bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, const IdToDataLookupType& prefabObjectIdToDiffData, const Array<Guid>& newPrefabObjectIds)
|
||||
{
|
||||
for (int32 instanceIndex = 0; instanceIndex < prefabInstancesData.Count(); instanceIndex++)
|
||||
{
|
||||
@@ -492,7 +493,7 @@ bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& p
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PrefabInstanceData::SynchronizePrefabInstances(Array<PrefabInstanceData>& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds)
|
||||
bool PrefabInstanceData::SynchronizePrefabInstances(PrefabInstancesData& prefabInstancesData, Actor* defaultInstance, SceneObjectsListCacheType& sceneObjects, const Guid& prefabId, rapidjson_flax::StringBuffer& tmpBuffer, const Array<Guid>& oldObjectsIds, const Array<Guid>& newObjectIds)
|
||||
{
|
||||
if (prefabInstancesData.IsEmpty())
|
||||
return false;
|
||||
@@ -588,8 +589,6 @@ bool FindCyclicReferences(Actor* actor, const Guid& prefabRootId)
|
||||
|
||||
bool Prefab::ApplyAll(Actor* targetActor)
|
||||
{
|
||||
// TODO: use more cached dictionaries and other collections containers to prevent memory allocations during apply (optimize for apply 10 times per second the same prefab on many changes in editor)
|
||||
|
||||
PROFILE_CPU();
|
||||
const auto startTime = DateTime::NowUTC();
|
||||
|
||||
@@ -695,22 +694,40 @@ bool Prefab::ApplyAll(Actor* targetActor)
|
||||
|
||||
ObjectsRemovalService::Flush();
|
||||
|
||||
// Collect existing prefab instances (this and nested ones) to cache 'before' state used later to restore it
|
||||
PrefabInstancesData thisPrefabInstancesData;
|
||||
Array<PrefabInstancesData> allPrefabsInstancesData;
|
||||
{
|
||||
PROFILE_CPU_NAMED("Prefab.CachePrefabInstancesData");
|
||||
|
||||
rapidjson_flax::StringBuffer dataBuffer;
|
||||
PrefabInstanceData::CollectPrefabInstances(thisPrefabInstancesData, GetID(), _defaultInstance, targetActor);
|
||||
PrefabInstanceData::SerializePrefabInstances(thisPrefabInstancesData, dataBuffer, this);
|
||||
|
||||
allPrefabsInstancesData.Resize(allPrefabs.Count());
|
||||
for (int32 i = 0; i < allPrefabs.Count(); i++)
|
||||
{
|
||||
Prefab* prefab = allPrefabs[i];
|
||||
PrefabInstanceData::CollectPrefabInstances(allPrefabsInstancesData[i], prefab->GetID(), prefab->GetDefaultInstance(), prefab->GetDefaultInstance());
|
||||
PrefabInstanceData::SerializePrefabInstances(allPrefabsInstancesData[i], dataBuffer, prefab);
|
||||
}
|
||||
}
|
||||
|
||||
// Use internal call to improve shared collections memory sharing
|
||||
if (ApplyAllInternal(targetActor, true))
|
||||
if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData))
|
||||
return true;
|
||||
|
||||
SyncNestedPrefabs(allPrefabs);
|
||||
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
|
||||
|
||||
const auto endTime = DateTime::NowUTC();
|
||||
LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab)
|
||||
bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Prefab.Apply");
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
const auto prefabId = GetID();
|
||||
|
||||
// Gather all scene objects in target instance (reused later)
|
||||
@@ -719,9 +736,7 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
||||
SceneQuery::GetAllSerializableSceneObjects(targetActor, *targetObjects.Value);
|
||||
if (PrefabManager::FilterPrefabInstancesToSave(prefabId, *targetObjects.Value))
|
||||
return true;
|
||||
|
||||
LOG(Info, "Applying prefab changes from actor {0} (total objects count: {2}) to {1}...", targetActor->ToString(), ToString(), targetObjects->Count());
|
||||
|
||||
Array<Guid> oldObjectsIds = ObjectsIds;
|
||||
|
||||
// Serialize to json data
|
||||
@@ -809,17 +824,8 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
|
||||
// TODO: what if user applied prefab with references to the other objects from scene? clear them or what?
|
||||
JsonTools::ChangeIds(diffDataDocument, objectInstanceIdToPrefabObjectId);
|
||||
}
|
||||
|
||||
dataBuffer.Clear();
|
||||
|
||||
// Fetch all existing prefab instances
|
||||
Array<PrefabInstanceData> prefabInstancesData;
|
||||
PrefabInstanceData::CollectPrefabInstances(prefabInstancesData, prefabId, _defaultInstance, targetActor);
|
||||
|
||||
// Serialize all prefab instances data (temporary storage)
|
||||
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
|
||||
sceneObjects->EnsureCapacity(ObjectsCount * 4);
|
||||
PrefabInstanceData::SerializePrefabInstances(prefabInstancesData, dataBuffer, sceneObjects);
|
||||
|
||||
// Destroy default instance and some cache data in Prefab
|
||||
DeleteDefaultInstance();
|
||||
@@ -1204,18 +1210,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Prefab::SyncChanges(const NestedPrefabsList& allPrefabs)
|
||||
{
|
||||
// Use internal call to improve shared collections memory sharing
|
||||
if (SyncChangesInternal())
|
||||
return true;
|
||||
|
||||
SyncNestedPrefabs(allPrefabs);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Prefab::SyncChangesInternal()
|
||||
bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Prefab.SyncChanges");
|
||||
|
||||
@@ -1252,10 +1247,10 @@ bool Prefab::SyncChangesInternal()
|
||||
AutoActorCleanup cleanupDefaultInstance(targetActor);
|
||||
|
||||
// Apply changes
|
||||
return ApplyAllInternal(targetActor, false);
|
||||
return ApplyAllInternal(targetActor, false, prefabInstancesData);
|
||||
}
|
||||
|
||||
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs)
|
||||
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const
|
||||
{
|
||||
PROFILE_CPU();
|
||||
LOG(Info, "Updating referencing prefabs");
|
||||
@@ -1274,9 +1269,13 @@ void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nestedPrefab->NestedPrefabs.Contains(GetID()))
|
||||
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
|
||||
if (nestedPrefabIndex != -1)
|
||||
{
|
||||
nestedPrefab->SyncChanges(allPrefabs);
|
||||
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[nestedPrefabIndex]))
|
||||
continue;
|
||||
|
||||
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
|
||||
|
||||
ObjectsRemovalService::Flush();
|
||||
}
|
||||
|
||||
@@ -82,12 +82,12 @@ public:
|
||||
|
||||
private:
|
||||
#if USE_EDITOR
|
||||
typedef Array<class PrefabInstanceData> PrefabInstancesData;
|
||||
typedef Array<AssetReference<Prefab>> NestedPrefabsList;
|
||||
bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab);
|
||||
bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData);
|
||||
bool UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer);
|
||||
bool SyncChanges(const NestedPrefabsList& allPrefabs);
|
||||
bool SyncChangesInternal();
|
||||
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs);
|
||||
bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData);
|
||||
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const;
|
||||
#endif
|
||||
void DeleteDefaultInstance();
|
||||
|
||||
|
||||
@@ -7,17 +7,14 @@
|
||||
Actor* SceneQuery::RaycastScene(const Ray& ray)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
#if SCENE_QUERIES_WITH_LOCK
|
||||
ScopeLock lock(Level::ScenesLock);
|
||||
#endif
|
||||
|
||||
Actor* target;
|
||||
Actor* minTarget = nullptr;
|
||||
Real distance;
|
||||
Vector3 normal;
|
||||
Real minDistance = MAX_Real;
|
||||
|
||||
for (int32 i = 0; i < Level::Scenes.Count(); i++)
|
||||
{
|
||||
target = Level::Scenes[i]->Intersects(ray, distance, normal);
|
||||
@@ -30,7 +27,6 @@ Actor* SceneQuery::RaycastScene(const Ray& ray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minTarget;
|
||||
}
|
||||
|
||||
@@ -38,16 +34,13 @@ bool GetAllSceneObjectsQuery(Actor* actor, Array<SceneObject*>& objects)
|
||||
{
|
||||
objects.Add(actor);
|
||||
objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SceneQuery::GetAllSceneObjects(Actor* root, Array<SceneObject*>& objects)
|
||||
{
|
||||
ASSERT(root);
|
||||
|
||||
PROFILE_CPU();
|
||||
|
||||
Function<bool(Actor*, Array<SceneObject*>&)> func(GetAllSceneObjectsQuery);
|
||||
root->TreeExecuteChildren<Array<SceneObject*>&>(func, objects);
|
||||
}
|
||||
@@ -56,19 +49,15 @@ bool GetAllSerializableSceneObjectsQuery(Actor* actor, Array<SceneObject*>& obje
|
||||
{
|
||||
if (EnumHasAnyFlags(actor->HideFlags, HideFlags::DontSave))
|
||||
return false;
|
||||
|
||||
objects.Add(actor);
|
||||
objects.Add(reinterpret_cast<SceneObject* const*>(actor->Scripts.Get()), actor->Scripts.Count());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SceneQuery::GetAllSerializableSceneObjects(Actor* root, Array<SceneObject*>& objects)
|
||||
{
|
||||
ASSERT(root);
|
||||
|
||||
PROFILE_CPU();
|
||||
|
||||
Function<bool(Actor*, Array<SceneObject*>&)> func(GetAllSerializableSceneObjectsQuery);
|
||||
root->TreeExecute<Array<SceneObject*>&>(func, objects);
|
||||
}
|
||||
@@ -82,9 +71,7 @@ bool GetAllActorsQuery(Actor* actor, Array<Actor*>& actors)
|
||||
void SceneQuery::GetAllActors(Actor* root, Array<Actor*>& actors)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
ASSERT(root);
|
||||
|
||||
Function<bool(Actor*, Array<Actor*>&)> func(GetAllActorsQuery);
|
||||
root->TreeExecuteChildren<Array<Actor*>&>(func, actors);
|
||||
}
|
||||
@@ -92,11 +79,9 @@ void SceneQuery::GetAllActors(Actor* root, Array<Actor*>& actors)
|
||||
void SceneQuery::GetAllActors(Array<Actor*>& actors)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
#if SCENE_QUERIES_WITH_LOCK
|
||||
ScopeLock lock(Level::ScenesLock);
|
||||
#endif
|
||||
|
||||
for (int32 i = 0; i < Level::Scenes.Count(); i++)
|
||||
GetAllActors(Level::Scenes[i], actors);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,21 @@ Tag Tags::Get(const StringView& tagName)
|
||||
return tag;
|
||||
}
|
||||
|
||||
bool Tags::HasTag(const Array<Tag>& list, const Tag& tag)
|
||||
Array<Tag> Tags::GetSubTags(Tag parentTag)
|
||||
{
|
||||
Array<Tag> subTags;
|
||||
const String& parentTagName = parentTag.ToString();
|
||||
for (int32 i = 0; i < List.Count(); i++)
|
||||
{
|
||||
const Tag tag(i + 1);
|
||||
const String& tagName = List[i];
|
||||
if (tagName.StartsWith(parentTagName) && parentTag.Index != tag.Index)
|
||||
subTags.Add(tag);
|
||||
}
|
||||
return subTags;
|
||||
}
|
||||
|
||||
bool Tags::HasTag(const Array<Tag>& list, const Tag tag)
|
||||
{
|
||||
if (tag.Index == 0)
|
||||
return false;
|
||||
@@ -69,7 +83,7 @@ bool Tags::HasTag(const Array<Tag>& list, const Tag& tag)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Tags::HasTagExact(const Array<Tag>& list, const Tag& tag)
|
||||
bool Tags::HasTagExact(const Array<Tag>& list, const Tag tag)
|
||||
{
|
||||
if (tag.Index == 0)
|
||||
return false;
|
||||
|
||||
@@ -92,6 +92,13 @@ API_CLASS(Static) class FLAXENGINE_API Tags
|
||||
/// <returns>The tag.</returns>
|
||||
API_FUNCTION() static Tag Get(const StringView& tagName);
|
||||
|
||||
/// <summary>
|
||||
/// Get all subtags of the specific Tag
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <returns></returns>
|
||||
API_FUNCTION() static Array<Tag> GetSubTags(Tag tag);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains a given tag (including parent tags check). For example, HasTag({"A.B"}, "A") returns true, for exact check use HasTagExact.
|
||||
@@ -99,7 +106,7 @@ public:
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
|
||||
static bool HasTag(const Array<Tag>& list, const Tag& tag);
|
||||
static bool HasTag(const Array<Tag>& list, const Tag tag);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains a given tag (exact match). For example, HasTag({"A.B"}, "A") returns false, for parents check use HasTag.
|
||||
@@ -107,7 +114,7 @@ public:
|
||||
/// <param name="list">The tags list to use.</param>
|
||||
/// <param name="tag">The tag to check.</param>
|
||||
/// <returns>True if given tag is contained by the list of tags. Returns false for empty list.</returns>
|
||||
static bool HasTagExact(const Array<Tag>& list, const Tag& tag);
|
||||
static bool HasTagExact(const Array<Tag>& list, const Tag tag);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the list of tags contains any of the given tags (including parent tags check). For example, HasAny({"A.B", "C"}, {"A"}) returns true, for exact check use HasAnyExact.
|
||||
|
||||
@@ -15,6 +15,7 @@ REGISTER_JSON_ASSET(LocalizedStringTable, "FlaxEngine.LocalizedStringTable", tru
|
||||
LocalizedStringTable::LocalizedStringTable(const SpawnParams& params, const AssetInfo* info)
|
||||
: JsonAssetBase(params, info)
|
||||
{
|
||||
DataTypeName = TypeName;
|
||||
}
|
||||
|
||||
void LocalizedStringTable::AddString(const StringView& id, const StringView& value)
|
||||
|
||||
@@ -861,7 +861,7 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj)
|
||||
DeleteNetworkObject(obj);
|
||||
}
|
||||
|
||||
uint32 NetworkReplicator::GetObjectOwnerClientId(ScriptingObject* obj)
|
||||
uint32 NetworkReplicator::GetObjectOwnerClientId(const ScriptingObject* obj)
|
||||
{
|
||||
uint32 id = NetworkManager::ServerClientId;
|
||||
if (obj)
|
||||
@@ -870,11 +870,23 @@ uint32 NetworkReplicator::GetObjectOwnerClientId(ScriptingObject* obj)
|
||||
const auto it = Objects.Find(obj->GetID());
|
||||
if (it != Objects.End())
|
||||
id = it->Item.OwnerClientId;
|
||||
else
|
||||
{
|
||||
for (const SpawnItem& item : SpawnQueue)
|
||||
{
|
||||
if (item.Object == obj)
|
||||
{
|
||||
if (item.HasOwnership)
|
||||
id = item.OwnerClientId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj)
|
||||
NetworkObjectRole NetworkReplicator::GetObjectRole(const ScriptingObject* obj)
|
||||
{
|
||||
NetworkObjectRole role = NetworkObjectRole::None;
|
||||
if (obj)
|
||||
@@ -883,6 +895,18 @@ NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj)
|
||||
const auto it = Objects.Find(obj->GetID());
|
||||
if (it != Objects.End())
|
||||
role = it->Item.Role;
|
||||
else
|
||||
{
|
||||
for (const SpawnItem& item : SpawnQueue)
|
||||
{
|
||||
if (item.Object == obj)
|
||||
{
|
||||
if (item.HasOwnership)
|
||||
role = item.Role;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return role;
|
||||
}
|
||||
@@ -901,6 +925,18 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
|
||||
auto& item = SpawnQueue[i];
|
||||
if (item.Object == obj)
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
if (ownerClientId == NetworkManager::LocalClientId)
|
||||
{
|
||||
// Ensure local client owns that object actually
|
||||
CHECK(localRole == NetworkObjectRole::OwnedAuthoritative);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure local client doesn't own that object since it's owned by other client
|
||||
CHECK(localRole != NetworkObjectRole::OwnedAuthoritative);
|
||||
}
|
||||
#endif
|
||||
item.HasOwnership = true;
|
||||
item.HierarchicalOwnership = hierarchical;
|
||||
item.OwnerClientId = ownerClientId;
|
||||
@@ -1469,8 +1505,8 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
}
|
||||
|
||||
// Recreate object locally (spawn only root)
|
||||
ScriptingObject* obj = nullptr;
|
||||
Actor* prefabInstance = nullptr;
|
||||
Array<ScriptingObject*> objects;
|
||||
if (msgData.PrefabId.IsValid())
|
||||
{
|
||||
const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId);
|
||||
@@ -1489,7 +1525,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
{
|
||||
if (Objects.Contains(child->GetID()))
|
||||
{
|
||||
obj = FindPrefabObject(child, rootItem.PrefabObjectID);
|
||||
ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID);
|
||||
if (Objects.Contains(obj->GetID()))
|
||||
{
|
||||
// Other instance with already spawned network object
|
||||
@@ -1521,46 +1557,77 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!obj)
|
||||
obj = FindPrefabObject(prefabInstance, rootItem.PrefabObjectID);
|
||||
if (!obj)
|
||||
|
||||
// Resolve objects from prefab instance
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", rootItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString());
|
||||
Delete(prefabInstance);
|
||||
return;
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString());
|
||||
Delete(prefabInstance);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (msgData.ItemsCount == 1)
|
||||
{
|
||||
// Spawn object
|
||||
if (msgData.ItemsCount != 1)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Only prefab object spawning can contain more than one object (for type {})", String(rootItem.ObjectTypeName));
|
||||
return;
|
||||
}
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName);
|
||||
obj = ScriptingObject::NewObject(objectType);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName));
|
||||
return;
|
||||
}
|
||||
objects.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Spawn objects
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName));
|
||||
for (ScriptingObject* e : objects)
|
||||
Delete(e);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
if (i != 0)
|
||||
{
|
||||
// Link hierarchy of spawned objects before calling any networking code for them
|
||||
if (auto sceneObject = ScriptingObject::Cast<SceneObject>(obj))
|
||||
{
|
||||
Actor* parent = nullptr;
|
||||
for (int32 j = 0; j < i; j++)
|
||||
{
|
||||
if (msgDataItems[j].ObjectId == msgDataItem.ParentId)
|
||||
{
|
||||
parent = ScriptingObject::Cast<Actor>(objects[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parent)
|
||||
sceneObject->SetParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup all newly spawned objects
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
if (i != 0)
|
||||
{
|
||||
obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString());
|
||||
Delete(prefabInstance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ScriptingObject* obj = objects[i];
|
||||
if (!obj->IsRegistered())
|
||||
obj->RegisterObject();
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
|
||||
@@ -104,14 +104,14 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="obj">The network object.</param>
|
||||
/// <returns>The Client Id.</returns>
|
||||
API_FUNCTION() static uint32 GetObjectOwnerClientId(ScriptingObject* obj);
|
||||
API_FUNCTION() static uint32 GetObjectOwnerClientId(const ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role of the network object used locally (eg. to check if can simulate object).
|
||||
/// </summary>
|
||||
/// <param name="obj">The network object.</param>
|
||||
/// <returns>The object role.</returns>
|
||||
API_FUNCTION() static NetworkObjectRole GetObjectRole(ScriptingObject* obj);
|
||||
API_FUNCTION() static NetworkObjectRole GetObjectRole(const ScriptingObject* obj);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the network object is owned locally (thus current client has authority to manage it).
|
||||
@@ -119,7 +119,7 @@ public:
|
||||
/// <remarks>Equivalent to GetObjectRole == OwnedAuthoritative.</remarks>
|
||||
/// <param name="obj">The network object.</param>
|
||||
/// <returns>True if object is owned by this client, otherwise false.</returns>
|
||||
API_FUNCTION() FORCE_INLINE static bool IsObjectOwned(ScriptingObject* obj)
|
||||
API_FUNCTION() FORCE_INLINE static bool IsObjectOwned(const ScriptingObject* obj)
|
||||
{
|
||||
return GetObjectRole(obj) == NetworkObjectRole::OwnedAuthoritative;
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public:
|
||||
/// <remarks>Equivalent to GetObjectRole != Replicated.</remarks>
|
||||
/// <param name="obj">The network object.</param>
|
||||
/// <returns>True if object is simulated on this client, otherwise false.</returns>
|
||||
API_FUNCTION() FORCE_INLINE static bool IsObjectSimulated(ScriptingObject* obj)
|
||||
API_FUNCTION() FORCE_INLINE static bool IsObjectSimulated(const ScriptingObject* obj)
|
||||
{
|
||||
return GetObjectRole(obj) != NetworkObjectRole::Replicated;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public:
|
||||
/// <remarks>Equivalent to (GetObjectRole == Replicated or GetObjectRole == ReplicatedAutonomous).</remarks>
|
||||
/// <param name="obj">The network object.</param>
|
||||
/// <returns>True if object is simulated on this client, otherwise false.</returns>
|
||||
API_FUNCTION() FORCE_INLINE static bool IsObjectReplicated(ScriptingObject* obj)
|
||||
API_FUNCTION() FORCE_INLINE static bool IsObjectReplicated(const ScriptingObject* obj)
|
||||
{
|
||||
const NetworkObjectRole role = GetObjectRole(obj);
|
||||
return role == NetworkObjectRole::Replicated || role == NetworkObjectRole::ReplicatedSimulated;
|
||||
|
||||
@@ -388,33 +388,33 @@ void ParticleEmitterGPUGenerator::ProcessGroupParticles(Box* box, Node* node, Va
|
||||
// Random Float Range
|
||||
case 213:
|
||||
{
|
||||
auto& a = node->Values[0].AsFloat;
|
||||
auto& b = node->Values[1].AsFloat;
|
||||
value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a, b), node);
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat();
|
||||
value = writeLocal(VariantType::Float, String::Format(TEXT("lerp({0}, {1}, RAND)"), a.Value, b.Value), node);
|
||||
break;
|
||||
}
|
||||
// Random Vector2 Range
|
||||
case 214:
|
||||
{
|
||||
auto& a = node->Values[0].AsFloat2();
|
||||
auto& b = node->Values[1].AsFloat2();
|
||||
value = writeLocal(VariantType::Float2, String::Format(TEXT("float2(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND))"), a.X, b.X, a.Y, b.Y), node);
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat2();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat2();
|
||||
value = writeLocal(VariantType::Float2, String::Format(TEXT("float2(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND))"), a.Value, b.Value), node);
|
||||
break;
|
||||
}
|
||||
// Random Vector3 Range
|
||||
case 215:
|
||||
{
|
||||
auto& a = node->Values[0].AsFloat3();
|
||||
auto& b = node->Values[1].AsFloat3();
|
||||
value = writeLocal(VariantType::Float3, String::Format(TEXT("float3(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z), node);
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat3();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat3();
|
||||
value = writeLocal(VariantType::Float3, String::Format(TEXT("float3(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND), lerp({0}.z, {1}.z, RAND))"), a.Value, b.Value), node);
|
||||
break;
|
||||
}
|
||||
// Random Vector4 Range
|
||||
case 216:
|
||||
{
|
||||
auto& a = node->Values[0].AsFloat4();
|
||||
auto& b = node->Values[1].AsFloat4();
|
||||
value = writeLocal(VariantType::Float4, String::Format(TEXT("float4(lerp({0}, {1}, RAND), lerp({2}, {3}, RAND), lerp({4}, {5}, RAND), lerp({6}, {7}, RAND))"), a.X, b.X, a.Y, b.Y, a.Z, b.Z, a.W, b.W), node);
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat4();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat4();
|
||||
value = writeLocal(VariantType::Float4, String::Format(TEXT("float4(lerp({0}.x, {1}.x, RAND), lerp({0}.y, {1}.y, RAND), lerp({0}.z, {1}.z, RAND), lerp({0}.w, {1}.w, RAND))"), a.Value, b.Value), node);
|
||||
break;
|
||||
}
|
||||
// Particle Emitter Function
|
||||
|
||||
@@ -212,6 +212,12 @@ Asset::LoadResult ParticleSystem::load()
|
||||
|
||||
int32 version;
|
||||
stream.ReadInt32(&version);
|
||||
#if USE_EDITOR
|
||||
// Skip unused parameters
|
||||
#define SKIP_UNUSED_PARAM_OVERRIDE() if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First] == nullptr || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr) continue
|
||||
#else
|
||||
#define SKIP_UNUSED_PARAM_OVERRIDE()
|
||||
#endif
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
@@ -281,13 +287,7 @@ Asset::LoadResult ParticleSystem::load()
|
||||
stream.ReadInt32(&key.First);
|
||||
stream.Read(key.Second);
|
||||
stream.ReadCommonValue(&value);
|
||||
|
||||
#if USE_EDITOR
|
||||
// Skip unused parameters
|
||||
if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
SKIP_UNUSED_PARAM_OVERRIDE();
|
||||
EmittersParametersOverrides.Add(key, Variant(value));
|
||||
}
|
||||
}
|
||||
@@ -361,13 +361,7 @@ Asset::LoadResult ParticleSystem::load()
|
||||
stream.ReadInt32(&key.First);
|
||||
stream.Read(key.Second);
|
||||
stream.ReadCommonValue(&value);
|
||||
|
||||
#if USE_EDITOR
|
||||
// Skip unused parameters
|
||||
if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
SKIP_UNUSED_PARAM_OVERRIDE();
|
||||
EmittersParametersOverrides[key] = Variant(value);
|
||||
}
|
||||
}
|
||||
@@ -440,13 +434,7 @@ Asset::LoadResult ParticleSystem::load()
|
||||
stream.ReadInt32(&key.First);
|
||||
stream.Read(key.Second);
|
||||
stream.ReadVariant(&value);
|
||||
|
||||
#if USE_EDITOR
|
||||
// Skip unused parameters
|
||||
if (key.First < 0 || key.First >= Emitters.Count() || Emitters[key.First]->Graph.GetParameter(key.Second) == nullptr)
|
||||
continue;
|
||||
#endif
|
||||
|
||||
SKIP_UNUSED_PARAM_OVERRIDE();
|
||||
EmittersParametersOverrides[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -457,6 +445,7 @@ Asset::LoadResult ParticleSystem::load()
|
||||
LOG(Warning, "Unknown timeline version {0}.", version);
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
#undef SKIP_UNUSED_PARAM_OVERRIDE
|
||||
|
||||
#if !BUILD_RELEASE
|
||||
_debugName = StringUtils::GetFileNameWithoutExtension(GetPath());
|
||||
|
||||
@@ -251,3 +251,5 @@ API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public Scrip
|
||||
public:
|
||||
PersistentScriptingObject(const SpawnParams& params);
|
||||
};
|
||||
|
||||
extern FLAXENGINE_API class ScriptingObject* FindObject(const Guid& id, class MClass* type);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace FlaxEngine.Json
|
||||
{
|
||||
// Skip serialization as reference id for the root object serialization (eg. Script)
|
||||
var cache = JsonSerializer.Current.Value;
|
||||
if (cache != null && cache.IsDuringSerialization && cache.SerializerWriter.SerializeStackSize == 0)
|
||||
if (cache != null && cache.IsWriting && cache.SerializerWriter.SerializeStackSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,12 +26,14 @@ namespace FlaxEngine.Json
|
||||
public JsonSerializerInternalWriter SerializerWriter;
|
||||
public UnmanagedMemoryStream MemoryStream;
|
||||
public StreamReader Reader;
|
||||
public bool IsDuringSerialization;
|
||||
public bool IsWriting;
|
||||
public bool IsReading;
|
||||
|
||||
public unsafe SerializerCache(JsonSerializerSettings settings)
|
||||
{
|
||||
JsonSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
|
||||
JsonSerializer.Formatting = Formatting.Indented;
|
||||
JsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
|
||||
StringBuilder = new StringBuilder(256);
|
||||
StringWriter = new StringWriter(StringBuilder, CultureInfo.InvariantCulture);
|
||||
SerializerWriter = new JsonSerializerInternalWriter(JsonSerializer);
|
||||
@@ -50,6 +52,49 @@ namespace FlaxEngine.Json
|
||||
DateFormatString = JsonSerializer.DateFormatString,
|
||||
};
|
||||
}
|
||||
|
||||
public void ReadBegin()
|
||||
{
|
||||
if (IsReading)
|
||||
{
|
||||
// TODO: Reset reading state (eg if previous deserialization got exception)
|
||||
}
|
||||
IsWriting = false;
|
||||
IsReading = true;
|
||||
}
|
||||
|
||||
public void ReadEnd()
|
||||
{
|
||||
IsReading = false;
|
||||
}
|
||||
|
||||
public void WriteBegin()
|
||||
{
|
||||
if (IsWriting)
|
||||
{
|
||||
// Reset writing state (eg if previous serialization got exception)
|
||||
JsonWriter = new JsonTextWriter(StringWriter)
|
||||
{
|
||||
IndentChar = '\t',
|
||||
Indentation = 1,
|
||||
Formatting = JsonSerializer.Formatting,
|
||||
DateFormatHandling = JsonSerializer.DateFormatHandling,
|
||||
DateTimeZoneHandling = JsonSerializer.DateTimeZoneHandling,
|
||||
FloatFormatHandling = JsonSerializer.FloatFormatHandling,
|
||||
StringEscapeHandling = JsonSerializer.StringEscapeHandling,
|
||||
Culture = JsonSerializer.Culture,
|
||||
DateFormatString = JsonSerializer.DateFormatString,
|
||||
};
|
||||
}
|
||||
StringBuilder.Clear();
|
||||
IsWriting = true;
|
||||
IsReading = false;
|
||||
}
|
||||
|
||||
public void WriteEnd()
|
||||
{
|
||||
IsWriting = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static JsonSerializerSettings Settings = CreateDefaultSettings(false);
|
||||
@@ -117,9 +162,9 @@ namespace FlaxEngine.Json
|
||||
var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value;
|
||||
Current.Value = cache;
|
||||
|
||||
cache.StringBuilder.Clear();
|
||||
cache.IsDuringSerialization = true;
|
||||
cache.WriteBegin();
|
||||
cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type);
|
||||
cache.WriteEnd();
|
||||
|
||||
return cache.StringBuilder.ToString();
|
||||
}
|
||||
@@ -136,9 +181,9 @@ namespace FlaxEngine.Json
|
||||
var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value;
|
||||
Current.Value = cache;
|
||||
|
||||
cache.StringBuilder.Clear();
|
||||
cache.IsDuringSerialization = true;
|
||||
cache.WriteBegin();
|
||||
cache.SerializerWriter.Serialize(cache.JsonWriter, obj, type);
|
||||
cache.WriteEnd();
|
||||
|
||||
return cache.StringBuilder.ToString();
|
||||
}
|
||||
@@ -156,9 +201,9 @@ namespace FlaxEngine.Json
|
||||
var cache = isManagedOnly ? CacheManagedOnly.Value : Cache.Value;
|
||||
Current.Value = cache;
|
||||
|
||||
cache.StringBuilder.Clear();
|
||||
cache.IsDuringSerialization = true;
|
||||
cache.WriteBegin();
|
||||
cache.SerializerWriter.SerializeDiff(cache.JsonWriter, obj, type, other);
|
||||
cache.WriteEnd();
|
||||
|
||||
return cache.StringBuilder.ToString();
|
||||
}
|
||||
@@ -171,21 +216,20 @@ namespace FlaxEngine.Json
|
||||
public static void Deserialize(object input, string json)
|
||||
{
|
||||
var cache = Cache.Value;
|
||||
cache.IsDuringSerialization = false;
|
||||
Current.Value = cache;
|
||||
|
||||
cache.ReadBegin();
|
||||
using (JsonReader reader = new JsonTextReader(new StringReader(json)))
|
||||
{
|
||||
cache.JsonSerializer.Populate(reader, input);
|
||||
|
||||
if (!cache.JsonSerializer.CheckAdditionalContent)
|
||||
return;
|
||||
while (reader.Read())
|
||||
if (cache.JsonSerializer.CheckAdditionalContent)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
}
|
||||
}
|
||||
}
|
||||
cache.ReadEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -209,22 +253,20 @@ namespace FlaxEngine.Json
|
||||
{
|
||||
object result;
|
||||
var cache = Cache.Value;
|
||||
cache.IsDuringSerialization = false;
|
||||
Current.Value = cache;
|
||||
|
||||
cache.ReadBegin();
|
||||
using (JsonReader reader = new JsonTextReader(new StringReader(json)))
|
||||
{
|
||||
result = cache.JsonSerializer.Deserialize(reader, objectType);
|
||||
|
||||
if (!cache.JsonSerializer.CheckAdditionalContent)
|
||||
return result;
|
||||
while (reader.Read())
|
||||
if (cache.JsonSerializer.CheckAdditionalContent)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.ReadEnd();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -237,22 +279,21 @@ namespace FlaxEngine.Json
|
||||
{
|
||||
object result;
|
||||
var cache = Cache.Value;
|
||||
cache.IsDuringSerialization = false;
|
||||
Current.Value = cache;
|
||||
|
||||
cache.ReadBegin();
|
||||
using (JsonReader reader = new JsonTextReader(new StringReader(json)))
|
||||
{
|
||||
result = cache.JsonSerializer.Deserialize(reader);
|
||||
|
||||
if (!cache.JsonSerializer.CheckAdditionalContent)
|
||||
return result;
|
||||
while (reader.Read())
|
||||
if (cache.JsonSerializer.CheckAdditionalContent)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.ReadEnd();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -265,8 +306,7 @@ namespace FlaxEngine.Json
|
||||
public static unsafe void Deserialize(object input, byte* jsonBuffer, int jsonLength)
|
||||
{
|
||||
var cache = Cache.Value;
|
||||
cache.IsDuringSerialization = false;
|
||||
Current.Value = cache;
|
||||
cache.ReadBegin();
|
||||
|
||||
/*// Debug json string reading
|
||||
cache.MemoryStream.Initialize(jsonBuffer, jsonLength);
|
||||
@@ -286,14 +326,16 @@ namespace FlaxEngine.Json
|
||||
{
|
||||
cache.JsonSerializer.Populate(jsonReader, input);
|
||||
}
|
||||
|
||||
if (!cache.JsonSerializer.CheckAdditionalContent)
|
||||
return;
|
||||
while (jsonReader.Read())
|
||||
if (cache.JsonSerializer.CheckAdditionalContent)
|
||||
{
|
||||
if (jsonReader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
while (jsonReader.Read())
|
||||
{
|
||||
if (jsonReader.TokenType != JsonToken.Comment)
|
||||
throw new Exception("Additional text found in JSON string after finishing deserializing object.");
|
||||
}
|
||||
}
|
||||
|
||||
cache.ReadEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -456,7 +456,7 @@ namespace Serialization
|
||||
Guid id;
|
||||
Deserialize(stream, id, modifier);
|
||||
modifier->IdsMapping.TryGet(id, id);
|
||||
v = (T*)FindObject(id, T::GetStaticClass());
|
||||
v = (T*)::FindObject(id, T::GetStaticClass());
|
||||
}
|
||||
|
||||
// Scripting Object Reference
|
||||
|
||||
@@ -245,4 +245,89 @@ TEST_CASE("Prefabs")
|
||||
Content::DeleteAsset(prefabA);
|
||||
Content::DeleteAsset(prefabB);
|
||||
}
|
||||
SECTION("Test Syncing Changes In Nested Prefab Instance")
|
||||
{
|
||||
// https://github.com/FlaxEngine/FlaxEngine/issues/1015
|
||||
|
||||
// Create TestActor prefab with just root object
|
||||
AssetReference<Prefab> testActorPrefab = Content::CreateVirtualAsset<Prefab>();
|
||||
REQUIRE(testActorPrefab);
|
||||
Guid id;
|
||||
Guid::Parse("7691e981482f2a486e10cfae149e07d3", id);
|
||||
testActorPrefab->ChangeID(id);
|
||||
auto testActorPrefabInit = testActorPrefab->Init(Prefab::TypeName,
|
||||
"["
|
||||
"{"
|
||||
"\"ID\": \"5d73990240497afc0c6d36814cc6ebbe\","
|
||||
"\"TypeName\": \"FlaxEngine.EmptyActor\","
|
||||
"\"Name\": \"TestActor\""
|
||||
"}"
|
||||
"]");
|
||||
REQUIRE(!testActorPrefabInit);
|
||||
|
||||
// Create NestedActor prefab that inherits from TestActor prefab
|
||||
AssetReference<Prefab> nestedActorPrefab = Content::CreateVirtualAsset<Prefab>();
|
||||
REQUIRE(nestedActorPrefab);
|
||||
Guid::Parse("1d521df4465ad849e274748c6d14b703", id);
|
||||
nestedActorPrefab->ChangeID(id);
|
||||
auto nestedActorPrefabInit = nestedActorPrefab->Init(Prefab::TypeName,
|
||||
"["
|
||||
"{"
|
||||
"\"ID\": \"75c1587b4caeea27241ba7af00dafd45\","
|
||||
"\"PrefabID\": \"7691e981482f2a486e10cfae149e07d3\","
|
||||
"\"PrefabObjectID\": \"5d73990240497afc0c6d36814cc6ebbe\","
|
||||
"\"Name\": \"NestedActor\""
|
||||
"}"
|
||||
"]");
|
||||
REQUIRE(!nestedActorPrefabInit);
|
||||
|
||||
// Spawn test instances of both prefabs
|
||||
ScriptingObjectReference<Actor> testActor = PrefabManager::SpawnPrefab(testActorPrefab);
|
||||
ScriptingObjectReference<Actor> nestedActor = PrefabManager::SpawnPrefab(nestedActorPrefab);
|
||||
|
||||
// Verify initial scenario
|
||||
REQUIRE(testActor);
|
||||
CHECK(testActor->GetName() == TEXT("TestActor"));
|
||||
CHECK(testActor->GetStaticFlags() == StaticFlags::FullyStatic);
|
||||
REQUIRE(nestedActor);
|
||||
CHECK(nestedActor->GetName() == TEXT("NestedActor"));
|
||||
CHECK(nestedActor->GetStaticFlags() == StaticFlags::FullyStatic);
|
||||
|
||||
// Modify TestActor instance to have Static Flags property changed
|
||||
testActor->SetStaticFlags(StaticFlags::None);
|
||||
|
||||
// Apply prefab changes
|
||||
auto applyResult = PrefabManager::ApplyAll(testActor);
|
||||
REQUIRE(!applyResult);
|
||||
|
||||
// Verify if instances were properly updated
|
||||
REQUIRE(testActor);
|
||||
CHECK(testActor->GetName() == TEXT("TestActor"));
|
||||
CHECK(testActor->GetStaticFlags() == StaticFlags::None);
|
||||
REQUIRE(nestedActor);
|
||||
CHECK(nestedActor->GetName() == TEXT("NestedActor"));
|
||||
CHECK(nestedActor->GetStaticFlags() == StaticFlags::None);
|
||||
|
||||
// Cleanup
|
||||
nestedActor->DeleteObject();
|
||||
testActor->DeleteObject();
|
||||
|
||||
// Spawn another test instances instances of both prefabs
|
||||
testActor = PrefabManager::SpawnPrefab(testActorPrefab);
|
||||
nestedActor = PrefabManager::SpawnPrefab(nestedActorPrefab);
|
||||
|
||||
// Verify if instances were properly updated
|
||||
REQUIRE(testActor);
|
||||
CHECK(testActor->GetName() == TEXT("TestActor"));
|
||||
CHECK(testActor->GetStaticFlags() == StaticFlags::None);
|
||||
REQUIRE(nestedActor);
|
||||
CHECK(nestedActor->GetName() == TEXT("NestedActor"));
|
||||
CHECK(nestedActor->GetStaticFlags() == StaticFlags::None);
|
||||
|
||||
// Cleanup
|
||||
nestedActor->DeleteObject();
|
||||
testActor->DeleteObject();
|
||||
Content::DeleteAsset(nestedActorPrefab);
|
||||
Content::DeleteAsset(testActorPrefab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,16 +1275,16 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
||||
// Random Float Range
|
||||
case 213:
|
||||
{
|
||||
auto& a = node->Values[0].AsFloat;
|
||||
auto& b = node->Values[1].AsFloat;
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat;
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat;
|
||||
value = Math::Lerp(a, b, RAND);
|
||||
break;
|
||||
}
|
||||
// Random Vector2 Range
|
||||
case 214:
|
||||
{
|
||||
auto a = (Float2)node->Values[0];
|
||||
auto b = (Float2)node->Values[1];
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat2();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat2();
|
||||
value = Float2(
|
||||
Math::Lerp(a.X, b.X, RAND),
|
||||
Math::Lerp(a.Y, b.Y, RAND)
|
||||
@@ -1294,8 +1294,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
||||
// Random Vector3 Range
|
||||
case 215:
|
||||
{
|
||||
auto a = (Float3)node->Values[0];
|
||||
auto b = (Float3)node->Values[1];
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat3();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat3();
|
||||
value = Float3(
|
||||
Math::Lerp(a.X, b.X, RAND),
|
||||
Math::Lerp(a.Y, b.Y, RAND),
|
||||
@@ -1306,8 +1306,8 @@ void VisjectExecutor::ProcessGroupParticles(Box* box, Node* node, Value& value)
|
||||
// Random Vector4 Range
|
||||
case 216:
|
||||
{
|
||||
auto a = (Float4)node->Values[0];
|
||||
auto b = (Float4)node->Values[1];
|
||||
auto a = tryGetValue(node->TryGetBox(1), node->Values[0]).AsFloat4();
|
||||
auto b = tryGetValue(node->TryGetBox(2), node->Values[1]).AsFloat4();
|
||||
value = Float4(
|
||||
Math::Lerp(a.X, b.X, RAND),
|
||||
Math::Lerp(a.Y, b.Y, RAND),
|
||||
|
||||
@@ -27,36 +27,31 @@ VS2PS VS(float3 Position : POSITION, float4 Color : COLOR)
|
||||
return output;
|
||||
}
|
||||
|
||||
void PerformDepthTest(float4 svPosition)
|
||||
META_PS(true, FEATURE_LEVEL_ES2)
|
||||
META_PERMUTATION_2(USE_DEPTH_TEST=0,USE_FAKE_LIGHTING=0)
|
||||
META_PERMUTATION_2(USE_DEPTH_TEST=0,USE_FAKE_LIGHTING=1)
|
||||
META_PERMUTATION_2(USE_DEPTH_TEST=1,USE_FAKE_LIGHTING=0)
|
||||
META_PERMUTATION_2(USE_DEPTH_TEST=1,USE_FAKE_LIGHTING=1)
|
||||
float4 PS(VS2PS input) : SV_Target
|
||||
{
|
||||
#if USE_DEPTH_TEST
|
||||
// Depth test manually if compositing editor primitives
|
||||
FLATTEN
|
||||
if (EnableDepthTest)
|
||||
{
|
||||
float sceneDepthDeviceZ = SceneDepthTexture.Load(int3(svPosition.xy, 0)).r;
|
||||
float interpolatedDeviceZ = svPosition.z;
|
||||
float sceneDepthDeviceZ = SceneDepthTexture.Load(int3(input.Position.xy, 0)).r;
|
||||
float interpolatedDeviceZ = input.Position.z;
|
||||
clip(sceneDepthDeviceZ - interpolatedDeviceZ);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
float4 PerformFakeLighting(float4 svPosition, float4 c)
|
||||
{
|
||||
float4 color = input.Color;
|
||||
#if USE_FAKE_LIGHTING
|
||||
// Reconstruct view normal and calculate faked lighting
|
||||
float depth = svPosition.z * 10000;
|
||||
float depth = input.Position.z * 10000;
|
||||
float3 n = normalize(float3(-ddx(depth), -ddy(depth), 1.0f));
|
||||
c.rgb *= saturate(abs(dot(n, float3(0, 1, 0))) + 0.5f);
|
||||
return c;
|
||||
}
|
||||
color.rgb *= saturate(abs(dot(n, float3(0, 1, 0))) + 0.5f);
|
||||
#endif
|
||||
|
||||
META_PS(true, FEATURE_LEVEL_ES2)
|
||||
float4 PS(VS2PS input) : SV_Target
|
||||
{
|
||||
return PerformFakeLighting(input.Position, input.Color);
|
||||
}
|
||||
|
||||
META_PS(true, FEATURE_LEVEL_ES2)
|
||||
float4 PS_DepthTest(VS2PS input) : SV_Target
|
||||
{
|
||||
PerformDepthTest(input.Position);
|
||||
return PerformFakeLighting(input.Position, input.Color);
|
||||
return color;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user