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

# Conflicts:
#	Flax.flaxproj
#	Source/Engine/Level/Actors/StaticModel.cpp
#	Source/Engine/Level/Prefabs/Prefab.cpp
#	Source/Engine/Tools/ModelTool/ModelTool.cpp
This commit is contained in:
2026-06-03 17:15:38 +02:00
91 changed files with 1290 additions and 579 deletions
@@ -12,7 +12,7 @@ namespace FlaxEngine.Tools
{
partial struct Options
{
private bool ShowBtiDepth => Format != AudioFormat.Vorbis;
private bool ShowBitDepth => Format != AudioFormat.Vorbis;
}
}
}
@@ -19,6 +19,7 @@ namespace FlaxEngine.Tools
private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None;
private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
private bool ShowGenerateLODs => ShowGeometry && GenerateLODs;
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
private bool ShowSplitting => Type != ModelType.Prefab;
}
+2 -2
View File
@@ -98,12 +98,12 @@ namespace FlaxEditor.Content
}
/// <summary>
/// Reloads the asset (if it's loaded).
/// Reloads the asset (if it's loaded or failed to load).
/// </summary>
public void Reload()
{
var asset = FlaxEngine.Content.GetAsset(ID);
if (asset != null && asset.IsLoaded)
if (asset != null && (asset.IsLoaded || asset.LastLoadFailed))
{
asset.Reload();
}
@@ -0,0 +1,109 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
/// <summary>
/// A base class for <see cref="MaterialBase"/> asset proxy object.
/// </summary>
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
public abstract class MaterialBaseProxy : BinaryAssetProxy
{
/// <summary>
/// The material preview drawer.
/// </summary>
protected MaterialPreview _preview;
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{
return targetLocation.CanHaveAssets;
}
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
base.OnContentWindowContextMenu(menu, item);
if (item is BinaryAssetItem binaryAssetItem)
{
var button = menu.AddButton("Create Material Instance", CreateMaterialInstanceClicked);
button.Tag = binaryAssetItem;
}
}
private void CreateMaterialInstanceClicked(ContextMenuButton button)
{
var binaryAssetItem = (BinaryAssetItem)button.Tag;
CreateMaterialInstance(binaryAssetItem);
}
/// <summary>
/// Creates the material instance from the given material.
/// </summary>
/// <param name="materialItem">The material item to use as a base material.</param>
public static void CreateMaterialInstance(BinaryAssetItem materialItem)
{
var materialInstanceName = materialItem.ShortName + " Instance";
var materialInstanceProxy = Editor.Instance.ContentDatabase.GetProxy<MaterialInstance>();
Editor.Instance.Windows.ContentWin.NewItem(materialInstanceProxy, null, item => OnMaterialInstanceCreated(item, materialItem), materialInstanceName);
}
private static void OnMaterialInstanceCreated(ContentItem item, BinaryAssetItem materialItem)
{
var assetItem = (AssetItem)item;
var materialInstance = FlaxEngine.Content.LoadAsync<MaterialInstance>(assetItem.ID);
if (materialInstance == null || materialInstance.WaitForLoaded())
{
Editor.LogError("Failed to load created material instance.");
return;
}
materialInstance.BaseMaterial = FlaxEngine.Content.LoadAsync<MaterialBase>(materialItem.ID);
materialInstance.Save();
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
if (_preview == null)
{
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
}
/// <inheritdoc />
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
{
_preview.Material = (MaterialBase)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.Task.OnDraw();
}
/// <inheritdoc />
public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot)
{
_preview.Material = null;
_preview.Parent = null;
}
/// <inheritdoc />
public override void Dispose()
{
if (_preview != null)
{
_preview.Dispose();
_preview = null;
}
base.Dispose();
}
}
}
@@ -2,23 +2,18 @@
using System;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
/// <summary>
/// A <see cref="MaterialInstance"/> asset proxy object.
/// </summary>
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
[ContentContextMenu("New/Material/Material Instance")]
public class MaterialInstanceProxy : BinaryAssetProxy
public class MaterialInstanceProxy : MaterialBaseProxy
{
private MaterialPreview _preview;
/// <inheritdoc />
public override string Name => "Material Instance";
@@ -34,12 +29,6 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override Type AssetType => typeof(MaterialInstance);
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{
return targetLocation.CanHaveAssets;
}
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
@@ -47,49 +36,10 @@ namespace FlaxEditor.Content
throw new Exception("Failed to create new asset.");
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
if (_preview == null)
{
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
}
/// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((MaterialInstance)request.Asset);
}
/// <inheritdoc />
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
{
_preview.Material = (MaterialInstance)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.Task.OnDraw();
}
/// <inheritdoc />
public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot)
{
_preview.Material = null;
_preview.Parent = null;
}
/// <inheritdoc />
public override void Dispose()
{
if (_preview != null)
{
_preview.Dispose();
_preview = null;
}
base.Dispose();
}
}
}
+1 -95
View File
@@ -2,24 +2,18 @@
using System;
using FlaxEditor.Content.Thumbnails;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.Viewport.Previews;
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Content
{
/// <summary>
/// A <see cref="Material"/> asset proxy object.
/// </summary>
/// <seealso cref="FlaxEditor.Content.BinaryAssetProxy" />
[ContentContextMenu("New/Material/Material")]
public class MaterialProxy : BinaryAssetProxy
public class MaterialProxy : MaterialBaseProxy
{
private MaterialPreview _preview;
/// <inheritdoc />
public override string Name => "Material";
@@ -35,12 +29,6 @@ namespace FlaxEditor.Content
/// <inheritdoc />
public override Type AssetType => typeof(Material);
/// <inheritdoc />
public override bool CanCreate(ContentFolder targetLocation)
{
return targetLocation.CanHaveAssets;
}
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{
@@ -48,92 +36,10 @@ namespace FlaxEditor.Content
throw new Exception("Failed to create new asset.");
}
/// <inheritdoc />
public override void OnContentWindowContextMenu(ContextMenu menu, ContentItem item)
{
base.OnContentWindowContextMenu(menu, item);
if (item is BinaryAssetItem binaryAssetItem)
{
var button = menu.AddButton("Create Material Instance", CreateMaterialInstanceClicked);
button.Tag = binaryAssetItem;
}
}
private void CreateMaterialInstanceClicked(ContextMenuButton obj)
{
var binaryAssetItem = (BinaryAssetItem)obj.Tag;
CreateMaterialInstance(binaryAssetItem);
}
/// <summary>
/// Creates the material instance from the given material.
/// </summary>
/// <param name="materialItem">The material item to use as a base material.</param>
public static void CreateMaterialInstance(BinaryAssetItem materialItem)
{
var materialInstanceName = materialItem.ShortName + " Instance";
var materialInstanceProxy = Editor.Instance.ContentDatabase.GetProxy<MaterialInstance>();
Editor.Instance.Windows.ContentWin.NewItem(materialInstanceProxy, null, item => OnMaterialInstanceCreated(item, materialItem), materialInstanceName);
}
private static void OnMaterialInstanceCreated(ContentItem item, BinaryAssetItem materialItem)
{
var assetItem = (AssetItem)item;
var materialInstance = FlaxEngine.Content.LoadAsync<MaterialInstance>(assetItem.ID);
if (materialInstance == null || materialInstance.WaitForLoaded())
{
Editor.LogError("Failed to load created material instance.");
return;
}
materialInstance.BaseMaterial = FlaxEngine.Content.LoadAsync<Material>(materialItem.ID);
materialInstance.Save();
}
/// <inheritdoc />
public override void OnThumbnailDrawPrepare(ThumbnailRequest request)
{
if (_preview == null)
{
_preview = new MaterialPreview(false);
InitAssetPreview(_preview);
}
}
/// <inheritdoc />
public override bool CanDrawThumbnail(ThumbnailRequest request)
{
return _preview.HasLoadedAssets && ThumbnailsModule.HasMinimumQuality((Material)request.Asset);
}
/// <inheritdoc />
public override void OnThumbnailDrawBegin(ThumbnailRequest request, ContainerControl guiRoot, GPUContext context)
{
_preview.Material = (Material)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.Task.OnDraw();
}
/// <inheritdoc />
public override void OnThumbnailDrawEnd(ThumbnailRequest request, ContainerControl guiRoot)
{
_preview.Material = null;
_preview.Parent = null;
}
/// <inheritdoc />
public override void Dispose()
{
if (_preview != null)
{
_preview.Dispose();
_preview = null;
}
base.Dispose();
}
}
}
@@ -191,7 +191,7 @@ bool WebPlatformTools::OnPostProcess(CookingData& data)
FileSystem::GetChildDirectories(pythons, emscriptenSdk / TEXT("/python"));
if (pythons.HasItems())
{
procSettings.Arguments = procSettings.FileName + TEXT(".py ") + procSettings.Arguments;
procSettings.Arguments = String::Format(TEXT("\"{}.py\" {}"), procSettings.FileName, procSettings.Arguments);
#if PLATFORM_WINDOWS
procSettings.FileName = pythons[0] / TEXT("/python.exe");
#else
@@ -589,6 +589,8 @@ namespace FlaxEditor.GUI
// Get the next item
bool controlDown = Root.GetKey(KeyboardKeys.Control);
var items = GetVisibleItems(!controlDown);
if (items.Count == 0)
return true;
var focusedIndex = items.IndexOf(focusedItem);
int delta = key == KeyboardKeys.ArrowDown ? -1 : 1;
+22 -6
View File
@@ -229,6 +229,7 @@ namespace FlaxEditor.GUI.Timeline
private List<Track> _mediaMoveStartTracks;
private byte[][] _mediaMoveStartData;
private float _zoom = 1.0f;
private float _tracksVScrollTarget;
private bool _isMovingPositionHandle;
private bool _canPlayPause = true, _canStop = true;
private List<IUndoAction> _batchedUndoActions;
@@ -1301,10 +1302,13 @@ namespace FlaxEditor.GUI.Timeline
if (track.ParentTrack != null)
OnTracksOrderChanged();
track.OnSpawned();
_tracksPanelArea.ScrollViewTo(track);
MarkAsEdited();
if (withUndo)
Undo?.AddAction(new AddRemoveTrackAction(this, track, true));
// Scroll to track
_tracksPanelArea.ScrollViewTo(track);
_tracksVScrollTarget = _tracksPanelArea.VScrollBar.TargetValue;
}
/// <summary>
@@ -2033,12 +2037,24 @@ namespace FlaxEditor.GUI.Timeline
base.Update(deltaTime);
// Synchronize scroll vertical bars for tracks and media panels to keep the view in sync
var scroll1 = _tracksPanelArea.VScrollBar;
var scroll2 = _backgroundArea.VScrollBar;
if (scroll1.IsThumbClicked || _tracksPanelArea.IsMouseOver)
scroll2.TargetValue = scroll1.Value;
var tracksVScroll = _tracksPanelArea.VScrollBar;
var backgroundVScroll = _backgroundArea.VScrollBar;
bool forceBackgroundToTracksScroll = _tracksVScrollTarget > 0;
if (forceBackgroundToTracksScroll)
{
backgroundVScroll.TargetValue = tracksVScroll.Value;
if (Mathf.Abs(tracksVScroll.Value - _tracksVScrollTarget) < 0.5f)
_tracksVScrollTarget = 0f;
}
else if (tracksVScroll.IsThumbClicked || _tracksPanelArea.IsMouseOver)
{
backgroundVScroll.TargetValue = tracksVScroll.Value;
}
else
scroll1.TargetValue = scroll2.Value;
{
tracksVScroll.TargetValue = backgroundVScroll.Value;
}
// Batch undo actions
if (_batchedUndoActions != null && _batchedUndoActions.Count != 0)
+2 -1
View File
@@ -181,7 +181,7 @@ public class WindowDecorations : ContainerControl
return WindowHitCodes.NoWhere;
var dpiScale = _window.DpiScale;
var pos = _window.ScreenToClient(mouse * dpiScale); // pos is not DPI adjusted
var pos = _window.ScreenToClient(mouse * dpiScale); // pos is DPI adjusted in window space
if (!_window.IsMaximized)
{
var winSize = _window.Size;
@@ -214,6 +214,7 @@ public class WindowDecorations : ContainerControl
return WindowHitCodes.Bottom;
}
pos /= dpiScale; // The position should not be DPI adjusted in control space
var controlUnderMouse = GetChildAt(pos, control => control != _title);
if (_title.Bounds.Contains(pos) && controlUnderMouse == null)
return WindowHitCodes.Caption;
+48 -3
View File
@@ -36,8 +36,10 @@ internal class DirectionGizmo : ContainerControl
private List<AxisData> _axisData = new List<AxisData>();
private int _hoveredAxisIndex = -1;
private bool _mouseDown;
private Float2 _mouseDownLocation;
private SpriteHandle _posHandle;
private SpriteHandle _negHandle;
private FontReference _fontReference;
@@ -110,7 +112,6 @@ internal class DirectionGizmo : ContainerControl
var editor = Editor.Instance;
_posHandle = editor.Icons.VisjectBoxClosed32;
_negHandle = editor.Icons.VisjectBoxOpen32;
_fontReference = new FontReference(Style.Current.FontSmall);
@@ -142,7 +143,25 @@ internal class DirectionGizmo : ContainerControl
public override void OnMouseMove(Float2 location)
{
_hoveredAxisIndex = -1;
if (_mouseDown)
{
StartMouseCapture(true);
Cursor = CursorType.Hidden;
const float sensitivity = 0.125f;
Float2 delta = Input.MousePositionDelta;
delta *= Mathf.DegreesToRadians;
delta *= sensitivity;
const float orbitRadius = 500f;
Quaternion newOrientation = _viewport.ViewOrientation * Quaternion.RotationYawPitchRoll(delta.X , delta.Y, 0f);
Vector3 orbitCenter = _viewport.ViewPosition + _viewport.ViewDirection * orbitRadius;
_viewport.ViewportCamera.SetArcBallView(newOrientation, orbitCenter, orbitRadius);
return;
}
// Check which axis is being hovered - check from closest to farthest for proper layering
for (int i = _spritePositions.Count - 1; i >= 0; i--)
{
@@ -156,9 +175,30 @@ internal class DirectionGizmo : ContainerControl
base.OnMouseMove(location);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
_mouseDown = true;
_mouseDownLocation = location;
return true;
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (_mouseDown)
{
_mouseDown = false;
if (_mouseDownLocation != location)
{
_mouseDown = false;
EndMouseCapture();
Root.MousePosition = PointToParent(Root, _mouseDownLocation);
Cursor = CursorType.Default;
return true;
}
}
if (base.OnMouseUp(location, button))
return true;
@@ -269,7 +309,12 @@ internal class DirectionGizmo : ContainerControl
// Rebuild sprite positions list for hover detection
_spritePositions.Clear();
Render2D.DrawSprite(_posHandle, new Rectangle(0, 0, Size), Color.Black.AlphaMultiplied(_backgroundOpacity));
if (IsMouseOver)
{
Rectangle backgroundRect = new Rectangle(0, 0, Size);
Color backgroundColor = Color.DarkGray.AlphaMultiplied(_backgroundOpacity);
Render2D.DrawSprite(_posHandle, backgroundRect, backgroundColor);
}
// Draw in order from farthest to closest
for (int i = 0; i < _axisData.Count; i++)
+1 -1
View File
@@ -187,7 +187,7 @@ namespace FlaxEditor.Options
public float DirectionGizmoScale { get; set; } = 1f;
/// <summary>
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/> background.
/// Gets or sets a value for the opacity of the main viewports <see cref="Gizmo.DirectionGizmo"/> background. Background will only show when the gizmo is hovered.
/// </summary>
[DefaultValue(0.1f), Limit(0.0f, 1.0f)]
[EditorDisplay("Direction Gizmo"), EditorOrder(502), Tooltip("The background opacity of the of the direction gizmo in the main viewport.")]
@@ -14,6 +14,9 @@
#if PLATFORM_WINDOWS
#include "Engine/Platform/Win32/IncludeWindowsHeaders.h"
#elif PLATFORM_MAC
#include "Engine/Platform/Apple/AppleUtils.h"
#include <AppKit/AppKit.h>
#endif
namespace
@@ -68,10 +71,14 @@ namespace
if (!launcherPath.HasChars() || !FileSystem::FileExists(exePath))
return;
if (launchOverridePath != String::Empty)
installations->Add(New<RiderInstallation>(launchOverridePath, versionMember->value.GetText()));
else
installations->Add(New<RiderInstallation>(exePath, versionMember->value.GetText()));
String installPath = launchOverridePath != String::Empty ? launchOverridePath : exePath;
StringUtils::PathRemoveRelativeParts(installPath);
for (RiderInstallation* installation : *installations)
{
if (installation->path == installPath)
return;
}
installations->Add(New<RiderInstallation>(installPath, versionMember->value.GetText()));
}
#if PLATFORM_WINDOWS
@@ -221,17 +228,29 @@ void RiderCodeEditor::FindEditors(Array<CodeEditor*>* output)
String applicationSupportFolder;
FileSystem::GetSpecialFolderPath(SpecialFolder::ProgramData, applicationSupportFolder);
NSURL* appURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.rider"];
if (appURL != nullptr)
{
const String appPath = AppleUtils::ToString((CFStringRef)[appURL path]);
SearchDirectory(&installations, appPath / TEXT("Contents/Resources"), appPath);
}
Array<String> subMacDirectories;
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-0/"));
FileSystem::GetChildDirectories(subMacDirectories, applicationSupportFolder / TEXT("JetBrains/Toolbox/apps/Rider/ch-1/"));
for (const String& directory : subMacDirectories)
{
String riderAppDirectory = directory / TEXT("Rider.app/Contents/Resources");
SearchDirectory(&installations, riderAppDirectory);
String riderAppPath = directory / TEXT("Rider.app");
SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath);
}
// Check the local installer version
SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"));
SearchDirectory(&installations, TEXT("/Applications/Rider.app/Contents/Resources"), TEXT("/Applications/Rider.app"));
String userFolder;
FileSystem::GetSpecialFolderPath(SpecialFolder::Documents, userFolder);
String riderAppPath = userFolder / TEXT("../Applications/Rider.app");
SearchDirectory(&installations, riderAppPath / TEXT("Contents/Resources"), riderAppPath);
#endif
for (const String& directory : subDirectories)
+15 -5
View File
@@ -8,6 +8,7 @@ using FlaxEditor.Gizmo;
using FlaxEditor.GUI.Tabs;
using FlaxEditor.Modules;
using FlaxEditor.SceneGraph;
using FlaxEditor.Utilities;
using FlaxEditor.Viewport.Modes;
using FlaxEngine;
using FlaxEngine.GUI;
@@ -307,7 +308,7 @@ namespace FlaxEditor.Tools
public VertexPaintingGizmo Gizmo;
public VertexColorsPreviewMode PreviewMode = VertexColorsPreviewMode.RGB;
public float PreviewVertexSize = 6.0f;
public float PreviewVertexSize = 4.0f;
public float BrushSize = 100.0f;
public float BrushStrength = 1.0f;
public float BrushFalloff = 1.0f;
@@ -402,7 +403,7 @@ namespace FlaxEditor.Tools
if (meshDatas == null)
throw new Exception("Missing mesh data of the model to paint.");
var instanceTransform = _selectedModel.Transform;
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize);
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f);
if (_paintUpdateCount == 0 && !_selectedModel.HasVertexColors)
{
// Initialize the instance vertex colors with originals from the asset
@@ -509,6 +510,13 @@ namespace FlaxEditor.Tools
return;
}
// Increase or decrease brush size with scroll
if (Input.GetKey(KeyboardKeys.Shift) && !Input.GetMouseButton(MouseButton.Right))
{
_gizmoMode.BrushSize += dt * _gizmoMode.BrushSize * Input.Mouse.ScrollDelta * 5f;
_gizmoMode.BrushSize = Mathf.Clamp(_gizmoMode.BrushSize, 0.0001f, 100000.0f);
}
// Perform detailed tracing to find cursor location for the brush
var ray = Owner.MouseRay;
var view = new Ray(Owner.ViewPosition, Owner.ViewDirection);
@@ -570,7 +578,7 @@ namespace FlaxEditor.Tools
}
if (_brushModel && _brushMaterial)
{
_brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f)); // TODO: expose to editor options
_brushMaterial.SetParameterValue("Color", new Color(1.0f, 0.85f, 0.0f));
_brushMaterial.SetParameterValue("DepthBuffer", Owner.RenderTask.Buffers.DepthBuffer);
Quaternion rotation = RootNode.RaycastNormalRotation(ref _hitNormal);
Matrix transform = Matrix.Scaling(_gizmoMode.BrushSize * 0.01f) * Matrix.RotationQuaternion(rotation) * Matrix.Translation(_hitLocation - viewOrigin);
@@ -586,8 +594,10 @@ namespace FlaxEditor.Tools
_verticesPreviewMaterial = FlaxEngine.Content.LoadAsyncInternal<MaterialBase>(EditorAssets.WiresDebugMaterial);
}
var instanceTransform = _selectedModel.Transform;
var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * 0.01f);
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize);
var distanceScale = (float)Vector3.Distance(instanceTransform.Translation, renderContext.View.Position) / (10.0f * Units.Meters2Units);
var vertexScale = Mathf.Lerp(0.005f, 0.01f, Mathf.Saturate(distanceScale));
var modelScaleMatrix = Matrix.Scaling(_gizmoMode.PreviewVertexSize * vertexScale);
var brushSphere = new BoundingSphere(_hitLocation, _gizmoMode.BrushSize * 0.5f);
var lodIndex = _gizmoMode.ModelLOD == -1 ? RenderTools.ComputeModelLOD(_selectedModel.Model, ref renderContext.View.Position, (float)_selectedModel.Sphere.Radius, ref renderContext) : _gizmoMode.ModelLOD;
lodIndex = Mathf.Clamp(lodIndex, 0, meshDatas.Length - 1);
var lodData = meshDatas[lodIndex];
@@ -714,7 +714,7 @@ namespace FlaxEditor.Viewport
{
base.OnLeftMouseButtonDown();
if (!IsAltKeyDown)
if (!IsAltKeyDown && !_directionGizmo.IsMouseOver)
_rubberBandSelector.TryStartingRubberBandSelection(_viewMousePos);
}
@@ -722,7 +722,7 @@ namespace FlaxEditor.Viewport
protected override void OnLeftMouseButtonUp()
{
// Skip if was controlling mouse or mouse is not over the area
if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos))
if (_prevInput.IsControllingMouse || !Bounds.Contains(ref _viewMousePos) || _directionGizmo.IsMouseOver)
return;
// Select rubberbanded rect actor nodes or pick with gizmo
@@ -2,6 +2,7 @@
using System;
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using Object = FlaxEngine.Object;
@@ -14,7 +15,7 @@ namespace FlaxEditor.Viewport.Previews
public class AnimatedModelPreview : AssetPreview
{
private AnimatedModel _previewModel;
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton;
private ContextMenuButton _showNodesButton, _showBoundsButton, _showFloorButton, _showNodesNamesButton, _nodeNameSizeButton;
private bool _showNodes, _showBounds, _showFloor, _showNodesNames;
private StaticModel _floorModel;
private bool _playAnimation, _playAnimationOnce, _autoAdjustCamera = true;
@@ -110,9 +111,16 @@ namespace FlaxEditor.Viewport.Previews
ShowDebugDraw = true;
if (_showNodesNamesButton != null)
_showNodesNamesButton.Checked = value;
if (_nodeNameSizeButton != null)
_nodeNameSizeButton.Enabled = value;
}
}
/// <summary>
/// The font size used in the node name debug draw.
/// </summary>
public int NodeNamesSize = 10;
/// <summary>
/// Gets or sets a value indicating whether show animated model bounding box debug view.
/// </summary>
@@ -208,6 +216,16 @@ namespace FlaxEditor.Viewport.Previews
_showFloorButton = ViewWidgetShowMenu.AddButton("Floor", button => ShowFloor = !ShowFloor);
_showFloorButton.IndexInParent = 1;
_showFloorButton.CloseMenuOnClick = false;
// Skeleton Names Size
_nodeNameSizeButton = ViewWidgetButtonMenu.AddButton("Skeleton Names Size");
_nodeNameSizeButton.CloseMenuOnClick = false;
var nodeNameSizeValue = new IntValueBox(NodeNamesSize, 118, 2, 70.0f, 1, 32)
{
Parent = _nodeNameSizeButton
};
_nodeNameSizeButton.Enabled = ShowNodesNames;
nodeNameSizeValue.ValueChanged += () => NodeNamesSize = nodeNameSizeValue.Value;
}
// Enable shadows
@@ -378,7 +396,7 @@ namespace FlaxEditor.Viewport.Previews
if (nodesMask != null && !nodesMask[nodeIndex])
continue;
//var t = new Transform(pose[nodeIndex].TranslationVector, Quaternion.Identity, new Float3(0.1f));
DebugDraw.DrawText(nodes[nodeIndex].Name, pose[nodeIndex].TranslationVector, Color.White, 20, 0.0f, 0.1f);
DebugDraw.DrawText(nodes[nodeIndex].Name, pose[nodeIndex].TranslationVector, Color.White, NodeNamesSize, 0.0f, 0.25f);
}
}
}
@@ -1,6 +1,5 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using FlaxEditor.GUI.ContextMenu;
using FlaxEditor.GUI.Input;
using FlaxEngine;
using FlaxEditor.Viewport.Widgets;
@@ -14,6 +13,7 @@ namespace FlaxEditor.Viewport.Previews
/// <seealso cref="AnimatedModelPreview" />
public class AnimationPreview : AnimatedModelPreview
{
private bool _baseModelMissing;
private ViewportWidgetButton _playPauseButton;
/// <summary>
@@ -94,14 +94,23 @@ namespace FlaxEditor.Viewport.Previews
var style = Style.Current;
var skinnedModel = SkinnedModel;
var baseModelMissing = false;
if (skinnedModel == null)
{
Render2D.DrawText(style.FontLarge, "Missing Base Model", new Rectangle(Float2.Zero, Size), Color.Red, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapWords);
baseModelMissing = true;
}
else if (!skinnedModel.IsLoaded)
{
Render2D.DrawText(style.FontLarge, skinnedModel.LastLoadFailed ? "Failed to load" : "Loading...", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center);
baseModelMissing = true;
}
if (_baseModelMissing && !baseModelMissing)
{
// Focus model when base model appears
ResetCamera();
}
_baseModelMissing = baseModelMissing;
}
/// <inheritdoc />
@@ -132,7 +132,8 @@ namespace FlaxEditor.Windows
if (item is AssetItem assetItem)
{
if (assetItem.IsLoaded)
var asset = FlaxEngine.Content.GetAsset(assetItem.ID);
if (asset != null && (asset.IsLoaded || asset.LastLoadFailed))
cm.AddButton("Reload", assetItem.Reload);
cm.AddButton("Copy asset ID", () => Clipboard.Text = JsonSerializer.GetStringID(assetItem.ID));
cm.AddButton("Select actors using this asset", () => Editor.SceneEditing.SelectActorsUsingAsset(assetItem.ID));
+1 -1
View File
@@ -123,7 +123,7 @@ namespace FlaxEditor.Windows
private readonly ScaledRenderOutputControl _viewport;
private readonly GameRoot _guiRoot;
private bool _showGUI = true, _editGUI = true;
private bool _showDebugDraw = false;
private bool _showDebugDraw = true;
private bool _audioMuted = false;
private float _audioVolume = 1;
private bool _isMaximized = false, _isUnlockingMouse = false;
@@ -98,10 +98,10 @@ namespace FlaxEditor.Windows
[NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)]
[EditorOrder(1400), EditorDisplay("Quality")]
[Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")]
public float RenderingPercentage
public float RenderScale
{
get => MainRenderTask.Instance.RenderingPercentage;
set => MainRenderTask.Instance.RenderingPercentage = value;
get => MainRenderTask.Instance.RenderScale;
set => MainRenderTask.Instance.RenderScale = value;
}
[NoSerialize, DefaultValue(RenderingUpscaleLocation.AfterAntiAliasingPass), VisibleIf(nameof(UpscaleLocation_Visible))]
@@ -113,7 +113,7 @@ namespace FlaxEditor.Windows
set => MainRenderTask.Instance.UpscaleLocation = value;
}
private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderingPercentage < 1.0f;
private bool UpscaleLocation_Visible => MainRenderTask.Instance.RenderScale < 1.0f;
[NoSerialize, DefaultValue(1.0f), Limit(0, 1)]
[EditorOrder(1500), EditorDisplay("Quality"), Tooltip("The global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.")]
+5 -8
View File
@@ -205,10 +205,9 @@ bool BehaviorKnowledge::Set(const StringAnsiView& path, const Variant& value)
bool BehaviorKnowledge::HasGoal(ScriptingTypeHandle type) const
{
for (int32 i = 0; i < Goals.Count(); i++)
for (const Variant& goal : Goals)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
if (goalType == type)
if (goal.Type.GetScriptingType() == type)
return true;
}
return false;
@@ -218,8 +217,7 @@ const Variant& BehaviorKnowledge::GetGoal(ScriptingTypeHandle type) const
{
for (const Variant& goal : Goals)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(goal.Type.GetTypeName());
if (goalType == type)
if (goal.Type.GetScriptingType() == type)
return goal;
}
return Variant::Null;
@@ -242,10 +240,9 @@ void BehaviorKnowledge::RemoveGoal(ScriptingTypeHandle type)
{
for (int32 i = 0; i < Goals.Count(); i++)
{
const ScriptingTypeHandle goalType = Scripting::FindScriptingType(Goals[i].Type.GetTypeName());
if (goalType == type)
if (Goals[i].Type.GetScriptingType() == type)
{
Goals.RemoveAt(i);
Goals.RemoveAtKeepOrder(i);
break;
}
}
@@ -61,14 +61,14 @@ void AnimGraphBase::Clear()
StateTransitions.Resize(0);
// Base
GraphType::Clear();
VisjectGraph::Clear();
}
#if USE_EDITOR
void AnimGraphBase::GetReferences(Array<Guid>& output) const
{
GraphType::GetReferences(output);
VisjectGraph::GetReferences(output);
// Collect references from nested graph (assets used in state machines)
for (const auto* subGraph : SubGraphs)
@@ -873,65 +873,41 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
switch (param->Type.Type)
{
case VariantType::Float2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsFloat2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsFloat3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float4:
case VariantType::Color:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsFloat4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsDouble2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsDouble3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double4:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsDouble4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Int2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsInt2().Raw[box->ID - 1];
break;
case VariantType::Int3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsInt3().Raw[box->ID - 1];
break;
case VariantType::Int4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsInt4().Raw[box->ID - 1];
break;
case VariantType::Matrix:
{
+1
View File
@@ -272,6 +272,7 @@ String Asset::ToString() const
void Asset::OnDeleteObject()
{
PROFILE_CPU_NAMED("Asset.Unload");
ASSERT(IsInMainThread());
// Send event to the gameplay so it can release handle to this asset
@@ -163,7 +163,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box)
// Add to the calling stack
VisualScripting::StackFrame frame = *stack.Stack;
frame.Node = parentNode;
frame.Node = (VisualScriptGraphNode*)parentNode;
frame.Box = box;
frame.PreviousFrame = stack.Stack;
stack.Stack = &frame;
@@ -189,7 +189,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box)
VisjectExecutor::Graph* VisualScriptExecutor::GetCurrentGraph() const
{
auto& stack = ThreadStacks.Get();
return stack.Stack && stack.Stack->Script ? &stack.Stack->Script->Graph : nullptr;
return stack.Stack && stack.Stack->Script ? (Graph*)&stack.Stack->Script->Graph : nullptr;
}
void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
@@ -339,7 +339,7 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
obj = Value::Null;
#else
const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName());
const ScriptingTypeHandle objType = obj.Type.GetScriptingType();
if (!type || !objType || !objType.IsSubclassOf(type))
obj = Value::Null;
#endif
@@ -432,7 +432,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Call Impulse or Pure Method
if (boxBase->ID == 0 || (bool)node->Values[3])
{
auto& cache = node->Data.InvokeMethod;
auto& cache = ((VisualScriptGraphNode*)node)->Data.InvokeMethod;
if (!cache.Method)
{
// Load method signature
@@ -667,7 +667,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Get Field
case 7:
{
auto& cache = node->Data.GetSetField;
auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField;
if (!cache.Field)
{
const auto typeName = (StringView)node->Values[0];
@@ -753,7 +753,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value&
// Get Field
case 8:
{
auto& cache = node->Data.GetSetField;
auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField;
if (!cache.Field)
{
const auto typeName = (StringView)node->Values[0];
+38 -2
View File
@@ -12,11 +12,47 @@
#define VISUAL_SCRIPT_GRAPH_MAX_CALL_STACK 250
#define VISUAL_SCRIPT_DEBUGGING USE_EDITOR
#define VisualScriptGraphNode VisjectGraphNode<>
class VisualScripting;
class VisualScriptingBinaryModule;
/// <summary>
/// Visual Script graph node.
/// </summary>
class VisualScriptGraphNode : public VisjectGraphNode<>
{
public:
struct InvokeMethodData
{
void* Method;
BinaryModule* Module;
int32 ParamsCount;
uint32 OutParamsMask;
bool IsStatic;
};
struct GetSetFieldData
{
void* Field;
BinaryModule* Module;
bool IsStatic;
};
/// <summary>
/// Custom cached data per node type. Compact to use as small amount of memory as possible.
/// </summary>
struct AdditionalData
{
union
{
InvokeMethodData InvokeMethod;
GetSetFieldData GetSetField;
};
};
// The custom per-node data. Used to cache data for faster usage at runtime.
AdditionalData Data;
};
/// <summary>
/// The Visual Script graph data.
/// </summary>
+1 -4
View File
@@ -564,10 +564,7 @@ ContentLoadTask* BinaryAsset::createLoadingTask()
loadTask = preLoadChunksTask;
}
// Before asset loading we have to initialize storage
// TODO: maybe in build game we could do it in place?
// This step is only for opening asset files in background and upgrading them
// In build game we have only a few packages which are ready to use
// Before asset loading we have to initialize storage and pull the asset header
auto initTask = New<InitAssetTask>(this);
initTask->ContinueWith(loadTask);
loadTask = initTask;
+79 -51
View File
@@ -113,7 +113,7 @@ void AssetsCache::Init()
}
// Use only valid entries
if (IsEntryValid(e))
if (IsEntryValid(e) != EntryValidation::Invalid)
_registry.Add(e.Info.ID, e);
else
rejectedCount++;
@@ -295,14 +295,23 @@ bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info)
auto& e = i->Value;
if (e.Info.Path == path)
{
if (!IsEntryValid(e))
const auto validation = IsEntryValid(e);
if (validation == EntryValidation::Invalid)
{
LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName);
_registry.Remove(i);
}
else
{
// Found
#if ENABLE_ASSETS_DISCOVERY
if (validation == EntryValidation::Inaccessible && !e.WarnedInaccessible)
{
e.WarnedInaccessible = true;
LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName);
}
#endif
// Found valid or inaccessible but return cached info either way
result = true;
info = e.Info;
}
@@ -322,13 +331,22 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
auto e = _registry.TryGet(id);
if (e != nullptr)
{
if (!IsEntryValid(*e))
const auto validation = IsEntryValid(*e);
if (validation == EntryValidation::Invalid)
{
LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName);
_registry.Remove(id);
}
else
{
#if ENABLE_ASSETS_DISCOVERY
if (validation == EntryValidation::Inaccessible && !e->WarnedInaccessible)
{
e->WarnedInaccessible = true;
LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName);
}
#endif
// Found
result = true;
info = e->Info;
@@ -360,13 +378,13 @@ void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& resu
void AssetsCache::RegisterAssets(FlaxStorage* storage)
{
PROFILE_CPU();
ASSERT(storage);
// Get all entries
Array<FlaxStorage::Entry> entries;
storage->GetEntries(entries);
ASSERT(entries.HasItems());
if (entries.IsEmpty())
return;
ASSETS_CACHE_LOCK();
auto storagePath = storage->GetPath();
@@ -567,60 +585,70 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa
#endif
bool AssetsCache::IsEntryValid(Entry& e)
AssetsCache::EntryValidation AssetsCache::IsEntryValid(Entry& e)
{
#if ENABLE_ASSETS_DISCOVERY
// Check if file exists
if (FileSystem::FileExists(e.Info.Path))
if (!FileSystem::FileExists(e.Info.Path))
return EntryValidation::Invalid;
// Check if file hasn't been modified
const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path);
if (fileModified == e.FileModified)
{
// Check if file hasn't been modified
const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path);
if (fileModified == e.FileModified)
return true;
const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower();
// Check if it's a binary asset
if (ContentStorageManager::IsFlaxStorageExtension(extension))
{
// Validate ID within storage container
const auto storage = ContentStorageManager::GetStorage(e.Info.Path);
if (storage)
{
// Check if storage at given location contains that asset
const bool isValid = storage->HasAsset(e.Info);
// Update entry and mark cache as dirty
e.FileModified = fileModified;
_isDirty = true;
return isValid;
}
}
// Check for json resource
else if (JsonStorageProxy::IsValidExtension(extension))
{
// Check Json storage layer
Guid jsonId;
String jsonTypeName;
if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName))
{
const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName;
// Update entry and mark cache as dirty
e.FileModified = fileModified;
_isDirty = true;
return isValid;
}
}
e.WarnedInaccessible = false;
return EntryValidation::Valid;
}
return false;
const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower();
// Check if it's a binary asset
if (ContentStorageManager::IsFlaxStorageExtension(extension))
{
// Validate ID within storage container
const auto storage = ContentStorageManager::GetStorage(e.Info.Path);
if (storage)
{
// Check if storage at given location contains that asset
const bool isValid = storage->HasAsset(e.Info);
// Update entry and mark cache as dirty
e.FileModified = fileModified;
e.WarnedInaccessible = false;
_isDirty = true;
return isValid ? EntryValidation::Valid : EntryValidation::Invalid;
}
}
// Check for json resource
else if (JsonStorageProxy::IsValidExtension(extension))
{
// Check Json storage layer
Guid jsonId;
String jsonTypeName;
if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName))
{
const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName;
// Update entry and mark cache as dirty
e.FileModified = fileModified;
e.WarnedInaccessible = false;
_isDirty = true;
return isValid ? EntryValidation::Valid : EntryValidation::Invalid;
}
}
else
{
// Unknown file type
return EntryValidation::Invalid;
}
// File exists but cannot be read (likely locked by git or another process)
return EntryValidation::Inaccessible;
#else
// In game we don't care about it because all cached asset entries are valid (precached)
// Skip only entries with missing file
return e.Info.Path.HasChars();
return e.Info.Path.HasChars() ? EntryValidation::Valid : EntryValidation::Invalid;
#endif
}
+28 -2
View File
@@ -58,6 +58,11 @@ public:
/// The file modified date.
/// </summary>
DateTime FileModified;
/// <summary>
/// True if a warning about this entry being inaccessible has already been logged (prevents log spam). Runtime-only, not serialized.
/// </summary>
bool WarnedInaccessible = false;
#endif
Entry()
@@ -73,6 +78,27 @@ public:
}
};
/// <summary>
/// Result of validating an asset cache entry.
/// </summary>
enum class EntryValidation
{
/// <summary>
/// File verified, contains this asset.
/// </summary>
Valid,
/// <summary>
/// File missing or contains a different asset.
/// </summary>
Invalid,
/// <summary>
/// File exists but cannot be opened (locked by another process).
/// </summary>
Inaccessible,
};
typedef Dictionary<Guid, Entry> Registry;
typedef Dictionary<String, Guid> PathsMapping;
@@ -232,6 +258,6 @@ public:
/// Determines whether cached asset entry is valid.
/// </summary>
/// <param name="e">The asset entry.</param>
/// <returns>True if is valid, otherwise false.</returns>
bool IsEntryValid(Entry& e);
/// <returns>The validation result.</returns>
EntryValidation IsEntryValid(Entry& e);
};
@@ -54,6 +54,7 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
Locker.Lock();
// Try fast lookup
bool wasCached = true;
FlaxStorage* storage;
if (!StorageMap.TryGet(path, storage))
{
@@ -74,6 +75,7 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
// Register storage container
StorageMap.Add(path, storage);
wasCached = false;
}
// Build reference (before releasing the lock so ContentStorageSystem::Job won't delete it when running from async thread)
@@ -90,6 +92,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
if (loadFailed)
{
LOG(Error, "Failed to load {0}.", path);
if (wasCached)
return result;
Locker.Lock();
StorageMap.Remove(path);
if (storage->IsPackage())
@@ -243,9 +243,9 @@ FlaxStorage::~FlaxStorage()
{
// Validate if has been disposed
ASSERT(IsDisposed());
CHECK(_chunksLock == 0);
CHECK(_refCount == 0);
CHECK(_isUnloadingData == 0);
CHECK_NO_RETURN(_chunksLock == 0);
CHECK_NO_RETURN(_refCount == 0);
CHECK_NO_RETURN(_isUnloadingData == 0);
ASSERT(_chunks.IsEmpty());
#if USE_EDITOR
+2
View File
@@ -9,6 +9,8 @@
// Float
static_assert(sizeof(Float2) == 8, "Invalid Float2 type size.");
static_assert(sizeof(Int2) == 8, "Invalid Int2 type size.");
static_assert(sizeof(Double2) == 16, "Invalid Double2 type size.");
template<>
const Float2 Float2::Zero(0.0f);
+2
View File
@@ -13,6 +13,8 @@
// Float
static_assert(sizeof(Float3) == 12, "Invalid Float3 type size.");
static_assert(sizeof(Int3) == 12, "Invalid Int3 type size.");
static_assert(sizeof(Double3) == 24, "Invalid Double3 type size.");
template<>
const Float3 Float3::Zero(0.0f);
+2
View File
@@ -11,6 +11,8 @@
// Float
static_assert(sizeof(Float4) == 16, "Invalid Float4 type size.");
static_assert(sizeof(Int4) == 16, "Invalid Int4 type size.");
static_assert(sizeof(Double4) == 32, "Invalid Double4 type size.");
template<>
const Float4 Float4::Zero(0.0f);
+130 -11
View File
@@ -4,6 +4,7 @@
#include "Engine/Core/Collections/HashFunctions.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Content/Asset.h"
#include "Engine/Content/Deprecated.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Mathd.h"
#include "Engine/Core/Math/BoundingBox.h"
@@ -120,7 +121,7 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName, bool static
VariantType::VariantType(Types type, const ScriptingType& sType)
: VariantType(type)
{
SetTypeName(sType.Fullname, sType.Module->CanReload);
SetTypeName(sType.Fullname, !sType.Module->CanReload);
}
VariantType::VariantType(Types type, const MClass* klass)
@@ -340,13 +341,13 @@ void VariantType::SetTypeName(const StringAnsiView& typeName, bool staticName)
void VariantType::SetTypeName(const ScriptingType& type)
{
SetTypeName(type.Fullname, type.Module->CanReload);
SetTypeName(type.Fullname, !type.Module->CanReload);
}
void VariantType::SetTypeName(const MClass& klass)
{
#if USE_CSHARP
SetTypeName(klass.GetFullName(), klass.GetAssembly()->CanReload());
SetTypeName(klass.GetFullName(), !klass.GetAssembly()->CanReload());
#endif
}
@@ -357,6 +358,11 @@ const char* VariantType::GetTypeName() const
return InBuiltTypesTypeNames[Type];
}
ScriptingTypeHandle VariantType::GetScriptingType() const
{
return Scripting::FindScriptingType(GetTypeName());
}
VariantType VariantType::GetElementType() const
{
if (Type == Array)
@@ -3189,6 +3195,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3212,6 +3221,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3235,6 +3247,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3258,6 +3273,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3281,6 +3299,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3304,6 +3325,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3327,6 +3351,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3350,6 +3377,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3373,6 +3403,9 @@ bool Variant::CanCast(const Variant& v, const VariantType& to)
case VariantType::Double2:
case VariantType::Double3:
case VariantType::Double4:
case VariantType::Int2:
case VariantType::Int3:
case VariantType::Int4:
case VariantType::Enum:
return true;
default:
@@ -3516,6 +3549,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3(v.AsBool ? 1.0 : 0.0));
case VariantType::Double4:
return Variant(Double4(v.AsBool ? 1.0 : 0.0));
case VariantType::Int2:
return Variant(Int2(v.AsBool ? 1 : 0));
case VariantType::Int3:
return Variant(Int3(v.AsBool ? 1 : 0));
case VariantType::Int4:
return Variant(Int4(v.AsBool ? 1 : 0));
case VariantType::Enum:
return Enum(to, v.AsBool ? 1 : 0);
default: ;
@@ -3554,6 +3593,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsInt16));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt16));
case VariantType::Int2:
return Variant(Int2(v.AsInt16));
case VariantType::Int3:
return Variant(Int3(v.AsInt16));
case VariantType::Int4:
return Variant(Int4(v.AsInt16));
case VariantType::Enum:
return Enum(to, (int64)v.AsInt16);
default: ;
@@ -3584,6 +3629,18 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Float3((float)v.AsInt));
case VariantType::Float4:
return Variant(Float4((float)v.AsInt));
case VariantType::Double2:
return Variant(Double2((double)v.AsInt));
case VariantType::Double3:
return Variant(Double3((double)v.AsInt));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt));
case VariantType::Int2:
return Variant(Int2(v.AsInt));
case VariantType::Int3:
return Variant(Int3(v.AsInt));
case VariantType::Int4:
return Variant(Int4(v.AsInt));
case VariantType::Color:
return Variant(Color((float)v.AsInt));
case VariantType::Enum:
@@ -3624,6 +3681,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsUint16));
case VariantType::Double4:
return Variant(Double4((double)v.AsUint16));
case VariantType::Int2:
return Variant(Int2(v.AsUint16));
case VariantType::Int3:
return Variant(Int3(v.AsUint16));
case VariantType::Int4:
return Variant(Int4(v.AsUint16));
case VariantType::Enum:
return Enum(to, (int64)v.AsUint16);
default: ;
@@ -3662,6 +3725,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsUint));
case VariantType::Double4:
return Variant(Double4((double)v.AsUint));
case VariantType::Int2:
return Variant(Int2((int32)v.AsUint));
case VariantType::Int3:
return Variant(Int3((int32)v.AsUint));
case VariantType::Int4:
return Variant(Int4((int32)v.AsUint));
case VariantType::Enum:
return Enum(to, (int64)v.AsUint);
default: ;
@@ -3700,6 +3769,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3((double)v.AsInt64));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt64));
case VariantType::Int2:
return Variant(Int2((int32)v.AsInt64));
case VariantType::Int3:
return Variant(Int3((int32)v.AsInt64));
case VariantType::Int4:
return Variant(Int4((int32)v.AsInt64));
case VariantType::Enum:
return Enum(to, (int64)v.AsInt64);
default: ;
@@ -3719,25 +3794,31 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
case VariantType::Uint16:
return Variant((uint16)v.AsUint16);
case VariantType::Uint:
return Variant((uint32)v.AsUint);
return Variant((uint32)v.AsUint64);
case VariantType::Float:
return Variant((float)v.AsUint64);
case VariantType::Double:
return Variant((double)v.AsUint64);
case VariantType::Float2:
return Variant(Float2((float)v.AsInt));
return Variant(Float2((float)v.AsUint64));
case VariantType::Float3:
return Variant(Float3((float)v.AsInt));
return Variant(Float3((float)v.AsUint64));
case VariantType::Float4:
return Variant(Float4((float)v.AsInt));
return Variant(Float4((float)v.AsUint64));
case VariantType::Color:
return Variant(Color((float)v.AsInt));
return Variant(Color((float)v.AsUint64));
case VariantType::Double2:
return Variant(Double2((double)v.AsInt));
return Variant(Double2((double)v.AsUint64));
case VariantType::Double3:
return Variant(Double3((double)v.AsInt));
return Variant(Double3((double)v.AsUint64));
case VariantType::Double4:
return Variant(Double4((double)v.AsInt));
return Variant(Double4((double)v.AsUint64));
case VariantType::Int2:
return Variant(Int2((int32)v.AsUint64));
case VariantType::Int3:
return Variant(Int3((int32)v.AsUint64));
case VariantType::Int4:
return Variant(Int4((int32)v.AsUint64));
case VariantType::Enum:
return Enum(to, (int64)v.AsInt);
default: ;
@@ -3776,6 +3857,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3(v.AsFloat));
case VariantType::Double4:
return Variant(Double4(v.AsFloat));
case VariantType::Int2:
return Variant(Int2((int32)v.AsFloat));
case VariantType::Int3:
return Variant(Int3((int32)v.AsFloat));
case VariantType::Int4:
return Variant(Int4((int32)v.AsFloat));
case VariantType::Enum:
return Enum(to, (int64)v.AsFloat);
default: ;
@@ -3814,6 +3901,12 @@ Variant Variant::Cast(const Variant& v, const VariantType& to)
return Variant(Double3(v.AsDouble));
case VariantType::Double4:
return Variant(Double4(v.AsDouble));
case VariantType::Int2:
return Variant(Int2((int32)v.AsFloat));
case VariantType::Int3:
return Variant(Int3((int32)v.AsFloat));
case VariantType::Int4:
return Variant(Int4((int32)v.AsFloat));
case VariantType::Enum:
return Enum(to, (int64)v.AsDouble);
default: ;
@@ -4242,6 +4335,7 @@ void Variant::AllocStructure()
AsBlob.Length = 2;
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
*((int16*)AsBlob.Data) = 0;
MARK_CONTENT_DEPRECATED();
}
#if USE_CSHARP
else if (const auto mclass = Scripting::FindClass(typeName))
@@ -4367,6 +4461,13 @@ uint32 GetHash(const Variant& key)
return GetHash((void*)key.AsObject);
case VariantType::Structure:
case VariantType::Blob:
case VariantType::Transform:
case VariantType::Matrix:
#if USE_LARGE_WORLDS
case VariantType::BoundingSphere:
case VariantType::BoundingBox:
case VariantType::Ray:
#endif
return Crc::MemCrc32(key.AsBlob.Data, key.AsBlob.Length);
case VariantType::Asset:
return GetHash((void*)key.AsAsset);
@@ -4376,6 +4477,24 @@ uint32 GetHash(const Variant& key)
return GetHash(*(Guid*)key.AsData);
case VariantType::Typename:
return GetHash((const char*)key.AsBlob.Data);
case VariantType::Float2:
return GetHash(*(const Float2*)key.AsData);
case VariantType::Float3:
return GetHash(*(const Float3*)key.AsData);
case VariantType::Float4:
return GetHash(*(const Float4*)key.AsData);
case VariantType::Int2:
return GetHash(*(const Int2*)key.AsData);
case VariantType::Int3:
return GetHash(*(const Int3*)key.AsData);
case VariantType::Int4:
return GetHash(*(const Int4*)key.AsData);
case VariantType::Double2:
return GetHash(*(const Double2*)key.AsData);
case VariantType::Double3:
return GetHash(*(const Double3*)key.AsData);
case VariantType::Double4:
return GetHash(*(const Double4*)key.AsBlob.Data);
case VariantType::ManagedObject:
#if USE_CSHARP
return key.MANAGED_GC_HANDLE ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.MANAGED_GC_HANDLE)) : 0;
+10
View File
@@ -151,6 +151,7 @@ public:
void SetTypeName(const ScriptingType& type);
void SetTypeName(const MClass& klass);
const char* GetTypeName() const;
ScriptingTypeHandle GetScriptingType() const;
VariantType GetElementType() const;
// Drops custom type name into the name allocated by the scripting module to reduce memory allocations when referencing types.
void Inline();
@@ -420,6 +421,15 @@ public:
return MoveTemp(v);
}
template<typename T>
static typename TEnableIf<TIsEnum<T>::Value, Variant>::Type Enum(const T value)
{
Variant v;
v.SetType(VariantType(VariantType::Enum, StaticType<T>().GetType()));
v.AsUint64 = (uint64)value;
return MoveTemp(v);
}
template<typename T>
static typename TEnableIf<!TIsEnum<T>::Value && !TIsPointer<T>::Value, Variant>::Type Structure(VariantType&& type, const T& value)
{
+1 -1
View File
@@ -85,7 +85,7 @@ struct CommandData
else if (value.Type.Type == VariantType::Structure)
{
// Prettify structure printing
ScriptingTypeHandle resultType = Scripting::FindScriptingType(value.Type.GetTypeName());
ScriptingTypeHandle resultType = value.Type.GetScriptingType();
if (resultType)
{
Array<void*> fields;
@@ -76,9 +76,10 @@ void GPUTasksContext::OnFrameBegin()
{
auto task = _tasksSyncing[i];
auto state = task->GetState();
if (EnumHasAllFlags(task->Flags, ObjectFlags::WasMarkedToDelete))
state = TaskState::Finished;
if (task->GetSyncPoint() <= _currentSyncPoint && state != TaskState::Finished)
{
// TODO: add stats counter and count performed jobs, print to log on exit.
task->Sync();
}
if (state == TaskState::Failed || state == TaskState::Canceled)
@@ -460,6 +460,18 @@ void MaterialParameter::Bind(BindMeta& meta) const
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Float4)));
*((Float4*)(meta.Constants.Get() + _offset)) = (Float4)e->Value.AsDouble4();
break;
case VariantType::Int2:
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int2)));
*((Int2*)(meta.Constants.Get() + _offset)) = (Int2)e->Value.AsInt2();
break;
case VariantType::Int3:
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int3)));
*((Int3*)(meta.Constants.Get() + _offset)) = (Int3)e->Value.AsInt3();
break;
case VariantType::Int4:
ASSERT_LOW_LAYER(meta.Constants.Get() && meta.Constants.Length() >= (int32)(_offset + sizeof(Int4)));
*((Int4*)(meta.Constants.Get() + _offset)) = (Int4)e->Value.AsInt4();
break;
default: ;
}
}
+10 -5
View File
@@ -621,7 +621,7 @@ namespace FlaxEngine
{
ibData = dataPtr[IB];
use16BitIndexBuffer = _formats[IB] == PixelFormat.R16_UInt;
triangles = (uint)(_data[IB].Length / PixelFormatExtensions.SizeInBytes(_formats[IB]));
triangles = (uint)(_data[IB].Length / (PixelFormatExtensions.SizeInBytes(_formats[IB]) * 3));
}
if (mesh.Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
@@ -643,11 +643,16 @@ namespace FlaxEngine
else
{
Float3 min = Float3.Maximum, max = Float3.Minimum;
for (int i = 0; i < vertices; i++)
PixelFormatSampler.Get(positionStream.Format, out var positionSampler);
int positionStride = positionStream.Stride;
fixed (byte* data = positionStream.Data)
{
Float3 pos = positionStream.GetFloat3(i);
Float3.Min(ref min, ref pos, out min);
Float3.Max(ref max, ref pos, out max);
for (int i = 0; i < vertices; i++)
{
Float3 pos = new Float3(positionSampler.Read(data + i * positionStride));
Float3.Min(ref min, ref pos, out min);
Float3.Max(ref max, ref pos, out max);
}
}
bounds = new BoundingBox(min, max);
}
@@ -21,6 +21,13 @@ bool ModelInstanceEntries::HasContentLoaded() const
return result;
}
bool ModelInstanceEntries::ShouldSerialize(const void* otherObj) const
{
if (!otherObj)
return true;
return !(*this == *(const ModelInstanceEntries*)otherObj);
}
void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherObj)
{
SERIALIZE_GET_OTHER_OBJ(ModelInstanceEntries);
@@ -43,12 +50,13 @@ void ModelInstanceEntries::Serialize(SerializeStream& stream, const void* otherO
void ModelInstanceEntries::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
PROFILE_MEM(Graphics);
const DeserializeStream& entries = stream["Entries"];
ASSERT(entries.IsArray());
Resize(entries.Size());
for (rapidjson::SizeType i = 0; i < entries.Size(); i++)
const DeserializeStream& entriesData = stream[rapidjson_flax::Value(rapidjson::StringRef("Entries", 7))];
CHECK(entriesData.IsArray());
Resize(entriesData.Size());
ModelInstanceEntry* entries = Get();
for (int32 i = 0; i < Count(); i++)
{
At(i).Deserialize((DeserializeStream&)entries[i], modifier);
entries[i].Deserialize((DeserializeStream&)entriesData[i], modifier);
}
}
@@ -115,6 +115,7 @@ public:
public:
// [ISerializable]
bool ShouldSerialize(const void* otherObj) const override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
+21 -11
View File
@@ -353,8 +353,11 @@ Viewport SceneRenderTask::GetViewport() const
viewport = Buffers->GetViewport();
else
viewport = Viewport(0, 0, 1280, 720);
viewport.Width *= RenderingPercentage;
viewport.Height *= RenderingPercentage;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
float renderScale = RenderingPercentage * RenderScale;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
viewport.Width *= renderScale;
viewport.Height *= renderScale;
return viewport;
}
@@ -394,13 +397,16 @@ void SceneRenderTask::OnBegin(GPUContext* context)
}
// Setup render buffers for the output rendering resolution
PRAGMA_DISABLE_DEPRECATION_WARNINGS
float renderScale = RenderingPercentage * RenderScale;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (Output)
{
Buffers->Init((int32)((float)Output->Width() * RenderingPercentage), (int32)((float)Output->Height() * RenderingPercentage));
Buffers->Init((int32)((float)Output->Width() * renderScale), (int32)((float)Output->Height() * renderScale));
}
else if (SwapChain)
{
Buffers->Init((int32)((float)SwapChain->GetWidth() * RenderingPercentage), (int32)((float)SwapChain->GetHeight() * RenderingPercentage));
Buffers->Init((int32)((float)SwapChain->GetWidth() * renderScale), (int32)((float)SwapChain->GetHeight() * renderScale));
}
}
@@ -434,7 +440,10 @@ bool SceneRenderTask::Resize(int32 width, int32 height)
PROFILE_MEM(Graphics);
if (Output && Output->Resize(width, height))
return true;
if (Buffers && Buffers->Init((int32)((float)width * RenderingPercentage), (int32)((float)height * RenderingPercentage)))
PRAGMA_DISABLE_DEPRECATION_WARNINGS
float renderScale = RenderingPercentage * RenderScale;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (Buffers && Buffers->Init((int32)((float)width * renderScale), (int32)((float)height * renderScale)))
return true;
return false;
}
@@ -477,12 +486,6 @@ void MainRenderTask::OnBegin(GPUContext* context)
// Use the main camera for the game (can be later overriden in Begin event by external code)
Camera = Camera::GetMainCamera();
#if !USE_EDITOR
// Sync render buffers size with the backbuffer
const auto size = Screen::GetSize();
Buffers->Init((int32)(size.X * RenderingPercentage), (int32)(size.Y * RenderingPercentage));
#endif
SceneRenderTask::OnBegin(context);
}
@@ -507,3 +510,10 @@ RenderContextBatch::RenderContextBatch(const RenderContext& context)
Contexts.Add(context);
EnableAsync = JobSystem::GetThreadsCount() > 1;
}
void RenderContextBatch::FlushWaitLabels()
{
for (const int64 label : WaitLabels)
JobSystem::Wait(label);
WaitLabels.Clear();
}
+12 -1
View File
@@ -268,8 +268,14 @@ public:
/// <summary>
/// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.
/// [Deprecated in v1.13]
/// </summary>
API_FIELD() float RenderingPercentage = 1.0f;
API_FIELD() DEPRECATED("Use RenderScale instead.") float RenderingPercentage = 1.0f;
/// <summary>
/// The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.
/// </summary>
API_FIELD() float RenderScale = 1.0f;
/// <summary>
/// The image resolution upscale location within rendering pipeline. Unused if RenderingPercentage is 1.
@@ -533,4 +539,9 @@ API_STRUCT(NoDefault) struct FLAXENGINE_API RenderContextBatch
{
return Contexts.Get()[0];
}
/// <summary>
/// Waits for all scheduled async jobs to complete and clears WaitLabels.
/// </summary>
void FlushWaitLabels();
};
@@ -57,7 +57,7 @@ void CmdBufferVulkan::End()
if (vkCmdEndDebugUtilsLabelEXT)
vkCmdEndDebugUtilsLabelEXT(GetHandle());
#endif
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
tracy::EndVkZoneScope(_tracyZones.Last().Data);
_tracyZones.RemoveLast();
#endif
@@ -101,7 +101,7 @@ void CmdBufferVulkan::BeginEvent(const Char* name, void* tracyContext)
char buffer[60];
int32 bufferSize = StringUtils::Copy(buffer, name, sizeof(buffer));
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
auto& zone = _tracyZones.AddOne();
tracy::BeginVkZoneScope(zone.Data, tracyContext, GetHandle(), buffer, bufferSize);
#endif
@@ -128,7 +128,7 @@ void CmdBufferVulkan::EndEvent()
vkCmdEndDebugUtilsLabelEXT(GetHandle());
#endif
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
tracy::EndVkZoneScope(_tracyZones.Last().Data);
_tracyZones.RemoveLast();
#endif
@@ -43,8 +43,10 @@ private:
FenceVulkan* _fence;
#if GPU_ALLOW_PROFILE_EVENTS
int32 _eventsBegin = 0;
#if VULKAN_USE_TRACY_GPU
struct TracyZone { byte Data[TracyVulkanZoneSize]; };
Array<TracyZone, InlinedAllocation<32>> _tracyZones;
#endif
#endif
// The latest value when command buffer was submitted.
@@ -49,6 +49,10 @@
#define VULKAN_USE_TIMER_QUERIES 1
#endif
#ifndef VULKAN_USE_TRACY_GPU
#define VULKAN_USE_TRACY_GPU (GPU_ENABLE_TRACY && VULKAN_USE_TIMER_QUERIES)
#endif
// Fence wait operation timeout in seconds
#ifndef VULKAN_WAIT_TIMEOUT
#define VULKAN_WAIT_TIMEOUT 5.0f
@@ -112,7 +112,7 @@ GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue)
_handlesSizes[(int32)SpirvShaderResourceBindingType::UAV] = GPU_MAX_UA_BINDED;
#endif
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
#if VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset && !PLATFORM_SWITCH
// Use calibrated timestamps extension
if (vkResetQueryPoolEXT && vkGetCalibratedTimestampsEXT && _device->PhysicalDeviceFeatures12.hostQueryReset)
@@ -139,7 +139,7 @@ GPUContextVulkan::GPUContextVulkan(GPUDeviceVulkan* device, QueueVulkan* queue)
GPUContextVulkan::~GPUContextVulkan()
{
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
tracy::DestroyVkContext(_tracyContext);
#endif
for (int32 i = 0; i < _descriptorPools.Count(); i++)
@@ -921,7 +921,7 @@ void GPUContextVulkan::FrameEnd()
// Execute any queued layout transitions that weren't already handled by the render pass
FlushBarriers();
#if GPU_ENABLE_TRACY
#if VULKAN_USE_TRACY_GPU
if (cmdBuffer)
tracy::CollectVkContext(_tracyContext, cmdBuffer->GetHandle());
#endif
@@ -935,7 +935,7 @@ void GPUContextVulkan::FrameEnd()
void GPUContextVulkan::EventBegin(const Char* name)
{
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
#if COMPILE_WITH_PROFILER
#if VULKAN_USE_TRACY_GPU
void* tracyContext = _tracyContext;
#else
void* tracyContext = nullptr;
@@ -44,7 +44,7 @@ static const char* GInstanceExtensions[] =
#if defined(VK_KHR_display) && 0
VK_KHR_DISPLAY_EXTENSION_NAME,
#endif
#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, // Required by VK_EXT_host_query_reset (unless using Vulkan 1.1 or newer)
#endif
nullptr
@@ -65,7 +65,7 @@ static const char* GDeviceExtensions[] =
#if VK_KHR_sampler_mirror_clamp_to_edge
VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME,
#endif
#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME,
VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME,
#endif
@@ -1732,7 +1732,7 @@ bool GPUDeviceVulkan::Init()
VulkanPlatform::RestrictEnabledPhysicalDeviceFeatures(PhysicalDeviceFeatures, enabledFeatures);
deviceInfo.pEnabledFeatures = &enabledFeatures;
#if GPU_ENABLE_TRACY && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
#if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset
VkPhysicalDeviceHostQueryResetFeatures resetFeatures;
if (PhysicalDeviceFeatures12.hostQueryReset)
{
+3 -7
View File
@@ -820,8 +820,7 @@ void AnimatedModel::RunBlendShapeDeformer(const MeshBase* mesh, MeshDeformationD
void AnimatedModel::BeginPlay(SceneBeginData* data)
{
if (SkinnedModel && SkinnedModel->IsLoaded())
PreInitSkinningData();
PreInitSkinningData();
// Base
ModelInstanceActor::BeginPlay(data);
@@ -1263,9 +1262,7 @@ void AnimatedModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
SERIALIZE(RootMotionTarget);
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
}
void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -1290,8 +1287,7 @@ void AnimatedModel::Deserialize(DeserializeStream& stream, ISerializeModifier* m
DESERIALIZE(ShadowsMode);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
DESERIALIZE(RootMotionTarget);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
+27 -17
View File
@@ -98,99 +98,99 @@ public:
/// <summary>
/// The skinned model asset used for rendering.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(10), DefaultValue(null), EditorDisplay(\"Skeleton\")")
AssetReference<SkinnedModel> SkinnedModel;
/// <summary>
/// The animation graph asset used for the skinned mesh skeleton bones evaluation (controls the animation).
/// </summary>
API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(15), DefaultValue(null), EditorDisplay(\"Skeleton\")")
AssetReference<AnimationGraph> AnimationGraph;
/// <summary>
/// If true, use per-bone motion blur on this skeletal model. It requires additional rendering, can be disabled to save performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(20), DefaultValue(true), EditorDisplay(\"Drawing\")")
bool PerBoneMotionBlur = true;
/// <summary>
/// If true, animation speed will be affected by the global timescale parameter.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(30), DefaultValue(true), EditorDisplay(\"Updating\")")
bool UseTimeScale = true;
/// <summary>
/// If true, the animation will be updated even when an actor cannot be seen by any camera. Otherwise, the animations themselves will also stop running when the actor is off-screen.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"Updating\")")
bool UpdateWhenOffscreen = false;
/// <summary>
/// The animation update delta timescale. Can be used to speed up animation playback or create slow motion effect.
/// </summary>
API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(45), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Updating\")")
float UpdateSpeed = 1.0f;
/// <summary>
/// The animation update mode. Can be used to optimize the performance.
/// </summary>
API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(50), DefaultValue(AnimationUpdateMode.Auto), EditorDisplay(\"Updating\")")
AnimationUpdateMode UpdateMode = AnimationUpdateMode::Auto;
/// <summary>
/// The master scale parameter for the actor bounding box. Helps to reduce mesh flickering effect on screen edges.
/// </summary>
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(60), DefaultValue(1.5f), Limit(0, float.MaxValue, 0.025f), EditorDisplay(\"Drawing\")")
float BoundsScale = 1.5f;
/// <summary>
/// The custom bounds(in actor local space). If set to empty bounds then source skinned model bind pose bounds will be used.
/// </summary>
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(70), EditorDisplay(\"Drawing\")")
BoundingBox CustomBounds = BoundingBox::Zero;
/// <summary>
/// The model Level Of Detail bias value. Allows to increase or decrease rendered model quality.
/// </summary>
API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"LOD Bias\")")
API_FIELD(Attributes="EditorOrder(80), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Drawing\", \"LOD Bias\")")
int32 LODBias = 0;
/// <summary>
/// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature.
/// </summary>
API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Skinned Model\", \"Forced LOD\")")
API_FIELD(Attributes="EditorOrder(90), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Drawing\", \"Forced LOD\")")
int32 ForcedLOD = -1;
/// <summary>
/// The draw passes to use for rendering this object.
/// </summary>
API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(100), DefaultValue(DrawPass.Default), EditorDisplay(\"Drawing\")")
DrawPass DrawModes = DrawPass::Default;
/// <summary>
/// The object sort order key used when sorting drawable objects during rendering. Use lower values to draw object before others, higher values are rendered later (on top). Can be used to control transparency drawing.
/// </summary>
API_FIELD(Attributes="EditorDisplay(\"Skinned Model\"), EditorOrder(110), DefaultValue(0)")
API_FIELD(Attributes="EditorOrder(110), DefaultValue(0), EditorDisplay(\"Drawing\")")
int8 SortOrder = 0;
/// <summary>
/// The shadows casting mode.
/// [Deprecated on 26.10.2022, expires on 26.10.2024]
/// </summary>
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(110), DefaultValue(ShadowsCastingMode.All), EditorDisplay(\"Drawing\")")
DEPRECATED() ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
/// <summary>
/// The animation root motion apply target. If not specified the animated model will apply it itself.
/// </summary>
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skinned Model\")")
API_FIELD(Attributes="EditorOrder(120), DefaultValue(null), EditorDisplay(\"Skeleton\")")
ScriptingObjectReference<Actor> RootMotionTarget;
#if USE_EDITOR
/// <summary>
/// If checked, the skeleton pose will be shawn during debug shapes drawing.
/// If checked, the skeleton pose will be shown during debug shapes drawing.
/// </summary>
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skinned Model\")") bool ShowDebugDrawSkeleton = false;
API_FIELD(Attributes="EditorOrder(200), EditorDisplay(\"Skeleton\"), VisibleIf(nameof(ShowDebugDrawOptions))") bool ShowDebugDrawSkeleton = false;
#endif
public:
@@ -440,6 +440,16 @@ public:
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
private:
#if USE_EDITOR
/// <summary>
/// Used to hide <see cref="ShowDebugDrawSkeleton"/> options if when the skinned model or animation graph is null.
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetShowDebugDrawOptions() const
{
return SkinnedModel != nullptr && AnimationGraph != nullptr;
}
#endif
void ApplyRootMotion(const Transform& rootMotionDelta);
void SyncParameters();
void RunBlendShapeDeformer(const MeshBase* mesh, struct MeshDeformationData& deformation);
+2 -5
View File
@@ -497,9 +497,7 @@ void SplineModel::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(PreTransform, _preTransform)
SERIALIZE(Model);
SERIALIZE(DrawModes);
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
}
void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -514,8 +512,7 @@ void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE_MEMBER(PreTransform, _preTransform);
DESERIALIZE(Model);
DESERIALIZE(DrawModes);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
// [Deprecated on 07.02.2022, expires on 07.02.2024]
if (modifier->EngineBuild <= 6330)
+2 -3
View File
@@ -486,8 +486,7 @@ void StaticModel::Serialize(SerializeStream& stream, const void* otherObj)
Lightmap.Serialize(stream);
}
stream.JKEY("Buffer");
stream.Object(&Entries, other ? &other->Entries : nullptr);
SERIALIZE_MEMBER(Buffer, Entries);
if (_vertexColorsCount)
{
@@ -525,7 +524,7 @@ void StaticModel::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
DESERIALIZE_MEMBER(SortOrder, _sortOrder);
DESERIALIZE_MEMBER(DrawModes, _drawModes);
Lightmap.Deserialize(stream);
Entries.DeserializeIfExists(stream, "Buffer", modifier);
DESERIALIZE_MEMBER(Buffer, Entries);
{
const auto member = stream.FindMember("VertexColors");
+35 -25
View File
@@ -24,6 +24,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Editor/Editor.h"
#include "FlaxEngine.Gen.h"
// Apply flow:
// - collect all prefabs using this prefab (load and create default instances)
@@ -772,7 +773,13 @@ bool Prefab::ApplyAll(Actor* targetActor)
if (ApplyAllInternal(targetActor, true, thisPrefabInstancesData))
return true;
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
// Sync nested prefabs
if (allPrefabs.HasItems())
{
LOG(Info, "Updating referencing prefabs");
HashSet<Guid> synced;
SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
}
const auto endTime = DateTime::NowUTC();
LOG(Info, "Prefab updated! {0} ms", (int32)(endTime - startTime).GetTotalMilliseconds());
@@ -1027,8 +1034,14 @@ bool Prefab::ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPr
rapidjson_flax::Document targetDataDocument;
if (NestedPrefabs.HasItems())
{
// Use initial data buffer (unstripped) but reorder objects to match the sequence (eg. when new object was added to the nested prefab)
targetDataDocument.Parse(dataBuffer.GetString(), dataBuffer.GetSize());
SceneObjectsFactory::PrefabSyncData prefabSyncData(*sceneObjects.Value, targetDataDocument, modifier.Value);
Array<SceneObject*> reorderedObjects = *sceneObjects.Value;
newPrefabInstanceIdToDataIndexCounter = 0;
for (auto i = newPrefabInstanceIdToDataIndex.Begin(); i.IsNotEnd(); ++i)
reorderedObjects.Insert(i->Value, sceneObjects->At(newPrefabInstanceIdToDataIndexStart + newPrefabInstanceIdToDataIndexCounter++));
reorderedObjects.Resize(sceneObjects.Value->Count()); // reorderedObjects matches order in targetDataDocument
SceneObjectsFactory::PrefabSyncData prefabSyncData(reorderedObjects, targetDataDocument, modifier.Value);
SceneObjectsFactory::SetupPrefabInstances(context, prefabSyncData);
if (context.Instances.HasItems())
@@ -1236,7 +1249,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
{
return Init(TypeName, StringAnsiView(tmpBuffer.GetString(), (int32)tmpBuffer.GetSize()));
}
#if 1 // Set to 0 to use memory-only reload that does not modifies the source file - useful for testing and debugging prefabs apply
#if 1 // Set to 0 to use memory-only reload that does not modify the source file - useful for testing and debugging prefabs apply
#if COMPILE_WITH_ASSETS_IMPORTER
Locker.Unlock();
@@ -1295,7 +1308,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
_defaultInstance->DeleteObject();
_defaultInstance = nullptr;
}
_isLoaded = false;
_loadState = 0;
// Update prefab data manually (to prevent updating source asset file - just for testing)
Document.Parse(buffer.GetString(), buffer.GetSize());
@@ -1348,7 +1361,7 @@ bool Prefab::UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, r
NestedPrefabs.Add(prefabId);
}
}
_isLoaded = true;
_loadState = 1;
}
#endif
@@ -1395,34 +1408,31 @@ bool Prefab::SyncChangesInternal(PrefabInstancesData& prefabInstancesData)
return ApplyAllInternal(targetActor, false, prefabInstancesData);
}
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const
void Prefab::SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid>& synced) const
{
PROFILE_CPU();
LOG(Info, "Updating referencing prefabs");
// TODO: this may not work well for very complex prefab nesting -> loop order matters, maybe build a graph of dependencies?
// Call recursive for all referencing prefab assets to refresh nested prefabs
for (int32 i = 0; i < allPrefabs.Count(); i++)
{
auto nestedPrefab = allPrefabs[i].Get();
if (nestedPrefab)
Prefab* nestedPrefab = allPrefabs[i].Get();
if (!nestedPrefab || synced.Contains(nestedPrefab->GetID()))
continue;
if (nestedPrefab->WaitForLoaded())
{
if (nestedPrefab->WaitForLoaded())
{
LOG(Warning, "Waiting for prefab asset load failed.");
continue;
}
LOG(Warning, "Waiting for '{}' load failed.", nestedPrefab->ToString());
continue;
}
// Sync only if prefab is used by this prefab (directly) and it has been captured before
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
if (nestedPrefabIndex != -1)
{
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
continue;
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData);
ObjectsRemovalService::Flush();
}
// Sync only if prefab is used by this prefab (directly) and it has been captured before
const int32 nestedPrefabIndex = nestedPrefab->NestedPrefabs.Find(GetID());
if (nestedPrefabIndex != -1)
{
synced.Add(nestedPrefab->GetID());
if (nestedPrefab->SyncChangesInternal(allPrefabsInstancesData[i]))
continue;
nestedPrefab->SyncNestedPrefabs(allPrefabs, allPrefabsInstancesData, synced);
ObjectsRemovalService::Flush();
}
}
}
+4 -1
View File
@@ -7,6 +7,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Level/Actor.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Scripting/Scripting.h"
@@ -22,6 +23,7 @@ Prefab::Prefab(const SpawnParams& params, const AssetInfo* info)
Guid Prefab::GetRootObjectId() const
{
PROFILE_CPU();
ASSERT(IsLoaded());
ScopeLock lock(Locker);
@@ -57,11 +59,12 @@ Actor* Prefab::GetDefaultInstance()
// Skip if already created (reuse cached result)
if (_defaultInstance)
return _defaultInstance;
PROFILE_CPU();
// Skip if not loaded
if (!IsLoaded())
{
LOG(Warning, "Cannot instantiate object from not loaded prefab asset '{}'", GetPath());
LOG(Warning, "Cannot instantiate object from not loaded prefab asset ({}, {})", GetPath(), GetID());
return nullptr;
}
+1 -1
View File
@@ -104,7 +104,7 @@ private:
bool ApplyAllInternal(Actor* targetActor, bool linkTargetActorObjectToPrefab, PrefabInstancesData& prefabInstancesData);
bool UpdateInternal(const Array<SceneObject*>& defaultInstanceObjects, rapidjson_flax::StringBuffer& tmpBuffer);
bool SyncChangesInternal(PrefabInstancesData& prefabInstancesData);
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData) const;
void SyncNestedPrefabs(const NestedPrefabsList& allPrefabs, Array<PrefabInstancesData>& allPrefabsInstancesData, HashSet<Guid, HeapAllocation>& synced) const;
#endif
void DeleteDefaultInstance();
+1 -1
View File
@@ -752,7 +752,7 @@ void SceneObjectsFactory::SynchronizePrefabInstances(Context& context, PrefabSyn
obj->SetOrderInParent(order);
}
// Setup hierarchy for the prefab instances (ensure any new objects are connected)
// Setup hierarchy for the prefab instances (after adding new objects to ensure they are connected, eg. when reparenting existing prefab into a new root)
for (const auto& instance : context.Instances)
{
const auto& prefabStartData = data.Data[instance.StatIndex];
@@ -27,65 +27,41 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* nod
switch (param->Type.Type)
{
case VariantType::Float2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsFloat2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsFloat3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Float4:
case VariantType::Color:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsFloat4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double2:
switch (box->ID)
{
case 1:
case 2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsDouble2().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double3:
switch (box->ID)
{
case 1:
case 2:
case 3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsDouble3().Raw[box->ID - 1];
break;
}
break;
case VariantType::Double4:
switch (box->ID)
{
case 1:
case 2:
case 3:
case 4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsDouble4().Raw[box->ID - 1];
break;
}
break;
case VariantType::Int2:
if (box->ID >= 1 && box->ID <= 2)
value = value.AsInt2().Raw[box->ID - 1];
break;
case VariantType::Int3:
if (box->ID >= 1 && box->ID <= 3)
value = value.AsInt3().Raw[box->ID - 1];
break;
case VariantType::Int4:
if (box->ID >= 1 && box->ID <= 4)
value = value.AsInt4().Raw[box->ID - 1];
break;
case VariantType::Matrix:
{
+9
View File
@@ -184,6 +184,15 @@ PhysicsScene* Physics::FindScene(const StringView& name)
return nullptr;
}
void Physics::DeleteScene(PhysicsScene* scene)
{
if (scene == nullptr || scene == DefaultScene)
return;
scene->CollectResults();
Scenes.RemoveKeepOrder(scene);
Delete(scene);
}
bool Physics::GetAutoSimulation()
{
return !DefaultScene || DefaultScene->GetAutoSimulation();
+5
View File
@@ -32,6 +32,11 @@ API_CLASS(Static) class FLAXENGINE_API Physics
/// </summary>
API_FUNCTION() static PhysicsScene* FindScene(const StringView& name);
/// <summary>
/// Delete an existing scene (excluding the default one).
/// </summary>
API_FUNCTION() static void DeleteScene(PhysicsScene* scene);
public:
/// <summary>
/// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it.
+6
View File
@@ -75,6 +75,12 @@
Platform::CheckFailed(#expression, __FILE__, __LINE__); \
return returnValue; \
}
// Performs a soft check of the expression. Logs the expression failure and continues execution.
#define CHECK_NO_RETURN(expression) \
if (!(expression)) \
{ \
Platform::CheckFailed(#expression, __FILE__, __LINE__); \
}
#if ENABLE_ASSERTION
// Performs a soft check of the expression. Logs the expression failure and returns from the function call.
@@ -189,6 +189,7 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM
POINT p;
p.x = static_cast<LONG>(WINDOWS_GET_X_LPARAM(lParam));
p.y = static_cast<LONG>(WINDOWS_GET_Y_LPARAM(lParam));
const Float2 mousePosScreen(static_cast<float>(p.x), static_cast<float>(p.y));
::ClientToScreen(window->GetHWND(), &p);
const Float2 mousePos(static_cast<float>(p.x), static_cast<float>(p.y));
@@ -203,7 +204,8 @@ bool WindowsMouse::WndProc(Window* window, const UINT msg, WPARAM wParam, LPARAM
}
case WM_NCMOUSEMOVE:
{
OnMouseMove(mousePos, window);
// The position in the message is already reported in screen-space
OnMouseMove(mousePosScreen, window);
result = true;
break;
}
+5 -7
View File
@@ -356,9 +356,7 @@ void Renderer::DrawActors(RenderContext& renderContext, const Array<Actor*>& cus
Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDraw);
Level::DrawActors(renderContextBatch, SceneRendering::DrawCategory::SceneDrawAsync);
JobSystem::SetJobStartingOnDispatch(true);
for (const int64 label : renderContextBatch.WaitLabels)
JobSystem::Wait(label);
renderContextBatch.WaitLabels.Clear();
renderContextBatch.FlushWaitLabels();
}
}
@@ -486,9 +484,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
// Wait for async jobs to finish
JobSystem::SetJobStartingOnDispatch(true);
for (const int64 label : renderContextBatch.WaitLabels)
JobSystem::Wait(label);
renderContextBatch.WaitLabels.Clear();
renderContextBatch.FlushWaitLabels();
// Perform custom post-scene drawing (eg. GPU dispatches used by VFX)
for (int32 i = 0; i < renderContextBatch.Contexts.Count(); i++)
@@ -741,7 +737,9 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
}
// Upscaling after scene rendering but before post processing
bool useUpscaling = task->RenderingPercentage < 1.0f;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
bool useUpscaling = task->RenderingPercentage * task->RenderScale < 1.0f;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
const Viewport outputViewport = task->GetOutputViewport();
if (useUpscaling && setup.UpscaleLocation == RenderingUpscaleLocation::BeforePostProcessingPass)
{
@@ -0,0 +1,15 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine
{
/// <summary>
/// Changes enum serialization to use string names instead of integer values. This makes saved data resilient to enum reordering or changes in values (but not to renaming enums). Deserialization accepts both string names and integer values for backward compatibility.
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Enum)]
public sealed class EnumStringAttribute : Attribute
{
}
}
+26 -37
View File
@@ -204,7 +204,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul
Struct.SetField = setField;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items)
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization)
: ManagedClass(nullptr)
, Module(module)
, InitRuntime(DefaultInitRuntime)
@@ -215,6 +215,7 @@ ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* modul
, Size(size)
{
Enum.Items = items;
Enum.StringSerialization = stringSerialization;
}
ScriptingType::ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper)
@@ -270,6 +271,7 @@ ScriptingType::ScriptingType(const ScriptingType& other)
break;
case ScriptingTypes::Enum:
Enum.Items = other.Enum.Items;
Enum.StringSerialization = other.Enum.StringSerialization;
break;
case ScriptingTypes::Interface:
Interface.SetupScriptVTable = other.Interface.SetupScriptVTable;
@@ -323,6 +325,7 @@ ScriptingType::ScriptingType(ScriptingType&& other)
break;
case ScriptingTypes::Enum:
Enum.Items = other.Enum.Items;
Enum.StringSerialization = other.Enum.StringSerialization;
break;
case ScriptingTypes::Interface:
Interface.SetupScriptVTable = other.Interface.SetupScriptVTable;
@@ -604,71 +607,57 @@ StringAnsiView ScriptingType::GetName() const
return Fullname;
}
#if BUILD_DEBUG || USE_EDITOR
#define INIT_TYPE(...) \
module->Types.AddUninitialized(); \
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \
if (module->TypeNameToTypeIndex.ContainsKey(fullname)) \
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName())); \
module->TypeNameToTypeIndex[fullname] = TypeIndex;
#else
#define INIT_TYPE(...) \
module->Types.AddUninitialized(); \
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, ##__VA_ARGS__); \
module->TypeNameToTypeIndex[fullname] = TypeIndex;
#endif
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SpawnHandler spawn, ScriptingTypeInitializer* baseType, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Script
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, initRuntime, spawn, baseType, setupScriptVTable, setupScriptObjectVTable, interfaces);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Class
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, baseType, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, initRuntime, ctor, dtor, baseType, interfaces);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType, const ScriptingType::InterfaceImplementation* interfaces)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Structure
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, initRuntime, ctor, dtor, copy, box, unbox, getField, setField, baseType, interfaces);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items)
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Enum
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, size, items);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(size, items, stringSerialization);
}
ScriptingTypeInitializer::ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper)
: ScriptingTypeHandle(module, module->Types.Count())
{
// Interface
module->Types.AddUninitialized();
new(module->Types.Get() + TypeIndex)ScriptingType(fullname, module, initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper);
#if BUILD_DEBUG
if (module->TypeNameToTypeIndex.ContainsKey(fullname))
LOG(Error, "Duplicated native typename {0} from module {1}.", String(fullname), String(module->GetName()));
#endif
module->TypeNameToTypeIndex[fullname] = TypeIndex;
INIT_TYPE(initRuntime, setupScriptVTable, setupScriptObjectVTable, getInterfaceWrapper);
}
#undef INIT_TYPE
CriticalSection BinaryModule::Locker;
BinaryModule::BinaryModulesList& BinaryModule::GetModules()
@@ -609,6 +609,12 @@ MObject* MUtils::BoxVariant(const Variant& value)
return MCore::Object::Box((void*)&value.AsData, Double3::TypeInitializer.GetClass());
case VariantType::Double4:
return MCore::Object::Box((void*)&value.AsData, Double4::TypeInitializer.GetClass());
case VariantType::Int2:
return MCore::Object::Box((void*)&value.AsData, Int2::TypeInitializer.GetClass());
case VariantType::Int3:
return MCore::Object::Box((void*)&value.AsData, Int3::TypeInitializer.GetClass());
case VariantType::Int4:
return MCore::Object::Box((void*)&value.AsData, Int4::TypeInitializer.GetClass());
case VariantType::Color:
return MCore::Object::Box((void*)&value.AsData, stdTypes.ColorClass);
case VariantType::Guid:
@@ -880,6 +886,12 @@ MClass* MUtils::GetClass(const VariantType& value)
return Double3::TypeInitializer.GetClass();
case VariantType::Double4:
return Double4::TypeInitializer.GetClass();
case VariantType::Int2:
return Int2::TypeInitializer.GetClass();
case VariantType::Int3:
return Int3::TypeInitializer.GetClass();
case VariantType::Int4:
return Int4::TypeInitializer.GetClass();
case VariantType::Color:
return Color::TypeInitializer.GetClass();
case VariantType::Guid:
@@ -970,6 +982,12 @@ MClass* MUtils::GetClass(const Variant& value)
return Double3::TypeInitializer.GetClass();
case VariantType::Double4:
return Double4::TypeInitializer.GetClass();
case VariantType::Int2:
return Int2::TypeInitializer.GetClass();
case VariantType::Int3:
return Int3::TypeInitializer.GetClass();
case VariantType::Int4:
return Int4::TypeInitializer.GetClass();
case VariantType::Color:
return stdTypes.ColorClass;
case VariantType::Guid:
@@ -1138,6 +1156,9 @@ void* MUtils::VariantToManagedArgPtr(Variant& value, MType* type, bool& failed)
CASE_IN_BUILD_TYPE(Double2, AsData);
CASE_IN_BUILD_TYPE(Double3, AsData);
CASE_IN_BUILD_TYPE(Double4, AsBlob.Data);
CASE_IN_BUILD_TYPE(Int2, AsData);
CASE_IN_BUILD_TYPE(Int3, AsData);
CASE_IN_BUILD_TYPE(Int4, AsData);
#undef CASE_IN_BUILD_TYPE
if (klass->IsValueType())
{
+4 -2
View File
@@ -266,6 +266,8 @@ struct FLAXENGINE_API ScriptingType
{
// Enum items table (the last item name is null)
EnumItem* Items;
// Enum uses string names serialization instead of integer values.
bool StringSerialization;
} Enum;
struct
@@ -290,7 +292,7 @@ struct FLAXENGINE_API ScriptingType
ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime = DefaultInitRuntime, SpawnHandler spawn = DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, SetupScriptVTableHandler setupScriptVTable = nullptr, SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const InterfaceImplementation* interfaces = nullptr);
ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr);
ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, InitRuntimeHandler initRuntime, Ctor ctor, Dtor dtor, Copy copy, Box box, Unbox unbox, GetField getField, SetField setField, ScriptingTypeInitializer* baseType, const InterfaceImplementation* interfaces = nullptr);
ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items);
ScriptingType(const StringAnsiView& fullname, BinaryModule* module, int32 size, EnumItem* items, bool stringSerialization);
ScriptingType(const StringAnsiView& fullname, BinaryModule* module, InitRuntimeHandler initRuntime, SetupScriptVTableHandler setupScriptVTable, SetupScriptObjectVTableHandler setupScriptObjectVTable, GetInterfaceWrapper getInterfaceWrapper);
ScriptingType(const ScriptingType& other);
ScriptingType(ScriptingType&& other);
@@ -339,7 +341,7 @@ struct FLAXENGINE_API ScriptingTypeInitializer : ScriptingTypeHandle
ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime = ScriptingType::DefaultInitRuntime, ScriptingType::SpawnHandler spawn = ScriptingType::DefaultSpawn, ScriptingTypeInitializer* baseType = nullptr, ScriptingType::SetupScriptVTableHandler setupScriptVTable = nullptr, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr);
ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr);
ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::Ctor ctor, ScriptingType::Dtor dtor, ScriptingType::Copy copy, ScriptingType::Box box, ScriptingType::Unbox unbox, ScriptingType::GetField getField, ScriptingType::SetField setField, ScriptingTypeInitializer* baseType = nullptr, const ScriptingType::InterfaceImplementation* interfaces = nullptr);
ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items);
ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, int32 size, ScriptingType::EnumItem* items, bool stringSerialization);
ScriptingTypeInitializer(BinaryModule* module, const StringAnsiView& fullname, ScriptingType::InitRuntimeHandler initRuntime, ScriptingType::SetupScriptVTableHandler setupScriptVTable, ScriptingType::SetupScriptObjectVTableHandler setupScriptObjectVTable, ScriptingType::GetInterfaceWrapper getInterfaceWrapper);
};
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
namespace FlaxEngine.Json.JsonCustomSerializers
@@ -44,6 +45,13 @@ namespace FlaxEngine.Json.JsonCustomSerializers
((JsonObjectContract)contract).ItemReferenceLoopHandling = ReferenceLoopHandling.Serialize;
}
// Check if use enum serialization as string
var type = Nullable.GetUnderlyingType(objectType) ?? objectType;
if (type.IsEnum && type.GetCustomAttribute<EnumStringAttribute>() != null)
{
contract.Converter = new StringEnumConverter();
}
return contract;
}
@@ -53,19 +61,19 @@ namespace FlaxEngine.Json.JsonCustomSerializers
var contract = base.CreateDictionaryContract(objectType);
// Override contract to save enums keys as integer
if (contract.DictionaryKeyType?.IsEnum ?? false)
var keyType = contract.DictionaryKeyType;
if ((keyType?.IsEnum ?? false) && keyType.GetCustomAttribute<EnumStringAttribute>() == null)
{
var enumType = contract.DictionaryKeyType;
contract.DictionaryKeyResolver = name =>
{
try
{
var e = Enum.Parse(enumType, name);
var e = Enum.Parse(keyType, name);
name = Convert.ToInt32(e).ToString();
}
catch
catch (Exception ex)
{
// Ignore errors
Debug.Logger.LogHandler.LogWrite(LogType.Warning, $"Failed to parse enum '{name}' as {keyType.Name}: {ex.Message}");
}
return name;
};
+54 -2
View File
@@ -49,6 +49,54 @@ void ISerializable::DeserializeIfExists(DeserializeStream& stream, const char* m
var = defaultValue;\
}
void Serialization::SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle)
{
if (typeHandle)
{
// Check if serialize enum as string
const ScriptingType& type = typeHandle.GetType();
if (type.Type == ScriptingTypes::Enum && type.Enum.StringSerialization)
{
const auto items = type.Enum.Items;
for (int32 i = 0; items[i].Name; i++)
{
if (items[i].Value == v)
{
stream.String(items[i].Name);
return;
}
}
}
}
stream.Uint(v);
}
int32 Serialization::DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle)
{
if (stream.IsString() && typeHandle)
{
// Deserialize enum from string
const ScriptingType& type = typeHandle.GetType();
if (type.Type == ScriptingTypes::Enum)
{
const auto str = stream.GetStringAnsiView();
const auto items = type.Enum.Items;
for (int32 i = 0; items[i].Name; i++)
{
if (str == items[i].Name)
{
return (int32)items[i].Value;
}
}
int32 result;
if (!StringUtils::Parse(stream.GetString(), &result))
return result;
LOG(Warning, "Failed to parse enum '{}' as {}", str.ToString(), type.Fullname.ToString());
}
}
return DeserializeInt(stream);
}
bool Serialization::ShouldSerialize(const VariantType& v, const void* otherObj)
{
return !otherObj || v != *(VariantType*)otherObj;
@@ -129,7 +177,6 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari
stream.Int64(v.AsInt64);
break;
case VariantType::Uint64:
case VariantType::Enum:
stream.Uint64(v.AsUint64);
break;
case VariantType::Float:
@@ -222,6 +269,9 @@ void Serialization::Serialize(ISerializable::SerializeStream& stream, const Vari
else
stream.String("", 0);
break;
case VariantType::Enum:
SerializeEnum(stream, (int32)v.AsUint64, v.Type.GetScriptingType());
break;
case VariantType::ManagedObject:
case VariantType::Structure:
{
@@ -276,7 +326,6 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian
v.AsInt64 = value.GetInt64();
break;
case VariantType::Uint64:
case VariantType::Enum:
v.AsUint64 = value.GetUint64();
break;
case VariantType::Float:
@@ -371,6 +420,9 @@ void Serialization::Deserialize(ISerializable::DeserializeStream& stream, Varian
CHECK(value.IsString());
v.SetTypename(value.GetStringAnsiView());
break;
case VariantType::Enum:
v.AsInt64 = DeserializeEnum(value, v.Type.GetScriptingType());
break;
case VariantType::ManagedObject:
case VariantType::Structure:
{
+6 -2
View File
@@ -38,12 +38,16 @@ namespace Serialization
int32 result = 0;
if (stream.IsInt())
result = stream.GetInt();
else if (stream.IsInt64())
result = (int32)stream.GetInt64();
else if (stream.IsFloat())
result = (int32)stream.GetFloat();
else if (stream.IsString())
StringUtils::Parse(stream.GetString(), &result);
return result;
}
FLAXENGINE_API void SerializeEnum(ISerializable::SerializeStream& stream, uint32 v, ScriptingTypeHandle typeHandle);
FLAXENGINE_API int32 DeserializeEnum(ISerializable::DeserializeStream& stream, ScriptingTypeHandle typeHandle);
// In-build types
@@ -226,12 +230,12 @@ namespace Serialization
template<typename T>
inline typename TEnableIf<TIsEnum<T>::Value>::Type Serialize(ISerializable::SerializeStream& stream, const T& v, const void* otherObj)
{
stream.Uint((uint32)v);
SerializeEnum(stream, (uint32)v, StaticType<T>());
}
template<typename T>
inline typename TEnableIf<TIsEnum<T>::Value>::Type Deserialize(ISerializable::DeserializeStream& stream, T& v, ISerializeModifier* modifier)
{
v = (T)DeserializeInt(stream);
v = (T)DeserializeEnum(stream, StaticType<T>());
}
// Common types
+6
View File
@@ -288,12 +288,15 @@ void ReadStream::Read(Variant& data)
break;
}
case VariantType::Float2:
case VariantType::Int2:
ReadBytes(&data.AsData, sizeof(Float2));
break;
case VariantType::Float3:
case VariantType::Int3:
ReadBytes(&data.AsData, sizeof(Float3));
break;
case VariantType::Float4:
case VariantType::Int4:
ReadBytes(&data.AsData, sizeof(Float4));
break;
case VariantType::Double2:
@@ -687,12 +690,15 @@ void WriteStream::Write(const Variant& data)
Write(id);
break;
case VariantType::Float2:
case VariantType::Int2:
WriteBytes(data.AsData, sizeof(Float2));
break;
case VariantType::Float3:
case VariantType::Int3:
WriteBytes(data.AsData, sizeof(Float3));
break;
case VariantType::Float4:
case VariantType::Int4:
WriteBytes(data.AsData, sizeof(Float4));
break;
case VariantType::Double2:
+121
View File
@@ -8,6 +8,7 @@
#include "Engine/Level/Actors/EmptyActor.h"
#include "Engine/Level/Actors/DirectionalLight.h"
#include "Engine/Level/Actors/ExponentialHeightFog.h"
#include "Engine/Level/Actors/AnimatedModel.h"
#include "Engine/Level/Prefabs/Prefab.h"
#include "Engine/Level/Prefabs/PrefabManager.h"
#include "Engine/Scripting/ScriptingObjectReference.h"
@@ -905,4 +906,124 @@ TEST_CASE("Prefabs")
instance1->DeleteObject();
instance2->DeleteObject();
}
SECTION("Test Adding Object To Base Prefab")
{
// https://github.com/LOOPDISK/FlaxEngine/pull/44
// Create inner prefab with 3 objects in hierarchy
AssetReference<Prefab> prefabInner = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabInner);
Guid id;
Guid::Parse("15dbe4b0416be0777a6ce59e8788b10f", id);
prefabInner->ChangeID(id);
auto prefabInnerInit = prefabInner->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"3de462104f56f681c14650a0171f88fb\","
"\"TypeName\" : \"FlaxEngine.SpotLight\","
"\"Name\" : \"Inner.Root\""
"},"
"{"
"\"ID\": \"19b181f846b6911635ffacb902c93c6a\","
"\"TypeName\" : \"FlaxEngine.StaticModel\","
"\"ParentID\" : \"3de462104f56f681c14650a0171f88fb\","
"\"Name\" : \"Inner.Cube\""
"},"
"{"
"\"ID\": \"8950889f4a2e752d55165fbf10eaf184\","
"\"TypeName\" : \"FlaxEngine.AnimatedModel\","
"\"ParentID\" : \"19b181f846b6911635ffacb902c93c6a\","
"\"Name\" : \"Inner.Model\""
"}"
"]");
REQUIRE(!prefabInnerInit);
// Create outer prefab with 2 instances of inner prefab
AssetReference<Prefab> prefabOuter = Content::CreateVirtualAsset<Prefab>();
REQUIRE(prefabOuter);
SCOPE_EXIT{ Content::DeleteAsset(prefabOuter); };
Guid::Parse("2ab744714f746e31855f41815612d14b", id);
prefabOuter->ChangeID(id);
auto prefabOuterInit = prefabOuter->Init(Prefab::TypeName,
"["
"{"
"\"ID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\","
"\"TypeName\": \"FlaxEngine.EmptyActor\","
"\"Name\": \"Outer.Root\""
"},"
"{"
"\"ID\": \"a3b705284432bed9f043829c04a2bc8f\","
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"3de462104f56f681c14650a0171f88fb\","
"\"ParentID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\","
"\"Name\": \"Instance 1\""
"},"
"{"
"\"ID\": \"06a8c15a41b822dd27f3ac9d79b142d3\","
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"19b181f846b6911635ffacb902c93c6a\","
"\"ParentID\": \"a3b705284432bed9f043829c04a2bc8f\""
"},"
"{"
"\"ID\": \"4759fb9e4c4dda3b61ab5ab43949e42f\","
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"8950889f4a2e752d55165fbf10eaf184\","
"\"ParentID\": \"06a8c15a41b822dd27f3ac9d79b142d3\""
"},"
"{"
"\"ID\": \"1225be664c0c081e714bbf93e09b99e4\","
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"3de462104f56f681c14650a0171f88fb\","
"\"ParentID\": \"dba7f4bb4acfd62608b9a8bf550f31a5\","
"\"Name\": \"Instance 2\""
"},"
"{"
"\"ID\": \"b397243540322182b806ad8339b7b617\","
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"19b181f846b6911635ffacb902c93c6a\","
"\"ParentID\": \"1225be664c0c081e714bbf93e09b99e4\""
"},"
"{"
"\"ID\": \"2c3b8e824daf038a58df528a238ca2de\","
"\"PrefabID\": \"15dbe4b0416be0777a6ce59e8788b10f\","
"\"PrefabObjectID\": \"8950889f4a2e752d55165fbf10eaf184\","
"\"ParentID\": \"b397243540322182b806ad8339b7b617\""
"}"
"]");
REQUIRE(!prefabOuterInit);
// Spawn test instances of both prefabs
ScriptingObjectReference<Actor> instanceInner = PrefabManager::SpawnPrefab(prefabInner);
ScriptingObjectReference<Actor> instanceOuter = PrefabManager::SpawnPrefab(prefabOuter);
// Add new object to the inner prefab
instanceInner->Children[0]->GetOrAddChild<DirectionalLight>();
// Apply changes
bool applyResult = PrefabManager::ApplyAll(instanceInner);
REQUIRE(!applyResult);
// Check state of outer instance to properly reflect hierarchy
REQUIRE(instanceOuter);
REQUIRE(instanceOuter->Children.Count() == 2);
REQUIRE(instanceOuter->Children[0] != nullptr);
REQUIRE(instanceOuter->Children[0]->Children.Count() == 1);
REQUIRE(instanceOuter->Children[0]->Children[0]);
REQUIRE(instanceOuter->Children[0]->Children[0]->Children.Count() == 2);
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[0]->Is<AnimatedModel>());
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1]->Is<DirectionalLight>());
REQUIRE(instanceOuter->Children[1] != nullptr);
REQUIRE(instanceOuter->Children[1]->Children.Count() == 1);
REQUIRE(instanceOuter->Children[1]->Children[0]);
REQUIRE(instanceOuter->Children[1]->Children[0]->Children.Count() == 2);
REQUIRE(instanceOuter->Children[1]->Children[0]->Children[0]->Is<AnimatedModel>());
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1]->Is<DirectionalLight>());
REQUIRE(instanceOuter->Children[0]->Children[0] != instanceOuter->Children[1]->Children[0]);
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[0] != instanceOuter->Children[1]->Children[0]->Children[0]);
REQUIRE(instanceOuter->Children[0]->Children[0]->Children[1] != instanceOuter->Children[1]->Children[0]->Children[1]);
// Cleanup
instanceInner->DeleteObject();
instanceOuter->DeleteObject();
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ public:
/// <summary>
/// The size of a single sample in bits. The clip will be converted to this bit depth on import.
/// </summary>
API_FIELD(Attributes="EditorOrder(50), VisibleIf(nameof(ShowBtiDepth))")
API_FIELD(Attributes="EditorOrder(50), VisibleIf(nameof(ShowBitDepth))")
BitDepth BitDepth = BitDepth::_16;
String ToString() const;
+52 -5
View File
@@ -589,6 +589,10 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(TriangleReduction);
SERIALIZE(SloppyOptimization);
SERIALIZE(LODTargetError);
SERIALIZE(LODTargetErrorAbsolute);
SERIALIZE(LODLockBorder);
SERIALIZE(LODPreserveUVs);
SERIALIZE(LODPreserveUVsWeight);
SERIALIZE(ImportMaterials);
SERIALIZE(CreateEmptyMaterialSlots);
SERIALIZE(ImportMaterialsAsInstances);
@@ -646,6 +650,10 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(TriangleReduction);
DESERIALIZE(SloppyOptimization);
DESERIALIZE(LODTargetError);
DESERIALIZE(LODTargetErrorAbsolute);
DESERIALIZE(LODLockBorder);
DESERIALIZE(LODPreserveUVs);
DESERIALIZE(LODPreserveUVsWeight);
DESERIALIZE(ImportMaterials);
DESERIALIZE(CreateEmptyMaterialSlots);
DESERIALIZE(ImportMaterialsAsInstances);
@@ -1945,8 +1953,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
// Automatic LOD generation
if (options.GenerateLODs && options.LODCount > 1 && data.LODs.HasItems() && options.TriangleReduction < 1.0f - ZeroTolerance)
{
InitMeshOpt();
PROFILE_CPU_NAMED("GenerateLODs");
auto lodStartTime = DateTime::NowUTC();
InitMeshOpt();
float triangleReduction = Math::Saturate(options.TriangleReduction);
int32 lodCount = Math::Max(options.LODCount, data.LODs.Count());
int32 baseLOD = Math::Clamp(options.BaseLOD, 0, lodCount - 1);
@@ -1983,13 +1992,51 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
continue;
indices.Clear();
indices.Resize(srcMeshIndexCount);
int32 dstMeshIndexCount = {};
int32 dstMeshIndexCount = 0;
if (options.SloppyOptimization)
{
PROFILE_CPU_NAMED("meshopt_simplifySloppy");
dstMeshIndexCount = (int32)meshopt_simplifySloppy(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError);
}
else
dstMeshIndexCount = (int32)meshopt_simplify(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError);
if (dstMeshIndexCount <= 0 || dstMeshIndexCount > indices.Count())
continue;
{
// Build simplification flags
unsigned int simplifyOptions = 0;
if (options.LODLockBorder)
simplifyOptions |= meshopt_SimplifyLockBorder;
if (options.LODTargetErrorAbsolute)
simplifyOptions |= meshopt_SimplifyErrorAbsolute;
if (options.LODPreserveUVs && srcMesh->UVs.HasItems())
{
// Pack UV channels as attributes for meshopt_simplifyWithAttributes
int32 uvChannelCount = srcMesh->UVs.Count();
int32 attributeCount = uvChannelCount * 2; // 2 floats (U, V) per channel
Array<float> attributes;
attributes.Resize(srcMeshVertexCount * attributeCount);
Array<float> attributeWeights;
attributeWeights.Resize(attributeCount);
for (int32 ch = 0; ch < uvChannelCount; ch++)
{
for (int32 v = 0; v < srcMeshVertexCount; v++)
{
Float2 uv = srcMesh->UVs[ch][v];
attributes[v * attributeCount + ch * 2 + 0] = uv.X;
attributes[v * attributeCount + ch * 2 + 1] = uv.Y;
}
attributeWeights[ch * 2 + 0] = options.LODPreserveUVsWeight;
attributeWeights[ch * 2 + 1] = options.LODPreserveUVsWeight;
}
PROFILE_CPU_NAMED("meshopt_simplifyWithAttributes");
dstMeshIndexCount = (int32)meshopt_simplifyWithAttributes(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), attributes.Get(), sizeof(float) * attributeCount, attributeWeights.Get(), attributeCount, nullptr, dstMeshIndexCountTarget, options.LODTargetError, simplifyOptions, nullptr);
}
else
{
PROFILE_CPU_NAMED("meshopt_simplify");
dstMeshIndexCount = (int32)meshopt_simplify(indices.Get(), srcMesh->Indices.Get(), srcMeshIndexCount, (const float*)srcMesh->Positions.Get(), srcMeshVertexCount, sizeof(Float3), dstMeshIndexCountTarget, options.LODTargetError, simplifyOptions, nullptr);
}
}
if (dstMeshIndexCount <= 0 || dstMeshIndexCount >= indices.Count())
continue; // Skip if failed to generate LOD or it doesn't have less vertices than source
indices.Resize(dstMeshIndexCount);
// Generate simplified vertex buffer remapping table (use only vertices from LOD index buffer)
+17 -5
View File
@@ -296,20 +296,32 @@ public:
API_FIELD(Attributes="EditorOrder(1100), EditorDisplay(\"Level Of Detail\", \"Generate LODs\"), VisibleIf(nameof(ShowGeometry))")
bool GenerateLODs = false;
// The index of the LOD from the source model data to use as a reference for following LODs generation.
API_FIELD(Attributes="EditorOrder(1110), EditorDisplay(\"Level Of Detail\", \"Base LOD\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 5, 0.065f)")
API_FIELD(Attributes="EditorOrder(1110), EditorDisplay(\"Level Of Detail\", \"Base LOD\"), VisibleIf(nameof(ShowGenerateLODs)), Limit(0, 5, 0.065f)")
int32 BaseLOD = 0;
// The amount of LODs to include in the model (all remaining ones starting from Base LOD will be generated).
API_FIELD(Attributes="EditorOrder(1120), EditorDisplay(\"Level Of Detail\", \"LOD Count\"), VisibleIf(nameof(ShowGeometry)), Limit(1, 6, 0.065f)")
API_FIELD(Attributes="EditorOrder(1120), EditorDisplay(\"Level Of Detail\", \"LOD Count\"), VisibleIf(nameof(ShowGenerateLODs)), Limit(1, 6, 0.065f)")
int32 LODCount = 4;
// The target amount of triangles for the generated LOD (based on the higher LOD). Normalized to range 0-1. For instance 0.4 cuts the triangle count to 40%.
API_FIELD(Attributes="EditorOrder(1130), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry)), Limit(0, 1, 0.001f)")
API_FIELD(Attributes="EditorOrder(1130), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGenerateLODs)), Limit(0, 1, 0.001f)")
float TriangleReduction = 0.5f;
// Whether to do a sloppy mesh optimization. This is faster but does not follow the topology of the original mesh.
API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))")
API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGenerateLODs))")
bool SloppyOptimization = false;
// Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0,1] range (e.g. 0.01 means that simplifier will try to maintain the error to be below 1% of the mesh extents). Only used if Sloppy is unchecked.
API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGeometry)), Limit(0.01f, 1, 0.001f)")
API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\", \"LOD Target Error\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs)), Limit(0.01f, 1, 0.001f)")
float LODTargetError = 0.05f;
// If checked, vertices on topological borders (edges without a paired triangle) will not be moved during simplification. Useful for meshes that tile or share edges with other meshes.
API_FIELD(Attributes="EditorOrder(1170), EditorDisplay(\"Level Of Detail\", \"Lock Border\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs))")
bool LODLockBorder = false;
// If checked, the target error will be treated as absolute rather than relative to the mesh extents. In that mode, error is defined in absolute units which can be universal across similar mesh types no matter their size.
API_FIELD(Attributes="EditorOrder(1160), EditorDisplay(\"Level Of Detail\", \"LOD Target Error Absolute\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs))")
bool LODTargetErrorAbsolute = false;
// If checked, UV channels will be included in the simplification error metric to preserve UV layout. Essential for trimsheets and atlased textures.
API_FIELD(Attributes="EditorOrder(1180), EditorDisplay(\"Level Of Detail\", \"Preserve UVs\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs))")
bool LODPreserveUVs = false;
// The weight of UV attributes in the simplification error metric. Higher values preserve UVs more aggressively at the cost of geometric quality. Only used when Preserve UVs is enabled.
API_FIELD(Attributes="EditorOrder(1190), EditorDisplay(\"Level Of Detail\", \"Preserve UVs Weight\"), VisibleIf(nameof(LODPreserveUVs)), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGenerateLODs)), Limit(0.001f, 1, 0.001f)")
float LODPreserveUVsWeight = 0.01f;
public: // Materials
+3 -3
View File
@@ -572,12 +572,12 @@ namespace FlaxEngine.GUI
/// <summary>
/// Starts the mouse tracking. Used by the scrollbars, splitters, etc.
/// </summary>
/// <param name="useMouseScreenOffset">If set to <c>true</c> will use mouse screen offset.</param>
/// <param name="screenSpaceMouseWrap">If set to <c>true</c> will wrap when it hits a screen border.</param>
[NoAnimate]
public void StartMouseCapture(bool useMouseScreenOffset = false)
public void StartMouseCapture(bool screenSpaceMouseWrap = false)
{
var parent = Root;
parent?.StartTrackingMouse(this, useMouseScreenOffset);
parent?.StartTrackingMouse(this, screenSpaceMouseWrap);
}
/// <summary>
+90
View File
@@ -256,6 +256,33 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, MathOp1 op)
vv.W = (double)op((float)aa.W);
break;
}
case VariantType::Int2:
{
Int2& vv = *(Int2*)v.AsData;
const Int2& aa = *(const Int2*)a.AsData;
vv.X = (int32)op((float)aa.X);
vv.Y = (int32)op((float)aa.Y);
break;
}
case VariantType::Int3:
{
Int3& vv = *(Int3*)v.AsData;
const Int3& aa = *(const Int3*)a.AsData;
vv.X = (int32)op((float)aa.X);
vv.Y = (int32)op((float)aa.Y);
vv.Z = (int32)op((float)aa.Z);
break;
}
case VariantType::Int4:
{
Int4& vv = *(Int4*)v.AsData;
const Int4& aa = *(const Int4*)a.AsData;
vv.X = (int32)op((float)aa.X);
vv.Y = (int32)op((float)aa.Y);
vv.Z = (int32)op((float)aa.Z);
vv.W = (int32)op((float)aa.W);
break;
}
case VariantType::Quaternion:
{
Quaternion& vv = *(Quaternion*)v.AsData;
@@ -381,6 +408,36 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, Variant& b, MathO
vv.W = (double)op((float)aa.W, (float)bb.W);
break;
}
case VariantType::Int2:
{
Int2& vv = *(Int2*)v.AsData;
const Int2& aa = *(const Int2*)a.AsData;
const Int2& bb = *(const Int2*)b.AsData;
vv.X = (int32)op((float)aa.X, (float)bb.X);
vv.Y = (int32)op((float)aa.Y, (float)bb.Y);
break;
}
case VariantType::Int3:
{
Int3& vv = *(Int3*)v.AsData;
const Int3& aa = *(const Int3*)a.AsData;
const Int3& bb = *(const Int3*)b.AsData;
vv.X = (int32)op((float)aa.X, (float)bb.X);
vv.Y = (int32)op((float)aa.Y, (float)bb.Y);
vv.Z = (int32)op((float)aa.Z, (float)bb.Z);
break;
}
case VariantType::Int4:
{
Int4& vv = *(Int4*)v.AsData;
const Int4& aa = *(const Int4*)a.AsData;
const Int4& bb = *(const Int4*)b.AsData;
vv.X = (int32)op((float)aa.X, (float)bb.X);
vv.Y = (int32)op((float)aa.Y, (float)bb.Y);
vv.Z = (int32)op((float)aa.Z, (float)bb.Z);
vv.W = (int32)op((float)aa.W, (float)bb.W);
break;
}
case VariantType::Quaternion:
{
Quaternion& vv = *(Quaternion*)v.AsData;
@@ -499,6 +556,39 @@ void GraphUtilities::ApplySomeMathHere(Variant& v, Variant& a, Variant& b, Varia
vv.W = (double)op((float)aa.W, (float)bb.W, (float)cc.W);
break;
}
case VariantType::Int2:
{
Int2& vv = *(Int2*)v.AsData;
const Int3& aa = *(const Int2*)a.AsData;
const Int3& bb = *(const Int2*)b.AsData;
const Int3& cc = *(const Int2*)c.AsData;
vv.X = (int32)op((float)aa.X, (float)bb.X, (float)cc.X);
vv.Y = (int32)op((float)aa.Y, (float)bb.Y, (float)cc.Y);
break;
}
case VariantType::Int3:
{
Int3& vv = *(Int3*)v.AsData;
const Int3& aa = *(const Int3*)a.AsData;
const Int3& bb = *(const Int3*)b.AsData;
const Int3& cc = *(const Int3*)c.AsData;
vv.X = (int32)op((float)aa.X, (float)bb.X, (float)cc.X);
vv.Y = (int32)op((float)aa.Y, (float)bb.Y, (float)cc.Y);
vv.Z = (int32)op((float)aa.Z, (float)bb.Z, (float)cc.Z);
break;
}
case VariantType::Int4:
{
Int4& vv = *(Int4*)v.AsData;
const Int4& aa = *(const Int4*)a.AsData;
const Int4& bb = *(const Int4*)b.AsData;
const Int4& cc = *(const Int4*)c.AsData;
vv.X = (int32)op((float)aa.X, (float)bb.X, (float)cc.X);
vv.Y = (int32)op((float)aa.Y, (float)bb.Y, (float)cc.Y);
vv.Z = (int32)op((float)aa.Z, (float)bb.Z, (float)cc.Z);
vv.W = (int32)op((float)aa.W, (float)bb.W, (float)cc.W);
break;
}
case VariantType::Quaternion:
{
Quaternion& vv = *(Quaternion*)v.AsData;
+9
View File
@@ -167,6 +167,15 @@ public:
// Base
return Base::onNodeLoaded(n);
}
void Clear() override
{
FloatCurves.Clear();
Float2Curves.Clear();
Float3Curves.Clear();
Float4Curves.Clear();
Base::Clear();
}
};
/// <summary>
+2 -2
View File
@@ -766,7 +766,7 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
structureValue = Variant::Cast(structureValue, typeVariantType);
}
structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format
const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName());
const ScriptingTypeHandle structureValueTypeHandle = structureValue.Type.GetScriptingType();
if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle)
{
OnError(node, box, String::Format(TEXT("Cannot unpack value of type {0} to structure of type {1}"), structureValue.Type, typeName));
@@ -952,7 +952,7 @@ void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
#define SAMPLE_CURVE(id, curves, type, graphType) \
case id: \
{ \
const auto& curve = GetCurrentGraph()->curves[node->Data.Curve.CurveIndex]; \
const auto& curve = GetCurrentGraph()->curves[node->CurveIndex]; \
const float time = (float)tryGetValue(node->GetBox(0), Value::Zero); \
value.Type = VariantType(VariantType::graphType); \
curve.Evaluate(*(type*)value.AsData, time, false); \
+11 -42
View File
@@ -42,42 +42,6 @@ public:
template<class BoxType = VisjectGraphBox>
class VisjectGraphNode : public GraphNode<BoxType>
{
public:
struct CurveData
{
/// <summary>
/// The curve index.
/// </summary>
int32 CurveIndex;
};
/// <summary>
/// Custom cached data per node type. Compact to use as small amount of memory as possible.
/// </summary>
struct AdditionalData
{
union
{
CurveData Curve;
struct
{
void* Method;
BinaryModule* Module;
int32 ParamsCount;
uint32 OutParamsMask;
bool IsStatic;
} InvokeMethod;
struct
{
void* Field;
BinaryModule* Module;
bool IsStatic;
} GetSetField;
};
};
public:
VisjectGraphNode()
: GraphNode<BoxType>()
@@ -85,10 +49,7 @@ public:
}
public:
/// <summary>
/// The custom data (depends on node type). Used to cache data for faster usage at runtime.
/// </summary>
AdditionalData Data;
int32 CurveIndex = MAX_uint16;
/// <summary>
/// The asset references. Linked resources such as Animation assets are referenced in graph data as ID. We need to keep valid refs to them at runtime to keep data in memory.
@@ -148,7 +109,7 @@ public:
#define SETUP_CURVE(id, curves, access) \
case id: \
{ \
n->Data.Curve.CurveIndex = curves.Count(); \
n->CurveIndex = curves.Count(); \
auto& curve = curves.AddOne(); \
const int32 keyframesCount = n->Values[0].AsInt; \
auto& keyframes = curve.GetKeyframes(); \
@@ -177,9 +138,17 @@ public:
}
}
// Base
return Base::onNodeLoaded(n);
}
void Clear() override
{
FloatCurves.Clear();
Float2Curves.Clear();
Float3Curves.Clear();
Float4Curves.Clear();
Base::Clear();
}
};
/// <summary>
+4 -2
View File
@@ -9,13 +9,15 @@ bool VisjectMeta::Load(ReadStream* stream, bool loadData)
{
Release();
int32 entries;
int32 entries = -1;
stream->ReadInt32(&entries);
if (entries < 0 || entries > MAX_uint16)
return true;
Entries.Resize(entries);
for (int32 i = 0; i < entries; i++)
{
Entry& e = Entries[i];
Entry& e = Entries.Get()[i];
stream->ReadInt32(&e.TypeID);
DateTime creationTime;
@@ -202,11 +202,11 @@ namespace Flax.Build.Bindings
var fullname = apiType.FullNameManaged;
if (apiType.IsEnum)
return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length})), {value})";
return $"Variant::Enum(VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {value})";
if (apiType.IsStruct && !CppInBuildVariantStructures.Contains(apiType.Name))
{
if (apiType.IsInBuild)
return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length})), {(typeInfo.IsPtr ? "*" + value : value)})";
return $"Variant::Structure(VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR), {(typeInfo.IsPtr ? "*" + value : value)})";
return $"Variant::Structure(VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType()), {(typeInfo.IsPtr ? "*" + value : value)})";
}
}
@@ -268,7 +268,7 @@ namespace Flax.Build.Bindings
{
var elementType = FindApiTypeInfo(buildData, typeInfo.GenericArgs[0], caller);
var elementName = $"{(elementType != null ? elementType.FullNameManaged : typeInfo.GenericArgs[0].Type)}[]";
return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}))";
return $"VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR)";
}
if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null)
return "VariantType(VariantType::Dictionary)";
@@ -279,11 +279,11 @@ namespace Flax.Build.Bindings
{
var fullname = apiType.FullNameManaged;
if (apiType.IsEnum)
return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}))";
return $"VariantType(VariantType::Enum, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)";
if (apiType.IsStruct)
{
if (apiType.IsInBuild)
return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}))";
return $"VariantType(VariantType::Structure, StringAnsiView(\"{fullname}\", {fullname.Length}), !USE_EDITOR)";
return $"VariantType(VariantType::Structure, {apiType.FullNameNative}::TypeInitializer.GetType())";
}
if (apiType.IsClass)
@@ -2702,7 +2702,8 @@ namespace Flax.Build.Bindings
contents.Append($"ScriptingTypeInitializer {enumTypeNameInternal}_TypeInitializer((BinaryModule*)GetBinaryModule{moduleInfo.Name}(), ");
contents.Append($"StringAnsiView(\"{enumTypeNameManaged}\", {enumTypeNameManaged.Length}), ");
contents.Append($"sizeof({enumTypeNameNative}), ");
contents.Append($"{enumTypeNameInternal}Internal::Items);").AppendLine();
var stringSerialization = enumInfo.Attributes != null && enumInfo.Attributes.Contains("EnumString") ? "true" : "false";
contents.Append($"{enumTypeNameInternal}Internal::Items, {stringSerialization});").AppendLine();
contents.AppendLine($"template<> {moduleInfo.Name.ToUpperInvariant()}_API ScriptingTypeHandle StaticType<{enumTypeNameNative}>() {{ return {enumTypeNameInternal}_TypeInitializer; }}");
}
@@ -3044,7 +3045,7 @@ namespace Flax.Build.Bindings
header.Append(" Variant result;").AppendLine();
var apiType = FindApiTypeInfo(buildData, valueType, moduleInfo);
var elementName = $"{(apiType != null ? apiType.FullNameManaged : valueType.Type)}[]";
header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length})));").AppendLine();
header.Append($" result.SetType(VariantType(VariantType::Array, StringAnsiView(\"{elementName}\", {elementName.Length}), !USE_EDITOR));").AppendLine();
header.Append(" auto* array = reinterpret_cast<Array<Variant, HeapAllocation>*>(result.AsData);").AppendLine();
header.Append(" array->Resize(length);").AppendLine();
header.Append(" for (int32 i = 0; i < length; i++)").AppendLine();
@@ -367,7 +367,7 @@ namespace Flax.Build.Platforms
if (args.All(arg => !arg.Contains("-sASSERTIONS")))
{
// minimum_runtime_check.js from Emscripten checks min browser versions only with ASSERTIONS enabled so use custom check
var checkBrowserVersion = File.ReadAllText(Path.Combine(Globals.EngineRoot, "Source/Platforms/Web/Binaries/Data/check_browser_version.js"));
var checkBrowserVersion = File.ReadAllText(Path.Combine(Globals.EngineRoot, "Source/Platforms/Web/Binaries/check_browser_version.js"));
checkBrowserVersion = checkBrowserVersion.Replace("TARGET_NOT_SUPPORTED", "0x7fffffff");
checkBrowserVersion = checkBrowserVersion.Replace("MIN_CHROME_VERSION", minChrome.ToString());
checkBrowserVersion = checkBrowserVersion.Replace("MIN_FIREFOX_VERSION", minFirefox.ToString());
@@ -377,11 +377,11 @@ namespace Flax.Build.Platforms
args.Add($"--pre-js \"{path}\"");
}
if (addJSPI)
args.Add($"--pre-js \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/Data/check_jspi.js\"");
args.Add($"--pre-js \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/check_jspi.js\"");
// Customize output HTML shell
if (options.LinkEnv.Output == LinkerOutput.Executable)
args.Add($"--shell-file \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/Data/shell.html\"");
args.Add($"--shell-file \"{Globals.EngineRoot}/Source/Platforms/Web/Binaries/shell.html\"");
}
args.Add("-Wl,--start-group");
@@ -140,6 +140,11 @@ namespace Flax.Build.Platforms
/// Windows 11 SDK (10.0.26100.0) 24H2
/// </summary>
v10_0_26100_0,
/// <summary>
/// Windows 11 SDK (10.0.28000.0)
/// </summary>
v10_0_28000_0,
}
/// <summary>
@@ -333,6 +338,7 @@ namespace Flax.Build.Platforms
case WindowsPlatformSDK.v10_0_22000_0: return new Version(10, 0, 22000, 0);
case WindowsPlatformSDK.v10_0_22621_0: return new Version(10, 0, 22621, 0);
case WindowsPlatformSDK.v10_0_26100_0: return new Version(10, 0, 26100, 0);
case WindowsPlatformSDK.v10_0_28000_0: return new Version(10, 0, 28000, 0);
default: throw new ArgumentOutOfRangeException(nameof(sdk), sdk, null);
}
}
@@ -388,6 +394,7 @@ namespace Flax.Build.Platforms
WindowsPlatformSDK.v10_0_22000_0,
WindowsPlatformSDK.v10_0_22621_0,
WindowsPlatformSDK.v10_0_26100_0,
WindowsPlatformSDK.v10_0_28000_0,
};
foreach (var sdk10 in sdk10Roots)
{
@@ -314,6 +314,7 @@ namespace Flax.Build.Platforms
case WindowsPlatformSDK.v10_0_22000_0:
case WindowsPlatformSDK.v10_0_22621_0:
case WindowsPlatformSDK.v10_0_26100_0:
case WindowsPlatformSDK.v10_0_28000_0:
{
var sdkVersionName = WindowsPlatformBase.GetSDKVersion(SDK).ToString();
string includeRootDir = Path.Combine(windowsSdkDir, "include", sdkVersionName);