diff --git a/.gitignore b/.gitignore index b7e11e554..30c2caeb2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ Source/*.Gen.* Source/*.csproj /Package_*/ !Source/Engine/Debug -/Source/Platforms/Editor/Linux/Mono/etc/mono/registry PackageEditor_Cert.command PackageEditor_Cert.bat PackagePlatforms_Cert.bat diff --git a/Content/Editor/Fonts/NotoSansSC-Regular.flax b/Content/Editor/Fonts/NotoSansSC-Regular.flax new file mode 100644 index 000000000..39964e6c5 --- /dev/null +++ b/Content/Editor/Fonts/NotoSansSC-Regular.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19fa43eb7b31ee3936348b1f271c464c79d7020a21d33e3cdbe54f98c3b14304 +size 10560899 diff --git a/Content/Shaders/ColorGrading.flax b/Content/Shaders/ColorGrading.flax index dae561361..ee620d1a2 100644 --- a/Content/Shaders/ColorGrading.flax +++ b/Content/Shaders/ColorGrading.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce60152b7076175eb50e07ad9895eacbfb4a9ed1365c1507266b21fcd3339958 -size 10925 +oid sha256:58bf83f2b334cd28a2db8a2c60a17c56f813edc9050a2d006456f8479cd05d13 +size 10629 diff --git a/Flax.sln.DotSettings b/Flax.sln.DotSettings index ff396d824..80cf8fa1e 100644 --- a/Flax.sln.DotSettings +++ b/Flax.sln.DotSettings @@ -73,8 +73,12 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + AI LO + RPC + SDK VS + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> diff --git a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs index 1be5330e7..76edde777 100644 --- a/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs +++ b/Source/Editor/Content/Create/ParticleEmitterCreateEntry.cs @@ -81,7 +81,7 @@ namespace FlaxEditor.Content.Create switch (_options.Template) { case Templates.Empty: - return Editor.CreateAsset(Editor.NewAssetType.ParticleEmitter, ResultUrl); + return Editor.CreateAsset("ParticleEmitter", ResultUrl); case Templates.ConstantBurst: templateName = "Constant Burst"; break; diff --git a/Source/Editor/Content/GUI/ContentView.cs b/Source/Editor/Content/GUI/ContentView.cs index 259be104b..755a3a1ac 100644 --- a/Source/Editor/Content/GUI/ContentView.cs +++ b/Source/Editor/Content/GUI/ContentView.cs @@ -56,6 +56,9 @@ namespace FlaxEditor.Content.GUI private float _viewScale = 1.0f; private ContentViewType _viewType = ContentViewType.Tiles; + private bool _isRubberBandSpanning = false; + private Float2 _mousePresslocation; + private Rectangle _rubberBandRectangle; #region External Events @@ -600,6 +603,12 @@ namespace FlaxEditor.Content.GUI { Render2D.DrawText(style.FontSmall, IsSearching ? "No results" : "Empty", new Rectangle(Float2.Zero, Size), style.ForegroundDisabled, TextAlignment.Center, TextAlignment.Center); } + + if (_isRubberBandSpanning) + { + Render2D.FillRectangle(_rubberBandRectangle, Color.Orange * 0.4f); + Render2D.DrawRectangle(_rubberBandRectangle, Color.Orange); + } } /// @@ -607,9 +616,54 @@ namespace FlaxEditor.Content.GUI { if (base.OnMouseDown(location, button)) return true; + + if (button == MouseButton.Left) + { + _mousePresslocation = location; + _rubberBandRectangle = new Rectangle(_mousePresslocation, 0, 0); + _isRubberBandSpanning = true; + StartMouseCapture(); + } return AutoFocus && Focus(this); } + /// + public override void OnMouseMove(Float2 location) + { + if (_isRubberBandSpanning) + { + _rubberBandRectangle.Width = location.X - _mousePresslocation.X; + _rubberBandRectangle.Height = location.Y - _mousePresslocation.Y; + } + + base.OnMouseMove(location); + } + + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (_isRubberBandSpanning) + { + _isRubberBandSpanning = false; + EndMouseCapture(); + if (_rubberBandRectangle.Width < 0 || _rubberBandRectangle.Height < 0) + { + // make sure we have a well-formed rectangle i.e. size is positive and X/Y is upper left corner + var size = _rubberBandRectangle.Size; + _rubberBandRectangle.X = Mathf.Min(_rubberBandRectangle.X, _rubberBandRectangle.X + _rubberBandRectangle.Width); + _rubberBandRectangle.Y = Mathf.Min(_rubberBandRectangle.Y, _rubberBandRectangle.Y + _rubberBandRectangle.Height); + size.X = Mathf.Abs(size.X); + size.Y = Mathf.Abs(size.Y); + _rubberBandRectangle.Size = size; + } + var itemsInRectangle = _items.Where(t => _rubberBandRectangle.Intersects(t.Bounds)).ToList(); + Select(itemsInRectangle, Input.GetKey(KeyboardKeys.Shift) || Input.GetKey(KeyboardKeys.Control)); + return true; + } + + return base.OnMouseUp(location, button); + } + /// public override bool OnMouseWheel(Float2 location, float delta) { diff --git a/Source/Editor/Content/Import/ModelImportEntry.cs b/Source/Editor/Content/Import/ModelImportEntry.cs index 484efb1d9..fac782cbd 100644 --- a/Source/Editor/Content/Import/ModelImportEntry.cs +++ b/Source/Editor/Content/Import/ModelImportEntry.cs @@ -16,6 +16,7 @@ namespace FlaxEngine.Tools private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab; private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab; private bool ShowAnimation => Type == ModelType.Animation || Type == ModelType.Prefab; + private bool ShowRootMotion => ShowAnimation && RootMotion != RootMotionMode.None; private bool ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals; private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents; private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom; diff --git a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs index f714ddbeb..7efc02368 100644 --- a/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationGraphFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraphFunction, outputPath)) + if (Editor.CreateAsset("AnimationGraphFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs index 3e6c35c6d..20d3c5a2c 100644 --- a/Source/Editor/Content/Proxy/AnimationGraphProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationGraphProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.AnimationGraph, outputPath)) + if (Editor.CreateAsset("AnimationGraph", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/AnimationProxy.cs b/Source/Editor/Content/Proxy/AnimationProxy.cs index 2cf46d3a3..6636fccb8 100644 --- a/Source/Editor/Content/Proxy/AnimationProxy.cs +++ b/Source/Editor/Content/Proxy/AnimationProxy.cs @@ -47,7 +47,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.Animation, outputPath)) + if (Editor.CreateAsset("Animation", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs index 33ad0862f..234dfaf7d 100644 --- a/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs +++ b/Source/Editor/Content/Proxy/BehaviorTreeProxy.cs @@ -47,7 +47,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.BehaviorTree, outputPath)) + if (Editor.CreateAsset("BehaviorTree", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/CollisionDataProxy.cs b/Source/Editor/Content/Proxy/CollisionDataProxy.cs index 55e8c6327..df26dca75 100644 --- a/Source/Editor/Content/Proxy/CollisionDataProxy.cs +++ b/Source/Editor/Content/Proxy/CollisionDataProxy.cs @@ -71,7 +71,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.CollisionData, outputPath)) + if (Editor.CreateAsset("CollisionData", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs index ab59f11f3..ca012f70a 100644 --- a/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.MaterialFunction, outputPath)) + if (Editor.CreateAsset("MaterialFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs index 331ff81c3..cd245b149 100644 --- a/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialInstanceProxy.cs @@ -43,7 +43,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.MaterialInstance, outputPath)) + if (Editor.CreateAsset("MaterialInstance", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/MaterialProxy.cs b/Source/Editor/Content/Proxy/MaterialProxy.cs index a7fcfecc8..f7db2c83d 100644 --- a/Source/Editor/Content/Proxy/MaterialProxy.cs +++ b/Source/Editor/Content/Proxy/MaterialProxy.cs @@ -44,7 +44,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.Material, outputPath)) + if (Editor.CreateAsset("Material", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs index 3a2ed749f..aaf9445e2 100644 --- a/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleEmitterFunctionProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.ParticleEmitterFunction, outputPath)) + if (Editor.CreateAsset("ParticleEmitterFunction", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs index 047853f0b..74f391513 100644 --- a/Source/Editor/Content/Proxy/ParticleSystemProxy.cs +++ b/Source/Editor/Content/Proxy/ParticleSystemProxy.cs @@ -75,7 +75,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.ParticleSystem, outputPath)) + if (Editor.CreateAsset("ParticleSystem", outputPath)) throw new Exception("Failed to create new asset."); } diff --git a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs index fe31f0e34..5f7fa800a 100644 --- a/Source/Editor/Content/Proxy/SceneAnimationProxy.cs +++ b/Source/Editor/Content/Proxy/SceneAnimationProxy.cs @@ -69,7 +69,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.SceneAnimation, outputPath)) + if (Editor.CreateAsset("SceneAnimation", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Content/Proxy/ScriptProxy.cs b/Source/Editor/Content/Proxy/ScriptProxy.cs index f688f37df..d728dcc38 100644 --- a/Source/Editor/Content/Proxy/ScriptProxy.cs +++ b/Source/Editor/Content/Proxy/ScriptProxy.cs @@ -69,8 +69,7 @@ namespace FlaxEditor.Content /// public override bool IsFileNameValid(string filename) { - // Scripts cannot start with digit. - if (Char.IsDigit(filename[0])) + if (char.IsDigit(filename[0])) return false; if (filename.Equals("Script")) return false; diff --git a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs index f300a4c61..560df0a7c 100644 --- a/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs +++ b/Source/Editor/Content/Proxy/SkeletonMaskProxy.cs @@ -38,7 +38,7 @@ namespace FlaxEditor.Content /// public override void Create(string outputPath, object arg) { - if (Editor.CreateAsset(Editor.NewAssetType.SkeletonMask, outputPath)) + if (Editor.CreateAsset("SkeletonMask", outputPath)) throw new Exception("Failed to create new asset."); } } diff --git a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp index c9317c683..f7aa5fedc 100644 --- a/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/Windows/WindowsPlatformTools.cpp @@ -5,11 +5,463 @@ #include "WindowsPlatformTools.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/Windows/WindowsPlatformSettings.h" +#include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Editor/Utilities/EditorUtilities.h" #include "Engine/Graphics/Textures/TextureData.h" +#include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/Content/Content.h" #include "Engine/Content/JsonAsset.h" +#include + +#define MSDOS_SIGNATURE 0x5A4D +#define PE_SIGNATURE 0x00004550 +#define PE_32BIT_SIGNATURE 0x10B +#define PE_64BIT_SIGNATURE 0x20B +#define PE_SECTION_UNINITIALIZED_DATA 0x00000080 +#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 +#define PE_IMAGE_RT_ICON 3 + +/// +/// MS-DOS header found at the beginning in a PE format file. +/// +struct MSDOSHeader +{ + uint16 signature; + uint16 lastSize; + uint16 numBlocks; + uint16 numReloc; + uint16 hdrSize; + uint16 minAlloc; + uint16 maxAlloc; + uint16 ss; + uint16 sp; + uint16 checksum; + uint16 ip; + uint16 cs; + uint16 relocPos; + uint16 numOverlay; + uint16 reserved1[4]; + uint16 oemId; + uint16 oemInfo; + uint16 reserved2[10]; + uint32 lfanew; +}; + +/// +/// COFF header found in a PE format file. +/// +struct COFFHeader +{ + uint16 machine; + uint16 numSections; + uint32 timeDateStamp; + uint32 ptrSymbolTable; + uint32 numSymbols; + uint16 sizeOptHeader; + uint16 characteristics; +}; + +/// +/// Contains address and size of data areas in a PE image. +/// +struct PEDataDirectory +{ + uint32 virtualAddress; + uint32 size; +}; + +/// +/// Optional header in a 32-bit PE format file. +/// +struct PEOptionalHeader32 +{ + uint16 signature; + uint8 majorLinkerVersion; + uint8 minorLinkerVersion; + uint32 sizeCode; + uint32 sizeInitializedData; + uint32 sizeUninitializedData; + uint32 addressEntryPoint; + uint32 baseCode; + uint32 baseData; + uint32 baseImage; + uint32 alignmentSection; + uint32 alignmentFile; + uint16 majorOSVersion; + uint16 minorOSVersion; + uint16 majorImageVersion; + uint16 minorImageVersion; + uint16 majorSubsystemVersion; + uint16 minorSubsystemVersion; + uint32 reserved; + uint32 sizeImage; + uint32 sizeHeaders; + uint32 checksum; + uint16 subsystem; + uint16 characteristics; + uint32 sizeStackReserve; + uint32 sizeStackCommit; + uint32 sizeHeapReserve; + uint32 sizeHeapCommit; + uint32 loaderFlags; + uint32 NumRvaAndSizes; + PEDataDirectory dataDirectory[16]; +}; + +/// +/// Optional header in a 64-bit PE format file. +/// +struct PEOptionalHeader64 +{ + uint16 signature; + uint8 majorLinkerVersion; + uint8 minorLinkerVersion; + uint32 sizeCode; + uint32 sizeInitializedData; + uint32 sizeUninitializedData; + uint32 addressEntryPoint; + uint32 baseCode; + uint64 baseImage; + uint32 alignmentSection; + uint32 alignmentFile; + uint16 majorOSVersion; + uint16 minorOSVersion; + uint16 majorImageVersion; + uint16 minorImageVersion; + uint16 majorSubsystemVersion; + uint16 minorSubsystemVersion; + uint32 reserved; + uint32 sizeImage; + uint32 sizeHeaders; + uint32 checksum; + uint16 subsystem; + uint16 characteristics; + uint64 sizeStackReserve; + uint64 sizeStackCommit; + uint64 sizeHeapReserve; + uint64 sizeHeapCommit; + uint32 loaderFlags; + uint32 NumRvaAndSizes; + PEDataDirectory dataDirectory[16]; +}; + +/// +/// A section header in a PE format file. +/// +struct PESectionHeader +{ + char name[8]; + uint32 virtualSize; + uint32 relativeVirtualAddress; + uint32 physicalSize; + uint32 physicalAddress; + uint8 deprecated[12]; + uint32 flags; +}; + +/// +/// A resource table header within a .rsrc section in a PE format file. +/// +struct PEImageResourceDirectory +{ + uint32 flags; + uint32 timeDateStamp; + uint16 majorVersion; + uint16 minorVersion; + uint16 numNamedEntries; + uint16 numIdEntries; +}; + +/// +/// A single entry in a resource table within a .rsrc section in a PE format file. +/// +struct PEImageResourceEntry +{ + uint32 type; + uint32 offsetDirectory : 31; + uint32 isDirectory : 1; +}; + +/// +/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file. +/// +struct PEImageResourceEntryData +{ + uint32 offsetData; + uint32 size; + uint32 codePage; + uint32 resourceHandle; +}; + +/// +/// Header used in icon file format. +/// +struct IconHeader +{ + uint32 size; + int32 width; + int32 height; + uint16 planes; + uint16 bitCount; + uint32 compression; + uint32 sizeImage; + int32 xPelsPerMeter; + int32 yPelsPerMeter; + uint32 clrUsed; + uint32 clrImportant; +}; + +void UpdateIconData(uint8* iconData, const TextureData* icon) +{ + IconHeader* iconHeader = (IconHeader*)iconData; + if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32) + { + // Unsupported format + return; + } + uint8* iconPixels = iconData + sizeof(IconHeader); + const uint32 width = iconHeader->width; + const uint32 height = iconHeader->height / 2; + + // Try to pick a proper mip (require the same size) + int32 srcPixelsMip = 0; + const int32 mipLevels = icon->GetMipLevels(); + for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) + { + const uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex); + const uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex); + if (width == iconWidth && height == iconHeight) + { + srcPixelsMip = mipIndex; + break; + } + } + const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip); + const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get(); + const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip)); + const auto sampler = TextureTool::GetSampler(icon->Format); + ASSERT_LOW_LAYER(sampler); + + // Write colors + uint32* colorData = (uint32*)iconPixels; + uint32 idx = 0; + for (int32 y = (int32)height - 1; y >= 0; y--) + { + float v = (float)y / height; + for (uint32 x = 0; x < width; x++) + { + float u = (float)x / width; + const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + colorData[idx++] = Color32(c).GetAsBGRA(); + } + } + + // Write AND mask + uint32 colorDataSize = width * height * sizeof(uint32); + uint8* maskData = iconPixels + colorDataSize; + uint32 numPackedPixels = width / 8; // One per bit in byte + for (int32 y = (int32)height - 1; y >= 0; y--) + { + uint8 mask = 0; + float v = (float)y / height; + for (uint32 packedX = 0; packedX < numPackedPixels; packedX++) + { + for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++) + { + uint32 x = packedX * 8 + pixelIdx; + float u = (float)x / width; + const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch); + if (c.A < 0.25f) + mask |= 1 << (7 - pixelIdx); + } + *maskData = mask; + maskData++; + } + } +} + +void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8) +{ + uint32 numEntries = current->numIdEntries; // Not supporting name entries + PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1); + for (uint32 i = 0; i < numEntries; i++) + { + // Only at root does the type identify resource type + if (base == current && entries[i].type != PE_IMAGE_RT_ICON) + continue; + + if (entries[i].isDirectory) + { + PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory); + SetIconData(base, child, imageData, sectionAddress, iconRGBA8); + } + else + { + PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory); + uint8* iconData = imageData + (data->offsetData - sectionAddress); + UpdateIconData(iconData, iconRGBA8); + } + } +} + +bool UpdateExeIcon(const String& path, const TextureData& icon) +{ + if (!FileSystem::FileExists(path)) + { + LOG(Warning, "Missing file"); + return true; + } + if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0) + { + LOG(Warning, "Inalid icon data"); + return true; + } + + // Ensure that image format can be sampled + const TextureData* iconRGBA8 = &icon; + TextureData tmpData1; + //if (icon.Format != PixelFormat::R8G8B8A8_UNorm) + if (TextureTool::GetSampler(icon.Format) == nullptr) + { + if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) + { + LOG(Warning, "Failed convert icon data."); + return true; + } + iconRGBA8 = &tmpData1; + } + + // Use fixed-size input icon image + TextureData tmpData2; + if (iconRGBA8->Width != 256 || iconRGBA8->Height != 256) + { + if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256)) + { + LOG(Warning, "Failed resize icon data."); + return true; + } + iconRGBA8 = &tmpData2; + } + + // A PE file is structured as such: + // - MSDOS Header + // - PE Signature + // - COFF Header + // - PE Optional Header + // - One or multiple sections + // - .code + // - .data + // - ... + // - .rsrc + // - icon/cursor/etc data + + std::fstream stream; +#if PLATFORM_WINDOWS + stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary); +#else + StringAsANSI<> pathAnsi(path.Get()); + stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); +#endif + if (!stream.is_open()) + { + LOG(Warning, "Cannot open file"); + return true; + } + + // First check magic number to ensure file is even an executable + uint16 magicNum; + stream.read((char*)&magicNum, sizeof(magicNum)); + if (magicNum != MSDOS_SIGNATURE) + { + LOG(Warning, "Provided file is not a valid executable."); + return true; + } + + // Read the MSDOS header and skip over it + stream.seekg(0); + MSDOSHeader msdosHeader; + stream.read((char*)&msdosHeader, sizeof(MSDOSHeader)); + + // Read PE signature + stream.seekg(msdosHeader.lfanew); + uint32 peSignature; + stream.read((char*)&peSignature, sizeof(peSignature)); + if (peSignature != PE_SIGNATURE) + { + LOG(Warning, "Provided file is not in PE format."); + return true; + } + + // Read COFF header + COFFHeader coffHeader; + stream.read((char*)&coffHeader, sizeof(COFFHeader)); + if (coffHeader.sizeOptHeader == 0) + { + LOG(Warning, "Provided file is not a valid executable."); + return true; + } + uint32 sectionHeadersCount = coffHeader.numSections; + + // Read optional header + auto optionalHeaderPos = stream.tellg(); + uint16 optionalHeaderSignature; + stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature)); + PEDataDirectory* dataDirectory = nullptr; + stream.seekg(optionalHeaderPos); + if (optionalHeaderSignature == PE_32BIT_SIGNATURE) + { + PEOptionalHeader32 optionalHeader; + stream.read((char*)&optionalHeader, sizeof(optionalHeader)); + dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; + } + else if (optionalHeaderSignature == PE_64BIT_SIGNATURE) + { + PEOptionalHeader64 optionalHeader; + stream.read((char*)&optionalHeader, sizeof(optionalHeader)); + dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; + } + else + { + LOG(Warning, "Unrecognized PE format."); + return true; + } + + // Read section headers + auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader; + stream.seekg(sectionHeaderPos); + Array sectionHeaders; + sectionHeaders.Resize(sectionHeadersCount); + stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * sectionHeadersCount); + + // Look for .rsrc section header + for (uint32 i = 0; i < sectionHeadersCount; i++) + { + PESectionHeader& sectionHeader = sectionHeaders[i]; + if (sectionHeader.flags & PE_SECTION_UNINITIALIZED_DATA) + continue; + if (strcmp(sectionHeader.name, ".rsrc") == 0) + { + uint32 imageSize = sectionHeader.physicalSize; + Array imageData; + imageData.Resize(imageSize); + + stream.seekg(sectionHeader.physicalAddress); + stream.read((char*)imageData.Get(), imageSize); + + uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeader.relativeVirtualAddress; + PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset]; + + SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeader.relativeVirtualAddress, iconRGBA8); + stream.seekp(sectionHeader.physicalAddress); + stream.write((char*)imageData.Get(), imageSize); + } + } + + stream.close(); + + return false; +} IMPLEMENT_ENGINE_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); @@ -50,7 +502,7 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data) TextureData iconData; if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, iconData)) { - if (EditorUtilities::UpdateExeIcon(files[0], iconData)) + if (UpdateExeIcon(files[0], iconData)) { data.Error(TEXT("Failed to change output executable file icon.")); return true; diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index a519b1da1..6ae0f0562 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -543,10 +543,11 @@ namespace FlaxEditor.CustomEditors try { string text; + var value = Values[0]; if (ParentEditor is Dedicated.ScriptsEditor) { // Script - text = JsonSerializer.Serialize(Values[0]); + text = JsonSerializer.Serialize(value); // Remove properties that should be ignored when copy/pasting data if (text == null) @@ -576,12 +577,12 @@ namespace FlaxEditor.CustomEditors else if (ScriptType.FlaxObject.IsAssignableFrom(Values.Type)) { // Object reference - text = JsonSerializer.GetStringID(Values[0] as FlaxEngine.Object); + text = JsonSerializer.GetStringID(value as FlaxEngine.Object); } else { // Default - text = JsonSerializer.Serialize(Values[0]); + text = JsonSerializer.Serialize(value, TypeUtils.GetType(Values.Type)); } Clipboard.Text = text; } diff --git a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs index f783822a5..63374361d 100644 --- a/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/ScriptsEditor.cs @@ -3,6 +3,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; +using System.Linq; using FlaxEditor.Actions; using FlaxEditor.Content; using FlaxEditor.GUI; @@ -16,6 +18,27 @@ using Object = FlaxEngine.Object; namespace FlaxEditor.CustomEditors.Dedicated { + internal class NewScriptItem : ItemsListContextMenu.Item + { + private string _scriptName; + + public string ScriptName + { + get => _scriptName; + set + { + _scriptName = value; + Name = $"Create script '{value}'"; + } + } + + public NewScriptItem(string scriptName) + { + ScriptName = scriptName; + TooltipText = "Create a new script"; + } + } + /// /// Drag and drop scripts area control. /// @@ -74,7 +97,43 @@ namespace FlaxEditor.CustomEditors.Dedicated { cm.AddItem(new TypeSearchPopup.TypeItemView(scripts[i])); } - cm.ItemClicked += item => AddScript((ScriptType)item.Tag); + cm.TextChanged += text => + { + if (!IsValidScriptName(text)) + return; + if (!cm.ItemsPanel.Children.Any(x => x.Visible && x is not NewScriptItem)) + { + // If there are no visible items, that means the search failed so we can find the create script button or create one if it's the first time + var newScriptItem = (NewScriptItem)cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (newScriptItem != null) + { + newScriptItem.Visible = true; + newScriptItem.ScriptName = text; + } + else + { + cm.AddItem(new NewScriptItem(text)); + } + } + else + { + // Make sure to hide the create script button if there + var newScriptItem = cm.ItemsPanel.Children.FirstOrDefault(x => x is NewScriptItem); + if (newScriptItem != null) + newScriptItem.Visible = false; + } + }; + cm.ItemClicked += item => + { + if (item.Tag is ScriptType script) + { + AddScript(script); + } + else if (item is NewScriptItem newScriptItem) + { + CreateScript(newScriptItem); + } + }; cm.SortItems(); cm.Show(this, button.BottomLeft - new Float2((cm.Width - button.Width) / 2, 0)); } @@ -113,6 +172,19 @@ namespace FlaxEditor.CustomEditors.Dedicated return false; } + private static bool IsValidScriptName(string text) + { + if (string.IsNullOrEmpty(text)) + return false; + if (text.Contains(' ')) + return false; + if (char.IsDigit(text[0])) + return false; + if (text.Any(c => !char.IsLetterOrDigit(c) && c != '_')) + return false; + return Editor.Instance.ContentDatabase.GetProxy("cs").IsFileNameValid(text); + } + /// public override DragDropEffect OnDragEnter(ref Float2 location, DragData data) { @@ -163,6 +235,7 @@ namespace FlaxEditor.CustomEditors.Dedicated if (_dragScripts.HasValidDrag) { result = _dragScripts.Effect; + AddScripts(_dragScripts.Objects); } else if (_dragAssets.HasValidDrag) @@ -177,7 +250,43 @@ namespace FlaxEditor.CustomEditors.Dedicated return result; } - private void AddScript(ScriptType item) + private void CreateScript(NewScriptItem item) + { + ScriptsEditor.NewScriptName = item.ScriptName; + var paths = Directory.GetFiles(Globals.ProjectSourceFolder, "*.Build.cs"); + + string moduleName = null; + foreach (var p in paths) + { + var file = File.ReadAllText(p); + if (!file.Contains("GameProjectTarget")) + continue; // Skip + + if (file.Contains("Modules.Add(\"Game\")")) + { + // Assume Game represents the main game module + moduleName = "Game"; + break; + } + } + + // Ensure the path slashes are correct for the OS + var correctedPath = Path.GetFullPath(Globals.ProjectSourceFolder); + if (string.IsNullOrEmpty(moduleName)) + { + var error = FileSystem.ShowBrowseFolderDialog(Editor.Instance.Windows.MainWindow, correctedPath, "Select a module folder to put the new script in", out moduleName); + if (error) + return; + } + var path = Path.Combine(Globals.ProjectSourceFolder, moduleName, item.ScriptName + ".cs"); + Editor.Instance.ContentDatabase.GetProxy("cs").Create(path, null); + } + + /// + /// Attach a script to the actor. + /// + /// The script. + public void AddScript(ScriptType item) { var list = new List(1) { item }; AddScripts(list); @@ -224,16 +333,67 @@ namespace FlaxEditor.CustomEditors.Dedicated private void AddScripts(List items) { - var actions = new List(4); + var actions = new List(); for (int i = 0; i < items.Count; i++) { var scriptType = items[i]; + RequireScriptAttribute scriptAttribute = null; + if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) + { + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireScriptAttribute requireScriptAttribute) + continue; + scriptAttribute = requireScriptAttribute; + break; + } + } + + // See if script requires a specific actor type + RequireActorAttribute actorAttribute = null; + if (scriptType.HasAttribute(typeof(RequireActorAttribute), false)) + { + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireActorAttribute requireActorAttribute) + continue; + actorAttribute = requireActorAttribute; + break; + } + } + var actors = ScriptsEditor.ParentEditor.Values; for (int j = 0; j < actors.Count; j++) { var actor = (Actor)actors[j]; + + // If required actor exists but is not this actor type then skip adding to actor + if (actorAttribute != null) + { + if (actor.GetType() != actorAttribute.RequiredType && !actor.GetType().IsSubclassOf(actorAttribute.RequiredType)) + { + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` not added to `{actor}` due to script requiring an Actor type of `{actorAttribute.RequiredType}`."); + continue; + } + } + actions.Add(AddRemoveScript.Add(actor, scriptType)); + // Check if actor has required scripts and add them if the actor does not. + if (scriptAttribute != null) + { + foreach (var type in scriptAttribute.RequiredTypes) + { + if (!type.IsSubclassOf(typeof(Script))) + { + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(type.Name)}` not added to `{actor}` due to the class not being a subclass of Script."); + continue; + } + if (actor.GetScript(type) != null) + continue; + actions.Add(AddRemoveScript.Add(actor, new ScriptType(type))); + } + } } } @@ -440,6 +600,11 @@ namespace FlaxEditor.CustomEditors.Dedicated /// public override IEnumerable UndoObjects => _scripts; + /// + /// Cached the newly created script name - used to add script after compilation. + /// + internal static string NewScriptName; + private void AddMissingScript(int index, LayoutElementsContainer layout) { var group = layout.Group("Missing script"); @@ -548,6 +713,21 @@ namespace FlaxEditor.CustomEditors.Dedicated var dragArea = layout.CustomContainer(); dragArea.CustomControl.ScriptsEditor = this; + // If the initialization is triggered by an editor recompilation, check if it was due to script generation from DragAreaControl + if (NewScriptName != null) + { + var script = Editor.Instance.CodeEditing.Scripts.Get().FirstOrDefault(x => x.Name == NewScriptName); + NewScriptName = null; + if (script != null) + { + dragArea.CustomControl.AddScript(script); + } + else + { + Editor.LogWarning("Failed to find newly created script."); + } + } + // No support for showing scripts from multiple actors that have different set of scripts var scripts = (Script[])Values[0]; _scripts.Clear(); @@ -586,19 +766,71 @@ namespace FlaxEditor.CustomEditors.Dedicated var scriptType = TypeUtils.GetObjectType(script); var editor = CustomEditorsUtil.CreateEditor(scriptType, false); + // Check if actor has all the required scripts + bool hasAllRequirements = true; + if (scriptType.HasAttribute(typeof(RequireScriptAttribute), false)) + { + RequireScriptAttribute scriptAttribute = null; + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireScriptAttribute requireScriptAttribute) + continue; + scriptAttribute = requireScriptAttribute; + } + + if (scriptAttribute != null) + { + foreach (var type in scriptAttribute.RequiredTypes) + { + if (!type.IsSubclassOf(typeof(Script))) + continue; + var requiredScript = script.Actor.GetScript(type); + if (requiredScript == null) + { + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Script of type `{type}`."); + hasAllRequirements = false; + } + } + } + } + if (scriptType.HasAttribute(typeof(RequireActorAttribute), false)) + { + RequireActorAttribute attribute = null; + foreach (var e in scriptType.GetAttributes(false)) + { + if (e is not RequireActorAttribute requireActorAttribute) + continue; + attribute = requireActorAttribute; + break; + } + + if (attribute != null) + { + var actor = script.Actor; + if (actor.GetType() != attribute.RequiredType && !actor.GetType().IsSubclassOf(attribute.RequiredType)) + { + Editor.LogWarning($"`{Utilities.Utils.GetPropertyNameUI(scriptType.Name)}` on `{script.Actor}` is missing a required Actor of type `{attribute.RequiredType}`."); + hasAllRequirements = false; + // Maybe call to remove script here? + } + } + } + // Create group var title = Utilities.Utils.GetPropertyNameUI(scriptType.Name); var group = layout.Group(title, editor); + if (!hasAllRequirements) + group.Panel.HeaderTextColor = FlaxEngine.GUI.Style.Current.Statusbar.Failed; if ((Presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { - if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) - group.Panel.Close(false); + if (Editor.Instance.ProjectCache.IsGroupToggled(title)) + group.Panel.Close(); else - group.Panel.Open(false); - group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed); + group.Panel.Open(); + group.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(panel.HeaderText, panel.IsClosed); } else - group.Panel.Open(false); + group.Panel.Open(); // Customize group.Panel.TooltipText = Editor.Instance.CodeDocs.GetTooltip(scriptType); diff --git a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs index 1f3359fd5..db8ec1152 100644 --- a/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs +++ b/Source/Editor/CustomEditors/Editors/AssetRefEditor.cs @@ -51,10 +51,13 @@ namespace FlaxEditor.CustomEditors.Editors return; Picker = layout.Custom().CustomControl; - _valueType = Values.Type.Type != typeof(object) || Values[0] == null ? Values.Type : TypeUtils.GetObjectType(Values[0]); + var value = Values[0]; + _valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value); var assetType = _valueType; if (assetType == typeof(string)) assetType = new ScriptType(typeof(Asset)); + else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name) + assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]); float height = 48; var attributes = Values.GetAttributes(); @@ -102,6 +105,12 @@ namespace FlaxEditor.CustomEditors.Editors SetValue(new SceneReference(Picker.Validator.SelectedID)); else if (_valueType.Type == typeof(string)) SetValue(Picker.Validator.SelectedPath); + else if (_valueType.Type.Name == typeof(JsonAssetReference<>).Name) + { + var value = Values[0]; + value.GetType().GetField("Asset").SetValue(value, Picker.Validator.SelectedAsset as JsonAsset); + SetValue(value); + } else SetValue(Picker.Validator.SelectedAsset); } @@ -114,16 +123,19 @@ namespace FlaxEditor.CustomEditors.Editors if (!HasDifferentValues) { _isRefreshing = true; - if (Values[0] is AssetItem assetItem) + var value = Values[0]; + if (value is AssetItem assetItem) Picker.Validator.SelectedItem = assetItem; - else if (Values[0] is Guid guid) + else if (value is Guid guid) Picker.Validator.SelectedID = guid; - else if (Values[0] is SceneReference sceneAsset) + else if (value is SceneReference sceneAsset) Picker.Validator.SelectedItem = Editor.Instance.ContentDatabase.FindAsset(sceneAsset.ID); - else if (Values[0] is string path) + else if (value is string path) Picker.Validator.SelectedPath = path; + else if (value != null && value.GetType().Name == typeof(JsonAssetReference<>).Name) + Picker.Validator.SelectedAsset = value.GetType().GetField("Asset").GetValue(value) as JsonAsset; else - Picker.Validator.SelectedAsset = Values[0] as Asset; + Picker.Validator.SelectedAsset = value as Asset; _isRefreshing = false; } } diff --git a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs index 00322fc81..38dce0a25 100644 --- a/Source/Editor/CustomEditors/Editors/CollectionEditor.cs +++ b/Source/Editor/CustomEditors/Editors/CollectionEditor.cs @@ -176,8 +176,8 @@ namespace FlaxEditor.CustomEditors.Editors private IntValueBox _sizeBox; private Color _background; - private int _elementsCount; - private bool _readOnly; + private int _elementsCount, _minCount, _maxCount; + private bool _canResize; private bool _canReorderItems; private CollectionAttribute.DisplayType _displayType; @@ -209,8 +209,10 @@ namespace FlaxEditor.CustomEditors.Editors return; var size = Count; - _readOnly = false; + _canResize = true; _canReorderItems = true; + _minCount = 0; + _maxCount = 0; _background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor; _displayType = CollectionAttribute.DisplayType.Header; NotNullItems = false; @@ -222,7 +224,9 @@ namespace FlaxEditor.CustomEditors.Editors var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { - _readOnly = collection.ReadOnly; + _canResize = !collection.ReadOnly; + _minCount = collection.MinCount; + _maxCount = collection.MaxCount; _canReorderItems = collection.CanReorderItems; NotNullItems = collection.NotNullItems; if (collection.BackgroundColor.HasValue) @@ -231,6 +235,9 @@ namespace FlaxEditor.CustomEditors.Editors spacing = collection.Spacing; _displayType = collection.Display; } + if (_maxCount == 0) + _maxCount = ushort.MaxValue; + _canResize &= _minCount < _maxCount; var dragArea = layout.CustomContainer(); dragArea.CustomControl.Editor = this; @@ -268,8 +275,8 @@ namespace FlaxEditor.CustomEditors.Editors var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top; _sizeBox = new IntValueBox(size) { - MinValue = 0, - MaxValue = ushort.MaxValue, + MinValue = _minCount, + MaxValue = _maxCount, AnchorPreset = AnchorPresets.TopRight, Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height), Parent = dropPanel, @@ -283,7 +290,7 @@ namespace FlaxEditor.CustomEditors.Editors Parent = dropPanel }; - if (_readOnly || (NotNullItems && size == 0)) + if (!_canResize || (NotNullItems && size == 0)) { _sizeBox.IsReadOnly = true; _sizeBox.Enabled = false; @@ -339,7 +346,7 @@ namespace FlaxEditor.CustomEditors.Editors _elementsCount = size; // Add/Remove buttons - if (!_readOnly) + if (_canResize) { var panel = dragArea.HorizontalPanel(); panel.Panel.Size = new Float2(0, 20); @@ -347,25 +354,23 @@ namespace FlaxEditor.CustomEditors.Editors var removeButton = panel.Button("-", "Remove last item"); removeButton.Button.Size = new Float2(16, 16); - removeButton.Button.Enabled = size > 0; + removeButton.Button.Enabled = size > _minCount; removeButton.Button.AnchorPreset = AnchorPresets.TopRight; removeButton.Button.Clicked += () => { if (IsSetBlocked) return; - Resize(Count - 1); }; var addButton = panel.Button("+", "Add new item"); addButton.Button.Size = new Float2(16, 16); - addButton.Button.Enabled = !NotNullItems || size > 0; + addButton.Button.Enabled = (!NotNullItems || size > 0) && size < _maxCount; addButton.Button.AnchorPreset = AnchorPresets.TopRight; addButton.Button.Clicked += () => { if (IsSetBlocked) return; - Resize(Count + 1); }; } diff --git a/Source/Editor/CustomEditors/LayoutElementsContainer.cs b/Source/Editor/CustomEditors/LayoutElementsContainer.cs index 143e180ee..ca2a1332c 100644 --- a/Source/Editor/CustomEditors/LayoutElementsContainer.cs +++ b/Source/Editor/CustomEditors/LayoutElementsContainer.cs @@ -25,6 +25,11 @@ namespace FlaxEditor.CustomEditors /// internal bool isRootGroup = true; + /// + /// Parent container who created this one. + /// + internal LayoutElementsContainer _parent; + /// /// The children. /// @@ -40,6 +45,24 @@ namespace FlaxEditor.CustomEditors /// public abstract ContainerControl ContainerControl { get; } + /// + /// Gets the Custom Editors layout presenter. + /// + internal CustomEditorPresenter Presenter + { + get + { + CustomEditorPresenter result; + var container = this; + do + { + result = container as CustomEditorPresenter; + container = container._parent; + } while (container != null); + return result; + } + } + /// /// Adds new group element. /// @@ -81,17 +104,31 @@ namespace FlaxEditor.CustomEditors public GroupElement Group(string title, bool useTransparentHeader = false) { var element = new GroupElement(); - if (!isRootGroup) + var presenter = Presenter; + var isSubGroup = !isRootGroup; + if (isSubGroup) + element.Panel.Close(); + if (presenter != null && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) { - element.Panel.Close(false); - } - else if (this is CustomEditorPresenter presenter && (presenter.Features & FeatureFlags.CacheExpandedGroups) != 0) - { - if (Editor.Instance.ProjectCache.IsCollapsedGroup(title)) - element.Panel.Close(false); - element.Panel.IsClosedChanged += OnPanelIsClosedChanged; + // Build group identifier (made of path from group titles) + var expandPath = title; + var container = this; + while (container != null && !(container is CustomEditorPresenter)) + { + if (container.ContainerControl is DropPanel dropPanel) + expandPath = dropPanel.HeaderText + "/" + expandPath; + container = container._parent; + } + + // Caching/restoring expanded groups (non-root groups cache expanded state so invert boolean expression) + if (Editor.Instance.ProjectCache.IsGroupToggled(expandPath) ^ isSubGroup) + element.Panel.Close(); + else + element.Panel.Open(); + element.Panel.IsClosedChanged += panel => Editor.Instance.ProjectCache.SetGroupToggle(expandPath, panel.IsClosed ^ isSubGroup); } element.isRootGroup = false; + element._parent = this; element.Panel.HeaderText = title; if (useTransparentHeader) { @@ -103,11 +140,6 @@ namespace FlaxEditor.CustomEditors return element; } - private void OnPanelIsClosedChanged(DropPanel panel) - { - Editor.Instance.ProjectCache.SetCollapsedGroup(panel.HeaderText, panel.IsClosed); - } - /// /// Adds new horizontal panel element. /// @@ -627,7 +659,6 @@ namespace FlaxEditor.CustomEditors if (style == DisplayStyle.Group) { var group = Group(name, editor, true); - group.Panel.Close(false); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } @@ -657,7 +688,6 @@ namespace FlaxEditor.CustomEditors if (style == DisplayStyle.Group) { var group = Group(label.Text, editor, true); - group.Panel.Close(false); group.Panel.TooltipText = tooltip; return group.Object(values, editor); } diff --git a/Source/Editor/Editor.cs b/Source/Editor/Editor.cs index 393bf564e..266d8235f 100644 --- a/Source/Editor/Editor.cs +++ b/Source/Editor/Editor.cs @@ -869,7 +869,9 @@ namespace FlaxEditor /// /// New asset types allowed to create. + /// [Deprecated in v1.8] /// + [Obsolete("Use CreateAsset with named tag.")] public enum NewAssetType { /// @@ -1046,12 +1048,59 @@ namespace FlaxEditor /// /// Creates new asset at the target location. + /// [Deprecated in v1.8] /// /// New asset type. /// Output asset path. + [Obsolete("Use CreateAsset with named tag.")] public static bool CreateAsset(NewAssetType type, string outputPath) { - return Internal_CreateAsset(type, outputPath); + // [Deprecated on 18.02.2024, expires on 18.02.2025] + string tag; + switch (type) + { + case NewAssetType.Material: + tag = "Material"; + break; + case NewAssetType.MaterialInstance: + tag = "MaterialInstance"; + break; + case NewAssetType.CollisionData: + tag = "CollisionData"; + break; + case NewAssetType.AnimationGraph: + tag = "AnimationGraph"; + break; + case NewAssetType.SkeletonMask: + tag = "SkeletonMask"; + break; + case NewAssetType.ParticleEmitter: + tag = "ParticleEmitter"; + break; + case NewAssetType.ParticleSystem: + tag = "ParticleSystem"; + break; + case NewAssetType.SceneAnimation: + tag = "SceneAnimation"; + break; + case NewAssetType.MaterialFunction: + tag = "MaterialFunction"; + break; + case NewAssetType.ParticleEmitterFunction: + tag = "ParticleEmitterFunction"; + break; + case NewAssetType.AnimationGraphFunction: + tag = "AnimationGraphFunction"; + break; + case NewAssetType.Animation: + tag = "Animation"; + break; + case NewAssetType.BehaviorTree: + tag = "BehaviorTree"; + break; + default: return true; + } + return CreateAsset(tag, outputPath); } /// @@ -1588,10 +1637,6 @@ namespace FlaxEditor [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CloseSplashScreen", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] internal static partial void Internal_CloseSplashScreen(); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateAsset", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] - [return: MarshalAs(UnmanagedType.U1)] - internal static partial bool Internal_CreateAsset(NewAssetType type, string outputPath); - [LibraryImport("FlaxEngine", EntryPoint = "EditorInternal_CreateVisualScript", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(StringMarshaller))] [return: MarshalAs(UnmanagedType.U1)] internal static partial bool Internal_CreateVisualScript(string outputPath, string baseTypename); diff --git a/Source/Editor/EditorAssets.cs b/Source/Editor/EditorAssets.cs index eb2f21356..8b2049ebd 100644 --- a/Source/Editor/EditorAssets.cs +++ b/Source/Editor/EditorAssets.cs @@ -54,6 +54,11 @@ namespace FlaxEditor /// public static string PrimaryFont = "Editor/Fonts/Roboto-Regular"; + /// + /// The secondary (fallback) font to use for missing characters rendering (CJK - Chinese/Japanese/Korean characters). + /// + public static string FallbackFont = "Editor/Fonts/NotoSansSC-Regular"; + /// /// The Inconsolata Regular font. /// diff --git a/Source/Editor/GUI/ItemsListContextMenu.cs b/Source/Editor/GUI/ItemsListContextMenu.cs index 42d236991..c8c2a9c22 100644 --- a/Source/Editor/GUI/ItemsListContextMenu.cs +++ b/Source/Editor/GUI/ItemsListContextMenu.cs @@ -189,6 +189,11 @@ namespace FlaxEditor.GUI /// public event Action ItemClicked; + /// + /// Event fired when search text in this popup menu gets changed. + /// + public event Action TextChanged; + /// /// The panel control where you should add your items. /// @@ -263,6 +268,7 @@ namespace FlaxEditor.GUI UnlockChildrenRecursive(); PerformLayout(true); _searchBox.Focus(); + TextChanged?.Invoke(_searchBox.Text); } /// @@ -439,6 +445,7 @@ namespace FlaxEditor.GUI Hide(); return true; case KeyboardKeys.ArrowDown: + { if (RootWindow.FocusedControl == null) { // Focus search box if nothing is focused @@ -447,20 +454,19 @@ namespace FlaxEditor.GUI } // Focus the first visible item or then next one + var items = GetVisibleItems(); + var focusedIndex = items.IndexOf(focusedItem); + if (focusedIndex == -1) + focusedIndex = -1; + if (focusedIndex + 1 < items.Count) { - var items = GetVisibleItems(); - var focusedIndex = items.IndexOf(focusedItem); - if (focusedIndex == -1) - focusedIndex = -1; - if (focusedIndex + 1 < items.Count) - { - var item = items[focusedIndex + 1]; - item.Focus(); - _scrollPanel.ScrollViewTo(item); - return true; - } + var item = items[focusedIndex + 1]; + item.Focus(); + _scrollPanel.ScrollViewTo(item); + return true; } break; + } case KeyboardKeys.ArrowUp: if (focusedItem != null) { diff --git a/Source/Editor/GUI/Row.cs b/Source/Editor/GUI/Row.cs index ca45ec6f5..3e2bb63bd 100644 --- a/Source/Editor/GUI/Row.cs +++ b/Source/Editor/GUI/Row.cs @@ -43,8 +43,9 @@ namespace FlaxEditor.GUI { Depth = -1; - if (Height < Style.Current.FontMedium.Height) - Height = Style.Current.FontMedium.Height + 4; + var fontHeight = Style.Current.FontMedium.Height; + if (Height < fontHeight) + Height = fontHeight + 4; } /// diff --git a/Source/Editor/GUI/Tabs/Tabs.cs b/Source/Editor/GUI/Tabs/Tabs.cs index 3c70363e7..c9b1e1eff 100644 --- a/Source/Editor/GUI/Tabs/Tabs.cs +++ b/Source/Editor/GUI/Tabs/Tabs.cs @@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs /// public Tab SelectedTab { - get => _selectedIndex < 0 || Children.Count <= _selectedIndex ? null : Children[_selectedIndex + 1] as Tab; + get => _selectedIndex < 0 || Children.Count <= (_selectedIndex+1) ? null : Children[_selectedIndex + 1] as Tab; set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1; } diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp index ae8b71ee2..53c0fbab2 100644 --- a/Source/Editor/Managed/ManagedEditor.Internal.cpp +++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp @@ -170,78 +170,6 @@ DEFINE_INTERNAL_CALL(bool) EditorInternal_CloneAssetFile(MString* dstPathObj, MS return Content::CloneAssetFile(dstPath, srcPath, *dstId); } -enum class NewAssetType -{ - Material = 0, - MaterialInstance = 1, - CollisionData = 2, - AnimationGraph = 3, - SkeletonMask = 4, - ParticleEmitter = 5, - ParticleSystem = 6, - SceneAnimation = 7, - MaterialFunction = 8, - ParticleEmitterFunction = 9, - AnimationGraphFunction = 10, - Animation = 11, - BehaviorTree = 12, -}; - -DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateAsset(NewAssetType type, MString* outputPathObj) -{ - String tag; - switch (type) - { - case NewAssetType::Material: - tag = AssetsImportingManager::CreateMaterialTag; - break; - case NewAssetType::MaterialInstance: - tag = AssetsImportingManager::CreateMaterialInstanceTag; - break; - case NewAssetType::CollisionData: - tag = AssetsImportingManager::CreateCollisionDataTag; - break; - case NewAssetType::AnimationGraph: - tag = AssetsImportingManager::CreateAnimationGraphTag; - break; - case NewAssetType::SkeletonMask: - tag = AssetsImportingManager::CreateSkeletonMaskTag; - break; - case NewAssetType::ParticleEmitter: - tag = AssetsImportingManager::CreateParticleEmitterTag; - break; - case NewAssetType::ParticleSystem: - tag = AssetsImportingManager::CreateParticleSystemTag; - break; - case NewAssetType::SceneAnimation: - tag = AssetsImportingManager::CreateSceneAnimationTag; - break; - case NewAssetType::MaterialFunction: - tag = AssetsImportingManager::CreateMaterialFunctionTag; - break; - case NewAssetType::ParticleEmitterFunction: - tag = AssetsImportingManager::CreateParticleEmitterFunctionTag; - break; - case NewAssetType::AnimationGraphFunction: - tag = AssetsImportingManager::CreateAnimationGraphFunctionTag; - break; - case NewAssetType::Animation: - tag = AssetsImportingManager::CreateAnimationTag; - break; - case NewAssetType::BehaviorTree: - tag = AssetsImportingManager::CreateBehaviorTreeTag; - break; - default: - return true; - } - - String outputPath; - MUtils::ToString(outputPathObj, outputPath); - FileSystem::NormalizePath(outputPath); - - return AssetsImportingManager::Create(tag, outputPath); -} - DEFINE_INTERNAL_CALL(bool) EditorInternal_CreateVisualScript(MString* outputPathObj, MString* baseTypenameObj) { String outputPath; @@ -634,13 +562,11 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String assetPath) { - // Initialize defaults + // Initialize defaults if (const auto* graphicsSettings = GraphicsSettings::Get()) { options.GenerateSDF = graphicsSettings->GenerateSDFOnModelImport; } - - // Get options from model FileSystem::NormalizePath(assetPath); return ImportModel::TryGetImportOptions(assetPath, options); } @@ -652,7 +578,12 @@ bool ManagedEditor::Import(const String& inputPath, const String& outputPath, co bool ManagedEditor::TryRestoreImportOptions(AudioTool::Options& options, String assetPath) { - // Get options from model FileSystem::NormalizePath(assetPath); return ImportAudio::TryGetImportOptions(assetPath, options); } + +bool ManagedEditor::CreateAsset(const String& tag, String outputPath) +{ + FileSystem::NormalizePath(outputPath); + return AssetsImportingManager::Create(tag, outputPath); +} diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 8c9571cfd..6aa7514b0 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -210,6 +210,13 @@ public: API_FUNCTION() static bool TryRestoreImportOptions(API_PARAM(Ref) AudioTool::Options& options, String assetPath); #endif + /// + /// Creates a new asset at the target location. + /// + /// New asset type. + /// Output asset path. + API_FUNCTION() static bool CreateAsset(const String& tag, String outputPath); + public: API_STRUCT(Internal, NoDefault) struct VisualScriptStackFrame { diff --git a/Source/Editor/Modules/ProjectCacheModule.cs b/Source/Editor/Modules/ProjectCacheModule.cs index acb6e997e..eebea3ba0 100644 --- a/Source/Editor/Modules/ProjectCacheModule.cs +++ b/Source/Editor/Modules/ProjectCacheModule.cs @@ -18,7 +18,7 @@ namespace FlaxEditor.Modules private DateTime _lastSaveTime; private readonly HashSet _expandedActors = new HashSet(); - private readonly HashSet _collapsedGroups = new HashSet(); + private readonly HashSet _toggledGroups = new HashSet(); private readonly Dictionary _customData = new Dictionary(); /// @@ -62,26 +62,26 @@ namespace FlaxEditor.Modules } /// - /// Determines whether group identified by the given title is collapsed in the UI. + /// Determines whether group identified by the given title is collapsed/opened in the UI. /// /// The group title. - /// true if group is collapsed; otherwise, false. - public bool IsCollapsedGroup(string title) + /// true if group is toggled; otherwise, false. + public bool IsGroupToggled(string title) { - return _collapsedGroups.Contains(title); + return _toggledGroups.Contains(title); } /// - /// Sets the group collapsed cached value. + /// Sets the group collapsed/opened cached value. /// /// The group title. - /// If set to true group will be cached as an collapsed, otherwise false. - public void SetCollapsedGroup(string title, bool isCollapsed) + /// If set to true group will be cached as a toggled, otherwise false. + public void SetGroupToggle(string title, bool isToggled) { - if (isCollapsed) - _collapsedGroups.Add(title); + if (isToggled) + _toggledGroups.Add(title); else - _collapsedGroups.Remove(title); + _toggledGroups.Remove(title); _isDirty = true; } @@ -160,7 +160,7 @@ namespace FlaxEditor.Modules _expandedActors.Add(new Guid(bytes16)); } - _collapsedGroups.Clear(); + _toggledGroups.Clear(); _customData.Clear(); break; @@ -176,7 +176,7 @@ namespace FlaxEditor.Modules _expandedActors.Add(new Guid(bytes16)); } - _collapsedGroups.Clear(); + _toggledGroups.Clear(); _customData.Clear(); int customDataCount = reader.ReadInt32(); @@ -201,11 +201,9 @@ namespace FlaxEditor.Modules } int collapsedGroupsCount = reader.ReadInt32(); - _collapsedGroups.Clear(); + _toggledGroups.Clear(); for (int i = 0; i < collapsedGroupsCount; i++) - { - _collapsedGroups.Add(reader.ReadString()); - } + _toggledGroups.Add(reader.ReadString()); _customData.Clear(); int customDataCount = reader.ReadInt32(); @@ -259,11 +257,9 @@ namespace FlaxEditor.Modules writer.Write(e.ToByteArray()); } - writer.Write(_collapsedGroups.Count); - foreach (var e in _collapsedGroups) - { + writer.Write(_toggledGroups.Count); + foreach (var e in _toggledGroups) writer.Write(e); - } writer.Write(_customData.Count); foreach (var e in _customData) @@ -284,7 +280,6 @@ namespace FlaxEditor.Modules try { SaveGuarded(); - _isDirty = false; } catch (Exception ex) diff --git a/Source/Editor/Modules/SceneEditingModule.cs b/Source/Editor/Modules/SceneEditingModule.cs index 970ca8e99..1657cb3a1 100644 --- a/Source/Editor/Modules/SceneEditingModule.cs +++ b/Source/Editor/Modules/SceneEditingModule.cs @@ -534,6 +534,41 @@ namespace FlaxEditor.Modules Delete(); } + /// + /// Create parent for selected actors. + /// + public void CreateParentForSelectedActors() + { + Actor actor = new EmptyActor(); + Editor.SceneEditing.Spawn(actor, null, false); + List selection = Editor.SceneEditing.Selection; + var actors = selection.Where(x => x is ActorNode).Select(x => ((ActorNode)x).Actor); + using (new UndoMultiBlock(Undo, actors, "Reparent actors")) + { + for (int i = 0; i < selection.Count; i++) + { + if (selection[i] is ActorNode node) + { + if (node.ParentNode != node.ParentScene) // If parent node is not a scene + { + if (selection.Contains(node.ParentNode)) + { + continue; // If parent and child nodes selected together, don't touch child nodes + } + + // Put created node as child of the Parent Node of node + int parentOrder = node.Actor.OrderInParent; + actor.Parent = node.Actor.Parent; + actor.OrderInParent = parentOrder; + } + node.Actor.Parent = actor; + } + } + } + Editor.SceneEditing.Select(actor); + Editor.Scene.GetActorNode(actor).TreeNode.StartRenaming(Editor.Windows.SceneWin, Editor.Windows.SceneWin.SceneTreePanel); + } + /// /// Duplicates the selected objects. Supports undo/redo. /// diff --git a/Source/Editor/Modules/UIModule.cs b/Source/Editor/Modules/UIModule.cs index 3386d411c..e71c93dcc 100644 --- a/Source/Editor/Modules/UIModule.cs +++ b/Source/Editor/Modules/UIModule.cs @@ -50,6 +50,7 @@ namespace FlaxEditor.Modules private ContextMenuButton _menuEditCut; private ContextMenuButton _menuEditCopy; private ContextMenuButton _menuEditPaste; + private ContextMenuButton _menuCreateParentForSelectedActors; private ContextMenuButton _menuEditDelete; private ContextMenuButton _menuEditDuplicate; private ContextMenuButton _menuEditSelectAll; @@ -535,6 +536,7 @@ namespace FlaxEditor.Modules _menuFileRecompileScripts = cm.AddButton("Recompile scripts", inputOptions.RecompileScripts, ScriptsBuilder.Compile); cm.AddSeparator(); cm.AddButton("Open project...", OpenProject); + cm.AddButton("Reload project", ReloadProject); cm.AddSeparator(); cm.AddButton("Exit", "Alt+F4", () => Editor.Windows.MainWindow.Close(ClosingReason.User)); @@ -548,11 +550,11 @@ namespace FlaxEditor.Modules _menuEditCut = cm.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); _menuEditCopy = cm.AddButton("Copy", inputOptions.Copy, Editor.SceneEditing.Copy); _menuEditPaste = cm.AddButton("Paste", inputOptions.Paste, Editor.SceneEditing.Paste); - cm.AddSeparator(); _menuEditDelete = cm.AddButton("Delete", inputOptions.Delete, Editor.SceneEditing.Delete); _menuEditDuplicate = cm.AddButton("Duplicate", inputOptions.Duplicate, Editor.SceneEditing.Duplicate); cm.AddSeparator(); _menuEditSelectAll = cm.AddButton("Select all", inputOptions.SelectAll, Editor.SceneEditing.SelectAllScenes); + _menuCreateParentForSelectedActors = cm.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); _menuEditFind = cm.AddButton("Find", inputOptions.Search, Editor.Windows.SceneWin.Search); cm.AddSeparator(); cm.AddButton("Game Settings", () => @@ -822,6 +824,13 @@ namespace FlaxEditor.Modules } } + private void ReloadProject() + { + // Open project, then close it + Editor.OpenProject(Editor.GameProject.ProjectPath); + Editor.Windows.MainWindow.Close(ClosingReason.User); + } + private void OnMenuFileShowHide(Control control) { if (control.Visible == false) @@ -858,6 +867,7 @@ namespace FlaxEditor.Modules _menuEditCut.Enabled = hasSthSelected; _menuEditCopy.Enabled = hasSthSelected; _menuEditPaste.Enabled = canEditScene; + _menuCreateParentForSelectedActors.Enabled = canEditScene && hasSthSelected; _menuEditDelete.Enabled = hasSthSelected; _menuEditDuplicate.Enabled = hasSthSelected; _menuEditSelectAll.Enabled = Level.IsAnySceneLoaded; diff --git a/Source/Editor/Modules/WindowsModule.cs b/Source/Editor/Modules/WindowsModule.cs index a935e73f2..cf2715b48 100644 --- a/Source/Editor/Modules/WindowsModule.cs +++ b/Source/Editor/Modules/WindowsModule.cs @@ -725,9 +725,7 @@ namespace FlaxEditor.Modules for (int i = 0; i < Windows.Count; i++) { if (string.Equals(Windows[i].SerializationTypename, typename, StringComparison.OrdinalIgnoreCase)) - { return Windows[i]; - } } // Check if it's an asset ID diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index 95a273f19..c33cd3674 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -172,9 +172,9 @@ namespace FlaxEditor.Options set { if (value == null) - _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + _outputLogFont = new FontReference(ConsoleFont, 10); else if (!value.Font) - _outputLogFont.Font = FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + _outputLogFont.Font = ConsoleFont; else _outputLogFont = value; } @@ -237,11 +237,19 @@ namespace FlaxEditor.Options public int NumberOfGameClientsToLaunch = 1; private static FontAsset DefaultFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.PrimaryFont); + private static FontAsset ConsoleFont => FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont); + private FontReference _titleFont = new FontReference(DefaultFont, 18); private FontReference _largeFont = new FontReference(DefaultFont, 14); private FontReference _mediumFont = new FontReference(DefaultFont, 9); private FontReference _smallFont = new FontReference(DefaultFont, 9); - private FontReference _outputLogFont = new FontReference(FlaxEngine.Content.LoadAsyncInternal(EditorAssets.InconsolataRegularFont), 10); + private FontReference _outputLogFont = new FontReference(ConsoleFont, 10); + + /// + /// The list of fallback fonts to use when main text font is missing certain characters. Empty to use fonts from GraphicsSettings. + /// + [EditorDisplay("Fonts"), EditorOrder(650)] + public FontAsset[] FallbackFonts = new FontAsset[1] { FlaxEngine.Content.LoadAsyncInternal(EditorAssets.FallbackFont) }; /// /// Gets or sets the title font for editor UI. diff --git a/Source/Editor/Options/OptionsModule.cs b/Source/Editor/Options/OptionsModule.cs index 1137e4c37..bb90be6d9 100644 --- a/Source/Editor/Options/OptionsModule.cs +++ b/Source/Editor/Options/OptionsModule.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using FlaxEditor.Content.Settings; using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.GUI; @@ -217,12 +219,18 @@ namespace FlaxEditor.Options if (styleName == ThemeOptions.LightDefault) { Style.Current = CreateLightStyle(); - } + } else { Style.Current = CreateDefaultStyle(); } } + + // Set fallback fonts + var fallbackFonts = Options.Interface.FallbackFonts; + if (fallbackFonts == null || fallbackFonts.Length == 0 || fallbackFonts.All(x => x == null)) + fallbackFonts = GameSettings.Load().FallbackFonts; + Font.FallbackFonts = fallbackFonts; } /// diff --git a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs index 5363d1f55..1eabc946d 100644 --- a/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/DirectionalLightNode.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors base.OnDebugDraw(data); var transform = Actor.Transform; - DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, Color.Red, 0.0f, false); + DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 1.0f, 0.5f, Color.Red, 0.0f, false); } } } diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index acd23130d..e6ff43e9f 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -7,11 +7,14 @@ using Real = System.Single; #endif using System; -using FlaxEditor.GUI.ContextMenu; +using System.Collections.Generic; +using FlaxEditor.Actions; using FlaxEditor.Modules; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.Json; +using FlaxEngine.Utilities; using Object = FlaxEngine.Object; namespace FlaxEditor.SceneGraph.Actors @@ -288,6 +291,8 @@ namespace FlaxEditor.SceneGraph.Actors private const Real PointNodeSize = 1.5f; private const Real TangentNodeSize = 1.0f; + private const Real SnapIndicatorSize = 1.7f; + private const Real SnapPointIndicatorSize = 2f; /// public SplineNode(Actor actor) @@ -297,9 +302,26 @@ namespace FlaxEditor.SceneGraph.Actors FlaxEngine.Scripting.Update += OnUpdate; } - private unsafe void OnUpdate() + private void OnUpdate() + { + // If this node's point is selected + var selection = Editor.Instance.SceneEditing.Selection; + if (selection.Count == 1 && selection[0] is SplinePointNode selectedPoint && selectedPoint.ParentNode == this) + { + if (Input.Keyboard.GetKey(KeyboardKeys.Shift)) + EditSplineWithSnap(selectedPoint); + + var canAddSplinePoint = Input.Mouse.PositionDelta == Float2.Zero && Input.Mouse.Position != Float2.Zero; + var requestAddSplinePoint = Input.Keyboard.GetKey(KeyboardKeys.Control) && Input.Mouse.GetButtonDown(MouseButton.Right); + if (requestAddSplinePoint && canAddSplinePoint) + AddSplinePoint(selectedPoint); + } + + SyncSplineKeyframeWithNodes(); + } + + private unsafe void SyncSplineKeyframeWithNodes() { - // Sync spline points with gizmo handles var actor = (Spline)Actor; var dstCount = actor.SplinePointsCount; if (dstCount > 1 && actor.IsLoop) @@ -329,6 +351,119 @@ namespace FlaxEditor.SceneGraph.Actors } } + private unsafe void AddSplinePoint(SplinePointNode selectedPoint) + { + // Check mouse hit on scene + var spline = (Spline)Actor; + var viewport = Editor.Instance.Windows.EditWin.Viewport; + var mouseRay = viewport.MouseRay; + var viewRay = viewport.ViewRay; + var flags = RayCastData.FlagTypes.SkipColliders | RayCastData.FlagTypes.SkipEditorPrimitives; + var hit = Editor.Instance.Scene.Root.RayCast(ref mouseRay, ref viewRay, out var closest, out var normal, flags); + if (hit == null) + return; + + // Undo data + var oldSpline = spline.SplineKeyframes; + var editAction = new EditSplineAction(spline, oldSpline); + Root.Undo.AddAction(editAction); + + // Get spline point to duplicate + var hitPoint = mouseRay.Position + mouseRay.Direction * closest; + var lastPointIndex = selectedPoint.Index; + var newPointIndex = lastPointIndex > 0 ? lastPointIndex + 1 : 0; + var lastKeyframe = spline.GetSplineKeyframe(lastPointIndex); + var isLastPoint = lastPointIndex == spline.SplinePointsCount - 1; + var isFirstPoint = lastPointIndex == 0; + + // Get data to create new point + var lastPointTime = spline.GetSplineTime(lastPointIndex); + var nextPointTime = isLastPoint ? lastPointTime : spline.GetSplineTime(newPointIndex); + var newTime = isLastPoint ? lastPointTime + 1.0f : (lastPointTime + nextPointTime) * 0.5f; + var distanceFromLastPoint = Vector3.Distance(hitPoint, spline.GetSplinePoint(lastPointIndex)); + var newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint; + + // Set correctly keyframe direction on spawn point + if (isFirstPoint) + newPointDirection = hitPoint - spline.GetSplineTangent(lastPointIndex, true).Translation; + else if (isLastPoint) + newPointDirection = spline.GetSplineTangent(lastPointIndex, false).Translation - hitPoint; + var newPointLocalPosition = spline.Transform.WorldToLocal(hitPoint); + var newPointLocalOrientation = Quaternion.LookRotation(newPointDirection); + + // Add new point + spline.InsertSplinePoint(newPointIndex, newTime, Transform.Identity, false); + var newKeyframe = lastKeyframe.DeepClone(); + var newKeyframeTransform = newKeyframe.Value; + newKeyframeTransform.Translation = newPointLocalPosition; + newKeyframeTransform.Orientation = newPointLocalOrientation; + newKeyframe.Value = newKeyframeTransform; + + // Set new point keyframe + var newKeyframeTangentIn = Transform.Identity; + var newKeyframeTangentOut = Transform.Identity; + newKeyframeTangentIn.Translation = (Vector3.Forward * newPointLocalOrientation) * distanceFromLastPoint; + newKeyframeTangentOut.Translation = (Vector3.Backward * newPointLocalOrientation) * distanceFromLastPoint; + newKeyframe.TangentIn = newKeyframeTangentIn; + newKeyframe.TangentOut = newKeyframeTangentOut; + spline.SetSplineKeyframe(newPointIndex, newKeyframe); + + for (int i = 1; i < spline.SplinePointsCount; i++) + { + // check all elements to don't left keyframe has invalid time + // because points can be added on start or on middle of spline + // conflicting with time of another keyframes + spline.SetSplinePointTime(i, i, false); + } + + // Select new point node + SyncSplineKeyframeWithNodes(); + Editor.Instance.SceneEditing.Select(ChildNodes[newPointIndex]); + + spline.UpdateSpline(); + } + + private void EditSplineWithSnap(SplinePointNode selectedPoint) + { + var spline = (Spline)Actor; + var selectedPointBounds = new BoundingSphere(selectedPoint.Transform.Translation, 1f); + var allSplinesInView = GetSplinesInView(); + allSplinesInView.Remove(spline); + if (allSplinesInView.Count == 0) + return; + + var snappedOnSplinePoint = false; + for (int i = 0; i < allSplinesInView.Count; i++) + { + for (int x = 0; x < allSplinesInView[i].SplineKeyframes.Length; x++) + { + var keyframePosition = allSplinesInView[i].GetSplinePoint(x); + var pointIndicatorSize = NodeSizeByDistance(keyframePosition, SnapPointIndicatorSize); + var keyframeBounds = new BoundingSphere(keyframePosition, pointIndicatorSize); + DebugDraw.DrawSphere(keyframeBounds, Color.Red, 0, false); + + if (keyframeBounds.Intersects(selectedPointBounds)) + { + spline.SetSplinePoint(selectedPoint.Index, keyframeBounds.Center); + snappedOnSplinePoint = true; + break; + } + } + } + + if (!snappedOnSplinePoint) + { + var nearSplineSnapPoint = GetNearSplineSnapPosition(selectedPoint.Transform.Translation, allSplinesInView); + var snapIndicatorSize = NodeSizeByDistance(nearSplineSnapPoint, SnapIndicatorSize); + var snapBounds = new BoundingSphere(nearSplineSnapPoint, snapIndicatorSize); + if (snapBounds.Intersects(selectedPointBounds)) + { + spline.SetSplinePoint(selectedPoint.Index, snapBounds.Center); + } + DebugDraw.DrawSphere(snapBounds, Color.Yellow, 0, true); + } + } + /// public override void PostSpawn() { @@ -343,14 +478,12 @@ namespace FlaxEditor.SceneGraph.Actors var spline = (Spline)Actor; spline.AddSplineLocalPoint(Vector3.Zero, false); spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f)); - spline.SetSplineKeyframe(0, new BezierCurve.Keyframe() { Value = new Transform(Vector3.Zero, Quaternion.Identity, Vector3.One), TangentIn = new Transform(Vector3.Backward * 100, Quaternion.Identity, Vector3.One), TangentOut = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One), }); - spline.SetSplineKeyframe(1, new BezierCurve.Keyframe() { Value = new Transform(Vector3.Forward * 100, Quaternion.Identity, Vector3.One), @@ -413,6 +546,39 @@ namespace FlaxEditor.SceneGraph.Actors } } + private static List GetSplinesInView() + { + var splines = Level.GetActors(true); + var result = new List(); + var viewBounds = Editor.Instance.Windows.EditWin.Viewport.ViewFrustum; + foreach (var s in splines) + { + var contains = viewBounds.Contains(s.EditorBox); + if (contains == ContainmentType.Contains || contains == ContainmentType.Intersects) + result.Add(s); + } + return result; + } + + private static Vector3 GetNearSplineSnapPosition(Vector3 position, List splines) + { + var nearPoint = splines[0].GetSplinePointClosestToPoint(position); + var nearDistance = Vector3.Distance(nearPoint, position); + + for (int i = 1; i < splines.Count; i++) + { + var point = splines[i].GetSplinePointClosestToPoint(position); + var distance = Vector3.Distance(point, position); + if (distance < nearDistance) + { + nearPoint = point; + nearDistance = distance; + } + } + + return nearPoint; + } + internal static Real NodeSizeByDistance(Vector3 nodePosition, Real nodeSize) { var cameraTransform = Editor.Instance.Windows.EditWin.Viewport.ViewportCamera.Viewport.ViewTransform; diff --git a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs index b961ce205..567e8e430 100644 --- a/Source/Editor/SceneGraph/Actors/SpotLightNode.cs +++ b/Source/Editor/SceneGraph/Actors/SpotLightNode.cs @@ -23,7 +23,7 @@ namespace FlaxEditor.SceneGraph.Actors base.OnDebugDraw(data); var transform = Actor.Transform; - DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, Color.Red, 0.0f, false); + DebugDraw.DrawWireArrow(transform.Translation, transform.Orientation, 0.3f, 0.15f, Color.Red, 0.0f, false); } } } diff --git a/Source/Editor/Surface/AnimGraphSurface.cs b/Source/Editor/Surface/AnimGraphSurface.cs index ccd78412a..56764bafd 100644 --- a/Source/Editor/Surface/AnimGraphSurface.cs +++ b/Source/Editor/Surface/AnimGraphSurface.cs @@ -115,16 +115,31 @@ namespace FlaxEditor.Surface internal AnimGraphTraceEvent[] LastTraceEvents; - internal bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent) + internal unsafe bool TryGetTraceEvent(SurfaceNode node, out AnimGraphTraceEvent traceEvent) { if (LastTraceEvents != null) { foreach (var e in LastTraceEvents) { + // Node IDs must match if (e.NodeId == node.ID) { - traceEvent = e; - return true; + uint* nodePath = e.NodePath0; + + // Get size of the path + int nodePathSize = 0; + while (nodePathSize < 8 && nodePath[nodePathSize] != 0) + nodePathSize++; + + // Follow input node contexts path to verify if it matches with the path in the event + var c = node.Context; + for (int i = nodePathSize - 1; i >= 0 && c != null; i--) + c = c.OwnerNodeID == nodePath[i] ? c.Parent : null; + if (c != null) + { + traceEvent = e; + return true; + } } } } diff --git a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs index 9ec1bab19..f96ac8e4b 100644 --- a/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs +++ b/Source/Editor/Surface/Archetypes/Animation.StateMachine.cs @@ -191,9 +191,7 @@ namespace FlaxEditor.Surface.Archetypes var value = title; int count = 1; while (!OnRenameValidate(null, value)) - { value = title + " " + count++; - } Values[0] = value; Title = value; @@ -484,7 +482,7 @@ namespace FlaxEditor.Surface.Archetypes var startPos = PointToParent(ref center); targetState.GetConnectionEndPoint(ref startPos, out var endPos); var color = style.Foreground; - StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } } @@ -514,7 +512,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - StateMachineState.DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } /// @@ -655,6 +653,7 @@ namespace FlaxEditor.Surface.Archetypes protected Rectangle _renameButtonRect; private bool _cursorChanged = false; private bool _textRectHovered = false; + private bool _debugActive; /// /// The transitions list from this state to the others. @@ -677,38 +676,6 @@ namespace FlaxEditor.Surface.Archetypes { } - /// - /// Draws the connection between two state machine nodes. - /// - /// The start position. - /// The end position. - /// The line color. - public static void DrawConnection(ref Float2 startPos, ref Float2 endPos, ref Color color) - { - var sub = endPos - startPos; - var length = sub.Length; - if (length > Mathf.Epsilon) - { - var dir = sub / length; - var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f); - float rotation = Float2.Dot(dir, Float2.UnitY); - if (endPos.X < startPos.X) - rotation = 2 - rotation; - var sprite = Editor.Instance.Icons.VisjectArrowClosed32; - var arrowTransform = - Matrix3x3.Translation2D(-6.5f, -8) * - Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * - Matrix3x3.Translation2D(endPos - dir * 8); - - Render2D.PushTransform(ref arrowTransform); - Render2D.DrawSprite(sprite, arrowRect, color); - Render2D.PopTransform(); - - endPos -= dir * 4.0f; - } - Render2D.DrawLine(startPos, endPos, color); - } - /// /// Gets the connection end point for the given input position. Puts the end point near the edge of the node bounds. /// @@ -1092,6 +1059,16 @@ namespace FlaxEditor.Surface.Archetypes // TODO: maybe update only on actual transitions change? UpdateTransitions(); + + // Debug current state + if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent)) + { + _debugActive = true; + } + else + { + _debugActive = false; + } } /// @@ -1132,6 +1109,10 @@ namespace FlaxEditor.Surface.Archetypes // Close button Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey); + + // Debug outline + if (_debugActive) + Render2D.DrawRectangle(_textRect.MakeExpanded(1.0f), style.ProgressNormal); } /// @@ -1295,7 +1276,7 @@ namespace FlaxEditor.Surface.Archetypes isMouseOver = Float2.DistanceSquared(ref mousePosition, ref point) < 25.0f; } var color = isMouseOver ? Color.Wheat : t.LineColor; - DrawConnection(ref t.StartPos, ref t.EndPos, ref color); + SurfaceStyle.DrawStraightConnection(t.StartPos, t.EndPos, color); } } @@ -1324,7 +1305,7 @@ namespace FlaxEditor.Surface.Archetypes /// public void DrawConnectingLine(ref Float2 startPos, ref Float2 endPos, ref Color color) { - DrawConnection(ref startPos, ref endPos, ref color); + SurfaceStyle.DrawStraightConnection(startPos, endPos, color); } /// diff --git a/Source/Editor/Surface/BehaviorTreeSurface.cs b/Source/Editor/Surface/BehaviorTreeSurface.cs index 9b201d3f8..760110769 100644 --- a/Source/Editor/Surface/BehaviorTreeSurface.cs +++ b/Source/Editor/Surface/BehaviorTreeSurface.cs @@ -31,7 +31,7 @@ namespace FlaxEditor.Surface var editor = Editor.Instance; var style = SurfaceStyle.CreateStyleHandler(editor); style.DrawBox = DrawBox; - style.DrawConnection = DrawConnection; + style.DrawConnection = SurfaceStyle.DrawStraightConnection; return style; } @@ -49,11 +49,6 @@ namespace FlaxEditor.Surface Render2D.FillRectangle(rect, color); } - private static void DrawConnection(Float2 start, Float2 end, Color color, float thickness) - { - Archetypes.Animation.StateMachineStateBase.DrawConnection(ref start, ref end, ref color); - } - private void OnActiveContextMenuVisibleChanged(Control activeCM) { _nodesCache.Wait(); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCM.cs b/Source/Editor/Surface/ContextMenu/VisjectCM.cs index 930741807..ad4bf8372 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCM.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCM.cs @@ -492,8 +492,7 @@ namespace FlaxEditor.Surface.ContextMenu // If no item is selected (or it's not visible anymore), select the top one Profiler.BeginEvent("VisjectCM.Layout"); - if (SelectedItem == null || !SelectedItem.VisibleInHierarchy) - SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem; + SelectedItem = _groups.Find(g => g.Visible)?.Children.Find(c => c.Visible && c is VisjectCMItem) as VisjectCMItem; PerformLayout(); if (SelectedItem != null) _panel1.ScrollViewTo(SelectedItem); @@ -704,7 +703,7 @@ namespace FlaxEditor.Surface.ContextMenu Hide(); return true; } - else if (key == KeyboardKeys.Return) + else if (key == KeyboardKeys.Return || key == KeyboardKeys.Tab) { if (SelectedItem != null) OnClickItem(SelectedItem); diff --git a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs index 207875a92..9adfb881e 100644 --- a/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs +++ b/Source/Editor/Surface/ContextMenu/VisjectCMItem.cs @@ -73,8 +73,6 @@ namespace FlaxEditor.Surface.ContextMenu { SortScore = 0; - if (!(_highlights?.Count > 0)) - return; if (!Visible) return; @@ -83,6 +81,8 @@ namespace FlaxEditor.Surface.ContextMenu SortScore += 1; if (Data != null) SortScore += 1; + if (_highlights is { Count: > 0 }) + SortScore += 1; if (_isStartsWithMatch) SortScore += 2; if (_isFullMatch) @@ -169,6 +169,7 @@ namespace FlaxEditor.Surface.ContextMenu /// True if item's group header got a filter match and item should stay visible. public void UpdateFilter(string filterText, Box selectedBox, bool groupHeaderMatches = false) { + // When dragging connection out of a box, validate if the box is compatible with this item's type if (selectedBox != null) { Visible = CanConnectTo(selectedBox); @@ -185,72 +186,87 @@ namespace FlaxEditor.Surface.ContextMenu // Clear filter _highlights?.Clear(); Visible = true; + return; } - else + + GetTextRectangle(out var textRect); + + // Check archetype title + if (QueryFilterHelper.Match(filterText, _archetype.Title, out var ranges)) { - GetTextRectangle(out var textRect); - if (QueryFilterHelper.Match(filterText, _archetype.Title, out var ranges)) + // Update highlights + if (_highlights == null) + _highlights = new List(ranges.Length); + else + _highlights.Clear(); + var style = Style.Current; + var font = style.FontSmall; + for (int i = 0; i < ranges.Length; i++) { - // Update highlights - if (_highlights == null) - _highlights = new List(ranges.Length); - else - _highlights.Clear(); - var style = Style.Current; - var font = style.FontSmall; - for (int i = 0; i < ranges.Length; i++) + var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); + var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); + _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); + + if (ranges[i].StartIndex <= 0) { - var start = font.GetCharPosition(_archetype.Title, ranges[i].StartIndex); - var end = font.GetCharPosition(_archetype.Title, ranges[i].EndIndex); - _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); - - if (ranges[i].StartIndex <= 0) - { - _isStartsWithMatch = true; - if (ranges[i].Length == _archetype.Title.Length) - _isFullMatch = true; - } + _isStartsWithMatch = true; + if (ranges[i].Length == _archetype.Title.Length) + _isFullMatch = true; } - Visible = true; - } - else if (_archetype.AlternativeTitles?.Any(altTitle => string.Equals(filterText, altTitle, StringComparison.CurrentCultureIgnoreCase)) == true) - { - // Update highlights - if (_highlights == null) - _highlights = new List(1); - else - _highlights.Clear(); - var style = Style.Current; - var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); - _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); - _isFullMatch = true; - Visible = true; - } - else if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data)) - { - // Update highlights - if (_highlights == null) - _highlights = new List(1); - else - _highlights.Clear(); - var style = Style.Current; - var font = style.FontSmall; - var start = font.GetCharPosition(_archetype.Title, 0); - var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); - _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); - Visible = true; - - Data = data; - } - else if (!groupHeaderMatches) - { - // Hide - _highlights?.Clear(); - Visible = false; } + Visible = true; + return; } + + // Check archetype synonyms + if (_archetype.AlternativeTitles!= null && _archetype.AlternativeTitles.Any(altTitle => QueryFilterHelper.Match(filterText, altTitle, out ranges))) + { + // Update highlights + if (_highlights == null) + _highlights = new List(1); + else + _highlights.Clear(); + var style = Style.Current; + var font = style.FontSmall; + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); + + for (int i = 0; i < ranges.Length; i++) + { + if (ranges[i].StartIndex <= 0) + { + _isStartsWithMatch = true; + } + } + + Visible = true; + return; + } + + // Check archetype data (if it exists) + if (NodeArchetype.TryParseText != null && NodeArchetype.TryParseText(filterText, out var data)) + { + // Update highlights + if (_highlights == null) + _highlights = new List(1); + else + _highlights.Clear(); + var style = Style.Current; + var font = style.FontSmall; + var start = font.GetCharPosition(_archetype.Title, 0); + var end = font.GetCharPosition(_archetype.Title, _archetype.Title.Length - 1); + _highlights.Add(new Rectangle(start.X + textRect.X, 0, end.X - start.X, Height)); + Visible = true; + Data = data; + return; + } + + _highlights?.Clear(); + + // Hide + if (!groupHeaderMatches) + Visible = false; } /// diff --git a/Source/Editor/Surface/SurfaceStyle.cs b/Source/Editor/Surface/SurfaceStyle.cs index 2b8e97f62..5a67d6fb2 100644 --- a/Source/Editor/Surface/SurfaceStyle.cs +++ b/Source/Editor/Surface/SurfaceStyle.cs @@ -295,5 +295,38 @@ namespace FlaxEditor.Surface Background = editor.UI.VisjectSurfaceBackground, }; } + + /// + /// Draws a simple straight connection between two locations. + /// + /// The start position. + /// The end position. + /// The line color. + /// The line thickness. + public static void DrawStraightConnection(Float2 startPos, Float2 endPos, Color color, float thickness = 1.0f) + { + var sub = endPos - startPos; + var length = sub.Length; + if (length > Mathf.Epsilon) + { + var dir = sub / length; + var arrowRect = new Rectangle(0, 0, 16.0f, 16.0f); + float rotation = Float2.Dot(dir, Float2.UnitY); + if (endPos.X < startPos.X) + rotation = 2 - rotation; + var sprite = Editor.Instance.Icons.VisjectArrowClosed32; + var arrowTransform = + Matrix3x3.Translation2D(-6.5f, -8) * + Matrix3x3.RotationZ(rotation * Mathf.PiOverTwo) * + Matrix3x3.Translation2D(endPos - dir * 8); + + Render2D.PushTransform(ref arrowTransform); + Render2D.DrawSprite(sprite, arrowRect, color); + Render2D.PopTransform(); + + endPos -= dir * 4.0f; + } + Render2D.DrawLine(startPos, endPos, color); + } } } diff --git a/Source/Editor/Surface/VisjectSurface.Context.cs b/Source/Editor/Surface/VisjectSurface.Context.cs index 1d8b97729..0cb8e3ead 100644 --- a/Source/Editor/Surface/VisjectSurface.Context.cs +++ b/Source/Editor/Surface/VisjectSurface.Context.cs @@ -33,6 +33,30 @@ namespace FlaxEditor.Surface /// public event Action ContextChanged; + /// + /// Finds the surface context with the given owning nodes IDs path. + /// + /// The node ids path. + /// Found context or null if cannot. + public VisjectSurfaceContext FindContext(Span nodePath) + { + // Get size of the path + int nodePathSize = 0; + while (nodePathSize < nodePath.Length && nodePath[nodePathSize] != 0) + nodePathSize++; + + // Follow each context path to verify if it matches with the path in the input path + foreach (var e in _contextCache) + { + var c = e.Value; + for (int i = nodePathSize - 1; i >= 0 && c != null; i--) + c = c.OwnerNodeID == nodePath[i] ? c.Parent : null; + if (c != null) + return e.Value; + } + return null; + } + /// /// Creates the Visject surface context for the given surface data source context. /// @@ -62,6 +86,8 @@ namespace FlaxEditor.Surface surfaceContext = CreateContext(_context, context); _context?.Children.Add(surfaceContext); _contextCache.Add(contextHandle, surfaceContext); + if (context is SurfaceNode asNode) + surfaceContext.OwnerNodeID = asNode.ID; context.OnContextCreated(surfaceContext); diff --git a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs index 3ca7ddfc7..51339d6bc 100644 --- a/Source/Editor/Surface/VisjectSurface.ContextMenu.cs +++ b/Source/Editor/Surface/VisjectSurface.ContextMenu.cs @@ -20,8 +20,15 @@ namespace FlaxEditor.Surface /// /// Utility for easy nodes archetypes generation for Visject Surface based on scripting types. /// - internal class NodesCache + [HideInEditor] + public class NodesCache { + /// + /// Delegate for scripting types filtering into cache. + /// + /// The input type to process. + /// Node groups cache that can be used for reusing groups for different nodes. + /// The cache version number. Can be used to reject any cached data after rebuilt. public delegate void IterateType(ScriptType scriptType, Dictionary, GroupArchetype> cache, int version); internal static readonly List Caches = new List(8); @@ -33,11 +40,18 @@ namespace FlaxEditor.Surface private VisjectCM _taskContextMenu; private Dictionary, GroupArchetype> _cache; + /// + /// Initializes a new instance of the class. + /// + /// The iterator callback to build node types from Scripting. public NodesCache(IterateType iterator) { _iterator = iterator; } + /// + /// Waits for the async caching job to finish. + /// public void Wait() { if (_task != null) @@ -48,6 +62,9 @@ namespace FlaxEditor.Surface } } + /// + /// Clears cache. + /// public void Clear() { Wait(); @@ -62,6 +79,10 @@ namespace FlaxEditor.Surface } } + /// + /// Updates the Visject Context Menu to contain current nodes. + /// + /// The output context menu to setup. public void Get(VisjectCM contextMenu) { Profiler.BeginEvent("Setup Context Menu"); diff --git a/Source/Editor/Surface/VisjectSurfaceContext.cs b/Source/Editor/Surface/VisjectSurfaceContext.cs index 0886996b6..9902f224a 100644 --- a/Source/Editor/Surface/VisjectSurfaceContext.cs +++ b/Source/Editor/Surface/VisjectSurfaceContext.cs @@ -156,6 +156,11 @@ namespace FlaxEditor.Surface /// public event Action ControlDeleted; + /// + /// Identifier of the node that 'owns' this context (eg. State Machine which created this graph of state nodes). + /// + public uint OwnerNodeID; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Editor/Surface/VisjectSurfaceWindow.cs b/Source/Editor/Surface/VisjectSurfaceWindow.cs index cce86d5c0..6a9cba841 100644 --- a/Source/Editor/Surface/VisjectSurfaceWindow.cs +++ b/Source/Editor/Surface/VisjectSurfaceWindow.cs @@ -25,7 +25,7 @@ namespace FlaxEditor.Surface /// The base interface for editor windows that use for content editing. /// /// - interface IVisjectSurfaceWindow : IVisjectSurfaceOwner + public interface IVisjectSurfaceWindow : IVisjectSurfaceOwner { /// /// Gets the asset edited by the window. diff --git a/Source/Editor/Tools/ClothPainting.cs b/Source/Editor/Tools/ClothPainting.cs index 28536ea83..6a98a3b5f 100644 --- a/Source/Editor/Tools/ClothPainting.cs +++ b/Source/Editor/Tools/ClothPainting.cs @@ -43,6 +43,19 @@ namespace FlaxEngine.Tools /// Enables continuous painting, otherwise single paint on click. /// public bool ContinuousPaint; + + /// + /// Enables drawing cloth paint debugging with Depth Test enabled (skips occluded vertices). + /// + public bool DebugDrawDepthTest + { + get => Gizmo.Cloth?.DebugDrawDepthTest ?? true; + set + { + if (Gizmo.Cloth != null) + Gizmo.Cloth.DebugDrawDepthTest = value; + } + } #pragma warning restore CS0649 public override void Init(IGizmoOwner owner) @@ -62,6 +75,7 @@ namespace FlaxEngine.Tools public override void Dispose() { Owner.Gizmos.Remove(Gizmo); + Gizmo = null; base.Dispose(); } @@ -83,6 +97,7 @@ namespace FlaxEngine.Tools private EditClothPaintAction _undoAction; public bool IsPainting => _isPainting; + public Cloth Cloth => _cloth; public ClothPaintingGizmo(IGizmoOwner owner, ClothPaintingGizmoMode mode) : base(owner) diff --git a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs index 252891d44..fd28c302b 100644 --- a/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs +++ b/Source/Editor/Tools/Terrain/CreateTerrainDialog.cs @@ -45,9 +45,6 @@ namespace FlaxEditor.Tools.Terrain [EditorOrder(130), EditorDisplay("Layout"), DefaultValue(null), Tooltip("The default material used for terrain rendering (chunks can override this). It must have Domain set to terrain.")] public MaterialBase Material; - [EditorOrder(200), EditorDisplay("Collision"), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), Tooltip("Terrain default physical material used to define the collider physical properties.")] - public JsonAsset PhysicalMaterial; - [EditorOrder(210), EditorDisplay("Collision", "Collision LOD"), DefaultValue(-1), Limit(-1, 100, 0.1f), Tooltip("Terrain geometry LOD index used for collision.")] public int CollisionLOD = -1; @@ -152,7 +149,6 @@ namespace FlaxEditor.Tools.Terrain terrain.Setup(_options.LODCount, (int)_options.ChunkSize); terrain.Transform = new Transform(_options.Position, _options.Orientation, _options.Scale); terrain.Material = _options.Material; - terrain.PhysicalMaterial = _options.PhysicalMaterial; terrain.CollisionLOD = _options.CollisionLOD; if (_options.Heightmap) terrain.Position -= new Vector3(0, _options.HeightmapScale * 0.5f, 0); diff --git a/Source/Editor/Tools/Terrain/Paint/Mode.cs b/Source/Editor/Tools/Terrain/Paint/Mode.cs index 3647691b3..03e4a4690 100644 --- a/Source/Editor/Tools/Terrain/Paint/Mode.cs +++ b/Source/Editor/Tools/Terrain/Paint/Mode.cs @@ -67,11 +67,13 @@ namespace FlaxEditor.Tools.Terrain.Paint // Prepare var splatmapIndex = ActiveSplatmapIndex; + var splatmapIndexOther = (splatmapIndex + 1) % 2; var chunkSize = terrain.ChunkSize; var heightmapSize = chunkSize * FlaxEngine.Terrain.PatchEdgeChunksCount + 1; var heightmapLength = heightmapSize * heightmapSize; var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount; - var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes).ToPointer(); + var tempBuffer = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, splatmapIndex).ToPointer(); + var tempBufferOther = (Color32*)gizmo.GetSplatmapTempBuffer(heightmapLength * Color32.SizeInBytes, (splatmapIndex + 1) % 2).ToPointer(); var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex; ApplyParams p = new ApplyParams { @@ -81,8 +83,10 @@ namespace FlaxEditor.Tools.Terrain.Paint Options = options, Strength = strength, SplatmapIndex = splatmapIndex, + SplatmapIndexOther = splatmapIndexOther, HeightmapSize = heightmapSize, TempBuffer = tempBuffer, + TempBufferOther = tempBufferOther, }; // Get brush bounds in terrain local space @@ -131,11 +135,16 @@ namespace FlaxEditor.Tools.Terrain.Paint var sourceData = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndex); if (sourceData == null) throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); + + var sourceDataOther = TerrainTools.GetSplatMapData(terrain, ref patch.PatchCoord, splatmapIndexOther); + if (sourceDataOther == null) + throw new Exception("Cannot modify terrain. Loading splatmap failed. See log for more info."); // Record patch data before editing it if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord)) { gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndex); + gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord, splatmapIndexOther); } // Apply modification @@ -144,6 +153,7 @@ namespace FlaxEditor.Tools.Terrain.Paint p.PatchCoord = patch.PatchCoord; p.PatchPositionLocal = patchPositionLocal; p.SourceData = sourceData; + p.SourceDataOther = sourceDataOther; Apply(ref p); } } @@ -197,16 +207,32 @@ namespace FlaxEditor.Tools.Terrain.Paint /// The splatmap texture index. /// public int SplatmapIndex; + + /// + /// The splatmap texture index. If is 0, this will be 1. If is 1, this will be 0. + /// + public int SplatmapIndexOther; /// /// The temporary data buffer (for modified data). /// public Color32* TempBuffer; + + /// + /// The 'other' temporary data buffer (for modified data). If refersto the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* TempBufferOther; /// /// The source data buffer. /// public Color32* SourceData; + + /// + /// The 'other' source data buffer. If refers + /// to the splatmap with index 0, this one will refer to the one with index 1. + /// + public Color32* SourceDataOther; /// /// The heightmap size (edge). diff --git a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs index ec41b5286..5921f7d10 100644 --- a/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs +++ b/Source/Editor/Tools/Terrain/Paint/SingleLayerMode.cs @@ -1,6 +1,5 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. -using System; using FlaxEngine; namespace FlaxEditor.Tools.Terrain.Paint @@ -73,53 +72,53 @@ namespace FlaxEditor.Tools.Terrain.Paint var strength = p.Strength; var layer = (int)Layer; var brushPosition = p.Gizmo.CursorPosition; - var layerComponent = layer % 4; + var c = layer % 4; // Apply brush modification Profiler.BeginEvent("Apply Brush"); + bool otherModified = false; for (int z = 0; z < p.ModifiedSize.Y; z++) { var zz = z + p.ModifiedOffset.Y; for (int x = 0; x < p.ModifiedSize.X; x++) { var xx = x + p.ModifiedOffset.X; - var src = p.SourceData[zz * p.HeightmapSize + xx]; + var src = (Color)p.SourceData[zz * p.HeightmapSize + xx]; var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, 0, zz * FlaxEngine.Terrain.UnitsPerVertex); Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld); + var sample = Mathf.Saturate(p.Brush.Sample(ref brushPosition, ref samplePositionWorld)); - var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength; + var paintAmount = sample * strength; + if (paintAmount < 0.0f) + continue; // Skip when pixel won't be affected - // Extract layer weight - byte* srcPtr = &src.R; - var srcWeight = *(srcPtr + layerComponent) / 255.0f; - - // Accumulate weight - float dstWeight = srcWeight + paintAmount; - - // Check for solid layer case - if (dstWeight >= 1.0f) - { - // Erase other layers - // TODO: maybe erase only the higher layers? - // TODO: need to erase also weights form the other splatmaps - src = Color32.Transparent; - - // Use limit value - dstWeight = 1.0f; - } - - // Modify packed weight - *(srcPtr + layerComponent) = (byte)(dstWeight * 255.0f); - - // Write back + // Paint on the active splatmap texture + src[c] = Mathf.Saturate(src[c] + paintAmount); + src[(c + 1) % 4] = Mathf.Saturate(src[(c + 1) % 4] - paintAmount); + src[(c + 2) % 4] = Mathf.Saturate(src[(c + 2) % 4] - paintAmount); + src[(c + 3) % 4] = Mathf.Saturate(src[(c + 3) % 4] - paintAmount); p.TempBuffer[z * p.ModifiedSize.X + x] = src; + + var other = (Color)p.SourceDataOther[zz * p.HeightmapSize + xx]; + //if (other.ValuesSum > 0.0f) // Skip editing the other splatmap if it's empty + { + // Remove 'paint' from the other splatmap texture + other[c] = Mathf.Saturate(other[c] - paintAmount); + other[(c + 1) % 4] = Mathf.Saturate(other[(c + 1) % 4] - paintAmount); + other[(c + 2) % 4] = Mathf.Saturate(other[(c + 2) % 4] - paintAmount); + other[(c + 3) % 4] = Mathf.Saturate(other[(c + 3) % 4] - paintAmount); + p.TempBufferOther[z * p.ModifiedSize.X + x] = other; + otherModified = true; + } } } Profiler.EndEvent(); // Update terrain patch TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndex, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize); + if (otherModified) + TerrainTools.ModifySplatMap(p.Terrain, ref p.PatchCoord, p.SplatmapIndexOther, p.TempBufferOther, ref p.ModifiedOffset, ref p.ModifiedSize); } } } diff --git a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs index 1d1bf87ca..7d6af355a 100644 --- a/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs +++ b/Source/Editor/Tools/Terrain/PaintTerrainGizmoMode.cs @@ -36,9 +36,35 @@ namespace FlaxEditor.Tools.Terrain "Layer 7", }; - private IntPtr _cachedSplatmapData; - private int _cachedSplatmapDataSize; + private struct SplatmapData + { + public IntPtr DataPtr; + public int Size; + + public void EnsureCapacity(int size) + { + if (Size < size) + { + if (DataPtr != IntPtr.Zero) + Marshal.FreeHGlobal(DataPtr); + DataPtr = Marshal.AllocHGlobal(size); + Utils.MemoryClear(DataPtr, (ulong)size); + Size = size; + } + } + + public void Free() + { + if (DataPtr == IntPtr.Zero) + return; + Marshal.FreeHGlobal(DataPtr); + DataPtr = IntPtr.Zero; + Size = 0; + } + } + private EditTerrainMapAction _activeAction; + private SplatmapData[] _cachedSplatmapData = new SplatmapData[2]; /// /// The terrain painting gizmo. @@ -230,20 +256,13 @@ namespace FlaxEditor.Tools.Terrain /// Gets the splatmap temporary scratch memory buffer used to modify terrain samples. Allocated memory is unmanaged by GC. /// /// The minimum buffer size (in bytes). + /// The splatmap index for which to return/create the temp buffer. /// The allocated memory using interface. - public IntPtr GetSplatmapTempBuffer(int size) + public IntPtr GetSplatmapTempBuffer(int size, int splatmapIndex) { - if (_cachedSplatmapDataSize < size) - { - if (_cachedSplatmapData != IntPtr.Zero) - { - Marshal.FreeHGlobal(_cachedSplatmapData); - } - _cachedSplatmapData = Marshal.AllocHGlobal(size); - _cachedSplatmapDataSize = size; - } - - return _cachedSplatmapData; + ref var splatmapData = ref _cachedSplatmapData[splatmapIndex]; + splatmapData.EnsureCapacity(size); + return splatmapData.DataPtr; } /// @@ -276,12 +295,8 @@ namespace FlaxEditor.Tools.Terrain base.OnDeactivated(); // Free temporary memory buffer - if (_cachedSplatmapData != IntPtr.Zero) - { - Marshal.FreeHGlobal(_cachedSplatmapData); - _cachedSplatmapData = IntPtr.Zero; - _cachedSplatmapDataSize = 0; - } + foreach (ref var splatmapData in _cachedSplatmapData.AsSpan()) + splatmapData.Free(); } /// diff --git a/Source/Editor/Tools/Terrain/TerrainTools.cpp b/Source/Editor/Tools/Terrain/TerrainTools.cpp index 89d0aa17d..ba1da6638 100644 --- a/Source/Editor/Tools/Terrain/TerrainTools.cpp +++ b/Source/Editor/Tools/Terrain/TerrainTools.cpp @@ -20,7 +20,7 @@ bool TerrainTools::TryGetPatchCoordToAdd(Terrain* terrain, const Ray& ray, Int2& { CHECK_RETURN(terrain, true); result = Int2::Zero; - const float patchSize = terrain->GetChunkSize() * TERRAIN_UNITS_PER_VERTEX * TerrainPatch::CHUNKS_COUNT_EDGE; + const float patchSize = terrain->GetChunkSize() * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge; // Try to pick any of the patch edges for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++) @@ -179,7 +179,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches terrain->AddPatches(numberOfPatches); // Prepare data - const auto heightmapSize = terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1; + const auto heightmapSize = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; Array heightmapData; heightmapData.Resize(heightmapSize * heightmapSize); @@ -380,7 +380,7 @@ bool TerrainTools::ExportTerrain(Terrain* terrain, String outputFolder) const auto firstPatch = terrain->GetPatch(0); // Calculate texture size - const int32 patchEdgeVertexCount = terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1; + const int32 patchEdgeVertexCount = terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; const int32 patchVertexCount = patchEdgeVertexCount * patchEdgeVertexCount; // Find size of heightmap in patches diff --git a/Source/Editor/Utilities/EditorUtilities.cpp b/Source/Editor/Utilities/EditorUtilities.cpp index c1c01be40..2d66147a4 100644 --- a/Source/Editor/Utilities/EditorUtilities.cpp +++ b/Source/Editor/Utilities/EditorUtilities.cpp @@ -9,7 +9,6 @@ #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Tools/TextureTool/TextureTool.h" -#include "Engine/Core/Math/Color32.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" #include "Engine/Content/Content.h" @@ -19,498 +18,6 @@ #if PLATFORM_MAC #include "Engine/Platform/Apple/ApplePlatformSettings.h" #endif -#include - -#define MSDOS_SIGNATURE 0x5A4D -#define PE_SIGNATURE 0x00004550 -#define PE_32BIT_SIGNATURE 0x10B -#define PE_64BIT_SIGNATURE 0x20B -#define PE_NUM_DIRECTORY_ENTRIES 16 -#define PE_SECTION_UNINITIALIZED_DATA 0x00000080 -#define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2 -#define PE_IMAGE_RT_ICON 3 - -/// -/// MS-DOS header found at the beginning in a PE format file. -/// -struct MSDOSHeader -{ - uint16 signature; - uint16 lastSize; - uint16 numBlocks; - uint16 numReloc; - uint16 hdrSize; - uint16 minAlloc; - uint16 maxAlloc; - uint16 ss; - uint16 sp; - uint16 checksum; - uint16 ip; - uint16 cs; - uint16 relocPos; - uint16 numOverlay; - uint16 reserved1[4]; - uint16 oemId; - uint16 oemInfo; - uint16 reserved2[10]; - uint32 lfanew; -}; - -/// -/// COFF header found in a PE format file. -/// -struct COFFHeader -{ - uint16 machine; - uint16 numSections; - uint32 timeDateStamp; - uint32 ptrSymbolTable; - uint32 numSymbols; - uint16 sizeOptHeader; - uint16 characteristics; -}; - -/// -/// Contains address and size of data areas in a PE image. -/// -struct PEDataDirectory -{ - uint32 virtualAddress; - uint32 size; -}; - -/// -/// Optional header in a 32-bit PE format file. -/// -struct PEOptionalHeader32 -{ - uint16 signature; - uint8 majorLinkerVersion; - uint8 minorLinkerVersion; - uint32 sizeCode; - uint32 sizeInitializedData; - uint32 sizeUninitializedData; - uint32 addressEntryPoint; - uint32 baseCode; - uint32 baseData; - uint32 baseImage; - uint32 alignmentSection; - uint32 alignmentFile; - uint16 majorOSVersion; - uint16 minorOSVersion; - uint16 majorImageVersion; - uint16 minorImageVersion; - uint16 majorSubsystemVersion; - uint16 minorSubsystemVersion; - uint32 reserved; - uint32 sizeImage; - uint32 sizeHeaders; - uint32 checksum; - uint16 subsystem; - uint16 characteristics; - uint32 sizeStackReserve; - uint32 sizeStackCommit; - uint32 sizeHeapReserve; - uint32 sizeHeapCommit; - uint32 loaderFlags; - uint32 NumRvaAndSizes; - PEDataDirectory dataDirectory[16]; -}; - -/// -/// Optional header in a 64-bit PE format file. -/// -struct PEOptionalHeader64 -{ - uint16 signature; - uint8 majorLinkerVersion; - uint8 minorLinkerVersion; - uint32 sizeCode; - uint32 sizeInitializedData; - uint32 sizeUninitializedData; - uint32 addressEntryPoint; - uint32 baseCode; - uint64 baseImage; - uint32 alignmentSection; - uint32 alignmentFile; - uint16 majorOSVersion; - uint16 minorOSVersion; - uint16 majorImageVersion; - uint16 minorImageVersion; - uint16 majorSubsystemVersion; - uint16 minorSubsystemVersion; - uint32 reserved; - uint32 sizeImage; - uint32 sizeHeaders; - uint32 checksum; - uint16 subsystem; - uint16 characteristics; - uint64 sizeStackReserve; - uint64 sizeStackCommit; - uint64 sizeHeapReserve; - uint64 sizeHeapCommit; - uint32 loaderFlags; - uint32 NumRvaAndSizes; - PEDataDirectory dataDirectory[16]; -}; - -/// -/// A section header in a PE format file. -/// -struct PESectionHeader -{ - char name[8]; - uint32 virtualSize; - uint32 relativeVirtualAddress; - uint32 physicalSize; - uint32 physicalAddress; - uint8 deprecated[12]; - uint32 flags; -}; - -/// -/// A resource table header within a .rsrc section in a PE format file. -/// -struct PEImageResourceDirectory -{ - uint32 flags; - uint32 timeDateStamp; - uint16 majorVersion; - uint16 minorVersion; - uint16 numNamedEntries; - uint16 numIdEntries; -}; - -/// -/// A single entry in a resource table within a .rsrc section in a PE format file. -/// -struct PEImageResourceEntry -{ - uint32 type; - uint32 offsetDirectory : 31; - uint32 isDirectory : 1; -}; - -/// -/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file. -/// -struct PEImageResourceEntryData -{ - uint32 offsetData; - uint32 size; - uint32 codePage; - uint32 resourceHandle; -}; - -/// -/// Header used in icon file format. -/// -struct IconHeader -{ - uint32 size; - int32 width; - int32 height; - uint16 planes; - uint16 bitCount; - uint32 compression; - uint32 sizeImage; - int32 xPelsPerMeter; - int32 yPelsPerMeter; - uint32 clrUsed; - uint32 clrImportant; -}; - -void UpdateIconData(uint8* iconData, const TextureData* icon) -{ - IconHeader* iconHeader = (IconHeader*)iconData; - - if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0 || iconHeader->planes != 1 || iconHeader->bitCount != 32) - { - // Unsupported format - return; - } - - uint8* iconPixels = iconData + sizeof(IconHeader); - uint32 width = iconHeader->width; - uint32 height = iconHeader->height / 2; - - // Check if can use mip from texture data or sample different mip - uint32 iconTexSize; - if (width != height) - { - // Only square icons are supported - return; - } - if (Math::IsPowerOfTwo(width)) - { - // Use mip - iconTexSize = width; - } - else - { - // Use resized mip - iconTexSize = Math::RoundUpToPowerOf2(width); - } - - // Try to pick a proper mip (require the same size) - const TextureMipData* srcPixels = nullptr; - int32 mipLevels = icon->GetMipLevels(); - for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++) - { - uint32 iconWidth = Math::Max(1, icon->Width >> mipIndex); - uint32 iconHeight = Math::Max(1, icon->Height >> mipIndex); - - if (iconTexSize == iconWidth && iconTexSize == iconHeight) - { - srcPixels = icon->GetData(0, mipIndex); - break; - } - } - if (srcPixels == nullptr) - { - // No icon of this size provided - return; - } - - // Write colors - uint32* colorData = (uint32*)iconPixels; - - uint32 idx = 0; - for (int32 y = (int32)height - 1; y >= 0; y--) - { - float v = (float)y / height; - for (uint32 x = 0; x < width; x++) - { - float u = (float)x / width; - - int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); - colorData[idx++] = ((Color32*)&srcPixels->Data.Get()[i])->GetAsBGRA(); - } - } - - // Write AND mask - uint32 colorDataSize = width * height * sizeof(uint32); - uint8* maskData = iconPixels + colorDataSize; - - // One per bit in byte - uint32 numPackedPixels = width / 8; - - for (int32 y = (int32)height - 1; y >= 0; y--) - { - uint8 mask = 0; - float v = (float)y / height; - for (uint32 packedX = 0; packedX < numPackedPixels; packedX++) - { - for (uint32 pixelIdx = 0; pixelIdx < 8; pixelIdx++) - { - uint32 x = packedX * 8 + pixelIdx; - float u = (float)x / width; - int32 i = (int32)(v * iconTexSize) * srcPixels->RowPitch + (int32)(u * iconTexSize) * sizeof(Color32); - Color32 color = *((Color32*)&srcPixels->Data.Get()[i]); - if (color.A < 64) - mask |= 1 << (7 - pixelIdx); - } - - *maskData = mask; - maskData++; - } - } -} - -void SetIconData(PEImageResourceDirectory* base, PEImageResourceDirectory* current, uint8* imageData, uint32 sectionAddress, const TextureData* iconRGBA8) -{ - uint32 numEntries = current->numIdEntries; // Not supporting name entries - PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1); - - for (uint32 i = 0; i < numEntries; i++) - { - // Only at root does the type identify resource type - if (base == current && entries[i].type != PE_IMAGE_RT_ICON) - continue; - - if (entries[i].isDirectory) - { - PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((uint8*)base) + entries[i].offsetDirectory); - SetIconData(base, child, imageData, sectionAddress, iconRGBA8); - } - else - { - PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((uint8*)base) + entries[i].offsetDirectory); - - uint8* iconData = imageData + (data->offsetData - sectionAddress); - UpdateIconData(iconData, iconRGBA8); - } - } -} - -bool EditorUtilities::UpdateExeIcon(const String& path, const TextureData& icon) -{ - // Validate input - if (!FileSystem::FileExists(path)) - { - LOG(Warning, "Missing file"); - return true; - } - if (icon.Width < 1 || icon.Height < 1 || icon.GetMipLevels() <= 0) - { - LOG(Warning, "Inalid icon data"); - return true; - } - - // Convert to RGBA8 format if need to - const TextureData* iconRGBA8 = &icon; - TextureData tmpData1; - if (icon.Format != PixelFormat::R8G8B8A8_UNorm) - { - if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm)) - { - LOG(Warning, "Failed convert icon data."); - return true; - } - iconRGBA8 = &tmpData1; - } - - // Resize if need to - TextureData tmpData2; - if (iconRGBA8->Width > 256 || iconRGBA8->Height > 256) - { - if (TextureTool::Resize(tmpData2, *iconRGBA8, 256, 256)) - { - LOG(Warning, "Failed resize icon data."); - return true; - } - iconRGBA8 = &tmpData2; - } - - // A PE file is structured as such: - // - MSDOS Header - // - PE Signature - // - COFF Header - // - PE Optional Header - // - One or multiple sections - // - .code - // - .data - // - ... - // - .rsrc - // - icon/cursor/etc data - - std::fstream stream; -#if PLATFORM_WINDOWS - stream.open(path.Get(), std::ios::in | std::ios::out | std::ios::binary); -#else - StringAsANSI<> pathAnsi(path.Get()); - stream.open(pathAnsi.Get(), std::ios::in | std::ios::out | std::ios::binary); -#endif - if (!stream.is_open()) - { - LOG(Warning, "Cannot open file"); - return true; - } - - // First check magic number to ensure file is even an executable - uint16 magicNum; - stream.read((char*)&magicNum, sizeof(magicNum)); - if (magicNum != MSDOS_SIGNATURE) - { - LOG(Warning, "Provided file is not a valid executable."); - return true; - } - - // Read the MSDOS header and skip over it - stream.seekg(0); - - MSDOSHeader msdosHeader; - stream.read((char*)&msdosHeader, sizeof(MSDOSHeader)); - - // Read PE signature - stream.seekg(msdosHeader.lfanew); - - uint32 peSignature; - stream.read((char*)&peSignature, sizeof(peSignature)); - - if (peSignature != PE_SIGNATURE) - { - LOG(Warning, "Provided file is not in PE format."); - return true; - } - - // Read COFF header - COFFHeader coffHeader; - stream.read((char*)&coffHeader, sizeof(COFFHeader)); - - // .exe files always have an optional header - if (coffHeader.sizeOptHeader == 0) - { - LOG(Warning, "Provided file is not a valid executable."); - return true; - } - - uint32 numSectionHeaders = coffHeader.numSections; - - // Read optional header - auto optionalHeaderPos = stream.tellg(); - - uint16 optionalHeaderSignature; - stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature)); - - PEDataDirectory* dataDirectory = nullptr; - stream.seekg(optionalHeaderPos); - if (optionalHeaderSignature == PE_32BIT_SIGNATURE) - { - PEOptionalHeader32 optionalHeader; - stream.read((char*)&optionalHeader, sizeof(optionalHeader)); - - dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; - } - else if (optionalHeaderSignature == PE_64BIT_SIGNATURE) - { - PEOptionalHeader64 optionalHeader; - stream.read((char*)&optionalHeader, sizeof(optionalHeader)); - - dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE; - } - else - { - LOG(Warning, "Unrecognized PE format."); - return true; - } - - // Read section headers - auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader; - stream.seekg(sectionHeaderPos); - - Array sectionHeaders; - sectionHeaders.Resize(numSectionHeaders); - stream.read((char*)sectionHeaders.Get(), sizeof(PESectionHeader) * numSectionHeaders); - - // Look for .rsrc section header - for (uint32 i = 0; i < numSectionHeaders; i++) - { - if (sectionHeaders[i].flags & PE_SECTION_UNINITIALIZED_DATA) - continue; - - if (strcmp(sectionHeaders[i].name, ".rsrc") == 0) - { - uint32 imageSize = sectionHeaders[i].physicalSize; - Array imageData; - imageData.Resize(imageSize); - - stream.seekg(sectionHeaders[i].physicalAddress); - stream.read((char*)imageData.Get(), imageSize); - - uint32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeaders[i].relativeVirtualAddress; - PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData.Get()[resourceDirOffset]; - - SetIconData(resourceDirectory, resourceDirectory, imageData.Get(), sectionHeaders[i].relativeVirtualAddress, iconRGBA8); - stream.seekp(sectionHeaders[i].physicalAddress); - stream.write((char*)imageData.Get(), imageSize); - } - } - - stream.close(); - - return false; -} String EditorUtilities::GetOutputName() { diff --git a/Source/Editor/Utilities/EditorUtilities.h b/Source/Editor/Utilities/EditorUtilities.h index 40518ed88..91d33522c 100644 --- a/Source/Editor/Utilities/EditorUtilities.h +++ b/Source/Editor/Utilities/EditorUtilities.h @@ -22,14 +22,6 @@ public: SplashScreen, }; - /// - /// Updates the Win32 executable file icon. - /// - /// The exe path. - /// The icon image data. - /// True if fails, otherwise false. - static bool UpdateExeIcon(const String& path, const TextureData& icon); - static String GetOutputName(); static bool FormatAppPackageName(String& packageName); static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon); diff --git a/Source/Editor/Utilities/QueryFilterHelper.cs b/Source/Editor/Utilities/QueryFilterHelper.cs index e9e5e9ccb..f2db81009 100644 --- a/Source/Editor/Utilities/QueryFilterHelper.cs +++ b/Source/Editor/Utilities/QueryFilterHelper.cs @@ -140,8 +140,7 @@ namespace FlaxEditor.Utilities // Check if start the matching sequence if (matchStartPos == -1) { - if (ranges == null) - ranges = new List(); + ranges ??= new List(); matchStartPos = textPos; } } @@ -152,7 +151,7 @@ namespace FlaxEditor.Utilities { var length = textPos - matchStartPos; if (length >= MinLength) - ranges.Add(new Range(matchStartPos, length)); + ranges!.Add(new Range(matchStartPos, length)); textPos = matchStartPos + length; matchStartPos = -1; } @@ -165,13 +164,13 @@ namespace FlaxEditor.Utilities { var length = endPos - matchStartPos; if (length >= MinLength) - ranges.Add(new Range(matchStartPos, length)); + ranges!.Add(new Range(matchStartPos, length)); textPos = matchStartPos + length; } } // Check if has any range - if (ranges != null && ranges.Count > 0) + if (ranges is { Count: > 0 }) { matches = ranges.ToArray(); return true; diff --git a/Source/Editor/Viewport/Cameras/FPSCamera.cs b/Source/Editor/Viewport/Cameras/FPSCamera.cs index bf2e840ea..0bbcfd0e8 100644 --- a/Source/Editor/Viewport/Cameras/FPSCamera.cs +++ b/Source/Editor/Viewport/Cameras/FPSCamera.cs @@ -174,7 +174,10 @@ namespace FlaxEditor.Viewport.Cameras } else { - position = sphere.Center - Vector3.Forward * orientation * (sphere.Radius * 2.5f); + // calculate the min. distance so that the sphere fits roughly 70% in FOV + // clip to far plane as a disappearing big object might be confusing + var distance = Mathf.Min(1.4f * sphere.Radius / Mathf.Tan(Mathf.DegreesToRadians * Viewport.FieldOfView / 2), Viewport.FarPlane); + position = sphere.Center - Vector3.Forward * orientation * distance; } TargetPoint = sphere.Center; MoveViewport(position, orientation); diff --git a/Source/Editor/Viewport/EditorViewport.cs b/Source/Editor/Viewport/EditorViewport.cs index 526a84c45..759c3736a 100644 --- a/Source/Editor/Viewport/EditorViewport.cs +++ b/Source/Editor/Viewport/EditorViewport.cs @@ -334,6 +334,22 @@ namespace FlaxEditor.Viewport } } + /// + /// Gets the bounding frustum of the current viewport camera. + /// + public BoundingFrustum ViewFrustum + { + get + { + Vector3 viewOrigin = Task.View.Origin; + Float3 position = ViewPosition - viewOrigin; + CreateViewMatrix(position, out var view); + CreateProjectionMatrix(out var projection); + Matrix.Multiply(ref view, ref projection, out var viewProjection); + return new BoundingFrustum(ref viewProjection); + } + } + /// /// Gets or sets the yaw angle (in degrees). /// diff --git a/Source/Editor/Viewport/ViewportDraggingHelper.cs b/Source/Editor/Viewport/ViewportDraggingHelper.cs index bf4ec7979..dce2867c3 100644 --- a/Source/Editor/Viewport/ViewportDraggingHelper.cs +++ b/Source/Editor/Viewport/ViewportDraggingHelper.cs @@ -234,7 +234,6 @@ namespace FlaxEditor.Viewport LocalOrientation = RootNode.RaycastNormalRotation(ref hitNormal), Name = item.ShortName }; - DebugDraw.DrawWireArrow(PostProcessSpawnedActorLocation(actor, ref hitNormal), actor.LocalOrientation, 1.0f, Color.Red, 1000000); Spawn(actor, ref hitLocation, ref hitNormal); } else if (hit is StaticModelNode staticModelNode) diff --git a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs index e0d8c9ded..93eea95e1 100644 --- a/Source/Editor/Windows/Assets/AnimationGraphWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationGraphWindow.cs @@ -154,10 +154,11 @@ namespace FlaxEditor.Windows.Assets } [StructLayout(LayoutKind.Sequential)] - private struct AnimGraphDebugFlowInfo + private unsafe struct AnimGraphDebugFlowInfo { public uint NodeId; public int BoxId; + public fixed uint NodePath[8]; } private FlaxObjectRefPickerControl _debugPicker; @@ -252,25 +253,26 @@ namespace FlaxEditor.Windows.Assets return obj is AnimatedModel player && player.AnimationGraph == OriginalAsset; } - private void OnDebugFlow(Asset asset, Object obj, uint nodeId, uint boxId) + private unsafe void OnDebugFlow(Animations.DebugFlowInfo flowInfo) { // Filter the flow if (_debugPicker.Value != null) { - if (asset != OriginalAsset || _debugPicker.Value != obj) + if (flowInfo.Asset != OriginalAsset || _debugPicker.Value != flowInfo.Instance) return; } else { - if (asset != Asset || _preview.PreviewActor != obj) + if (flowInfo.Asset != Asset || _preview.PreviewActor != flowInfo.Instance) return; } // Register flow to show it in UI on a surface - var flowInfo = new AnimGraphDebugFlowInfo { NodeId = nodeId, BoxId = (int)boxId }; + var flow = new AnimGraphDebugFlowInfo { NodeId = flowInfo.NodeId, BoxId = (int)flowInfo.BoxId }; + Utils.MemoryCopy(new IntPtr(flow.NodePath), new IntPtr(flowInfo.NodePath0), sizeof(uint) * 8ul); lock (_debugFlows) { - _debugFlows.Add(flowInfo); + _debugFlows.Add(flow); } } @@ -394,7 +396,7 @@ namespace FlaxEditor.Windows.Assets } /// - public override void OnUpdate() + public override unsafe void OnUpdate() { // Extract animations playback state from the events tracing var debugActor = _debugPicker.Value as AnimatedModel; @@ -413,7 +415,8 @@ namespace FlaxEditor.Windows.Assets { foreach (var debugFlow in _debugFlows) { - var node = Surface.Context.FindNode(debugFlow.NodeId); + var context = Surface.FindContext(new Span(debugFlow.NodePath, 8)); + var node = context?.FindNode(debugFlow.NodeId); var box = node?.GetBox(debugFlow.BoxId); box?.HighlightConnections(); } diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 8f88d93f6..c512d287f 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -48,7 +48,7 @@ namespace FlaxEditor.Windows.Assets // Use virtual animation graph to playback the animation _animGraph = FlaxEngine.Content.CreateVirtualAsset(); - _animGraph.InitAsAnimation(model, _window.Asset); + _animGraph.InitAsAnimation(model, _window.Asset, true, true); PreviewActor.AnimationGraph = _animGraph; } diff --git a/Source/Editor/Windows/Assets/FontWindow.cs b/Source/Editor/Windows/Assets/FontWindow.cs index ff4135165..bc91ca5e2 100644 --- a/Source/Editor/Windows/Assets/FontWindow.cs +++ b/Source/Editor/Windows/Assets/FontWindow.cs @@ -144,7 +144,7 @@ namespace FlaxEditor.Windows.Assets protected override void OnAssetLinked() { Asset.WaitForLoaded(); - _textPreview.Font = new FontReference(Asset.CreateFont(30)); + _textPreview.Font = new FontReference(Asset, 30); _inputText.Text = string.Format("This is a sample text using font {0}.", Asset.FamilyName); var options = Asset.Options; _proxy.Set(ref options); diff --git a/Source/Editor/Windows/ContentWindow.ContextMenu.cs b/Source/Editor/Windows/ContentWindow.ContextMenu.cs index dda812f84..a21ccbed7 100644 --- a/Source/Editor/Windows/ContentWindow.ContextMenu.cs +++ b/Source/Editor/Windows/ContentWindow.ContextMenu.cs @@ -79,7 +79,14 @@ namespace FlaxEditor.Windows if (item.HasDefaultThumbnail == false) { - cm.AddButton("Refresh thumbnail", item.RefreshThumbnail); + if (_view.SelectedCount > 1) + cm.AddButton("Refresh thumbnails", () => + { + foreach (var e in _view.Selection) + e.RefreshThumbnail(); + }); + else + cm.AddButton("Refresh thumbnail", item.RefreshThumbnail); } if (!isFolder) diff --git a/Source/Editor/Windows/EditorWindow.cs b/Source/Editor/Windows/EditorWindow.cs index 445287cab..17b558d28 100644 --- a/Source/Editor/Windows/EditorWindow.cs +++ b/Source/Editor/Windows/EditorWindow.cs @@ -263,6 +263,8 @@ namespace FlaxEditor.Windows /// public override void OnDestroy() { + if (IsDisposing) + return; OnExit(); // Unregister diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs index 4a8c11176..9368a30fd 100644 --- a/Source/Editor/Windows/GameWindow.cs +++ b/Source/Editor/Windows/GameWindow.cs @@ -485,6 +485,16 @@ namespace FlaxEditor.Windows { base.OnShowContextMenu(menu); + // Focus on play + { + var focus = menu.AddButton("Start Focused"); + focus.CloseMenuOnClick = false; + var checkbox = new CheckBox(140, 2, FocusOnPlay) { Parent = focus }; + checkbox.StateChanged += state => FocusOnPlay = state.Checked; + } + + menu.AddSeparator(); + // Viewport Brightness { var brightness = menu.AddButton("Viewport Brightness"); diff --git a/Source/Editor/Windows/Profiler/CPU.cs b/Source/Editor/Windows/Profiler/CPU.cs index ba569f04f..7638485e4 100644 --- a/Source/Editor/Windows/Profiler/CPU.cs +++ b/Source/Editor/Windows/Profiler/CPU.cs @@ -20,9 +20,7 @@ namespace FlaxEngine get { fixed (short* name = Name0) - { return new string((char*)name); - } } } @@ -31,9 +29,7 @@ namespace FlaxEngine fixed (short* name = Name0) { fixed (char* p = prefix) - { return Utils.MemoryCompare(new IntPtr(name), new IntPtr(p), (ulong)(prefix.Length * 2)) == 0; - } } } } diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 0c8e0f283..abad36829 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -132,10 +132,13 @@ namespace FlaxEditor.Windows b = contextMenu.AddButton("Cut", inputOptions.Cut, Editor.SceneEditing.Cut); b.Enabled = canEditScene; - // Prefab options + // Create option contextMenu.AddSeparator(); + b = contextMenu.AddButton("Create parent for selected actors", Editor.SceneEditing.CreateParentForSelectedActors); + b.Enabled = canEditScene && hasSthSelected; + b = contextMenu.AddButton("Create Prefab", Editor.Prefabs.CreatePrefab); b.Enabled = isSingleActorSelected && ((ActorNode)Editor.SceneEditing.Selection[0]).CanCreatePrefab && diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 97bf85b35..c5d790a3d 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; using FlaxEditor.Gizmo; using FlaxEditor.Content; using FlaxEditor.GUI.Tree; @@ -14,7 +13,6 @@ using FlaxEditor.Scripting; using FlaxEditor.States; using FlaxEngine; using FlaxEngine.GUI; -using static FlaxEditor.GUI.ItemsListContextMenu; namespace FlaxEditor.Windows { @@ -35,6 +33,11 @@ namespace FlaxEditor.Windows private DragScriptItems _dragScriptItems; private DragHandlers _dragHandlers; + /// + /// Scene tree panel. + /// + public Panel SceneTreePanel => _sceneTreePanel; + /// /// Initializes a new instance of the class. /// diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.cs b/Source/Engine/AI/BehaviorKnowledgeSelector.cs index 5c642e92a..cdcaf6c40 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.cs +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.cs @@ -146,7 +146,7 @@ namespace FlaxEngine public string Path; /// - /// Initializes a new instance of the structure. + /// Initializes a new instance of the structure. /// /// The selector path. public BehaviorKnowledgeSelector(string path) @@ -155,7 +155,7 @@ namespace FlaxEngine } /// - /// Initializes a new instance of the structure. + /// Initializes a new instance of the structure. /// /// The other selector. public BehaviorKnowledgeSelector(BehaviorKnowledgeSelectorAny other) diff --git a/Source/Engine/AI/BehaviorKnowledgeSelector.h b/Source/Engine/AI/BehaviorKnowledgeSelector.h index 976711282..092d42b1c 100644 --- a/Source/Engine/AI/BehaviorKnowledgeSelector.h +++ b/Source/Engine/AI/BehaviorKnowledgeSelector.h @@ -91,6 +91,13 @@ API_STRUCT(InBuild, Template, MarshalAs=StringAnsi) struct FLAXENGINE_API Behavi return false; } + BehaviorKnowledgeSelector() = default; + + BehaviorKnowledgeSelector(const StringAnsi& other) + { + Path = other; + } + BehaviorKnowledgeSelector& operator=(const StringAnsiView& other) noexcept { Path = other; diff --git a/Source/Engine/Animations/AnimationData.cpp b/Source/Engine/Animations/AnimationData.cpp new file mode 100644 index 000000000..8c3611d7a --- /dev/null +++ b/Source/Engine/Animations/AnimationData.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#include "AnimationData.h" + +void NodeAnimationData::Evaluate(float time, Transform* result, bool loop) const +{ + if (Position.GetKeyframes().HasItems()) +#if USE_LARGE_WORLDS + { + Float3 position; + Position.Evaluate(position, time, loop); + result->Translation = position; + } +#else + Position.Evaluate(result->Translation, time, loop); +#endif + if (Rotation.GetKeyframes().HasItems()) + Rotation.Evaluate(result->Orientation, time, loop); + if (Scale.GetKeyframes().HasItems()) + Scale.Evaluate(result->Scale, time, loop); +} + +void NodeAnimationData::EvaluateAll(float time, Transform* result, bool loop) const +{ + Float3 position; + Position.Evaluate(position, time, loop); + result->Translation = position; + Rotation.Evaluate(result->Orientation, time, loop); + Scale.Evaluate(result->Scale, time, loop); +} + +int32 NodeAnimationData::GetKeyframesCount() const +{ + return Position.GetKeyframes().Count() + Rotation.GetKeyframes().Count() + Scale.GetKeyframes().Count(); +} + +uint64 NodeAnimationData::GetMemoryUsage() const +{ + return NodeName.Length() * sizeof(Char) + Position.GetMemoryUsage() + Rotation.GetMemoryUsage() + Scale.GetMemoryUsage(); +} + +uint64 AnimationData::GetMemoryUsage() const +{ + uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData); + for (const auto& e : Channels) + result += e.GetMemoryUsage(); + return result; +} + +int32 AnimationData::GetKeyframesCount() const +{ + int32 result = 0; + for (int32 i = 0; i < Channels.Count(); i++) + result += Channels[i].GetKeyframesCount(); + return result; +} + +NodeAnimationData* AnimationData::GetChannel(const StringView& name) +{ + for (auto& e : Channels) + if (e.NodeName == name) + return &e; + return nullptr; +} + +void AnimationData::Swap(AnimationData& other) +{ + ::Swap(Duration, other.Duration); + ::Swap(FramesPerSecond, other.FramesPerSecond); + ::Swap(RootMotionFlags, other.RootMotionFlags); + ::Swap(Name, other.Name); + ::Swap(RootNodeName, other.RootNodeName); + Channels.Swap(other.Channels); +} + +void AnimationData::Dispose() +{ + Name.Clear(); + Duration = 0.0; + FramesPerSecond = 0.0; + RootNodeName.Clear(); + RootMotionFlags = AnimationRootMotionFlags::None; + Channels.Resize(0); +} diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h index c69de8afa..00717b05f 100644 --- a/Source/Engine/Animations/AnimationData.h +++ b/Source/Engine/Animations/AnimationData.h @@ -3,8 +3,8 @@ #pragma once #include "Engine/Core/Types/String.h" -#include "Engine/Animations/Curve.h" #include "Engine/Core/Math/Transform.h" +#include "Engine/Animations/Curve.h" /// /// Single node animation data container. @@ -50,19 +50,7 @@ public: /// The time to evaluate the curves at. /// The interpolated value from the curve at provided time. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. - void Evaluate(float time, Transform* result, bool loop = true) const - { - if (Position.GetKeyframes().HasItems()) - { - Float3 position; - Position.Evaluate(position, time, loop); - result->Translation = position; - } - if (Rotation.GetKeyframes().HasItems()) - Rotation.Evaluate(result->Orientation, time, loop); - if (Scale.GetKeyframes().HasItems()) - Scale.Evaluate(result->Scale, time, loop); - } + void Evaluate(float time, Transform* result, bool loop = true) const; /// /// Evaluates the animation transformation at the specified time. @@ -70,29 +58,37 @@ public: /// The time to evaluate the curves at. /// The interpolated value from the curve at provided time. /// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped. - void EvaluateAll(float time, Transform* result, bool loop = true) const - { - Float3 position; - Position.Evaluate(position, time, loop); - result->Translation = position; - Rotation.Evaluate(result->Orientation, time, loop); - Scale.Evaluate(result->Scale, time, loop); - } + void EvaluateAll(float time, Transform* result, bool loop = true) const; /// /// Gets the total amount of keyframes in the animation curves. /// - int32 GetKeyframesCount() const - { - return Position.GetKeyframes().Count() + Rotation.GetKeyframes().Count() + Scale.GetKeyframes().Count(); - } + int32 GetKeyframesCount() const; - uint64 GetMemoryUsage() const - { - return NodeName.Length() * sizeof(Char) + Position.GetMemoryUsage() + Rotation.GetMemoryUsage() + Scale.GetMemoryUsage(); - } + uint64 GetMemoryUsage() const; }; +/// +/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior. +/// +API_ENUM(Attributes="Flags") enum class AnimationRootMotionFlags : byte +{ + // No root motion. + None = 0, + // Root node position along XZ plane. Applies horizontal movement. Good for stationary animations (eg. idle). + RootPositionXZ = 1 << 0, + // Root node position along Y axis (up). Applies vertical movement. Good for all 'grounded' animations unless jumping is handled from code. + RootPositionY = 1 << 1, + // Root node rotation. Applies orientation changes. Good for animations that have baked-in root rotation (eg. turn animations). + RootRotation = 1 << 2, + // Root node position. + RootPosition = RootPositionXZ | RootPositionY, + // Root node position and rotation. + RootTransform = RootPosition | RootRotation, +}; + +DECLARE_ENUM_OPERATORS(AnimationRootMotionFlags); + /// /// Skeleton nodes animation data container. Includes metadata about animation sampling, duration and node animations curves. /// @@ -111,7 +107,7 @@ struct AnimationData /// /// Enables root motion extraction support from this animation. /// - bool EnableRootMotion = false; + AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::None; /// /// The animation name. @@ -140,49 +136,23 @@ public: return static_cast(Duration / FramesPerSecond); } - uint64 GetMemoryUsage() const - { - uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData); - for (const auto& e : Channels) - result += e.GetMemoryUsage(); - return result; - } + uint64 GetMemoryUsage() const; /// /// Gets the total amount of keyframes in the all animation channels. /// - int32 GetKeyframesCount() const - { - int32 result = 0; - for (int32 i = 0; i < Channels.Count(); i++) - result += Channels[i].GetKeyframesCount(); - return result; - } + int32 GetKeyframesCount() const; + + NodeAnimationData* GetChannel(const StringView& name); /// /// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange. /// /// The other object. - void Swap(AnimationData& other) - { - ::Swap(Duration, other.Duration); - ::Swap(FramesPerSecond, other.FramesPerSecond); - ::Swap(EnableRootMotion, other.EnableRootMotion); - ::Swap(Name, other.Name); - ::Swap(RootNodeName, other.RootNodeName); - Channels.Swap(other.Channels); - } + void Swap(AnimationData& other); /// /// Releases data. /// - void Dispose() - { - Name.Clear(); - Duration = 0.0; - FramesPerSecond = 0.0; - RootNodeName.Clear(); - EnableRootMotion = false; - Channels.Resize(0); - } + void Dispose(); }; diff --git a/Source/Engine/Animations/Animations.cpp b/Source/Engine/Animations/Animations.cpp index e571af162..59acc5860 100644 --- a/Source/Engine/Animations/Animations.cpp +++ b/Source/Engine/Animations/Animations.cpp @@ -52,7 +52,7 @@ namespace AnimationsService AnimationManagerInstance; TaskGraphSystem* Animations::System = nullptr; #if USE_EDITOR -Delegate Animations::DebugFlow; +Delegate Animations::DebugFlow; #endif AnimEvent::AnimEvent(const SpawnParams& params) @@ -127,7 +127,7 @@ void AnimationsSystem::Execute(TaskGraph* graph) #if USE_EDITOR // If debug flow is registered, then warm it up (eg. static cached method inside DebugFlow_ManagedWrapper) so it doesn't crash on highly multi-threaded code if (Animations::DebugFlow.IsBinded()) - Animations::DebugFlow(nullptr, nullptr, 0, 0); + Animations::DebugFlow(Animations::DebugFlowInfo()); #endif // Schedule work to update all animated models in async diff --git a/Source/Engine/Animations/Animations.h b/Source/Engine/Animations/Animations.h index d82b9d05f..038d20d9d 100644 --- a/Source/Engine/Animations/Animations.h +++ b/Source/Engine/Animations/Animations.h @@ -22,8 +22,25 @@ API_CLASS(Static) class FLAXENGINE_API Animations API_FIELD(ReadOnly) static TaskGraphSystem* System; #if USE_EDITOR - // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. Args are: anim graph asset, animated object, node id, box id - API_EVENT() static Delegate DebugFlow; + // Data wrapper for the debug flow information. + API_STRUCT(NoDefault) struct DebugFlowInfo + { + DECLARE_SCRIPTING_TYPE_MINIMAL(DebugFlowInfo); + + // Anim Graph asset + API_FIELD() Asset* Asset = nullptr; + // Animated actor + API_FIELD() ScriptingObject* Instance = nullptr; + // Graph node id. + API_FIELD() uint32 NodeId = 0; + // Graph box id. + API_FIELD() uint32 BoxId = 0; + // Ids of graph nodes (call of hierarchy). + API_FIELD(Internal, NoArray) uint32 NodePath[8] = {}; + }; + + // Custom event that is called every time the Anim Graph signal flows over the graph (including the data connections). Can be used to read and visualize the animation blending logic. + API_EVENT() static Delegate DebugFlow; #endif /// diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 74ad4168d..e0e7bd6b5 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -730,7 +730,7 @@ public: void TransformTime(float timeScale, float timeOffset) { for (int32 i = 0; i < _keyframes.Count(); i++) - _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset;; + _keyframes[i].Time = _keyframes[i].Time * timeScale + timeOffset; } uint64 GetMemoryUsage() const diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index c05b82b70..af5508705 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -157,7 +157,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n) if (_rootNode->Values.Count() < 1) { _rootNode->Values.Resize(1); - _rootNode->Values[0] = (int32)RootMotionMode::NoExtraction; + _rootNode->Values[0] = (int32)RootMotionExtraction::NoExtraction; } break; // Animation diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index cdd5b03eb..860ac99d4 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -89,7 +89,7 @@ void AnimGraphExecutor::initRuntime() void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value) { #if USE_CSHARP - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; auto box = (AnimGraphBox*)boxBase; diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index f7b883bd7..2fc9332ba 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -9,7 +9,7 @@ extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i); -ThreadLocal AnimGraphExecutor::Context; +ThreadLocal AnimGraphExecutor::Context; Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const { @@ -104,7 +104,7 @@ AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(Ani AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor) { - auto& context = AnimGraphExecutor::Context.Get(); + auto& context = *AnimGraphExecutor::Context.Get(); const int32 count = executor->_skeletonNodesCount; if (context.PoseCacheSize == context.PoseCache.Count()) context.PoseCache.AddOne(); @@ -204,19 +204,24 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Initialize auto& skeleton = _graph.BaseModel->Skeleton; - auto& context = Context.Get(); + auto& contextPtr = Context.Get(); + if (!contextPtr) + contextPtr = New(); + auto& context = *contextPtr; { ANIM_GRAPH_PROFILE_EVENT("Init"); // Init data from base model _skeletonNodesCount = skeleton.Nodes.Count(); - _rootMotionMode = (RootMotionMode)(int32)_graph._rootNode->Values[0]; + _rootMotionMode = (RootMotionExtraction)(int32)_graph._rootNode->Values[0]; // Prepare context data for the evaluation context.GraphStack.Clear(); context.GraphStack.Push((Graph*)&_graph); + context.NodePath.Clear(); context.Data = &data; context.DeltaTime = dt; + context.StackOverFlow = false; context.CurrentFrameIndex = ++data.CurrentFrame; context.CallStack.Clear(); context.Functions.Clear(); @@ -377,12 +382,12 @@ void AnimGraphExecutor::GetInputValue(Box* box, Value& result) AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes() { - return &Context.Get().EmptyNodes; + return &Context.Get()->EmptyNodes; } void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const { - const auto& emptyNodes = Context.Get().EmptyNodes; + const auto& emptyNodes = Context.Get()->EmptyNodes; Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); nodes->RootMotion = emptyNodes.RootMotion; nodes->Position = emptyNodes.Position; @@ -404,12 +409,15 @@ void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* g VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) { - auto& context = Context.Get(); + auto& context = *Context.Get(); // Check if graph is looped or is too deep + if (context.StackOverFlow) + return Value::Zero; if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); + context.StackOverFlow = true; return Value::Zero; } #if !BUILD_RELEASE @@ -424,7 +432,15 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) context.CallStack.Add(caller); #if USE_EDITOR - Animations::DebugFlow(_graph._owner, context.Data->Object, box->GetParent()->ID, box->ID); + Animations::DebugFlowInfo flowInfo; + flowInfo.Asset = _graph._owner; + flowInfo.Instance = context.Data->Object; + flowInfo.NodeId = box->GetParent()->ID; + flowInfo.BoxId = box->ID; + const auto* nodePath = context.NodePath.Get(); + for (int32 i = 0; i < context.NodePath.Count(); i++) + flowInfo.NodePath[i] = nodePath[i]; + Animations::DebugFlow(flowInfo); #endif // Call per group custom processing event @@ -441,6 +457,6 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const { - auto& context = Context.Get(); + auto& context = *Context.Get(); return context.GraphStack.Peek(); } diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index c160b671c..c069ddb06 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -92,9 +92,9 @@ enum class BoneTransformMode }; /// -/// The animated model root motion mode. +/// The animated model root motion extraction modes. /// -enum class RootMotionMode +enum class RootMotionExtraction { /// /// Don't extract nor apply the root motion. @@ -205,7 +205,7 @@ struct FLAXENGINE_API AnimGraphSlot /// /// The animation graph state container for a single node playback trace (eg. animation sample info or state transition). Can be used by Anim Graph debugging or custom scripting. /// -API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent +API_STRUCT(NoDefault) struct FLAXENGINE_API AnimGraphTraceEvent { DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent); @@ -215,6 +215,8 @@ API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent API_FIELD() float Value = 0; // Identifier of the node in the graph. API_FIELD() uint32 NodeId = 0; + // Ids of graph nodes (call of hierarchy). + API_FIELD(Internal, NoArray) uint32 NodePath[8] = {}; }; /// @@ -794,12 +796,16 @@ struct AnimGraphContext AnimGraphInstanceData* Data; AnimGraphImpulse EmptyNodes; AnimGraphTransitionData TransitionData; + bool StackOverFlow; Array> CallStack; Array> GraphStack; + Array > NodePath; Dictionary Functions; ChunkedArray PoseCache; int32 PoseCacheSize; Dictionary ValueCache; + + AnimGraphTraceEvent& AddTraceEvent(const AnimGraphNode* node); }; /// @@ -810,11 +816,11 @@ class AnimGraphExecutor : public VisjectExecutor friend AnimGraphNode; private: AnimGraph& _graph; - RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction; + RootMotionExtraction _rootMotionMode = RootMotionExtraction::NoExtraction; int32 _skeletonNodesCount = 0; // Per-thread context to allow async execution - static ThreadLocal Context; + static ThreadLocal Context; public: /// @@ -891,7 +897,7 @@ private: Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, float speedA, float speedB, float alpha); Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC); Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode); - Variant SampleState(AnimGraphNode* state); + Variant SampleState(AnimGraphContext& context, const AnimGraphNode* state); void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr); AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr); void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData); diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index d3b61c951..30b1bb022 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -21,13 +21,13 @@ namespace base += additive; } - FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionMode rootMotionMode) + FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionExtraction rootMotionMode) { for (int32 i = 0; i < nodes->Nodes.Count(); i++) { nodes->Nodes[i].Orientation.Normalize(); } - if (rootMotionMode != RootMotionMode::NoExtraction) + if (rootMotionMode != RootMotionExtraction::NoExtraction) { nodes->RootMotion.Orientation.Normalize(); } @@ -52,6 +52,17 @@ void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData node = value; } +AnimGraphTraceEvent& AnimGraphContext::AddTraceEvent(const AnimGraphNode* node) +{ + auto& trace = Data->TraceEvents.AddOne(); + trace.Value = 0.0f; + trace.NodeId = node->ID; + const auto* nodePath = NodePath.Get(); + for (int32 i = 0; i < NodePath.Count(); i++) + trace.NodePath[i] = nodePath[i]; + return trace; +} + int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim) { // TODO: cache the root node index (use dictionary with Animation* -> int32 for fast lookups) @@ -76,7 +87,7 @@ void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float if (anim->Events.Count() == 0) return; ANIM_GRAPH_PROFILE_EVENT("Events"); - auto& context = Context.Get(); + auto& context = *Context.Get(); float eventTimeMin = animPrevPos; float eventTimeMax = animPos; if (loop && context.DeltaTime * speed < 0) @@ -220,13 +231,12 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed); // Add to trace - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.Data->EnableTracing) { - auto& trace = context.Data->TraceEvents.AddOne(); + auto& trace = context.AddTraceEvent(node); trace.Asset = anim; trace.Value = animPos; - trace.NodeId = node->ID; } // Evaluate nested animations @@ -313,16 +323,21 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* } // Handle root motion - if (_rootMotionMode != RootMotionMode::NoExtraction && anim->Data.EnableRootMotion) + if (_rootMotionMode != RootMotionExtraction::NoExtraction && anim->Data.RootMotionFlags != AnimationRootMotionFlags::None) { // Calculate the root motion node transformation + const bool motionPositionXZ = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionXZ); + const bool motionPositionY = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootPositionY); + const bool motionRotation = EnumHasAnyFlags(anim->Data.RootMotionFlags, AnimationRootMotionFlags::RootRotation); + const Vector3 motionPositionMask(motionPositionXZ ? 1.0f : 0.0f, motionPositionY ? 1.0f : 0.0f, motionPositionXZ ? 1.0f : 0.0f); + const bool motionPosition = motionPositionXZ | motionPositionY; const int32 rootNodeIndex = GetRootNodeIndex(anim); const Transform& refPose = emptyNodes->Nodes[rootNodeIndex]; Transform& rootNode = nodes->Nodes[rootNodeIndex]; Transform& dstNode = nodes->RootMotion; Transform srcNode = Transform::Identity; const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex]; - if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1) + if (_rootMotionMode == RootMotionExtraction::Enable && nodeToChannel != -1) { // Get the root bone transformation Transform rootBefore = refPose; @@ -346,16 +361,20 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* // Complex motion calculation to preserve the looped movement // (end - before + now - begin) // It sums the motion since the last update to anim end and since the start to now - srcNode.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation; - srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated()); + if (motionPosition) + srcNode.Translation = (rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation) * motionPositionMask; + if (motionRotation) + srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated()); //srcNode.Orientation = Quaternion::Identity; } else { // Simple motion delta // (now - before) - srcNode.Translation = rootNode.Translation - rootBefore.Translation; - srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation; + if (motionPosition) + srcNode.Translation = (rootNode.Translation - rootBefore.Translation) * motionPositionMask; + if (motionRotation) + srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation; } // Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale) @@ -369,28 +388,40 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* } } - // Remove root node motion after extraction - rootNode = refPose; + // Remove root node motion after extraction (only extracted components) + if (motionPosition) + rootNode.Translation = refPose.Translation * motionPositionMask + rootNode.Translation * (Vector3::One - motionPositionMask); + if (motionRotation) + rootNode.Orientation = refPose.Orientation; // Blend root motion if (mode == ProcessAnimationMode::BlendAdditive) { - dstNode.Translation += srcNode.Translation * weight; - BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight); + if (motionPosition) + dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight); } else if (mode == ProcessAnimationMode::Add) { - dstNode.Translation += srcNode.Translation * weight; - dstNode.Orientation += srcNode.Orientation * weight; + if (motionPosition) + dstNode.Translation += srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + dstNode.Orientation += srcNode.Orientation * weight; } else if (weighted) { - dstNode.Translation = srcNode.Translation * weight; - dstNode.Orientation = srcNode.Orientation * weight; + if (motionPosition) + dstNode.Translation = srcNode.Translation * weight * motionPositionMask; + if (motionRotation) + dstNode.Orientation = srcNode.Orientation * weight; } else { - dstNode = srcNode; + if (motionPosition) + dstNode.Translation = srcNode.Translation * motionPositionMask; + if (motionRotation) + dstNode.Orientation = srcNode.Orientation; } } @@ -494,19 +525,30 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const return nodes; } -Variant AnimGraphExecutor::SampleState(AnimGraphNode* state) +Variant AnimGraphExecutor::SampleState(AnimGraphContext& context, const AnimGraphNode* state) { auto& data = state->Data.State; if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr) return Value::Null; + + // Add to trace + if (context.Data->EnableTracing) + { + auto& trace = context.AddTraceEvent(state); + } + ANIM_GRAPH_PROFILE_EVENT("Evaluate State"); + context.NodePath.Add(state->ID); auto rootNode = data.Graph->GetRootNode(); - return eatBox((Node*)rootNode, &rootNode->Boxes[0]); + auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]); + context.NodePath.Pop(); + + return result; } void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition) { - // Reset transiton + // Reset transition stateMachineBucket.ActiveTransition = transition; stateMachineBucket.TransitionPosition = 0.0f; @@ -537,7 +579,7 @@ AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphCon } // Evaluate source state transition data (position, length, etc.) - const Value sourceStatePtr = SampleState(state); + const Value sourceStatePtr = SampleState(context, state); auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr)) { @@ -634,7 +676,7 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node) void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Get @@ -745,7 +787,7 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto node = (AnimGraphNode*)nodeBase; switch (node->TypeID) { @@ -769,7 +811,7 @@ void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; auto box = (AnimGraphBox*)boxBase; @@ -1660,6 +1702,8 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu bucket.CurrentState = bucket.ActiveTransition->Destination; \ InitStateTransition(context, bucket) + context.NodePath.Push(node->ID); + // Update the active transition if (bucket.ActiveTransition) { @@ -1767,11 +1811,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (bucket.BaseTransitionState) { // Sample the other state (eg. when blending from interrupted state to the another state from the old destination) - value = SampleState(bucket.BaseTransitionState); + value = SampleState(context, bucket.BaseTransitionState); if (bucket.BaseTransition) { // Evaluate the base pose from the time when transition was interrupted - const auto destinationState = SampleState(bucket.BaseTransition->Destination); + const auto destinationState = SampleState(context, bucket.BaseTransition->Destination); const float alpha = bucket.BaseTransitionPosition / bucket.BaseTransition->BlendDuration; value = Blend(node, value, destinationState, alpha, bucket.BaseTransition->BlendMode); } @@ -1779,14 +1823,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu else { // Sample the current state - value = SampleState(bucket.CurrentState); + value = SampleState(context, bucket.CurrentState); } // Handle active transition blending if (bucket.ActiveTransition) { // Sample the active transition destination state - const auto destinationState = SampleState(bucket.ActiveTransition->Destination); + const auto destinationState = SampleState(context, bucket.ActiveTransition->Destination); // Perform blending const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration; @@ -1794,6 +1838,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } bucket.LastUpdateFrame = context.CurrentFrameIndex; + context.NodePath.Pop(); #undef END_TRANSITION break; } @@ -2248,7 +2293,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.ValueCache.TryGet(boxBase, value)) return; switch (node->TypeID) diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 8aa635244..ec418c5fb 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -413,10 +413,10 @@ bool Animation::Save(const StringView& path) MemoryWriteStream stream(4096); // Info - stream.WriteInt32(102); + stream.WriteInt32(103); stream.WriteDouble(Data.Duration); stream.WriteDouble(Data.FramesPerSecond); - stream.WriteBool(Data.EnableRootMotion); + stream.WriteByte((byte)Data.RootMotionFlags); stream.WriteString(Data.RootNodeName, 13); // Animation channels @@ -532,17 +532,22 @@ Asset::LoadResult Animation::load() int32 headerVersion = *(int32*)stream.GetPositionHandle(); switch (headerVersion) { - case 100: - case 101: - case 102: - { + case 103: stream.ReadInt32(&headerVersion); stream.ReadDouble(&Data.Duration); stream.ReadDouble(&Data.FramesPerSecond); - Data.EnableRootMotion = stream.ReadBool(); + stream.ReadByte((byte*)&Data.RootMotionFlags); + stream.ReadString(&Data.RootNodeName, 13); + break; + case 100: + case 101: + case 102: + stream.ReadInt32(&headerVersion); + stream.ReadDouble(&Data.Duration); + stream.ReadDouble(&Data.FramesPerSecond); + Data.RootMotionFlags = stream.ReadBool() ? AnimationRootMotionFlags::RootPositionXZ : AnimationRootMotionFlags::None; stream.ReadString(&Data.RootNodeName, 13); break; - } default: stream.ReadDouble(&Data.Duration); stream.ReadDouble(&Data.FramesPerSecond); diff --git a/Source/Engine/Content/Assets/AnimationGraph.cpp b/Source/Engine/Content/Assets/AnimationGraph.cpp index fe87abfe4..9b431aefa 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.cpp +++ b/Source/Engine/Content/Assets/AnimationGraph.cpp @@ -67,7 +67,7 @@ void AnimationGraph::OnDependencyModified(BinaryAsset* asset) #endif -bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop) +bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop, bool rootMotion) { if (!IsVirtual()) { @@ -89,7 +89,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1); rootNode.ID = 1; rootNode.Values.Resize(1); - rootNode.Values[0] = (int32)RootMotionMode::NoExtraction; + rootNode.Values[0] = (int32)(rootMotion ? RootMotionExtraction::Enable : RootMotionExtraction::Ignore); rootNode.Boxes.Resize(1); rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void); auto& animNode = graph.Nodes[1]; diff --git a/Source/Engine/Content/Assets/AnimationGraph.h b/Source/Engine/Content/Assets/AnimationGraph.h index f378b9bbe..716329347 100644 --- a/Source/Engine/Content/Assets/AnimationGraph.h +++ b/Source/Engine/Content/Assets/AnimationGraph.h @@ -37,8 +37,9 @@ public: /// The base model asset. /// The animation to play. /// True if play animation in a loop. + /// True if apply root motion. Otherwise it will be ignored. /// True if failed, otherwise false. - API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true); + API_FUNCTION() bool InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop = true, bool rootMotion = false); /// /// Tries to load surface graph from the asset. diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 897817850..2be4df29d 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -277,6 +277,8 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info) // Find asset in registry if (Cache.FindAsset(path, info)) return true; + if (!FileSystem::FileExists(path)) + return false; PROFILE_CPU(); const auto extension = FileSystem::GetExtension(path).ToLower(); diff --git a/Source/Engine/Content/JsonAsset.cpp b/Source/Engine/Content/JsonAsset.cpp index 9977a28e1..60ff29be1 100644 --- a/Source/Engine/Content/JsonAsset.cpp +++ b/Source/Engine/Content/JsonAsset.cpp @@ -393,34 +393,57 @@ bool JsonAsset::CreateInstance() if (typeHandle) { auto& type = typeHandle.GetType(); + + // Ensure that object can deserialized + const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); + if (!interface) + { + LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); + return false; + } + auto modifier = Cache::ISerializeModifier.Get(); + modifier->EngineBuild = DataEngineBuild; + + // Create object switch (type.Type) { case ScriptingTypes::Class: + case ScriptingTypes::Structure: { - // Ensure that object can deserialized - const ScriptingType::InterfaceImplementation* interface = type.GetInterface(ISerializable::TypeInitializer); - if (!interface) - { - LOG(Warning, "Cannot deserialize {0} from Json Asset because it doesn't implement ISerializable interface.", type.ToString()); - break; - } - - // Allocate object const auto instance = Allocator::Allocate(type.Size); if (!instance) return true; Instance = instance; - InstanceType = typeHandle; - _dtor = type.Class.Dtor; - type.Class.Ctor(instance); + if (type.Type == ScriptingTypes::Class) + { + _dtor = type.Class.Dtor; + type.Class.Ctor(instance); + } + else + { + _dtor = type.Struct.Dtor; + type.Struct.Ctor(instance); + } // Deserialize object - auto modifier = Cache::ISerializeModifier.Get(); - modifier->EngineBuild = DataEngineBuild; ((ISerializable*)((byte*)instance + interface->VTableOffset))->Deserialize(*Data, modifier.Value); break; } + case ScriptingTypes::Script: + { + const ScriptingObjectSpawnParams params(Guid::New(), typeHandle); + const auto instance = type.Script.Spawn(params); + if (!instance) + return true; + Instance = instance; + _dtor = nullptr; + + // Deserialize object + ToInterface(instance)->Deserialize(*Data, modifier.Value); + break; } + } + InstanceType = typeHandle; } return false; @@ -441,13 +464,20 @@ void JsonAsset::DeleteInstance() } // C++ instance - if (!Instance || !_dtor) + if (!Instance) return; - _dtor(Instance); + if (_dtor) + { + _dtor(Instance); + _dtor = nullptr; + Allocator::Free(Instance); + } + else + { + Delete((ScriptingObject*)Instance); + } InstanceType = ScriptingTypeHandle(); - Allocator::Free(Instance); Instance = nullptr; - _dtor = nullptr; } #if USE_EDITOR diff --git a/Source/Engine/Content/JsonAsset.h b/Source/Engine/Content/JsonAsset.h index a8e89cec0..21b68c691 100644 --- a/Source/Engine/Content/JsonAsset.h +++ b/Source/Engine/Content/JsonAsset.h @@ -139,7 +139,8 @@ public: T* GetInstance() const { const_cast(this)->CreateInstance(); - return Instance && InstanceType.IsAssignableFrom(T::TypeInitializer) ? (T*)Instance : nullptr; + const ScriptingTypeHandle& type = T::TypeInitializer; + return Instance && type.IsAssignableFrom(InstanceType) ? (T*)Instance : nullptr; } public: diff --git a/Source/Engine/Content/JsonAssetReference.cs b/Source/Engine/Content/JsonAssetReference.cs new file mode 100644 index 000000000..766645161 --- /dev/null +++ b/Source/Engine/Content/JsonAssetReference.cs @@ -0,0 +1,133 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; +using System.Runtime.CompilerServices; + +namespace FlaxEngine +{ + /// + /// Json asset reference utility. References resource with a typed data type. + /// + /// Type of the asset instance type. +#if FLAX_EDITOR + [CustomEditor(typeof(FlaxEditor.CustomEditors.Editors.AssetRefEditor))] +#endif + public struct JsonAssetReference : IComparable, IComparable>, IEquatable> + { + /// + /// Gets or sets the referenced asset. + /// + public JsonAsset Asset; + + /// + /// Gets the instance of the serialized object from the json asset data. Cached internally. + /// + public T Instance => (T)Asset?.Instance; + + /// + /// Initializes a new instance of the structure. + /// + /// The Json Asset. + public JsonAssetReference(JsonAsset asset) + { + Asset = asset; + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAsset(JsonAssetReference value) + { + return value.Asset; + } + + /// + /// Implicit cast operator. + /// + public static implicit operator IntPtr(JsonAssetReference value) + { + return Object.GetUnmanagedPtr(value.Asset); + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAssetReference(JsonAsset value) + { + return new JsonAssetReference(value); + } + + /// + /// Implicit cast operator. + /// + public static implicit operator JsonAssetReference(IntPtr valuePtr) + { + return new JsonAssetReference(Object.FromUnmanagedPtr(valuePtr) as JsonAsset); + } + + /// + /// Checks if the object exists (reference is not null and the unmanaged object pointer is valid). + /// + /// The object to check. + /// True if object is valid, otherwise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator bool(JsonAssetReference obj) + { + return obj.Asset; + } + + /// + /// Checks whether the two objects are equal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(JsonAssetReference left, JsonAssetReference right) + { + return left.Asset == right.Asset; + } + + /// + /// Checks whether the two objects are not equal. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(JsonAssetReference left, JsonAssetReference right) + { + return left.Asset != right.Asset; + } + + /// + public bool Equals(JsonAssetReference other) + { + return Asset == other.Asset; + } + + /// + public int CompareTo(JsonAssetReference other) + { + return Object.GetUnmanagedPtr(Asset).CompareTo(Object.GetUnmanagedPtr(other.Asset)); + } + + /// + public override bool Equals(object obj) + { + return obj is JsonAssetReference other && Asset == other.Asset; + } + + /// + public override string ToString() + { + return Asset?.ToString(); + } + + /// + public int CompareTo(object obj) + { + return obj is JsonAssetReference other ? CompareTo(other) : 1; + } + + /// + public override int GetHashCode() + { + return (Asset != null ? Asset.GetHashCode() : 0); + } + } +} diff --git a/Source/Engine/Content/JsonAssetReference.h b/Source/Engine/Content/JsonAssetReference.h new file mode 100644 index 000000000..201d0b3a3 --- /dev/null +++ b/Source/Engine/Content/JsonAssetReference.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Content/JsonAsset.h" +#include "Engine/Content/AssetReference.h" + +/// +/// Json asset reference utility. References resource with a typed data type. +/// +/// Type of the asset instance type. +template +API_STRUCT(NoDefault, Template, MarshalAs=JsonAsset*) struct JsonAssetReference : AssetReference +{ + JsonAssetReference() = default; + + JsonAssetReference(JsonAsset* asset) + { + OnSet(asset); + } + + /// + /// Gets the deserialized native object instance of the given type. Returns null if asset is not loaded or loaded object has different type. + /// + /// The asset instance object or null. + FORCE_INLINE T* GetInstance() const + { + return _asset ? Get()->template GetInstance() : nullptr; + } + + JsonAssetReference& operator=(JsonAsset* asset) noexcept + { + OnSet(asset); + return *this; + } + + operator JsonAsset*() const + { + return Get(); + } +}; diff --git a/Source/Engine/Content/Storage/ContentStorageManager.cpp b/Source/Engine/Content/Storage/ContentStorageManager.cpp index 61e73a3f2..47bdcd3b2 100644 --- a/Source/Engine/Content/Storage/ContentStorageManager.cpp +++ b/Source/Engine/Content/Storage/ContentStorageManager.cpp @@ -58,8 +58,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b Locker.Lock(); // Try fast lookup - FlaxStorage* result; - if (!StorageMap.TryGet(path, result)) + FlaxStorage* storage; + if (!StorageMap.TryGet(path, storage)) { // Detect storage type and create object const bool isPackage = path.EndsWith(StringView(PACKAGE_FILES_EXTENSION)); @@ -67,39 +67,42 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b { auto package = New(path); Packages.Add(package); - result = package; + storage = package; } else { auto file = New(path); Files.Add(file); - result = file; + storage = file; } // Register storage container - StorageMap.Add(path, result); + StorageMap.Add(path, storage); } + // Build reference (before releasing the lock so ContentStorageSystem::Job won't delete it when running from async thread) + FlaxStorageReference result(storage); + Locker.Unlock(); if (loadIt) { // Initialize storage container - result->LockChunks(); - const bool loadFailed = result->Load(); - result->UnlockChunks(); + storage->LockChunks(); + const bool loadFailed = storage->Load(); + storage->UnlockChunks(); if (loadFailed) { LOG(Error, "Failed to load {0}.", path); Locker.Lock(); StorageMap.Remove(path); - if (result->IsPackage()) - Packages.Remove((FlaxPackage*)result); + if (storage->IsPackage()) + Packages.Remove((FlaxPackage*)storage); else - Files.Remove((FlaxFile*)result); + Files.Remove((FlaxFile*)storage); Locker.Unlock(); - Delete(result); - return nullptr; + result = nullptr; + Delete(storage); } } diff --git a/Source/Engine/Content/Storage/FlaxStorage.cpp b/Source/Engine/Content/Storage/FlaxStorage.cpp index a24e01884..92d982877 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.cpp +++ b/Source/Engine/Content/Storage/FlaxStorage.cpp @@ -211,7 +211,13 @@ FlaxStorage::~FlaxStorage() #if USE_EDITOR // Ensure to close any outstanding file handles to prevent file locking in case it failed to load - _file.DeleteAll(); + Array streams; + _file.GetValues(streams); + for (FileReadStream* stream : streams) + { + if (stream) + Delete(stream); + } #endif } @@ -1266,7 +1272,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) } #if ASSETS_LOADING_EXTRA_VERIFICATION - // Validate loaded header (asset ID and type ID must be the same) if (e.ID != data.Header.ID) { @@ -1276,7 +1281,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data) { LOG(Error, "Loading asset header data mismatch! Expected Type Name: {0}, loaded header: {1}.\nSource: {2}", e.TypeName, data.Header.ToString(), ToString()); } - #endif return false; @@ -1339,7 +1343,14 @@ bool FlaxStorage::CloseFileHandles() return true; // Failed, someone is still accessing the file // Close file handles (from all threads) - _file.DeleteAll(); + Array streams; + _file.GetValues(streams); + for (FileReadStream* stream : streams) + { + if (stream) + Delete(stream); + } + _file.Clear(); return false; } diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 23fb7a35f..6a7f59154 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -93,7 +93,7 @@ protected: CriticalSection _loadLocker; // Storage - ThreadLocalObject _file; + ThreadLocal _file; Array _chunks; // Metadata diff --git a/Source/Engine/Content/Storage/FlaxStorageReference.h b/Source/Engine/Content/Storage/FlaxStorageReference.h index 03f4d0c8d..e4a5df079 100644 --- a/Source/Engine/Content/Storage/FlaxStorageReference.h +++ b/Source/Engine/Content/Storage/FlaxStorageReference.h @@ -58,17 +58,17 @@ public: return _storage != nullptr; } - FORCE_INLINE bool operator ==(const FlaxStorageReference& other) const + FORCE_INLINE bool operator==(const FlaxStorageReference& other) const { return _storage == other._storage; } - FORCE_INLINE bool operator !=(const FlaxStorageReference& other) const + FORCE_INLINE bool operator!=(const FlaxStorageReference& other) const { return _storage != other._storage; } - FORCE_INLINE FlaxStorage* operator ->() const + FORCE_INLINE FlaxStorage* operator->() const { return _storage; } diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp index a0b28f4f8..5fb5ff0c0 100644 --- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp +++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp @@ -234,7 +234,6 @@ bool AssetsImportingManager::Create(const String& tag, const StringView& outputP LOG(Warning, "Cannot find asset creator object for tag \'{0}\'.", tag); return true; } - return Create(creator->Callback, outputPath, assetId, arg); } diff --git a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp index 695e5c2a7..e9c88b68c 100644 --- a/Source/Engine/ContentImporters/CreateAnimationGraph.cpp +++ b/Source/Engine/ContentImporters/CreateAnimationGraph.cpp @@ -20,7 +20,7 @@ CreateAssetResult CreateAnimationGraph::Create(CreateAssetContext& context) rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1); rootNode.ID = 1; rootNode.Values.Resize(1); - rootNode.Values[0] = (int32)RootMotionMode::NoExtraction; + rootNode.Values[0] = (int32)RootMotionExtraction::NoExtraction; rootNode.Boxes.Resize(1); rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void); diff --git a/Source/Engine/ContentImporters/Types.h b/Source/Engine/ContentImporters/Types.h index 388c533a8..0275edc35 100644 --- a/Source/Engine/ContentImporters/Types.h +++ b/Source/Engine/ContentImporters/Types.h @@ -113,7 +113,7 @@ private: /// /// Asset importer entry /// -struct AssetImporter +struct FLAXENGINE_API AssetImporter { public: /// @@ -135,7 +135,7 @@ public: /// /// Asset creator entry /// -struct AssetCreator +struct FLAXENGINE_API AssetCreator { public: /// diff --git a/Source/Engine/Core/Collections/Array.h b/Source/Engine/Core/Collections/Array.h index a8390f051..d67aecbc1 100644 --- a/Source/Engine/Core/Collections/Array.h +++ b/Source/Engine/Core/Collections/Array.h @@ -250,6 +250,16 @@ public: return _count == 0; } + /// + /// Determines if given index is valid. + /// + /// The index. + /// true if is valid a index; otherwise, false. + bool IsValidIndex(int32 index) const + { + return index < _count && index >= 0; + } + /// /// Gets the pointer to the first item in the collection (linear allocation). /// diff --git a/Source/Engine/Core/Collections/Sorting.cpp b/Source/Engine/Core/Collections/Sorting.cpp index 49ce0f3d4..85c0fc9f1 100644 --- a/Source/Engine/Core/Collections/Sorting.cpp +++ b/Source/Engine/Core/Collections/Sorting.cpp @@ -5,11 +5,14 @@ #include "Engine/Threading/ThreadLocal.h" // Use a cached storage for the sorting (one per thread to reduce locking) -ThreadLocal SortingStacks; +ThreadLocal SortingStacks; Sorting::SortingStack& Sorting::SortingStack::Get() { - return SortingStacks.Get(); + SortingStack*& stack = SortingStacks.Get(); + if (!stack) + stack = New(); + return *stack; } Sorting::SortingStack::SortingStack() diff --git a/Source/Engine/Core/Config/GameSettings.cs b/Source/Engine/Core/Config/GameSettings.cs index 40d9f5dbc..76c968968 100644 --- a/Source/Engine/Core/Config/GameSettings.cs +++ b/Source/Engine/Core/Config/GameSettings.cs @@ -119,7 +119,7 @@ namespace FlaxEditor.Content.Settings /// /// The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair). /// - [EditorOrder(1100), EditorDisplay("Other Settings"), Tooltip("The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair).")] + [EditorOrder(1500), EditorDisplay("Other Settings"), Tooltip("The custom settings to use with a game. Can be specified by the user to define game-specific options and be used by the external plugins (used as key-value pair).")] public Dictionary CustomSettings; #if FLAX_EDITOR || PLATFORM_WINDOWS diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 81d80cb35..b639b9e64 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -6,6 +6,8 @@ #include "Engine/Graphics/Enums.h" #include "Engine/Graphics/PostProcessSettings.h" +class FontAsset; + /// /// Graphics rendering settings. /// @@ -13,6 +15,7 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings", NoConstructor) class { API_AUTO_SERIALIZATION(); DECLARE_SCRIPTING_TYPE_MINIMAL(GraphicsSettings); + public: /// /// Enables rendering synchronization with the refresh rate of the display device to avoid "tearing" artifacts. @@ -118,6 +121,12 @@ public: API_FIELD(Attributes="EditorOrder(10000), EditorDisplay(\"Post Process Settings\", EditorDisplayAttribute.InlineStyle)") PostProcessSettings PostProcessSettings; + /// + /// The list of fallback fonts used for text rendering. Ignored if empty. + /// + API_FIELD(Attributes="EditorOrder(5000), EditorDisplay(\"Text\")") + Array> FallbackFonts; + private: /// /// Renamed UeeHDRProbes into UseHDRProbes diff --git a/Source/Engine/Core/Math/BoundingFrustum.cs b/Source/Engine/Core/Math/BoundingFrustum.cs index 970957fbb..25cd0c33b 100644 --- a/Source/Engine/Core/Math/BoundingFrustum.cs +++ b/Source/Engine/Core/Math/BoundingFrustum.cs @@ -104,6 +104,16 @@ namespace FlaxEngine GetPlanesFromMatrix(ref pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom); } + /// + /// Creates a new instance of BoundingFrustum. + /// + /// Combined matrix that usually takes view × projection matrix. + public BoundingFrustum(ref Matrix matrix) + { + pMatrix = matrix; + GetPlanesFromMatrix(ref pMatrix, out pNear, out pFar, out pLeft, out pRight, out pTop, out pBottom); + } + /// /// Returns a hash code for this instance. /// diff --git a/Source/Engine/Core/Math/Color.cs b/Source/Engine/Core/Math/Color.cs index d585177a0..7474b913d 100644 --- a/Source/Engine/Core/Math/Color.cs +++ b/Source/Engine/Core/Math/Color.cs @@ -92,6 +92,21 @@ namespace FlaxEngine /// public float MaxColorComponent => Mathf.Max(Mathf.Max(R, G), B); + /// + /// Gets a minimum component value (max of r,g,b,a). + /// + public float MinValue => Math.Min(R, Math.Min(G, Math.Min(B, A))); + + /// + /// Gets a maximum component value (min of r,g,b,a). + /// + public float MaxValue => Math.Max(R, Math.Max(G, Math.Max(B, A))); + + /// + /// Gets a sum of the component values. + /// + public float ValuesSum => R + G + B + A; + /// /// Constructs a new Color with given r,g,b,a component. /// diff --git a/Source/Engine/Debug/DebugDraw.cpp b/Source/Engine/Debug/DebugDraw.cpp index 059ebbd5d..c54108b1e 100644 --- a/Source/Engine/Debug/DebugDraw.cpp +++ b/Source/Engine/Debug/DebugDraw.cpp @@ -400,10 +400,10 @@ DebugDrawCall WriteList(int32& vertexCounter, const Array& list) drawCall.StartVertex = vertexCounter; drawCall.VertexCount = list.Count() * 2; vertexCounter += drawCall.VertexCount; - Vertex* dst = DebugDrawVB->WriteReserve(drawCall.VertexCount); + Vertex* dst = DebugDrawVB->WriteReserve(list.Count() * 2); for (int32 i = 0, j = 0; i < list.Count(); i++) { - const DebugLine& l = list[i]; + const DebugLine& l = list.Get()[i]; dst[j++] = { l.Start, l.Color }; dst[j++] = { l.End, l.Color }; } @@ -416,10 +416,10 @@ DebugDrawCall WriteList(int32& vertexCounter, const Array& list) drawCall.StartVertex = vertexCounter; drawCall.VertexCount = list.Count() * 3; vertexCounter += drawCall.VertexCount; - Vertex* dst = DebugDrawVB->WriteReserve(drawCall.VertexCount); + Vertex* dst = DebugDrawVB->WriteReserve(list.Count() * 3); for (int32 i = 0, j = 0; i < list.Count(); i++) { - const DebugTriangle& l = list[i]; + const DebugTriangle& l = list.Get()[i]; dst[j++] = { l.V0, l.Color }; dst[j++] = { l.V1, l.Color }; dst[j++] = { l.V2, l.Color }; @@ -923,11 +923,40 @@ void DebugDraw::DrawActors(Actor** selectedActors, int32 selectedActorsCount, bo } } -void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) +void DebugDraw::DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, float size, float duration, bool depthTest) { + const auto rot = Quaternion::FromDirection(direction.GetNormalized()); + const Vector3 up = (rot * Vector3::Up); + const Vector3 forward = (rot * Vector3::Forward); + const Vector3 right = (rot * Vector3::Right); + const float sizeHalf = size * 0.5f; + DrawLine(origin, origin + up * sizeHalf + up, Color::Green, duration, depthTest); + DrawLine(origin, origin + forward * sizeHalf + forward, Color::Blue, duration, depthTest); + DrawLine(origin, origin + right * sizeHalf + right, Color::Red, duration, depthTest); +} + +void DebugDraw::DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color, float duration, bool depthTest) +{ + auto dir = origin + direction; + if (dir.IsNanOrInfinity()) + return; DrawLine(origin, origin + direction, color, duration, depthTest); } +void DebugDraw::DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float length, float duration, bool depthTest) +{ + if (isnan(length) || isinf(length)) + return; + DrawLine(origin, origin + (direction.GetNormalized() * length), color, duration, depthTest); +} + +void DebugDraw::DrawRay(const Ray& ray, const Color& color, float length, float duration, bool depthTest) +{ + if (isnan(length) || isinf(length)) + return; + DrawLine(ray.Position, ray.Position + (ray.Direction.GetNormalized() * length), color, duration, depthTest); +} + void DebugDraw::DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration, bool depthTest) { const Float3 startF = start - Context->Origin, endF = end - Context->Origin; @@ -1940,15 +1969,15 @@ void DebugDraw::DrawWireArc(const Vector3& position, const Quaternion& orientati DrawLine(prevPos, world.GetTranslation(), color, duration, depthTest); } -void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration, bool depthTest) +void DebugDraw::DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color, float duration, bool depthTest) { Float3 direction, up, right; Float3::Transform(Float3::Forward, orientation, direction); Float3::Transform(Float3::Up, orientation, up); Float3::Transform(Float3::Right, orientation, right); const Vector3 end = position + direction * (100.0f * scale); - const Vector3 capEnd = position + direction * (70.0f * scale); - const float arrowSidesRatio = scale * 30.0f; + const Vector3 capEnd = end - (direction * (100 * Math::Min(capScale, scale * 0.5f))); + const float arrowSidesRatio = Math::Min(capScale, scale * 0.5f) * 30.0f; DrawLine(position, end, color, duration, depthTest); DrawLine(end, capEnd + up * arrowSidesRatio, color, duration, depthTest); diff --git a/Source/Engine/Debug/DebugDraw.cs b/Source/Engine/Debug/DebugDraw.cs index bc000e03d..134c3f852 100644 --- a/Source/Engine/Debug/DebugDraw.cs +++ b/Source/Engine/Debug/DebugDraw.cs @@ -31,6 +31,17 @@ namespace FlaxEngine { } + /// + /// Draws the lines axis from direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The size of the axis. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + public static void DrawAxisFromDirection(Vector3 origin, Vector3 direction, Color color ,float Size = 100.0f, float duration = 0.0f, bool depthTest = true){} + /// /// Draws the line in a direction. /// @@ -39,7 +50,28 @@ namespace FlaxEngine /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - public static void DrawRay(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true) + public static void DrawDirection(Vector3 origin, Vector3 direction, Color color, float duration = 0.0f, bool depthTest = true){} + + /// + /// Draws the line in a direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The length of the ray. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + public static void DrawRay(Vector3 origin, Vector3 direction, Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true){} + + /// + /// Draws the line in a direction. + /// + /// The ray. + /// The color. + /// The length of the ray. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + public static void DrawRay(Ray ray,Color color, float length = 3.402823466e+38f, float duration = 0.0f, bool depthTest = true) { } @@ -218,10 +250,11 @@ namespace FlaxEngine /// The arrow origin position. /// The orientation (defines the arrow direction). /// The arrow scale (used to adjust the arrow size). + /// The arrow cap scale. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, Color color, float duration = 0.0f, bool depthTest = true) + public static void DrawWireArrow(Vector3 position, Quaternion orientation, float scale, float capScale, Color color, float duration = 0.0f, bool depthTest = true) { } diff --git a/Source/Engine/Debug/DebugDraw.h b/Source/Engine/Debug/DebugDraw.h index b02b94475..8d44b81e6 100644 --- a/Source/Engine/Debug/DebugDraw.h +++ b/Source/Engine/Debug/DebugDraw.h @@ -24,7 +24,6 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw DECLARE_SCRIPTING_TYPE_NO_SPAWN(DebugDraw); #if USE_EDITOR - /// /// Allocates the context for Debug Drawing. Can be use to redirect debug shapes collecting to a separate container (instead of global state). /// @@ -49,7 +48,6 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// /// The context or null. API_FUNCTION() static void SetContext(void* context); - #endif /// @@ -69,6 +67,16 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// True if draw all debug shapes from scenes too or false if draw just from specified actor list. API_FUNCTION() static void DrawActors(Actor** selectedActors, int32 selectedActorsCount, bool drawScenes); + /// + /// Draws the lines axis from direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The size of the axis. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawAxisFromDirection(const Vector3& origin, const Vector3& direction, float size = 100.0f, float duration = 0.0f, bool depthTest = true); + /// /// Draws the line in a direction. /// @@ -77,7 +85,28 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawDirection(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the line in a direction. + /// + /// The origin of the line. + /// The direction of the line. + /// The color. + /// The length of the ray. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawRay(const Vector3& origin, const Vector3& direction, const Color& color = Color::White, float length = MAX_float, float duration = 0.0f, bool depthTest = true); + + /// + /// Draws the line in a direction. + /// + /// The ray. + /// The color. + /// The length of the ray. + /// The duration (in seconds). Use 0 to draw it only once. + /// If set to true depth test will be performed, otherwise depth will be ignored. + API_FUNCTION() static void DrawRay(const Ray& ray, const Color& color = Color::White, float length = MAX_float, float duration = 0.0f, bool depthTest = true); /// /// Draws the line. @@ -87,7 +116,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLine(const Vector3& start, const Vector3& end, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the line. @@ -108,7 +137,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -118,7 +147,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawLines(const Array& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawLines(const Array& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -128,7 +157,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawLines(const Span& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the lines. Line positions are located one after another (e.g. l0.start, l0.end, l1.start, l1.end,...). @@ -138,7 +167,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawLines(const Array& lines, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawLines(const Array& lines, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws a Bezier curve. @@ -150,7 +179,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The line color /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBezier(const Vector3& p1, const Vector3& p2, const Vector3& p3, const Vector3& p4, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the circle. @@ -161,7 +190,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCircle(const Vector3& position, const Float3& normal, float radius, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCircle(const Vector3& position, const Float3& normal, float radius, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangle. @@ -172,7 +201,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangle. @@ -183,7 +212,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangle(const Vector3& v0, const Vector3& v1, const Vector3& v2, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -192,7 +221,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -202,7 +231,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -211,7 +240,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -221,7 +250,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -231,7 +260,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -242,7 +271,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -252,7 +281,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -263,7 +292,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -272,7 +301,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -282,7 +311,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -291,7 +320,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles. @@ -301,7 +330,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -311,7 +340,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -322,7 +351,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTriangles(const Span& vertices, const Span& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -332,7 +361,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the triangles using the given index buffer. @@ -343,7 +372,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawTriangles(const Array& vertices, const Array& indices, const Matrix& transform, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -352,7 +381,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -361,7 +390,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -371,7 +400,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -381,7 +410,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -390,7 +419,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles. @@ -399,7 +428,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -409,7 +438,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTriangles(const Span& vertices, const Span& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe triangles using the given index buffer. @@ -419,7 +448,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color, float duration = 0.0f, bool depthTest = true); + static void DrawWireTriangles(const Array& vertices, const Array& indices, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe box. @@ -428,7 +457,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireBox(const BoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireBox(const BoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe frustum. @@ -437,7 +466,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireFrustum(const BoundingFrustum& frustum, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireFrustum(const BoundingFrustum& frustum, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe box. @@ -446,7 +475,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireBox(const OrientedBoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireBox(const OrientedBoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe sphere. @@ -455,7 +484,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireSphere(const BoundingSphere& sphere, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireSphere(const BoundingSphere& sphere, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the sphere. @@ -464,7 +493,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawSphere(const BoundingSphere& sphere, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawSphere(const BoundingSphere& sphere, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the tube. @@ -476,7 +505,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe tube. @@ -488,7 +517,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireTube(const Vector3& position, const Quaternion& orientation, float radius, float length, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the cylinder. @@ -500,7 +529,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe cylinder. @@ -512,7 +541,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireCylinder(const Vector3& position, const Quaternion& orientation, float radius, float height, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the cone. @@ -525,7 +554,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe cone. @@ -538,7 +567,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireCone(const Vector3& position, const Quaternion& orientation, float radius, float angleXY, float angleXZ, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the arc. @@ -550,7 +579,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe arc. @@ -562,7 +591,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArc(const Vector3& position, const Quaternion& orientation, float radius, float angle, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the wireframe arrow. @@ -570,10 +599,11 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The arrow origin position. /// The orientation (defines the arrow direction). /// The arrow scale (used to adjust the arrow size). + /// The arrow cap scale. /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawWireArrow(const Vector3& position, const Quaternion& orientation, float scale, float capScale, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -582,7 +612,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBox(const BoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBox(const BoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the box. @@ -591,7 +621,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The duration (in seconds). Use 0 to draw it only once. /// If set to true depth test will be performed, otherwise depth will be ignored. - API_FUNCTION() static void DrawBox(const OrientedBoundingBox& box, const Color& color, float duration = 0.0f, bool depthTest = true); + API_FUNCTION() static void DrawBox(const OrientedBoundingBox& box, const Color& color = Color::White, float duration = 0.0f, bool depthTest = true); /// /// Draws the text on a screen (2D). @@ -601,7 +631,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The font size. /// The duration (in seconds). Use 0 to draw it only once. - API_FUNCTION() static void DrawText(const StringView& text, const Float2& position, const Color& color, int32 size = 20, float duration = 0.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Float2& position, const Color& color = Color::White, int32 size = 20, float duration = 0.0f); /// /// Draws the text (3D) that automatically faces the camera. @@ -612,7 +642,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The font size. /// The duration (in seconds). Use 0 to draw it only once. /// The text scale. - API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f, float scale = 1.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color = Color::White, int32 size = 32, float duration = 0.0f, float scale = 1.0f); /// /// Draws the text (3D). @@ -622,39 +652,45 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw /// The color. /// The font size. /// The duration (in seconds). Use 0 to draw it only once. - API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color, int32 size = 32, float duration = 0.0f); + API_FUNCTION() static void DrawText(const StringView& text, const Transform& transform, const Color& color = Color::White, int32 size = 32, float duration = 0.0f); }; -#define DEBUG_DRAW_RAY(origin, direction, color, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, duration, depthTest) -#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) DebugDraw::DrawLine(start, end, color, duration, depthTest) -#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) DebugDraw::DrawLines(lines, transform, color, duration, depthTest) -#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) DebugDraw::DrawBezier(p1, p2, p3, p4, color, duration, depthTest) -#define DEBUG_DRAW_CIRCLE(position, normal, radius, color, duration, depthTest) DebugDraw::DrawCircle(position, normal, radius, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawTriangle(v0, v1, v2, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, color, duration, depthTest) -#define DEBUG_DRAW_TRIANGLES_EX2(vertices, indices, transform, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, transform, color, duration, depthTest) -#define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) -#define DEBUG_DRAW_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawTube(position, orientation, radius, length, color, duration, depthTest) -#define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) -#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawCylinder(position, orientation, radius, height, color, duration, depthTest) -#define DEBUG_DRAW_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) -#define DEBUG_DRAW_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawArc(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) DebugDraw::DrawWireBox(box, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, length, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawWireCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, color, duration, depthTest) -#define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) +#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin, direction, size, duration, depthTest) DebugDraw::DrawAxisFromDirection(origin, direction, size, duration, depthTest); +#define DEBUG_DRAW_DIRECTION(origin, direction, color, duration, depthTest) DebugDraw::DrawDirection(origin, direction, color, duration, depthTest); +#define DEBUG_DRAW_RAY(origin, direction, color, length, duration, depthTest) DebugDraw::DrawRay(origin, direction, color, length, duration, depthTest); +#define DEBUG_DRAW_RAY(ray, color, length, duration, depthTest) DebugDraw::DrawRay(ray, color, length, duration, depthTest); +#define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) DebugDraw::DrawLine(start, end, color, duration, depthTest) +#define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) DebugDraw::DrawLines(lines, transform, color, duration, depthTest) +#define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) DebugDraw::DrawBezier(p1, p2, p3, p4, color, duration, depthTest) +#define DEBUG_DRAW_CIRCLE(position, normal, radius, color, duration, depthTest) DebugDraw::DrawCircle(position, normal, radius, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawTriangle(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, color, duration, depthTest) +#define DEBUG_DRAW_TRIANGLES_EX2(vertices, indices, transform, color, duration, depthTest) DebugDraw::DrawTriangles(vertices, indices, transform, color, duration, depthTest) +#define DEBUG_DRAW_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawSphere(sphere, color, duration, depthTest) +#define DEBUG_DRAW_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawTube(position, orientation, radius, length, color, duration, depthTest) +#define DEBUG_DRAW_BOX(box, color, duration, depthTest) DebugDraw::DrawBox(box, color, duration, depthTest) +#define DEBUG_DRAW_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawCylinder(position, orientation, radius, height, color, duration, depthTest) +#define DEBUG_DRAW_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) +#define DEBUG_DRAW_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawArc(position, orientation, radius, angle, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLE(v0, v1, v2, color, duration, depthTest) DebugDraw::DrawWireTriangle(v0, v1, v2, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES(vertices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TRIANGLES_EX(vertices, indices, color, duration, depthTest) DebugDraw::DrawWireTriangles(vertices, indices, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_BOX(box, color, duration, depthTest) DebugDraw::DrawWireBox(box, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_FRUSTUM(frustum, color, duration, depthTest) DebugDraw::DrawWireFrustum(frustum, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_SPHERE(sphere, color, duration, depthTest) DebugDraw::DrawWireSphere(sphere, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_TUBE(position, orientation, radius, length, color, duration, depthTest) DebugDraw::DrawWireTube(position, orientation, radius, length, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) DebugDraw::DrawWireCylinder(position, orientation, radius, height, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) DebugDraw::DrawWireCone(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) DebugDraw::DrawWireArc(position, orientation, radius, angle, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) DebugDraw::DrawWireArrow(position, orientation, scale, capScale, color, duration, depthTest) +#define DEBUG_DRAW_TEXT(text, position, color, size, duration) DebugDraw::DrawText(text, position, color, size, duration) #else +#define DEBUG_DRAW_AXIS_FROM_DIRECTION(origin, direction, size, duration, depthTest) +#define DEBUG_DRAW_DIRECTION(origin, direction,color,duration, depthTest) +#define DEBUG_DRAW_RAY(ray, color, length, duration, depthTest) #define DEBUG_DRAW_LINE(start, end, color, duration, depthTest) #define DEBUG_DRAW_LINES(lines, transform, color, duration, depthTest) #define DEBUG_DRAW_BEZIER(p1, p2, p3, p4, color, duration, depthTest) @@ -679,7 +715,7 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw #define DEBUG_DRAW_WIRE_CYLINDER(position, orientation, radius, height, color, duration, depthTest) #define DEBUG_DRAW_WIRE_CONE(position, orientation, radius, angleXY, angleXZ, color, duration, depthTest) #define DEBUG_DRAW_WIRE_ARC(position, orientation, radius, angle, color, duration, depthTest) -#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, color, duration, depthTest) +#define DEBUG_DRAW_WIRE_ARROW(position, orientation, scale, capScale, color, duration, depthTest) #define DEBUG_DRAW_TEXT(text, position, color, size, duration) #endif diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 259649062..fe7541adb 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -214,9 +214,6 @@ int32 Engine::Main(const Char* cmdLine) Time::OnEndDraw(); FrameMark; } - - // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) - Physics::CollectResults(); } // Call on exit event @@ -288,6 +285,9 @@ void Engine::OnLateFixedUpdate() // Update services EngineService::OnLateFixedUpdate(); + + // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) + Physics::CollectResults(); } void Engine::OnUpdate() diff --git a/Source/Engine/Engine/EngineService.cpp b/Source/Engine/Engine/EngineService.cpp index 7eea66853..c400ef943 100644 --- a/Source/Engine/Engine/EngineService.cpp +++ b/Source/Engine/Engine/EngineService.cpp @@ -72,9 +72,6 @@ void EngineService::OnInit() // Init services from front to back auto& services = GetServices(); -#if TRACY_ENABLE - Char nameBuffer[100]; -#endif for (int32 i = 0; i < services.Count(); i++) { const auto service = services[i]; @@ -82,6 +79,7 @@ void EngineService::OnInit() #if TRACY_ENABLE ZoneScoped; int32 nameBufferLength = 0; + Char nameBuffer[100]; for (int32 j = 0; j < name.Length(); j++) if (name[j] != ' ') nameBuffer[nameBufferLength++] = name[j]; @@ -114,6 +112,18 @@ void EngineService::OnDispose() const auto service = services[i]; if (service->IsInitialized) { +#if TRACY_ENABLE + ZoneScoped; + const StringView name(service->Name); + int32 nameBufferLength = 0; + Char nameBuffer[100]; + for (int32 j = 0; j < name.Length(); j++) + if (name[j] != ' ') + nameBuffer[nameBufferLength++] = name[j]; + Platform::MemoryCopy(nameBuffer + nameBufferLength, TEXT("::Dispose"), 10 * sizeof(Char)); + nameBufferLength += 10; + ZoneName(nameBuffer, nameBufferLength); +#endif service->IsInitialized = false; service->Dispose(); } diff --git a/Source/Engine/Engine/NativeInterop.cs b/Source/Engine/Engine/NativeInterop.cs index 026f54fec..7dfd52d22 100644 --- a/Source/Engine/Engine/NativeInterop.cs +++ b/Source/Engine/Engine/NativeInterop.cs @@ -249,7 +249,7 @@ namespace FlaxEngine.Interop /// The input array. /// Converter callback. /// The output array. - public static TDst[] ConvertArray(Span src, Func convertFunc) + public static TDst[] ConvertArray(this Span src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) @@ -265,7 +265,7 @@ namespace FlaxEngine.Interop /// The input array. /// Converter callback. /// The output array. - public static TDst[] ConvertArray(TSrc[] src, Func convertFunc) + public static TDst[] ConvertArray(this TSrc[] src, Func convertFunc) { TDst[] dst = new TDst[src.Length]; for (int i = 0; i < src.Length; i++) diff --git a/Source/Engine/Graphics/GPUBuffer.cpp b/Source/Engine/Graphics/GPUBuffer.cpp index 6e400bbdd..6d1ceea41 100644 --- a/Source/Engine/Graphics/GPUBuffer.cpp +++ b/Source/Engine/Graphics/GPUBuffer.cpp @@ -12,6 +12,7 @@ #include "Engine/Debug/Exceptions/InvalidOperationException.h" #include "Engine/Debug/Exceptions/ArgumentNullException.h" #include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Threading/Threading.h" @@ -81,33 +82,10 @@ bool GPUBufferDescription::Equals(const GPUBufferDescription& other) const String GPUBufferDescription::ToString() const { - // TODO: add tool to Format to string - - String flags; - if (Flags == GPUBufferFlags::None) - { - flags = TEXT("None"); - } - else - { - // TODO: create tool to auto convert flag enums to string - -#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUBufferFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); } - CONVERT_FLAGS_FLAGS_2_STR(ShaderResource); - CONVERT_FLAGS_FLAGS_2_STR(VertexBuffer); - CONVERT_FLAGS_FLAGS_2_STR(IndexBuffer); - CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess); - CONVERT_FLAGS_FLAGS_2_STR(Append); - CONVERT_FLAGS_FLAGS_2_STR(Counter); - CONVERT_FLAGS_FLAGS_2_STR(Argument); - CONVERT_FLAGS_FLAGS_2_STR(Structured); -#undef CONVERT_FLAGS_FLAGS_2_STR - } - return String::Format(TEXT("Size: {0}, Stride: {1}, Flags: {2}, Format: {3}, Usage: {4}"), Size, Stride, - flags, + ScriptingEnum::ToStringFlags(Flags), ScriptingEnum::ToString(Format), (int32)Usage); } @@ -212,7 +190,7 @@ GPUBuffer* GPUBuffer::ToStagingUpload() const bool GPUBuffer::Resize(uint32 newSize) { - // Validate input + PROFILE_CPU(); if (!IsAllocated()) { Log::InvalidOperationException(TEXT("Buffer.Resize")); @@ -236,12 +214,12 @@ bool GPUBuffer::DownloadData(BytesContainer& result) LOG(Warning, "Cannot download GPU buffer data from an empty buffer."); return true; } - if (_desc.Usage == GPUResourceUsage::StagingReadback || _desc.Usage == GPUResourceUsage::Dynamic) { // Use faster path for staging resources return GetData(result); } + PROFILE_CPU(); // Ensure not running on main thread if (IsInMainThread()) @@ -358,6 +336,7 @@ Task* GPUBuffer::DownloadDataAsync(BytesContainer& result) bool GPUBuffer::GetData(BytesContainer& output) { + PROFILE_CPU(); void* mapped = Map(GPUResourceMapMode::Read); if (!mapped) return true; @@ -368,6 +347,7 @@ bool GPUBuffer::GetData(BytesContainer& output) void GPUBuffer::SetData(const void* data, uint32 size) { + PROFILE_CPU(); if (size == 0 || data == nullptr) { Log::ArgumentNullException(TEXT("Buffer.SetData")); diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index f91c58cea..a9bfef470 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -8,6 +8,7 @@ #include "Engine/Core/Config/GraphicsSettings.h" #include "Engine/Engine/CommandLine.h" #include "Engine/Engine/EngineService.h" +#include "Engine/Render2D/Font.h" bool Graphics::UseVSync = false; Quality Graphics::AAQuality = Quality::Medium; @@ -69,6 +70,9 @@ void GraphicsSettings::Apply() Graphics::GIQuality = GIQuality; Graphics::PostProcessSettings = ::PostProcessSettings(); Graphics::PostProcessSettings.BlendWith(PostProcessSettings, 1.0f); +#if !USE_EDITOR // OptionsModule handles fallback fonts in Editor + Font::FallbackFonts = FallbackFonts; +#endif } void Graphics::DisposeDevice() diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index 6e6ae04b5..cc95ed6ba 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -911,10 +911,10 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const } // Info - stream->WriteInt32(100); // Header version (for fast version upgrades without serialization format change) + stream->WriteInt32(103); // Header version (for fast version upgrades without serialization format change) stream->WriteDouble(anim.Duration); stream->WriteDouble(anim.FramesPerSecond); - stream->WriteBool(anim.EnableRootMotion); + stream->WriteByte((byte)anim.RootMotionFlags); stream->WriteString(anim.RootNodeName, 13); // Animation channels @@ -928,6 +928,12 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const Serialization::Serialize(*stream, channel.Scale); } + // Animation events + stream->WriteInt32(0); + + // Nested animations + stream->WriteInt32(0); + return false; } diff --git a/Source/Engine/Graphics/Models/SkeletonData.h b/Source/Engine/Graphics/Models/SkeletonData.h index 816f69c85..83908d18a 100644 --- a/Source/Engine/Graphics/Models/SkeletonData.h +++ b/Source/Engine/Graphics/Models/SkeletonData.h @@ -91,7 +91,7 @@ public: FORCE_INLINE SkeletonNode& RootNode() { ASSERT(Nodes.HasItems()); - return Nodes[0]; + return Nodes.Get()[0]; } /// @@ -100,52 +100,24 @@ public: FORCE_INLINE const SkeletonNode& RootNode() const { ASSERT(Nodes.HasItems()); - return Nodes[0]; + return Nodes.Get()[0]; } /// /// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange. /// - void Swap(SkeletonData& other) - { - Nodes.Swap(other.Nodes); - Bones.Swap(other.Bones); - } + void Swap(SkeletonData& other); - int32 FindNode(const StringView& name) const - { - for (int32 i = 0; i < Nodes.Count(); i++) - { - if (Nodes[i].Name == name) - return i; - } - return -1; - } + Transform GetNodeTransform(int32 nodeIndex) const; + void SetNodeTransform(int32 nodeIndex, const Transform& value); - int32 FindBone(int32 nodeIndex) const - { - for (int32 i = 0; i < Bones.Count(); i++) - { - if (Bones[i].NodeIndex == nodeIndex) - return i; - } - return -1; - } + int32 FindNode(const StringView& name) const; + int32 FindBone(int32 nodeIndex) const; - uint64 GetMemoryUsage() const - { - uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone); - for (const auto& e : Nodes) - result += (e.Name.Length() + 1) * sizeof(Char); - return result; - } + uint64 GetMemoryUsage() const; /// /// Releases data. /// - void Dispose() - { - Nodes.Resize(0); - Bones.Resize(0); - } + void Dispose(); }; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 48ff53549..fc0dccf8a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -18,6 +18,69 @@ #include "Engine/Threading/Task.h" #include "Engine/Threading/Threading.h" +void SkeletonData::Swap(SkeletonData& other) +{ + Nodes.Swap(other.Nodes); + Bones.Swap(other.Bones); +} + +Transform SkeletonData::GetNodeTransform(int32 nodeIndex) const +{ + const int32 parentIndex = Nodes[nodeIndex].ParentIndex; + if (parentIndex == -1) + { + return Nodes[nodeIndex].LocalTransform; + } + const Transform parentTransform = GetNodeTransform(parentIndex); + return parentTransform.LocalToWorld(Nodes[nodeIndex].LocalTransform); +} + +void SkeletonData::SetNodeTransform(int32 nodeIndex, const Transform& value) +{ + const int32 parentIndex = Nodes[nodeIndex].ParentIndex; + if (parentIndex == -1) + { + Nodes[nodeIndex].LocalTransform = value; + return; + } + const Transform parentTransform = GetNodeTransform(parentIndex); + parentTransform.WorldToLocal(value, Nodes[nodeIndex].LocalTransform); +} + +int32 SkeletonData::FindNode(const StringView& name) const +{ + for (int32 i = 0; i < Nodes.Count(); i++) + { + if (Nodes[i].Name == name) + return i; + } + return -1; +} + +int32 SkeletonData::FindBone(int32 nodeIndex) const +{ + for (int32 i = 0; i < Bones.Count(); i++) + { + if (Bones[i].NodeIndex == nodeIndex) + return i; + } + return -1; +} + +uint64 SkeletonData::GetMemoryUsage() const +{ + uint64 result = Nodes.Capacity() * sizeof(SkeletonNode) + Bones.Capacity() * sizeof(SkeletonBone); + for (const auto& e : Nodes) + result += (e.Name.Length() + 1) * sizeof(Char); + return result; +} + +void SkeletonData::Dispose() +{ + Nodes.Resize(0); + Bones.Resize(0); +} + void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere) { _model = model; diff --git a/Source/Engine/Graphics/RenderView.cs b/Source/Engine/Graphics/RenderView.cs index b770037a3..72cfe3577 100644 --- a/Source/Engine/Graphics/RenderView.cs +++ b/Source/Engine/Graphics/RenderView.cs @@ -27,7 +27,7 @@ namespace FlaxEngine Matrix.Invert(ref View, out IV); Matrix.Invert(ref Projection, out IP); Matrix.Multiply(ref View, ref Projection, out var viewProjection); - Frustum = new BoundingFrustum(viewProjection); + Frustum = new BoundingFrustum(ref viewProjection); Matrix.Invert(ref viewProjection, out IVP); CullingFrustum = Frustum; } diff --git a/Source/Engine/Graphics/Textures/GPUTexture.cpp b/Source/Engine/Graphics/Textures/GPUTexture.cpp index 4d43e20ec..619efd21a 100644 --- a/Source/Engine/Graphics/Textures/GPUTexture.cpp +++ b/Source/Engine/Graphics/Textures/GPUTexture.cpp @@ -15,6 +15,7 @@ #include "Engine/Graphics/GPULimits.h" #include "Engine/Threading/ThreadPoolTask.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Enums.h" namespace @@ -158,29 +159,6 @@ bool GPUTextureDescription::Equals(const GPUTextureDescription& other) const String GPUTextureDescription::ToString() const { - // TODO: add tool to Format to string - - String flags; - if (Flags == GPUTextureFlags::None) - { - flags = TEXT("None"); - } - else - { - // TODO: create tool to auto convert flag enums to string - -#define CONVERT_FLAGS_FLAGS_2_STR(value) if (EnumHasAnyFlags(Flags, GPUTextureFlags::value)) { if (flags.HasChars()) flags += TEXT('|'); flags += TEXT(#value); } - CONVERT_FLAGS_FLAGS_2_STR(ShaderResource); - CONVERT_FLAGS_FLAGS_2_STR(RenderTarget); - CONVERT_FLAGS_FLAGS_2_STR(UnorderedAccess); - CONVERT_FLAGS_FLAGS_2_STR(DepthStencil); - CONVERT_FLAGS_FLAGS_2_STR(PerMipViews); - CONVERT_FLAGS_FLAGS_2_STR(PerSliceViews); - CONVERT_FLAGS_FLAGS_2_STR(ReadOnlyDepthView); - CONVERT_FLAGS_FLAGS_2_STR(BackBuffer); -#undef CONVERT_FLAGS_FLAGS_2_STR - } - return String::Format(TEXT("Size: {0}x{1}x{2}[{3}], Type: {4}, Mips: {5}, Format: {6}, MSAA: {7}, Flags: {8}, Usage: {9}"), Width, Height, @@ -190,7 +168,7 @@ String GPUTextureDescription::ToString() const MipLevels, ScriptingEnum::ToString(Format), ::ToString(MultiSampleLevel), - flags, + ScriptingEnum::ToStringFlags(Flags), (int32)Usage); } @@ -545,7 +523,7 @@ GPUTexture* GPUTexture::ToStagingUpload() const bool GPUTexture::Resize(int32 width, int32 height, int32 depth, PixelFormat format) { - // Validate texture is created + PROFILE_CPU(); if (!IsAllocated()) { LOG(Warning, "Cannot resize not created textures."); @@ -609,6 +587,7 @@ GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipInde GPUTask* GPUTexture::UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData) { + PROFILE_CPU(); ASSERT(IsAllocated()); ASSERT(mipIndex < MipLevels() && data.IsValid()); ASSERT(data.Length() >= slicePitch); @@ -700,6 +679,7 @@ bool GPUTexture::DownloadData(TextureData& result) { MISSING_CODE("support volume texture data downloading."); } + PROFILE_CPU(); // Use faster path for staging resources if (IsStaging()) @@ -781,6 +761,7 @@ Task* GPUTexture::DownloadDataAsync(TextureData& result) { MISSING_CODE("support volume texture data downloading."); } + PROFILE_CPU(); // Use faster path for staging resources if (IsStaging()) diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 7e2329389..64ad37b8e 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -1356,6 +1356,16 @@ bool Actor::IsPrefabRoot() const return _isPrefabRoot != 0; } +Actor* Actor::GetPrefabRoot() +{ + if (!HasPrefabLink()) + return nullptr; + Actor* result = this; + while (result && !result->IsPrefabRoot()) + result = result->GetParent(); + return result; +} + Actor* Actor::FindActor(const StringView& name) const { Actor* result = nullptr; @@ -1376,14 +1386,16 @@ Actor* Actor::FindActor(const StringView& name) const return result; } -Actor* Actor::FindActor(const MClass* type) const +Actor* Actor::FindActor(const MClass* type, bool activeOnly) const { CHECK_RETURN(type, nullptr); + if (activeOnly && !_isActive) + return nullptr; if (GetClass()->IsSubClassOf(type)) return const_cast(this); for (auto child : Children) { - const auto actor = child->FindActor(type); + const auto actor = child->FindActor(type, activeOnly); if (actor) return actor; } @@ -1404,14 +1416,16 @@ Actor* Actor::FindActor(const MClass* type, const StringView& name) const return nullptr; } -Actor* Actor::FindActor(const MClass* type, const Tag& tag) const +Actor* Actor::FindActor(const MClass* type, const Tag& tag, bool activeOnly) const { CHECK_RETURN(type, nullptr); + if (activeOnly && !_isActive) + return nullptr; if (GetClass()->IsSubClassOf(type) && HasTag(tag)) return const_cast(this); for (auto child : Children) { - const auto actor = child->FindActor(type, tag); + const auto actor = child->FindActor(type, tag, activeOnly); if (actor) return actor; } diff --git a/Source/Engine/Level/Actor.cs b/Source/Engine/Level/Actor.cs index dbe8a89b5..5b9d365d3 100644 --- a/Source/Engine/Level/Actor.cs +++ b/Source/Engine/Level/Actor.cs @@ -253,10 +253,11 @@ namespace FlaxEngine /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). /// /// Type of the object. + /// Finds only a active actor. /// Actor instance if found, null otherwise. - public T FindActor() where T : Actor + public T FindActor(bool activeOnly = false) where T : Actor { - return FindActor(typeof(T)) as T; + return FindActor(typeof(T), activeOnly) as T; } /// @@ -269,16 +270,17 @@ namespace FlaxEngine { return FindActor(typeof(T), name) as T; } - + /// /// Tries to find actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy). /// /// A tag on the object. /// Type of the object. + /// Finds only an active actor. /// Actor instance if found, null otherwise. - public T FindActor(Tag tag) where T : Actor + public T FindActor(Tag tag, bool activeOnly = false) where T : Actor { - return FindActor(typeof(T), tag) as T; + return FindActor(typeof(T), tag, activeOnly) as T; } /// @@ -386,5 +388,9 @@ namespace FlaxEngine { return $"{Name} ({GetType().Name})"; } + +#if FLAX_EDITOR + internal bool ShowTransform => !(this is UIControl); +#endif } } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 0ce9a0dbc..ddf6f0587 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -534,9 +534,7 @@ public: /// /// Gets actor direction vector (forward vector). /// - /// The result value. - API_PROPERTY(Attributes="HideInEditor, NoSerialize") - FORCE_INLINE Float3 GetDirection() const + API_PROPERTY(Attributes="HideInEditor, NoSerialize") FORCE_INLINE Float3 GetDirection() const { return Float3::Transform(Float3::Forward, GetOrientation()); } @@ -571,7 +569,7 @@ public: /// /// Gets local position of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Position\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Vector3), \"0,0,0\"), EditorOrder(-30), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+PositionEditor\")") FORCE_INLINE Vector3 GetLocalPosition() const { return _localTransform.Translation; @@ -587,7 +585,7 @@ public: /// Gets local rotation of the actor in parent actor space. /// /// Actor.LocalOrientation *= Quaternion.Euler(0, 10 * Time.DeltaTime, 0) - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Rotation\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Quaternion), \"0,0,0,1\"), EditorOrder(-20), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+OrientationEditor\")") FORCE_INLINE Quaternion GetLocalOrientation() const { return _localTransform.Orientation; @@ -602,7 +600,7 @@ public: /// /// Gets local scale vector of the actor in parent actor space. /// - API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")") + API_PROPERTY(Attributes="EditorDisplay(\"Transform\", \"Scale\"), VisibleIf(\"ShowTransform\"), DefaultValue(typeof(Float3), \"1,1,1\"), Limit(float.MinValue, float.MaxValue, 0.01f), EditorOrder(-10), NoSerialize, CustomEditorAlias(\"FlaxEditor.CustomEditors.Editors.ActorTransformEditor+ScaleEditor\")") FORCE_INLINE Float3 GetLocalScale() const { return _localTransform.Scale; @@ -739,6 +737,12 @@ public: /// API_PROPERTY() bool IsPrefabRoot() const; + /// + /// Gets the root of the prefab this actor is attached to. + /// + /// The root prefab object, or null if this actor is not a prefab. + API_FUNCTION() Actor* GetPrefabRoot(); + public: /// /// Tries to find the actor with the given name in this actor hierarchy (checks this actor and all children hierarchy). @@ -751,8 +755,9 @@ public: /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). /// /// Type of the actor to search for. Includes any actors derived from the type. + /// Finds only a active actor. /// Actor instance if found, null otherwise. - API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type) const; + API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false) const; /// /// Tries to find the actor of the given type and name in this actor hierarchy (checks this actor and all children hierarchy). @@ -767,8 +772,9 @@ public: /// /// Type of the actor to search for. Includes any actors derived from the type. /// The tag of the actor to search for. + /// Finds only an active actor. /// Actor instance if found, null otherwise. - API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag) const; + API_FUNCTION() Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, bool activeOnly = false) const; /// /// Tries to find the actor of the given type in this actor hierarchy (checks this actor and all children hierarchy). @@ -790,7 +796,7 @@ public: { return (T*)FindActor(T::GetStaticClass(), name); } - + /// /// Tries to find the actor of the given type and tag in this actor hierarchy (checks this actor and all children hierarchy). /// diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index e7f193ea8..296ea4284 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -1393,13 +1393,13 @@ Actor* Level::FindActor(const StringView& name) return result; } -Actor* Level::FindActor(const MClass* type) +Actor* Level::FindActor(const MClass* type, bool activeOnly) { CHECK_RETURN(type, nullptr); Actor* result = nullptr; ScopeLock lock(ScenesLock); for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++) - result = Scenes[i]->FindActor(type); + result = Scenes[i]->FindActor(type, activeOnly); return result; } @@ -1413,29 +1413,33 @@ Actor* Level::FindActor(const MClass* type, const StringView& name) return result; } -Actor* FindActorRecursive(Actor* node, const Tag& tag) +Actor* FindActorRecursive(Actor* node, const Tag& tag, bool activeOnly) { + if (activeOnly && !node->GetIsActive()) + return nullptr; if (node->HasTag(tag)) return node; Actor* result = nullptr; for (Actor* child : node->Children) { - result = FindActorRecursive(child, tag); + result = FindActorRecursive(child, tag, activeOnly); if (result) break; } return result; } -Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag) +Actor* FindActorRecursiveByType(Actor* node, const MClass* type, const Tag& tag, bool activeOnly) { CHECK_RETURN(type, nullptr); + if (activeOnly && !node->GetIsActive()) + return nullptr; if (node->HasTag(tag) && node->GetClass()->IsSubClassOf(type)) return node; Actor* result = nullptr; for (Actor* child : node->Children) { - result = FindActorRecursiveByType(child, type, tag); + result = FindActorRecursiveByType(child, type, tag, activeOnly); if (result) break; } @@ -1468,30 +1472,30 @@ void FindActorsRecursiveByParentTags(Actor* node, const Array& tags, const FindActorsRecursiveByParentTags(child, tags, activeOnly, result); } -Actor* Level::FindActor(const Tag& tag, Actor* root) +Actor* Level::FindActor(const Tag& tag, bool activeOnly, Actor* root) { PROFILE_CPU(); if (root) - return FindActorRecursive(root, tag); + return FindActorRecursive(root, tag, activeOnly); Actor* result = nullptr; for (Scene* scene : Scenes) { - result = FindActorRecursive(scene, tag); + result = FindActorRecursive(scene, tag, activeOnly); if (result) break; } return result; } -Actor* Level::FindActor(const MClass* type, const Tag& tag, Actor* root) +Actor* Level::FindActor(const MClass* type, const Tag& tag, bool activeOnly, Actor* root) { CHECK_RETURN(type, nullptr); if (root) - return FindActorRecursiveByType(root, type, tag); + return FindActorRecursiveByType(root, type, tag, activeOnly); Actor* result = nullptr; ScopeLock lock(ScenesLock); for (int32 i = 0; result == nullptr && i < Scenes.Count(); i++) - result = Scenes[i]->FindActor(type, tag); + result = Scenes[i]->FindActor(type, tag, activeOnly); return result; } @@ -1562,12 +1566,14 @@ Script* Level::FindScript(const MClass* type) namespace { - void GetActors(const MClass* type, Actor* actor, Array& result) + void GetActors(const MClass* type, Actor* actor, bool activeOnly, Array& result) { + if (activeOnly && !actor->GetIsActive()) + return; if (actor->GetClass()->IsSubClassOf(type)) result.Add(actor); for (auto child : actor->Children) - GetActors(type, child, result); + GetActors(type, child, activeOnly, result); } void GetScripts(const MClass* type, Actor* actor, Array& result) @@ -1580,13 +1586,13 @@ namespace } } -Array Level::GetActors(const MClass* type) +Array Level::GetActors(const MClass* type, bool activeOnly) { Array result; CHECK_RETURN(type, result); ScopeLock lock(ScenesLock); for (int32 i = 0; i < Scenes.Count(); i++) - ::GetActors(type, Scenes[i], result); + ::GetActors(type, Scenes[i], activeOnly, result); return result; } diff --git a/Source/Engine/Level/Level.cs b/Source/Engine/Level/Level.cs index 1e8524f2c..3e367195e 100644 --- a/Source/Engine/Level/Level.cs +++ b/Source/Engine/Level/Level.cs @@ -109,6 +109,8 @@ namespace FlaxEngine public static T[] GetScripts() where T : Script { var scripts = GetScripts(typeof(T)); + if (scripts.Length == 0) + return Array.Empty(); var result = new T[scripts.Length]; for (int i = 0; i < scripts.Length; i++) result[i] = scripts[i] as T; @@ -119,10 +121,13 @@ namespace FlaxEngine /// Finds all the actors of the given type in all the loaded scenes. /// /// Type of the object. + /// Finds only active actors. /// Found actors list. - public static T[] GetActors() where T : Actor + public static T[] GetActors(bool activeOnly = false) where T : Actor { - var actors = GetActors(typeof(T)); + var actors = GetActors(typeof(T), activeOnly); + if (actors.Length == 0) + return Array.Empty(); var result = new T[actors.Length]; for (int i = 0; i < actors.Length; i++) result[i] = actors[i] as T; diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index e09f8e376..ac6a14238 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -360,8 +360,9 @@ public: /// Tries to find the actor of the given type in all the loaded scenes. /// /// Type of the actor to search for. Includes any actors derived from the type. + /// Finds only an active actor. /// Found actor or null. - API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type); + API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false); /// /// Tries to find the actor of the given type and name in all the loaded scenes. @@ -375,18 +376,20 @@ public: /// Tries to find the actor with the given tag (returns the first one found). /// /// The tag of the actor to search for. + /// Finds only an active actor. /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes. /// Found actor or null. - API_FUNCTION() static Actor* FindActor(const Tag& tag, Actor* root = nullptr); + API_FUNCTION() static Actor* FindActor(const Tag& tag, bool activeOnly = false, Actor* root = nullptr); /// /// Tries to find the actor of the given type and tag in all the loaded scenes. /// /// Type of the actor to search for. Includes any actors derived from the type. /// The tag of the actor to search for. + /// Finds only an active actor. /// The custom root actor to start searching from (hierarchical), otherwise null to search all loaded scenes. /// Actor instance if found, null otherwise. - API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, Actor* root = nullptr); + API_FUNCTION() static Actor* FindActor(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, const Tag& tag, bool activeOnly = false, Actor* root = nullptr); /// /// Tries to find the actors with the given tag (returns all found). @@ -460,8 +463,9 @@ public: /// Finds all the actors of the given type in all the loaded scenes. /// /// Type of the actor to search for. Includes any actors derived from the type. + /// Finds only active actors in the scene. /// Found actors list. - API_FUNCTION() static Array GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type); + API_FUNCTION() static Array GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false); /// /// Finds all the scripts of the given type in all the loaded scenes. diff --git a/Source/Engine/Level/SceneObjectsFactory.cpp b/Source/Engine/Level/SceneObjectsFactory.cpp index d9c86d250..b60e5fc7d 100644 --- a/Source/Engine/Level/SceneObjectsFactory.cpp +++ b/Source/Engine/Level/SceneObjectsFactory.cpp @@ -63,7 +63,7 @@ SceneObjectsFactory::Context::~Context() { if (Async) { - Array> modifiers; + Array> modifiers; Modifiers.GetValues(modifiers); for (ISerializeModifier* e : modifiers) { @@ -475,7 +475,7 @@ void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyn const ISerializable::DeserializeStream* prefabData; if (prefab->ObjectsDataCache.TryGet(prefabObjectId, prefabData) && JsonTools::GetGuidIfValid(prefabObjectId, *prefabData, "PrefabObjectID")) { - prefabId = JsonTools::GetGuid(stream, "PrefabID"); + prefabId = JsonTools::GetGuid(*prefabData, "PrefabID"); prefab = Content::LoadAsync(prefabId); if (prefab && !prefab->WaitForLoaded()) { diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index f9efba4a5..407bd8c58 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -714,6 +714,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b stream->SenderId = senderClientId; // Deserialize object + Scripting::ObjectsLookupIdMapping.Set(&IdsRemappingTable); const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false); if (failed) { diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp index 6ea4b8b4e..f198fdcd7 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp @@ -52,7 +52,7 @@ namespace int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) { const auto node = _graph.SpawnModules[index]; - auto& context = Context.Get(); + auto& context = *Context.Get(); auto& data = context.Data->SpawnModulesData[index]; // Accumulate the previous frame fraction @@ -120,7 +120,7 @@ int32 ParticleEmitterGraphCPUExecutor::ProcessSpawnModule(int32 index) void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode* node, int32 particlesStart, int32 particlesEnd) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto stride = context.Data->Buffer->Stride; auto start = context.Data->Buffer->GetParticleCPU(particlesStart); diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp index cb2d7004e..4c30746c3 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.Particles.cpp @@ -12,7 +12,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Get @@ -168,7 +168,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTextures(Box* box, Node* node, void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Linearize Depth @@ -202,7 +202,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupTools(Box* box, Node* node, Va void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* nodeBase, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); auto node = (ParticleEmitterGraphCPUNode*)nodeBase; switch (node->TypeID) { @@ -468,7 +468,7 @@ void ParticleEmitterGraphCPUExecutor::ProcessGroupParticles(Box* box, Node* node void ParticleEmitterGraphCPUExecutor::ProcessGroupFunction(Box* box, Node* node, Value& value) { - auto& context = Context.Get(); + auto& context = *Context.Get(); switch (node->TypeID) { // Function Input diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp index a8f7898e1..12ecd054b 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp @@ -8,7 +8,7 @@ #include "Engine/Engine/Time.h" #include "Engine/Profiler/ProfilerCPU.h" -ThreadLocal ParticleEmitterGraphCPUExecutor::Context; +ThreadLocal ParticleEmitterGraphCPUExecutor::Context; namespace { @@ -122,7 +122,10 @@ ParticleEmitterGraphCPUExecutor::ParticleEmitterGraphCPUExecutor(ParticleEmitter void ParticleEmitterGraphCPUExecutor::Init(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt) { - auto& context = Context.Get(); + auto& contextPtr = Context.Get(); + if (!contextPtr) + contextPtr = New(); + auto& context = *contextPtr; context.GraphStack.Clear(); context.GraphStack.Push(&_graph); context.Data = &data; @@ -252,8 +255,8 @@ bool ParticleEmitterGraphCPUExecutor::ComputeBounds(ParticleEmitter* emitter, Pa case 401: { // Prepare graph data - auto& context = Context.Get(); Init(emitter, effect, data); + auto& context = *Context.Get(); // Find the maximum radius of the particle light float maxRadius = 0.0f; @@ -377,7 +380,7 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff // Prepare graph data Init(emitter, effect, data); - auto& context = Context.Get(); + auto& context = *Context.Get(); // Draw lights for (int32 moduleIndex = 0; moduleIndex < emitter->Graph.LightModules.Count(); moduleIndex++) @@ -571,7 +574,6 @@ int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, Par PROFILE_CPU_NAMED("Spawn"); // Prepare data - auto& context = Context.Get(); Init(emitter, effect, data, dt); // Spawn particles @@ -587,7 +589,7 @@ int32 ParticleEmitterGraphCPUExecutor::UpdateSpawn(ParticleEmitter* emitter, Par VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box* box) { // Check if graph is looped or is too deep - auto& context = Context.Get(); + auto& context = *Context.Get(); if (context.CallStackSize >= PARTICLE_EMITTER_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); @@ -618,6 +620,6 @@ VisjectExecutor::Value ParticleEmitterGraphCPUExecutor::eatBox(Node* caller, Box VisjectExecutor::Graph* ParticleEmitterGraphCPUExecutor::GetCurrentGraph() const { - auto& context = Context.Get(); + auto& context = *Context.Get(); return (Graph*)context.GraphStack.Peek(); } diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h index 4f55da6e2..34a65d721 100644 --- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h +++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h @@ -133,7 +133,7 @@ private: ParticleEmitterGraphCPU& _graph; // Per-thread context to allow async execution - static ThreadLocal Context; + static ThreadLocal Context; public: /// diff --git a/Source/Engine/Physics/Actors/Cloth.cpp b/Source/Engine/Physics/Actors/Cloth.cpp index b2db80b0b..a35acb066 100644 --- a/Source/Engine/Physics/Actors/Cloth.cpp +++ b/Source/Engine/Physics/Actors/Cloth.cpp @@ -415,9 +415,9 @@ void Cloth::OnDebugDrawSelected() c1 = Color::Lerp(Color::Red, Color::White, _paint[i1]); c2 = Color::Lerp(Color::Red, Color::White, _paint[i2]); } - DebugDraw::DrawLine(v0, v1, c0, c1, 0, false); - DebugDraw::DrawLine(v1, v2, c1, c2, 0, false); - DebugDraw::DrawLine(v2, v0, c2, c0, 0, false); + DebugDraw::DrawLine(v0, v1, c0, c1, 0, DebugDrawDepthTest); + DebugDraw::DrawLine(v1, v2, c1, c2, 0, DebugDrawDepthTest); + DebugDraw::DrawLine(v2, v0, c2, c0, 0, DebugDrawDepthTest); } PhysicsBackend::UnlockClothParticles(_cloth); } diff --git a/Source/Engine/Physics/Actors/Cloth.h b/Source/Engine/Physics/Actors/Cloth.h index 6137ec3b6..1b97330e0 100644 --- a/Source/Engine/Physics/Actors/Cloth.h +++ b/Source/Engine/Physics/Actors/Cloth.h @@ -332,6 +332,11 @@ public: bool OnPreUpdate(); void OnPostUpdate(); +private: +#if USE_EDITOR + API_FIELD(Internal) bool DebugDrawDepthTest = true; +#endif + public: // [Actor] void Draw(RenderContext& renderContext) override; diff --git a/Source/Engine/Physics/Actors/PhysicsColliderActor.h b/Source/Engine/Physics/Actors/PhysicsColliderActor.h index caea76c79..92693695d 100644 --- a/Source/Engine/Physics/Actors/PhysicsColliderActor.h +++ b/Source/Engine/Physics/Actors/PhysicsColliderActor.h @@ -5,6 +5,7 @@ #include "Engine/Level/Actor.h" #include "Engine/Physics/Collisions.h" +struct RayCastHit; struct Collision; /// @@ -42,6 +43,40 @@ public: /// The rigid body or null. API_PROPERTY() virtual RigidBody* GetAttachedRigidBody() const = 0; + /// + /// Performs a raycast against this collider shape. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. + /// The maximum distance the ray should check for collisions. + /// True if ray hits an object, otherwise false. + API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const = 0; + + /// + /// Performs a raycast against this collider, returns results in a RaycastHit structure. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The result hit information. Valid only when method returns true. + /// The maximum distance the ray should check for collisions. + /// True if ray hits an object, otherwise false. + API_FUNCTION(Sealed) virtual bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const = 0; + + /// + /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. + /// + /// The position to find the closest point to it. + /// The result point on the collider that is closest to the specified location. + API_FUNCTION(Sealed) virtual void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const = 0; + + /// + /// Checks if a point is inside the collider. + /// + /// The point to check if is contained by the collider shape (in world-space). + /// True if collider shape contains a given point, otherwise false. + API_FUNCTION(Sealed) virtual bool ContainsPoint(const Vector3& point) const = 0; + public: /// /// Called when a collision start gets registered for this collider (it collides with something). diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 9b36a92ff..db15dd46f 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -206,11 +206,11 @@ void CharacterController::CreateController() _cachedScale = GetScale(); const float scaling = _cachedScale.GetAbsolute().MaxValue(); const Vector3 position = _transform.LocalToWorld(_center); - _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material.Get(), Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); + _controller = PhysicsBackend::CreateController(GetPhysicsScene()->GetPhysicsScene(), this, this, _contactOffset, position, _slopeLimit, (int32)_nonWalkableMode, Material, Math::Abs(_radius) * scaling, Math::Abs(_height) * scaling, _stepOffset, _shape); // Setup PhysicsBackend::SetControllerUpDirection(_controller, _upDirection); - PhysicsBackend::SetShapeLocalPose(_shape, _center, Quaternion::Identity); + PhysicsBackend::SetShapeLocalPose(_shape, Vector3::Zero, Quaternion::Identity); UpdateLayerBits(); UpdateBounds(); } @@ -280,12 +280,8 @@ void CharacterController::OnActiveTransformChanged() // Change actor transform (but with locking) ASSERT(!_isUpdatingTransform); _isUpdatingTransform = true; - Transform transform; - PhysicsBackend::GetRigidActorPose(PhysicsBackend::GetShapeActor(_shape), transform.Translation, transform.Orientation); - transform.Translation -= _center; - transform.Orientation = _transform.Orientation; - transform.Scale = _transform.Scale; - SetTransform(transform); + const Vector3 position = PhysicsBackend::GetControllerPosition(_controller) - _center; + SetPosition(position); _isUpdatingTransform = false; UpdateBounds(); diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 4326a033d..e354057d2 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -201,7 +201,7 @@ void Collider::CreateShape() // Create shape const bool isTrigger = _isTrigger && CanBeTrigger(); - _shape = PhysicsBackend::CreateShape(this, shape, Material.Get(), IsActiveInHierarchy(), isTrigger); + _shape = PhysicsBackend::CreateShape(this, shape, Material, IsActiveInHierarchy(), isTrigger); PhysicsBackend::SetShapeContactOffset(_shape, _contactOffset); UpdateLayerBits(); } @@ -288,7 +288,7 @@ void Collider::OnMaterialChanged() { // Update the shape material if (_shape) - PhysicsBackend::SetShapeMaterial(_shape, Material.Get()); + PhysicsBackend::SetShapeMaterial(_shape, Material); } void Collider::BeginPlay(SceneBeginData* data) diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index bd17aa27a..68f5fe346 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -4,7 +4,7 @@ #include "Engine/Physics/Types.h" #include "Engine/Content/JsonAsset.h" -#include "Engine/Content/AssetReference.h" +#include "Engine/Content/JsonAssetReference.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" struct RayCastHit; @@ -80,44 +80,10 @@ public: /// /// The physical material used to define the collider physical properties. /// - API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), AssetReference(typeof(PhysicalMaterial), true), EditorDisplay(\"Collider\")") - AssetReference Material; + API_FIELD(Attributes="EditorOrder(2), DefaultValue(null), EditorDisplay(\"Collider\")") + JsonAssetReference Material; public: - /// - /// Performs a raycast against this collider shape. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; - - /// - /// Performs a raycast against this collider, returns results in a RaycastHit structure. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The result hit information. Valid only when method returns true. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; - - /// - /// Gets a point on the collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. - /// - /// The position to find the closest point to it. - /// The result point on the collider that is closest to the specified location. - API_FUNCTION() void ClosestPoint(const Vector3& point, API_PARAM(Out) Vector3& result) const; - - /// - /// Checks if a point is inside the collider. - /// - /// The point to check if is contained by the collider shape (in world-space). - /// True if collider shape contains a given point, otherwise false. - API_FUNCTION() bool ContainsPoint(const Vector3& point) const; - /// /// Computes minimum translational distance between two geometry objects. /// Translating the first collider by direction * distance will separate the colliders apart if the function returned true. Otherwise, direction and distance are not defined. @@ -198,6 +164,10 @@ private: public: // [PhysicsColliderActor] RigidBody* GetAttachedRigidBody() const override; + bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final; + bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final; + void ClosestPoint(const Vector3& point, Vector3& result) const final; + bool ContainsPoint(const Vector3& point) const final; protected: // [PhysicsColliderActor] diff --git a/Source/Engine/Physics/Collisions.h b/Source/Engine/Physics/Collisions.h index 7336e8d5c..c1a2679a4 100644 --- a/Source/Engine/Physics/Collisions.h +++ b/Source/Engine/Physics/Collisions.h @@ -9,7 +9,7 @@ class PhysicsColliderActor; /// /// Contains a contact point data for the collision location. /// -API_STRUCT() struct FLAXENGINE_API ContactPoint +API_STRUCT(NoDefault) struct FLAXENGINE_API ContactPoint { DECLARE_SCRIPTING_TYPE_MINIMAL(ContactPoint); @@ -41,7 +41,7 @@ struct TIsPODType /// /// Contains a collision information passed to the OnCollisionEnter/OnCollisionExit events. /// -API_STRUCT() struct FLAXENGINE_API Collision +API_STRUCT(NoDefault) struct FLAXENGINE_API Collision { DECLARE_SCRIPTING_TYPE_MINIMAL(Collision); @@ -58,9 +58,7 @@ API_STRUCT() struct FLAXENGINE_API Collision /// /// The total impulse applied to this contact pair to resolve the collision. /// - /// - /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair. - /// + /// The total impulse is obtained by summing up impulses applied at all contact points in this collision pair. API_FIELD() Vector3 Impulse; /// @@ -81,15 +79,13 @@ API_STRUCT() struct FLAXENGINE_API Collision /// /// The contacts locations. /// - API_FIELD(Private, NoArray) ContactPoint Contacts[COLLISION_NAX_CONTACT_POINTS]; + API_FIELD(Internal, NoArray) ContactPoint Contacts[COLLISION_NAX_CONTACT_POINTS]; public: /// /// Gets the relative linear velocity of the two colliding objects. /// - /// - /// Can be used to detect stronger collisions. - /// + /// Can be used to detect stronger collisions. Vector3 GetRelativeVelocity() const { return ThisVelocity - OtherVelocity; diff --git a/Source/Engine/Physics/Joints/D6Joint.cpp b/Source/Engine/Physics/Joints/D6Joint.cpp index a5ef035a3..685fa760a 100644 --- a/Source/Engine/Physics/Joints/D6Joint.cpp +++ b/Source/Engine/Physics/Joints/D6Joint.cpp @@ -159,7 +159,8 @@ void D6Joint::OnDebugDrawSelected() const float twistSize = 9.0f; const Color swingColor = Color::Green.AlphaMultiplied(0.6f); const Color twistColor = Color::Yellow.AlphaMultiplied(0.5f); - DEBUG_DRAW_WIRE_ARROW(target, targetRotation, swingSize / 100.0f * 0.5f, Color::Red, 0, false); + const float arrowSize = swingSize / 100.0f * 0.5f; + DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false); if (_motion[(int32)D6JointAxis::SwingY] == D6JointMotion::Locked && _motion[(int32)D6JointAxis::SwingZ] == D6JointMotion::Locked) { // Swing is locked diff --git a/Source/Engine/Physics/Joints/HingeJoint.cpp b/Source/Engine/Physics/Joints/HingeJoint.cpp index 7f85941ef..6952711e8 100644 --- a/Source/Engine/Physics/Joints/HingeJoint.cpp +++ b/Source/Engine/Physics/Joints/HingeJoint.cpp @@ -63,8 +63,9 @@ void HingeJoint::OnDebugDrawSelected() const Quaternion targetRotation = GetTargetOrientation() * xRotation; const float size = 15.0f; const Color color = Color::Green.AlphaMultiplied(0.6f); - DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, size / 100.0f * 0.5f, Color::Red, 0, false); - DEBUG_DRAW_WIRE_ARROW(target, targetRotation, size / 100.0f * 0.5f, Color::Blue, 0, false); + const float arrowSize = size / 100.0f * 0.5f; + DEBUG_DRAW_WIRE_ARROW(source, sourceRotation, arrowSize, arrowSize * 0.5f, Color::Red, 0, false); + DEBUG_DRAW_WIRE_ARROW(target, targetRotation, arrowSize, arrowSize * 0.5f, Color::Blue, 0, false); if (EnumHasAnyFlags(_flags, HingeJointFlag::Limit)) { const float upper = Math::Max(_limit.Upper, _limit.Lower); diff --git a/Source/Engine/Physics/Joints/SphericalJoint.cpp b/Source/Engine/Physics/Joints/SphericalJoint.cpp index c44d4bc6e..84ab9d6e9 100644 --- a/Source/Engine/Physics/Joints/SphericalJoint.cpp +++ b/Source/Engine/Physics/Joints/SphericalJoint.cpp @@ -38,8 +38,9 @@ void SphericalJoint::OnDebugDrawSelected() const Vector3 source = GetPosition(); const Vector3 target = GetTargetPosition(); const float size = 15.0f; + const float arrowSize = size / 100.0f * 0.5f; const Color color = Color::Green.AlphaMultiplied(0.6f); - DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), size / 100.0f * 0.5f, Color::Red, 0, false); + DEBUG_DRAW_WIRE_ARROW(source, GetOrientation(), arrowSize, arrowSize * 0.5f, Color::Red, 0, false); if (EnumHasAnyFlags(_flags, SphericalJointFlag::Limit)) { DEBUG_DRAW_CONE(source, GetOrientation(), size, _limit.YLimitAngle * DegreesToRadians, _limit.ZLimitAngle * DegreesToRadians, color, 0, false); diff --git a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp index 99a5abc56..75c088db7 100644 --- a/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp +++ b/Source/Engine/Physics/PhysX/PhysicsBackendPhysX.cpp @@ -228,9 +228,7 @@ class QueryFilterPhysX : public PxQueryFilterCallback // Check mask const PxFilterData shapeFilter = shape->getQueryFilterData(); if ((filterData.word0 & shapeFilter.word0) == 0) - { return PxQueryHitType::eNONE; - } // Check if skip triggers const bool hitTriggers = filterData.word2 != 0; @@ -483,8 +481,10 @@ protected: } }; +#define PxHitFlagEmpty (PxHitFlags)0 +#define SCENE_QUERY_FLAGS (PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV) + #define SCENE_QUERY_SETUP(blockSingle) auto scenePhysX = (ScenePhysX*)scene; if (scene == nullptr) return false; \ - const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eUV; \ PxQueryFilterData filterData; \ filterData.flags |= PxQueryFlag::ePREFILTER; \ filterData.data.word0 = layerMask; \ @@ -644,6 +644,19 @@ void GetShapeGeometry(const CollisionShape& shape, PxGeometryHolder& geometry) } } +void GetShapeMaterials(Array>& materialsPhysX, Span materials) +{ + materialsPhysX.Resize(materials.Length()); + for (int32 i = 0; i < materials.Length(); i++) + { + PxMaterial* materialPhysX = DefaultMaterial; + const JsonAsset* material = materials.Get()[i]; + if (material && !material->WaitForLoaded() && material->Instance) + materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); + materialsPhysX.Get()[i] = materialPhysX; + } +} + PxFilterFlags FilterShader( PxFilterObjectAttributes attributes0, PxFilterData filterData0, PxFilterObjectAttributes attributes1, PxFilterData filterData1, @@ -1735,8 +1748,6 @@ void PhysicsBackend::EndSimulateScene(void* scene) { PROFILE_CPU_NAMED("Physics.SendEvents"); - - scenePhysX->EventsCallback.CollectResults(); scenePhysX->EventsCallback.SendTriggerEvents(); scenePhysX->EventsCallback.SendCollisionEvents(); scenePhysX->EventsCallback.SendJointEvents(); @@ -1880,14 +1891,14 @@ bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& { SCENE_QUERY_SETUP(true); PxRaycastBuffer buffer; - return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::RayCast(void* scene, const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers) { SCENE_QUERY_SETUP(true); PxRaycastBuffer buffer; - if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1897,7 +1908,7 @@ bool PhysicsBackend::RayCastAll(void* scene, const Vector3& origin, const Vector { SCENE_QUERY_SETUP(false); DynamicHitBuffer buffer; - if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->raycast(C2P(origin - scenePhysX->Origin), C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1908,7 +1919,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1916,7 +1927,7 @@ bool PhysicsBackend::BoxCast(void* scene, const Vector3& center, const Vector3& SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1927,7 +1938,7 @@ bool PhysicsBackend::BoxCastAll(void* scene, const Vector3& center, const Vector SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxBoxGeometry geometry(C2P(halfExtents)); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1938,7 +1949,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1946,7 +1957,7 @@ bool PhysicsBackend::SphereCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1957,7 +1968,7 @@ bool PhysicsBackend::SphereCastAll(void* scene, const Vector3& center, const flo SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin)); const PxSphereGeometry geometry(radius); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1968,7 +1979,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -1976,7 +1987,7 @@ bool PhysicsBackend::CapsuleCast(void* scene, const Vector3& center, const float SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -1987,7 +1998,7 @@ bool PhysicsBackend::CapsuleCastAll(void* scene, const Vector3& center, const fl SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxCapsuleGeometry geometry(radius, height * 0.5f); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -1999,7 +2010,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter); + return scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, PxHitFlagEmpty, filterData, &QueryFilter); } bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers) @@ -2008,7 +2019,7 @@ bool PhysicsBackend::ConvexCast(void* scene, const Vector3& center, const Collis SCENE_QUERY_SETUP_SWEEP_1(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_SINGLE(); return true; @@ -2020,7 +2031,7 @@ bool PhysicsBackend::ConvexCastAll(void* scene, const Vector3& center, const Col SCENE_QUERY_SETUP_SWEEP(); const PxTransform pose(C2P(center - scenePhysX->Origin), C2P(rotation)); const PxConvexMeshGeometry geometry((PxConvexMesh*)convexMesh->GetConvex(), PxMeshScale(C2P(scale))); - if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, &QueryFilter)) + if (!scenePhysX->Scene->sweep(geometry, pose, C2P(direction), maxDistance, buffer, SCENE_QUERY_FLAGS, filterData, &QueryFilter)) return false; SCENE_QUERY_COLLECT_ALL(); return true; @@ -2451,17 +2462,14 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq actorPhysX->addTorque(C2P(torque), static_cast(mode)); } -void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) +void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger) { const PxShapeFlags shapeFlags = GetShapeFlags(trigger, enabled); - PxMaterial* materialPhysX = DefaultMaterial; - if (material && !material->WaitForLoaded() && material->Instance) - { - materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); - } + Array> materialsPhysX; + GetShapeMaterials(materialsPhysX, materials); PxGeometryHolder geometryPhysX; GetShapeGeometry(geometry, geometryPhysX); - PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), *materialPhysX, true, shapeFlags); + PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), materialsPhysX.Get(), materialsPhysX.Count(), true, shapeFlags); shapePhysX->userData = collider; #if PHYSX_DEBUG_NAMING shapePhysX->setName("Shape"); @@ -2551,15 +2559,12 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value) shapePhysX->setContactOffset(Math::Max(shapePhysX->getRestOffset() + ZeroTolerance, value)); } -void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material) +void PhysicsBackend::SetShapeMaterials(void* shape, Span materials) { auto shapePhysX = (PxShape*)shape; - PxMaterial* materialPhysX = DefaultMaterial; - if (material && !material->WaitForLoaded() && material->Instance) - { - materialPhysX = (PxMaterial*)((PhysicalMaterial*)material->Instance)->GetPhysicsMaterial(); - } - shapePhysX->setMaterials(&materialPhysX, 1); + Array> materialsPhysX; + GetShapeMaterials(materialsPhysX, materials); + shapePhysX->setMaterials(materialsPhysX.Get(), materialsPhysX.Count()); } void PhysicsBackend::SetShapeGeometry(void* shape, const CollisionShape& geometry) @@ -2608,9 +2613,8 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu auto shapePhysX = (PxShape*)shape; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); - const PxHitFlags hitFlags = (PxHitFlags)0; PxRaycastHit hit; - if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) != 0) + if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, PxHitFlagEmpty, 1, &hit) != 0) { resultHitDistance = hit.distance; return true; @@ -2623,10 +2627,10 @@ bool PhysicsBackend::RayCastShape(void* shape, const Vector3& position, const Qu auto shapePhysX = (PxShape*)shape; const Vector3 sceneOrigin = SceneOrigins[shapePhysX->getActor() ? shapePhysX->getActor()->getScene() : nullptr]; const PxTransform trans(C2P(position - sceneOrigin), C2P(orientation)); - const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX | PxHitFlag::eUV; PxRaycastHit hit; - if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, hitFlags, 1, &hit) == 0) + if (PxGeometryQuery::raycast(C2P(origin - sceneOrigin), C2P(direction), shapePhysX->getGeometry(), trans, maxDistance, SCENE_QUERY_FLAGS, 1, &hit) == 0) return false; + hit.shape = shapePhysX; P2C(hit, hitInfo); hitInfo.Point += sceneOrigin; return true; @@ -3004,7 +3008,7 @@ void* PhysicsBackend::CreateController(void* scene, IPhysicsActor* actor, Physic desc.material = DefaultMaterial; const float minSize = 0.001f; desc.height = Math::Max(height, minSize); - desc.radius = Math::Max(radius - desc.contactOffset, minSize); + desc.radius = Math::Max(radius - Math::Max(contactOffset, 0.0f), minSize); desc.stepOffset = Math::Min(stepOffset, desc.height + desc.radius * 2.0f - minSize); auto controllerPhysX = (PxCapsuleController*)scenePhysX->ControllerManager->createController(desc); PxRigidActor* actorPhysX = controllerPhysX->getActor(); @@ -4081,10 +4085,17 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c columns = (int32)heightFieldPhysX->getNbColumns(); } -float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z) +float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z) { auto heightFieldPhysX = (PxHeightField*)heightField; - return heightFieldPhysX->getHeight(x, z); + return heightFieldPhysX->getHeight((float)x, (float)z); +} + +PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z) +{ + auto heightFieldPhysX = (PxHeightField*)heightField; + auto sample = heightFieldPhysX->getSample(x, z); + return { sample.height, sample.materialIndex0, sample.materialIndex1 }; } bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data) diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp index 5781e4641..62e588771 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.cpp @@ -38,10 +38,6 @@ void SimulationEventCallback::Clear() BrokenJoints.Clear(); } -void SimulationEventCallback::CollectResults() -{ -} - void SimulationEventCallback::SendCollisionEvents() { for (auto& c : RemovedCollisions) @@ -132,7 +128,6 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c //const PxU32 flippedContacts = (pair.flags & PxContactPairFlag::eINTERNAL_CONTACTS_ARE_FLIPPED); const bool hasImpulses = pair.flags.isSet(PxContactPairFlag::eINTERNAL_HAS_IMPULSES); const bool hasPostVelocities = !pair.flags.isSet(PxContactPairFlag::eACTOR_PAIR_LOST_TOUCH); - PxU32 nbContacts = 0; PxVec3 totalImpulse(0.0f); c.ThisActor = static_cast(pair.shapes[0]->userData); @@ -144,48 +139,38 @@ void SimulationEventCallback::onContact(const PxContactPairHeader& pairHeader, c } // Extract contact points + c.ContactsCount = 0; while (i.hasNextPatch()) { i.nextPatch(); - while (i.hasNextContact() && nbContacts < COLLISION_NAX_CONTACT_POINTS) + while (i.hasNextContact() && c.ContactsCount < COLLISION_NAX_CONTACT_POINTS) { i.nextContact(); - const PxVec3 point = i.getContactPoint(); const PxVec3 normal = i.getContactNormal(); if (hasImpulses) - totalImpulse += normal * impulses[nbContacts]; + totalImpulse += normal * impulses[c.ContactsCount]; - //PxU32 internalFaceIndex0 = flippedContacts ? iter.getFaceIndex1() : iter.getFaceIndex0(); - //PxU32 internalFaceIndex1 = flippedContacts ? iter.getFaceIndex0() : iter.getFaceIndex1(); - - ContactPoint& contact = c.Contacts[nbContacts]; + ContactPoint& contact = c.Contacts[c.ContactsCount++]; contact.Point = P2C(point); contact.Normal = P2C(normal); contact.Separation = i.getSeparation(); - - nbContacts++; } } + c.Impulse = P2C(totalImpulse); // Extract velocities c.ThisVelocity = c.OtherVelocity = Vector3::Zero; if (hasPostVelocities && j.nextItemSet()) { - ASSERT(j.contactPairIndex == pairIndex); + ASSERT_LOW_LAYER(j.contactPairIndex == pairIndex); if (j.postSolverVelocity) { - const PxVec3 linearVelocityActor0 = j.postSolverVelocity->linearVelocity[0]; - const PxVec3 linearVelocityActor1 = j.postSolverVelocity->linearVelocity[1]; - - c.ThisVelocity = P2C(linearVelocityActor0); - c.OtherVelocity = P2C(linearVelocityActor1); + c.ThisVelocity = P2C(j.postSolverVelocity->linearVelocity[0]); + c.OtherVelocity = P2C(j.postSolverVelocity->linearVelocity[1]); } } - c.ContactsCount = nbContacts; - c.Impulse = P2C(totalImpulse); - if (pair.flags & PxContactPairFlag::eACTOR_PAIR_HAS_FIRST_TOUCH) { NewCollisions.Add(c); diff --git a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h index f9081df66..f10f926eb 100644 --- a/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h +++ b/Source/Engine/Physics/PhysX/SimulationEventCallbackPhysX.h @@ -49,11 +49,6 @@ public: /// void Clear(); - /// - /// Generates the new/old/removed collisions and a valid trigger pairs. - /// - void CollectResults(); - /// /// Sends the collision events to the managed objects. /// diff --git a/Source/Engine/Physics/PhysX/Types.h b/Source/Engine/Physics/PhysX/Types.h index d95d6b140..c86a03a62 100644 --- a/Source/Engine/Physics/PhysX/Types.h +++ b/Source/Engine/Physics/PhysX/Types.h @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace physx @@ -233,12 +234,28 @@ inline float RadPerSToRpm(float v) return v * (30.0f / PI); } +inline PhysicalMaterial* GetMaterial(const PxShape* shape, PxU32 faceIndex) +{ + if (faceIndex != 0xFFFFffff) + { + PxBaseMaterial* mat = shape->getMaterialFromInternalFaceIndex(faceIndex); + return mat ? (PhysicalMaterial*)mat->userData : nullptr; + } + else + { + PxMaterial* mat; + shape->getMaterials(&mat, 1); + return mat ? (PhysicalMaterial*)mat->userData : nullptr; + } +} + inline void P2C(const PxRaycastHit& hit, RayCastHit& result) { result.Point = P2C(hit.position); result.Normal = P2C(hit.normal); result.Distance = hit.distance; result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr; + result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr; result.FaceIndex = hit.faceIndex; result.UV.X = hit.u; result.UV.Y = hit.v; @@ -250,6 +267,7 @@ inline void P2C(const PxSweepHit& hit, RayCastHit& result) result.Normal = P2C(hit.normal); result.Distance = hit.distance; result.Collider = hit.shape ? static_cast(hit.shape->userData) : nullptr; + result.Material = hit.shape ? GetMaterial(hit.shape, hit.faceIndex) : nullptr; result.FaceIndex = hit.faceIndex; result.UV = Vector2::Zero; } diff --git a/Source/Engine/Physics/PhysicalMaterial.h b/Source/Engine/Physics/PhysicalMaterial.h index b9c2e4554..cb8202876 100644 --- a/Source/Engine/Physics/PhysicalMaterial.h +++ b/Source/Engine/Physics/PhysicalMaterial.h @@ -4,27 +4,21 @@ #include "Types.h" #include "Engine/Core/ISerializable.h" +#include "Engine/Scripting/ScriptingObject.h" #include "Engine/Level/Tags.h" /// /// Physical materials are used to define the response of a physical object when interacting dynamically with the world. /// -API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") class FLAXENGINE_API PhysicalMaterial final : public ISerializable +API_CLASS(Attributes = "ContentContextMenu(\"New/Physics/Physical Material\")") +class FLAXENGINE_API PhysicalMaterial final : public ScriptingObject, public ISerializable { API_AUTO_SERIALIZATION(); - DECLARE_SCRIPTING_TYPE_MINIMAL(PhysicalMaterial); + DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(PhysicalMaterial, ScriptingObject); private: - void* _material; + void* _material = nullptr; public: - /// - /// Initializes a new instance of the class. - /// - PhysicalMaterial(); - - /// - /// Finalizes an instance of the class. - /// ~PhysicalMaterial(); public: diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 5d9b218ec..942f1b79b 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -78,11 +78,6 @@ void PhysicsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* } } -PhysicalMaterial::PhysicalMaterial() - : _material(nullptr) -{ -} - PhysicalMaterial::~PhysicalMaterial() { if (_material) diff --git a/Source/Engine/Physics/PhysicsBackend.h b/Source/Engine/Physics/PhysicsBackend.h index f20683e31..d7393df2c 100644 --- a/Source/Engine/Physics/PhysicsBackend.h +++ b/Source/Engine/Physics/PhysicsBackend.h @@ -4,6 +4,7 @@ #include "Physics.h" #include "PhysicsSettings.h" +#include "Engine/Core/Types/Span.h" struct HingeJointDrive; struct SpringParameters; @@ -182,7 +183,7 @@ public: static void AddRigidDynamicActorTorque(void* actor, const Vector3& torque, ForceMode mode); // Shapes - static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger); + static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger); static void SetShapeState(void* shape, bool enabled, bool trigger); static void SetShapeFilterMask(void* shape, uint32 mask0, uint32 mask1); static void* GetShapeActor(void* shape); @@ -191,7 +192,7 @@ public: static void GetShapeLocalPose(void* shape, Vector3& position, Quaternion& orientation); static void SetShapeLocalPose(void* shape, const Vector3& position, const Quaternion& orientation); static void SetShapeContactOffset(void* shape, float value); - static void SetShapeMaterial(void* shape, JsonAsset* material); + static void SetShapeMaterials(void* shape, Span materials); static void SetShapeGeometry(void* shape, const CollisionShape& geometry); static void AttachShape(void* shape, void* actor); static void DetachShape(void* shape, void* actor); @@ -303,7 +304,8 @@ public: static void GetTriangleMeshTriangles(void* triangleMesh, Array& vertexBuffer, Array& indexBuffer); static const uint32* GetTriangleMeshRemap(void* triangleMesh, uint32& count); static void GetHeightFieldSize(void* heightField, int32& rows, int32& columns); - static float GetHeightFieldHeight(void* heightField, float x, float z); + static float GetHeightFieldHeight(void* heightField, int32 x, int32 z); + static HeightFieldSample GetHeightFieldSample(void* heightField, int32 x, int32 z); static bool ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data); static void FlushRequests(); static void FlushRequests(void* scene); @@ -330,6 +332,14 @@ public: flags = (RigidDynamicFlags)(((uint32)flags & ~(uint32)flag) | (value ? (uint32)flag : 0)); SetRigidDynamicActorFlags(actor, flags); } + FORCE_INLINE static void* CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) + { + return CreateShape(collider, geometry, Span(&material, 1), enabled, trigger); + } + FORCE_INLINE static void SetShapeMaterial(void* shape, JsonAsset* material) + { + SetShapeMaterials(shape, Span(&material, 1)); + } }; DECLARE_ENUM_OPERATORS(PhysicsBackend::ActorFlags); diff --git a/Source/Engine/Physics/PhysicsBackendEmpty.cpp b/Source/Engine/Physics/PhysicsBackendEmpty.cpp index 1a418af0c..e26898494 100644 --- a/Source/Engine/Physics/PhysicsBackendEmpty.cpp +++ b/Source/Engine/Physics/PhysicsBackendEmpty.cpp @@ -408,7 +408,7 @@ void PhysicsBackend::AddRigidDynamicActorTorque(void* actor, const Vector3& torq { } -void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, JsonAsset* material, bool enabled, bool trigger) +void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const CollisionShape& geometry, Span materials, bool enabled, bool trigger) { return DUMY_HANDLE; } @@ -447,7 +447,7 @@ void PhysicsBackend::SetShapeContactOffset(void* shape, float value) { } -void PhysicsBackend::SetShapeMaterial(void* shape, JsonAsset* material) +void PhysicsBackend::SetShapeMaterials(void* shape, Span materials) { } @@ -826,11 +826,16 @@ void PhysicsBackend::GetHeightFieldSize(void* heightField, int32& rows, int32& c columns = 0; } -float PhysicsBackend::GetHeightFieldHeight(void* heightField, float x, float z) +float PhysicsBackend::GetHeightFieldHeight(void* heightField, int32 x, int32 z) { return 0.0f; } +PhysicsBackend::HeightFieldSample PhysicsBackend::GetHeightFieldSample(void* heightField, int32 x, int32 z) +{ + return HeightFieldSample(); +} + bool PhysicsBackend::ModifyHeightField(void* heightField, int32 startCol, int32 startRow, int32 cols, int32 rows, const HeightFieldSample* data) { return true; diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index ed12f8611..f7cb9f89c 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -10,6 +10,7 @@ struct PhysicsStatistics; class PhysicsColliderActor; class PhysicsScene; +class PhysicalMaterial; class Joint; class Collider; class CollisionData; @@ -132,7 +133,7 @@ DECLARE_ENUM_OPERATORS(RigidbodyConstraints); /// /// Raycast hit result data. /// -API_STRUCT() struct RayCastHit +API_STRUCT(NoDefault) struct RayCastHit { DECLARE_SCRIPTING_TYPE_NO_SPAWN(RayCastHit); @@ -141,6 +142,11 @@ API_STRUCT() struct RayCastHit /// API_FIELD() PhysicsColliderActor* Collider = nullptr; + /// + /// The physical material of the surface that was hit. + /// + API_FIELD() PhysicalMaterial* Material = nullptr; + /// /// The normal of the surface the ray hit. /// @@ -151,17 +157,17 @@ API_STRUCT() struct RayCastHit /// API_FIELD() float Distance; + /// + /// The point in the world space where ray hit the collider. + /// + API_FIELD() Vector3 Point; + /// /// The index of the face that was hit. Valid only for convex mesh (polygon index), triangle mesh (triangle index) and height field (triangle index). /// /// API_FIELD() uint32 FaceIndex; - /// - /// The point in the world space where ray hit the collider. - /// - API_FIELD() Vector3 Point; - /// /// The barycentric coordinates of hit triangle. Valid only for triangle mesh and height field. /// diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index e2244d306..061f21e2a 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -41,6 +41,10 @@ static_assert(sizeof(bool) == 1, "Invalid bool type size."); static_assert(sizeof(float) == 4, "Invalid float type size."); static_assert(sizeof(double) == 8, "Invalid double type size."); +// Check configuration +static_assert((PLATFORM_THREADS_LIMIT & (PLATFORM_THREADS_LIMIT - 1)) == 0, "Threads limit must be power of two."); +static_assert(PLATFORM_THREADS_LIMIT % 4 == 0, "Threads limit must be multiple of 4."); + float PlatformBase::CustomDpiScale = 1.0f; Array> PlatformBase::Users; Delegate PlatformBase::UserAdded; diff --git a/Source/Engine/Platform/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp index 49b4cc631..f4b840a5e 100644 --- a/Source/Engine/Platform/Base/WindowBase.cpp +++ b/Source/Engine/Platform/Base/WindowBase.cpp @@ -100,9 +100,6 @@ WindowBase::WindowBase(const CreateWindowSettings& settings) , _dpi(96) , _dpiScale(1.0f) , _trackingMouseOffset(Float2::Zero) - , _isUsingMouseOffset(false) - , _isTrackingMouse(false) - , _isClippingCursor(false) , RenderTask(nullptr) { // Update window location based on start location diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index 812359fb4..4c5082c9c 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -284,12 +284,12 @@ protected: float _dpiScale; Float2 _trackingMouseOffset; - bool _isUsingMouseOffset; + bool _isUsingMouseOffset = false; Rectangle _mouseOffsetScreenSize; - bool _isTrackingMouse; - bool _isHorizontalFlippingMouse; - bool _isVerticalFlippingMouse; - bool _isClippingCursor; + bool _isTrackingMouse = false; + bool _isHorizontalFlippingMouse = false; + bool _isVerticalFlippingMouse = false; + bool _isClippingCursor = false; explicit WindowBase(const CreateWindowSettings& settings); virtual ~WindowBase(); diff --git a/Source/Engine/Render2D/Font.cpp b/Source/Engine/Render2D/Font.cpp index 90423f5ce..973bf21e3 100644 --- a/Source/Engine/Render2D/Font.cpp +++ b/Source/Engine/Render2D/Font.cpp @@ -7,6 +7,8 @@ #include "Engine/Threading/Threading.h" #include "IncludeFreeType.h" +Array, HeapAllocation> Font::FallbackFonts; + Font::Font(FontAsset* parentAsset, float size) : ManagedScriptingObject(SpawnParams(Guid::New(), Font::TypeInitializer)) , _asset(parentAsset) @@ -32,7 +34,7 @@ Font::~Font() _asset->_fonts.Remove(this); } -void Font::GetCharacter(Char c, FontCharacterEntry& result) +void Font::GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback) { // Try to get the character or cache it if cannot be found if (!_characters.TryGet(c, result)) @@ -44,8 +46,23 @@ void Font::GetCharacter(Char c, FontCharacterEntry& result) if (_characters.TryGet(c, result)) return; + // Try to use fallback font if character is missing + if (enableFallback && !_asset->ContainsChar(c)) + { + for (int32 fallbackIndex = 0; fallbackIndex < FallbackFonts.Count(); fallbackIndex++) + { + FontAsset* fallbackFont = FallbackFonts.Get()[fallbackIndex].Get(); + if (fallbackFont && fallbackFont->ContainsChar(c)) + { + fallbackFont->CreateFont(GetSize())->GetCharacter(c, result, enableFallback); + return; + } + } + } + // Create character cache FontManager::AddNewEntry(this, c, result); + ASSERT(result.Font); // Add to the dictionary _characters.Add(c, result); @@ -87,7 +104,7 @@ void Font::CacheText(const StringView& text) FontCharacterEntry entry; for (int32 i = 0; i < text.Length(); i++) { - GetCharacter(text[i], entry); + GetCharacter(text[i], entry, false); } } @@ -104,12 +121,14 @@ void Font::Invalidate() void Font::ProcessText(const StringView& text, Array& outputLines, const TextLayoutOptions& layout) { + int32 textLength = text.Length(); + if (textLength == 0) + return; float cursorX = 0; int32 kerning; FontLineCache tmpLine; FontCharacterEntry entry; FontCharacterEntry previous; - int32 textLength = text.Length(); float scale = layout.Scale / FontManager::FontScale; float boundsWidth = layout.Bounds.GetWidth(); float baseLinesDistance = static_cast(_height) * layout.BaseLinesGapScale * scale; @@ -157,7 +176,7 @@ void Font::ProcessText(const StringView& text, Array& outputLines // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -178,8 +197,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines if (lastWrapCharIndex != INVALID_INDEX) { // Skip moving twice for the same character - int32 lastLineLasCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; - if (lastLineLasCharIndex == lastWrapCharIndex || lastLineLasCharIndex == lastWrapCharIndex - 1 || lastLineLasCharIndex == lastWrapCharIndex - 2) + int32 lastLineLastCharIndex = outputLines.HasItems() ? outputLines.Last().LastCharIndex : -10000; + if (lastLineLastCharIndex == lastWrapCharIndex || lastLineLastCharIndex == lastWrapCharIndex - 1 || lastLineLastCharIndex == lastWrapCharIndex - 2) { currentIndex = nextCharIndex; lastMoveLine = moveLine; @@ -238,7 +257,8 @@ void Font::ProcessText(const StringView& text, Array& outputLines lastMoveLine = moveLine; } - if (textLength != 0 && (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n')) + // Check if an additional line should be created + if (tmpLine.LastCharIndex >= tmpLine.FirstCharIndex || text[textLength - 1] == '\n') { // Add line tmpLine.Size.X = cursorX; @@ -341,7 +361,7 @@ int32 Font::HitTestText(const StringView& text, const Float2& location, const Te // Apply kerning if (!isWhitespace && previous.IsValid) { - x += GetKerning(previous.Character, entry.Character); + x += entry.Font->GetKerning(previous.Character, entry.Character); } previous = entry; @@ -415,7 +435,7 @@ Float2 Font::GetCharPosition(const StringView& text, int32 index, const TextLayo // Apply kerning if (!isWhitespace && previous.IsValid) { - x += GetKerning(previous.Character, entry.Character); + x += entry.Font->GetKerning(previous.Character, entry.Character); } previous = entry; diff --git a/Source/Engine/Render2D/Font.h b/Source/Engine/Render2D/Font.h index 90f723cd8..c55bb58a1 100644 --- a/Source/Engine/Render2D/Font.h +++ b/Source/Engine/Render2D/Font.h @@ -18,9 +18,9 @@ struct FontTextureAtlasSlot; /// /// The text range. /// -API_STRUCT(NoDefault) struct TextRange +API_STRUCT(NoDefault) struct FLAXENGINE_API TextRange { -DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); + DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// The start index (inclusive). @@ -35,7 +35,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// Gets the range length. /// - int32 Length() const + FORCE_INLINE int32 Length() const { return EndIndex - StartIndex; } @@ -43,7 +43,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// Gets a value indicating whether range is empty. /// - bool IsEmpty() const + FORCE_INLINE bool IsEmpty() const { return (EndIndex - StartIndex) <= 0; } @@ -53,7 +53,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(TextRange); /// /// The index. /// true if range contains the specified character index; otherwise, false. - bool Contains(int32 index) const + FORCE_INLINE bool Contains(int32 index) const { return index >= StartIndex && index < EndIndex; } @@ -88,9 +88,9 @@ struct TIsPODType /// /// The font line info generated during text processing. /// -API_STRUCT(NoDefault) struct FontLineCache +API_STRUCT(NoDefault) struct FLAXENGINE_API FontLineCache { -DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); + DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); /// /// The root position of the line (upper left corner). @@ -108,7 +108,7 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FontLineCache); API_FIELD() int32 FirstCharIndex; /// - /// The last character index (from the input text). + /// The last character index (from the input text), inclusive. /// API_FIELD() int32 LastCharIndex; }; @@ -152,9 +152,9 @@ struct TIsPODType /// /// The cached font character entry (read for rendering and further processing). /// -API_STRUCT(NoDefault) struct FontCharacterEntry +API_STRUCT(NoDefault) struct FLAXENGINE_API FontCharacterEntry { -DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); + DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); /// /// The character represented by this entry. @@ -210,6 +210,11 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(FontCharacterEntry); /// The slot in texture atlas, containing the pixel data of the glyph. /// API_FIELD() const FontTextureAtlasSlot* Slot; + + /// + /// The owner font. + /// + API_FIELD() const class Font* Font; }; template<> @@ -223,10 +228,10 @@ struct TIsPODType /// API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API Font : public ManagedScriptingObject { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(Font); friend FontAsset; -private: +private: FontAsset* _asset; float _size; int32 _height; @@ -238,7 +243,6 @@ private: mutable Dictionary _kerningTable; public: - /// /// Initializes a new instance of the class. /// @@ -252,6 +256,10 @@ public: ~Font(); public: + /// + /// The active fallback fonts. + /// + API_FIELD() static Array, HeapAllocation> FallbackFonts; /// /// Gets parent font asset that contains font family used by this font. @@ -302,13 +310,13 @@ public: } public: - /// /// Gets character entry. /// /// The character. /// The output character entry. - void GetCharacter(Char c, FontCharacterEntry& result); + /// True if fallback to secondary font when the primary font doesn't contains this character. + void GetCharacter(Char c, FontCharacterEntry& result, bool enableFallback = true); /// /// Gets the kerning amount for a pair of characters. @@ -330,7 +338,6 @@ public: API_FUNCTION() void Invalidate(); public: - /// /// Processes text to get cached lines for rendering. /// @@ -524,7 +531,6 @@ public: void FlushFaceSize() const; public: - // [Object] String ToString() const override; }; diff --git a/Source/Engine/Render2D/FontAsset.cpp b/Source/Engine/Render2D/FontAsset.cpp index a477e5f4d..53fbdc930 100644 --- a/Source/Engine/Render2D/FontAsset.cpp +++ b/Source/Engine/Render2D/FontAsset.cpp @@ -199,14 +199,29 @@ bool FontAsset::Save(const StringView& path) #endif +bool FontAsset::ContainsChar(Char c) const +{ + return FT_Get_Char_Index(_face, c) > 0; +} + void FontAsset::Invalidate() { ScopeLock lock(Locker); - for (auto font : _fonts) - { font->Invalidate(); - } +} + +uint64 FontAsset::GetMemoryUsage() const +{ + Locker.Lock(); + uint64 result = BinaryAsset::GetMemoryUsage(); + result += sizeof(FontAsset) - sizeof(BinaryAsset); + result += sizeof(FT_FaceRec); + result += _fontFile.Length(); + for (auto font : _fonts) + result += sizeof(Font); + Locker.Unlock(); + return result; } bool FontAsset::init(AssetInitData& initData) diff --git a/Source/Engine/Render2D/FontAsset.h b/Source/Engine/Render2D/FontAsset.h index 4dea84a5b..a93276bf1 100644 --- a/Source/Engine/Render2D/FontAsset.h +++ b/Source/Engine/Render2D/FontAsset.h @@ -93,6 +93,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API FontAsset : public BinaryAsset { DECLARE_BINARY_ASSET_HEADER(FontAsset, 3); friend Font; + private: FT_Face _face; FontOptions _options; @@ -174,11 +175,22 @@ public: API_FUNCTION() bool Save(const StringView& path = StringView::Empty); #endif + /// + /// Check if the font contains the glyph of a char. + /// + /// The char to test. + /// True if the font contains the glyph of the char, otherwise false. + API_FUNCTION() bool ContainsChar(Char c) const; + /// /// Invalidates all cached dynamic font atlases using this font. Can be used to reload font characters after changing font asset options. /// API_FUNCTION() void Invalidate(); +public: + // [BinaryAsset] + uint64 GetMemoryUsage() const override; + protected: // [BinaryAsset] bool init(AssetInitData& initData) override; diff --git a/Source/Engine/Render2D/FontManager.cpp b/Source/Engine/Render2D/FontManager.cpp index bb7edf974..7aef7098e 100644 --- a/Source/Engine/Render2D/FontManager.cpp +++ b/Source/Engine/Render2D/FontManager.cpp @@ -27,7 +27,6 @@ using namespace FontManagerImpl; class FontManagerService : public EngineService { public: - FontManagerService() : EngineService(TEXT("Font Manager"), -700) { @@ -155,6 +154,18 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) // Get the index to the glyph in the font face const FT_UInt glyphIndex = FT_Get_Char_Index(face, c); +#if !BUILD_RELEASE + if (glyphIndex == 0) + { + LOG(Warning, "Font `{}` doesn't contain character `\\u{:x}`, consider choosing another font. ", String(face->family_name), c); + } +#endif + + // Init the character data + Platform::MemoryClear(&entry, sizeof(entry)); + entry.Character = c; + entry.Font = font; + entry.IsValid = false; // Load the glyph const FT_Error error = FT_Load_Glyph(face, glyphIndex, glyphFlags); @@ -190,8 +201,6 @@ bool FontManager::AddNewEntry(Font* font, Char c, FontCharacterEntry& entry) ASSERT(bitmap && bitmap->pixel_mode == FT_PIXEL_MODE_GRAY); // Fill the character data - Platform::MemoryClear(&entry, sizeof(entry)); - entry.Character = c; entry.AdvanceX = Convert26Dot6ToRoundedPixel(glyph->advance.x); entry.OffsetY = glyph->bitmap_top; entry.OffsetX = glyph->bitmap_left; diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp index b56c3e1a2..34cb2c693 100644 --- a/Source/Engine/Render2D/Render2D.cpp +++ b/Source/Engine/Render2D/Render2D.cpp @@ -27,11 +27,11 @@ #if USE_EDITOR #define RENDER2D_CHECK_RENDERING_STATE \ - if (!Render2D::IsRendering()) \ - { \ - LOG(Error, "Calling Render2D is only valid during rendering."); \ - return; \ - } + if (!Render2D::IsRendering()) \ + { \ + LOG(Error, "Calling Render2D is only valid during rendering."); \ + return; \ + } #else #define RENDER2D_CHECK_RENDERING_STATE #endif @@ -180,7 +180,7 @@ struct ClipMask Rectangle Bounds; }; -Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping; +Render2D::RenderingFeatures Render2D::Features = RenderingFeatures::VertexSnapping | RenderingFeatures::FallbackFonts; namespace { @@ -1137,8 +1137,8 @@ void DrawBatch(int32 startIndex, int32 count) } // Draw - Context->BindVB(ToSpan(&vb, 1)); // TODO: reduce bindings frequency - Context->BindIB(ib); // TODO: reduce bindings frequency + Context->BindVB(ToSpan(&vb, 1)); + Context->BindIB(ib); Context->DrawIndexed(countIb, 0, d.StartIB); } @@ -1159,6 +1159,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, FontCharacterEntry previous; int32 kerning; float scale = 1.0f / FontManager::FontScale; + const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Render all characters FontCharacterEntry entry; @@ -1183,7 +1184,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, if (currentChar != '\n') { // Get character entry - font->GetCharacter(currentChar, entry); + font->GetCharacter(currentChar, entry, enableFallbackFonts); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1210,7 +1211,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, // Get kerning if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -1273,6 +1274,7 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, FontCharacterEntry previous; int32 kerning; float scale = layout.Scale / FontManager::FontScale; + const bool enableFallbackFonts = EnumHasAllFlags(Features, RenderingFeatures::FallbackFonts); // Process text to get lines Lines.Clear(); @@ -1299,10 +1301,14 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, // Render all characters from the line for (int32 charIndex = line.FirstCharIndex; charIndex <= line.LastCharIndex; charIndex++) { - const Char c = text[charIndex]; - if (c != '\n') + // Cache current character + const Char currentChar = text[charIndex]; + + // Check if it isn't a newline character + if (currentChar != '\n') { - font->GetCharacter(c, entry); + // Get character entry + font->GetCharacter(currentChar, entry, enableFallbackFonts); // Check if need to select/change font atlas (since characters even in the same font may be located in different atlases) if (fontAtlas == nullptr || entry.TextureIndex != fontAtlasIndex) @@ -1324,10 +1330,10 @@ void Render2D::DrawText(Font* font, const StringView& text, const Color& color, } // Get kerning - const bool isWhitespace = StringUtils::IsWhitespace(c); + const bool isWhitespace = StringUtils::IsWhitespace(currentChar); if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { @@ -1931,7 +1937,7 @@ void Render2D::DrawBlur(const Rectangle& rect, float blurStrength) void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& vertices, const Span& uvs) { RENDER2D_CHECK_RENDERING_STATE; - CHECK(vertices.Length() == uvs.Length()) + CHECK(vertices.Length() == uvs.Length()); Render2DDrawCall& drawCall = DrawCalls.AddOne(); drawCall.Type = DrawCallType::FillTexture; @@ -1977,7 +1983,7 @@ void Render2D::DrawTexturedTriangles(GPUTexture* t, const Span& indices, drawCall.StartIB = IBIndex; drawCall.CountIB = indices.Length(); drawCall.AsTexture.Ptr = t; - + for (int32 i = 0; i < indices.Length();) { const uint16 i0 = indices.Get()[i++]; diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h index 2b890ced9..5e5e952dc 100644 --- a/Source/Engine/Render2D/Render2D.h +++ b/Source/Engine/Render2D/Render2D.h @@ -44,6 +44,11 @@ API_CLASS(Static) class FLAXENGINE_API Render2D /// Enables automatic geometry vertices snapping to integer coordinates in screen space. Reduces aliasing and sampling artifacts. Might be disabled for 3D projection viewport or for complex UI transformations. /// VertexSnapping = 1, + + /// + /// Enables automatic characters usage from fallback fonts. + /// + FallbackFonts = 2, }; struct CustomData @@ -452,3 +457,5 @@ public: /// The color. API_FUNCTION() static void FillTriangle(const Float2& p0, const Float2& p1, const Float2& p2, const Color& color); }; + +DECLARE_ENUM_OPERATORS(Render2D::RenderingFeatures); diff --git a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs index cd2e962e6..0603a9390 100644 --- a/Source/Engine/Scripting/Attributes/CollectionAttribute.cs +++ b/Source/Engine/Scripting/Attributes/CollectionAttribute.cs @@ -61,6 +61,16 @@ namespace FlaxEngine /// public float Spacing; + /// + /// The minimum size of the collection. + /// + public int MinCount; + + /// + /// The maximum size of the collection. Zero if unlimited. + /// + public int MaxCount; + /// /// The collection background color. /// diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs new file mode 100644 index 000000000..6e08217bf --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/RequireActorAttribute.cs @@ -0,0 +1,25 @@ +using System; + +namespace FlaxEngine; + +/// +/// This attribute is used to check for if a script requires an Actor type. +/// +[Serializable] +[AttributeUsage(AttributeTargets.Class)] +public class RequireActorAttribute : Attribute +{ + /// + /// The required type. + /// + public Type RequiredType; + + /// + /// Initializes a new instance of the class. + /// + /// The required type. + public RequireActorAttribute(Type type) + { + RequiredType = type; + } +} diff --git a/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs new file mode 100644 index 000000000..414a13aeb --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/RequireScriptAttribute.cs @@ -0,0 +1,34 @@ +using System; + +namespace FlaxEngine; + +/// +/// This attribute is used to check for if a script requires other script types. +/// +[Serializable] +[AttributeUsage(AttributeTargets.Class)] +public class RequireScriptAttribute : Attribute +{ + /// + /// The required types. + /// + public Type[] RequiredTypes; + + /// + /// Initializes a new instance of the class. + /// + /// The required type. + public RequireScriptAttribute(Type type) + { + RequiredTypes = new[] { type }; + } + + /// + /// Initializes a new instance of the class. + /// + /// The required types. + public RequireScriptAttribute(Type[] types) + { + RequiredTypes = types; + } +} diff --git a/Source/Engine/Scripting/BinaryModule.cpp b/Source/Engine/Scripting/BinaryModule.cpp index 44ef32503..0cded22b4 100644 --- a/Source/Engine/Scripting/BinaryModule.cpp +++ b/Source/Engine/Scripting/BinaryModule.cpp @@ -533,7 +533,12 @@ void ScriptingType::HackObjectVTable(void* object, ScriptingTypeHandle baseTypeH if (!Script.VTable) { // Ensure to have valid Script VTable hacked - SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex); + BinaryModule::Locker.Lock(); + if (!Script.VTable) + { + SetupScriptObjectVTable(object, baseTypeHandle, wrapperIndex); + } + BinaryModule::Locker.Unlock(); } // Override object vtable with hacked one that has calls to overriden scripting functions diff --git a/Source/Engine/Scripting/Enums.h b/Source/Engine/Scripting/Enums.h index dd605d303..7c8d36f24 100644 --- a/Source/Engine/Scripting/Enums.h +++ b/Source/Engine/Scripting/Enums.h @@ -68,4 +68,30 @@ public: { return FromString(StringAnsi(name)); } + + // Gets the name of the enum value as separated flags + template + static String ToStringFlags(EnumType value, Char separator = '|') + { + String result; + if (const auto items = GetItems()) + { + for (int32 i = 0; items[i].Name; i++) + { + const uint64 itemValue = items[i].Value; + if ((uint64)value == 0 && itemValue == 0) + { + result = items[i].Name; + break; + } + if (itemValue != 0 && EnumHasAllFlags(value, (EnumType)itemValue)) + { + if (result.HasChars()) + result += separator; + result += items[i].Name; + } + } + } + return result; + } }; diff --git a/Source/Engine/Scripting/Scripting.cpp b/Source/Engine/Scripting/Scripting.cpp index f375b7bf0..47de473dd 100644 --- a/Source/Engine/Scripting/Scripting.cpp +++ b/Source/Engine/Scripting/Scripting.cpp @@ -116,7 +116,7 @@ Action Scripting::ScriptsLoaded; Action Scripting::ScriptsUnload; Action Scripting::ScriptsReloading; Action Scripting::ScriptsReloaded; -ThreadLocal Scripting::ObjectsLookupIdMapping; +ThreadLocal Scripting::ObjectsLookupIdMapping; ScriptingService ScriptingServiceInstance; bool initFlaxEngine(); diff --git a/Source/Engine/Scripting/Scripting.h b/Source/Engine/Scripting/Scripting.h index a22349928..6ed2beca4 100644 --- a/Source/Engine/Scripting/Scripting.h +++ b/Source/Engine/Scripting/Scripting.h @@ -6,7 +6,7 @@ #include "Engine/Scripting/ScriptingType.h" #include "Types.h" -template +template class ThreadLocal; /// @@ -114,7 +114,7 @@ public: /// /// The objects lookup identifier mapping used to override the object ids on FindObject call (used by the object references deserialization). /// - static ThreadLocal ObjectsLookupIdMapping; + static ThreadLocal ObjectsLookupIdMapping; /// /// Finds the object by the given identifier. Searches registered scene objects and optionally assets. Logs warning if fails. diff --git a/Source/Engine/Serialization/FileReadStream.h b/Source/Engine/Serialization/FileReadStream.h index 40a14185b..57287abcf 100644 --- a/Source/Engine/Serialization/FileReadStream.h +++ b/Source/Engine/Serialization/FileReadStream.h @@ -12,7 +12,6 @@ class FLAXENGINE_API FileReadStream : public ReadStream { private: - File* _file; uint32 _virtualPosInBuffer; // Current position in the buffer (index) uint32 _bufferSize; // Amount of loaded bytes from the file to the buffer @@ -33,11 +32,9 @@ public: ~FileReadStream(); public: - /// /// Gets the file handle. /// - /// File FORCE_INLINE const File* GetFile() const { return _file; @@ -49,7 +46,6 @@ public: void Unlink(); public: - /// /// Open file to write data to it /// @@ -58,7 +54,6 @@ public: static FileReadStream* Open(const StringView& path); public: - // [ReadStream] void Flush() final override; void Close() final override; diff --git a/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp b/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp index ed6ecbf9c..67af59585 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp @@ -84,7 +84,7 @@ bool cacheStaticGeometryTree(Actor* actor, ShadowsOfMordor::Builder::SceneBuildC { auto patch = terrain->GetPatch(patchIndex); entry.AsTerrain.PatchIndex = patchIndex; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { auto chunk = patch->Chunks[chunkIndex]; entry.AsTerrain.ChunkIndex = chunkIndex; diff --git a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp index 11d136d62..8aed89eb3 100644 --- a/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp +++ b/Source/Engine/ShadowsOfMordor/Builder.Jobs.cpp @@ -165,7 +165,7 @@ void ShadowsOfMordor::Builder::onJobRender(GPUContext* context) Matrix::Transpose(world, shaderData.WorldMatrix); shaderData.LightmapArea = chunk->Lightmap.UVsArea; shaderData.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; - chunk->GetHeightmapUVScaleBias(&shaderData.HeightmapUVScaleBias); + shaderData.HeightmapUVScaleBias = chunk->GetHeightmapUVScaleBias(); // Extract per axis scales from LocalToWorld transform const float scaleX = Float3(world.M11, world.M12, world.M13).Length(); diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index f347e3732..83b30166d 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -29,7 +29,7 @@ Terrain::Terrain(const SpawnParams& params) , _cachedScale(1.0f) { _drawCategory = SceneRendering::SceneDrawAsync; - PhysicalMaterial.Changed.Bind(this); + _physicalMaterials.Resize(8); } Terrain::~Terrain() @@ -59,7 +59,7 @@ void Terrain::CacheNeighbors() for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) { const auto patch = _patches[pathIndex]; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { patch->Chunks[chunkIndex].CacheNeighbors(); } @@ -185,7 +185,7 @@ bool Terrain::RayCast(const Vector3& origin, const Vector3& direction, RayCastHi return result; } -void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const +void Terrain::ClosestPoint(const Vector3& point, Vector3& result) const { Real minDistance = MAX_Real; Vector3 tmp; @@ -194,8 +194,8 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const const auto patch = _patches[pathIndex]; if (patch->HasCollision()) { - patch->ClosestPoint(position, tmp); - const auto distance = Vector3::DistanceSquared(position, tmp); + patch->ClosestPoint(point, tmp); + const auto distance = Vector3::DistanceSquared(point, tmp); if (distance < minDistance) { minDistance = distance; @@ -205,12 +205,17 @@ void Terrain::ClosestPoint(const Vector3& position, Vector3& result) const } } +bool Terrain::ContainsPoint(const Vector3& point) const +{ + return false; +} + void Terrain::DrawPatch(const RenderContext& renderContext, const Int2& patchCoord, MaterialBase* material, int32 lodIndex) const { auto patch = GetPatch(patchCoord); if (patch) { - for (int32 i = 0; i < TerrainPatch::CHUNKS_COUNT; i++) + for (int32 i = 0; i < Terrain::ChunksCount; i++) patch->Chunks[i].Draw(renderContext, material, lodIndex); } } @@ -228,22 +233,6 @@ void Terrain::DrawChunk(const RenderContext& renderContext, const Int2& patchCoo } } -void Terrain::OnPhysicalMaterialChanged() -{ - if (_patches.IsEmpty()) - return; - - // Update the shapes material - for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) - { - const auto patch = _patches[pathIndex]; - if (patch->HasCollision()) - { - PhysicsBackend::SetShapeMaterial(patch->_physicsShape, PhysicalMaterial.Get()); - } - } -} - #if TERRAIN_USE_PHYSICS_DEBUG void Terrain::DrawPhysicsDebug(RenderView& view) @@ -295,6 +284,21 @@ void Terrain::SetCollisionLOD(int32 value) #endif } +void Terrain::SetPhysicalMaterials(const Array, FixedAllocation<8>>& value) +{ + _physicalMaterials = value; + _physicalMaterials.Resize(8); + JsonAsset* materials[8]; + for (int32 i = 0;i<8;i++) + materials[i] = _physicalMaterials[i]; + for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) + { + const auto patch = _patches.Get()[pathIndex]; + if (patch->HasCollision()) + PhysicsBackend::SetShapeMaterials(patch->_physicsShape, ToSpan(materials, 8)); + } +} + TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const { return GetPatch(patchCoord.X, patchCoord.Y); @@ -540,7 +544,7 @@ void Terrain::Draw(RenderContext& renderContext) Matrix localToWorld, worldToLocal; BoundingSphere chunkSphere; BoundingBox localBounds; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { TerrainChunk* chunk = &patch->Chunks[chunkIndex]; chunk->GetTransform().GetWorld(localToWorld); // TODO: large-worlds @@ -570,7 +574,7 @@ void Terrain::Draw(RenderContext& renderContext) continue; // Frustum vs Box culling for chunks - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { auto chunk = &patch->Chunks[chunkIndex]; chunk->_cachedDrawLOD = 0; @@ -588,7 +592,7 @@ void Terrain::Draw(RenderContext& renderContext) else { // Reset cached LOD for chunks (prevent LOD transition from invisible chunks) - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { auto chunk = &patch->Chunks[chunkIndex]; chunk->_cachedDrawLOD = 0; @@ -616,10 +620,10 @@ void Terrain::OnDebugDrawSelected() for (int32 pathIndex = 0; pathIndex < _patches.Count(); pathIndex++) { const auto patch = _patches[pathIndex]; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { auto chunk = &patch->Chunks[chunkIndex]; - DebugDraw::DrawBox(chunk->_bounds, Color(chunk->_x / (float)TerrainPatch::CHUNKS_COUNT_EDGE, 1.0f, chunk->_z / (float)TerrainPatch::CHUNKS_COUNT_EDGE)); + DebugDraw::DrawBox(chunk->_bounds, Color(chunk->_x / (float)Terrain::ChunksCountEdge, 1.0f, chunk->_z / (float)Terrain::ChunksCountEdge)); } } */ @@ -667,8 +671,8 @@ void Terrain::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap); SERIALIZE_MEMBER(BoundsExtent, _boundsExtent); SERIALIZE_MEMBER(CollisionLOD, _collisionLod); + SERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials); SERIALIZE(Material); - SERIALIZE(PhysicalMaterial); SERIALIZE(DrawModes); SERIALIZE_MEMBER(LODCount, _lodCount); @@ -714,8 +718,8 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie DESERIALIZE_MEMBER(LODDistribution, _lodDistribution); DESERIALIZE_MEMBER(ScaleInLightmap, _scaleInLightmap); DESERIALIZE_MEMBER(BoundsExtent, _boundsExtent); + DESERIALIZE_MEMBER(PhysicalMaterials, _physicalMaterials); DESERIALIZE(Material); - DESERIALIZE(PhysicalMaterial); DESERIALIZE(DrawModes); member = stream.FindMember("LODCount"); @@ -780,6 +784,15 @@ void Terrain::Deserialize(DeserializeStream& stream, ISerializeModifier* modifie // [Deprecated on 27.04.2022, expires on 27.04.2024] if (modifier->EngineBuild <= 6331) DrawModes |= DrawPass::GlobalSurfaceAtlas; + + // [Deprecated on 15.02.2024, expires on 15.02.2026] + JsonAssetReference PhysicalMaterial; + DESERIALIZE(PhysicalMaterial); + if (PhysicalMaterial) + { + for (auto& e : _physicalMaterials) + e = PhysicalMaterial; + } } RigidBody* Terrain::GetAttachedRigidBody() const diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 636f30206..5a1eb5e95 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -2,7 +2,7 @@ #pragma once -#include "Engine/Content/JsonAsset.h" +#include "Engine/Content/JsonAssetReference.h" #include "Engine/Content/Assets/MaterialBase.h" #include "Engine/Physics/Actors/PhysicsColliderActor.h" @@ -10,6 +10,7 @@ class Terrain; class TerrainChunk; class TerrainPatch; class TerrainManager; +class PhysicalMaterial; struct RayCastHit; struct RenderView; @@ -23,10 +24,7 @@ struct RenderView; #define TERRAIN_EDITING 1 // Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes. -#define TERRAIN_UPDATING (USE_EDITOR) - -// Enable/disable precise terrain geometry collision testing (with in-build vertex buffer caching, this will increase memory usage) -#define USE_PRECISE_TERRAIN_INTERSECTS (USE_EDITOR) +#define TERRAIN_UPDATING 1 // Enable/disable terrain physics collision drawing #define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1) @@ -41,13 +39,28 @@ struct RenderView; /// API_CLASS(Sealed) class FLAXENGINE_API Terrain : public PhysicsColliderActor { -DECLARE_SCENE_OBJECT(Terrain); + DECLARE_SCENE_OBJECT(Terrain); friend Terrain; friend TerrainPatch; friend TerrainChunk; -private: + /// + /// Various defines regarding terrain configuration. + /// + API_ENUM() enum Config + { + /// + /// The maximum allowed amount of chunks per patch. + /// + ChunksCount = 16, + /// + /// The maximum allowed amount of chunks per chunk. + /// + ChunksCountEdge = 4, + }; + +private: char _lodBias; char _forcedLod; char _collisionLod; @@ -60,28 +73,21 @@ private: Float3 _cachedScale; Array> _patches; Array _drawChunks; + Array, FixedAllocation<8>> _physicalMaterials; public: - /// /// Finalizes an instance of the class. /// ~Terrain(); public: - /// /// The default material used for terrain rendering (chunks can override this). /// API_FIELD(Attributes="EditorOrder(100), DefaultValue(null), EditorDisplay(\"Terrain\")") AssetReference Material; - /// - /// The physical material used to define the terrain collider physical properties. - /// - API_FIELD(Attributes="EditorOrder(520), DefaultValue(null), Limit(-1, 100, 0.1f), EditorDisplay(\"Collision\"), AssetReference(typeof(PhysicalMaterial), true)") - AssetReference PhysicalMaterial; - /// /// The draw passes to use for rendering this object. /// @@ -89,7 +95,6 @@ public: DrawPass DrawModes = DrawPass::Default; public: - /// /// Gets the terrain Level Of Detail bias value. Allows to increase or decrease rendered terrain quality. /// @@ -180,6 +185,21 @@ public: /// API_PROPERTY() void SetCollisionLOD(int32 value); + /// + /// Gets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array). + /// + API_PROPERTY(Attributes="EditorOrder(520), EditorDisplay(\"Collision\"), Collection(MinCount = 8, MaxCount = 8)") + FORCE_INLINE const Array, FixedAllocation<8>>& GetPhysicalMaterials() const + { + return _physicalMaterials; + } + + /// + /// Sets the list with physical materials used to define the terrain collider physical properties - each for terrain layer (layer index matches index in this array). + /// + API_PROPERTY() + void SetPhysicalMaterials(const Array, FixedAllocation<8>>& value); + /// /// Gets the terrain Level Of Detail count. /// @@ -219,7 +239,7 @@ public: /// /// The patch location (x and z). /// The patch. - TerrainPatch* GetPatch(const Int2& patchCoord) const; + API_FUNCTION() TerrainPatch* GetPatch(API_PARAM(Ref) const Int2& patchCoord) const; /// /// Gets the patch at the given location. @@ -227,7 +247,7 @@ public: /// The patch location x. /// The patch location z. /// The patch. - TerrainPatch* GetPatch(int32 x, int32 z) const; + API_FUNCTION() TerrainPatch* GetPatch(int32 x, int32 z) const; /// /// Gets the zero-based index of the terrain patch in the terrain patches collection. @@ -241,7 +261,7 @@ public: /// /// The index. /// The patch. - FORCE_INLINE TerrainPatch* GetPatch(int32 index) const + API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch(int32 index) const { return _patches[index]; } @@ -311,9 +331,7 @@ public: #endif public: - #if TERRAIN_EDITING - /// /// Setups the terrain. Clears the existing data. /// @@ -338,7 +356,6 @@ public: /// /// The patch location (x and z). API_FUNCTION() void RemovePatch(API_PARAM(Ref) const Int2& patchCoord); - #endif /// @@ -362,17 +379,6 @@ public: void RemoveLightmap(); public: - - /// - /// Performs a raycast against this terrain collision shape. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; - /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. /// @@ -382,7 +388,7 @@ public: /// The raycast result hit chunk. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. @@ -395,23 +401,6 @@ public: /// True if ray hits an object, otherwise false. API_FUNCTION() bool RayCast(const Ray& ray, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Int2& resultPatchCoord, API_PARAM(Out) Int2& resultChunkCoord, float maxDistance = MAX_float) const; - /// - /// Performs a raycast against terrain collision, returns results in a RayCastHit structure. - /// - /// The origin of the ray. - /// The normalized direction of the ray. - /// The result hit information. Valid only when method returns true. - /// The maximum distance the ray should check for collisions. - /// True if ray hits an object, otherwise false. - API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; - - /// - /// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. - /// - /// The position to find the closest point to it. - /// The result point on the collider that is closest to the specified location. - API_FUNCTION() void ClosestPoint(const Vector3& position, API_PARAM(Out) Vector3& result) const; - /// /// Draws the terrain patch. /// @@ -432,14 +421,11 @@ public: API_FUNCTION() void DrawChunk(API_PARAM(Ref) const RenderContext& renderContext, API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* material, int32 lodIndex = 0) const; private: - - void OnPhysicalMaterialChanged(); #if TERRAIN_USE_PHYSICS_DEBUG - void DrawPhysicsDebug(RenderView& view); + void DrawPhysicsDebug(RenderView& view); #endif public: - // [PhysicsColliderActor] void Draw(RenderContext& renderContext) override; #if USE_EDITOR @@ -450,9 +436,12 @@ public: void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; RigidBody* GetAttachedRigidBody() const override; + bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const final; + bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const final; + void ClosestPoint(const Vector3& point, Vector3& result) const final; + bool ContainsPoint(const Vector3& point) const final; protected: - // [PhysicsColliderActor] void OnEnable() override; void OnDisable() override; diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 9bf1c1208..8ef32e58b 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -11,7 +11,14 @@ #include "Engine/Renderer/RenderList.h" #include "Engine/Core/Math/OrientedBoundingBox.h" #include "Engine/Level/Scene/Scene.h" +#if USE_EDITOR #include "Engine/Level/Prefabs/PrefabManager.h" +#endif + +TerrainChunk::TerrainChunk(const SpawnParams& params) + : ScriptingObject(params) +{ +} void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) { @@ -21,7 +28,7 @@ void TerrainChunk::Init(TerrainPatch* patch, uint16 x, uint16 z) _z = z; _yOffset = 0; _yHeight = 1; - _heightmapUVScaleBias = Float4(1.0f, 1.0f, _x, _z) * (1.0f / TerrainPatch::CHUNKS_COUNT_EDGE); + _heightmapUVScaleBias = Float4(1.0f, 1.0f, _x, _z) * (1.0f / Terrain::ChunksCountEdge); _perInstanceRandom = (_patch->_terrain->_id.C ^ _x ^ _z) * (1.0f / (float)MAX_uint32); OverrideMaterial = nullptr; } @@ -51,8 +58,8 @@ bool TerrainChunk::PrepareDraw(const RenderContext& renderContext) //lod = 0; //lod = 10; - //lod = (_x + _z + TerrainPatch::CHUNKS_COUNT_EDGE * (_patch->_x + _patch->_z)); - //lod = (int32)Vector2::Distance(Vector2(2, 2), Vector2(_patch->_x, _patch->_z) * TerrainPatch::CHUNKS_COUNT_EDGE + Vector2(_x, _z)); + //lod = (_x + _z + Terrain::ChunksCountEdge * (_patch->_x + _patch->_z)); + //lod = (int32)Vector2::Distance(Vector2(2, 2), Vector2(_patch->_x, _patch->_z) * Terrain::ChunksCountEdge + Vector2(_x, _z)); //lod = (int32)(Vector3::Distance(_bounds.GetCenter(), view.Position) / 10000.0f); } lod = Math::Clamp(lod, minStreamedLod, lodCount - 1); @@ -93,7 +100,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const drawCall.ObjectRadius = _sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z)); drawCall.Terrain.CurrentLOD = (float)lod; drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; @@ -151,7 +158,7 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi drawCall.ObjectRadius = _sphere.Radius; drawCall.Terrain.Patch = _patch; drawCall.Terrain.HeightmapUVScaleBias = _heightmapUVScaleBias; - drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * TerrainPatch::CHUNKS_COUNT_EDGE + _x), (float)(_patch->_z * TerrainPatch::CHUNKS_COUNT_EDGE + _z)); + drawCall.Terrain.OffsetUV = Vector2((float)(_patch->_x * Terrain::ChunksCountEdge + _x), (float)(_patch->_z * Terrain::ChunksCountEdge + _z)); drawCall.Terrain.CurrentLOD = (float)lod; drawCall.Terrain.ChunkSizeNextLOD = (float)(((chunkSize + 1) >> (lod + 1)) - 1); drawCall.Terrain.TerrainChunkSizeLOD0 = TERRAIN_UNITS_PER_VERTEX * chunkSize; @@ -232,46 +239,46 @@ void TerrainChunk::CacheNeighbors() _neighbors[0] = this; if (_z > 0) { - _neighbors[0] = &_patch->Chunks[(_z - 1) * TerrainPatch::CHUNKS_COUNT_EDGE + _x]; + _neighbors[0] = &_patch->Chunks[(_z - 1) * Terrain::ChunksCountEdge + _x]; } else { const auto patch = _patch->_terrain->GetPatch(_patch->_x, _patch->_z - 1); if (patch) - _neighbors[0] = &patch->Chunks[(TerrainPatch::CHUNKS_COUNT_EDGE - 1) * TerrainPatch::CHUNKS_COUNT_EDGE + _x]; + _neighbors[0] = &patch->Chunks[(Terrain::ChunksCountEdge - 1) * Terrain::ChunksCountEdge + _x]; } // 1: left _neighbors[1] = this; if (_x > 0) { - _neighbors[1] = &_patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE + (_x - 1)]; + _neighbors[1] = &_patch->Chunks[_z * Terrain::ChunksCountEdge + (_x - 1)]; } else { const auto patch = _patch->_terrain->GetPatch(_patch->_x - 1, _patch->_z); if (patch) - _neighbors[1] = &patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE + (TerrainPatch::CHUNKS_COUNT_EDGE - 1)]; + _neighbors[1] = &patch->Chunks[_z * Terrain::ChunksCountEdge + (Terrain::ChunksCountEdge - 1)]; } // 2: right _neighbors[2] = this; - if (_x < TerrainPatch::CHUNKS_COUNT_EDGE - 1) + if (_x < Terrain::ChunksCountEdge - 1) { - _neighbors[2] = &_patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE + (_x + 1)]; + _neighbors[2] = &_patch->Chunks[_z * Terrain::ChunksCountEdge + (_x + 1)]; } else { const auto patch = _patch->_terrain->GetPatch(_patch->_x + 1, _patch->_z); if (patch) - _neighbors[2] = &patch->Chunks[_z * TerrainPatch::CHUNKS_COUNT_EDGE]; + _neighbors[2] = &patch->Chunks[_z * Terrain::ChunksCountEdge]; } // 3: top _neighbors[3] = this; - if (_z < TerrainPatch::CHUNKS_COUNT_EDGE - 1) + if (_z < Terrain::ChunksCountEdge - 1) { - _neighbors[3] = &_patch->Chunks[(_z + 1) * TerrainPatch::CHUNKS_COUNT_EDGE + _x]; + _neighbors[3] = &_patch->Chunks[(_z + 1) * Terrain::ChunksCountEdge + _x]; } else { diff --git a/Source/Engine/Terrain/TerrainChunk.h b/Source/Engine/Terrain/TerrainChunk.h index 884160b45..60f38ed0e 100644 --- a/Source/Engine/Terrain/TerrainChunk.h +++ b/Source/Engine/Terrain/TerrainChunk.h @@ -17,14 +17,14 @@ struct RenderContext; /// /// Represents a single terrain chunk. /// -class FLAXENGINE_API TerrainChunk : public ISerializable +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainChunk : public ScriptingObject, public ISerializable { + DECLARE_SCRIPTING_TYPE(TerrainChunk); friend Terrain; friend TerrainPatch; friend TerrainChunk; private: - TerrainPatch* _patch; uint16 _x, _z; Float4 _heightmapUVScaleBias; @@ -41,11 +41,10 @@ private: void Init(TerrainPatch* patch, uint16 x, uint16 z); public: - /// /// The material to override the terrain default one for this chunk. /// - AssetReference OverrideMaterial; + API_FIELD() AssetReference OverrideMaterial; /// /// The baked lightmap entry info for this chunk. @@ -53,11 +52,10 @@ public: LightmapEntry Lightmap; public: - /// /// Gets the x coordinate. /// - FORCE_INLINE int32 GetX() const + API_FUNCTION() FORCE_INLINE int32 GetX() const { return _x; } @@ -65,7 +63,7 @@ public: /// /// Gets the z coordinate. /// - FORCE_INLINE int32 GetZ() const + API_FUNCTION() FORCE_INLINE int32 GetZ() const { return _z; } @@ -73,7 +71,7 @@ public: /// /// Gets the patch. /// - FORCE_INLINE TerrainPatch* GetPatch() const + API_FUNCTION() FORCE_INLINE TerrainPatch* GetPatch() const { return _patch; } @@ -81,7 +79,7 @@ public: /// /// Gets the chunk world bounds. /// - FORCE_INLINE const BoundingBox& GetBounds() const + API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } @@ -89,7 +87,7 @@ public: /// /// Gets the chunk transformation (world to local). /// - FORCE_INLINE const Transform& GetTransform() const + API_FUNCTION() FORCE_INLINE const Transform& GetTransform() const { return _transform; } @@ -97,10 +95,9 @@ public: /// /// Gets the scale (in XY) and bias (in ZW) applied to the vertex UVs to get the chunk coordinates. /// - /// The result. - FORCE_INLINE void GetHeightmapUVScaleBias(Float4* result) const + API_FUNCTION() FORCE_INLINE const Float4& GetHeightmapUVScaleBias() const { - *result = _heightmapUVScaleBias; + return _heightmapUVScaleBias; } /// @@ -120,7 +117,6 @@ public: } public: - /// /// Prepares for drawing chunk. Cached LOD and material. /// @@ -140,7 +136,7 @@ public: /// The rendering context. /// The material to use for rendering. /// The LOD index. - void Draw(const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const; + API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, int32 lodIndex = 0) const; /// /// Determines if there is an intersection between the terrain chunk and a point @@ -148,7 +144,7 @@ public: /// The ray. /// The output distance. /// True if chunk intersects with the ray, otherwise false. - bool Intersects(const Ray& ray, Real& distance); + API_FUNCTION() bool Intersects(const Ray& ray, API_PARAM(Out) Real& distance); /// /// Updates the cached bounds of the chunk. @@ -166,7 +162,6 @@ public: void CacheNeighbors(); public: - // [ISerializable] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 8e05062c8..e24376f1f 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -27,6 +27,9 @@ #include "Engine/ContentImporters/AssetsImportingManager.h" #endif #endif +#if TERRAIN_EDITING || TERRAIN_UPDATING +#include "Engine/Core/Collections/ArrayExtensions.h" +#endif #if USE_EDITOR #include "Engine/Debug/DebugDraw.h" #endif @@ -41,6 +44,11 @@ struct TerrainCollisionDataHeader float ScaleXZ; }; +TerrainPatch::TerrainPatch(const SpawnParams& params) + : ScriptingObject(params) +{ +} + void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) { ScopeLock lock(_collisionLocker); @@ -51,13 +59,13 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z) _physicsHeightField = nullptr; _x = x; _z = z; - const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; _offset = Float3(_x * size, 0.0f, _z * size); _yOffset = 0.0f; _yHeight = 1.0f; - for (int32 i = 0; i < CHUNKS_COUNT; i++) + for (int32 i = 0; i < Terrain::ChunksCount; i++) { - Chunks[i].Init(this, i % CHUNKS_COUNT_EDGE, i / CHUNKS_COUNT_EDGE); + Chunks[i].Init(this, i % Terrain::Terrain::ChunksCountEdge, i / Terrain::Terrain::ChunksCountEdge); } Heightmap = nullptr; for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) @@ -105,9 +113,10 @@ void TerrainPatch::RemoveLightmap() void TerrainPatch::UpdateBounds() { + PROFILE_CPU(); Chunks[0].UpdateBounds(); _bounds = Chunks[0]._bounds; - for (int32 i = 1; i < CHUNKS_COUNT; i++) + for (int32 i = 1; i < Terrain::ChunksCount; i++) { Chunks[i].UpdateBounds(); BoundingBox::Merge(_bounds, Chunks[i]._bounds, _bounds); @@ -116,6 +125,8 @@ void TerrainPatch::UpdateBounds() void TerrainPatch::UpdateTransform() { + PROFILE_CPU(); + // Update physics if (_physicsActor) { @@ -124,7 +135,7 @@ void TerrainPatch::UpdateTransform() } // Update chunks cache - for (int32 i = 0; i < CHUNKS_COUNT; i++) + for (int32 i = 0; i < Terrain::ChunksCount; i++) { Chunks[i].UpdateTransform(); } @@ -138,8 +149,14 @@ void TerrainPatch::UpdateTransform() #if TERRAIN_EDITING || TERRAIN_UPDATING +bool IsValidMaterial(const JsonAssetReference& e) +{ + return e; +} + struct TerrainDataUpdateInfo { + TerrainPatch* Patch; int32 ChunkSize; int32 VertexCountEdge; int32 HeightmapSize; @@ -147,6 +164,40 @@ struct TerrainDataUpdateInfo int32 TextureSize; float PatchOffset; float PatchHeight; + Color32* SplatMaps[TERRAIN_MAX_SPLATMAPS_COUNT] = {}; + + TerrainDataUpdateInfo(TerrainPatch* patch, float patchOffset = 0.0f, float patchHeight = 1.0f) + : Patch(patch) + , PatchOffset(patchOffset) + , PatchHeight(patchHeight) + { + ChunkSize = patch->GetTerrain()->GetChunkSize(); + VertexCountEdge = ChunkSize + 1; + HeightmapSize = ChunkSize * Terrain::ChunksCountEdge + 1; + HeightmapLength = HeightmapSize * HeightmapSize; + TextureSize = VertexCountEdge * Terrain::ChunksCountEdge; + } + + bool UsePhysicalMaterials() const + { + return ArrayExtensions::Any>(Patch->GetTerrain()->GetPhysicalMaterials(), IsValidMaterial); + } + + // When using physical materials, then get splatmaps data required for per-triangle material indices + void GetSplatMaps() + { +#if TERRAIN_UPDATING + if (SplatMaps[0]) + return; + if (UsePhysicalMaterials()) + { + for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++) + SplatMaps[i] = Patch->GetSplatMapData(i); + } +#else + LOG(Warning, "Splatmaps reading not implemented for physical layers updating."); +#endif + } }; // Shared data container for the terrain data updating shared by the normals and collision generation logic @@ -185,7 +236,7 @@ FORCE_INLINE bool ReadIsHole(const Color32& raw) return (raw.B + raw.A) >= (int32)(1.9f * MAX_uint8); } -void CalculateHeightmapRange(Terrain* terrain, TerrainDataUpdateInfo& info, const float* heightmap, float chunkOffsets[TerrainPatch::CHUNKS_COUNT], float chunkHeights[TerrainPatch::CHUNKS_COUNT]) +void CalculateHeightmapRange(Terrain* terrain, TerrainDataUpdateInfo& info, const float* heightmap, float chunkOffsets[Terrain::ChunksCount], float chunkHeights[Terrain::ChunksCount]) { PROFILE_CPU_NAMED("Terrain.CalculateRange"); @@ -194,10 +245,10 @@ void CalculateHeightmapRange(Terrain* terrain, TerrainDataUpdateInfo& info, cons float minPatchHeight = MAX_float; float maxPatchHeight = MIN_float; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { - const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE) * info.ChunkSize; - const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE) * info.ChunkSize; + const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge) * info.ChunkSize; + const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge) * info.ChunkSize; float minHeight = MAX_float; float maxHeight = MIN_float; @@ -240,10 +291,10 @@ void UpdateHeightMap(const TerrainDataUpdateInfo& info, const float* heightmap, const auto heightmapPtr = heightmap; const auto ptr = (Color32*)data; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { - const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE); - const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE); + const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge); + const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge); const int32 chunkTextureX = chunkX * info.VertexCountEdge; const int32 chunkTextureZ = chunkZ * info.VertexCountEdge; @@ -282,10 +333,10 @@ void UpdateSplatMap(const TerrainDataUpdateInfo& info, const Color32* splatMap, const auto splatPtr = splatMap; const auto ptr = (Color32*)data; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { - const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE); - const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE); + const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge); + const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge); const int32 chunkTextureX = chunkX * info.VertexCountEdge; const int32 chunkTextureZ = chunkZ * info.VertexCountEdge; @@ -321,10 +372,9 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh PROFILE_CPU_NAMED("Terrain.CalculateNormals"); // Expand the area for the normals to prevent issues on the edges (for the averaged normals) - const int32 heightMapSize = info.HeightmapSize; const Int2 modifiedEnd = modifiedOffset + modifiedSize; const Int2 normalsStart = Int2::Max(Int2::Zero, modifiedOffset - 1); - const Int2 normalsEnd = Int2::Min(heightMapSize, modifiedEnd + 1); + const Int2 normalsEnd = Int2::Min(info.HeightmapSize, modifiedEnd + 1); const Int2 normalsSize = normalsEnd - normalsStart; // Prepare memory @@ -342,7 +392,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh // Get four vertices from the quad #define GET_VERTEX(a, b) \ int32 i##a##b = (z + (b) - normalsStart.Y) * normalsSize.X + (x + (a) - normalsStart.X); \ - int32 h##a##b = (z + (b)) * heightMapSize + (x + (a)); \ + int32 h##a##b = (z + (b)) * info.HeightmapSize + (x + (a)); \ Float3 v##a##b; v##a##b.X = (x + (a)) * TERRAIN_UNITS_PER_VERTEX; \ v##a##b.Y = heightmap[h##a##b]; \ v##a##b.Z = (z + (b)) * TERRAIN_UNITS_PER_VERTEX @@ -405,10 +455,10 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh // Write back to the data container const auto ptr = (Color32*)data; - for (int32 chunkIndex = 0; chunkIndex < TerrainPatch::CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { - const int32 chunkX = (chunkIndex % TerrainPatch::CHUNKS_COUNT_EDGE); - const int32 chunkZ = (chunkIndex / TerrainPatch::CHUNKS_COUNT_EDGE); + const int32 chunkX = (chunkIndex % Terrain::ChunksCountEdge); + const int32 chunkZ = (chunkIndex / Terrain::ChunksCountEdge); const int32 chunkTextureX = chunkX * info.VertexCountEdge; const int32 chunkTextureZ = chunkZ * info.VertexCountEdge; @@ -428,7 +478,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh const int32 dz = chunkHeightmapZ + z - modifiedOffset.Y; if (dz < 0 || dz >= modifiedSize.Y) continue; - const int32 hz = (chunkHeightmapZ + z) * heightMapSize; + const int32 hz = (chunkHeightmapZ + z) * info.HeightmapSize; const int32 sz = (chunkHeightmapZ + z - normalsStart.Y) * normalsSize.X; const int32 tz = (chunkTextureZ + z) * info.TextureSize; @@ -498,9 +548,9 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, const int32 textureSizeMipHigher = textureSizeMip << 1; // Make heightmap values on left edge the same as the left edge of the chunk on the higher LOD - for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++) + for (int32 chunkX = 0; chunkX < Terrain::ChunksCountEdge; chunkX++) { - for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++) + for (int32 chunkZ = 0; chunkZ < Terrain::ChunksCountEdge; chunkZ++) { const int32 chunkTextureX = chunkX * vertexCountEdgeMip; const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip; @@ -513,11 +563,11 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 x = 0, xCount = vertexCountEdgeMip; if (chunkX == 0) x = 1; - else if (chunkX == TerrainPatch::CHUNKS_COUNT_EDGE - 1) + else if (chunkX == Terrain::ChunksCountEdge - 1) xCount--; if (chunkZ == 0) z = 1; - else if (chunkZ == TerrainPatch::CHUNKS_COUNT_EDGE - 1) + else if (chunkZ == Terrain::ChunksCountEdge - 1) zCount--; for (; z < zCount; z++) @@ -546,15 +596,55 @@ void FixMips(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, } } -bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData) +FORCE_INLINE byte GetPhysicalMaterial(const Color32& raw, const TerrainDataUpdateInfo& info, int32 chunkZ, int32 chunkX, int32 z, int32 x) +{ + byte result = 0; + if (ReadIsHole(raw)) + { + // Hole + result = (uint8)PhysicsBackend::HeightFieldMaterial::Hole; + } + else if (info.SplatMaps[0]) + { + // Use the layer with the highest influence (splatmap data is Mip0 so convert x/z coords back to LOD0) + uint8 layer = 0; + uint8 layerWeight = 0; + const int32 splatmapTextureIndex = (chunkZ * info.ChunkSize + z) * info.HeightmapSize + chunkX * info.ChunkSize + x; + ASSERT(splatmapTextureIndex < info.HeightmapLength); + for (int32 splatIndex = 0; splatIndex < TERRAIN_MAX_SPLATMAPS_COUNT; splatIndex++) + { + for (int32 channelIndex = 0; channelIndex < 4; channelIndex++) + { + // Assume splatmap data pitch matches the row size and shift by channel index to simply sample at R chanel + const Color32* splatmap = (const Color32*)((const byte*)info.SplatMaps[splatIndex] + channelIndex); + const uint8 splat = splatmap[splatmapTextureIndex].R; + if (splat > layerWeight) + { + layer = splatIndex * 4 + channelIndex; + layerWeight = splat; + if (layerWeight == MAX_uint8) + break; + } + } + if (layerWeight == MAX_uint8) + break; + } + result = layer; + } + return result; +} + +bool CookCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, Array* collisionData) { #if COMPILE_WITH_PHYSICS_COOKING + info.GetSplatMaps(); PROFILE_CPU_NAMED("Terrain.CookCollision"); // Prepare data const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1); + const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD); const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1; - const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; + const int32 heightFieldSize = heightFieldChunkSize * Terrain::ChunksCountEdge + 1; const int32 heightFieldLength = heightFieldSize * heightFieldSize; GET_TERRAIN_SCRATCH_BUFFER(heightFieldData, heightFieldLength, PhysicsBackend::HeightFieldSample); PhysicsBackend::HeightFieldSample sample; @@ -562,36 +652,30 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldLength); // Setup terrain collision information - auto& mip = initData->Mips[collisionLOD]; + const auto& mip = initData->Mips[collisionLOD]; const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD; const int32 textureSizeMip = info.TextureSize >> collisionLOD; - for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++) + for (int32 chunkX = 0; chunkX < Terrain::ChunksCountEdge; chunkX++) { const int32 chunkTextureX = chunkX * vertexCountEdgeMip; const int32 chunkStartX = chunkX * heightFieldChunkSize; - - for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++) + for (int32 chunkZ = 0; chunkZ < Terrain::ChunksCountEdge; chunkZ++) { const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip; const int32 chunkStartZ = chunkZ * heightFieldChunkSize; - for (int32 z = 0; z < vertexCountEdgeMip; z++) { + const int32 heightmapZ = chunkStartZ + z; for (int32 x = 0; x < vertexCountEdgeMip; x++) { - const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; - - const Color32 raw = mip.Data.Get()[textureIndex]; - const float normalizedHeight = ReadNormalizedHeight(raw); - const bool isHole = ReadIsHole(raw); - const int32 heightmapX = chunkStartX + x; - const int32 heightmapZ = chunkStartZ + z; + + const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; + const Color32 raw = mip.Data.Get()[textureIndex]; + sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw)); + sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv); + const int32 dstIndex = (heightmapX * heightFieldSize) + heightmapZ; - - sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight); - sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0; - heightFieldData[dstIndex] = sample; } } @@ -620,16 +704,18 @@ bool CookCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* ini #endif } -bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField) +bool ModifyCollision(TerrainDataUpdateInfo& info, TextureBase::InitData* initData, int32 collisionLod, const Int2& modifiedOffset, const Int2& modifiedSize, void* heightField) { + info.GetSplatMaps(); PROFILE_CPU_NAMED("Terrain.ModifyCollision"); // Prepare data const Vector2 modifiedOffsetRatio((float)modifiedOffset.X / info.HeightmapSize, (float)modifiedOffset.Y / info.HeightmapSize); const Vector2 modifiedSizeRatio((float)modifiedSize.X / info.HeightmapSize, (float)modifiedSize.Y / info.HeightmapSize); const int32 collisionLOD = Math::Clamp(collisionLod, 0, initData->Mips.Count() - 1); + const int32 collisionLODInv = (int32)Math::Pow(2.0f, (float)collisionLOD); const int32 heightFieldChunkSize = ((info.ChunkSize + 1) >> collisionLOD) - 1; - const int32 heightFieldSize = heightFieldChunkSize * TerrainPatch::CHUNKS_COUNT_EDGE + 1; + const int32 heightFieldSize = heightFieldChunkSize * Terrain::ChunksCountEdge + 1; const Int2 samplesOffset(Vector2::Floor(modifiedOffsetRatio * (float)heightFieldSize)); Int2 samplesSize(Vector2::Ceil(modifiedSizeRatio * (float)heightFieldSize)); samplesSize.X = Math::Max(samplesSize.X, 1); @@ -646,56 +732,45 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i Platform::MemoryClear(heightFieldData, sizeof(PhysicsBackend::HeightFieldSample) * heightFieldDataLength); // Setup terrain collision information - auto& mip = initData->Mips[collisionLOD]; + const auto& mip = initData->Mips[collisionLOD]; const int32 vertexCountEdgeMip = info.VertexCountEdge >> collisionLOD; const int32 textureSizeMip = info.TextureSize >> collisionLOD; - for (int32 chunkX = 0; chunkX < TerrainPatch::CHUNKS_COUNT_EDGE; chunkX++) + for (int32 chunkX = 0; chunkX < Terrain::ChunksCountEdge; chunkX++) { const int32 chunkTextureX = chunkX * vertexCountEdgeMip; const int32 chunkStartX = chunkX * heightFieldChunkSize; - - // Skip unmodified chunks if (chunkStartX >= samplesEnd.X || chunkStartX + vertexCountEdgeMip < samplesOffset.X) - continue; + continue; // Skip unmodified chunks - for (int32 chunkZ = 0; chunkZ < TerrainPatch::CHUNKS_COUNT_EDGE; chunkZ++) + for (int32 chunkZ = 0; chunkZ < Terrain::ChunksCountEdge; chunkZ++) { const int32 chunkTextureZ = chunkZ * vertexCountEdgeMip; const int32 chunkStartZ = chunkZ * heightFieldChunkSize; - - // Skip unmodified chunks if (chunkStartZ >= samplesEnd.Y || chunkStartZ + vertexCountEdgeMip < samplesOffset.Y) - continue; + continue; // Skip unmodified chunks // TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples) for (int32 z = 0; z < vertexCountEdgeMip; z++) { - // Skip unmodified columns const int32 heightmapZ = chunkStartZ + z; const int32 heightmapLocalZ = heightmapZ - samplesOffset.Y; if (heightmapLocalZ < 0 || heightmapLocalZ >= samplesSize.Y) - continue; + continue; // Skip unmodified columns // TODO: adjust loop range to reduce iterations count for edge cases (skip checking unmodified samples) for (int32 x = 0; x < vertexCountEdgeMip; x++) { - // Skip unmodified rows const int32 heightmapX = chunkStartX + x; const int32 heightmapLocalX = heightmapX - samplesOffset.X; if (heightmapLocalX < 0 || heightmapLocalX >= samplesSize.X) - continue; + continue; // Skip unmodified rows const int32 textureIndex = (chunkTextureZ + z) * textureSizeMip + chunkTextureX + x; - const Color32 raw = mip.Data.Get()[textureIndex]; - const float normalizedHeight = ReadNormalizedHeight(raw); - const bool isHole = ReadIsHole(raw); + sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * ReadNormalizedHeight(raw)); + sample.MaterialIndex0 = sample.MaterialIndex1 = GetPhysicalMaterial(raw, info, chunkZ, chunkX, z * collisionLODInv, x * collisionLODInv); const int32 dstIndex = (heightmapLocalX * samplesSize.Y) + heightmapLocalZ; - - sample.Height = int16(TERRAIN_PATCH_COLLISION_QUANTIZATION * normalizedHeight); - sample.MaterialIndex0 = sample.MaterialIndex1 = isHole ? (uint8)PhysicsBackend::HeightFieldMaterial::Hole : 0; - heightFieldData[dstIndex] = sample; } } @@ -718,49 +793,34 @@ bool ModifyCollision(const TerrainDataUpdateInfo& info, TextureBase::InitData* i bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask, bool forceUseVirtualStorage) { - // Validate input + PROFILE_CPU_NAMED("Terrain.Setup"); if (heightMap == nullptr) { LOG(Warning, "Cannot create terrain without a heightmap specified."); return true; } - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - if (heightMapLength != heightMapSize * heightMapSize) + TerrainDataUpdateInfo info(this); + if (heightMapLength != info.HeightmapLength) { - LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapSize * heightMapSize, heightMapLength); + LOG(Warning, "Invalid heightmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, heightMapLength); return true; } const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm; - PROFILE_CPU_NAMED("Terrain.Setup"); - // Input heightmap data overlaps on chunk edges but it needs to be duplicated for chunks (each chunk has own scale-bias for height values normalization) - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat); - const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapLength; - info.TextureSize = textureSize; - info.PatchOffset = 0.0f; - info.PatchHeight = 1.0f; + const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2); // Process heightmap to get per-patch height normalization values - float chunkOffsets[CHUNKS_COUNT]; - float chunkHeights[CHUNKS_COUNT]; + float chunkOffsets[Terrain::ChunksCount]; + float chunkHeights[Terrain::ChunksCount]; CalculateHeightmapRange(_terrain, info, heightMap, chunkOffsets, chunkHeights); // Prepare #if USE_EDITOR const bool useVirtualStorage = Editor::IsPlayMode || forceUseVirtualStorage; #else - const bool useVirtualStorage = true; + const bool useVirtualStorage = true; #endif #if USE_EDITOR String heightMapPath, heightFieldPath; @@ -782,18 +842,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, // Create heightmap texture data source container auto initData = New(); initData->Format = pixelFormat; - initData->Width = textureSize; - initData->Height = textureSize; + initData->Width = info.TextureSize; + initData->Height = info.TextureSize; initData->ArraySize = 1; initData->Mips.Resize(lodCount); // Allocate top mip data { PROFILE_CPU_NAMED("Terrain.AllocateHeightmap"); - auto& mip = initData->Mips[0]; - mip.RowPitch = textureSize * pixelStride; - mip.SlicePitch = mip.RowPitch * textureSize; + mip.RowPitch = info.TextureSize * pixelStride; + mip.SlicePitch = mip.RowPitch * info.TextureSize; mip.Data.Allocate(mip.SlicePitch); } @@ -857,11 +916,11 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, } } #else - else - { - // Not supported - CRASH; - } + else + { + // Not supported + CRASH; + } #endif // Prepare collision data destination container @@ -922,7 +981,7 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, // Update data _yOffset = info.PatchOffset; _yHeight = info.PatchHeight; - for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { auto& chunk = Chunks[chunkIndex]; chunk._yOffset = chunkOffsets[chunkIndex]; @@ -945,21 +1004,17 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap, bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage) { + PROFILE_CPU_NAMED("Terrain.SetupSplatMap"); CHECK_RETURN(index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT, true); - - // Validate input if (splatMap == nullptr) { LOG(Warning, "Cannot create terrain without any splatmap specified."); return true; } - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - const int32 heightMapLength = heightMapSize * heightMapSize; - if (splatMapLength != heightMapLength) + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); + if (splatMapLength != info.HeightmapLength) { - LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", chunkSize, heightMapSize, heightMapLength, splatMapLength); + LOG(Warning, "Invalid splatmap length. Terrain of chunk size equal {0} uses heightmap of size {1}x{1} (heightmap array length must be {2}). Input heightmap has length {3}.", info.ChunkSize, info.HeightmapSize, info.HeightmapLength, splatMapLength); return true; } const PixelFormat pixelFormat = PixelFormat::R8G8B8A8_UNorm; @@ -974,28 +1029,15 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 } } - PROFILE_CPU_NAMED("Terrain.SetupSplatMap"); - // Input splatmap data overlaps on chunk edges but it needs to be duplicated for chunks - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat); - const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(vertexCountEdge) - 2); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapLength; - info.TextureSize = textureSize; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; + const int32 lodCount = Math::Min(_terrain->_lodCount, MipLevelsCount(info.VertexCountEdge) - 2); // Prepare #if USE_EDITOR const bool useVirtualStorage = Editor::IsPlayMode || forceUseVirtualStorage; #else - const bool useVirtualStorage = true; + const bool useVirtualStorage = true; #endif #if USE_EDITOR String splatMapPath; @@ -1016,18 +1058,17 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 // Create heightmap texture data source container auto initData = New(); initData->Format = pixelFormat; - initData->Width = textureSize; - initData->Height = textureSize; + initData->Width = info.TextureSize; + initData->Height = info.TextureSize; initData->ArraySize = 1; initData->Mips.Resize(lodCount); // Allocate top mip data { PROFILE_CPU_NAMED("Terrain.AllocateSplatmap"); - auto& mip = initData->Mips[0]; - mip.RowPitch = textureSize * pixelStride; - mip.SlicePitch = mip.RowPitch * textureSize; + mip.RowPitch = info.TextureSize * pixelStride; + mip.SlicePitch = mip.RowPitch * info.TextureSize; mip.Data.Allocate(mip.SlicePitch); } @@ -1091,11 +1132,11 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 } } #else - else - { - // Not supported - CRASH; - } + else + { + // Not supported + CRASH; + } #endif #if TERRAIN_UPDATING @@ -1112,9 +1153,7 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3 bool TerrainPatch::InitializeHeightMap() { PROFILE_CPU_NAMED("Terrain.InitializeHeightMap"); - - // Initialize with flat heightmap data - const auto heightmapSize = _terrain->GetChunkSize() * TerrainPatch::CHUNKS_COUNT_EDGE + 1; + const auto heightmapSize = _terrain->GetChunkSize() * Terrain::ChunksCountEdge + 1; Array heightmap; heightmap.Resize(heightmapSize * heightmapSize); heightmap.SetAll(0.0f); @@ -1179,6 +1218,7 @@ void TerrainPatch::ClearCache() void TerrainPatch::CacheHeightData() { PROFILE_CPU_NAMED("Terrain.CacheHeightData"); + const TerrainDataUpdateInfo info(this); // Ensure that heightmap data is all loaded // TODO: disable streaming for heightmap texture if it's being modified by the editor @@ -1198,16 +1238,9 @@ void TerrainPatch::CacheHeightData() return; } - // Get texture input (note: this must match Setup method) - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - // Allocate data - const int32 heightMapLength = heightMapSize * heightMapSize; - _cachedHeightMap.Resize(heightMapLength); - _cachedHolesMask.Resize(heightMapLength); + _cachedHeightMap.Resize(info.HeightmapLength); + _cachedHolesMask.Resize(info.HeightmapLength); _wasHeightModified = false; // Extract heightmap data and denormalize it to get the pure height field @@ -1215,20 +1248,20 @@ void TerrainPatch::CacheHeightData() const float patchHeight = _yHeight; const auto heightmapPtr = _cachedHeightMap.Get(); const auto holesMaskPtr = _cachedHolesMask.Get(); - for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { - const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge; - const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge; + const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge; + const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge; - const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize; - const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize; + const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize; + const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize; - for (int32 z = 0; z < vertexCountEdge; z++) + for (int32 z = 0; z < info.VertexCountEdge; z++) { - const int32 tz = (chunkTextureZ + z) * textureSize; - const int32 sz = (chunkHeightmapZ + z) * heightMapSize; + const int32 tz = (chunkTextureZ + z) * info.TextureSize; + const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize; - for (int32 x = 0; x < vertexCountEdge; x++) + for (int32 x = 0; x < info.VertexCountEdge; x++) { const int32 tx = chunkTextureX + x; const int32 sx = chunkHeightmapX + x; @@ -1249,18 +1282,14 @@ void TerrainPatch::CacheHeightData() void TerrainPatch::CacheSplatData() { - // Prepare - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 textureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; - const int32 heightMapLength = heightMapSize * heightMapSize; + PROFILE_CPU_NAMED("Terrain.CacheSplatData"); + const TerrainDataUpdateInfo info(this); // Cache all the splatmaps for (int32 index = 0; index < TERRAIN_MAX_SPLATMAPS_COUNT; index++) { // Allocate data - _cachedSplatMap[index].Resize(heightMapLength); + _cachedSplatMap[index].Resize(info.HeightmapLength); _wasSplatmapModified[index] = false; // Skip if has missing splatmap asset @@ -1272,8 +1301,6 @@ void TerrainPatch::CacheSplatData() continue; } - PROFILE_CPU_NAMED("Terrain.CacheSplatData"); - // Ensure that splatmap data is all loaded // TODO: disable streaming for heightmap texture if it's being modified by the editor if (Splatmap[index]->WaitForLoaded()) @@ -1294,20 +1321,20 @@ void TerrainPatch::CacheSplatData() // Extract splatmap data const auto splatMapPtr = static_cast(_cachedSplatMap[index].Get()); - for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { - const int32 chunkTextureX = Chunks[chunkIndex]._x * vertexCountEdge; - const int32 chunkTextureZ = Chunks[chunkIndex]._z * vertexCountEdge; + const int32 chunkTextureX = Chunks[chunkIndex]._x * info.VertexCountEdge; + const int32 chunkTextureZ = Chunks[chunkIndex]._z * info.VertexCountEdge; - const int32 chunkHeightmapX = Chunks[chunkIndex]._x * chunkSize; - const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * chunkSize; + const int32 chunkHeightmapX = Chunks[chunkIndex]._x * info.ChunkSize; + const int32 chunkHeightmapZ = Chunks[chunkIndex]._z * info.ChunkSize; - for (int32 z = 0; z < vertexCountEdge; z++) + for (int32 z = 0; z < info.VertexCountEdge; z++) { - const int32 tz = (chunkTextureZ + z) * textureSize; - const int32 sz = (chunkHeightmapZ + z) * heightMapSize; + const int32 tz = (chunkTextureZ + z) * info.TextureSize; + const int32 sz = (chunkHeightmapZ + z) * info.HeightmapSize; - for (int32 x = 0; x < vertexCountEdge; x++) + for (int32 x = 0; x < info.VertexCountEdge; x++) { const int32 tx = chunkTextureX + x; const int32 sx = chunkHeightmapX + x; @@ -1324,9 +1351,7 @@ void TerrainPatch::CacheSplatData() bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize) { // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this); if (samples == nullptr) { LOG(Warning, "Missing heightmap samples data."); @@ -1334,13 +1359,12 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid heightmap samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifyHeightMap"); // Check if has no heightmap @@ -1364,31 +1388,20 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff // Modify heightmap data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - heightMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + heightMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = 0.0f; - info.PatchHeight = 1.0f; - // Process heightmap to get per-patch height normalization values - float chunkOffsets[CHUNKS_COUNT]; - float chunkHeights[CHUNKS_COUNT]; + float chunkOffsets[Terrain::ChunksCount]; + float chunkHeights[Terrain::ChunksCount]; CalculateHeightmapRange(_terrain, info, heightMap, chunkOffsets, chunkHeights); // TODO: maybe calculate chunk ranges for only modified chunks const bool wasHeightRangeChanged = Math::NotNearEqual(_yOffset, info.PatchOffset) || Math::NotNearEqual(_yHeight, info.PatchHeight); @@ -1418,7 +1431,7 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff // Update all the stuff _yOffset = info.PatchOffset; _yHeight = info.PatchHeight; - for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { auto& chunk = Chunks[chunkIndex]; chunk._yOffset = chunkOffsets[chunkIndex]; @@ -1426,15 +1439,13 @@ bool TerrainPatch::ModifyHeightMap(const float* samples, const Int2& modifiedOff chunk.UpdateTransform(); } _terrain->UpdateBounds(); - return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged); + return UpdateHeightData(info, modifiedOffset, modifiedSize, wasHeightRangeChanged, true); } bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize) { // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); if (samples == nullptr) { LOG(Warning, "Missing holes mask samples data."); @@ -1442,13 +1453,12 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid holes mask samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifyHolesMask"); // Check if has no heightmap @@ -1472,28 +1482,17 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs // Modify holes mask data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - holesMask[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + holesMask[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; - // Check if has allocated texture if (_dataHeightmap) { @@ -1505,7 +1504,7 @@ bool TerrainPatch::ModifyHolesMask(const byte* samples, const Int2& modifiedOffs } // Update all the stuff - return UpdateHeightData(info, modifiedOffset, modifiedSize, false); + return UpdateHeightData(info, modifiedOffset, modifiedSize, false, true); } bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize) @@ -1523,9 +1522,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int } // Validate input samples range - const int32 chunkSize = _terrain->_chunkSize; - const int32 vertexCountEdge = chunkSize + 1; - const int32 heightMapSize = chunkSize * CHUNKS_COUNT_EDGE + 1; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); if (samples == nullptr) { LOG(Warning, "Missing splatmap samples data."); @@ -1533,13 +1530,12 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int } if (modifiedOffset.X < 0 || modifiedOffset.Y < 0 || modifiedSize.X <= 0 || modifiedSize.Y <= 0 || - modifiedOffset.X + modifiedSize.X > heightMapSize || - modifiedOffset.Y + modifiedSize.Y > heightMapSize) + modifiedOffset.X + modifiedSize.X > info.HeightmapSize || + modifiedOffset.Y + modifiedSize.Y > info.HeightmapSize) { LOG(Warning, "Invalid heightmap samples range."); return true; } - PROFILE_CPU_NAMED("Terrain.ModifySplatMap"); // Get the current data to modify it @@ -1552,14 +1548,13 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int // Modify splat map data { PROFILE_CPU_NAMED("Terrain.WrtieCache"); - for (int32 z = 0; z < modifiedSize.Y; z++) { // TODO: use batches row mem copy for (int32 x = 0; x < modifiedSize.X; x++) { - splatMap[(z + modifiedOffset.Y) * heightMapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; + splatMap[(z + modifiedOffset.Y) * info.HeightmapSize + (x + modifiedOffset.X)] = samples[z * modifiedSize.X + x]; } } } @@ -1570,7 +1565,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int if (dataSplatmap == nullptr) { PROFILE_CPU_NAMED("Terrain.InitDataStorage"); - if (Heightmap->WaitForLoaded()) { LOG(Error, "Failed to load heightmap."); @@ -1597,16 +1591,6 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int mip.Data.Allocate(mip.SlicePitch); } - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = chunkSize; - info.VertexCountEdge = vertexCountEdge; - info.HeightmapSize = heightMapSize; - info.HeightmapLength = heightMapSize * heightMapSize; - info.TextureSize = vertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; - // Update splat map storage data const bool hasSplatmap = splatmap; const auto splatmapData = dataSplatmap->Mips[0].Data.Get(); @@ -1653,7 +1637,7 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int #if USE_EDITOR const bool useVirtualStorage = Editor::IsPlayMode || Heightmap->IsVirtual(); #else - const bool useVirtualStorage = true; + const bool useVirtualStorage = true; #endif // Save the splatmap data to the asset @@ -1697,11 +1681,11 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int } } #else - else - { - // Not supported - CRASH; - } + else + { + // Not supported + CRASH; + } #endif } @@ -1712,12 +1696,18 @@ bool TerrainPatch::ModifySplatMap(int32 index, const Color32* samples, const Int // TODO: disable splatmap dynamic streaming - data on a GPU was modified and we don't want to override it with the old data stored in the asset container + // Update heightfield to reflect physical materials layering + if (info.UsePhysicalMaterials() && HasCollision()) + { + UpdateHeightData(info, modifiedOffset, modifiedSize, false, false); + } + return false; } -bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged) +bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged) { - // Prepare + PROFILE_CPU(); float* heightMap = GetHeightmapData(); byte* holesMask = GetHolesMaskData(); ASSERT(heightMap && holesMask); @@ -1753,9 +1743,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int // Downscale mip data for all lower LODs if (GenerateMips(_dataHeightmap)) - { return true; - } // Fix generated mip maps to keep the same values for chunk edges (reduce cracks on continuous LOD transitions) FixMips(info, _dataHeightmap, pixelStride); @@ -1779,9 +1767,7 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int } const auto collisionData = &_heightfield->Data; if (CookCollision(info, _dataHeightmap, _terrain->_collisionLod, collisionData)) - { return true; - } UpdateCollision(); } else @@ -1789,7 +1775,8 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int ScopeLock lock(_collisionLocker); if (ModifyCollision(info, _dataHeightmap, _terrain->_collisionLod, modifiedOffset, modifiedSize, _physicsHeightField)) return true; - UpdateCollisionScale(); + if (wasHeightChanged) + UpdateCollisionScale(); } #else // Modify heightfield samples (without cooking collision which is done on a separate async task) @@ -1811,6 +1798,9 @@ bool TerrainPatch::UpdateHeightData(const TerrainDataUpdateInfo& info, const Int } #endif + if (!wasHeightChanged) + return false; + // Invalidate cache #if TERRAIN_USE_PHYSICS_DEBUG _debugLines.Resize(0); @@ -1843,18 +1833,8 @@ void TerrainPatch::SaveHeightData() { return; } - PROFILE_CPU_NAMED("Terrain.Save"); - - // Setup patch data info - TerrainDataUpdateInfo info; - info.ChunkSize = _terrain->_chunkSize; - info.VertexCountEdge = info.ChunkSize + 1; - info.HeightmapSize = info.ChunkSize * CHUNKS_COUNT_EDGE + 1; - info.HeightmapLength = info.HeightmapSize * info.HeightmapSize; - info.TextureSize = info.VertexCountEdge * CHUNKS_COUNT_EDGE; - info.PatchOffset = _yOffset; - info.PatchHeight = _yHeight; + TerrainDataUpdateInfo info(this, _yOffset, _yHeight); // Save heightmap to asset if (Heightmap->WaitForLoaded()) @@ -1913,7 +1893,6 @@ void TerrainPatch::SaveSplatData(int32 index) { return; } - PROFILE_CPU_NAMED("Terrain.Save"); // Save splatmap to asset @@ -1937,6 +1916,7 @@ void TerrainPatch::SaveSplatData(int32 index) bool TerrainPatch::UpdateCollision() { + PROFILE_CPU(); ScopeLock lock(_collisionLocker); // Update collision @@ -2013,7 +1993,7 @@ bool TerrainPatch::RayCast(const Vector3& origin, const Vector3& direction, floa // Find hit chunk resultChunk = nullptr; const auto hitPoint = origin + direction * hitDistance; - for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { const auto box = Chunks[chunkIndex]._bounds; if (box.Minimum.X <= hitPoint.X && box.Maximum.X >= hitPoint.X && @@ -2068,7 +2048,7 @@ void TerrainPatch::ClosestPoint(const Vector3& position, Vector3& result) const void TerrainPatch::UpdatePostManualDeserialization() { // Update data - for (int32 chunkIndex = 0; chunkIndex < CHUNKS_COUNT; chunkIndex++) + for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++) { auto& chunk = Chunks[chunkIndex]; chunk.UpdateTransform(); @@ -2111,6 +2091,7 @@ void TerrainPatch::UpdatePostManualDeserialization() void TerrainPatch::CreateCollision() { + PROFILE_CPU(); ASSERT(!HasCollision()); if (CreateHeightField()) return; @@ -2125,7 +2106,10 @@ void TerrainPatch::CreateCollision() shape.SetHeightField(_physicsHeightField, heightScale, rowScale, columnScale); // Create shape - _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, _terrain->PhysicalMaterial.Get(), _terrain->IsActiveInHierarchy(), false); + JsonAsset* materials[8]; + for (int32 i = 0; i < 8; i++) + materials[i] = _terrain->GetPhysicalMaterials()[i]; + _physicsShape = PhysicsBackend::CreateShape(_terrain, shape, ToSpan(materials, 8), _terrain->IsActiveInHierarchy(), false); PhysicsBackend::SetShapeLocalPose(_physicsShape, Vector3(0, _yOffset * terrainTransform.Scale.Y, 0), Quaternion::Identity); // Create static actor @@ -2137,6 +2121,7 @@ void TerrainPatch::CreateCollision() bool TerrainPatch::CreateHeightField() { + PROFILE_CPU(); ASSERT(_physicsHeightField == nullptr); // Skip if height field data is missing but warn on loading failed @@ -2162,6 +2147,7 @@ bool TerrainPatch::CreateHeightField() void TerrainPatch::UpdateCollisionScale() const { + PROFILE_CPU(); ASSERT(HasCollision()); // Create geometry @@ -2179,6 +2165,7 @@ void TerrainPatch::UpdateCollisionScale() const void TerrainPatch::DestroyCollision() { + PROFILE_CPU(); ScopeLock lock(_collisionLocker); ASSERT(HasCollision()); @@ -2205,6 +2192,7 @@ void TerrainPatch::DestroyCollision() void TerrainPatch::CacheDebugLines() { + PROFILE_CPU(); ASSERT(_debugLines.IsEmpty() && _physicsHeightField); int32 rows, cols; @@ -2213,12 +2201,21 @@ void TerrainPatch::CacheDebugLines() _debugLines.Resize((rows - 1) * (cols - 1) * 6 + (cols + rows - 2) * 2); Vector3* data = _debugLines.Get(); -#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))) +#define GET_VERTEX(x, y) const Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))) for (int32 row = 0; row < rows - 1; row++) { for (int32 col = 0; col < cols - 1; col++) { + // Skip holes + const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col); + if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole) + { + for (int32 i = 0; i < 6; i++) + *data++ = Vector3::Zero; + continue; + } + GET_VERTEX(0, 0); GET_VERTEX(0, 1); GET_VERTEX(1, 0); @@ -2294,6 +2291,7 @@ const Array& TerrainPatch::GetCollisionTriangles() ScopeLock lock(_collisionLocker); if (!_physicsShape || _collisionTriangles.HasItems()) return _collisionTriangles; + PROFILE_CPU(); int32 rows, cols; PhysicsBackend::GetHeightFieldSize(_physicsHeightField, rows, cols); @@ -2301,9 +2299,9 @@ const Array& TerrainPatch::GetCollisionTriangles() _collisionTriangles.Resize((rows - 1) * (cols - 1) * 6); Vector3* data = _collisionTriangles.Get(); -#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, (float)(row + (x)), (float)(col + (y))) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) +#define GET_VERTEX(x, y) Vector3 v##x##y((float)(row + (x)), PhysicsBackend::GetHeightFieldHeight(_physicsHeightField, row + (x), col + (y)) / TERRAIN_PATCH_COLLISION_QUANTIZATION, (float)(col + (y))); Vector3::Transform(v##x##y, world, v##x##y) - const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; const Transform terrainTransform = _terrain->_transform; Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Vector3(_collisionScaleXZ, _yHeight, _collisionScaleXZ)); const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld(); @@ -2312,6 +2310,15 @@ const Array& TerrainPatch::GetCollisionTriangles() { for (int32 col = 0; col < cols - 1; col++) { + // Skip holes + const auto sample = PhysicsBackend::GetHeightFieldSample(_physicsHeightField, row, col); + if (sample.MaterialIndex0 == (uint8)PhysicsBackend::HeightFieldMaterial::Hole) + { + for (int32 i = 0; i < 6; i++) + *data++ = Vector3::Zero; + continue; + } + GET_VERTEX(0, 0); GET_VERTEX(0, 1); GET_VERTEX(1, 0); @@ -2334,6 +2341,7 @@ const Array& TerrainPatch::GetCollisionTriangles() void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& result) { + PROFILE_CPU(); result.Clear(); // Skip if no intersection with patch @@ -2342,7 +2350,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; Transform transform; transform.Translation = _offset + Vector3(0, _yOffset, 0); transform.Orientation = Quaternion::Identity; @@ -2430,6 +2438,7 @@ void TerrainPatch::GetCollisionTriangles(const BoundingSphere& bounds, Array& vertexBuffer, Array& indexBuffer) { + PROFILE_CPU(); vertexBuffer.Clear(); indexBuffer.Clear(); @@ -2447,7 +2456,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, Array_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::Terrain::ChunksCountEdge; const Transform terrainTransform = _terrain->_transform; const Transform localTransform(Vector3(_x * size, _yOffset, _z * size), Quaternion::Identity, Float3(_collisionScaleXZ, _yHeight, _collisionScaleXZ)); const Matrix world = localTransform.GetWorld() * terrainTransform.GetWorld(); @@ -2459,7 +2468,7 @@ void TerrainPatch::ExtractCollisionGeometry(Array& vertexBuffer, ArrayChunks[i] : nullptr); @@ -2536,15 +2545,14 @@ void TerrainPatch::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE_MEMBER(Heightfield, _heightfield); // Update offset (x or/and z may be modified) - const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * CHUNKS_COUNT_EDGE; + const float size = _terrain->_chunkSize * TERRAIN_UNITS_PER_VERTEX * Terrain::ChunksCountEdge; _offset = Vector3(_x * size, 0.0f, _z * size); auto member = stream.FindMember("Chunks"); if (member != stream.MemberEnd() && member->value.IsArray()) { auto& chunksData = member->value; - const auto chunksCount = Math::Min((int32)chunksData.Size(), CHUNKS_COUNT); - + const auto chunksCount = Math::Min((int32)chunksData.Size(), Terrain::ChunksCount); for (int32 i = 0; i < chunksCount; i++) { Chunks[i].Deserialize(chunksData[i], modifier); diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index 689c629c5..f1a82dea1 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -15,22 +15,14 @@ class TerrainMaterialShader; /// /// Represents single terrain patch made of 16 terrain chunks. /// -class FLAXENGINE_API TerrainPatch : public ISerializable +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API TerrainPatch : public ScriptingObject, public ISerializable { + DECLARE_SCRIPTING_TYPE(TerrainPatch); friend Terrain; friend TerrainPatch; friend TerrainChunk; -public: - - enum - { - CHUNKS_COUNT = 16, - CHUNKS_COUNT_EDGE = 4, - }; - private: - Terrain* _terrain; int16 _x, _z; float _yOffset, _yHeight; @@ -52,7 +44,7 @@ private: TextureBase::InitData* _dataSplatmap[TERRAIN_MAX_SPLATMAPS_COUNT] = {}; #endif #if TERRAIN_USE_PHYSICS_DEBUG - Array _debugLines; // TODO: large-worlds + Array _debugLines; // TODO: large-worlds #endif #if USE_EDITOR Array _collisionTriangles; // TODO: large-worlds @@ -62,23 +54,21 @@ private: void Init(Terrain* terrain, int16 x, int16 z); public: - /// /// Finalizes an instance of the class. /// ~TerrainPatch(); public: - /// /// The chunks contained within the patch. Organized in 4x4 square. /// - TerrainChunk Chunks[CHUNKS_COUNT]; + TerrainChunk Chunks[Terrain::ChunksCount]; /// /// The heightmap texture. /// - AssetReference Heightmap; + API_FIELD() AssetReference Heightmap; /// /// The splatmap textures. @@ -86,12 +76,10 @@ public: AssetReference Splatmap[TERRAIN_MAX_SPLATMAPS_COUNT]; public: - /// /// Gets the Y axis heightmap offset from terrain origin. /// - /// The offset. - FORCE_INLINE float GetOffsetY() const + API_FUNCTION() FORCE_INLINE float GetOffsetY() const { return _yOffset; } @@ -99,8 +87,7 @@ public: /// /// Gets the Y axis heightmap height. /// - /// The height. - FORCE_INLINE float GetHeightY() const + API_FUNCTION() FORCE_INLINE float GetHeightY() const { return _yHeight; } @@ -108,8 +95,7 @@ public: /// /// Gets the x coordinate. /// - /// The x position. - FORCE_INLINE int32 GetX() const + API_FUNCTION() FORCE_INLINE int32 GetX() const { return _x; } @@ -117,8 +103,7 @@ public: /// /// Gets the z coordinate. /// - /// The z position. - FORCE_INLINE int32 GetZ() const + API_FUNCTION() FORCE_INLINE int32 GetZ() const { return _z; } @@ -126,8 +111,7 @@ public: /// /// Gets the terrain. /// - /// The terrain, - FORCE_INLINE Terrain* GetTerrain() const + API_FUNCTION() FORCE_INLINE Terrain* GetTerrain() const { return _terrain; } @@ -137,9 +121,9 @@ public: /// /// The chunk zero-based index. /// The chunk. - TerrainChunk* GetChunk(int32 index) + API_FUNCTION() TerrainChunk* GetChunk(int32 index) { - if (index < 0 || index >= CHUNKS_COUNT) + if (index < 0 || index >= Terrain::ChunksCount) return nullptr; return &Chunks[index]; } @@ -149,9 +133,9 @@ public: /// /// The chunk location (x and z). /// The chunk. - TerrainChunk* GetChunk(const Int2& chunkCoord) + API_FUNCTION() TerrainChunk* GetChunk(API_PARAM(Ref) const Int2& chunkCoord) { - return GetChunk(chunkCoord.Y * CHUNKS_COUNT_EDGE + chunkCoord.X); + return GetChunk(chunkCoord.Y * Terrain::ChunksCountEdge + chunkCoord.X); } /// @@ -160,22 +144,43 @@ public: /// The chunk location x. /// The chunk location z. /// The chunk. - TerrainChunk* GetChunk(int32 x, int32 z) + API_FUNCTION() TerrainChunk* GetChunk(int32 x, int32 z) { - return GetChunk(z * CHUNKS_COUNT_EDGE + x); + return GetChunk(z * Terrain::ChunksCountEdge + x); + } + + /// + /// Gets the splatmap assigned to this patch. + /// + /// The zero-based index of the splatmap. + /// The splatmap texture. + API_FUNCTION() AssetReference GetSplatmap(int32 index) + { + if (index < 0 || index >= TERRAIN_MAX_SPLATMAPS_COUNT) + return nullptr; + return Splatmap[index]; + } + + /// + /// Sets a splatmap to this patch. + /// + /// The zero-based index of the splatmap. + /// Splatmap texture. + API_FUNCTION() void SetSplatmap(int32 index, const AssetReference& splatMap) + { + if (index >= 0 && index < TERRAIN_MAX_SPLATMAPS_COUNT) + Splatmap[index] = splatMap; } /// /// Gets the patch world bounds. /// - /// The bounding box. - FORCE_INLINE const BoundingBox& GetBounds() const + API_FUNCTION() FORCE_INLINE const BoundingBox& GetBounds() const { return _bounds; } public: - /// /// Removes the lightmap data from the terrain patch. /// @@ -192,12 +197,11 @@ public: void UpdateTransform(); #if TERRAIN_EDITING - /// /// Initializes the patch heightmap and collision to the default flat level. /// /// True if failed, otherwise false. - bool InitializeHeightMap(); + API_FUNCTION() bool InitializeHeightMap(); /// /// Setups the terrain patch using the specified heightmap data. @@ -207,7 +211,7 @@ public: /// The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - bool SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); + API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false); /// /// Setups the terrain patch layer weights using the specified splatmaps data. @@ -217,50 +221,48 @@ public: /// The splat map. Each array item contains 4 layer weights. /// If set to true patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM). /// True if failed, otherwise false. - bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false); - + API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false); #endif #if TERRAIN_UPDATING - /// /// Gets the raw pointer to the heightmap data. /// /// The heightmap data. - float* GetHeightmapData(); + API_FUNCTION() float* GetHeightmapData(); /// /// Clears cache of the heightmap data. /// - void ClearHeightmapCache(); + API_FUNCTION() void ClearHeightmapCache(); /// /// Gets the raw pointer to the holes mask data. /// /// The holes mask data. - byte* GetHolesMaskData(); + API_FUNCTION() byte* GetHolesMaskData(); /// /// Clears cache of the holes mask data. /// - void ClearHolesMaskCache(); + API_FUNCTION() void ClearHolesMaskCache(); /// /// Gets the raw pointer to the splat map data. /// /// The zero-based index of the splatmap texture. /// The splat map data. - Color32* GetSplatMapData(int32 index); + API_FUNCTION() Color32* GetSplatMapData(int32 index); /// /// Clears cache of the splat map data. /// - void ClearSplatMapCache(); + API_FUNCTION() void ClearSplatMapCache(); /// /// Clears all caches. /// - void ClearCache(); + API_FUNCTION() void ClearCache(); /// /// Modifies the terrain patch heightmap with the given samples. @@ -269,7 +271,7 @@ public: /// The offset from the first row and column of the heightmap data (offset destination x and z start position). /// The size of the heightmap to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifyHeightMap(API_PARAM(Ref) const float* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); /// /// Modifies the terrain patch holes mask with the given samples. @@ -278,7 +280,7 @@ public: /// The offset from the first row and column of the holes map data (offset destination x and z start position). /// The size of the holes map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifyHolesMask(API_PARAM(Ref) const byte* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); /// /// Modifies the terrain patch splat map (layers mask) with the given samples. @@ -288,21 +290,18 @@ public: /// The offset from the first row and column of the splat map data (offset destination x and z start position). /// The size of the splat map to modify (x and z). Amount of samples in each direction. /// True if failed, otherwise false. - bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize); + API_FUNCTION() bool ModifySplatMap(int32 index, API_PARAM(Ref) const Color32* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize); private: - - bool UpdateHeightData(const struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged); + bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged); void SaveHeightData(); void CacheHeightData(); void SaveSplatData(); void SaveSplatData(int32 index); void CacheSplatData(); - #endif public: - /// /// Performs a raycast against this terrain collision shape. /// @@ -311,7 +310,7 @@ public: /// The raycast result hit position distance from the ray origin. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. @@ -322,7 +321,7 @@ public: /// The raycast result hit position normal vector. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, Vector3& resultHitNormal, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) Vector3& resultHitNormal, float maxDistance = MAX_float) const; /// /// Performs a raycast against this terrain collision shape. Returns the hit chunk. @@ -333,7 +332,7 @@ public: /// The raycast result hit chunk. Valid only if raycast hits anything. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, float& resultHitDistance, TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) float& resultHitDistance, API_PARAM(Out) TerrainChunk*& resultChunk, float maxDistance = MAX_float) const; /// /// Performs a raycast against terrain collision, returns results in a RaycastHit structure. @@ -343,28 +342,24 @@ public: /// The result hit information. Valid only when method returns true. /// The maximum distance the ray should check for collisions. /// True if ray hits an object, otherwise false. - bool RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, float maxDistance = MAX_float) const; + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float) const; /// /// Gets a point on the terrain collider that is closest to a given location. Can be used to find a hit location or position to apply explosion force or any other special effects. /// /// The position to find the closest point to it. /// The result point on the collider that is closest to the specified location. - void ClosestPoint(const Vector3& position, Vector3& result) const; + API_FUNCTION() void ClosestPoint(API_PARAM(Ref) const Vector3& position, API_PARAM(Out) Vector3& result) const; #if USE_EDITOR - /// /// Updates the patch data after manual deserialization called at runtime (eg. by editor undo). /// void UpdatePostManualDeserialization(); - #endif public: - #if USE_EDITOR - /// /// Gets the collision mesh triangles array (3 vertices per triangle in linear list). Cached internally to reuse data. /// @@ -376,8 +371,7 @@ public: /// /// The world-space bounds to find terrain triangles that intersect with it. /// The result triangles that intersect with the given bounds (in world-space). - void GetCollisionTriangles(const BoundingSphere& bounds, Array& result); - + void GetCollisionTriangles(API_PARAM(Ref) const BoundingSphere& bounds, API_PARAM(Out) Array& result); #endif /// @@ -385,10 +379,9 @@ public: /// /// The output vertex buffer. /// The output index buffer. - void ExtractCollisionGeometry(Array& vertexBuffer, Array& indexBuffer); + API_FUNCTION() void ExtractCollisionGeometry(API_PARAM(Out) Array& vertexBuffer, API_PARAM(Out) Array& indexBuffer); private: - /// /// Determines whether this patch has created collision representation. /// @@ -419,8 +412,8 @@ private: void DestroyCollision(); #if TERRAIN_USE_PHYSICS_DEBUG - void CacheDebugLines(); - void DrawPhysicsDebug(RenderView& view); + void CacheDebugLines(); + void DrawPhysicsDebug(RenderView& view); #endif /// @@ -430,8 +423,8 @@ private: bool UpdateCollision(); void OnPhysicsSceneChanged(PhysicsScene* previous); -public: +public: // [ISerializable] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index c89e6aca5..dea8298ce 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -93,7 +93,7 @@ struct TIsPODType namespace { JobSystemService JobSystemInstance; - Thread* Threads[PLATFORM_THREADS_LIMIT] = {}; + Thread* Threads[PLATFORM_THREADS_LIMIT / 2] = {}; int32 ThreadsCount = 0; bool JobStartingOnDispatch = true; volatile int64 ExitFlag = 0; diff --git a/Source/Engine/Threading/ThreadLocal.h b/Source/Engine/Threading/ThreadLocal.h index 4de8ced57..b766d0430 100644 --- a/Source/Engine/Threading/ThreadLocal.h +++ b/Source/Engine/Threading/ThreadLocal.h @@ -5,15 +5,16 @@ #include "Engine/Core/Types/BaseTypes.h" #include "Engine/Platform/Platform.h" +#define THREAD_LOCAL_USE_DYNAMIC_BUCKETS (PLATFORM_DESKTOP) + /// -/// Per-thread local variable storage. -/// Implemented using atomic with per-thread storage indexed via thread id hashing. -/// ForConsider using 'THREADLOCAL' define before the variable instead. +/// Per-thread local variable storage for basic types (POD). Implemented using atomic with per-thread storage indexed via thread id hashing. Consider using 'THREADLOCAL' define before the variable instead. /// -template +template class ThreadLocal { protected: + static_assert(TIsPODType::Value, "Only POD types are supported"); struct Bucket { @@ -21,34 +22,34 @@ protected: T Value; }; - Bucket _buckets[MaxThreads]; + Bucket _staticBuckets[MaxThreads]; +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS + Bucket* _dynamicBuckets = nullptr; + constexpr static int32 DynamicMaxThreads = 1024; +#endif public: - ThreadLocal() { - // Clear buckets - if (ClearMemory) - { - Platform::MemoryClear(_buckets, sizeof(_buckets)); - } - else - { - for (int32 i = 0; i < MaxThreads; i++) - _buckets[i].ThreadID = 0; - } + Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS + ~ThreadLocal() + { + Platform::Free(_dynamicBuckets); + } +#endif + public: - - T& Get() + FORCE_INLINE T& Get() { - return _buckets[GetIndex()].Value; + return GetBucket().Value; } - void Set(const T& value) + FORCE_INLINE void Set(const T& value) { - _buckets[GetIndex()].Value = value; + GetBucket().Value = value; } int32 Count() const @@ -56,9 +57,19 @@ public: int32 result = 0; for (int32 i = 0; i < MaxThreads; i++) { - if (Platform::AtomicRead((int64 volatile*)&_buckets[i].ThreadID) != 0) + if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) result++; } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS + if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) + { + for (int32 i = 0; i < MaxThreads; i++) + { + if (Platform::AtomicRead((int64 volatile*)&dynamicBuckets[i].ThreadID) != 0) + result++; + } + } +#endif return result; } @@ -67,89 +78,79 @@ public: { for (int32 i = 0; i < MaxThreads; i++) { - if (Platform::AtomicRead((int64 volatile*)&_buckets[i].ThreadID) != 0) - result.Add(_buckets[i].Value); + if (Platform::AtomicRead((int64 volatile*)&_staticBuckets[i].ThreadID) != 0) + result.Add(_staticBuckets[i].Value); } +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS + if (auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets)) + { + for (int32 i = 0; i < MaxThreads; i++) + { + if (Platform::AtomicRead((int64 volatile*)&dynamicBuckets[i].ThreadID) != 0) + result.Add(dynamicBuckets[i].Value); + } + } +#endif + } + + void Clear() + { + Platform::MemoryClear(_staticBuckets, sizeof(_staticBuckets)); +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS + Platform::Free(_dynamicBuckets); + _dynamicBuckets = nullptr; +#endif } protected: - - FORCE_INLINE static int32 Hash(const int64 value) + Bucket& GetBucket() { - return value & (MaxThreads - 1); - } + const int64 key = (int64)Platform::GetCurrentThreadID(); - FORCE_INLINE int32 GetIndex() - { - int64 key = (int64)Platform::GetCurrentThreadID(); - auto index = Hash(key); - while (true) + // Search statically allocated buckets + int32 index = (int32)(key & (MaxThreads - 1)); + int32 spaceLeft = MaxThreads; + while (spaceLeft) { - const int64 value = Platform::AtomicRead(&_buckets[index].ThreadID); + const int64 value = Platform::AtomicRead(&_staticBuckets[index].ThreadID); if (value == key) - break; - if (value == 0 && Platform::InterlockedCompareExchange(&_buckets[index].ThreadID, key, 0) == 0) - break; - index = Hash(index + 1); + return _staticBuckets[index]; + if (value == 0 && Platform::InterlockedCompareExchange(&_staticBuckets[index].ThreadID, key, 0) == 0) + return _staticBuckets[index]; + index = (index + 1) & (MaxThreads - 1); + spaceLeft--; } - return index; - } -}; -/// -/// Per thread local object -/// -template -class ThreadLocalObject : public ThreadLocal -{ -public: - - typedef ThreadLocal Base; - -public: - - void Delete() - { - auto value = Base::Get(); - Base::SetAll(nullptr); - ::Delete(value); - } - - void DeleteAll() - { - for (int32 i = 0; i < MaxThreads; i++) +#if THREAD_LOCAL_USE_DYNAMIC_BUCKETS + // Allocate dynamic buckets if missing + DYNAMIC: + auto dynamicBuckets = (Bucket*)Platform::AtomicRead((intptr volatile*)&_dynamicBuckets); + if (!dynamicBuckets) { - auto& bucket = Base::_buckets[i]; - if (bucket.Value != nullptr) + dynamicBuckets = (Bucket*)Platform::Allocate(DynamicMaxThreads * sizeof(Bucket), 16); + Platform::MemoryClear(dynamicBuckets, DynamicMaxThreads * sizeof(Bucket)); + if (Platform::InterlockedCompareExchange((intptr volatile*)&_dynamicBuckets, (intptr)dynamicBuckets, 0) != 0) { - ::Delete(bucket.Value); - bucket.ThreadID = 0; - bucket.Value = nullptr; + Platform::Free(dynamicBuckets); + goto DYNAMIC; } } - } - template - void GetNotNullValues(Array& result) const - { - result.EnsureCapacity(MaxThreads); - for (int32 i = 0; i < MaxThreads; i++) + // Search dynamically allocated buckets + index = (int32)(key & (DynamicMaxThreads - 1)); + spaceLeft = DynamicMaxThreads; + while (spaceLeft) { - if (Base::_buckets[i].Value != nullptr) - { - result.Add(Base::_buckets[i].Value); - } + const int64 value = Platform::AtomicRead(&dynamicBuckets[index].ThreadID); + if (value == key) + return dynamicBuckets[index]; + if (value == 0 && Platform::InterlockedCompareExchange(&dynamicBuckets[index].ThreadID, key, 0) == 0) + return dynamicBuckets[index]; + index = (index + 1) & (DynamicMaxThreads - 1); + spaceLeft--; } - } +#endif - int32 CountNotNullValues() const - { - int32 result = 0; - for (int32 i = 0; i < MaxThreads; i++) - { - if (Base::_buckets[i].Value != nullptr) - result++; - } - return result; + return *(Bucket*)nullptr; } }; diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index b7db81ffa..2b6ed5e26 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -58,7 +58,7 @@ ThreadPoolService ThreadPoolServiceInstance; bool ThreadPoolService::Init() { // Spawn threads - const int32 numThreads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 2, PLATFORM_THREADS_LIMIT); + const int32 numThreads = Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 2, PLATFORM_THREADS_LIMIT / 2); LOG(Info, "Spawning {0} Thread Pool workers", numThreads); for (int32 i = ThreadPoolImpl::Threads.Count(); i < numThreads; i++) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index d0aa537f6..9578f6406 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -19,15 +19,15 @@ #include "Engine/Content/Content.h" #include "Engine/Serialization/MemoryWriteStream.h" #if USE_EDITOR +#include "Engine/Core/Utilities.h" +#include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/DateTime.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Types/Pair.h" #include "Engine/Core/Types/Variant.h" +#include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/Models/SkeletonUpdater.h" #include "Engine/Graphics/Models/SkeletonMapping.h" -#include "Engine/Core/Utilities.h" -#include "Engine/Core/Types/StringView.h" -#include "Engine/Platform/FileSystem.h" #include "Engine/Tools/TextureTool/TextureTool.h" #include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/CreateMaterial.h" @@ -35,6 +35,7 @@ #include "Engine/ContentImporters/CreateCollisionData.h" #include "Engine/Serialization/Serialization.h" #include "Editor/Utilities/EditorUtilities.h" +#include "Engine/Animations/Graph/AnimGraph.h" #include #endif @@ -361,7 +362,8 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj SERIALIZE(SkipEmptyCurves); SERIALIZE(OptimizeKeyframes); SERIALIZE(ImportScaleTracks); - SERIALIZE(EnableRootMotion); + SERIALIZE(RootMotion); + SERIALIZE(RootMotionFlags); SERIALIZE(RootNodeName); SERIALIZE(GenerateLODs); SERIALIZE(BaseLOD); @@ -410,7 +412,8 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(SkipEmptyCurves); DESERIALIZE(OptimizeKeyframes); DESERIALIZE(ImportScaleTracks); - DESERIALIZE(EnableRootMotion); + DESERIALIZE(RootMotion); + DESERIALIZE(RootMotionFlags); DESERIALIZE(RootNodeName); DESERIALIZE(GenerateLODs); DESERIALIZE(BaseLOD); @@ -435,6 +438,15 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi DESERIALIZE(AnimationIndex); if (AnimationIndex != -1) ObjectIndex = AnimationIndex; + + // [Deprecated on 08.02.2024, expires on 08.02.2026] + bool EnableRootMotion = false; + DESERIALIZE(EnableRootMotion); + if (EnableRootMotion) + { + RootMotion = RootMotionMode::ExtractNode; + RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ; + } } void RemoveNamespace(String& name) @@ -809,6 +821,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option break; case ModelType::Animation: options.ImportTypes = ImportDataTypes::Animations; + if (options.RootMotion == RootMotionMode::ExtractCenterOfMass) + options.ImportTypes |= ImportDataTypes::Skeleton; break; case ModelType::Prefab: options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations; @@ -1373,6 +1387,129 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } + // Process root motion setup + animation.RootMotionFlags = options.RootMotion != RootMotionMode::None ? options.RootMotionFlags : AnimationRootMotionFlags::None; + animation.RootNodeName = options.RootNodeName.TrimTrailing(); + if (animation.RootMotionFlags != AnimationRootMotionFlags::None && animation.Channels.HasItems()) + { + if (options.RootMotion == RootMotionMode::ExtractNode) + { + if (animation.RootNodeName.HasChars() && animation.GetChannel(animation.RootNodeName) == nullptr) + { + LOG(Warning, "Missing Root Motion node '{}'", animation.RootNodeName); + } + } + else if (options.RootMotion == RootMotionMode::ExtractCenterOfMass && data.Skeleton.Nodes.HasItems()) // TODO: finish implementing this + { + // Setup root node animation track + const auto& rootName = data.Skeleton.Nodes.First().Name; + auto rootChannelPtr = animation.GetChannel(rootName); + if (!rootChannelPtr) + { + animation.Channels.Insert(0, NodeAnimationData()); + rootChannelPtr = &animation.Channels[0]; + rootChannelPtr->NodeName = rootName; + } + animation.RootNodeName = rootName; + auto& rootChannel = *rootChannelPtr; + rootChannel.Position.Clear(); + + // Calculate skeleton center of mass position over the animation frames + const int32 frames = (int32)animation.Duration; + const int32 nodes = data.Skeleton.Nodes.Count(); + Array centerOfMass; + centerOfMass.Resize(frames); + for (int32 frame = 0; frame < frames; frame++) + { + auto& key = centerOfMass[frame]; + + // Evaluate skeleton pose at the animation frame + AnimGraphImpulse pose; + pose.Nodes.Resize(nodes); + for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) + { + Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; + auto& node = data.Skeleton.Nodes[nodeIndex]; + if (auto* channel = animation.GetChannel(node.Name)) + channel->Evaluate((float)frame, &srcNode, false); + pose.Nodes[nodeIndex] = srcNode; + } + + // Calculate average location of the pose (center of mass) + key = Float3::Zero; + for (int32 nodeIndex = 0; nodeIndex < nodes; nodeIndex++) + key += pose.GetNodeModelTransformation(data.Skeleton, nodeIndex).Translation; + key /= (float)nodes; + } + + // Calculate skeleton center of mass movement over the animation frames + rootChannel.Position.Resize(frames); + const Float3 centerOfMassRefPose = centerOfMass[0]; + for (int32 frame = 0; frame < frames; frame++) + { + auto& key = rootChannel.Position[frame]; + key.Time = (float)frame; + key.Value = centerOfMass[frame] - centerOfMassRefPose; + } + + // Remove root motion from the children (eg. if Root moves, then Hips should skip that movement delta) + Float3 maxMotion = Float3::Zero; + for (int32 i = 0; i < animation.Channels.Count(); i++) + { + auto& anim = animation.Channels[i]; + const int32 animNodeIndex = data.Skeleton.FindNode(anim.NodeName); + + // Skip channels that have one of their parents already animated + { + int32 nodeIndex = animNodeIndex; + nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex; + while (nodeIndex > 0) + { + const String& nodeName = data.Skeleton.Nodes[nodeIndex].Name; + if (animation.GetChannel(nodeName) != nullptr) + break; + nodeIndex = data.Skeleton.Nodes[nodeIndex].ParentIndex; + } + if (nodeIndex > 0 || &anim == rootChannelPtr) + continue; + } + + // Remove motion + auto& animPos = anim.Position.GetKeyframes(); + for (int32 frame = 0; frame < animPos.Count(); frame++) + { + auto& key = animPos[frame]; + + // Evaluate root motion at the keyframe location + Float3 rootMotion; + rootChannel.Position.Evaluate(rootMotion, key.Time, false); + Float3::Max(maxMotion, rootMotion, maxMotion); + + // Evaluate skeleton pose at the animation frame + AnimGraphImpulse pose; + pose.Nodes.Resize(nodes); + pose.Nodes[0] = data.Skeleton.Nodes[0].LocalTransform; // Use ref pose of root + for (int32 nodeIndex = 1; nodeIndex < nodes; nodeIndex++) // Skip new root + { + Transform srcNode = data.Skeleton.Nodes[nodeIndex].LocalTransform; + auto& node = data.Skeleton.Nodes[nodeIndex]; + if (auto* channel = animation.GetChannel(node.Name)) + channel->Evaluate((float)frame, &srcNode, false); + pose.Nodes[nodeIndex] = srcNode; + } + + // Convert root motion to the local space of this node so the node stays at the same location after adding new root channel + Transform parentNodeTransform = pose.GetNodeModelTransformation(data.Skeleton, data.Skeleton.Nodes[animNodeIndex].ParentIndex); + rootMotion = parentNodeTransform.WorldToLocal(rootMotion); + + // Remove motion + key.Value -= rootMotion; + } + } + LOG(Info, "Calculated root motion: {}", maxMotion); + } + } + // Optimize the keyframes if (options.OptimizeKeyframes) { @@ -1395,9 +1532,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option const int32 after = animation.GetKeyframesCount(); LOG(Info, "Optimized {0} animation keyframe(s). Before: {1}, after: {2}, Ratio: {3}%", before - after, before, after, Utilities::RoundTo2DecimalPlaces((float)after / before)); } - - animation.EnableRootMotion = options.EnableRootMotion; - animation.RootNodeName = options.RootNodeName; } } diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index df89a1519..6468c1a43 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -129,6 +129,19 @@ public: Custom = 1, }; + /// + /// Declares the imported animation Root Motion modes. + /// + API_ENUM(Attributes="HideInEditor") enum class RootMotionMode + { + // Root Motion feature is disabled. + None = 0, + // Motion is extracted from the root node (or node specified by name). + ExtractNode = 1, + // Motion is extracted from the center of mass movement (estimated based on the skeleton pose animation). + ExtractCenterOfMass = 2, + }; + /// /// Model import options. /// @@ -228,9 +241,12 @@ public: bool ImportScaleTracks = false; // Enables root motion extraction support from this animation. API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") - bool EnableRootMotion = false; + RootMotionMode RootMotion = RootMotionMode::None; + // Adjusts root motion applying flags. Can customize how root node animation can affect target actor movement (eg. apply both position and rotation changes). + API_FIELD(Attributes="EditorOrder(1060), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))") + AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::RootPositionXZ; // The custom node name to be used as a root motion source. If not specified the actual root node will be used. - API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowAnimation))") + API_FIELD(Attributes="EditorOrder(1070), EditorDisplay(\"Animation\"), VisibleIf(nameof(ShowRootMotion))") String RootNodeName = TEXT(""); public: // Level Of Detail @@ -251,7 +267,7 @@ public: API_FIELD(Attributes="EditorOrder(1140), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(ShowGeometry))") bool SloppyOptimization = false; // Only used if Sloppy is false. Target error is an approximate measure of the deviation from the original mesh using distance normalized to [0..1] range (e.g. 1e-2f means that simplifier will try to maintain the error to be below 1% of the mesh extents). - API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), Limit(0.01f, 1, 0.001f)") + API_FIELD(Attributes="EditorOrder(1150), EditorDisplay(\"Level Of Detail\"), VisibleIf(nameof(SloppyOptimization), true), VisibleIf(nameof(ShowGeometry)), Limit(0.01f, 1, 0.001f)") float LODTargetError = 0.05f; public: // Materials diff --git a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs index 044c65044..b4351da75 100644 --- a/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs +++ b/Source/Engine/UI/GUI/Common/RichTextBox.Tags.cs @@ -280,7 +280,7 @@ namespace FlaxEngine.GUI foreach (var id in ids) { var path = Content.GetEditorAssetPath(id); - if (!string.IsNullOrEmpty(path) && + if (!string.IsNullOrEmpty(path) && string.Equals(name, System.IO.Path.GetFileNameWithoutExtension(path), System.StringComparison.OrdinalIgnoreCase)) { return Content.LoadAsync(id, type); diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs index 6dbd5082c..efffa6728 100644 --- a/Source/Engine/UI/GUI/Common/Slider.cs +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; namespace FlaxEngine.GUI; @@ -7,6 +9,32 @@ namespace FlaxEngine.GUI; /// public class Slider : ContainerControl { + /// + /// The slider direction + /// + public enum SliderDirection + { + /// + /// Slider direction, horizontal right + /// + HorizontalRight, + + /// + /// Slider direction, horizontal left + /// + HorizontalLeft, + + /// + /// Slider direction, vertical up + /// + VerticalUp, + + /// + /// Slider direction, vertical down + /// + VerticalDown, + } + /// /// The minimum value. /// @@ -15,26 +43,7 @@ public class Slider : ContainerControl /// /// The maximum value. /// - protected float _maximum = 100f; - - /// - /// Gets or sets the minimum value. - /// - [EditorOrder(20), Tooltip("The minimum value.")] - public float Minimum - { - get => _minimum; - set - { - if (value > _maximum) - throw new ArgumentOutOfRangeException(); - if (WholeNumbers) - value = Mathf.RoundToInt(value); - _minimum = value; - if (Value < _minimum) - Value = _minimum; - } - } + protected float _maximum = 100; /// /// Gets or sets the maximum value. @@ -45,8 +54,6 @@ public class Slider : ContainerControl get => _maximum; set { - if (value < _minimum || Mathf.IsZero(value)) - throw new ArgumentOutOfRangeException(); if (WholeNumbers) value = Mathf.RoundToInt(value); _maximum = value; @@ -55,6 +62,38 @@ public class Slider : ContainerControl } } + /// + /// Gets or sets the minimum value. + /// + [EditorOrder(20), Tooltip("The minimum value.")] + public float Minimum + { + get => _minimum; + set + { + if (WholeNumbers) + value = Mathf.RoundToInt(value); + _minimum = value; + if (Value < _minimum) + Value = _minimum; + } + } + + /// + /// Gets or sets the slider direction. + /// + [EditorOrder(40), Tooltip("Slider Direction.")] + public SliderDirection Direction + { + get => _direction; + set + { + _direction = value; + UpdateThumb(); + } + } + + private SliderDirection _direction = SliderDirection.HorizontalRight; private float _value = 100f; private Rectangle _thumbRect; private float _thumbCenter; @@ -89,31 +128,60 @@ public class Slider : ContainerControl /// The local position of the thumb center /// [HideInEditor] - public Float2 ThumbCenter => new(_thumbCenter, Height / 2); + public Float2 ThumbCenter => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? new Float2(_thumbCenter, Height / 2) : new Float2(Width / 2, _thumbCenter); /// /// The local position of the beginning of the track. /// [HideInEditor] - public Float2 TrackBeginning => new(_thumbSize.X / 2, Height / 2); + public Float2 TrackBeginning + { + get + { + switch (Direction) + { + case SliderDirection.HorizontalRight: return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: return new Float2(Width / 2, Height - _thumbSize.Y / 2); + case SliderDirection.VerticalDown: return new Float2(Width / 2, _thumbSize.Y / 2); + default: break; + } + return Float2.Zero; + } + } /// /// The local position of the end of the track. /// [HideInEditor] - public Float2 TrackEnd => new(Width - _thumbSize.X / 2, Height / 2); - + public Float2 TrackEnd + { + get + { + switch (Direction) + { + case SliderDirection.HorizontalRight: return new Float2(Width - _thumbSize.X / 2, Height / 2); + case SliderDirection.HorizontalLeft: return new Float2(_thumbSize.X / 2, Height / 2); + case SliderDirection.VerticalUp: return new Float2(Width / 2, _thumbSize.Y / 2); + case SliderDirection.VerticalDown: return new Float2(Width / 2, Height - _thumbSize.Y / 2); + default: break; + } + return Float2.Zero; + } + } + /// /// The height of the track. /// [EditorOrder(40), Tooltip("The track height.")] - public int TrackHeight { get; set; } = 2; + public int TrackThickness { get; set; } = 2; /// /// The thumb size. /// [EditorOrder(41), Tooltip("The size of the thumb.")] - public Float2 ThumbSize { + public Float2 ThumbSize + { get => _thumbSize; set { @@ -127,7 +195,7 @@ public class Slider : ContainerControl /// [EditorOrder(42), Tooltip("Fill the track.")] public bool FillTrack = true; - + /// /// Whether to use whole numbers. /// @@ -147,9 +215,14 @@ public class Slider : ContainerControl public Color TrackFillLineColor { get; set; } /// - /// Gets the size of the track. + /// Gets the width of the track. /// - private float TrackWidth => Width; + private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness; + + /// + /// Gets the height of the track. + /// + private float TrackHeight => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? TrackThickness : Height - _thumbSize.Y; /// /// Gets or sets the brush used for slider track drawing. @@ -168,7 +241,7 @@ public class Slider : ContainerControl /// [EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups] public Color ThumbColor { get; set; } - + /// /// The color of the slider thumb when it's highlighted. /// @@ -202,12 +275,12 @@ public class Slider : ContainerControl /// Occurs when sliding ends. /// public event Action SlidingEnd; - + /// /// Occurs when value gets changed. /// public event Action ValueChanged; - + /// /// Initializes a new instance of the class. /// @@ -236,13 +309,32 @@ public class Slider : ContainerControl private void UpdateThumb() { // Cache data - float trackSize = TrackWidth; + var isHorizontal = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft; + float trackSize = isHorizontal ? Width : Height; float range = Maximum - Minimum; - float pixelRange = trackSize - _thumbSize.X; + float pixelRange = trackSize - (isHorizontal ? _thumbSize.X : _thumbSize.Y); float perc = (_value - Minimum) / range; float thumbPosition = (int)(perc * pixelRange); - _thumbCenter = thumbPosition + _thumbSize.X / 2; - _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + switch (Direction) + { + case SliderDirection.HorizontalRight: + _thumbCenter = thumbPosition + _thumbSize.X / 2; + _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.VerticalDown: + _thumbCenter = thumbPosition + _thumbSize.Y / 2; + _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, thumbPosition, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.HorizontalLeft: + _thumbCenter = Width - thumbPosition - _thumbSize.X / 2; + _thumbRect = new Rectangle(Width - thumbPosition - _thumbSize.X, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + break; + case SliderDirection.VerticalUp: + _thumbCenter = Height - thumbPosition - _thumbSize.Y / 2; + _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, Height - thumbPosition - _thumbSize.Y, _thumbSize.X, _thumbSize.Y); + break; + default: break; + } } private void EndSliding() @@ -256,19 +348,36 @@ public class Slider : ContainerControl public override void Draw() { base.Draw(); - + + // Set rectangles + var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackThickness) / 2, Width - _thumbSize.X, TrackThickness); + var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackThickness - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2 + 1, TrackThickness + 2); + switch (Direction) + { + case SliderDirection.HorizontalRight: break; + case SliderDirection.VerticalDown: + lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, _thumbSize.Y / 2 - 1, TrackThickness + 2, Height - (Height - _thumbCenter) - _thumbSize.Y / 2 + 1); + break; + case SliderDirection.HorizontalLeft: + fillLineRect = new Rectangle(Width - (Width - _thumbCenter) - 1, (Height - TrackThickness - 2) / 2, Width - _thumbCenter + 1, TrackThickness + 2); + break; + case SliderDirection.VerticalUp: + lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); + fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, Height - (Height - _thumbCenter) - 1, TrackThickness + 2, Height - _thumbCenter + 1); + break; + default: break; + } + // Draw track line - //var lineRect = new Rectangle(4, (Height - TrackHeight) / 2, Width - 8, TrackHeight); - var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight) / 2, Width - _thumbSize.X, TrackHeight); if (TrackBrush != null) TrackBrush.Draw(lineRect, TrackLineColor); else Render2D.FillRectangle(lineRect, TrackLineColor); - + // Draw track fill if (FillTrack) { - var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); Render2D.PushClip(ref fillLineRect); if (FillTrackBrush != null) FillTrackBrush.Draw(lineRect, TrackFillLineColor); @@ -276,13 +385,13 @@ public class Slider : ContainerControl Render2D.FillRectangle(lineRect, TrackFillLineColor); Render2D.PopClip(); } - + // Draw thumb - var thumbColor = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); + var thumbColorV = _isSliding ? ThumbColorSelected : (_mouseOverThumb ? ThumbColorHighlighted : ThumbColor); if (ThumbBrush != null) - ThumbBrush.Draw(_thumbRect, thumbColor); + ThumbBrush.Draw(_thumbRect, thumbColorV); else - Render2D.FillRectangle(_thumbRect, thumbColor); + Render2D.FillRectangle(_thumbRect, thumbColorV); } /// @@ -302,7 +411,7 @@ public class Slider : ContainerControl if (button == MouseButton.Left) { Focus(); - float mousePosition = location.X; + float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y; if (_thumbRect.Contains(ref location)) { @@ -315,7 +424,16 @@ public class Slider : ContainerControl else { // Click change - Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + switch (Direction) + { + case SliderDirection.HorizontalRight or SliderDirection.VerticalDown: + Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + break; + case SliderDirection.HorizontalLeft or SliderDirection.VerticalUp: + Value -= (mousePosition < _thumbCenter ? -1 : 1) * 10; + break; + default: break; + } } } @@ -330,7 +448,22 @@ public class Slider : ContainerControl { // Update sliding var slidePosition = location + Root.TrackingMouseOffset; - Value = Mathf.Remap(slidePosition.X, 4, TrackWidth - 4, Minimum, Maximum); + switch (Direction) + { + case SliderDirection.HorizontalRight: + Value = Mathf.Remap(slidePosition.X, 4, Width - 4, Minimum, Maximum); + break; + case SliderDirection.VerticalDown: + Value = Mathf.Remap(slidePosition.Y, 4, Height - 4, Minimum, Maximum); + break; + case SliderDirection.HorizontalLeft: + Value = Mathf.Remap(slidePosition.X, Width - 4, 4, Minimum, Maximum); + break; + case SliderDirection.VerticalUp: + Value = Mathf.Remap(slidePosition.Y, Height - 4, 4, Minimum, Maximum); + break; + default: break; + } } else { diff --git a/Source/Engine/UI/GUI/Common/TextBoxBase.cs b/Source/Engine/UI/GUI/Common/TextBoxBase.cs index 55b58634d..35819da79 100644 --- a/Source/Engine/UI/GUI/Common/TextBoxBase.cs +++ b/Source/Engine/UI/GUI/Common/TextBoxBase.cs @@ -280,13 +280,13 @@ namespace FlaxEngine.GUI /// [EditorDisplay("Border Style"), EditorOrder(2010), Tooltip("Whether to have a border."), ExpandGroups] public bool HasBorder { get; set; } = true; - + /// /// Gets or sets the border thickness. /// [EditorDisplay("Border Style"), EditorOrder(2011), Tooltip("The thickness of the border."), Limit(0)] public float BorderThickness { get; set; } = 1.0f; - + /// /// Gets or sets the color of the border (Transparent if not used). /// diff --git a/Source/Engine/UI/TextRender.cpp b/Source/Engine/UI/TextRender.cpp index aa49dac45..d190f1123 100644 --- a/Source/Engine/UI/TextRender.cpp +++ b/Source/Engine/UI/TextRender.cpp @@ -239,7 +239,7 @@ void TextRender::UpdateLayout() const bool isWhitespace = StringUtils::IsWhitespace(c); if (!isWhitespace && previous.IsValid) { - kerning = font->GetKerning(previous.Character, entry.Character); + kerning = entry.Font->GetKerning(previous.Character, entry.Character); } else { diff --git a/Source/Engine/UI/UICanvas.cs b/Source/Engine/UI/UICanvas.cs index bc8c360d1..dda941745 100644 --- a/Source/Engine/UI/UICanvas.cs +++ b/Source/Engine/UI/UICanvas.cs @@ -450,7 +450,7 @@ namespace FlaxEngine { camera.GetMatrices(out tmp1, out var tmp3, ref viewport); Matrix.Multiply(ref tmp1, ref tmp3, out tmp2); - var frustum = new BoundingFrustum(tmp2); + var frustum = new BoundingFrustum(ref tmp2); _guiRoot.Size = new Float2(frustum.GetWidthAtDepth(Distance), frustum.GetHeightAtDepth(Distance)); } else diff --git a/Source/Engine/Utilities/Extensions.cs b/Source/Engine/Utilities/Extensions.cs index a1834624e..4b85541c8 100644 --- a/Source/Engine/Utilities/Extensions.cs +++ b/Source/Engine/Utilities/Extensions.cs @@ -297,8 +297,21 @@ namespace FlaxEngine.Utilities /// A random . public static Vector2 NextUnitVector2(this Random random, float radius = 1.0f) { - var randomRadius = (float)random.NextDouble() * radius; - return new Vector2((float)Math.Cos(random.NextDouble()) * randomRadius, (float)Math.Sin(random.NextDouble()) * randomRadius); + float magnitude = (float)random.NextDouble() * radius; + double randomRadian = random.NextDouble() * Mathf.RevolutionsToRadians; + return new Vector2((float)Math.Cos(randomRadian) * magnitude, (float)Math.Sin(randomRadian) * magnitude); + } + + /// + /// Generates a random point on a circle of a given radius. + /// + /// An instance of . + /// Radius of circle. Default 1.0f./>. + /// A random . + public static Vector2 NextUnitCircleVector2(this Random random, float radius = 1.0f) + { + double randomRadian = random.NextDouble() * Mathf.RevolutionsToRadians; + return new Vector2((float)Math.Cos(randomRadian) * radius, (float)Math.Sin(randomRadian) * radius); } /// diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 085c05de0..5054bd54d 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -101,7 +101,7 @@ public: /// Visject graph parameter. /// /// -API_CLASS() class VisjectGraphParameter : public GraphParameter +API_CLASS() class FLAXENGINE_API VisjectGraphParameter : public GraphParameter { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(VisjectGraphParameter, GraphParameter); public: diff --git a/Source/Engine/Visject/VisjectMeta.cpp b/Source/Engine/Visject/VisjectMeta.cpp index 37631df55..624345813 100644 --- a/Source/Engine/Visject/VisjectMeta.cpp +++ b/Source/Engine/Visject/VisjectMeta.cpp @@ -5,10 +5,6 @@ #include "Engine/Serialization/ReadStream.h" #include "Engine/Serialization/WriteStream.h" -VisjectMeta::VisjectMeta() -{ -} - bool VisjectMeta::Load(ReadStream* stream, bool loadData) { Release(); diff --git a/Source/Engine/Visject/VisjectMeta.h b/Source/Engine/Visject/VisjectMeta.h index c417b7030..b35add815 100644 --- a/Source/Engine/Visject/VisjectMeta.h +++ b/Source/Engine/Visject/VisjectMeta.h @@ -8,7 +8,7 @@ /// /// Visject metadata container /// -class VisjectMeta +class FLAXENGINE_API VisjectMeta { public: /// @@ -27,19 +27,6 @@ public: /// Array> Entries; -public: - /// - /// Initializes a new instance of the class. - /// - VisjectMeta(); - - /// - /// Finalizes an instance of the class. - /// - ~VisjectMeta() - { - } - public: /// /// Load from the stream diff --git a/Source/Shaders/ColorGrading.shader b/Source/Shaders/ColorGrading.shader index ae639fc4d..9d9175614 100644 --- a/Source/Shaders/ColorGrading.shader +++ b/Source/Shaders/ColorGrading.shader @@ -243,7 +243,7 @@ float4 CombineLUTs(float2 uv, uint layerIndex) // Apply LDR LUT color grading { - float3 uvw = color * (15.0 / 16.0) + (0.5f / 16.0); + float3 uvw = saturate(color) * (15.0 / 16.0) + (0.5f / 16.0); float3 lutColor = SampleUnwrappedTexture3D(LutTexture, SamplerLinearClamp, uvw, 16).rgb; color = lerp(color, lutColor, LutWeight); } diff --git a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs index b0167c024..72653900c 100644 --- a/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/ApiTypeInfo.cs @@ -22,7 +22,7 @@ namespace Flax.Build.Bindings public string[] Comment; public bool IsInBuild; public bool IsDeprecated; - public string MarshalAs; + public TypeInfo MarshalAs; internal bool IsInited; internal TypedefInfo Instigator; diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs index 4a0070710..1849513f9 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Api.cs @@ -197,7 +197,7 @@ namespace Flax.Build.Bindings if (apiType != null) { if (apiType.MarshalAs != null) - return UsePassByReference(buildData, new TypeInfo(apiType.MarshalAs), caller); + return UsePassByReference(buildData, apiType.MarshalAs, caller); // Skip for scripting objects if (apiType.IsScriptingObject) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index 1587fce79..959e2ee24 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -90,6 +90,15 @@ namespace Flax.Build.Bindings "Int4", }; + private static bool GenerateCSharpUseFixedBuffer(string managedType) + { + return managedType == "byte" || managedType == "char" || + managedType == "short" || managedType == "ushort" || + managedType == "int" || managedType == "uint" || + managedType == "long" || managedType == "ulong" || + managedType == "float" || managedType == "double"; + } + private static string GenerateCSharpDefaultValueNativeToManaged(BuildData buildData, string value, ApiTypeInfo caller, TypeInfo valueType = null, bool attribute = false, string managedType = null) { if (string.IsNullOrEmpty(value)) @@ -261,7 +270,7 @@ namespace Flax.Build.Bindings return value; } - private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + private static string GenerateCSharpNativeToManaged(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { string result; if (typeInfo?.Type == null) @@ -271,7 +280,7 @@ namespace Flax.Build.Bindings if (typeInfo.IsArray) { typeInfo.IsArray = false; - result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + result = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); typeInfo.IsArray = true; return result + "[]"; } @@ -298,7 +307,7 @@ namespace Flax.Build.Bindings // Object reference property if (typeInfo.IsObjectRef) - return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling); if (typeInfo.Type == "SoftTypeReference" || typeInfo.Type == "SoftObjectReference") return typeInfo.Type; @@ -308,15 +317,25 @@ namespace Flax.Build.Bindings #else if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) #endif - return GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller) + "[]"; + { + var arrayTypeInfo = typeInfo.GenericArgs[0]; + if (marshalling) + { + // Convert array that uses different type for marshalling + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; + } + return GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller) + "[]"; + } // Dictionary if (typeInfo.Type == "Dictionary" && typeInfo.GenericArgs != null) - return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller)); + return string.Format("System.Collections.Generic.Dictionary<{0}, {1}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling), GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[1], caller, marshalling)); // HashSet if (typeInfo.Type == "HashSet" && typeInfo.GenericArgs != null) - return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)); + return string.Format("System.Collections.Generic.HashSet<{0}>", GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)); // BitArray if (typeInfo.Type == "BitArray" && typeInfo.GenericArgs != null) @@ -339,16 +358,16 @@ namespace Flax.Build.Bindings // TODO: generate delegates globally in the module namespace to share more code (smaller binary size) var key = string.Empty; for (int i = 0; i < typeInfo.GenericArgs.Count; i++) - key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); + key += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling); if (!CSharpAdditionalCodeCache.TryGetValue(key, out var delegateName)) { delegateName = "Delegate" + CSharpAdditionalCodeCache.Count; - var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller)} {delegateName}("; + var signature = $"public delegate {GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[0], caller, marshalling)} {delegateName}("; for (int i = 1; i < typeInfo.GenericArgs.Count; i++) { if (i != 1) signature += ", "; - signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller); + signature += GenerateCSharpNativeToManaged(buildData, typeInfo.GenericArgs[i], caller, marshalling); signature += $" arg{(i - 1)}"; } signature += ");"; @@ -381,11 +400,14 @@ namespace Flax.Build.Bindings { typeName += '<'; foreach (var arg in typeInfo.GenericArgs) - typeName += GenerateCSharpNativeToManaged(buildData, arg, caller); + typeName += GenerateCSharpNativeToManaged(buildData, arg, caller, marshalling); typeName += '>'; } if (apiType != null) { + if (marshalling && apiType.MarshalAs != null) + return GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller); + // Add reference to the namespace CSharpUsedNamespaces.Add(apiType.Namespace); var apiTypeParent = apiType.Parent; @@ -410,11 +432,11 @@ namespace Flax.Build.Bindings return typeName; } - private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) + private static string GenerateCSharpManagedToNativeType(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, bool marshalling = false) { // Fixed-size array if (typeInfo.IsArray) - return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); // Find API type info var apiType = FindApiTypeInfo(buildData, typeInfo, caller); @@ -430,7 +452,7 @@ namespace Flax.Build.Bindings } if (apiType.MarshalAs != null) - return GenerateCSharpManagedToNativeType(buildData, new TypeInfo(apiType.MarshalAs), caller); + return GenerateCSharpManagedToNativeType(buildData, apiType.MarshalAs, caller, marshalling); if (apiType.IsScriptingObject || apiType.IsInterface) return "IntPtr"; } @@ -443,7 +465,7 @@ namespace Flax.Build.Bindings if (typeInfo.Type == "Function" && typeInfo.GenericArgs != null) return "IntPtr"; - return GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + return GenerateCSharpNativeToManaged(buildData, typeInfo, caller, marshalling); } private static string GenerateCSharpManagedToNativeConverter(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller) @@ -476,6 +498,18 @@ namespace Flax.Build.Bindings case "Function": // delegate return "NativeInterop.GetFunctionPointerForDelegate({0})"; + case "Array": + case "Span": + case "DataContainer": + if (typeInfo.GenericArgs != null) + { + // Convert array that uses different type for marshalling + var arrayTypeInfo = typeInfo.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + return $"{{0}}.ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayApiType.MarshalAs, caller)})x)"; + } + return string.Empty; default: var apiType = FindApiTypeInfo(buildData, typeInfo, caller); if (apiType != null) @@ -522,9 +556,9 @@ namespace Flax.Build.Bindings { var apiType = FindApiTypeInfo(buildData, functionInfo.ReturnType, caller); if (apiType != null && apiType.MarshalAs != null) - returnValueType = GenerateCSharpNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, apiType.MarshalAs, caller, true); else - returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller); + returnValueType = GenerateCSharpNativeToManaged(buildData, functionInfo.ReturnType, caller, true); } #if USE_NETCORE @@ -585,7 +619,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); + var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true); #if USE_NETCORE string parameterMarshalType = ""; if (nativeType == "System.Type") @@ -638,7 +672,7 @@ namespace Flax.Build.Bindings contents.Append(", "); separator = true; - var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller); + var nativeType = GenerateCSharpManagedToNativeType(buildData, parameterInfo.Type, caller, true); #if USE_NETCORE string parameterMarshalType = ""; if (parameterInfo.IsOut && parameterInfo.DefaultValue == "var __resultAsRef") @@ -751,7 +785,16 @@ namespace Flax.Build.Bindings } } - contents.Append(");"); + contents.Append(')'); + if ((functionInfo.ReturnType.Type == "Array" || functionInfo.ReturnType.Type == "Span" || functionInfo.ReturnType.Type == "DataContainer") && functionInfo.ReturnType.GenericArgs != null) + { + // Convert array that uses different type for marshalling + var arrayTypeInfo = functionInfo.ReturnType.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + contents.Append($".ConvertArray(x => ({GenerateCSharpNativeToManaged(buildData, arrayTypeInfo, caller)})x)"); + } + contents.Append(';'); // Return result if (functionInfo.Glue.UseReferenceForResult) @@ -1439,10 +1482,10 @@ namespace Flax.Build.Bindings indent += " "; - StringBuilder toManagedContent = new StringBuilder(); - StringBuilder toNativeContent = new StringBuilder(); - StringBuilder freeContents = new StringBuilder(); - StringBuilder freeContents2 = new StringBuilder(); + var toManagedContent = GetStringBuilder(); + var toNativeContent = GetStringBuilder(); + var freeContents = GetStringBuilder(); + var freeContents2 = GetStringBuilder(); { // Native struct begin @@ -1457,10 +1500,9 @@ namespace Flax.Build.Bindings contents.Append(indent + "{"); indent += " "; - toNativeContent.Append($"return new {structureInfo.Name}Internal() {{ "); - toManagedContent.Append($"return new {structureInfo.Name}() {{ "); + toNativeContent.Append($"var unmanaged = new {structureInfo.Name}Internal();").AppendLine(); + toManagedContent.Append($"var managed = new {structureInfo.Name}();").AppendLine(); - bool useSeparator = false; contents.AppendLine(); foreach (var fieldInfo in structureInfo.Fields) { @@ -1478,11 +1520,7 @@ namespace Flax.Build.Bindings else originalType = type = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); - contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); - if (fieldInfo.IsConstexpr) - contents.Append("const "); - else if (fieldInfo.IsStatic) - contents.Append("static "); + contents.Append(indent).Append("public "); var apiType = FindApiTypeInfo(buildData, fieldInfo.Type, structureInfo); bool internalType = apiType is StructureInfo fieldStructureInfo && UseCustomMarshalling(buildData, fieldStructureInfo, structureInfo); @@ -1490,16 +1528,43 @@ namespace Flax.Build.Bindings if (fieldInfo.Type.IsArray && (fieldInfo.NoArray || structureInfo.IsPod)) { - contents.Append(type).Append(' ').Append(fieldInfo.Name + "0;").AppendLine(); - for (int i = 1; i < fieldInfo.Type.ArraySize; i++) +#if USE_NETCORE + if (GenerateCSharpUseFixedBuffer(originalType)) { - contents.AppendLine(); - GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); - contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); - if (fieldInfo.IsStatic) - contents.Append("static "); - contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + // Use fixed statement with primitive types of buffers + contents.Append($"fixed {originalType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); + + // Copy fixed-size array + toManagedContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(managed.{fieldInfo.Name}0), new IntPtr(unmanaged.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); + toNativeContent.AppendLine($"FlaxEngine.Utils.MemoryCopy(new IntPtr(unmanaged.{fieldInfo.Name}0), new IntPtr(managed.{fieldInfo.Name}0), sizeof({originalType}) * {fieldInfo.Type.ArraySize}ul);"); } + else +#endif + { + // Padding in structs for fixed-size array + contents.Append(type).Append(' ').Append(fieldInfo.Name).Append("0;").AppendLine(); + for (int i = 1; i < fieldInfo.Type.ArraySize; i++) + { + GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); + contents.Append(indent).Append("public "); + contents.Append(type).Append(' ').Append(fieldInfo.Name + i).Append(';').AppendLine(); + } + + // Copy fixed-size array item one-by-one + if (fieldInfo.Access == AccessLevel.Public || fieldInfo.Access == AccessLevel.Internal) + { + for (int i = 0; i < fieldInfo.Type.ArraySize; i++) + { + toManagedContent.AppendLine($"managed.{fieldInfo.Name}{i} = unmanaged.{fieldInfo.Name}{i};"); + toNativeContent.AppendLine($"unmanaged.{fieldInfo.Name}{i} = managed.{fieldInfo.Name}{i};"); + } + } + else + { + throw new NotImplementedException("TODO: generate utility method to copy private/protected array data items"); + } + } + continue; } else { @@ -1529,34 +1594,17 @@ namespace Flax.Build.Bindings //else if (type == "Guid") // type = "GuidNative"; - contents.Append(type).Append(' ').Append(fieldInfo.Name); - contents.Append(';').AppendLine(); + contents.Append(type).Append(' ').Append(fieldInfo.Name).Append(';').AppendLine(); } // Generate struct constructor/getter and deconstructor/setter function - if (fieldInfo.NoArray && fieldInfo.Type.IsArray) - continue; - - if (useSeparator) - { - toManagedContent.Append(", "); - toNativeContent.Append(", "); - freeContents2.Append(""); - freeContents.Append(""); - } - useSeparator = true; - - toManagedContent.Append(fieldInfo.Name); - toManagedContent.Append(" = "); - - toNativeContent.Append(fieldInfo.Name); - toNativeContent.Append(" = "); - + toManagedContent.Append("managed.").Append(fieldInfo.Name).Append(" = "); + toNativeContent.Append("unmanaged.").Append(fieldInfo.Name).Append(" = "); if (fieldInfo.Type.IsObjectRef) { var managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type.GenericArgs[0], structureInfo); - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{managedType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1564,8 +1612,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.Type == "ScriptingObject") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1573,8 +1621,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.IsPtr && originalType != "IntPtr" && !originalType.EndsWith("*")) { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} != null ? ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1582,8 +1630,8 @@ namespace Flax.Build.Bindings } else if (fieldInfo.Type.Type == "Dictionary") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } @@ -1595,16 +1643,16 @@ namespace Flax.Build.Bindings // Marshal blittable array elements back to original non-blittable elements string originalElementTypeMarshaller = originalElementType + "Marshaller"; string internalElementType = $"{originalElementTypeMarshaller}.{originalElementType}Internal"; - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.ConvertArray((Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToSpan<{internalElementType}>(), {originalElementTypeMarshaller}.ToManaged) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(NativeInterop.ConvertArray(managed.{fieldInfo.Name}, {originalElementTypeMarshaller}.ToNative)), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span<{internalElementType}> values = (Unsafe.As(handle.Target)).ToSpan<{internalElementType}>(); foreach (var value in values) {{ {originalElementTypeMarshaller}.Free(value); }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } else if (fieldInfo.Type.GenericArgs[0].IsObjectRef) { // Array elements passed as GCHandles - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)) : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? NativeInterop.GCHandleArrayToManagedArray<{originalElementType}>(Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)) : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(NativeInterop.ManagedArrayToGCHandleWrappedArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); Span ptrs = (Unsafe.As(handle.Target)).ToSpan(); foreach (var ptr in ptrs) {{ if (ptr != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(ptr).Free(); }} }} (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); // Permanent ScriptingObject handle is passed from native side, do not release it @@ -1613,53 +1661,53 @@ namespace Flax.Build.Bindings else { // Blittable array elements - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null"); - toNativeContent.Append($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? (Unsafe.As(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target)).ToArray<{originalElementType}>() : null;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name}?.Length > 0 ? ManagedHandle.ToIntPtr(ManagedArray.WrapNewArray(managed.{fieldInfo.Name}), GCHandleType.Weak) : IntPtr.Zero;"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle handle = ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}); (Unsafe.As(handle.Target)).Free(); handle.Free(); }}"); } } else if (fieldInfo.Type.Type == "Version") { - toManagedContent.Append($"managed.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(managed.{fieldInfo.Name}).Target) : null"); - toNativeContent.Append($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak)"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != IntPtr.Zero ? Unsafe.As<{originalType}>(ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Target) : null;"); + toNativeContent.AppendLine($"ManagedHandle.ToIntPtr(managed.{fieldInfo.Name}, GCHandleType.Weak);"); freeContents.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); freeContents2.AppendLine($"if (unmanaged.{fieldInfo.Name} != IntPtr.Zero) {{ ManagedHandle.FromIntPtr(unmanaged.{fieldInfo.Name}).Free(); }}"); } else if (originalType == "string") { - toManagedContent.Append($"ManagedString.ToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"ManagedString.ToNative(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"ManagedString.ToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"ManagedString.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"ManagedString.Free(unmanaged.{fieldInfo.Name});"); } else if (originalType == "bool") { - toManagedContent.Append($"managed.{fieldInfo.Name} != 0"); - toNativeContent.Append($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0"); + toManagedContent.AppendLine($"unmanaged.{fieldInfo.Name} != 0;"); + toNativeContent.AppendLine($"managed.{fieldInfo.Name} ? (byte)1 : (byte)0;"); } else if (fieldInfo.Type.Type == "Variant") { // Variant passed as boxed object handle - toManagedContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"ManagedHandleMarshaller.NativeToManaged.ConvertToUnmanaged(managed.{fieldInfo.Name});"); } else if (internalType) { - toManagedContent.Append($"{internalTypeMarshaller}.ToManaged(managed.{fieldInfo.Name})"); - toNativeContent.Append($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name})"); + toManagedContent.AppendLine($"{internalTypeMarshaller}.ToManaged(unmanaged.{fieldInfo.Name});"); + toNativeContent.AppendLine($"{internalTypeMarshaller}.ToNative(managed.{fieldInfo.Name});"); freeContents.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); freeContents2.AppendLine($"{internalTypeMarshaller}.Free(unmanaged.{fieldInfo.Name});"); } /*else if (originalType == "Guid") { - toManagedContent.Append("(Guid)managed.").Append(fieldInfo.Name); + toManagedContent.Append("(Guid)unmanaged.").Append(fieldInfo.Name); toNativeContent.Append("(GuidNative)managed.").Append(fieldInfo.Name); }*/ else { - toManagedContent.Append("managed.").Append(fieldInfo.Name); - toNativeContent.Append("managed.").Append(fieldInfo.Name); + toManagedContent.Append("unmanaged.").Append(fieldInfo.Name).AppendLine(";"); + toNativeContent.Append("managed.").Append(fieldInfo.Name).AppendLine(";"); } } @@ -1667,8 +1715,8 @@ namespace Flax.Build.Bindings indent = indent.Substring(0, indent.Length - 4); contents.AppendLine(indent + "}").AppendLine(); - toManagedContent.AppendLine(" };"); - toNativeContent.AppendLine(" };"); + toNativeContent.Append("return unmanaged;"); + toManagedContent.Append("return managed;"); } var indent2 = indent + " "; @@ -1716,7 +1764,7 @@ namespace Flax.Build.Bindings contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(freeContents.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); // Managed/native converters - contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal managed)"); + contents.Append(indent).AppendLine($"internal static {structureInfo.Name} ToManaged({structureInfo.Name}Internal unmanaged)"); contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toManagedContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); contents.Append(indent).AppendLine($"internal static {structureInfo.Name}Internal ToNative({structureInfo.Name} managed)"); contents.Append(indent).AppendLine("{").Append(indent2).AppendLine(toNativeContent.Replace("\n", "\n" + indent2).ToString().TrimEnd()).Append(indent).AppendLine("}"); @@ -1724,6 +1772,11 @@ namespace Flax.Build.Bindings contents.AppendLine("#pragma warning restore 1591"); indent = indent.Substring(0, indent.Length - 4); contents.Append(indent).AppendLine("}").AppendLine(); + + PutStringBuilder(toManagedContent); + PutStringBuilder(toNativeContent); + PutStringBuilder(freeContents); + PutStringBuilder(freeContents2); } #endif // Struct docs @@ -1771,15 +1824,12 @@ namespace Flax.Build.Bindings managedType = GenerateCSharpNativeToManaged(buildData, fieldInfo.Type, structureInfo); fieldInfo.Type.IsArray = true; #if USE_NETCORE - // Use fixed statement with primitive types of buffers - if (managedType == "char") + if (GenerateCSharpUseFixedBuffer(managedType)) { - // char's are not blittable, store as short instead - contents.Append($"fixed short {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); - } - else if (managedType == "byte") - { - contents.Append($"fixed byte {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}]; // {managedType}*").AppendLine(); + // Use fixed statement with primitive types of buffers + if (managedType == "char") + managedType = "short"; // char's are not blittable, store as short instead + contents.Append($"fixed {managedType} {fieldInfo.Name}0[{fieldInfo.Type.ArraySize}];").AppendLine(); } else #endif @@ -1789,7 +1839,6 @@ namespace Flax.Build.Bindings for (int i = 1; i < fieldInfo.Type.ArraySize; i++) { contents.AppendLine(); - GenerateCSharpComment(contents, indent, fieldInfo.Comment); GenerateCSharpAttributes(buildData, contents, indent, structureInfo, fieldInfo, fieldInfo.IsStatic); contents.Append(indent).Append(GenerateCSharpAccessLevel(fieldInfo.Access)); if (fieldInfo.IsStatic) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs index 079a5839f..d78af861f 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cache.cs @@ -19,7 +19,7 @@ namespace Flax.Build.Bindings partial class BindingsGenerator { private static readonly Dictionary TypeCache = new Dictionary(); - private const int CacheVersion = 21; + private const int CacheVersion = 22; internal static void Write(BinaryWriter writer, string e) { diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs index 4e632014a..35d1d32fa 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Cpp.cs @@ -167,11 +167,7 @@ namespace Flax.Build.Bindings return $"Variant(StringView({value}))"; if (typeInfo.Type == "StringAnsi") return $"Variant(StringAnsiView({value}))"; - if (typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "SoftObjectReference") + if (typeInfo.IsObjectRef) return $"Variant({value}.Get())"; if (typeInfo.IsArray) { @@ -227,10 +223,10 @@ namespace Flax.Build.Bindings return $"(StringAnsiView){value}"; if (typeInfo.IsPtr && typeInfo.IsConst && typeInfo.Type == "Char") return $"((StringView){value}).GetText()"; // (StringView)Variant, if not empty, is guaranteed to point to a null-terminated buffer. - if (typeInfo.Type == "AssetReference" || typeInfo.Type == "WeakAssetReference" || typeInfo.Type == "SoftAssetReference") - return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.Type == "ScriptingObjectReference" || typeInfo.Type == "SoftObjectReference") return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((ScriptingObject*){value})"; + if (typeInfo.IsObjectRef) + return $"ScriptingObject::Cast<{typeInfo.GenericArgs[0].Type}>((Asset*){value})"; if (typeInfo.IsArray) throw new Exception($"Not supported type to convert from the Variant to fixed-size array '{typeInfo}[{typeInfo.ArraySize}]'."); if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) @@ -309,7 +305,7 @@ namespace Flax.Build.Bindings private static string GenerateCppGetMClass(BuildData buildData, TypeInfo typeInfo, ApiTypeInfo caller, FunctionInfo functionInfo) { // Optimal path for in-build types - var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { // In-built types (cached by the engine on startup) @@ -392,7 +388,7 @@ namespace Flax.Build.Bindings CppIncludeFiles.Add("Engine/Scripting/ManagedCLR/MClass.h"); // Optimal path for in-build types - var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller); + var managedType = GenerateCSharpNativeToManaged(buildData, typeInfo, caller, true); switch (managedType) { case "bool": @@ -514,11 +510,7 @@ namespace Flax.Build.Bindings return "MUtils::ToManaged({0})"; default: // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) { type = "MObject*"; return "{0}.GetManagedInstance()"; @@ -527,16 +519,28 @@ namespace Flax.Build.Bindings // Array or DataContainer if ((typeInfo.Type == "Array" || typeInfo.Type == "Span" || typeInfo.Type == "DataContainer") && typeInfo.GenericArgs != null) { + var arrayTypeInfo = typeInfo.GenericArgs[0]; #if USE_NETCORE // Boolean arrays does not support custom marshalling for some unknown reason - if (typeInfo.GenericArgs[0].Type == "bool") + if (arrayTypeInfo.Type == "bool") { type = "bool*"; return "MUtils::ToBoolArray({0})"; } + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); #endif type = "MArray*"; - return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, typeInfo.GenericArgs[0], caller, functionInfo) + ")"; + if (arrayApiType != null && arrayApiType.MarshalAs != null) + { + // Convert array that uses different type for marshalling + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; // Convert array that uses different type for marshalling + var genericArgs = arrayApiType.MarshalAs.GetFullNameNative(buildData, caller); + if (typeInfo.GenericArgs.Count != 1) + genericArgs += ", " + typeInfo.GenericArgs[1]; + return "MUtils::ToArray(Array<" + genericArgs + ">({0}), " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; + } + return "MUtils::ToArray({0}, " + GenerateCppGetMClass(buildData, arrayTypeInfo, caller, functionInfo) + ")"; } // Span @@ -597,7 +601,7 @@ namespace Flax.Build.Bindings CppReferencesFiles.Add(apiType.File); if (apiType.MarshalAs != null) - return GenerateCppWrapperNativeToManaged(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, functionInfo); + return GenerateCppWrapperNativeToManaged(buildData, apiType.MarshalAs, caller, out type, functionInfo); // Scripting Object if (apiType.IsScriptingObject) @@ -704,11 +708,7 @@ namespace Flax.Build.Bindings return "MUtils::ToNative({0})"; default: // Object reference property - if ((typeInfo.Type == "ScriptingObjectReference" || - typeInfo.Type == "AssetReference" || - typeInfo.Type == "WeakAssetReference" || - typeInfo.Type == "SoftAssetReference" || - typeInfo.Type == "SoftObjectReference") && typeInfo.GenericArgs != null) + if (typeInfo.IsObjectRef) { // For non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration) @@ -731,11 +731,26 @@ namespace Flax.Build.Bindings // Array if (typeInfo.Type == "Array" && typeInfo.GenericArgs != null) { - var T = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); - type = "MArray*"; + var arrayTypeInfo = typeInfo.GenericArgs[0]; + var arrayApiType = FindApiTypeInfo(buildData, arrayTypeInfo, caller); + if (arrayApiType != null && arrayApiType.MarshalAs != null) + arrayTypeInfo = arrayApiType.MarshalAs; + var genericArgs = arrayTypeInfo.GetFullNameNative(buildData, caller); if (typeInfo.GenericArgs.Count != 1) - return "MUtils::ToArray<" + T + ", " + typeInfo.GenericArgs[1] + ">({0})"; - return "MUtils::ToArray<" + T + ">({0})"; + genericArgs += ", " + typeInfo.GenericArgs[1]; + + type = "MArray*"; + var result = "MUtils::ToArray<" + genericArgs + ">({0})"; + + if (arrayApiType != null && arrayApiType.MarshalAs != null) + { + // Convert array that uses different type for marshalling + genericArgs = typeInfo.GenericArgs[0].GetFullNameNative(buildData, caller); + if (typeInfo.GenericArgs.Count != 1) + genericArgs += ", " + typeInfo.GenericArgs[1]; + result = $"Array<{genericArgs}>({result})"; + } + return result; } // Span or DataContainer @@ -801,7 +816,7 @@ namespace Flax.Build.Bindings if (apiType != null) { if (apiType.MarshalAs != null) - return GenerateCppWrapperManagedToNative(buildData, new TypeInfo(apiType.MarshalAs), caller, out type, out apiType, functionInfo, out needLocalVariable); + return GenerateCppWrapperManagedToNative(buildData, apiType.MarshalAs, caller, out type, out apiType, functionInfo, out needLocalVariable); // Scripting Object (for non-pod types converting only, other API converts managed to unmanaged object in C# wrapper code) if (CppNonPodTypesConvertingGeneration && apiType.IsScriptingObject && typeInfo.IsPtr) diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs index 0b71af086..3db43edf0 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.Parsing.cs @@ -185,6 +185,11 @@ namespace Flax.Build.Bindings tag.Value = tag.Value.Substring(1, tag.Value.Length - 2); if (tag.Value.Contains("\\\"")) tag.Value = tag.Value.Replace("\\\"", "\""); + token = context.Tokenizer.NextToken(); + if (token.Type == TokenType.Multiply) + tag.Value += token.Value; + else + context.Tokenizer.PreviousToken(); parameters.Add(tag); break; case TokenType.Whitespace: @@ -647,7 +652,7 @@ namespace Flax.Build.Bindings desc.Namespace = tag.Value; break; case "marshalas": - desc.MarshalAs = tag.Value; + desc.MarshalAs = TypeInfo.FromString(tag.Value); break; case "tag": ParseTag(ref desc.Tags, tag); @@ -1236,7 +1241,7 @@ namespace Flax.Build.Bindings desc.Namespace = tag.Value; break; case "marshalas": - desc.MarshalAs = tag.Value; + desc.MarshalAs = TypeInfo.FromString(tag.Value); break; case "tag": ParseTag(ref desc.Tags, tag); diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index cdf293085..61946bae2 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -180,6 +180,17 @@ namespace Flax.Build.Bindings return sb.ToString(); } + public static TypeInfo FromString(string text) + { + var result = new TypeInfo(text); + if (result.Type.EndsWith('*')) + { + result.IsPtr = true; + result.Type = result.Type.Substring(0, result.Type.Length - 1); + } + return result; + } + public string ToString(bool canRef = true) { var sb = new StringBuilder(64); diff --git a/Source/Tools/Flax.Build/Build/FileCache.cs b/Source/Tools/Flax.Build/Build/FileCache.cs index a11b53415..d21e0848a 100644 --- a/Source/Tools/Flax.Build/Build/FileCache.cs +++ b/Source/Tools/Flax.Build/Build/FileCache.cs @@ -9,32 +9,30 @@ namespace Flax.Build /// public static class FileCache { - private static Dictionary fileInfoCache = new Dictionary(); + private static readonly Dictionary _cache = new(); public static void FileRemoveFromCache(string path) { - //fileInfoCache[path].Refresh(); - fileInfoCache.Remove(path); + _cache.Remove(path); } - + public static bool Exists(string path) { - if (fileInfoCache.TryGetValue(path, out var fileInfo)) + if (_cache.TryGetValue(path, out var fileInfo)) return fileInfo.Exists; fileInfo = new FileInfo(path); - fileInfoCache.Add(path, fileInfo); + _cache.Add(path, fileInfo); return fileInfo.Exists; } public static DateTime GetLastWriteTime(string path) { - - if (fileInfoCache.TryGetValue(path, out var fileInfo)) + if (_cache.TryGetValue(path, out var fileInfo)) return fileInfo.LastWriteTime; fileInfo = new FileInfo(path); - fileInfoCache.Add(path, fileInfo); + _cache.Add(path, fileInfo); return fileInfo.LastWriteTime; } } diff --git a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs index f2146c82a..de8434023 100644 --- a/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs +++ b/Source/Tools/Flax.Build/Build/NativeCpp/Builder.NativeCpp.cs @@ -845,11 +845,11 @@ namespace Flax.Build foreach (var moduleName in moduleOptions.PrivateDependencies.Concat(moduleOptions.PublicDependencies)) { var dependencyModule = buildData.Rules.GetModule(moduleName); - if (dependencyModule != null && - !string.IsNullOrEmpty(dependencyModule.BinaryModuleName) && - dependencyModule.BinaryModuleName != binaryModule.Key && + if (dependencyModule != null && + !string.IsNullOrEmpty(dependencyModule.BinaryModuleName) && + dependencyModule.BinaryModuleName != binaryModule.Key && !moduleNamesUsed.Contains(dependencyModule.BinaryModuleName) && - GetModuleProject(dependencyModule, project) != null && + GetModuleProject(dependencyModule, project) != null && buildData.Modules.TryGetValue(dependencyModule, out var dependencyOptions)) { // Import symbols from referenced binary module diff --git a/Source/Tools/Flax.Build/Build/Platform.cs b/Source/Tools/Flax.Build/Build/Platform.cs index f458225fa..6b0d48dba 100644 --- a/Source/Tools/Flax.Build/Build/Platform.cs +++ b/Source/Tools/Flax.Build/Build/Platform.cs @@ -77,14 +77,10 @@ namespace Flax.Build var architectureId = RuntimeInformation.ProcessArchitecture; switch (architectureId) { - case Architecture.X86: - return TargetArchitecture.x86; - case Architecture.X64: - return TargetArchitecture.x64; - case Architecture.Arm: - return TargetArchitecture.ARM; - case Architecture.Arm64: - return TargetArchitecture.ARM64; + case Architecture.X86: return TargetArchitecture.x86; + case Architecture.X64: return TargetArchitecture.x64; + case Architecture.Arm: return TargetArchitecture.ARM; + case Architecture.Arm64: return TargetArchitecture.ARM64; default: throw new NotImplementedException(string.Format("Unsupported build platform {0}.", architectureId)); } } @@ -290,12 +286,9 @@ namespace Flax.Build var subdir = "Binaries/Editor/"; switch (Platform.BuildTargetPlatform) { - case TargetPlatform.Windows: - return subdir + "Win64"; - case TargetPlatform.Linux: - return subdir + "Linux"; - case TargetPlatform.Mac: - return subdir + "Mac"; + case TargetPlatform.Windows: return subdir + "Win64"; + case TargetPlatform.Linux: return subdir + "Linux"; + case TargetPlatform.Mac: return subdir + "Mac"; } throw new NotImplementedException(); } diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index d646a81cd..085c56680 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -802,9 +802,12 @@ namespace Flax.Build.Plugins // Serialize base type if (type.BaseType != null && type.BaseType.FullName != "System.ValueType" && type.BaseType.FullName != "FlaxEngine.Object" && type.BaseType.CanBeResolved()) { - GenerateSerializeCallback(module, il, type.BaseType.Resolve(), serialize); + GenerateSerializeCallback(module, il, type.BaseType, serialize); } + if (type.HasGenericParameters) // TODO: implement network replication for generic classes + MonoCecil.CompilationError($"Not supported generic type '{type.FullName}' for network replication."); + var ildContext = new DotnetIlContext(il); // Serialize all type fields marked with NetworkReplicated attribute @@ -874,12 +877,13 @@ namespace Flax.Build.Plugins return m; } - private static void GenerateSerializeCallback(ModuleDefinition module, ILProcessor il, TypeDefinition type, bool serialize) + private static void GenerateSerializeCallback(ModuleDefinition module, ILProcessor il, TypeReference type, bool serialize) { if (type.IsScriptingObject()) { // NetworkReplicator.InvokeSerializer(typeof(), instance, stream, ) - il.Emit(OpCodes.Ldtoken, module.ImportReference(type)); + module.ImportReference(type); + il.Emit(OpCodes.Ldtoken, type); module.GetType("System.Type", out var typeType); var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle"); il.Emit(OpCodes.Call, module.ImportReference(getTypeFromHandle)); diff --git a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs index e1f2ab4e6..75f43fd68 100644 --- a/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs +++ b/Source/Tools/Flax.Build/Deploy/Deployment.Editor.cs @@ -58,7 +58,7 @@ namespace Flax.Deploy DeployFile(src, dst, buildToolExe); CodeSign(Path.Combine(dst, buildToolExe)); var buildToolDll = "Flax.Build.dll"; - DeployFile(src, dst,buildToolDll); + DeployFile(src, dst, buildToolDll); CodeSign(Path.Combine(dst, buildToolDll)); DeployFile(src, dst, "Flax.Build.xml", true); DeployFile(src, dst, "Flax.Build.pdb"); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs index 528256f69..bc501c3a5 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/Assimp.cs @@ -128,7 +128,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { RunCmake(root, platform, architecture, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + globalConfig); Utilities.Run("make", null, null, root, Utilities.RunOptions.ThrowExceptionOnError); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs index 6e565b94c..bb42fc253 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/NvCloth.cs @@ -49,7 +49,7 @@ namespace Flax.Deps.Dependencies } } } - + /// public override void Build(BuildOptions options) { @@ -185,16 +185,16 @@ namespace Flax.Deps.Dependencies // Print the NvCloth version Log.Info($"Building {File.ReadAllLines(Path.Combine(root, "README.md"))[0].Trim()} to {platform} {architecture}"); - + // Generate project files SetupDirectory(buildFolder, false); Utilities.FileDelete(Path.Combine(cmakeFolder, "CMakeCache.txt")); cmakeArgs += $" -DPX_STATIC_LIBRARIES=1 -DPX_OUTPUT_DLL_DIR=\"{Path.Combine(buildFolder, "bin")}\" -DPX_OUTPUT_LIB_DIR=\"{Path.Combine(buildFolder, "lib")}\" -DPX_OUTPUT_EXE_DIR=\"{Path.Combine(buildFolder, "bin")}\""; RunCmake(cmakeFolder, platform, architecture, " -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF " + cmakeArgs, envVars); - + // Run build Utilities.Run("cmake", "--build . --config Release", null, cmakeFolder, Utilities.RunOptions.ThrowExceptionOnError, envVars); - + // Deploy binaries var libs = new[] { diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs index d0390eff7..6b82089d2 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/OpenAL.cs @@ -172,7 +172,7 @@ namespace Flax.Deps.Dependencies var buildDir = Path.Combine(root, "build"); // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DLIBTYPE=STATIC -DCMAKE_BUILD_TYPE=Release " + config); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs index 382c5707c..651c849d8 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/curl.cs @@ -95,7 +95,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Linux: { // Build for Linux - var settings = new [] + var settings = new[] { "--without-librtmp", "--without-ssl", @@ -126,7 +126,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - var settings = new [] + var settings = new[] { "--with-secure-transport", "--without-librtmp", @@ -137,7 +137,7 @@ namespace Flax.Deps.Dependencies "--enable-static", "-disable-ldap --disable-sspi --disable-ftp --disable-file --disable-dict --disable-telnet --disable-tftp --disable-rtsp --disable-pop3 --disable-imap --disable-smtp --disable-gopher --disable-smb", }; - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { var arch = GetAppleArchName(architecture); var archName = arch + "-apple-darwin19"; @@ -146,7 +146,7 @@ namespace Flax.Deps.Dependencies var compilerFlags = string.Format("-mmacosx-version-min={0} -arch {1}", Configuration.MacOSXMinVer, arch); var envVars = new Dictionary { - { "CC", "clang" }, + { "CC", "clang" }, { "CXX", "clang" }, { "CFLAGS", compilerFlags }, { "CXXFLAGS", compilerFlags }, diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs index 4efe4ed43..a3b5cfaf0 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/freetype.cs @@ -247,7 +247,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release"); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs b/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs index 46863ec08..3e6eff1b9 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/glslang.cs @@ -130,7 +130,7 @@ namespace Flax.Deps.Dependencies }; // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { RunCmake(root, platform, architecture, cmakeArgs); Utilities.Run("cmake", string.Format("--build . --config {0} --target install", configuration), null, buildDir, Utilities.RunOptions.ConsoleLogOutput); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs b/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs index 1716ff08d..857dd333c 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/ogg.cs @@ -217,7 +217,7 @@ namespace Flax.Deps.Dependencies case TargetPlatform.Mac: { // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(buildDir, true); RunCmake(buildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release"); diff --git a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs index b6acc4bf9..e36484f13 100644 --- a/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs +++ b/Source/Tools/Flax.Build/Deps/Dependencies/vorbis.cs @@ -376,7 +376,7 @@ namespace Flax.Deps.Dependencies GitCheckout(oggRoot, "master", "4380566a44b8d5e85ad511c9c17eb04197863ec5"); // Build for Mac - foreach (var architecture in new []{ TargetArchitecture.x64, TargetArchitecture.ARM64 }) + foreach (var architecture in new[] { TargetArchitecture.x64, TargetArchitecture.ARM64 }) { SetupDirectory(oggBuildDir, true); RunCmake(oggBuildDir, platform, architecture, ".. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=\"../install\""); diff --git a/Source/Tools/Flax.Build/Deps/Downloader.cs b/Source/Tools/Flax.Build/Deps/Downloader.cs index dcba01780..19135d634 100644 --- a/Source/Tools/Flax.Build/Deps/Downloader.cs +++ b/Source/Tools/Flax.Build/Deps/Downloader.cs @@ -105,8 +105,7 @@ namespace Flax.Deps if (totalBytes.HasValue) progress.Update(totalBytesRead, totalBytes.Value); } - } - while (hasMoreToRead); + } while (hasMoreToRead); } } } diff --git a/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs b/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs index 5ad6e10d3..cce10f2eb 100644 --- a/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs +++ b/Source/Tools/Flax.Build/Deps/ProgressDisplay.cs @@ -96,7 +96,7 @@ namespace Flax.Deps Console.WriteLine(); return false; } - + return true; */ } diff --git a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs index a023d33b2..0fd5b6a41 100644 --- a/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs +++ b/Source/Tools/Flax.Build/Platforms/Android/AndroidSdk.cs @@ -18,7 +18,7 @@ namespace Flax.Build.Platforms public static readonly AndroidSdk Instance = new AndroidSdk(); /// - public override TargetPlatform[] Platforms => new [] + public override TargetPlatform[] Platforms => new[] { TargetPlatform.Windows, TargetPlatform.Linux, diff --git a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs index 764ac81c7..22e365862 100644 --- a/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs +++ b/Source/Tools/Flax.Build/Platforms/Mac/MacPlatform.cs @@ -90,15 +90,15 @@ namespace Flax.Build.Platforms /// Returns true if running an x64 binary an arm64 host machine. /// public unsafe static bool GetProcessIsTranslated() - { - int ret = 0; - ulong size = sizeof(int); - if (sysctlbyname("sysctl.proc_translated", &ret, &size, null, 0) == -1) - return false; - return ret != 0; - } + { + int ret = 0; + ulong size = sizeof(int); + if (sysctlbyname("sysctl.proc_translated", &ret, &size, null, 0) == -1) + return false; + return ret != 0; + } [DllImport("c")] - private static unsafe extern int sysctlbyname(string name, void* oldp, ulong* oldlenp, void* newp, ulong newlen); + private static unsafe extern int sysctlbyname(string name, void* oldp, ulong* oldlenp, void* newp, ulong newlen); } } diff --git a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs index 3438f8960..7bd5495b2 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudio/VisualStudioProjectGenerator.cs @@ -702,7 +702,7 @@ namespace Flax.Build.Projects.VisualStudio { // Build command for the build tool var buildToolPath = Path.ChangeExtension(typeof(Builder).Assembly.Location, null); - + var targetsFileContent = new StringBuilder(); targetsFileContent.AppendLine(""); targetsFileContent.AppendLine(" "); diff --git a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs index 15b236972..7f3b11e15 100644 --- a/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs +++ b/Source/Tools/Flax.Build/Projects/VisualStudioCode/VisualStudioCodeProjectGenerator.cs @@ -410,8 +410,7 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("stopAtEntry", false); json.AddField("externalConsole", true); break; - case TargetPlatform.Linux: - break; + case TargetPlatform.Linux: break; } } json.EndObject(); @@ -622,7 +621,7 @@ namespace Flax.Build.Projects.VisualStudioCode json.AddField("**/Output", true); json.AddField("**/*.flax", true); json.EndObject(); - + // Extension settings json.AddField("omnisharp.useModernNet", true); diff --git a/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs b/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs index 1d0166a72..ba00db071 100644 --- a/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs +++ b/Source/Tools/Flax.Build/Utilities/CppNameMangling.cs @@ -79,8 +79,7 @@ namespace Flax.Build } } break; - default: - throw new InvalidPlatformException(buildData.Platform.Target); + default: throw new InvalidPlatformException(buildData.Platform.Target); } var result = sb.ToString(); BindingsGenerator.PutStringBuilder(sb); diff --git a/Source/Tools/Flax.Build/Utilities/WinAPI.cs b/Source/Tools/Flax.Build/Utilities/WinAPI.cs index ae0c5a5fb..47939fc1f 100644 --- a/Source/Tools/Flax.Build/Utilities/WinAPI.cs +++ b/Source/Tools/Flax.Build/Utilities/WinAPI.cs @@ -130,7 +130,7 @@ namespace Flax.Build System = 0x00001000, Task = 0x00002000 } - + public enum Icon : uint { Warning = 0x00000030,