Merge remote-tracking branch 'origin/master' into 1.6

# Conflicts:
#	.github/workflows/tests.yml
#	Source/Engine/Content/JsonAsset.cs
This commit is contained in:
Wojtek Figat
2023-05-07 19:46:29 +02:00
31 changed files with 603 additions and 311 deletions

BIN
Content/Shaders/DebugDraw.flax (Stored with Git LFS)

Binary file not shown.

View File

@@ -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,

View File

@@ -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))

View File

@@ -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));
}

View File

@@ -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)
{

View File

@@ -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),
}
},

View File

@@ -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();
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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++)
{

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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());

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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),

View File

@@ -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;
}