Merge branch 'FlaxEngine:master' into model-prefab-fix
This commit is contained in:
BIN
Content/Shaders/ColorGrading.flax
(Stored with Git LFS)
BIN
Content/Shaders/ColorGrading.flax
(Stored with Git LFS)
Binary file not shown.
@@ -16,6 +16,7 @@ namespace FlaxEngine.Tools
|
|||||||
private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab;
|
private bool ShowModel => Type == ModelType.Model || Type == ModelType.Prefab;
|
||||||
private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
|
private bool ShowSkinnedModel => Type == ModelType.SkinnedModel || Type == ModelType.Prefab;
|
||||||
private bool ShowAnimation => Type == ModelType.Animation || 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 ShowSmoothingNormalsAngle => ShowGeometry && CalculateNormals;
|
||||||
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
|
private bool ShowSmoothingTangentsAngle => ShowGeometry && CalculateTangents;
|
||||||
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
|
private bool ShowFramesRange => ShowAnimation && Duration == AnimationDuration.Custom;
|
||||||
|
|||||||
@@ -22,6 +22,22 @@ namespace FlaxEditor.Content
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract string TypeName { get; }
|
public abstract string TypeName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance is virtual Proxy not linked to any asset.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool IsVirtual { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether [is virtual proxy].
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public bool IsVirtualProxy()
|
||||||
|
{
|
||||||
|
return IsVirtual && CanExport == false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if this proxy supports the given asset type id at the given path.
|
/// Checks if this proxy supports the given asset type id at the given path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -5,11 +5,463 @@
|
|||||||
#include "WindowsPlatformTools.h"
|
#include "WindowsPlatformTools.h"
|
||||||
#include "Engine/Platform/FileSystem.h"
|
#include "Engine/Platform/FileSystem.h"
|
||||||
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
|
#include "Engine/Platform/Windows/WindowsPlatformSettings.h"
|
||||||
|
#include "Engine/Core/Math/Color32.h"
|
||||||
#include "Engine/Core/Config/GameSettings.h"
|
#include "Engine/Core/Config/GameSettings.h"
|
||||||
#include "Editor/Utilities/EditorUtilities.h"
|
#include "Editor/Utilities/EditorUtilities.h"
|
||||||
#include "Engine/Graphics/Textures/TextureData.h"
|
#include "Engine/Graphics/Textures/TextureData.h"
|
||||||
|
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||||
#include "Engine/Content/Content.h"
|
#include "Engine/Content/Content.h"
|
||||||
#include "Engine/Content/JsonAsset.h"
|
#include "Engine/Content/JsonAsset.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MS-DOS header found at the beginning in a PE format file.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// COFF header found in a PE format file.
|
||||||
|
/// </summary>
|
||||||
|
struct COFFHeader
|
||||||
|
{
|
||||||
|
uint16 machine;
|
||||||
|
uint16 numSections;
|
||||||
|
uint32 timeDateStamp;
|
||||||
|
uint32 ptrSymbolTable;
|
||||||
|
uint32 numSymbols;
|
||||||
|
uint16 sizeOptHeader;
|
||||||
|
uint16 characteristics;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains address and size of data areas in a PE image.
|
||||||
|
/// </summary>
|
||||||
|
struct PEDataDirectory
|
||||||
|
{
|
||||||
|
uint32 virtualAddress;
|
||||||
|
uint32 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional header in a 32-bit PE format file.
|
||||||
|
/// </summary>
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional header in a 64-bit PE format file.
|
||||||
|
/// </summary>
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A section header in a PE format file.
|
||||||
|
/// </summary>
|
||||||
|
struct PESectionHeader
|
||||||
|
{
|
||||||
|
char name[8];
|
||||||
|
uint32 virtualSize;
|
||||||
|
uint32 relativeVirtualAddress;
|
||||||
|
uint32 physicalSize;
|
||||||
|
uint32 physicalAddress;
|
||||||
|
uint8 deprecated[12];
|
||||||
|
uint32 flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A resource table header within a .rsrc section in a PE format file.
|
||||||
|
/// </summary>
|
||||||
|
struct PEImageResourceDirectory
|
||||||
|
{
|
||||||
|
uint32 flags;
|
||||||
|
uint32 timeDateStamp;
|
||||||
|
uint16 majorVersion;
|
||||||
|
uint16 minorVersion;
|
||||||
|
uint16 numNamedEntries;
|
||||||
|
uint16 numIdEntries;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single entry in a resource table within a .rsrc section in a PE format file.
|
||||||
|
/// </summary>
|
||||||
|
struct PEImageResourceEntry
|
||||||
|
{
|
||||||
|
uint32 type;
|
||||||
|
uint32 offsetDirectory : 31;
|
||||||
|
uint32 isDirectory : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file.
|
||||||
|
/// </summary>
|
||||||
|
struct PEImageResourceEntryData
|
||||||
|
{
|
||||||
|
uint32 offsetData;
|
||||||
|
uint32 size;
|
||||||
|
uint32 codePage;
|
||||||
|
uint32 resourceHandle;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Header used in icon file format.
|
||||||
|
/// </summary>
|
||||||
|
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<PESectionHeader> 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<uint8> 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);
|
IMPLEMENT_ENGINE_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform);
|
||||||
|
|
||||||
@@ -50,7 +502,7 @@ bool WindowsPlatformTools::OnDeployBinaries(CookingData& data)
|
|||||||
TextureData iconData;
|
TextureData iconData;
|
||||||
if (!EditorUtilities::GetApplicationImage(platformSettings->OverrideIcon, 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."));
|
data.Error(TEXT("Failed to change output executable file icon."));
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using FlaxEditor.CustomEditors;
|
using FlaxEditor.CustomEditors;
|
||||||
|
using FlaxEditor.GUI.Docking;
|
||||||
using FlaxEditor.Windows;
|
using FlaxEditor.Windows;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
using DockState = FlaxEditor.GUI.Docking.DockState;
|
|
||||||
|
|
||||||
namespace FlaxEditor
|
namespace FlaxEditor
|
||||||
{
|
{
|
||||||
@@ -97,9 +97,12 @@ namespace FlaxEditor
|
|||||||
/// Shows the window.
|
/// Shows the window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Initial window state.</param>
|
/// <param name="state">Initial window state.</param>
|
||||||
public void Show(DockState state = DockState.Float)
|
/// <param name="toDock">The panel to dock to, if any.</param>
|
||||||
|
/// <param name="autoSelect">Only used if <paramref name="toDock"/> is set. If true the window will be selected after docking it.</param>
|
||||||
|
/// <param name="splitterValue">The splitter value to use if toDock is not null. If not specified, a default value will be used.</param>
|
||||||
|
public void Show(DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null)
|
||||||
{
|
{
|
||||||
_win.Show(state);
|
_win.Show(state, toDock, autoSelect, splitterValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
96
Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs
Normal file
96
Source/Editor/CustomEditors/Editors/BindableButtonEditor.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using FlaxEditor.GUI;
|
||||||
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for custom button editors.
|
||||||
|
/// See <seealso cref="MouseButtonEditor"/>, <seealso cref="KeyboardKeysEditor"/> and <seealso cref="GamepadButtonEditor"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class BindableButtonEditor : EnumEditor
|
||||||
|
{
|
||||||
|
private bool _isListeningForInput;
|
||||||
|
private Button _button;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Where or not we are currently listening for any input.
|
||||||
|
/// </summary>
|
||||||
|
protected bool IsListeningForInput
|
||||||
|
{
|
||||||
|
get => _isListeningForInput;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isListeningForInput = value;
|
||||||
|
if (_isListeningForInput)
|
||||||
|
SetupButton();
|
||||||
|
else
|
||||||
|
ResetButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The window this editor is attached to.
|
||||||
|
/// Useful to hook into for key pressed, mouse buttons etc.
|
||||||
|
/// </summary>
|
||||||
|
protected Window Window { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
|
{
|
||||||
|
Window = layout.Control.RootWindow.Window;
|
||||||
|
|
||||||
|
var panel = layout.CustomContainer<UniformGridPanel>();
|
||||||
|
panel.CustomControl.SlotsHorizontally = 2;
|
||||||
|
panel.CustomControl.SlotsVertically = 1;
|
||||||
|
|
||||||
|
var button = panel.Button("Listen", "Press to listen for input events");
|
||||||
|
_button = button.Button;
|
||||||
|
_button.Clicked += OnButtonClicked;
|
||||||
|
ResetButton();
|
||||||
|
|
||||||
|
var padding = panel.CustomControl.SlotPadding;
|
||||||
|
panel.CustomControl.Height = ComboBox.DefaultHeight + padding.Height;
|
||||||
|
|
||||||
|
base.Initialize(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Deinitialize()
|
||||||
|
{
|
||||||
|
_button.Clicked -= OnButtonClicked;
|
||||||
|
base.Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetButton()
|
||||||
|
{
|
||||||
|
_button.Text = "Listen";
|
||||||
|
_button.BorderThickness = 1;
|
||||||
|
|
||||||
|
var style = FlaxEngine.GUI.Style.Current;
|
||||||
|
_button.BorderColor = style.BorderNormal;
|
||||||
|
_button.BorderColorHighlighted = style.BorderHighlighted;
|
||||||
|
_button.BorderColorSelected = style.BorderSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupButton()
|
||||||
|
{
|
||||||
|
_button.Text = "Listening...";
|
||||||
|
_button.BorderThickness = 2;
|
||||||
|
|
||||||
|
var color = FlaxEngine.GUI.Style.Current.ProgressNormal;
|
||||||
|
_button.BorderColor = color;
|
||||||
|
_button.BorderColorHighlighted = color;
|
||||||
|
_button.BorderColorSelected = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnButtonClicked()
|
||||||
|
{
|
||||||
|
_isListeningForInput = !_isListeningForInput;
|
||||||
|
if (_isListeningForInput)
|
||||||
|
SetupButton();
|
||||||
|
else
|
||||||
|
ResetButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FlaxEditor.Content;
|
|
||||||
using FlaxEditor.CustomEditors.Elements;
|
|
||||||
using FlaxEditor.CustomEditors.GUI;
|
using FlaxEditor.CustomEditors.GUI;
|
||||||
|
using FlaxEditor.GUI.Input;
|
||||||
|
using FlaxEditor.Content;
|
||||||
using FlaxEditor.GUI.ContextMenu;
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
using FlaxEditor.GUI.Drag;
|
using FlaxEditor.GUI.Drag;
|
||||||
using FlaxEditor.SceneGraph;
|
using FlaxEditor.SceneGraph;
|
||||||
@@ -54,8 +54,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
|
|
||||||
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
|
||||||
{
|
{
|
||||||
menu.AddSeparator();
|
menu.ItemsContainer.RemoveChildren();
|
||||||
|
|
||||||
|
menu.AddButton("Copy", linkedEditor.Copy);
|
||||||
|
var paste = menu.AddButton("Paste", linkedEditor.Paste);
|
||||||
|
paste.Enabled = linkedEditor.CanPaste;
|
||||||
|
|
||||||
|
menu.AddSeparator();
|
||||||
var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked);
|
var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked);
|
||||||
moveUpButton.Enabled = Index > 0;
|
moveUpButton.Enabled = Index > 0;
|
||||||
|
|
||||||
@@ -65,17 +70,100 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
menu.AddButton("Remove", OnRemoveClicked);
|
menu.AddButton("Remove", OnRemoveClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMoveUpClicked(ContextMenuButton button)
|
private void OnMoveUpClicked()
|
||||||
{
|
{
|
||||||
Editor.Move(Index, Index - 1);
|
Editor.Move(Index, Index - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMoveDownClicked(ContextMenuButton button)
|
private void OnMoveDownClicked()
|
||||||
{
|
{
|
||||||
Editor.Move(Index, Index + 1);
|
Editor.Move(Index, Index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRemoveClicked(ContextMenuButton button)
|
private void OnRemoveClicked()
|
||||||
|
{
|
||||||
|
Editor.Remove(Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CollectionDropPanel : DropPanel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The collection editor.
|
||||||
|
/// </summary>
|
||||||
|
public CollectionEditor Editor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The index of the item (zero-based).
|
||||||
|
/// </summary>
|
||||||
|
public int Index { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The linked editor.
|
||||||
|
/// </summary>
|
||||||
|
public CustomEditor LinkedEditor;
|
||||||
|
|
||||||
|
private bool _canReorder = true;
|
||||||
|
|
||||||
|
public void Setup(CollectionEditor editor, int index, bool canReorder = true)
|
||||||
|
{
|
||||||
|
HeaderHeight = 18;
|
||||||
|
_canReorder = canReorder;
|
||||||
|
EnableDropDownIcon = true;
|
||||||
|
var icons = FlaxEditor.Editor.Instance.Icons;
|
||||||
|
ArrowImageClosed = new SpriteBrush(icons.ArrowRight12);
|
||||||
|
ArrowImageOpened = new SpriteBrush(icons.ArrowDown12);
|
||||||
|
HeaderText = $"Element {index}";
|
||||||
|
IsClosed = false;
|
||||||
|
Editor = editor;
|
||||||
|
Index = index;
|
||||||
|
Offsets = new Margin(7, 7, 0, 0);
|
||||||
|
|
||||||
|
MouseButtonRightClicked += OnMouseButtonRightClicked;
|
||||||
|
if (_canReorder)
|
||||||
|
{
|
||||||
|
// TODO: Drag drop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMouseButtonRightClicked(DropPanel panel, Float2 location)
|
||||||
|
{
|
||||||
|
if (LinkedEditor == null)
|
||||||
|
return;
|
||||||
|
var linkedEditor = LinkedEditor;
|
||||||
|
var menu = new ContextMenu();
|
||||||
|
|
||||||
|
menu.AddButton("Copy", linkedEditor.Copy);
|
||||||
|
var paste = menu.AddButton("Paste", linkedEditor.Paste);
|
||||||
|
paste.Enabled = linkedEditor.CanPaste;
|
||||||
|
|
||||||
|
if (_canReorder)
|
||||||
|
{
|
||||||
|
menu.AddSeparator();
|
||||||
|
|
||||||
|
var moveUpButton = menu.AddButton("Move up", OnMoveUpClicked);
|
||||||
|
moveUpButton.Enabled = Index > 0;
|
||||||
|
|
||||||
|
var moveDownButton = menu.AddButton("Move down", OnMoveDownClicked);
|
||||||
|
moveDownButton.Enabled = Index + 1 < Editor.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.AddButton("Remove", OnRemoveClicked);
|
||||||
|
|
||||||
|
menu.Show(panel, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMoveUpClicked()
|
||||||
|
{
|
||||||
|
Editor.Move(Index, Index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMoveDownClicked()
|
||||||
|
{
|
||||||
|
Editor.Move(Index, Index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRemoveClicked()
|
||||||
{
|
{
|
||||||
Editor.Remove(Index);
|
Editor.Remove(Index);
|
||||||
}
|
}
|
||||||
@@ -86,12 +174,12 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool NotNullItems;
|
protected bool NotNullItems;
|
||||||
|
|
||||||
private IntegerValueElement _size;
|
private IntValueBox _sizeBox;
|
||||||
private PropertyNameLabel _sizeLabel;
|
|
||||||
private Color _background;
|
private Color _background;
|
||||||
private int _elementsCount;
|
private int _elementsCount;
|
||||||
private bool _readOnly;
|
private bool _readOnly;
|
||||||
private bool _canReorderItems;
|
private bool _canReorderItems;
|
||||||
|
private CollectionAttribute.DisplayType _displayType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the length of the collection.
|
/// Gets the length of the collection.
|
||||||
@@ -124,12 +212,13 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_readOnly = false;
|
_readOnly = false;
|
||||||
_canReorderItems = true;
|
_canReorderItems = true;
|
||||||
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
|
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
|
||||||
|
_displayType = CollectionAttribute.DisplayType.Header;
|
||||||
NotNullItems = false;
|
NotNullItems = false;
|
||||||
|
|
||||||
// Try get CollectionAttribute for collection editor meta
|
// Try get CollectionAttribute for collection editor meta
|
||||||
var attributes = Values.GetAttributes();
|
var attributes = Values.GetAttributes();
|
||||||
Type overrideEditorType = null;
|
Type overrideEditorType = null;
|
||||||
float spacing = 10.0f;
|
float spacing = 1.0f;
|
||||||
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
|
var collection = (CollectionAttribute)attributes?.FirstOrDefault(x => x is CollectionAttribute);
|
||||||
if (collection != null)
|
if (collection != null)
|
||||||
{
|
{
|
||||||
@@ -140,6 +229,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_background = collection.BackgroundColor.Value;
|
_background = collection.BackgroundColor.Value;
|
||||||
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
|
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
|
||||||
spacing = collection.Spacing;
|
spacing = collection.Spacing;
|
||||||
|
_displayType = collection.Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dragArea = layout.CustomContainer<DragAreaControl>();
|
var dragArea = layout.CustomContainer<DragAreaControl>();
|
||||||
@@ -172,76 +262,77 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Size
|
// Size
|
||||||
if (_readOnly || (NotNullItems && size == 0))
|
if (layout.ContainerControl is DropPanel dropPanel)
|
||||||
{
|
{
|
||||||
dragArea.Label("Size", size.ToString());
|
var height = dropPanel.HeaderHeight - dropPanel.HeaderTextMargin.Height;
|
||||||
}
|
var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top;
|
||||||
else
|
_sizeBox = new IntValueBox(size)
|
||||||
{
|
{
|
||||||
var sizeProperty = dragArea.AddPropertyItem("Size");
|
MinValue = 0,
|
||||||
_sizeLabel = sizeProperty.Labels.Last();
|
MaxValue = ushort.MaxValue,
|
||||||
_size = sizeProperty.IntegerValue();
|
AnchorPreset = AnchorPresets.TopRight,
|
||||||
_size.IntValue.MinValue = 0;
|
Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height),
|
||||||
_size.IntValue.MaxValue = ushort.MaxValue;
|
Parent = dropPanel,
|
||||||
_size.IntValue.Value = size;
|
};
|
||||||
_size.IntValue.EditEnd += OnSizeChanged;
|
|
||||||
|
var label = new Label
|
||||||
|
{
|
||||||
|
Text = "Size",
|
||||||
|
AnchorPreset = AnchorPresets.TopRight,
|
||||||
|
Bounds = new Rectangle(-_sizeBox.Width - 40 - dropPanel.ItemsMargin.Right - 2, y, 40, height),
|
||||||
|
Parent = dropPanel
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_readOnly || (NotNullItems && size == 0))
|
||||||
|
{
|
||||||
|
_sizeBox.IsReadOnly = true;
|
||||||
|
_sizeBox.Enabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_sizeBox.EditEnd += OnSizeChanged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
{
|
{
|
||||||
var panel = dragArea.VerticalPanel();
|
var panel = dragArea.VerticalPanel();
|
||||||
|
panel.Panel.Offsets = new Margin(7, 7, 0, 0);
|
||||||
panel.Panel.BackgroundColor = _background;
|
panel.Panel.BackgroundColor = _background;
|
||||||
var elementType = ElementType;
|
var elementType = ElementType;
|
||||||
|
bool single = elementType.IsPrimitive ||
|
||||||
|
elementType.Equals(new ScriptType(typeof(string))) ||
|
||||||
|
elementType.IsEnum ||
|
||||||
|
(elementType.GetFields().Length == 1 && elementType.GetProperties().Length == 0) ||
|
||||||
|
(elementType.GetProperties().Length == 1 && elementType.GetFields().Length == 0) ||
|
||||||
|
elementType.Equals(new ScriptType(typeof(JsonAsset))) ||
|
||||||
|
elementType.Equals(new ScriptType(typeof(SettingsBase)));
|
||||||
|
|
||||||
// Use separate layout cells for each collection items to improve layout updates for them in separation
|
for (int i = 0; i < size; i++)
|
||||||
var useSharedLayout = elementType.IsPrimitive || elementType.IsEnum;
|
|
||||||
|
|
||||||
if (_canReorderItems)
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < size; i++)
|
// Apply spacing
|
||||||
{
|
if (i > 0 && i < size && spacing > 0 && !single)
|
||||||
if (i != 0 && spacing > 0f)
|
panel.Space(spacing);
|
||||||
{
|
|
||||||
if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
|
||||||
{
|
|
||||||
if (propertiesListElement.Labels.Count > 0)
|
|
||||||
{
|
|
||||||
var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1];
|
|
||||||
var margin = label.Margin;
|
|
||||||
margin.Bottom += spacing;
|
|
||||||
label.Margin = margin;
|
|
||||||
}
|
|
||||||
propertiesListElement.Space(spacing);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
panel.Space(spacing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
|
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
|
||||||
var property = panel.AddPropertyItem(new CollectionItemLabel(this, i));
|
if (_displayType == CollectionAttribute.DisplayType.Inline || (collection == null && single) || (_displayType == CollectionAttribute.DisplayType.Default && single))
|
||||||
var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel();
|
{
|
||||||
itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
|
PropertyNameLabel itemLabel;
|
||||||
|
if (_canReorderItems)
|
||||||
|
itemLabel = new CollectionItemLabel(this, i);
|
||||||
|
else
|
||||||
|
itemLabel = new PropertyNameLabel("Element " + i);
|
||||||
|
var property = panel.AddPropertyItem(itemLabel);
|
||||||
|
var itemLayout = (LayoutElementsContainer)property;
|
||||||
|
itemLabel.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
|
||||||
}
|
}
|
||||||
}
|
else if (_displayType == CollectionAttribute.DisplayType.Header || (_displayType == CollectionAttribute.DisplayType.Default && !single))
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < size; i++)
|
|
||||||
{
|
{
|
||||||
if (i != 0 && spacing > 0f)
|
var cdp = panel.CustomContainer<CollectionDropPanel>();
|
||||||
{
|
cdp.CustomControl.Setup(this, i, _canReorderItems);
|
||||||
if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
var itemLayout = cdp.VerticalPanel();
|
||||||
propertiesListElement.Space(spacing);
|
cdp.CustomControl.LinkedEditor = itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
|
||||||
else
|
|
||||||
panel.Space(spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null;
|
|
||||||
var property = panel.AddPropertyItem("Element " + i);
|
|
||||||
var itemLayout = useSharedLayout ? (LayoutElementsContainer)property : property.VerticalPanel();
|
|
||||||
itemLayout.Object(new ListValueContainer(elementType, i, Values, attributes), overrideEditor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,8 +374,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Deinitialize()
|
protected override void Deinitialize()
|
||||||
{
|
{
|
||||||
_size = null;
|
_sizeBox = null;
|
||||||
_sizeLabel = null;
|
|
||||||
|
|
||||||
base.Deinitialize();
|
base.Deinitialize();
|
||||||
}
|
}
|
||||||
@@ -311,7 +401,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
{
|
{
|
||||||
if (IsSetBlocked)
|
if (IsSetBlocked)
|
||||||
return;
|
return;
|
||||||
Resize(_size.IntValue.Value);
|
|
||||||
|
Resize(_sizeBox.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -384,14 +475,14 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Update reference/default value indicator
|
// Update reference/default value indicator
|
||||||
if (_sizeLabel != null)
|
if (_sizeBox != null)
|
||||||
{
|
{
|
||||||
var color = Color.Transparent;
|
var color = Color.Transparent;
|
||||||
if (Values.HasReferenceValue && Values.ReferenceValue is IList referenceValue && referenceValue.Count != Count)
|
if (Values.HasReferenceValue && Values.ReferenceValue is IList referenceValue && referenceValue.Count != Count)
|
||||||
color = FlaxEngine.GUI.Style.Current.BackgroundSelected;
|
color = FlaxEngine.GUI.Style.Current.BackgroundSelected;
|
||||||
else if (Values.HasDefaultValue && Values.DefaultValue is IList defaultValue && defaultValue.Count != Count)
|
else if (Values.HasDefaultValue && Values.DefaultValue is IList defaultValue && defaultValue.Count != Count)
|
||||||
color = Color.Yellow * 0.8f;
|
color = Color.Yellow * 0.8f;
|
||||||
_sizeLabel.HighlightStripColor = color;
|
_sizeBox.BorderColor = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if collection has been resized (by UI or from external source)
|
// Check if collection has been resized (by UI or from external source)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using FlaxEditor.CustomEditors.Elements;
|
|||||||
using FlaxEditor.CustomEditors.GUI;
|
using FlaxEditor.CustomEditors.GUI;
|
||||||
using FlaxEditor.GUI;
|
using FlaxEditor.GUI;
|
||||||
using FlaxEditor.GUI.ContextMenu;
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
|
using FlaxEditor.GUI.Input;
|
||||||
using FlaxEditor.Scripting;
|
using FlaxEditor.Scripting;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.GUI;
|
using FlaxEngine.GUI;
|
||||||
@@ -149,13 +150,14 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntegerValueElement _size;
|
private IntValueBox _sizeBox;
|
||||||
private Color _background;
|
private Color _background;
|
||||||
private int _elementsCount;
|
private int _elementsCount;
|
||||||
private bool _readOnly;
|
private bool _readOnly;
|
||||||
private bool _notNullItems;
|
private bool _notNullItems;
|
||||||
private bool _canEditKeys;
|
private bool _canEditKeys;
|
||||||
private bool _keyEdited;
|
private bool _keyEdited;
|
||||||
|
private CollectionAttribute.DisplayType _displayType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the length of the collection.
|
/// Gets the length of the collection.
|
||||||
@@ -178,6 +180,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
|
_background = FlaxEngine.GUI.Style.Current.CollectionBackgroundColor;
|
||||||
_readOnly = false;
|
_readOnly = false;
|
||||||
_notNullItems = false;
|
_notNullItems = false;
|
||||||
|
_displayType = CollectionAttribute.DisplayType.Header;
|
||||||
|
|
||||||
// Try get CollectionAttribute for collection editor meta
|
// Try get CollectionAttribute for collection editor meta
|
||||||
var attributes = Values.GetAttributes();
|
var attributes = Values.GetAttributes();
|
||||||
@@ -192,20 +195,40 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_background = collection.BackgroundColor.Value;
|
_background = collection.BackgroundColor.Value;
|
||||||
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
|
overrideEditorType = TypeUtils.GetType(collection.OverrideEditorTypeName).Type;
|
||||||
spacing = collection.Spacing;
|
spacing = collection.Spacing;
|
||||||
|
_displayType = collection.Display;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size
|
// Size
|
||||||
if (_readOnly || !_canEditKeys)
|
if (layout.ContainerControl is DropPanel dropPanel)
|
||||||
{
|
{
|
||||||
layout.Label("Size", size.ToString());
|
var height = dropPanel.HeaderHeight - dropPanel.HeaderTextMargin.Height;
|
||||||
}
|
var y = -dropPanel.HeaderHeight + dropPanel.HeaderTextMargin.Top;
|
||||||
else
|
_sizeBox = new IntValueBox(size)
|
||||||
{
|
{
|
||||||
_size = layout.IntegerValue("Size");
|
MinValue = 0,
|
||||||
_size.IntValue.MinValue = 0;
|
MaxValue = _notNullItems ? size : ushort.MaxValue,
|
||||||
_size.IntValue.MaxValue = _notNullItems ? size : ushort.MaxValue;
|
AnchorPreset = AnchorPresets.TopRight,
|
||||||
_size.IntValue.Value = size;
|
Bounds = new Rectangle(-40 - dropPanel.ItemsMargin.Right, y, 40, height),
|
||||||
_size.IntValue.EditEnd += OnSizeChanged;
|
Parent = dropPanel,
|
||||||
|
};
|
||||||
|
|
||||||
|
var label = new Label
|
||||||
|
{
|
||||||
|
Text = "Size",
|
||||||
|
AnchorPreset = AnchorPresets.TopRight,
|
||||||
|
Bounds = new Rectangle(-_sizeBox.Width - 40 - dropPanel.ItemsMargin.Right - 2, y, 40, height),
|
||||||
|
Parent = dropPanel
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_readOnly || !_canEditKeys)
|
||||||
|
{
|
||||||
|
_sizeBox.IsReadOnly = true;
|
||||||
|
_sizeBox.Enabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_sizeBox.EditEnd += OnSizeChanged;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Elements
|
// Elements
|
||||||
@@ -216,29 +239,23 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType<object>();
|
var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType<object>();
|
||||||
var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray();
|
var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray();
|
||||||
var valuesType = new ScriptType(valueType);
|
var valuesType = new ScriptType(valueType);
|
||||||
|
|
||||||
|
bool single = valuesType.IsPrimitive ||
|
||||||
|
valuesType.Equals(new ScriptType(typeof(string))) ||
|
||||||
|
valuesType.IsEnum ||
|
||||||
|
(valuesType.GetFields().Length == 1 && valuesType.GetProperties().Length == 0) ||
|
||||||
|
(valuesType.GetProperties().Length == 1 && valuesType.GetFields().Length == 0) ||
|
||||||
|
valuesType.Equals(new ScriptType(typeof(JsonAsset))) ||
|
||||||
|
valuesType.Equals(new ScriptType(typeof(SettingsBase)));
|
||||||
|
|
||||||
// Use separate layout cells for each collection items to improve layout updates for them in separation
|
// Use separate layout cells for each collection items to improve layout updates for them in separation
|
||||||
var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum;
|
var useSharedLayout = valueType.IsPrimitive || valueType.IsEnum;
|
||||||
|
|
||||||
for (int i = 0; i < size; i++)
|
for (int i = 0; i < size; i++)
|
||||||
{
|
{
|
||||||
if (i != 0 && spacing > 0f)
|
if (i > 0 && i < size && spacing > 0)
|
||||||
{
|
{
|
||||||
if (panel.Children.Count > 0 && panel.Children[panel.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
panel.Space(spacing);
|
||||||
{
|
|
||||||
if (propertiesListElement.Labels.Count > 0)
|
|
||||||
{
|
|
||||||
var label = propertiesListElement.Labels[propertiesListElement.Labels.Count - 1];
|
|
||||||
var margin = label.Margin;
|
|
||||||
margin.Bottom += spacing;
|
|
||||||
label.Margin = margin;
|
|
||||||
}
|
|
||||||
propertiesListElement.Space(spacing);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
panel.Space(spacing);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = keys.ElementAt(i);
|
var key = keys.ElementAt(i);
|
||||||
@@ -310,7 +327,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
if (IsSetBlocked)
|
if (IsSetBlocked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Resize(_size.IntValue.Value);
|
Resize(_sizeBox.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_element.Value = asFloat;
|
_element.Value = asFloat;
|
||||||
else if (value is double asDouble)
|
else if (value is double asDouble)
|
||||||
_element.Value = (float)asDouble;
|
_element.Value = (float)asDouble;
|
||||||
|
else if (value is int asInt)
|
||||||
|
_element.Value = (float)asInt;
|
||||||
else
|
else
|
||||||
throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "<null>"));
|
throw new Exception(string.Format("Invalid value type {0}.", value?.GetType().ToString() ?? "<null>"));
|
||||||
}
|
}
|
||||||
|
|||||||
51
Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs
Normal file
51
Source/Editor/CustomEditors/Editors/GamepadButtonEditor.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using FlaxEngine;
|
||||||
|
|
||||||
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Custom editor for <see cref="GamepadButton"/>.
|
||||||
|
/// Allows capturing gamepad buttons and assigning them
|
||||||
|
/// to the edited value.
|
||||||
|
/// </summary>
|
||||||
|
[CustomEditor(typeof(GamepadButton))]
|
||||||
|
public class GamepadButtonEditor : BindableButtonEditor
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
|
{
|
||||||
|
base.Initialize(layout);
|
||||||
|
FlaxEngine.Scripting.Update += OnUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Deinitialize()
|
||||||
|
{
|
||||||
|
FlaxEngine.Scripting.Update -= OnUpdate;
|
||||||
|
base.Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUpdate()
|
||||||
|
{
|
||||||
|
if (!IsListeningForInput)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Since there is no way to get an event about
|
||||||
|
// which gamepad pressed what button, we have
|
||||||
|
// to poll all gamepads and buttons manually.
|
||||||
|
for (var i = 0; i < Input.GamepadsCount; i++)
|
||||||
|
{
|
||||||
|
var pad = Input.Gamepads[i];
|
||||||
|
foreach (var btn in Enum.GetValues<GamepadButton>())
|
||||||
|
{
|
||||||
|
if (pad.GetButtonUp(btn))
|
||||||
|
{
|
||||||
|
IsListeningForInput = false;
|
||||||
|
SetValue(btn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,9 +66,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public HeaderAttribute Header;
|
public HeaderAttribute Header;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The visible if attribute.
|
/// The visible if attributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VisibleIfAttribute VisibleIf;
|
public VisibleIfAttribute[] VisibleIfs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The read-only attribute usage flag.
|
/// The read-only attribute usage flag.
|
||||||
@@ -128,7 +128,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
CustomEditorAlias = (CustomEditorAliasAttribute)attributes.FirstOrDefault(x => x is CustomEditorAliasAttribute);
|
CustomEditorAlias = (CustomEditorAliasAttribute)attributes.FirstOrDefault(x => x is CustomEditorAliasAttribute);
|
||||||
Space = (SpaceAttribute)attributes.FirstOrDefault(x => x is SpaceAttribute);
|
Space = (SpaceAttribute)attributes.FirstOrDefault(x => x is SpaceAttribute);
|
||||||
Header = (HeaderAttribute)attributes.FirstOrDefault(x => x is HeaderAttribute);
|
Header = (HeaderAttribute)attributes.FirstOrDefault(x => x is HeaderAttribute);
|
||||||
VisibleIf = (VisibleIfAttribute)attributes.FirstOrDefault(x => x is VisibleIfAttribute);
|
VisibleIfs = attributes.OfType<VisibleIfAttribute>().ToArray();
|
||||||
IsReadOnly = attributes.FirstOrDefault(x => x is ReadOnlyAttribute) != null;
|
IsReadOnly = attributes.FirstOrDefault(x => x is ReadOnlyAttribute) != null;
|
||||||
ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null;
|
ExpandGroups = attributes.FirstOrDefault(x => x is ExpandGroupsAttribute) != null;
|
||||||
|
|
||||||
@@ -210,17 +210,24 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
private struct VisibleIfCache
|
private struct VisibleIfCache
|
||||||
{
|
{
|
||||||
public ScriptMemberInfo Target;
|
public ScriptMemberInfo Target;
|
||||||
public ScriptMemberInfo Source;
|
public ScriptMemberInfo[] Sources;
|
||||||
public PropertiesListElement PropertiesList;
|
public PropertiesListElement PropertiesList;
|
||||||
public GroupElement Group;
|
public GroupElement Group;
|
||||||
public bool Invert;
|
public bool[] InversionList;
|
||||||
public int LabelIndex;
|
public int LabelIndex;
|
||||||
|
|
||||||
public bool GetValue(object instance)
|
public bool GetValue(object instance)
|
||||||
{
|
{
|
||||||
var value = (bool)Source.GetValue(instance);
|
bool value = true;
|
||||||
if (Invert)
|
|
||||||
value = !value;
|
for (int i = 0; i < Sources.Length; i++)
|
||||||
|
{
|
||||||
|
bool currentValue = (bool)Sources[i].GetValue(instance);
|
||||||
|
if (InversionList[i])
|
||||||
|
currentValue = !currentValue;
|
||||||
|
|
||||||
|
value = value && currentValue;
|
||||||
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,40 +305,48 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ScriptMemberInfo GetVisibleIfSource(ScriptType type, VisibleIfAttribute visibleIf)
|
private static ScriptMemberInfo[] GetVisibleIfSources(ScriptType type, VisibleIfAttribute[] visibleIfs)
|
||||||
{
|
{
|
||||||
var property = type.GetProperty(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
ScriptMemberInfo[] members = Array.Empty<ScriptMemberInfo>();
|
||||||
if (property != ScriptMemberInfo.Null)
|
|
||||||
|
for (int i = 0; i < visibleIfs.Length; i++)
|
||||||
{
|
{
|
||||||
if (!property.HasGet)
|
var property = type.GetProperty(visibleIfs[i].MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||||
|
if (property != ScriptMemberInfo.Null)
|
||||||
{
|
{
|
||||||
Debug.LogError("Invalid VisibleIf rule. Property has missing getter " + visibleIf.MemberName);
|
if (!property.HasGet)
|
||||||
return ScriptMemberInfo.Null;
|
{
|
||||||
|
Debug.LogError("Invalid VisibleIf rule. Property has missing getter " + visibleIfs[i].MemberName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.ValueType.Type != typeof(bool))
|
||||||
|
{
|
||||||
|
Debug.LogError("Invalid VisibleIf rule. Property has to return bool type " + visibleIfs[i].MemberName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
members = members.Append(property).ToArray();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property.ValueType.Type != typeof(bool))
|
var field = type.GetField(visibleIfs[i].MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||||
|
if (field != ScriptMemberInfo.Null)
|
||||||
{
|
{
|
||||||
Debug.LogError("Invalid VisibleIf rule. Property has to return bool type " + visibleIf.MemberName);
|
if (field.ValueType.Type != typeof(bool))
|
||||||
return ScriptMemberInfo.Null;
|
{
|
||||||
|
Debug.LogError("Invalid VisibleIf rule. Field has to be bool type " + visibleIfs[i].MemberName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
members = members.Append(field).ToArray();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return property;
|
Debug.LogError("Invalid VisibleIf rule. Cannot find member " + visibleIfs[i].MemberName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var field = type.GetField(visibleIf.MemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
return members;
|
||||||
if (field != ScriptMemberInfo.Null)
|
|
||||||
{
|
|
||||||
if (field.ValueType.Type != typeof(bool))
|
|
||||||
{
|
|
||||||
Debug.LogError("Invalid VisibleIf rule. Field has to be bool type " + visibleIf.MemberName);
|
|
||||||
return ScriptMemberInfo.Null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.LogError("Invalid VisibleIf rule. Cannot find member " + visibleIf.MemberName);
|
|
||||||
return ScriptMemberInfo.Null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GroupPanelCheckIfCanRevert(LayoutElementsContainer layout, ref bool canRevertReference, ref bool canRevertDefault)
|
private static void GroupPanelCheckIfCanRevert(LayoutElementsContainer layout, ref bool canRevertReference, ref bool canRevertDefault)
|
||||||
@@ -575,7 +590,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
protected virtual void SpawnProperty(LayoutElementsContainer itemLayout, ValueContainer itemValues, ItemInfo item)
|
||||||
{
|
{
|
||||||
int labelIndex = 0;
|
int labelIndex = 0;
|
||||||
if ((item.IsReadOnly || item.VisibleIf != null) &&
|
if ((item.IsReadOnly || item.VisibleIfs.Length > 0) &&
|
||||||
itemLayout.Children.Count > 0 &&
|
itemLayout.Children.Count > 0 &&
|
||||||
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
itemLayout.Children[itemLayout.Children.Count - 1] is PropertiesListElement propertiesListElement)
|
||||||
{
|
{
|
||||||
@@ -616,7 +631,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.VisibleIf != null && itemLayout.Children.Count > 0)
|
if (item.VisibleIfs.Length > 0 && itemLayout.Children.Count > 0)
|
||||||
{
|
{
|
||||||
PropertiesListElement list = null;
|
PropertiesListElement list = null;
|
||||||
GroupElement group = null;
|
GroupElement group = null;
|
||||||
@@ -628,8 +643,8 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Get source member used to check rule
|
// Get source member used to check rule
|
||||||
var sourceMember = GetVisibleIfSource(item.Info.DeclaringType, item.VisibleIf);
|
var sourceMembers = GetVisibleIfSources(item.Info.DeclaringType, item.VisibleIfs);
|
||||||
if (sourceMember == ScriptType.Null)
|
if (sourceMembers.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Resize cache
|
// Resize cache
|
||||||
@@ -645,11 +660,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
_visibleIfCaches[count] = new VisibleIfCache
|
_visibleIfCaches[count] = new VisibleIfCache
|
||||||
{
|
{
|
||||||
Target = item.Info,
|
Target = item.Info,
|
||||||
Source = sourceMember,
|
Sources = sourceMembers,
|
||||||
PropertiesList = list,
|
PropertiesList = list,
|
||||||
Group = group,
|
Group = group,
|
||||||
LabelIndex = labelIndex,
|
LabelIndex = labelIndex,
|
||||||
Invert = item.VisibleIf.Invert,
|
InversionList = item.VisibleIfs.Select((x, i) => x.Invert).ToArray(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using FlaxEditor.CustomEditors.Elements;
|
using FlaxEditor.CustomEditors.Elements;
|
||||||
|
using FlaxEditor.GUI;
|
||||||
|
using FlaxEditor.Scripting;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.Utilities;
|
||||||
|
|
||||||
namespace FlaxEditor.CustomEditors.Editors
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
{
|
{
|
||||||
@@ -13,6 +17,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public sealed class GuidEditor : CustomEditor
|
public sealed class GuidEditor : CustomEditor
|
||||||
{
|
{
|
||||||
private TextBoxElement _element;
|
private TextBoxElement _element;
|
||||||
|
private AssetPicker _picker;
|
||||||
|
private bool _isReference;
|
||||||
|
private bool _isRefreshing;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override DisplayStyle Style => DisplayStyle.Inline;
|
public override DisplayStyle Style => DisplayStyle.Inline;
|
||||||
@@ -20,8 +27,55 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Initialize(LayoutElementsContainer layout)
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
{
|
{
|
||||||
_element = layout.TextBox();
|
var attributes = Values.GetAttributes();
|
||||||
_element.TextBox.EditEnd += OnEditEnd;
|
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
|
||||||
|
if (assetReference != null)
|
||||||
|
{
|
||||||
|
_picker = layout.Custom<AssetPicker>().CustomControl;
|
||||||
|
ScriptType assetType = new ScriptType();
|
||||||
|
|
||||||
|
float height = 48;
|
||||||
|
if (assetReference.UseSmallPicker)
|
||||||
|
height = 32;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(assetReference.TypeName))
|
||||||
|
{
|
||||||
|
assetType = ScriptType.Void;
|
||||||
|
}
|
||||||
|
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
|
||||||
|
{
|
||||||
|
// Generic file picker
|
||||||
|
assetType = ScriptType.Null;
|
||||||
|
_picker.Validator.FileExtension = assetReference.TypeName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var customType = TypeUtils.GetType(assetReference.TypeName);
|
||||||
|
if (customType != ScriptType.Null)
|
||||||
|
assetType = customType;
|
||||||
|
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
|
||||||
|
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
|
||||||
|
else
|
||||||
|
assetType = ScriptType.Void;
|
||||||
|
}
|
||||||
|
|
||||||
|
_picker.Validator.AssetType = assetType;
|
||||||
|
_picker.Height = height;
|
||||||
|
_picker.SelectedItemChanged += OnSelectedItemChanged;
|
||||||
|
_isReference = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_element = layout.TextBox();
|
||||||
|
_element.TextBox.EditEnd += OnEditEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectedItemChanged()
|
||||||
|
{
|
||||||
|
if (_isRefreshing)
|
||||||
|
return;
|
||||||
|
SetValue(_picker.Validator.SelectedID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEditEnd()
|
private void OnEditEnd()
|
||||||
@@ -36,17 +90,32 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public override void Refresh()
|
public override void Refresh()
|
||||||
{
|
{
|
||||||
base.Refresh();
|
base.Refresh();
|
||||||
|
_isRefreshing = true;
|
||||||
if (HasDifferentValues)
|
if (HasDifferentValues)
|
||||||
{
|
{
|
||||||
_element.TextBox.Text = string.Empty;
|
if (_isReference)
|
||||||
_element.TextBox.WatermarkText = "Different values";
|
{
|
||||||
|
// Not supported
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_element.TextBox.Text = string.Empty;
|
||||||
|
_element.TextBox.WatermarkText = "Different values";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_element.TextBox.Text = ((Guid)Values[0]).ToString("D");
|
if (_isReference)
|
||||||
_element.TextBox.WatermarkText = string.Empty;
|
{
|
||||||
|
_picker.Validator.SelectedID = (Guid)Values[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_element.TextBox.Text = ((Guid)Values[0]).ToString("D");
|
||||||
|
_element.TextBox.WatermarkText = string.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_isRefreshing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs
Normal file
36
Source/Editor/CustomEditors/Editors/KeyboardKeysEditor.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using FlaxEngine;
|
||||||
|
|
||||||
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Custom editor for <see cref="KeyboardKeys"/>.
|
||||||
|
/// Allows capturing key presses and assigning them
|
||||||
|
/// to the edited value.
|
||||||
|
/// </summary>
|
||||||
|
[CustomEditor(typeof(KeyboardKeys))]
|
||||||
|
public class KeyboardKeysEditor : BindableButtonEditor
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
|
{
|
||||||
|
base.Initialize(layout);
|
||||||
|
Window.KeyUp += OnKeyUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Deinitialize()
|
||||||
|
{
|
||||||
|
Window.KeyUp -= OnKeyUp;
|
||||||
|
base.Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKeyUp(KeyboardKeys key)
|
||||||
|
{
|
||||||
|
if (!IsListeningForInput)
|
||||||
|
return;
|
||||||
|
IsListeningForInput = false;
|
||||||
|
|
||||||
|
SetValue(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using FlaxEditor.CustomEditors.Elements;
|
|||||||
using FlaxEditor.CustomEditors.GUI;
|
using FlaxEditor.CustomEditors.GUI;
|
||||||
using FlaxEditor.Scripting;
|
using FlaxEditor.Scripting;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
using FlaxEngine.GUI;
|
||||||
|
|
||||||
namespace FlaxEditor.CustomEditors.Editors
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
{
|
{
|
||||||
@@ -13,7 +14,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
[CustomEditor(typeof(ModelInstanceEntry)), DefaultEditor]
|
[CustomEditor(typeof(ModelInstanceEntry)), DefaultEditor]
|
||||||
public sealed class ModelInstanceEntryEditor : GenericEditor
|
public sealed class ModelInstanceEntryEditor : GenericEditor
|
||||||
{
|
{
|
||||||
private GroupElement _group;
|
private DropPanel _mainPanel;
|
||||||
private bool _updateName;
|
private bool _updateName;
|
||||||
private int _entryIndex;
|
private int _entryIndex;
|
||||||
private bool _isRefreshing;
|
private bool _isRefreshing;
|
||||||
@@ -25,8 +26,11 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
public override void Initialize(LayoutElementsContainer layout)
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
{
|
{
|
||||||
_updateName = true;
|
_updateName = true;
|
||||||
var group = layout.Group("Entry");
|
if (layout.ContainerControl.Parent is DropPanel panel)
|
||||||
_group = group;
|
{
|
||||||
|
_mainPanel = panel;
|
||||||
|
_mainPanel.HeaderText = "Entry";
|
||||||
|
}
|
||||||
|
|
||||||
if (ParentEditor == null || HasDifferentTypes)
|
if (ParentEditor == null || HasDifferentTypes)
|
||||||
return;
|
return;
|
||||||
@@ -60,19 +64,19 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
|
var materialValue = new CustomValueContainer(new ScriptType(typeof(MaterialBase)), _material, (instance, index) => _material, (instance, index, value) => _material = value as MaterialBase);
|
||||||
for (var i = 1; i < parentEditorValues.Count; i++)
|
for (var i = 1; i < parentEditorValues.Count; i++)
|
||||||
materialValue.Add(_material);
|
materialValue.Add(_material);
|
||||||
var materialEditor = (AssetRefEditor)_group.Property(materialLabel, materialValue);
|
var materialEditor = (AssetRefEditor)layout.Property(materialLabel, materialValue);
|
||||||
materialEditor.Values.SetDefaultValue(defaultValue);
|
materialEditor.Values.SetDefaultValue(defaultValue);
|
||||||
materialEditor.RefreshDefaultValue();
|
materialEditor.RefreshDefaultValue();
|
||||||
materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged;
|
materialEditor.Picker.SelectedItemChanged += OnSelectedMaterialChanged;
|
||||||
_materialEditor = materialEditor;
|
_materialEditor = materialEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Initialize(group);
|
base.Initialize(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSelectedMaterialChanged()
|
private void OnSelectedMaterialChanged()
|
||||||
{
|
{
|
||||||
if (_isRefreshing)
|
if (_isRefreshing || _modelInstance == null)
|
||||||
return;
|
return;
|
||||||
_isRefreshing = true;
|
_isRefreshing = true;
|
||||||
var slots = _modelInstance.MaterialSlots;
|
var slots = _modelInstance.MaterialSlots;
|
||||||
@@ -120,7 +124,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
{
|
{
|
||||||
// Update panel title to match material slot name
|
// Update panel title to match material slot name
|
||||||
if (_updateName &&
|
if (_updateName &&
|
||||||
_group != null &&
|
_mainPanel != null &&
|
||||||
ParentEditor?.ParentEditor != null &&
|
ParentEditor?.ParentEditor != null &&
|
||||||
ParentEditor.ParentEditor.Values.Count > 0)
|
ParentEditor.ParentEditor.Values.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -131,7 +135,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
|||||||
if (slots != null && slots.Length > entryIndex)
|
if (slots != null && slots.Length > entryIndex)
|
||||||
{
|
{
|
||||||
_updateName = false;
|
_updateName = false;
|
||||||
_group.Panel.HeaderText = "Entry " + slots[entryIndex].Name;
|
_mainPanel.HeaderText = "Entry " + slots[entryIndex].Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs
Normal file
37
Source/Editor/CustomEditors/Editors/MouseButtonEditor.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using FlaxEngine;
|
||||||
|
|
||||||
|
namespace FlaxEditor.CustomEditors.Editors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Custom editor for <see cref="MouseButton"/>.
|
||||||
|
/// Allows capturing mouse button presses and assigning them
|
||||||
|
/// to the edited value.
|
||||||
|
/// </summary>
|
||||||
|
[CustomEditor(typeof(MouseButton))]
|
||||||
|
public class MouseButtonEditor : BindableButtonEditor
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Initialize(LayoutElementsContainer layout)
|
||||||
|
{
|
||||||
|
base.Initialize(layout);
|
||||||
|
Window.MouseUp += OnMouseUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Deinitialize()
|
||||||
|
{
|
||||||
|
Window.MouseUp -= OnMouseUp;
|
||||||
|
base.Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMouseUp(ref Float2 mouse, MouseButton button, ref bool handled)
|
||||||
|
{
|
||||||
|
if (!IsListeningForInput)
|
||||||
|
return;
|
||||||
|
IsListeningForInput = false;
|
||||||
|
|
||||||
|
SetValue(button);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,6 +75,9 @@ namespace FlaxEditor.GUI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void OnSelectedItemChanged()
|
protected virtual void OnSelectedItemChanged()
|
||||||
{
|
{
|
||||||
|
if (IsDisposing)
|
||||||
|
return;
|
||||||
|
|
||||||
// Update tooltip
|
// Update tooltip
|
||||||
string tooltip;
|
string tooltip;
|
||||||
if (Validator.SelectedItem is AssetItem assetItem)
|
if (Validator.SelectedItem is AssetItem assetItem)
|
||||||
|
|||||||
@@ -428,6 +428,13 @@ namespace FlaxEditor.GUI
|
|||||||
// Show dropdown list
|
// Show dropdown list
|
||||||
_popupMenu.MinimumWidth = Width;
|
_popupMenu.MinimumWidth = Width;
|
||||||
_popupMenu.Show(this, new Float2(1, Height));
|
_popupMenu.Show(this, new Float2(1, Height));
|
||||||
|
|
||||||
|
// Adjust menu position if it is not the down direction
|
||||||
|
if (_popupMenu.Direction == ContextMenuDirection.RightUp)
|
||||||
|
{
|
||||||
|
var position = _popupMenu.RootWindow.Window.Position;
|
||||||
|
_popupMenu.RootWindow.Window.Position = new Float2(position.X, position.Y - Height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -408,9 +408,9 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
{
|
{
|
||||||
foreach (var child in _panel.Children)
|
foreach (var child in _panel.Children)
|
||||||
{
|
{
|
||||||
if (child is ContextMenuChildMenu item && item.Visible)
|
if (child is ContextMenuButton item && item.Visible)
|
||||||
{
|
{
|
||||||
item.AdjustArrowAmount = -_panel.VScrollBar.Width;
|
item.ExtraAdjustmentAmount = -_panel.VScrollBar.Width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
public class ContextMenuButton : ContextMenuItem
|
public class ContextMenuButton : ContextMenuItem
|
||||||
{
|
{
|
||||||
private bool _isMouseDown;
|
private bool _isMouseDown;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount to adjust the short keys and arrow image by in x coordinates.
|
||||||
|
/// </summary>
|
||||||
|
public float ExtraAdjustmentAmount = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when user clicks on the button.
|
/// Event fired when user clicks on the button.
|
||||||
@@ -133,7 +138,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
if (!string.IsNullOrEmpty(ShortKeys))
|
if (!string.IsNullOrEmpty(ShortKeys))
|
||||||
{
|
{
|
||||||
// Draw short keys
|
// Draw short keys
|
||||||
Render2D.DrawText(style.FontMedium, ShortKeys, textRect, textColor, TextAlignment.Far, TextAlignment.Center);
|
Render2D.DrawText(style.FontMedium, ShortKeys, new Rectangle(textRect.X + ExtraAdjustmentAmount, textRect.Y, textRect.Width, textRect.Height), textColor, TextAlignment.Far, TextAlignment.Center);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw icon
|
// Draw icon
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ContextMenu ContextMenu = new ContextMenu();
|
public readonly ContextMenu ContextMenu = new ContextMenu();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The amount to adjust the arrow image by in x coordinates.
|
|
||||||
/// </summary>
|
|
||||||
public float AdjustArrowAmount = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ContextMenuChildMenu"/> class.
|
/// Initializes a new instance of the <see cref="ContextMenuChildMenu"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -49,7 +44,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
|||||||
|
|
||||||
// Draw arrow
|
// Draw arrow
|
||||||
if (ContextMenu.HasChildren)
|
if (ContextMenu.HasChildren)
|
||||||
Render2D.DrawSprite(style.ArrowRight, new Rectangle(Width - 15 + AdjustArrowAmount, (Height - 12) / 2, 12, 12), Enabled ? isCMopened ? style.BackgroundSelected : style.Foreground : style.ForegroundDisabled);
|
Render2D.DrawSprite(style.ArrowRight, new Rectangle(Width - 15 + ExtraAdjustmentAmount, (Height - 12) / 2, 12, 12), Enabled ? isCMopened ? style.BackgroundSelected : style.Foreground : style.ForegroundDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -518,9 +518,9 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual void DockWindowInternal(DockState state, DockWindow window)
|
internal virtual void DockWindowInternal(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
|
||||||
{
|
{
|
||||||
DockWindow(state, window);
|
DockWindow(state, window, autoSelect, splitterValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -528,7 +528,9 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
/// <param name="window">The window.</param>
|
/// <param name="window">The window.</param>
|
||||||
protected virtual void DockWindow(DockState state, DockWindow window)
|
/// <param name="autoSelect">Whether or not to automatically select the window after docking it.</param>
|
||||||
|
/// <param name="splitterValue">The splitter value to use when docking to window.</param>
|
||||||
|
protected virtual void DockWindow(DockState state, DockWindow window, bool autoSelect = true, float? splitterValue = null)
|
||||||
{
|
{
|
||||||
CreateTabsProxy();
|
CreateTabsProxy();
|
||||||
|
|
||||||
@@ -536,12 +538,12 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
if (state == DockState.DockFill)
|
if (state == DockState.DockFill)
|
||||||
{
|
{
|
||||||
// Add tab
|
// Add tab
|
||||||
AddTab(window);
|
AddTab(window, autoSelect);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create child panel
|
// Create child panel
|
||||||
var dockPanel = CreateChildPanel(state, DefaultSplitterValue);
|
var dockPanel = CreateChildPanel(state, splitterValue ?? DefaultSplitterValue);
|
||||||
|
|
||||||
// Dock window as a tab in a child panel
|
// Dock window as a tab in a child panel
|
||||||
dockPanel.DockWindow(DockState.DockFill, window);
|
dockPanel.DockWindow(DockState.DockFill, window);
|
||||||
|
|||||||
@@ -214,7 +214,9 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">Initial window state.</param>
|
/// <param name="state">Initial window state.</param>
|
||||||
/// <param name="toDock">Panel to dock to it.</param>
|
/// <param name="toDock">Panel to dock to it.</param>
|
||||||
public void Show(DockState state = DockState.Float, DockPanel toDock = null)
|
/// <param name="autoSelect">Only used if <paramref name="toDock"/> is set. If true the window will be selected after docking it.</param>
|
||||||
|
/// <param name="splitterValue">Only used if <paramref name="toDock"/> is set. The splitter value to use. If not specified, a default value will be used.</param>
|
||||||
|
public void Show(DockState state = DockState.Float, DockPanel toDock = null, bool autoSelect = true, float? splitterValue = null)
|
||||||
{
|
{
|
||||||
if (state == DockState.Hidden)
|
if (state == DockState.Hidden)
|
||||||
{
|
{
|
||||||
@@ -232,7 +234,7 @@ namespace FlaxEditor.GUI.Docking
|
|||||||
Undock();
|
Undock();
|
||||||
|
|
||||||
// Then dock
|
// Then dock
|
||||||
(toDock ?? _masterPanel).DockWindowInternal(state, this);
|
(toDock ?? _masterPanel).DockWindowInternal(state, this, autoSelect, splitterValue);
|
||||||
OnShow();
|
OnShow();
|
||||||
PerformLayout();
|
PerformLayout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ namespace FlaxEditor.GUI.Tabs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Tab SelectedTab
|
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;
|
set => SelectedTabIndex = value != null ? Children.IndexOf(value) - 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The key updates timeout in seconds.
|
/// The key updates timeout in seconds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static float KeyUpdateTimeout = 0.12f;
|
public static float KeyUpdateTimeout = 0.25f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for selected tree nodes collection change.
|
/// Delegate for selected tree nodes collection change.
|
||||||
@@ -113,7 +113,7 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
AutoFocus = false;
|
AutoFocus = false;
|
||||||
|
|
||||||
_supportMultiSelect = supportMultiSelect;
|
_supportMultiSelect = supportMultiSelect;
|
||||||
_keyUpdateTime = KeyUpdateTimeout * 10;
|
_keyUpdateTime = KeyUpdateTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnRightClickInternal(TreeNode node, ref Float2 location)
|
internal void OnRightClickInternal(TreeNode node, ref Float2 location)
|
||||||
@@ -347,10 +347,12 @@ namespace FlaxEditor.GUI.Tree
|
|||||||
if (ContainsFocus && node != null && node.AutoFocus)
|
if (ContainsFocus && node != null && node.AutoFocus)
|
||||||
{
|
{
|
||||||
var window = Root;
|
var window = Root;
|
||||||
|
if (window.GetKeyDown(KeyboardKeys.ArrowUp) || window.GetKeyDown(KeyboardKeys.ArrowDown))
|
||||||
|
_keyUpdateTime = KeyUpdateTimeout;
|
||||||
if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused)
|
if (_keyUpdateTime >= KeyUpdateTimeout && window is WindowRootControl windowRoot && windowRoot.Window.IsFocused)
|
||||||
{
|
{
|
||||||
bool keyUpArrow = window.GetKeyDown(KeyboardKeys.ArrowUp);
|
bool keyUpArrow = window.GetKey(KeyboardKeys.ArrowUp);
|
||||||
bool keyDownArrow = window.GetKeyDown(KeyboardKeys.ArrowDown);
|
bool keyDownArrow = window.GetKey(KeyboardKeys.ArrowDown);
|
||||||
|
|
||||||
// Check if arrow flags are different
|
// Check if arrow flags are different
|
||||||
if (keyDownArrow != keyUpArrow)
|
if (keyDownArrow != keyUpArrow)
|
||||||
|
|||||||
@@ -196,6 +196,25 @@ namespace FlaxEditor.Modules
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the virtual proxy object from given path.
|
||||||
|
/// <br></br>use case if the asset u trying to display is not a flax asset but u like to add custom functionality
|
||||||
|
/// <br></br>to context menu,or display it the asset
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The asset path.</param>
|
||||||
|
/// <returns>Asset proxy or null if cannot find.</returns>
|
||||||
|
public AssetProxy GetAssetVirtuallProxy(string path)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Proxy.Count; i++)
|
||||||
|
{
|
||||||
|
if (Proxy[i] is AssetProxy proxy && proxy.IsVirtualProxy() && path.EndsWith(proxy.FileExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes the given item folder. Tries to find new content items and remove not existing ones.
|
/// Refreshes the given item folder. Tries to find new content items and remove not existing ones.
|
||||||
@@ -996,7 +1015,14 @@ namespace FlaxEditor.Modules
|
|||||||
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
|
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
|
||||||
}
|
}
|
||||||
if (item == null)
|
if (item == null)
|
||||||
item = new FileItem(path);
|
{
|
||||||
|
var proxy = GetAssetVirtuallProxy(path);
|
||||||
|
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
item = new FileItem(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Link
|
// Link
|
||||||
item.ParentFolder = parent.Folder;
|
item.ParentFolder = parent.Folder;
|
||||||
|
|||||||
@@ -45,6 +45,21 @@ namespace FlaxEditor.Modules.SourceCodeEditing
|
|||||||
_checkAssembly = checkAssembly;
|
_checkAssembly = checkAssembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type matching the certain Script.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">The content item.</param>
|
||||||
|
/// <returns>The type matching that item, or null if not found.</returns>
|
||||||
|
public ScriptType Get(Content.ScriptItem script)
|
||||||
|
{
|
||||||
|
foreach (var type in Get())
|
||||||
|
{
|
||||||
|
if (type.ContentItem == script)
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
return ScriptType.Null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all the types from the all loaded assemblies (including project scripts and scripts from the plugins).
|
/// Gets all the types from the all loaded assemblies (including project scripts and scripts from the plugins).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -36,6 +36,22 @@ namespace FlaxEditor.Modules
|
|||||||
{
|
{
|
||||||
public string AssemblyName;
|
public string AssemblyName;
|
||||||
public string TypeName;
|
public string TypeName;
|
||||||
|
|
||||||
|
public DockState DockState;
|
||||||
|
public DockPanel DockedTo;
|
||||||
|
public float? SplitterValue = null;
|
||||||
|
|
||||||
|
public bool SelectOnShow = false;
|
||||||
|
|
||||||
|
public bool Maximize;
|
||||||
|
public bool Minimize;
|
||||||
|
public Float2 FloatSize;
|
||||||
|
public Float2 FloatPosition;
|
||||||
|
|
||||||
|
// Constructor, to allow for default values
|
||||||
|
public WindowRestoreData()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<WindowRestoreData> _restoreWindows = new List<WindowRestoreData>();
|
private readonly List<WindowRestoreData> _restoreWindows = new List<WindowRestoreData>();
|
||||||
@@ -709,9 +725,7 @@ namespace FlaxEditor.Modules
|
|||||||
for (int i = 0; i < Windows.Count; i++)
|
for (int i = 0; i < Windows.Count; i++)
|
||||||
{
|
{
|
||||||
if (string.Equals(Windows[i].SerializationTypename, typename, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(Windows[i].SerializationTypename, typename, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
|
||||||
return Windows[i];
|
return Windows[i];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's an asset ID
|
// Check if it's an asset ID
|
||||||
@@ -802,10 +816,38 @@ namespace FlaxEditor.Modules
|
|||||||
if (constructor == null || type.IsGenericType)
|
if (constructor == null || type.IsGenericType)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
WindowRestoreData winData;
|
var winData = new WindowRestoreData();
|
||||||
|
var panel = win.Window.ParentDockPanel;
|
||||||
|
|
||||||
|
// Ensure that this window is only selected following recompilation
|
||||||
|
// if it was the active tab in its dock panel. Otherwise, there is a
|
||||||
|
// risk of interrupting the user's workflow by potentially selecting
|
||||||
|
// background tabs.
|
||||||
|
winData.SelectOnShow = panel.SelectedTab == win.Window;
|
||||||
|
if (panel is FloatWindowDockPanel)
|
||||||
|
{
|
||||||
|
winData.DockState = DockState.Float;
|
||||||
|
var window = win.Window.RootWindow.Window;
|
||||||
|
winData.FloatPosition = window.Position;
|
||||||
|
winData.FloatSize = window.ClientSize;
|
||||||
|
winData.Maximize = window.IsMaximized;
|
||||||
|
winData.Minimize = window.IsMinimized;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (panel.TabsCount > 1)
|
||||||
|
{
|
||||||
|
winData.DockState = DockState.DockFill;
|
||||||
|
winData.DockedTo = panel;
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
winData.DockState = panel.TryGetDockState(out var splitterValue);
|
||||||
|
winData.DockedTo = panel.ParentDockPanel;
|
||||||
|
winData.SplitterValue = splitterValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
winData.AssemblyName = type.Assembly.GetName().Name;
|
winData.AssemblyName = type.Assembly.GetName().Name;
|
||||||
winData.TypeName = type.FullName;
|
winData.TypeName = type.FullName;
|
||||||
// TODO: cache and restore docking info
|
|
||||||
_restoreWindows.Add(winData);
|
_restoreWindows.Add(winData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,7 +866,24 @@ namespace FlaxEditor.Modules
|
|||||||
if (type != null)
|
if (type != null)
|
||||||
{
|
{
|
||||||
var win = (CustomEditorWindow)Activator.CreateInstance(type);
|
var win = (CustomEditorWindow)Activator.CreateInstance(type);
|
||||||
win.Show();
|
win.Show(winData.DockState, winData.DockedTo, winData.SelectOnShow, winData.SplitterValue);
|
||||||
|
if (winData.DockState == DockState.Float)
|
||||||
|
{
|
||||||
|
var window = win.Window.RootWindow.Window;
|
||||||
|
window.Position = winData.FloatPosition;
|
||||||
|
if (winData.Maximize)
|
||||||
|
{
|
||||||
|
window.Maximize();
|
||||||
|
}
|
||||||
|
else if (winData.Minimize)
|
||||||
|
{
|
||||||
|
window.Minimize();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.ClientSize = winData.FloatSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ namespace FlaxEditor.SceneGraph.GUI
|
|||||||
private DragScripts _dragScripts;
|
private DragScripts _dragScripts;
|
||||||
private DragAssets _dragAssets;
|
private DragAssets _dragAssets;
|
||||||
private DragActorType _dragActorType;
|
private DragActorType _dragActorType;
|
||||||
|
private DragScriptItems _dragScriptItems;
|
||||||
private DragHandlers _dragHandlers;
|
private DragHandlers _dragHandlers;
|
||||||
private List<Rectangle> _highlights;
|
private List<Rectangle> _highlights;
|
||||||
private bool _hasSearchFilter;
|
private bool _hasSearchFilter;
|
||||||
@@ -395,6 +396,13 @@ namespace FlaxEditor.SceneGraph.GUI
|
|||||||
}
|
}
|
||||||
if (_dragActorType.OnDragEnter(data))
|
if (_dragActorType.OnDragEnter(data))
|
||||||
return _dragActorType.Effect;
|
return _dragActorType.Effect;
|
||||||
|
if (_dragScriptItems == null)
|
||||||
|
{
|
||||||
|
_dragScriptItems = new DragScriptItems(ValidateDragScriptItem);
|
||||||
|
_dragHandlers.Add(_dragScriptItems);
|
||||||
|
}
|
||||||
|
if (_dragScriptItems.OnDragEnter(data))
|
||||||
|
return _dragScriptItems.Effect;
|
||||||
|
|
||||||
return DragDropEffect.None;
|
return DragDropEffect.None;
|
||||||
}
|
}
|
||||||
@@ -673,7 +681,34 @@ namespace FlaxEditor.SceneGraph.GUI
|
|||||||
actor.Transform = Actor.Transform;
|
actor.Transform = Actor.Transform;
|
||||||
ActorNode.Root.Spawn(actor, Actor);
|
ActorNode.Root.Spawn(actor, Actor);
|
||||||
}
|
}
|
||||||
|
result = DragDropEffect.Move;
|
||||||
|
}
|
||||||
|
// Drag script item
|
||||||
|
else if (_dragScriptItems != null && _dragScriptItems.HasValidDrag)
|
||||||
|
{
|
||||||
|
var spawnParent = myActor;
|
||||||
|
if (DragOverMode == DragItemPositioning.Above || DragOverMode == DragItemPositioning.Below)
|
||||||
|
spawnParent = newParent;
|
||||||
|
|
||||||
|
for (int i = 0; i < _dragScriptItems.Objects.Count; i++)
|
||||||
|
{
|
||||||
|
var item = _dragScriptItems.Objects[i];
|
||||||
|
var actorType = Editor.Instance.CodeEditing.Actors.Get(item);
|
||||||
|
if (actorType != ScriptType.Null)
|
||||||
|
{
|
||||||
|
var actor = actorType.CreateInstance() as Actor;
|
||||||
|
if (actor == null)
|
||||||
|
{
|
||||||
|
Editor.LogWarning("Failed to spawn actor of type " + actorType.TypeName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
actor.StaticFlags = spawnParent.StaticFlags;
|
||||||
|
actor.Name = actorType.Name;
|
||||||
|
actor.Transform = spawnParent.Transform;
|
||||||
|
ActorNode.Root.Spawn(actor, spawnParent);
|
||||||
|
actor.OrderInParent = newOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
result = DragDropEffect.Move;
|
result = DragDropEffect.Move;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,6 +763,11 @@ namespace FlaxEditor.SceneGraph.GUI
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ValidateDragScriptItem(ScriptItem script)
|
||||||
|
{
|
||||||
|
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void DoDragDrop()
|
protected override void DoDragDrop()
|
||||||
{
|
{
|
||||||
@@ -768,6 +808,7 @@ namespace FlaxEditor.SceneGraph.GUI
|
|||||||
_dragScripts = null;
|
_dragScripts = null;
|
||||||
_dragAssets = null;
|
_dragAssets = null;
|
||||||
_dragActorType = null;
|
_dragActorType = null;
|
||||||
|
_dragScriptItems = null;
|
||||||
_dragHandlers?.Clear();
|
_dragHandlers?.Clear();
|
||||||
_dragHandlers = null;
|
_dragHandlers = null;
|
||||||
_highlights = null;
|
_highlights = null;
|
||||||
|
|||||||
@@ -113,6 +113,40 @@ namespace FlaxEditor.Surface
|
|||||||
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal AnimGraphTraceEvent[] LastTraceEvents;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traceEvent = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static SurfaceStyle CreateStyle()
|
private static SurfaceStyle CreateStyle()
|
||||||
{
|
{
|
||||||
var editor = Editor.Instance;
|
var editor = Editor.Instance;
|
||||||
@@ -383,6 +417,7 @@ namespace FlaxEditor.Surface
|
|||||||
}
|
}
|
||||||
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||||
_nodesCache.Wait();
|
_nodesCache.Wait();
|
||||||
|
LastTraceEvents = null;
|
||||||
|
|
||||||
base.OnDestroy();
|
base.OnDestroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,9 +191,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
var value = title;
|
var value = title;
|
||||||
int count = 1;
|
int count = 1;
|
||||||
while (!OnRenameValidate(null, value))
|
while (!OnRenameValidate(null, value))
|
||||||
{
|
|
||||||
value = title + " " + count++;
|
value = title + " " + count++;
|
||||||
}
|
|
||||||
Values[0] = value;
|
Values[0] = value;
|
||||||
Title = value;
|
Title = value;
|
||||||
|
|
||||||
@@ -655,6 +653,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
protected Rectangle _renameButtonRect;
|
protected Rectangle _renameButtonRect;
|
||||||
private bool _cursorChanged = false;
|
private bool _cursorChanged = false;
|
||||||
private bool _textRectHovered = false;
|
private bool _textRectHovered = false;
|
||||||
|
private bool _debugActive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The transitions list from this state to the others.
|
/// The transitions list from this state to the others.
|
||||||
@@ -1092,6 +1091,16 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
|
|
||||||
// TODO: maybe update only on actual transitions change?
|
// TODO: maybe update only on actual transitions change?
|
||||||
UpdateTransitions();
|
UpdateTransitions();
|
||||||
|
|
||||||
|
// Debug current state
|
||||||
|
if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent))
|
||||||
|
{
|
||||||
|
_debugActive = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_debugActive = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -1132,6 +1141,10 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
|
|
||||||
// Close button
|
// Close button
|
||||||
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
Render2D.DrawSprite(style.Cross, _closeButtonRect, _closeButtonRect.Contains(_mousePosition) ? style.Foreground : style.ForegroundGrey);
|
||||||
|
|
||||||
|
// Debug outline
|
||||||
|
if (_debugActive)
|
||||||
|
Render2D.DrawRectangle(_textRect.MakeExpanded(1.0f), style.ProgressNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -1583,14 +1596,24 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transition rule will be rechecked during active transition with option to interrupt transition.
|
/// Transition rule will be rechecked during active transition with option to interrupt transition (to go back to the source state).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RuleRechecking = 1,
|
RuleRechecking = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interrupted transition is immediately stopped without blending out.
|
/// Interrupted transition is immediately stopped without blending out (back to the source/destination state).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Instant = 2,
|
Instant = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables checking other transitions in the source state that might interrupt this one.
|
||||||
|
/// </summary>
|
||||||
|
SourceState = 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables checking transitions in the destination state that might interrupt this one.
|
||||||
|
/// </summary>
|
||||||
|
DestinationState = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1613,6 +1636,8 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
UseDefaultRule = 4,
|
UseDefaultRule = 4,
|
||||||
InterruptionRuleRechecking = 8,
|
InterruptionRuleRechecking = 8,
|
||||||
InterruptionInstant = 16,
|
InterruptionInstant = 16,
|
||||||
|
InterruptionSourceState = 32,
|
||||||
|
InterruptionDestinationState = 64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1773,7 +1798,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transition interruption options.
|
/// Transition interruption options (flags, can select multiple values).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EditorOrder(70), DefaultValue(InterruptionFlags.None)]
|
[EditorOrder(70), DefaultValue(InterruptionFlags.None)]
|
||||||
public InterruptionFlags Interruption
|
public InterruptionFlags Interruption
|
||||||
@@ -1785,12 +1810,18 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
flags |= InterruptionFlags.RuleRechecking;
|
flags |= InterruptionFlags.RuleRechecking;
|
||||||
if (_data.HasFlag(Data.FlagTypes.InterruptionInstant))
|
if (_data.HasFlag(Data.FlagTypes.InterruptionInstant))
|
||||||
flags |= InterruptionFlags.Instant;
|
flags |= InterruptionFlags.Instant;
|
||||||
|
if (_data.HasFlag(Data.FlagTypes.InterruptionSourceState))
|
||||||
|
flags |= InterruptionFlags.SourceState;
|
||||||
|
if (_data.HasFlag(Data.FlagTypes.InterruptionDestinationState))
|
||||||
|
flags |= InterruptionFlags.DestinationState;
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_data.SetFlag(Data.FlagTypes.InterruptionRuleRechecking, value.HasFlag(InterruptionFlags.RuleRechecking));
|
_data.SetFlag(Data.FlagTypes.InterruptionRuleRechecking, value.HasFlag(InterruptionFlags.RuleRechecking));
|
||||||
_data.SetFlag(Data.FlagTypes.InterruptionInstant, value.HasFlag(InterruptionFlags.Instant));
|
_data.SetFlag(Data.FlagTypes.InterruptionInstant, value.HasFlag(InterruptionFlags.Instant));
|
||||||
|
_data.SetFlag(Data.FlagTypes.InterruptionSourceState, value.HasFlag(InterruptionFlags.SourceState));
|
||||||
|
_data.SetFlag(Data.FlagTypes.InterruptionDestinationState, value.HasFlag(InterruptionFlags.DestinationState));
|
||||||
SourceState.SaveTransitions(true);
|
SourceState.SaveTransitions(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,51 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Customized <see cref="SurfaceNode"/> for Blend with Mask node.
|
||||||
|
/// </summary>
|
||||||
|
public class SkeletonMaskSample : SurfaceNode
|
||||||
|
{
|
||||||
|
private AssetSelect _assetSelect;
|
||||||
|
private Box _assetBox;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public SkeletonMaskSample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||||
|
: base(id, context, nodeArch, groupArch)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void OnSurfaceLoaded(SurfaceNodeActions action)
|
||||||
|
{
|
||||||
|
base.OnSurfaceLoaded(action);
|
||||||
|
|
||||||
|
if (Surface != null)
|
||||||
|
{
|
||||||
|
_assetSelect = GetChild<AssetSelect>();
|
||||||
|
|
||||||
|
// 4 is the id of skeleton mask parameter node.
|
||||||
|
if (TryGetBox(4, out var box))
|
||||||
|
{
|
||||||
|
_assetBox = box;
|
||||||
|
_assetSelect.Visible = !_assetBox.HasAnyConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ConnectionTick(Box box)
|
||||||
|
{
|
||||||
|
base.ConnectionTick(box);
|
||||||
|
|
||||||
|
if (_assetBox == null)
|
||||||
|
return;
|
||||||
|
if (box.ID != _assetBox.ID)
|
||||||
|
return;
|
||||||
|
_assetSelect.Visible = !box.HasAnyConnection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Customized <see cref="SurfaceNode"/> for the animation sampling nodes
|
/// Customized <see cref="SurfaceNode"/> for the animation sampling nodes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -36,6 +81,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
{
|
{
|
||||||
private AssetSelect _assetSelect;
|
private AssetSelect _assetSelect;
|
||||||
private Box _assetBox;
|
private Box _assetBox;
|
||||||
|
private ProgressBar _playbackPos;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
public Sample(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch)
|
||||||
@@ -75,7 +121,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName;
|
Title = _assetBox.HasAnyConnection || asset == null ? "Animation" : asset.ShortName;
|
||||||
else
|
else
|
||||||
Title = asset?.ShortName ?? "Animation";
|
Title = asset?.ShortName ?? "Animation";
|
||||||
|
|
||||||
var style = Style.Current;
|
var style = Style.Current;
|
||||||
Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160);
|
Resize(Mathf.Max(230, style.FontLarge.MeasureText(Title).X + 30), 160);
|
||||||
}
|
}
|
||||||
@@ -93,6 +139,36 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
_assetSelect.Visible = !box.HasAnyConnection;
|
_assetSelect.Visible = !box.HasAnyConnection;
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Update(float deltaTime)
|
||||||
|
{
|
||||||
|
// Debug current playback position
|
||||||
|
if (((AnimGraphSurface)Surface).TryGetTraceEvent(this, out var traceEvent) && traceEvent.Asset is FlaxEngine.Animation anim)
|
||||||
|
{
|
||||||
|
if (_playbackPos == null)
|
||||||
|
{
|
||||||
|
_playbackPos = new ProgressBar
|
||||||
|
{
|
||||||
|
SmoothingScale = 0.0f,
|
||||||
|
Offsets = Margin.Zero,
|
||||||
|
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
|
||||||
|
Parent = this,
|
||||||
|
Height = 12.0f,
|
||||||
|
};
|
||||||
|
_playbackPos.Y -= 16.0f;
|
||||||
|
}
|
||||||
|
_playbackPos.Visible = true;
|
||||||
|
_playbackPos.Maximum = anim.Duration;
|
||||||
|
_playbackPos.Value = traceEvent.Value; // AnimGraph reports position in animation frames, not time
|
||||||
|
}
|
||||||
|
else if (_playbackPos != null)
|
||||||
|
{
|
||||||
|
_playbackPos.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Update(deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -493,7 +569,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
{
|
{
|
||||||
TypeID = 10,
|
TypeID = 10,
|
||||||
Title = "Blend Additive",
|
Title = "Blend Additive",
|
||||||
Description =
|
Description =
|
||||||
"Blend animation poses (with additive mode)" +
|
"Blend animation poses (with additive mode)" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\nNote: " +
|
"\nNote: " +
|
||||||
@@ -521,6 +597,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
TypeID = 11,
|
TypeID = 11,
|
||||||
Title = "Blend with Mask",
|
Title = "Blend with Mask",
|
||||||
Description = "Blend animation poses using skeleton mask",
|
Description = "Blend animation poses using skeleton mask",
|
||||||
|
Create = (id, context, arch, groupArch) => new SkeletonMaskSample(id, context, arch, groupArch),
|
||||||
Flags = NodeFlags.AnimGraph,
|
Flags = NodeFlags.AnimGraph,
|
||||||
Size = new Float2(180, 140),
|
Size = new Float2(180, 140),
|
||||||
DefaultValues = new object[]
|
DefaultValues = new object[]
|
||||||
@@ -534,7 +611,8 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
NodeElementArchetype.Factory.Input(0, "Pose A", true, typeof(void), 1),
|
NodeElementArchetype.Factory.Input(0, "Pose A", true, typeof(void), 1),
|
||||||
NodeElementArchetype.Factory.Input(1, "Pose B", true, typeof(void), 2),
|
NodeElementArchetype.Factory.Input(1, "Pose B", true, typeof(void), 2),
|
||||||
NodeElementArchetype.Factory.Input(2, "Alpha", true, typeof(float), 3, 0),
|
NodeElementArchetype.Factory.Input(2, "Alpha", true, typeof(float), 3, 0),
|
||||||
NodeElementArchetype.Factory.Asset(0, 70, 1, typeof(SkeletonMask)),
|
NodeElementArchetype.Factory.Input(3, "Skeleton Mask Asset", true, typeof(SkeletonMask), 4),
|
||||||
|
NodeElementArchetype.Factory.Asset(0, Surface.Constants.LayoutOffsetY * 4, 1, typeof(SkeletonMask)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new NodeArchetype
|
new NodeArchetype
|
||||||
|
|||||||
@@ -763,6 +763,17 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
public string Name;
|
public string Name;
|
||||||
public ScriptType Type;
|
public ScriptType Type;
|
||||||
public bool IsOut;
|
public bool IsOut;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
if (IsOut)
|
||||||
|
sb.Append("out ");
|
||||||
|
sb.Append(Type.ToString());
|
||||||
|
sb.Append(" ");
|
||||||
|
sb.Append(Name);
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SignatureInfo
|
private struct SignatureInfo
|
||||||
@@ -892,7 +903,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
{
|
{
|
||||||
ref var param = ref signature.Params[i];
|
ref var param = ref signature.Params[i];
|
||||||
ref var paramMember = ref memberParameters[i];
|
ref var paramMember = ref memberParameters[i];
|
||||||
if (param.Type != paramMember.Type || param.IsOut != paramMember.IsOut)
|
if (!SurfaceUtils.AreScriptTypesEqual(param.Type, paramMember.Type) || param.IsOut != paramMember.IsOut)
|
||||||
{
|
{
|
||||||
// Special case: param.Type is serialized as just a type while paramMember.Type might be a reference for output parameters (eg. `out Int32` vs `out Int32&`)
|
// Special case: param.Type is serialized as just a type while paramMember.Type might be a reference for output parameters (eg. `out Int32` vs `out Int32&`)
|
||||||
var paramMemberTypeName = paramMember.Type.TypeName;
|
var paramMemberTypeName = paramMember.Type.TypeName;
|
||||||
@@ -1660,7 +1671,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
SaveSignature();
|
SaveSignature();
|
||||||
|
|
||||||
// Check if return type has been changed
|
// Check if return type has been changed
|
||||||
if (_signature.ReturnType != prevReturnType)
|
if (!SurfaceUtils.AreScriptTypesEqual(_signature.ReturnType, prevReturnType))
|
||||||
{
|
{
|
||||||
// Update all return nodes used by this function to match the new type
|
// Update all return nodes used by this function to match the new type
|
||||||
var usedNodes = DepthFirstTraversal(false);
|
var usedNodes = DepthFirstTraversal(false);
|
||||||
@@ -2158,7 +2169,7 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
return false;
|
return false;
|
||||||
for (int i = 0; i < _signature.Length; i++)
|
for (int i = 0; i < _signature.Length; i++)
|
||||||
{
|
{
|
||||||
if (_signature[i].Type != sig.Parameters[i].Type)
|
if (!SurfaceUtils.AreScriptTypesEqual(_signature[i].Type, sig.Parameters[i].Type))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ namespace FlaxEditor.Surface
|
|||||||
|
|
||||||
if (node != null)
|
if (node != null)
|
||||||
{
|
{
|
||||||
args.SurfaceLocation.X += node.Width + 10;
|
args.SurfaceLocation.Y += node.Height + 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -532,5 +532,24 @@ namespace FlaxEditor.Surface
|
|||||||
value = new Double4(i);
|
value = new Double4(i);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool AreScriptTypesEqualInner(ScriptType left, ScriptType right)
|
||||||
|
{
|
||||||
|
// Special case for Vector types that use typedefs and might overlap
|
||||||
|
if (left.Type == typeof(Vector2) && (right.Type == typeof(Float2) || right.Type == typeof(Double2)))
|
||||||
|
return true;
|
||||||
|
if (left.Type == typeof(Vector3) && (right.Type == typeof(Float3) || right.Type == typeof(Double3)))
|
||||||
|
return true;
|
||||||
|
if (left.Type == typeof(Vector4) && (right.Type == typeof(Float4) || right.Type == typeof(Double4)))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool AreScriptTypesEqual(ScriptType left, ScriptType right)
|
||||||
|
{
|
||||||
|
if (left == right)
|
||||||
|
return true;
|
||||||
|
return AreScriptTypesEqualInner(left, right) || AreScriptTypesEqualInner(right, left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace FlaxEditor.Surface.Undo
|
|||||||
|
|
||||||
public ConnectBoxesAction(InputBox iB, OutputBox oB, bool connect)
|
public ConnectBoxesAction(InputBox iB, OutputBox oB, bool connect)
|
||||||
{
|
{
|
||||||
|
if (iB == null || oB == null || iB.ParentNode == null || oB.ParentNode == null)
|
||||||
|
throw new System.ArgumentNullException();
|
||||||
_surface = iB.Surface;
|
_surface = iB.Surface;
|
||||||
_context = new ContextHandle(iB.ParentNode.Context);
|
_context = new ContextHandle(iB.ParentNode.Context);
|
||||||
_connect = connect;
|
_connect = connect;
|
||||||
|
|||||||
@@ -33,6 +33,30 @@ namespace FlaxEditor.Surface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<VisjectSurfaceContext> ContextChanged;
|
public event Action<VisjectSurfaceContext> ContextChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the surface context with the given owning nodes IDs path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodePath">The node ids path.</param>
|
||||||
|
/// <returns>Found context or null if cannot.</returns>
|
||||||
|
public VisjectSurfaceContext FindContext(Span<uint> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the Visject surface context for the given surface data source context.
|
/// Creates the Visject surface context for the given surface data source context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -62,6 +86,8 @@ namespace FlaxEditor.Surface
|
|||||||
surfaceContext = CreateContext(_context, context);
|
surfaceContext = CreateContext(_context, context);
|
||||||
_context?.Children.Add(surfaceContext);
|
_context?.Children.Add(surfaceContext);
|
||||||
_contextCache.Add(contextHandle, surfaceContext);
|
_contextCache.Add(contextHandle, surfaceContext);
|
||||||
|
if (context is SurfaceNode asNode)
|
||||||
|
surfaceContext.OwnerNodeID = asNode.ID;
|
||||||
|
|
||||||
context.OnContextCreated(surfaceContext);
|
context.OnContextCreated(surfaceContext);
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,11 @@ namespace FlaxEditor.Surface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<SurfaceControl> ControlDeleted;
|
public event Action<SurfaceControl> ControlDeleted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier of the node that 'owns' this context (eg. State Machine which created this graph of state nodes).
|
||||||
|
/// </summary>
|
||||||
|
public uint OwnerNodeID;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="VisjectSurfaceContext"/> class.
|
/// Initializes a new instance of the <see cref="VisjectSurfaceContext"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
public float TargetHeight = 0.0f;
|
public float TargetHeight = 0.0f;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override unsafe void Apply(ref ApplyParams p)
|
public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
|
||||||
{
|
{
|
||||||
// If used with invert mode pick the target height level
|
// If used with invert mode pick the target height level
|
||||||
if (p.Options.Invert)
|
if (p.Options.Invert)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override unsafe void Apply(ref ApplyParams p)
|
public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
|
||||||
{
|
{
|
||||||
var strength = p.Strength * -10.0f;
|
var strength = p.Strength * -10.0f;
|
||||||
var brushPosition = p.Gizmo.CursorPosition;
|
var brushPosition = p.Gizmo.CursorPosition;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using FlaxEditor.Tools.Terrain.Brushes;
|
using FlaxEditor.Tools.Terrain.Brushes;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
|
||||||
@@ -50,18 +51,20 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
public virtual bool EditHoles => false;
|
public virtual bool EditHoles => false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies the modification to the terrain.
|
/// Gets all patches that will be affected by the brush
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="brush">The brush.</param>
|
/// <param name="brush">The brush.</param>
|
||||||
/// <param name="options">The options.</param>
|
/// <param name="options">The options.</param>
|
||||||
/// <param name="gizmo">The gizmo.</param>
|
/// <param name="gizmo">The gizmo.</param>
|
||||||
/// <param name="terrain">The terrain.</param>
|
/// <param name="terrain">The terrain.</param>
|
||||||
public unsafe void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
|
public virtual unsafe List<ApplyParams> GetAffectedPatches(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
|
||||||
{
|
{
|
||||||
|
List<ApplyParams> affectedPatches = new();
|
||||||
|
|
||||||
// Combine final apply strength
|
// Combine final apply strength
|
||||||
float strength = Strength * options.Strength * options.DeltaTime;
|
float strength = Strength * options.Strength * options.DeltaTime;
|
||||||
if (strength <= 0.0f)
|
if (strength <= 0.0f)
|
||||||
return;
|
return affectedPatches;
|
||||||
if (options.Invert && SupportsNegativeApply)
|
if (options.Invert && SupportsNegativeApply)
|
||||||
strength *= -1;
|
strength *= -1;
|
||||||
|
|
||||||
@@ -72,20 +75,10 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
|
var patchSize = chunkSize * FlaxEngine.Terrain.UnitsPerVertex * FlaxEngine.Terrain.PatchEdgeChunksCount;
|
||||||
var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
|
var tempBuffer = (float*)gizmo.GetHeightmapTempBuffer(heightmapLength * sizeof(float)).ToPointer();
|
||||||
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
|
var unitsPerVertexInv = 1.0f / FlaxEngine.Terrain.UnitsPerVertex;
|
||||||
ApplyParams p = new ApplyParams
|
|
||||||
{
|
|
||||||
Terrain = terrain,
|
|
||||||
Brush = brush,
|
|
||||||
Gizmo = gizmo,
|
|
||||||
Options = options,
|
|
||||||
Strength = strength,
|
|
||||||
HeightmapSize = heightmapSize,
|
|
||||||
TempBuffer = tempBuffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get brush bounds in terrain local space
|
// Get brush bounds in terrain local space
|
||||||
var brushBounds = gizmo.CursorBrushBounds;
|
var brushBounds = gizmo.CursorBrushBounds;
|
||||||
terrain.GetLocalToWorldMatrix(out p.TerrainWorld);
|
terrain.GetLocalToWorldMatrix(out var terrainWorld);
|
||||||
terrain.GetWorldToLocalMatrix(out var terrainInvWorld);
|
terrain.GetWorldToLocalMatrix(out var terrainInvWorld);
|
||||||
BoundingBox.Transform(ref brushBounds, ref terrainInvWorld, out var brushBoundsLocal);
|
BoundingBox.Transform(ref brushBounds, ref terrainInvWorld, out var brushBoundsLocal);
|
||||||
|
|
||||||
@@ -131,26 +124,78 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
if (sourceHeights == null && sourceHoles == null)
|
if (sourceHeights == null && sourceHoles == null)
|
||||||
throw new Exception("Cannot modify terrain. Loading heightmap failed. See log for more info.");
|
throw new Exception("Cannot modify terrain. Loading heightmap failed. See log for more info.");
|
||||||
|
|
||||||
// Record patch data before editing it
|
ApplyParams p = new ApplyParams
|
||||||
if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
|
|
||||||
{
|
{
|
||||||
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord);
|
Terrain = terrain,
|
||||||
}
|
TerrainWorld = terrainWorld,
|
||||||
|
Brush = brush,
|
||||||
|
Gizmo = gizmo,
|
||||||
|
Options = options,
|
||||||
|
Strength = strength,
|
||||||
|
HeightmapSize = heightmapSize,
|
||||||
|
TempBuffer = tempBuffer,
|
||||||
|
ModifiedOffset = modifiedOffset,
|
||||||
|
ModifiedSize = modifiedSize,
|
||||||
|
PatchCoord = patch.PatchCoord,
|
||||||
|
PatchPositionLocal = patchPositionLocal,
|
||||||
|
SourceHeightMap = sourceHeights,
|
||||||
|
SourceHolesMask = sourceHoles,
|
||||||
|
};
|
||||||
|
|
||||||
// Apply modification
|
affectedPatches.Add(p);
|
||||||
p.ModifiedOffset = modifiedOffset;
|
|
||||||
p.ModifiedSize = modifiedSize;
|
|
||||||
p.PatchCoord = patch.PatchCoord;
|
|
||||||
p.PatchPositionLocal = patchPositionLocal;
|
|
||||||
p.SourceHeightMap = sourceHeights;
|
|
||||||
p.SourceHolesMask = sourceHoles;
|
|
||||||
Apply(ref p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return affectedPatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the modification to the terrain.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="brush">The brush.</param>
|
||||||
|
/// <param name="options">The options.</param>
|
||||||
|
/// <param name="gizmo">The gizmo.</param>
|
||||||
|
/// <param name="terrain">The terrain.</param>
|
||||||
|
public void Apply(Brush brush, ref Options options, SculptTerrainGizmoMode gizmo, FlaxEngine.Terrain terrain)
|
||||||
|
{
|
||||||
|
var affectedPatches = GetAffectedPatches(brush, ref options, gizmo, terrain);
|
||||||
|
|
||||||
|
if (affectedPatches.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyBrush(gizmo, affectedPatches);
|
||||||
|
|
||||||
// Auto NavMesh rebuild
|
// Auto NavMesh rebuild
|
||||||
|
var brushBounds = gizmo.CursorBrushBounds;
|
||||||
gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
|
gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies the brush to all affected patches
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gizmo"></param>
|
||||||
|
/// <param name="affectedPatches"></param>
|
||||||
|
public virtual void ApplyBrush(SculptTerrainGizmoMode gizmo, List<ApplyParams> affectedPatches)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < affectedPatches.Count; i++)
|
||||||
|
{
|
||||||
|
ApplyParams patchApplyParams = affectedPatches[i];
|
||||||
|
|
||||||
|
// Record patch data before editing it
|
||||||
|
if (!gizmo.CurrentEditUndoAction.HashPatch(ref patchApplyParams.PatchCoord))
|
||||||
|
{
|
||||||
|
gizmo.CurrentEditUndoAction.AddPatch(ref patchApplyParams.PatchCoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyBrushToPatch(ref patchApplyParams);
|
||||||
|
|
||||||
|
// Auto NavMesh rebuild
|
||||||
|
var brushBounds = gizmo.CursorBrushBounds;
|
||||||
|
gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The mode apply parameters.
|
/// The mode apply parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -231,6 +276,6 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
/// Applies the modification to the terrain.
|
/// Applies the modification to the terrain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="p">The parameters to use.</param>
|
/// <param name="p">The parameters to use.</param>
|
||||||
public abstract void Apply(ref ApplyParams p);
|
public abstract void ApplyBrushToPatch(ref ApplyParams p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
public override bool SupportsNegativeApply => true;
|
public override bool SupportsNegativeApply => true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override unsafe void Apply(ref ApplyParams p)
|
public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
|
||||||
{
|
{
|
||||||
// Prepare
|
// Prepare
|
||||||
var brushPosition = p.Gizmo.CursorPosition;
|
var brushPosition = p.Gizmo.CursorPosition;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
public override bool SupportsNegativeApply => true;
|
public override bool SupportsNegativeApply => true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override unsafe void Apply(ref ApplyParams p)
|
public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
|
||||||
{
|
{
|
||||||
var strength = p.Strength * 1000.0f;
|
var strength = p.Strength * 1000.0f;
|
||||||
var brushPosition = p.Gizmo.CursorPosition;
|
var brushPosition = p.Gizmo.CursorPosition;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace FlaxEditor.Tools.Terrain.Sculpt
|
namespace FlaxEditor.Tools.Terrain.Sculpt
|
||||||
{
|
{
|
||||||
@@ -19,43 +20,109 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
public float FilterRadius = 0.4f;
|
public float FilterRadius = 0.4f;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override unsafe void Apply(ref ApplyParams p)
|
public override unsafe void ApplyBrush(SculptTerrainGizmoMode gizmo, List<ApplyParams> affectedPatches)
|
||||||
{
|
{
|
||||||
// Prepare
|
|
||||||
var brushPosition = p.Gizmo.CursorPosition;
|
|
||||||
var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * p.Brush.Size), 2);
|
|
||||||
var max = p.HeightmapSize - 1;
|
|
||||||
var strength = Mathf.Saturate(p.Strength);
|
|
||||||
|
|
||||||
// Apply brush modification
|
|
||||||
Profiler.BeginEvent("Apply Brush");
|
Profiler.BeginEvent("Apply Brush");
|
||||||
for (int z = 0; z < p.ModifiedSize.Y; z++)
|
|
||||||
|
// TODO: don't need these on each patch; just need them once
|
||||||
|
var heightmapSize = affectedPatches[0].HeightmapSize;
|
||||||
|
var radius = Mathf.Max(Mathf.CeilToInt(FilterRadius * 0.01f * affectedPatches[0].Brush.Size), 2);
|
||||||
|
|
||||||
|
// Calculate bounding coordinates of the total affected area
|
||||||
|
Int2 modifieedAreaMinCoord = Int2.Maximum;
|
||||||
|
Int2 modifiedAreaMaxCoord = Int2.Minimum;
|
||||||
|
for (int i = 0; i < affectedPatches.Count; i++)
|
||||||
{
|
{
|
||||||
var zz = z + p.ModifiedOffset.Y;
|
var patch = affectedPatches[i];
|
||||||
for (int x = 0; x < p.ModifiedSize.X; x++)
|
|
||||||
|
var tl = (patch.PatchCoord * (heightmapSize - 1)) + patch.ModifiedOffset;
|
||||||
|
var br = tl + patch.ModifiedSize;
|
||||||
|
|
||||||
|
if (tl.X <= modifieedAreaMinCoord.X && tl.Y <= modifieedAreaMinCoord.Y)
|
||||||
|
modifieedAreaMinCoord = tl;
|
||||||
|
if (br.X >= modifiedAreaMaxCoord.X && br.Y >= modifiedAreaMaxCoord.Y)
|
||||||
|
modifiedAreaMaxCoord = br;
|
||||||
|
}
|
||||||
|
var totalModifiedSize = modifiedAreaMaxCoord - modifieedAreaMinCoord;
|
||||||
|
|
||||||
|
// Build map of heights in affected area
|
||||||
|
var modifiedHeights = new float[totalModifiedSize.X * totalModifiedSize.Y];
|
||||||
|
for (int i = 0; i < affectedPatches.Count; i++)
|
||||||
|
{
|
||||||
|
var patch = affectedPatches[i];
|
||||||
|
|
||||||
|
for (int z = 0; z < patch.ModifiedSize.Y; z++)
|
||||||
{
|
{
|
||||||
var xx = x + p.ModifiedOffset.X;
|
for (int x = 0; x < patch.ModifiedSize.X; x++)
|
||||||
var sourceHeight = p.SourceHeightMap[zz * p.HeightmapSize + xx];
|
|
||||||
|
|
||||||
var samplePositionLocal = p.PatchPositionLocal + new Vector3(xx * FlaxEngine.Terrain.UnitsPerVertex, sourceHeight, zz * FlaxEngine.Terrain.UnitsPerVertex);
|
|
||||||
Vector3.Transform(ref samplePositionLocal, ref p.TerrainWorld, out Vector3 samplePositionWorld);
|
|
||||||
|
|
||||||
var paintAmount = p.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength;
|
|
||||||
|
|
||||||
if (paintAmount > 0)
|
|
||||||
{
|
{
|
||||||
|
// Read height from current patch
|
||||||
|
var localCoordX = (x + patch.ModifiedOffset.X);
|
||||||
|
var localCoordY = (z + patch.ModifiedOffset.Y);
|
||||||
|
var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX];
|
||||||
|
|
||||||
|
// Calculate the absolute coordinate of the terrain point
|
||||||
|
var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z);
|
||||||
|
var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord;
|
||||||
|
|
||||||
|
// Store height
|
||||||
|
var index = (currentPointCoordRelativeToModifiedArea.Y * totalModifiedSize.X) + currentPointCoordRelativeToModifiedArea.X;
|
||||||
|
modifiedHeights[index] = coordHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through modified points and smooth now that we have height information for all necessary points
|
||||||
|
for (int i = 0; i < affectedPatches.Count; i++)
|
||||||
|
{
|
||||||
|
var patch = affectedPatches[i];
|
||||||
|
var brushPosition = patch.Gizmo.CursorPosition;
|
||||||
|
var strength = Mathf.Saturate(patch.Strength);
|
||||||
|
|
||||||
|
for (int z = 0; z < patch.ModifiedSize.Y; z++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < patch.ModifiedSize.X; x++)
|
||||||
|
{
|
||||||
|
// Read height from current patch
|
||||||
|
var localCoordX = (x + patch.ModifiedOffset.X);
|
||||||
|
var localCoordY = (z + patch.ModifiedOffset.Y);
|
||||||
|
var coordHeight = patch.SourceHeightMap[(localCoordY * heightmapSize) + localCoordX];
|
||||||
|
|
||||||
|
// Calculate the absolute coordinate of the terrain point
|
||||||
|
var absoluteCurrentPointCoord = patch.PatchCoord * (heightmapSize - 1) + patch.ModifiedOffset + new Int2(x, z);
|
||||||
|
var currentPointCoordRelativeToModifiedArea = absoluteCurrentPointCoord - modifieedAreaMinCoord;
|
||||||
|
|
||||||
|
// Calculate brush influence at the current position
|
||||||
|
var samplePositionLocal = patch.PatchPositionLocal + new Vector3(localCoordX * FlaxEngine.Terrain.UnitsPerVertex, coordHeight, localCoordY * FlaxEngine.Terrain.UnitsPerVertex);
|
||||||
|
Vector3.Transform(ref samplePositionLocal, ref patch.TerrainWorld, out Vector3 samplePositionWorld);
|
||||||
|
var paintAmount = patch.Brush.Sample(ref brushPosition, ref samplePositionWorld) * strength;
|
||||||
|
|
||||||
|
if (paintAmount == 0)
|
||||||
|
{
|
||||||
|
patch.TempBuffer[z * patch.ModifiedSize.X + x] = coordHeight;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record patch data before editing it
|
||||||
|
if (!gizmo.CurrentEditUndoAction.HashPatch(ref patch.PatchCoord))
|
||||||
|
{
|
||||||
|
gizmo.CurrentEditUndoAction.AddPatch(ref patch.PatchCoord);
|
||||||
|
}
|
||||||
|
|
||||||
// Sum the nearby values
|
// Sum the nearby values
|
||||||
float smoothValue = 0;
|
float smoothValue = 0;
|
||||||
int smoothValueSamples = 0;
|
int smoothValueSamples = 0;
|
||||||
int minX = Math.Max(x - radius + p.ModifiedOffset.X, 0);
|
|
||||||
int minZ = Math.Max(z - radius + p.ModifiedOffset.Y, 0);
|
var minX = Math.Max(0, currentPointCoordRelativeToModifiedArea.X - radius);
|
||||||
int maxX = Math.Min(x + radius + p.ModifiedOffset.X, max);
|
var maxX = Math.Min(totalModifiedSize.X - 1, currentPointCoordRelativeToModifiedArea.X + radius);
|
||||||
int maxZ = Math.Min(z + radius + p.ModifiedOffset.Y, max);
|
var minZ = Math.Max(0, currentPointCoordRelativeToModifiedArea.Y - radius);
|
||||||
|
var maxZ = Math.Min(totalModifiedSize.Y - 1, currentPointCoordRelativeToModifiedArea.Y + radius);
|
||||||
|
|
||||||
for (int dz = minZ; dz <= maxZ; dz++)
|
for (int dz = minZ; dz <= maxZ; dz++)
|
||||||
{
|
{
|
||||||
for (int dx = minX; dx <= maxX; dx++)
|
for (int dx = minX; dx <= maxX; dx++)
|
||||||
{
|
{
|
||||||
var height = p.SourceHeightMap[dz * p.HeightmapSize + dx];
|
var coordIndex = (dz * totalModifiedSize.X) + dx;
|
||||||
|
var height = modifiedHeights[coordIndex];
|
||||||
smoothValue += height;
|
smoothValue += height;
|
||||||
smoothValueSamples++;
|
smoothValueSamples++;
|
||||||
}
|
}
|
||||||
@@ -65,18 +132,26 @@ namespace FlaxEditor.Tools.Terrain.Sculpt
|
|||||||
smoothValue /= smoothValueSamples;
|
smoothValue /= smoothValueSamples;
|
||||||
|
|
||||||
// Blend between the height and smooth value
|
// Blend between the height and smooth value
|
||||||
p.TempBuffer[z * p.ModifiedSize.X + x] = Mathf.Lerp(sourceHeight, smoothValue, paintAmount);
|
var newHeight = Mathf.Lerp(coordHeight, smoothValue, paintAmount);
|
||||||
}
|
patch.TempBuffer[z * patch.ModifiedSize.X + x] = newHeight;
|
||||||
else
|
|
||||||
{
|
|
||||||
p.TempBuffer[z * p.ModifiedSize.X + x] = sourceHeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Profiler.EndEvent();
|
|
||||||
|
|
||||||
// Update terrain patch
|
// Update terrain patch
|
||||||
TerrainTools.ModifyHeightMap(p.Terrain, ref p.PatchCoord, p.TempBuffer, ref p.ModifiedOffset, ref p.ModifiedSize);
|
TerrainTools.ModifyHeightMap(patch.Terrain, ref patch.PatchCoord, patch.TempBuffer, ref patch.ModifiedOffset, ref patch.ModifiedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto NavMesh rebuild
|
||||||
|
var brushBounds = gizmo.CursorBrushBounds;
|
||||||
|
gizmo.CurrentEditUndoAction.AddDirtyBounds(ref brushBounds);
|
||||||
|
|
||||||
|
Profiler.EndEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override unsafe void ApplyBrushToPatch(ref ApplyParams p)
|
||||||
|
{
|
||||||
|
// noop; unused
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include "Engine/Graphics/Textures/TextureData.h"
|
#include "Engine/Graphics/Textures/TextureData.h"
|
||||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||||
#include "Engine/Core/Math/Color32.h"
|
|
||||||
#include "Engine/Core/Config/GameSettings.h"
|
#include "Engine/Core/Config/GameSettings.h"
|
||||||
#include "Engine/Content/Content.h"
|
#include "Engine/Content/Content.h"
|
||||||
#include "Engine/Content/AssetReference.h"
|
#include "Engine/Content/AssetReference.h"
|
||||||
@@ -20,497 +19,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MS-DOS header found at the beginning in a PE format file.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// COFF header found in a PE format file.
|
|
||||||
/// </summary>
|
|
||||||
struct COFFHeader
|
|
||||||
{
|
|
||||||
uint16 machine;
|
|
||||||
uint16 numSections;
|
|
||||||
uint32 timeDateStamp;
|
|
||||||
uint32 ptrSymbolTable;
|
|
||||||
uint32 numSymbols;
|
|
||||||
uint16 sizeOptHeader;
|
|
||||||
uint16 characteristics;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains address and size of data areas in a PE image.
|
|
||||||
/// </summary>
|
|
||||||
struct PEDataDirectory
|
|
||||||
{
|
|
||||||
uint32 virtualAddress;
|
|
||||||
uint32 size;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional header in a 32-bit PE format file.
|
|
||||||
/// </summary>
|
|
||||||
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];
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Optional header in a 64-bit PE format file.
|
|
||||||
/// </summary>
|
|
||||||
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];
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A section header in a PE format file.
|
|
||||||
/// </summary>
|
|
||||||
struct PESectionHeader
|
|
||||||
{
|
|
||||||
char name[8];
|
|
||||||
uint32 virtualSize;
|
|
||||||
uint32 relativeVirtualAddress;
|
|
||||||
uint32 physicalSize;
|
|
||||||
uint32 physicalAddress;
|
|
||||||
uint8 deprecated[12];
|
|
||||||
uint32 flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A resource table header within a .rsrc section in a PE format file.
|
|
||||||
/// </summary>
|
|
||||||
struct PEImageResourceDirectory
|
|
||||||
{
|
|
||||||
uint32 flags;
|
|
||||||
uint32 timeDateStamp;
|
|
||||||
uint16 majorVersion;
|
|
||||||
uint16 minorVersion;
|
|
||||||
uint16 numNamedEntries;
|
|
||||||
uint16 numIdEntries;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A single entry in a resource table within a .rsrc section in a PE format file.
|
|
||||||
/// </summary>
|
|
||||||
struct PEImageResourceEntry
|
|
||||||
{
|
|
||||||
uint32 type;
|
|
||||||
uint32 offsetDirectory : 31;
|
|
||||||
uint32 isDirectory : 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// An entry in a resource table referencing resource data. Found within a .rsrc section in a PE format file.
|
|
||||||
/// </summary>
|
|
||||||
struct PEImageResourceEntryData
|
|
||||||
{
|
|
||||||
uint32 offsetData;
|
|
||||||
uint32 size;
|
|
||||||
uint32 codePage;
|
|
||||||
uint32 resourceHandle;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Header used in icon file format.
|
|
||||||
/// </summary>
|
|
||||||
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<PESectionHeader> 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<uint8> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EditorUtilities::FormatAppPackageName(String& packageName)
|
bool EditorUtilities::FormatAppPackageName(String& packageName)
|
||||||
{
|
{
|
||||||
const auto gameSettings = GameSettings::Get();
|
const auto gameSettings = GameSettings::Get();
|
||||||
|
|||||||
@@ -22,14 +22,6 @@ public:
|
|||||||
SplashScreen,
|
SplashScreen,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates the Win32 executable file icon.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The exe path.</param>
|
|
||||||
/// <param name="icon">The icon image data.</param>
|
|
||||||
/// <returns>True if fails, otherwise false.</returns>
|
|
||||||
static bool UpdateExeIcon(const String& path, const TextureData& icon);
|
|
||||||
|
|
||||||
static bool FormatAppPackageName(String& packageName);
|
static bool FormatAppPackageName(String& packageName);
|
||||||
static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon);
|
static bool GetApplicationImage(const Guid& imageId, TextureData& imageData, ApplicationImageType type = ApplicationImageType::Icon);
|
||||||
static bool GetTexture(const Guid& textureId, TextureData& textureData);
|
static bool GetTexture(const Guid& textureId, TextureData& textureData);
|
||||||
|
|||||||
@@ -1310,8 +1310,17 @@ namespace FlaxEditor.Utilities
|
|||||||
inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF);
|
inputActions.Add(options => options.BuildSDF, Editor.Instance.BuildAllMeshesSDF);
|
||||||
inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot);
|
inputActions.Add(options => options.TakeScreenshot, Editor.Instance.Windows.TakeScreenshot);
|
||||||
inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow());
|
inputActions.Add(options => options.ProfilerWindow, () => Editor.Instance.Windows.ProfilerWin.FocusOrShow());
|
||||||
inputActions.Add(options => options.ProfilerStartStop, () => { Editor.Instance.Windows.ProfilerWin.LiveRecording = !Editor.Instance.Windows.ProfilerWin.LiveRecording; Editor.Instance.UI.AddStatusMessage($"Profiling {(Editor.Instance.Windows.ProfilerWin.LiveRecording ? "started" : "stopped")}."); });
|
inputActions.Add(options => options.ProfilerStartStop, () =>
|
||||||
inputActions.Add(options => options.ProfilerClear, () => { Editor.Instance.Windows.ProfilerWin.Clear(); Editor.Instance.UI.AddStatusMessage($"Profiling results cleared."); });
|
{
|
||||||
|
bool recording = !Editor.Instance.Windows.ProfilerWin.LiveRecording;
|
||||||
|
Editor.Instance.Windows.ProfilerWin.LiveRecording = recording;
|
||||||
|
Editor.Instance.UI.AddStatusMessage($"Profiling {(recording ? "started" : "stopped")}.");
|
||||||
|
});
|
||||||
|
inputActions.Add(options => options.ProfilerClear, () =>
|
||||||
|
{
|
||||||
|
Editor.Instance.Windows.ProfilerWin.Clear();
|
||||||
|
Editor.Instance.UI.AddStatusMessage($"Profiling results cleared.");
|
||||||
|
});
|
||||||
inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes());
|
inputActions.Add(options => options.SaveScenes, () => Editor.Instance.Scene.SaveScenes());
|
||||||
inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes());
|
inputActions.Add(options => options.CloseScenes, () => Editor.Instance.Scene.CloseAllScenes());
|
||||||
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
|
inputActions.Add(options => options.OpenScriptsProject, () => Editor.Instance.CodeEditing.OpenSolution());
|
||||||
|
|||||||
@@ -1113,7 +1113,7 @@ namespace FlaxEditor.Viewport
|
|||||||
private void OnFarPlaneChanged(FloatValueBox control)
|
private void OnFarPlaneChanged(FloatValueBox control)
|
||||||
{
|
{
|
||||||
_farPlane = control.Value;
|
_farPlane = control.Value;
|
||||||
_editor.ProjectCache.SetCustomData("CameraNearPlaneValue", _farPlane.ToString());
|
_editor.ProjectCache.SetCustomData("CameraFarPlaneValue", _farPlane.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using FlaxEditor.Content;
|
using FlaxEditor.Content;
|
||||||
using FlaxEditor.Gizmo;
|
using FlaxEditor.Gizmo;
|
||||||
using FlaxEditor.GUI.ContextMenu;
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
@@ -194,7 +195,7 @@ namespace FlaxEditor.Viewport
|
|||||||
: base(Object.New<SceneRenderTask>(), editor.Undo, editor.Scene.Root)
|
: base(Object.New<SceneRenderTask>(), editor.Undo, editor.Scene.Root)
|
||||||
{
|
{
|
||||||
_editor = editor;
|
_editor = editor;
|
||||||
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType);
|
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
|
||||||
var inputOptions = editor.Options.Options.Input;
|
var inputOptions = editor.Options.Options.Input;
|
||||||
|
|
||||||
// Prepare rendering task
|
// Prepare rendering task
|
||||||
@@ -940,6 +941,11 @@ namespace FlaxEditor.Viewport
|
|||||||
return Level.IsAnySceneLoaded;
|
return Level.IsAnySceneLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ValidateDragScriptItem(ScriptItem script)
|
||||||
|
{
|
||||||
|
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using FlaxEditor.Content;
|
using FlaxEditor.Content;
|
||||||
using FlaxEditor.Gizmo;
|
using FlaxEditor.Gizmo;
|
||||||
using FlaxEditor.GUI.ContextMenu;
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
@@ -81,7 +82,7 @@ namespace FlaxEditor.Viewport
|
|||||||
_window.SelectionChanged += OnSelectionChanged;
|
_window.SelectionChanged += OnSelectionChanged;
|
||||||
Undo = window.Undo;
|
Undo = window.Undo;
|
||||||
ViewportCamera = new FPSCamera();
|
ViewportCamera = new FPSCamera();
|
||||||
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType);
|
DragHandlers = new ViewportDragHandlers(this, this, ValidateDragItem, ValidateDragActorType, ValidateDragScriptItem);
|
||||||
ShowDebugDraw = true;
|
ShowDebugDraw = true;
|
||||||
ShowEditorPrimitives = true;
|
ShowEditorPrimitives = true;
|
||||||
Gizmos = new GizmosCollection(this);
|
Gizmos = new GizmosCollection(this);
|
||||||
@@ -702,6 +703,11 @@ namespace FlaxEditor.Viewport
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ValidateDragScriptItem(ScriptItem script)
|
||||||
|
{
|
||||||
|
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
public override DragDropEffect OnDragMove(ref Float2 location, DragData data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,17 +39,19 @@ namespace FlaxEditor.Viewport
|
|||||||
private readonly EditorViewport _viewport;
|
private readonly EditorViewport _viewport;
|
||||||
private readonly DragAssets<DragDropEventArgs> _dragAssets;
|
private readonly DragAssets<DragDropEventArgs> _dragAssets;
|
||||||
private readonly DragActorType<DragDropEventArgs> _dragActorType;
|
private readonly DragActorType<DragDropEventArgs> _dragActorType;
|
||||||
|
private readonly DragScriptItems<DragDropEventArgs> _dragScriptItem;
|
||||||
|
|
||||||
private StaticModel _previewStaticModel;
|
private StaticModel _previewStaticModel;
|
||||||
private int _previewModelEntryIndex;
|
private int _previewModelEntryIndex;
|
||||||
private BrushSurface _previewBrushSurface;
|
private BrushSurface _previewBrushSurface;
|
||||||
|
|
||||||
internal ViewportDragHandlers(IGizmoOwner owner, EditorViewport viewport, Func<AssetItem, bool> validateAsset, Func<ScriptType, bool> validateDragActorType)
|
internal ViewportDragHandlers(IGizmoOwner owner, EditorViewport viewport, Func<AssetItem, bool> validateAsset, Func<ScriptType, bool> validateDragActorType, Func<ScriptItem, bool> validateDragScriptItem)
|
||||||
{
|
{
|
||||||
_owner = owner;
|
_owner = owner;
|
||||||
_viewport = viewport;
|
_viewport = viewport;
|
||||||
Add(_dragAssets = new DragAssets<DragDropEventArgs>(validateAsset));
|
Add(_dragAssets = new DragAssets<DragDropEventArgs>(validateAsset));
|
||||||
Add(_dragActorType = new DragActorType<DragDropEventArgs>(validateDragActorType));
|
Add(_dragActorType = new DragActorType<DragDropEventArgs>(validateDragActorType));
|
||||||
|
Add(_dragScriptItem = new DragScriptItems<DragDropEventArgs>(validateDragScriptItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ClearDragEffects()
|
internal void ClearDragEffects()
|
||||||
@@ -102,7 +104,12 @@ namespace FlaxEditor.Viewport
|
|||||||
foreach (var actorType in _dragActorType.Objects)
|
foreach (var actorType in _dragActorType.Objects)
|
||||||
Spawn(actorType, hit, ref location, ref hitLocation, ref hitNormal);
|
Spawn(actorType, hit, ref location, ref hitLocation, ref hitNormal);
|
||||||
}
|
}
|
||||||
|
else if (_dragScriptItem.HasValidDrag)
|
||||||
|
{
|
||||||
|
result = _dragScriptItem.Effect;
|
||||||
|
foreach (var scripItem in _dragScriptItem.Objects)
|
||||||
|
Spawn(scripItem, hit, ref location, ref hitLocation, ref hitNormal);
|
||||||
|
}
|
||||||
OnDragDrop(new DragDropEventArgs { Hit = hit, HitLocation = hitLocation });
|
OnDragDrop(new DragDropEventArgs { Hit = hit, HitLocation = hitLocation });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -193,6 +200,15 @@ namespace FlaxEditor.Viewport
|
|||||||
_viewport.Focus();
|
_viewport.Focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Spawn(ScriptItem item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
|
||||||
|
{
|
||||||
|
var actorType = Editor.Instance.CodeEditing.Actors.Get(item);
|
||||||
|
if (actorType != ScriptType.Null)
|
||||||
|
{
|
||||||
|
Spawn(actorType, hit, ref location, ref hitLocation, ref hitNormal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Spawn(ScriptType item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
|
private void Spawn(ScriptType item, SceneGraphNode hit, ref Float2 location, ref Vector3 hitLocation, ref Vector3 hitNormal)
|
||||||
{
|
{
|
||||||
var actor = item.CreateInstance() as Actor;
|
var actor = item.CreateInstance() as Actor;
|
||||||
|
|||||||
@@ -154,10 +154,11 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
private struct AnimGraphDebugFlowInfo
|
private unsafe struct AnimGraphDebugFlowInfo
|
||||||
{
|
{
|
||||||
public uint NodeId;
|
public uint NodeId;
|
||||||
public int BoxId;
|
public int BoxId;
|
||||||
|
public fixed uint NodePath[8];
|
||||||
}
|
}
|
||||||
|
|
||||||
private FlaxObjectRefPickerControl _debugPicker;
|
private FlaxObjectRefPickerControl _debugPicker;
|
||||||
@@ -252,25 +253,26 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
return obj is AnimatedModel player && player.AnimationGraph == OriginalAsset;
|
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
|
// Filter the flow
|
||||||
if (_debugPicker.Value != null)
|
if (_debugPicker.Value != null)
|
||||||
{
|
{
|
||||||
if (asset != OriginalAsset || _debugPicker.Value != obj)
|
if (flowInfo.Asset != OriginalAsset || _debugPicker.Value != flowInfo.Instance)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (asset != Asset || _preview.PreviewActor != obj)
|
if (flowInfo.Asset != Asset || _preview.PreviewActor != flowInfo.Instance)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register flow to show it in UI on a surface
|
// 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)
|
lock (_debugFlows)
|
||||||
{
|
{
|
||||||
_debugFlows.Add(flowInfo);
|
_debugFlows.Add(flow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,8 +396,18 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnUpdate()
|
public override unsafe void OnUpdate()
|
||||||
{
|
{
|
||||||
|
// Extract animations playback state from the events tracing
|
||||||
|
var debugActor = _debugPicker.Value as AnimatedModel;
|
||||||
|
if (debugActor == null)
|
||||||
|
debugActor = _preview.PreviewActor;
|
||||||
|
if (debugActor != null)
|
||||||
|
{
|
||||||
|
debugActor.EnableTracing = true;
|
||||||
|
Surface.LastTraceEvents = debugActor.TraceEvents;
|
||||||
|
}
|
||||||
|
|
||||||
base.OnUpdate();
|
base.OnUpdate();
|
||||||
|
|
||||||
// Update graph execution flow debugging visualization
|
// Update graph execution flow debugging visualization
|
||||||
@@ -403,7 +415,8 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
{
|
{
|
||||||
foreach (var debugFlow in _debugFlows)
|
foreach (var debugFlow in _debugFlows)
|
||||||
{
|
{
|
||||||
var node = Surface.Context.FindNode(debugFlow.NodeId);
|
var context = Surface.FindContext(new Span<uint>(debugFlow.NodePath, 8));
|
||||||
|
var node = context?.FindNode(debugFlow.NodeId);
|
||||||
var box = node?.GetBox(debugFlow.BoxId);
|
var box = node?.GetBox(debugFlow.BoxId);
|
||||||
box?.HighlightConnections();
|
box?.HighlightConnections();
|
||||||
}
|
}
|
||||||
@@ -416,6 +429,8 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnDestroy()
|
public override void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (IsDisposing)
|
||||||
|
return;
|
||||||
Animations.DebugFlow -= OnDebugFlow;
|
Animations.DebugFlow -= OnDebugFlow;
|
||||||
|
|
||||||
_properties = null;
|
_properties = null;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
|
|
||||||
// Use virtual animation graph to playback the animation
|
// Use virtual animation graph to playback the animation
|
||||||
_animGraph = FlaxEngine.Content.CreateVirtualAsset<AnimationGraph>();
|
_animGraph = FlaxEngine.Content.CreateVirtualAsset<AnimationGraph>();
|
||||||
_animGraph.InitAsAnimation(model, _window.Asset);
|
_animGraph.InitAsAnimation(model, _window.Asset, true, true);
|
||||||
PreviewActor.AnimationGraph = _animGraph;
|
PreviewActor.AnimationGraph = _animGraph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using FlaxEditor.Content;
|
using FlaxEditor.Content;
|
||||||
using FlaxEditor.GUI.ContextMenu;
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
using FlaxEditor.GUI.Drag;
|
using FlaxEditor.GUI.Drag;
|
||||||
@@ -64,6 +65,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
private PrefabWindow _window;
|
private PrefabWindow _window;
|
||||||
private DragAssets _dragAssets;
|
private DragAssets _dragAssets;
|
||||||
private DragActorType _dragActorType;
|
private DragActorType _dragActorType;
|
||||||
|
private DragScriptItems _dragScriptItems;
|
||||||
private DragHandlers _dragHandlers;
|
private DragHandlers _dragHandlers;
|
||||||
|
|
||||||
public SceneTreePanel(PrefabWindow window)
|
public SceneTreePanel(PrefabWindow window)
|
||||||
@@ -84,6 +86,11 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ValidateDragScriptItem(ScriptItem script)
|
||||||
|
{
|
||||||
|
return Editor.Instance.CodeEditing.Actors.Get(script);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
public override DragDropEffect OnDragEnter(ref Float2 location, DragData data)
|
||||||
{
|
{
|
||||||
@@ -106,6 +113,13 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
}
|
}
|
||||||
if (_dragActorType.OnDragEnter(data))
|
if (_dragActorType.OnDragEnter(data))
|
||||||
return _dragActorType.Effect;
|
return _dragActorType.Effect;
|
||||||
|
if (_dragScriptItems == null)
|
||||||
|
{
|
||||||
|
_dragScriptItems = new DragScriptItems(ValidateDragScriptItem);
|
||||||
|
_dragHandlers.Add(_dragScriptItems);
|
||||||
|
}
|
||||||
|
if (_dragScriptItems.OnDragEnter(data))
|
||||||
|
return _dragScriptItems.Effect;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -162,7 +176,27 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
}
|
}
|
||||||
result = DragDropEffect.Move;
|
result = DragDropEffect.Move;
|
||||||
}
|
}
|
||||||
|
// Drag script item
|
||||||
|
else if (_dragScriptItems != null && _dragScriptItems.HasValidDrag)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _dragScriptItems.Objects.Count; i++)
|
||||||
|
{
|
||||||
|
var item = _dragScriptItems.Objects[i];
|
||||||
|
var actorType = Editor.Instance.CodeEditing.Actors.Get(item);
|
||||||
|
if (actorType != ScriptType.Null)
|
||||||
|
{
|
||||||
|
var actor = actorType.CreateInstance() as Actor;
|
||||||
|
if (actor == null)
|
||||||
|
{
|
||||||
|
Editor.LogWarning("Failed to spawn actor of type " + actorType.TypeName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
actor.Name = actorType.Name;
|
||||||
|
_window.Spawn(actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = DragDropEffect.Move;
|
||||||
|
}
|
||||||
_dragHandlers.OnDragDrop(null);
|
_dragHandlers.OnDragDrop(null);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -173,6 +207,7 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
_window = null;
|
_window = null;
|
||||||
_dragAssets = null;
|
_dragAssets = null;
|
||||||
_dragActorType = null;
|
_dragActorType = null;
|
||||||
|
_dragScriptItems = null;
|
||||||
_dragHandlers?.Clear();
|
_dragHandlers?.Clear();
|
||||||
_dragHandlers = null;
|
_dragHandlers = null;
|
||||||
|
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ namespace FlaxEditor.Windows.Assets
|
|||||||
set => Sprite.Name = value;
|
set => Sprite.Name = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[EditorOrder(1), Limit(-4096, 4096)]
|
[EditorOrder(1)]
|
||||||
public Float2 Location
|
public Float2 Location
|
||||||
{
|
{
|
||||||
get => Sprite.Location;
|
get => Sprite.Location;
|
||||||
set => Sprite.Location = value;
|
set => Sprite.Location = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[EditorOrder(3), Limit(0, 4096)]
|
[EditorOrder(3), Limit(0)]
|
||||||
public Float2 Size
|
public Float2 Size
|
||||||
{
|
{
|
||||||
get => Sprite.Size;
|
get => Sprite.Size;
|
||||||
|
|||||||
@@ -799,7 +799,10 @@ namespace FlaxEditor.Windows
|
|||||||
if (proxy == null)
|
if (proxy == null)
|
||||||
throw new ArgumentNullException(nameof(proxy));
|
throw new ArgumentNullException(nameof(proxy));
|
||||||
|
|
||||||
|
// Setup name
|
||||||
string name = initialName ?? proxy.NewItemName;
|
string name = initialName ?? proxy.NewItemName;
|
||||||
|
if (!proxy.IsFileNameValid(name) || Utilities.Utils.HasInvalidPathChar(name))
|
||||||
|
name = proxy.NewItemName;
|
||||||
|
|
||||||
// If the proxy can not be created in the current folder, then navigate to the content folder
|
// If the proxy can not be created in the current folder, then navigate to the content folder
|
||||||
if (!proxy.CanCreate(CurrentViewFolder))
|
if (!proxy.CanCreate(CurrentViewFolder))
|
||||||
|
|||||||
@@ -226,6 +226,8 @@ namespace FlaxEditor.Windows
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnDestroy()
|
public override void OnDestroy()
|
||||||
{
|
{
|
||||||
|
if (IsDisposing)
|
||||||
|
return;
|
||||||
OnExit();
|
OnExit();
|
||||||
|
|
||||||
// Unregister
|
// Unregister
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ namespace FlaxEditor.Windows
|
|||||||
Bounds = new Rectangle(nameLabel.X, tmp1, nameLabel.Width, Height - tmp1 - margin),
|
Bounds = new Rectangle(nameLabel.X, tmp1, nameLabel.Width, Height - tmp1 - margin),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var xOffset = nameLabel.Width;
|
||||||
string versionString = string.Empty;
|
string versionString = string.Empty;
|
||||||
if (desc.IsAlpha)
|
if (desc.IsAlpha)
|
||||||
versionString = "ALPHA ";
|
versionString = "ALPHA ";
|
||||||
@@ -109,7 +110,7 @@ namespace FlaxEditor.Windows
|
|||||||
AnchorPreset = AnchorPresets.TopRight,
|
AnchorPreset = AnchorPresets.TopRight,
|
||||||
Text = versionString,
|
Text = versionString,
|
||||||
Parent = this,
|
Parent = this,
|
||||||
Bounds = new Rectangle(Width - 140 - margin, margin, 140, 14),
|
Bounds = new Rectangle(Width - 140 - margin - xOffset, margin, 140, 14),
|
||||||
};
|
};
|
||||||
|
|
||||||
string url = null;
|
string url = null;
|
||||||
@@ -129,7 +130,7 @@ namespace FlaxEditor.Windows
|
|||||||
AnchorPreset = AnchorPresets.TopRight,
|
AnchorPreset = AnchorPresets.TopRight,
|
||||||
Text = desc.Author,
|
Text = desc.Author,
|
||||||
Parent = this,
|
Parent = this,
|
||||||
Bounds = new Rectangle(Width - authorWidth - margin, versionLabel.Bottom + margin, authorWidth, 14),
|
Bounds = new Rectangle(Width - authorWidth - margin - xOffset, versionLabel.Bottom + margin, authorWidth, 14),
|
||||||
};
|
};
|
||||||
if (url != null)
|
if (url != null)
|
||||||
{
|
{
|
||||||
@@ -671,11 +672,11 @@ namespace FlaxEditor.Windows
|
|||||||
Editor.Log($"Using plugin code type name: {pluginCodeName}");
|
Editor.Log($"Using plugin code type name: {pluginCodeName}");
|
||||||
|
|
||||||
var oldPluginPath = Path.Combine(extractPath, "ExamplePlugin-master");
|
var oldPluginPath = Path.Combine(extractPath, "ExamplePlugin-master");
|
||||||
var newPluginPath = Path.Combine(extractPath, pluginName);
|
var newPluginPath = Path.Combine(extractPath, pluginCodeName);
|
||||||
Directory.Move(oldPluginPath, newPluginPath);
|
Directory.Move(oldPluginPath, newPluginPath);
|
||||||
|
|
||||||
var oldFlaxProjFile = Path.Combine(newPluginPath, "ExamplePlugin.flaxproj");
|
var oldFlaxProjFile = Path.Combine(newPluginPath, "ExamplePlugin.flaxproj");
|
||||||
var newFlaxProjFile = Path.Combine(newPluginPath, $"{pluginName}.flaxproj");
|
var newFlaxProjFile = Path.Combine(newPluginPath, $"{pluginCodeName}.flaxproj");
|
||||||
File.Move(oldFlaxProjFile, newFlaxProjFile);
|
File.Move(oldFlaxProjFile, newFlaxProjFile);
|
||||||
|
|
||||||
var readme = Path.Combine(newPluginPath, "README.md");
|
var readme = Path.Combine(newPluginPath, "README.md");
|
||||||
@@ -687,7 +688,7 @@ namespace FlaxEditor.Windows
|
|||||||
|
|
||||||
// Flax plugin project file
|
// Flax plugin project file
|
||||||
var flaxPluginProjContents = JsonSerializer.Deserialize<ProjectInfo>(await File.ReadAllTextAsync(newFlaxProjFile));
|
var flaxPluginProjContents = JsonSerializer.Deserialize<ProjectInfo>(await File.ReadAllTextAsync(newFlaxProjFile));
|
||||||
flaxPluginProjContents.Name = pluginName;
|
flaxPluginProjContents.Name = pluginCodeName;
|
||||||
if (!string.IsNullOrEmpty(pluginVersion))
|
if (!string.IsNullOrEmpty(pluginVersion))
|
||||||
flaxPluginProjContents.Version = new Version(pluginVersion);
|
flaxPluginProjContents.Version = new Version(pluginVersion);
|
||||||
if (!string.IsNullOrEmpty(companyName))
|
if (!string.IsNullOrEmpty(companyName))
|
||||||
@@ -751,7 +752,7 @@ namespace FlaxEditor.Windows
|
|||||||
}
|
}
|
||||||
Editor.Log($"Plugin project {pluginName} has successfully been created.");
|
Editor.Log($"Plugin project {pluginName} has successfully been created.");
|
||||||
|
|
||||||
await AddReferenceToProject(pluginName, pluginName);
|
await AddReferenceToProject(pluginCodeName, pluginCodeName);
|
||||||
MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
|
MessageBox.Show($"{pluginName} has been successfully created. Restart editor for changes to take effect.", "Plugin Project Created", MessageBoxButtons.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,8 +776,12 @@ namespace FlaxEditor.Windows
|
|||||||
var pluginModuleScriptPath = Path.Combine(subDir, pluginModuleName + ".Build.cs");
|
var pluginModuleScriptPath = Path.Combine(subDir, pluginModuleName + ".Build.cs");
|
||||||
if (File.Exists(pluginModuleScriptPath))
|
if (File.Exists(pluginModuleScriptPath))
|
||||||
{
|
{
|
||||||
gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");");
|
var text = await File.ReadAllTextAsync(pluginModuleScriptPath);
|
||||||
modifiedAny = true;
|
if (!text.Contains("GameEditorModule", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
gameScriptContents = gameScriptContents.Insert(insertLocation, $"\n options.PublicDependencies.Add(\"{pluginModuleName}\");");
|
||||||
|
modifiedAny = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ namespace FlaxEngine
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
fixed (short* name = Name0)
|
fixed (short* name = Name0)
|
||||||
{
|
|
||||||
return new string((char*)name);
|
return new string((char*)name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,9 +29,7 @@ namespace FlaxEngine
|
|||||||
fixed (short* name = Name0)
|
fixed (short* name = Name0)
|
||||||
{
|
{
|
||||||
fixed (char* p = prefix)
|
fixed (char* p = prefix)
|
||||||
{
|
|
||||||
return Utils.MemoryCompare(new IntPtr(name), new IntPtr(p), (ulong)(prefix.Length * 2)) == 0;
|
return Utils.MemoryCompare(new IntPtr(name), new IntPtr(p), (ulong)(prefix.Length * 2)) == 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,8 +321,7 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
var data = _events.Get(_drawTimeCPU.SelectedSampleIndex);
|
var data = _events.Get(_drawTimeCPU.SelectedSampleIndex);
|
||||||
if (data == null || data.Length == 0)
|
if (data == null || data.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
float totalTimeMs = _drawTimeGPU.SelectedSample;
|
||||||
float totalTimeMs = _drawTimeCPU.SelectedSample;
|
|
||||||
|
|
||||||
// Add rows
|
// Add rows
|
||||||
var rowColor2 = Style.Current.Background * 1.4f;
|
var rowColor2 = Style.Current.Background * 1.4f;
|
||||||
@@ -343,14 +342,19 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
row = new Row
|
row = new Row
|
||||||
{
|
{
|
||||||
Values = new object[6],
|
Values = new object[6],
|
||||||
|
BackgroundColors = new Color[6],
|
||||||
};
|
};
|
||||||
|
for (int k = 0; k < row.BackgroundColors.Length; k++)
|
||||||
|
row.BackgroundColors[k] = Color.Transparent;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// Event
|
// Event
|
||||||
row.Values[0] = name;
|
row.Values[0] = name;
|
||||||
|
|
||||||
// Total (%)
|
// Total (%)
|
||||||
row.Values[1] = (int)(e.Time / totalTimeMs * 1000.0f) / 10.0f;
|
float rowTimePerc = (float)(e.Time / totalTimeMs);
|
||||||
|
row.Values[1] = (int)(rowTimePerc * 1000.0f) / 10.0f;
|
||||||
|
row.BackgroundColors[1] = Color.Red.AlphaMultiplied(Mathf.Min(1, rowTimePerc) * 0.5f);
|
||||||
|
|
||||||
// GPU ms
|
// GPU ms
|
||||||
row.Values[2] = (e.Time * 10000.0f) / 10000.0f;
|
row.Values[2] = (e.Time * 10000.0f) / 10000.0f;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace FlaxEditor.Windows.Profiler
|
|||||||
if (value != LiveRecording)
|
if (value != LiveRecording)
|
||||||
{
|
{
|
||||||
_liveRecordingButton.Checked = value;
|
_liveRecordingButton.Checked = value;
|
||||||
|
OnLiveRecordingChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using FlaxEditor.Gizmo;
|
using FlaxEditor.Gizmo;
|
||||||
using FlaxEditor.Content;
|
using FlaxEditor.Content;
|
||||||
using FlaxEditor.GUI.Tree;
|
using FlaxEditor.GUI.Tree;
|
||||||
@@ -31,6 +32,7 @@ namespace FlaxEditor.Windows
|
|||||||
|
|
||||||
private DragAssets _dragAssets;
|
private DragAssets _dragAssets;
|
||||||
private DragActorType _dragActorType;
|
private DragActorType _dragActorType;
|
||||||
|
private DragScriptItems _dragScriptItems;
|
||||||
private DragHandlers _dragHandlers;
|
private DragHandlers _dragHandlers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -273,6 +275,11 @@ namespace FlaxEditor.Windows
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ValidateDragScriptItem(ScriptItem script)
|
||||||
|
{
|
||||||
|
return Editor.Instance.CodeEditing.Actors.Get(script) != ScriptType.Null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
@@ -380,6 +387,13 @@ namespace FlaxEditor.Windows
|
|||||||
}
|
}
|
||||||
if (_dragActorType.OnDragEnter(data) && result == DragDropEffect.None)
|
if (_dragActorType.OnDragEnter(data) && result == DragDropEffect.None)
|
||||||
return _dragActorType.Effect;
|
return _dragActorType.Effect;
|
||||||
|
if (_dragScriptItems == null)
|
||||||
|
{
|
||||||
|
_dragScriptItems = new DragScriptItems(ValidateDragScriptItem);
|
||||||
|
_dragHandlers.Add(_dragScriptItems);
|
||||||
|
}
|
||||||
|
if (_dragScriptItems.OnDragEnter(data) && result == DragDropEffect.None)
|
||||||
|
return _dragScriptItems.Effect;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -445,6 +459,28 @@ namespace FlaxEditor.Windows
|
|||||||
}
|
}
|
||||||
result = DragDropEffect.Move;
|
result = DragDropEffect.Move;
|
||||||
}
|
}
|
||||||
|
// Drag script item
|
||||||
|
else if (_dragScriptItems != null && _dragScriptItems.HasValidDrag)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _dragScriptItems.Objects.Count; i++)
|
||||||
|
{
|
||||||
|
var item = _dragScriptItems.Objects[i];
|
||||||
|
var actorType = Editor.Instance.CodeEditing.Actors.Get(item);
|
||||||
|
if (actorType != ScriptType.Null)
|
||||||
|
{
|
||||||
|
var actor = actorType.CreateInstance() as Actor;
|
||||||
|
if (actor == null)
|
||||||
|
{
|
||||||
|
Editor.LogWarning("Failed to spawn actor of type " + actorType.TypeName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
actor.Name = actorType.Name;
|
||||||
|
Level.SpawnActor(actor);
|
||||||
|
Editor.Scene.MarkSceneEdited(actor.Scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = DragDropEffect.Move;
|
||||||
|
}
|
||||||
|
|
||||||
_dragHandlers.OnDragDrop(null);
|
_dragHandlers.OnDragDrop(null);
|
||||||
}
|
}
|
||||||
@@ -456,6 +492,7 @@ namespace FlaxEditor.Windows
|
|||||||
{
|
{
|
||||||
_dragAssets = null;
|
_dragAssets = null;
|
||||||
_dragActorType = null;
|
_dragActorType = null;
|
||||||
|
_dragScriptItems = null;
|
||||||
_dragHandlers?.Clear();
|
_dragHandlers?.Clear();
|
||||||
_dragHandlers = null;
|
_dragHandlers = null;
|
||||||
_tree = null;
|
_tree = null;
|
||||||
|
|||||||
84
Source/Engine/Animations/AnimationData.cpp
Normal file
84
Source/Engine/Animations/AnimationData.cpp
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Engine/Core/Types/String.h"
|
#include "Engine/Core/Types/String.h"
|
||||||
#include "Engine/Animations/Curve.h"
|
|
||||||
#include "Engine/Core/Math/Transform.h"
|
#include "Engine/Core/Math/Transform.h"
|
||||||
|
#include "Engine/Animations/Curve.h"
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Single node animation data container.
|
/// Single node animation data container.
|
||||||
@@ -50,19 +50,7 @@ public:
|
|||||||
/// <param name="time">The time to evaluate the curves at.</param>
|
/// <param name="time">The time to evaluate the curves at.</param>
|
||||||
/// <param name="result">The interpolated value from the curve at provided time.</param>
|
/// <param name="result">The interpolated value from the curve at provided time.</param>
|
||||||
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
||||||
void Evaluate(float time, Transform* result, bool loop = true) const
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Evaluates the animation transformation at the specified time.
|
/// Evaluates the animation transformation at the specified time.
|
||||||
@@ -70,29 +58,37 @@ public:
|
|||||||
/// <param name="time">The time to evaluate the curves at.</param>
|
/// <param name="time">The time to evaluate the curves at.</param>
|
||||||
/// <param name="result">The interpolated value from the curve at provided time.</param>
|
/// <param name="result">The interpolated value from the curve at provided time.</param>
|
||||||
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
||||||
void EvaluateAll(float time, Transform* result, bool loop = true) const
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total amount of keyframes in the animation curves.
|
/// Gets the total amount of keyframes in the animation curves.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int32 GetKeyframesCount() const
|
int32 GetKeyframesCount() const;
|
||||||
{
|
|
||||||
return Position.GetKeyframes().Count() + Rotation.GetKeyframes().Count() + Scale.GetKeyframes().Count();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64 GetMemoryUsage() const
|
uint64 GetMemoryUsage() const;
|
||||||
{
|
|
||||||
return NodeName.Length() * sizeof(Char) + Position.GetMemoryUsage() + Rotation.GetMemoryUsage() + Scale.GetMemoryUsage();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skeleton nodes animation data container. Includes metadata about animation sampling, duration and node animations curves.
|
/// Skeleton nodes animation data container. Includes metadata about animation sampling, duration and node animations curves.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -111,7 +107,7 @@ struct AnimationData
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables root motion extraction support from this animation.
|
/// Enables root motion extraction support from this animation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool EnableRootMotion = false;
|
AnimationRootMotionFlags RootMotionFlags = AnimationRootMotionFlags::None;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The animation name.
|
/// The animation name.
|
||||||
@@ -140,49 +136,23 @@ public:
|
|||||||
return static_cast<float>(Duration / FramesPerSecond);
|
return static_cast<float>(Duration / FramesPerSecond);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64 GetMemoryUsage() const
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total amount of keyframes in the all animation channels.
|
/// Gets the total amount of keyframes in the all animation channels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int32 GetKeyframesCount() const
|
int32 GetKeyframesCount() const;
|
||||||
{
|
|
||||||
int32 result = 0;
|
NodeAnimationData* GetChannel(const StringView& name);
|
||||||
for (int32 i = 0; i < Channels.Count(); i++)
|
|
||||||
result += Channels[i].GetKeyframesCount();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange.
|
/// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="other">The other object.</param>
|
/// <param name="other">The other object.</param>
|
||||||
void Swap(AnimationData& other)
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases data.
|
/// Releases data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Dispose()
|
void Dispose();
|
||||||
{
|
|
||||||
Name.Clear();
|
|
||||||
Duration = 0.0;
|
|
||||||
FramesPerSecond = 0.0;
|
|
||||||
RootNodeName.Clear();
|
|
||||||
EnableRootMotion = false;
|
|
||||||
Channels.Resize(0);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace
|
|||||||
AnimationsService AnimationManagerInstance;
|
AnimationsService AnimationManagerInstance;
|
||||||
TaskGraphSystem* Animations::System = nullptr;
|
TaskGraphSystem* Animations::System = nullptr;
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
Delegate<Asset*, ScriptingObject*, uint32, uint32> Animations::DebugFlow;
|
Delegate<Animations::DebugFlowInfo> Animations::DebugFlow;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
AnimEvent::AnimEvent(const SpawnParams& params)
|
AnimEvent::AnimEvent(const SpawnParams& params)
|
||||||
@@ -127,7 +127,7 @@ void AnimationsSystem::Execute(TaskGraph* graph)
|
|||||||
#if USE_EDITOR
|
#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 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())
|
if (Animations::DebugFlow.IsBinded())
|
||||||
Animations::DebugFlow(nullptr, nullptr, 0, 0);
|
Animations::DebugFlow(Animations::DebugFlowInfo());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Schedule work to update all animated models in async
|
// Schedule work to update all animated models in async
|
||||||
|
|||||||
@@ -22,8 +22,25 @@ API_CLASS(Static) class FLAXENGINE_API Animations
|
|||||||
API_FIELD(ReadOnly) static TaskGraphSystem* System;
|
API_FIELD(ReadOnly) static TaskGraphSystem* System;
|
||||||
|
|
||||||
#if USE_EDITOR
|
#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
|
// Data wrapper for the debug flow information.
|
||||||
API_EVENT() static Delegate<Asset*, ScriptingObject*, uint32, uint32> DebugFlow;
|
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<DebugFlowInfo> DebugFlow;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -730,7 +730,7 @@ public:
|
|||||||
void TransformTime(float timeScale, float timeOffset)
|
void TransformTime(float timeScale, float timeOffset)
|
||||||
{
|
{
|
||||||
for (int32 i = 0; i < _keyframes.Count(); i++)
|
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
|
uint64 GetMemoryUsage() const
|
||||||
|
|||||||
@@ -99,10 +99,7 @@ void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
|||||||
|
|
||||||
void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
||||||
{
|
{
|
||||||
bucket.StateMachine.LastUpdateFrame = 0;
|
Platform::MemoryClear(&bucket.StateMachine, sizeof(bucket.StateMachine));
|
||||||
bucket.StateMachine.CurrentState = nullptr;
|
|
||||||
bucket.StateMachine.ActiveTransition = nullptr;
|
|
||||||
bucket.StateMachine.TransitionPosition = 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
||||||
@@ -160,7 +157,7 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
|||||||
if (_rootNode->Values.Count() < 1)
|
if (_rootNode->Values.Count() < 1)
|
||||||
{
|
{
|
||||||
_rootNode->Values.Resize(1);
|
_rootNode->Values.Resize(1);
|
||||||
_rootNode->Values[0] = (int32)RootMotionMode::NoExtraction;
|
_rootNode->Values[0] = (int32)RootMotionExtraction::NoExtraction;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// Animation
|
// Animation
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ void AnimGraphExecutor::initRuntime()
|
|||||||
void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value)
|
void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value)
|
||||||
{
|
{
|
||||||
#if USE_CSHARP
|
#if USE_CSHARP
|
||||||
auto& context = Context.Get();
|
auto& context = *Context.Get();
|
||||||
if (context.ValueCache.TryGet(boxBase, value))
|
if (context.ValueCache.TryGet(boxBase, value))
|
||||||
return;
|
return;
|
||||||
auto box = (AnimGraphBox*)boxBase;
|
auto box = (AnimGraphBox*)boxBase;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i);
|
extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i);
|
||||||
|
|
||||||
ThreadLocal<AnimGraphContext> AnimGraphExecutor::Context;
|
ThreadLocal<AnimGraphContext*> AnimGraphExecutor::Context;
|
||||||
|
|
||||||
Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const
|
Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const
|
||||||
{
|
{
|
||||||
@@ -39,6 +39,7 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32
|
|||||||
void AnimGraphInstanceData::Clear()
|
void AnimGraphInstanceData::Clear()
|
||||||
{
|
{
|
||||||
ClearState();
|
ClearState();
|
||||||
|
Slots.Clear();
|
||||||
Parameters.Resize(0);
|
Parameters.Resize(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ void AnimGraphInstanceData::ClearState()
|
|||||||
RootMotion = Transform::Identity;
|
RootMotion = Transform::Identity;
|
||||||
State.Resize(0);
|
State.Resize(0);
|
||||||
NodesPose.Resize(0);
|
NodesPose.Resize(0);
|
||||||
Slots.Clear();
|
TraceEvents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphInstanceData::Invalidate()
|
void AnimGraphInstanceData::Invalidate()
|
||||||
@@ -103,7 +104,7 @@ AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(Ani
|
|||||||
|
|
||||||
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
|
AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor)
|
||||||
{
|
{
|
||||||
auto& context = AnimGraphExecutor::Context.Get();
|
auto& context = *AnimGraphExecutor::Context.Get();
|
||||||
const int32 count = executor->_skeletonNodesCount;
|
const int32 count = executor->_skeletonNodesCount;
|
||||||
if (context.PoseCacheSize == context.PoseCache.Count())
|
if (context.PoseCacheSize == context.PoseCache.Count())
|
||||||
context.PoseCache.AddOne();
|
context.PoseCache.AddOne();
|
||||||
@@ -203,17 +204,21 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
|||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
auto& skeleton = _graph.BaseModel->Skeleton;
|
auto& skeleton = _graph.BaseModel->Skeleton;
|
||||||
auto& context = Context.Get();
|
auto& contextPtr = Context.Get();
|
||||||
|
if (!contextPtr)
|
||||||
|
contextPtr = New<AnimGraphContext>();
|
||||||
|
auto& context = *contextPtr;
|
||||||
{
|
{
|
||||||
ANIM_GRAPH_PROFILE_EVENT("Init");
|
ANIM_GRAPH_PROFILE_EVENT("Init");
|
||||||
|
|
||||||
// Init data from base model
|
// Init data from base model
|
||||||
_skeletonNodesCount = skeleton.Nodes.Count();
|
_skeletonNodesCount = skeleton.Nodes.Count();
|
||||||
_rootMotionMode = (RootMotionMode)(int32)_graph._rootNode->Values[0];
|
_rootMotionMode = (RootMotionExtraction)(int32)_graph._rootNode->Values[0];
|
||||||
|
|
||||||
// Prepare context data for the evaluation
|
// Prepare context data for the evaluation
|
||||||
context.GraphStack.Clear();
|
context.GraphStack.Clear();
|
||||||
context.GraphStack.Push((Graph*)&_graph);
|
context.GraphStack.Push((Graph*)&_graph);
|
||||||
|
context.NodePath.Clear();
|
||||||
context.Data = &data;
|
context.Data = &data;
|
||||||
context.DeltaTime = dt;
|
context.DeltaTime = dt;
|
||||||
context.CurrentFrameIndex = ++data.CurrentFrame;
|
context.CurrentFrameIndex = ++data.CurrentFrame;
|
||||||
@@ -238,6 +243,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
|||||||
}
|
}
|
||||||
for (auto& e : data.ActiveEvents)
|
for (auto& e : data.ActiveEvents)
|
||||||
e.Hit = false;
|
e.Hit = false;
|
||||||
|
data.TraceEvents.Clear();
|
||||||
|
|
||||||
// Init empty nodes data
|
// Init empty nodes data
|
||||||
context.EmptyNodes.RootMotion = Transform::Identity;
|
context.EmptyNodes.RootMotion = Transform::Identity;
|
||||||
@@ -282,6 +288,20 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if !BUILD_RELEASE
|
||||||
|
{
|
||||||
|
// Perform sanity check on nodes pose to prevent crashes due to NaNs
|
||||||
|
bool anyInvalid = animResult->RootMotion.IsNanOrInfinity();
|
||||||
|
for (int32 i = 0; i < animResult->Nodes.Count(); i++)
|
||||||
|
anyInvalid |= animResult->Nodes.Get()[i].IsNanOrInfinity();
|
||||||
|
if (anyInvalid)
|
||||||
|
{
|
||||||
|
LOG(Error, "Animated Model pose contains NaNs due to animations sampling/blending bug.");
|
||||||
|
context.Data = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
SkeletonData* animResultSkeleton = &skeleton;
|
SkeletonData* animResultSkeleton = &skeleton;
|
||||||
|
|
||||||
// Retarget animation when using output pose from other skeleton
|
// Retarget animation when using output pose from other skeleton
|
||||||
@@ -361,12 +381,12 @@ void AnimGraphExecutor::GetInputValue(Box* box, Value& result)
|
|||||||
|
|
||||||
AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes()
|
AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes()
|
||||||
{
|
{
|
||||||
return &Context.Get().EmptyNodes;
|
return &Context.Get()->EmptyNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const
|
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);
|
Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount);
|
||||||
nodes->RootMotion = emptyNodes.RootMotion;
|
nodes->RootMotion = emptyNodes.RootMotion;
|
||||||
nodes->Position = emptyNodes.Position;
|
nodes->Position = emptyNodes.Position;
|
||||||
@@ -388,7 +408,7 @@ void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* g
|
|||||||
|
|
||||||
VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
|
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
|
// Check if graph is looped or is too deep
|
||||||
if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK)
|
if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK)
|
||||||
@@ -408,7 +428,15 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
|
|||||||
context.CallStack.Add(caller);
|
context.CallStack.Add(caller);
|
||||||
|
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
Animations::DebugFlow(_graph._owner, context.Data->Object, box->GetParent<Node>()->ID, box->ID);
|
Animations::DebugFlowInfo flowInfo;
|
||||||
|
flowInfo.Asset = _graph._owner;
|
||||||
|
flowInfo.Instance = context.Data->Object;
|
||||||
|
flowInfo.NodeId = box->GetParent<Node>()->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
|
#endif
|
||||||
|
|
||||||
// Call per group custom processing event
|
// Call per group custom processing event
|
||||||
@@ -425,6 +453,6 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box)
|
|||||||
|
|
||||||
VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const
|
VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const
|
||||||
{
|
{
|
||||||
auto& context = Context.Get();
|
auto& context = *Context.Get();
|
||||||
return context.GraphStack.Peek();
|
return context.GraphStack.Peek();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ enum class BoneTransformMode
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The animated model root motion mode.
|
/// The animated model root motion extraction modes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
enum class RootMotionMode
|
enum class RootMotionExtraction
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Don't extract nor apply the root motion.
|
/// Don't extract nor apply the root motion.
|
||||||
@@ -129,6 +129,8 @@ public:
|
|||||||
UseDefaultRule = 4,
|
UseDefaultRule = 4,
|
||||||
InterruptionRuleRechecking = 8,
|
InterruptionRuleRechecking = 8,
|
||||||
InterruptionInstant = 16,
|
InterruptionInstant = 16,
|
||||||
|
InterruptionSourceState = 32,
|
||||||
|
InterruptionDestinationState = 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -197,6 +199,24 @@ struct FLAXENGINE_API AnimGraphSlot
|
|||||||
float BlendOutTime = 0.0f;
|
float BlendOutTime = 0.0f;
|
||||||
int32 LoopCount = 0;
|
int32 LoopCount = 0;
|
||||||
bool Pause = false;
|
bool Pause = false;
|
||||||
|
bool Reset = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
API_STRUCT(NoDefault) struct FLAXENGINE_API AnimGraphTraceEvent
|
||||||
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent);
|
||||||
|
|
||||||
|
// Contextual asset used. For example, sampled animation.
|
||||||
|
API_FIELD() Asset* Asset = nullptr;
|
||||||
|
// Generic value contextual to playback type (eg. animation sample position).
|
||||||
|
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] = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -240,7 +260,10 @@ public:
|
|||||||
uint64 LastUpdateFrame;
|
uint64 LastUpdateFrame;
|
||||||
AnimGraphNode* CurrentState;
|
AnimGraphNode* CurrentState;
|
||||||
AnimGraphStateTransition* ActiveTransition;
|
AnimGraphStateTransition* ActiveTransition;
|
||||||
|
AnimGraphStateTransition* BaseTransition;
|
||||||
|
AnimGraphNode* BaseTransitionState;
|
||||||
float TransitionPosition;
|
float TransitionPosition;
|
||||||
|
float BaseTransitionPosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SlotBucket
|
struct SlotBucket
|
||||||
@@ -357,6 +380,12 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void InvokeAnimEvents();
|
void InvokeAnimEvents();
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Anim Graph logic tracing feature that allows to collect insights of animations sampling and skeleton poses operations.
|
||||||
|
bool EnableTracing = false;
|
||||||
|
// Trace events collected when using EnableTracing option.
|
||||||
|
Array<AnimGraphTraceEvent> TraceEvents;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct OutgoingEvent
|
struct OutgoingEvent
|
||||||
{
|
{
|
||||||
@@ -769,10 +798,13 @@ struct AnimGraphContext
|
|||||||
AnimGraphTransitionData TransitionData;
|
AnimGraphTransitionData TransitionData;
|
||||||
Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack;
|
Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack;
|
||||||
Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack;
|
Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack;
|
||||||
|
Array<uint32, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK> > NodePath;
|
||||||
Dictionary<VisjectExecutor::Node*, VisjectExecutor::Graph*> Functions;
|
Dictionary<VisjectExecutor::Node*, VisjectExecutor::Graph*> Functions;
|
||||||
ChunkedArray<AnimGraphImpulse, 256> PoseCache;
|
ChunkedArray<AnimGraphImpulse, 256> PoseCache;
|
||||||
int32 PoseCacheSize;
|
int32 PoseCacheSize;
|
||||||
Dictionary<VisjectExecutor::Box*, Variant> ValueCache;
|
Dictionary<VisjectExecutor::Box*, Variant> ValueCache;
|
||||||
|
|
||||||
|
AnimGraphTraceEvent& AddTraceEvent(const AnimGraphNode* node);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -783,11 +815,11 @@ class AnimGraphExecutor : public VisjectExecutor
|
|||||||
friend AnimGraphNode;
|
friend AnimGraphNode;
|
||||||
private:
|
private:
|
||||||
AnimGraph& _graph;
|
AnimGraph& _graph;
|
||||||
RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction;
|
RootMotionExtraction _rootMotionMode = RootMotionExtraction::NoExtraction;
|
||||||
int32 _skeletonNodesCount = 0;
|
int32 _skeletonNodesCount = 0;
|
||||||
|
|
||||||
// Per-thread context to allow async execution
|
// Per-thread context to allow async execution
|
||||||
static ThreadLocal<AnimGraphContext> Context;
|
static ThreadLocal<AnimGraphContext*> Context;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -836,7 +868,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children).
|
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can be used to reset the animation state of the nested graph (including children).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
|
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
|
||||||
|
|
||||||
@@ -864,6 +896,8 @@ 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, 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 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 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);
|
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ namespace
|
|||||||
base += additive;
|
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++)
|
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||||
{
|
{
|
||||||
nodes->Nodes[i].Orientation.Normalize();
|
nodes->Nodes[i].Orientation.Normalize();
|
||||||
}
|
}
|
||||||
if (rootMotionMode != RootMotionMode::NoExtraction)
|
if (rootMotionMode != RootMotionExtraction::NoExtraction)
|
||||||
{
|
{
|
||||||
nodes->RootMotion.Orientation.Normalize();
|
nodes->RootMotion.Orientation.Normalize();
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,17 @@ void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData
|
|||||||
node = value;
|
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)
|
int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
|
||||||
{
|
{
|
||||||
// TODO: cache the root node index (use dictionary with Animation* -> int32 for fast lookups)
|
// 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)
|
if (anim->Events.Count() == 0)
|
||||||
return;
|
return;
|
||||||
ANIM_GRAPH_PROFILE_EVENT("Events");
|
ANIM_GRAPH_PROFILE_EVENT("Events");
|
||||||
auto& context = Context.Get();
|
auto& context = *Context.Get();
|
||||||
float eventTimeMin = animPrevPos;
|
float eventTimeMin = animPrevPos;
|
||||||
float eventTimeMax = animPos;
|
float eventTimeMax = animPos;
|
||||||
if (loop && context.DeltaTime * speed < 0)
|
if (loop && context.DeltaTime * speed < 0)
|
||||||
@@ -219,6 +230,15 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
|||||||
const float animPos = GetAnimSamplePos(length, anim, pos, speed);
|
const float animPos = GetAnimSamplePos(length, anim, pos, speed);
|
||||||
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
|
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
|
||||||
|
|
||||||
|
// Add to trace
|
||||||
|
auto& context = *Context.Get();
|
||||||
|
if (context.Data->EnableTracing)
|
||||||
|
{
|
||||||
|
auto& trace = context.AddTraceEvent(node);
|
||||||
|
trace.Asset = anim;
|
||||||
|
trace.Value = animPos;
|
||||||
|
}
|
||||||
|
|
||||||
// Evaluate nested animations
|
// Evaluate nested animations
|
||||||
bool hasNested = false;
|
bool hasNested = false;
|
||||||
if (anim->NestedAnims.Count() != 0)
|
if (anim->NestedAnims.Count() != 0)
|
||||||
@@ -303,16 +323,21 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle root motion
|
// 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
|
// 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 int32 rootNodeIndex = GetRootNodeIndex(anim);
|
||||||
const Transform& refPose = emptyNodes->Nodes[rootNodeIndex];
|
const Transform& refPose = emptyNodes->Nodes[rootNodeIndex];
|
||||||
Transform& rootNode = nodes->Nodes[rootNodeIndex];
|
Transform& rootNode = nodes->Nodes[rootNodeIndex];
|
||||||
Transform& dstNode = nodes->RootMotion;
|
Transform& dstNode = nodes->RootMotion;
|
||||||
Transform srcNode = Transform::Identity;
|
Transform srcNode = Transform::Identity;
|
||||||
const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex];
|
const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex];
|
||||||
if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
|
if (_rootMotionMode == RootMotionExtraction::Enable && nodeToChannel != -1)
|
||||||
{
|
{
|
||||||
// Get the root bone transformation
|
// Get the root bone transformation
|
||||||
Transform rootBefore = refPose;
|
Transform rootBefore = refPose;
|
||||||
@@ -336,16 +361,20 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
|||||||
// Complex motion calculation to preserve the looped movement
|
// Complex motion calculation to preserve the looped movement
|
||||||
// (end - before + now - begin)
|
// (end - before + now - begin)
|
||||||
// It sums the motion since the last update to anim end and since the start to now
|
// 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;
|
if (motionPosition)
|
||||||
srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
|
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;
|
//srcNode.Orientation = Quaternion::Identity;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Simple motion delta
|
// Simple motion delta
|
||||||
// (now - before)
|
// (now - before)
|
||||||
srcNode.Translation = rootNode.Translation - rootBefore.Translation;
|
if (motionPosition)
|
||||||
srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
|
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)
|
// 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)
|
||||||
@@ -359,28 +388,40 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove root node motion after extraction
|
// Remove root node motion after extraction (only extracted components)
|
||||||
rootNode = refPose;
|
if (motionPosition)
|
||||||
|
rootNode.Translation = refPose.Translation * motionPositionMask + rootNode.Translation * (Vector3::One - motionPositionMask);
|
||||||
|
if (motionRotation)
|
||||||
|
rootNode.Orientation = refPose.Orientation;
|
||||||
|
|
||||||
// Blend root motion
|
// Blend root motion
|
||||||
if (mode == ProcessAnimationMode::BlendAdditive)
|
if (mode == ProcessAnimationMode::BlendAdditive)
|
||||||
{
|
{
|
||||||
dstNode.Translation += srcNode.Translation * weight;
|
if (motionPosition)
|
||||||
BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight);
|
dstNode.Translation += srcNode.Translation * weight * motionPositionMask;
|
||||||
|
if (motionRotation)
|
||||||
|
BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight);
|
||||||
}
|
}
|
||||||
else if (mode == ProcessAnimationMode::Add)
|
else if (mode == ProcessAnimationMode::Add)
|
||||||
{
|
{
|
||||||
dstNode.Translation += srcNode.Translation * weight;
|
if (motionPosition)
|
||||||
dstNode.Orientation += srcNode.Orientation * weight;
|
dstNode.Translation += srcNode.Translation * weight * motionPositionMask;
|
||||||
|
if (motionRotation)
|
||||||
|
dstNode.Orientation += srcNode.Orientation * weight;
|
||||||
}
|
}
|
||||||
else if (weighted)
|
else if (weighted)
|
||||||
{
|
{
|
||||||
dstNode.Translation = srcNode.Translation * weight;
|
if (motionPosition)
|
||||||
dstNode.Orientation = srcNode.Orientation * weight;
|
dstNode.Translation = srcNode.Translation * weight * motionPositionMask;
|
||||||
|
if (motionRotation)
|
||||||
|
dstNode.Orientation = srcNode.Orientation * weight;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dstNode = srcNode;
|
if (motionPosition)
|
||||||
|
dstNode.Translation = srcNode.Translation * motionPositionMask;
|
||||||
|
if (motionRotation)
|
||||||
|
dstNode.Orientation = srcNode.Orientation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,10 +501,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
|
|||||||
{
|
{
|
||||||
ANIM_GRAPH_PROFILE_EVENT("Blend Pose");
|
ANIM_GRAPH_PROFILE_EVENT("Blend Pose");
|
||||||
|
|
||||||
|
if (isnan(alpha) || isinf(alpha))
|
||||||
|
alpha = 0;
|
||||||
|
alpha = Math::Saturate(alpha);
|
||||||
alpha = AlphaBlend::Process(alpha, alphaMode);
|
alpha = AlphaBlend::Process(alpha, alphaMode);
|
||||||
|
|
||||||
const auto nodes = node->GetNodes(this);
|
const auto nodes = node->GetNodes(this);
|
||||||
|
|
||||||
auto nodesA = static_cast<AnimGraphImpulse*>(poseA.AsPointer);
|
auto nodesA = static_cast<AnimGraphImpulse*>(poseA.AsPointer);
|
||||||
auto nodesB = static_cast<AnimGraphImpulse*>(poseB.AsPointer);
|
auto nodesB = static_cast<AnimGraphImpulse*>(poseB.AsPointer);
|
||||||
if (!ANIM_GRAPH_IS_VALID_PTR(poseA))
|
if (!ANIM_GRAPH_IS_VALID_PTR(poseA))
|
||||||
@@ -482,34 +525,53 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
|
Variant AnimGraphExecutor::SampleState(AnimGraphContext& context, const AnimGraphNode* state)
|
||||||
{
|
{
|
||||||
// Prepare
|
|
||||||
auto& data = state->Data.State;
|
auto& data = state->Data.State;
|
||||||
if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr)
|
if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr)
|
||||||
{
|
|
||||||
// Invalid state graph
|
|
||||||
return Value::Null;
|
return Value::Null;
|
||||||
|
|
||||||
|
// Add to trace
|
||||||
|
if (context.Data->EnableTracing)
|
||||||
|
{
|
||||||
|
auto& trace = context.AddTraceEvent(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
ANIM_GRAPH_PROFILE_EVENT("Evaluate State");
|
ANIM_GRAPH_PROFILE_EVENT("Evaluate State");
|
||||||
|
context.NodePath.Add(state->ID);
|
||||||
// Evaluate state
|
|
||||||
auto rootNode = data.Graph->GetRootNode();
|
auto rootNode = data.Graph->GetRootNode();
|
||||||
auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]);
|
auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]);
|
||||||
|
context.NodePath.Pop();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
|
void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition)
|
||||||
|
{
|
||||||
|
// Reset transition
|
||||||
|
stateMachineBucket.ActiveTransition = transition;
|
||||||
|
stateMachineBucket.TransitionPosition = 0.0f;
|
||||||
|
|
||||||
|
// End base transition
|
||||||
|
if (stateMachineBucket.BaseTransition)
|
||||||
|
{
|
||||||
|
ResetBuckets(context, stateMachineBucket.BaseTransitionState->Data.State.Graph);
|
||||||
|
stateMachineBucket.BaseTransition = nullptr;
|
||||||
|
stateMachineBucket.BaseTransitionState = nullptr;
|
||||||
|
stateMachineBucket.BaseTransitionPosition = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState)
|
||||||
{
|
{
|
||||||
int32 transitionIndex = 0;
|
int32 transitionIndex = 0;
|
||||||
|
const AnimGraphNode::StateBaseData& stateData = state->Data.State;
|
||||||
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
|
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
|
||||||
{
|
{
|
||||||
const uint16 idx = stateData.Transitions[transitionIndex];
|
const uint16 idx = stateData.Transitions[transitionIndex];
|
||||||
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
|
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
|
||||||
auto& transition = stateMachineData.Graph->StateTransitions[idx];
|
auto& transition = stateMachineData.Graph->StateTransitions[idx];
|
||||||
if (transition.Destination == stateMachineBucket.CurrentState)
|
if (transition.Destination == state || transition.Destination == ignoreState)
|
||||||
{
|
{
|
||||||
// Ignore transition to the current state
|
// Ignore transition to the current state
|
||||||
transitionIndex++;
|
transitionIndex++;
|
||||||
@@ -517,7 +579,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate source state transition data (position, length, etc.)
|
// Evaluate source state transition data (position, length, etc.)
|
||||||
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
|
const Value sourceStatePtr = SampleState(context, state);
|
||||||
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
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))
|
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
|
||||||
{
|
{
|
||||||
@@ -538,6 +600,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
if (transition.RuleGraph && !useDefaultRule)
|
if (transition.RuleGraph && !useDefaultRule)
|
||||||
{
|
{
|
||||||
// Execute transition rule
|
// Execute transition rule
|
||||||
|
ANIM_GRAPH_PROFILE_EVENT("Rule");
|
||||||
auto rootNode = transition.RuleGraph->GetRootNode();
|
auto rootNode = transition.RuleGraph->GetRootNode();
|
||||||
ASSERT(rootNode);
|
ASSERT(rootNode);
|
||||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||||
@@ -560,10 +623,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
canEnter = true;
|
canEnter = true;
|
||||||
if (canEnter)
|
if (canEnter)
|
||||||
{
|
{
|
||||||
// Start transition
|
return &transition;
|
||||||
stateMachineBucket.ActiveTransition = &transition;
|
|
||||||
stateMachineBucket.TransitionPosition = 0.0f;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip after Solo transition
|
// Skip after Solo transition
|
||||||
@@ -573,6 +633,18 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
|
|||||||
|
|
||||||
transitionIndex++;
|
transitionIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No transition
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
|
||||||
|
{
|
||||||
|
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState);
|
||||||
|
if (transition)
|
||||||
|
{
|
||||||
|
InitStateTransition(context, stateMachineBucket, transition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
||||||
@@ -604,7 +676,7 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
|||||||
|
|
||||||
void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
|
void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value)
|
||||||
{
|
{
|
||||||
auto& context = Context.Get();
|
auto& context = *Context.Get();
|
||||||
switch (node->TypeID)
|
switch (node->TypeID)
|
||||||
{
|
{
|
||||||
// Get
|
// Get
|
||||||
@@ -715,7 +787,7 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu
|
|||||||
|
|
||||||
void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value)
|
void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value)
|
||||||
{
|
{
|
||||||
auto& context = Context.Get();
|
auto& context = *Context.Get();
|
||||||
auto node = (AnimGraphNode*)nodeBase;
|
auto node = (AnimGraphNode*)nodeBase;
|
||||||
switch (node->TypeID)
|
switch (node->TypeID)
|
||||||
{
|
{
|
||||||
@@ -739,7 +811,7 @@ void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value
|
|||||||
|
|
||||||
void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, 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))
|
if (context.ValueCache.TryGet(boxBase, value))
|
||||||
return;
|
return;
|
||||||
auto box = (AnimGraphBox*)boxBase;
|
auto box = (AnimGraphBox*)boxBase;
|
||||||
@@ -1101,6 +1173,17 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
{
|
{
|
||||||
const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
|
const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
|
||||||
auto mask = node->Assets[0].As<SkeletonMask>();
|
auto mask = node->Assets[0].As<SkeletonMask>();
|
||||||
|
auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node.
|
||||||
|
|
||||||
|
// Check if have some mask asset connected with the mask node
|
||||||
|
if (maskAssetBox->HasConnection())
|
||||||
|
{
|
||||||
|
const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null);
|
||||||
|
|
||||||
|
// Use the mask connected with this node instead of default mask asset
|
||||||
|
if (assetBoxValue != Value::Null)
|
||||||
|
mask = (SkeletonMask*)assetBoxValue.AsAsset;
|
||||||
|
}
|
||||||
|
|
||||||
// Only A or missing/invalid mask
|
// Only A or missing/invalid mask
|
||||||
if (Math::NearEqual(alpha, 0.0f, ANIM_GRAPH_BLEND_THRESHOLD) || mask == nullptr || mask->WaitForLoaded())
|
if (Math::NearEqual(alpha, 0.0f, ANIM_GRAPH_BLEND_THRESHOLD) || mask == nullptr || mask->WaitForLoaded())
|
||||||
@@ -1367,33 +1450,29 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use 1D blend if points are on the same line (degenerated triangle)
|
// Use 1D blend if points are on the same line (degenerated triangle)
|
||||||
// TODO: simplify this code
|
struct BlendData
|
||||||
|
{
|
||||||
|
float AlphaX, AlphaY;
|
||||||
|
Animation* AnimA, *AnimB;
|
||||||
|
const Float4* AnimAd, *AnimBd;
|
||||||
|
};
|
||||||
|
BlendData blendData;
|
||||||
if (v1.Y >= v0.Y)
|
if (v1.Y >= v0.Y)
|
||||||
{
|
{
|
||||||
if (p.Y < v0.Y && v1.Y >= v0.Y)
|
if (p.Y < v0.Y && v1.Y >= v0.Y)
|
||||||
{
|
blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData };
|
||||||
const float alpha = p.Y / v0.Y;
|
|
||||||
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData };
|
||||||
const float alpha = (p.Y - v0.Y) / (v1.Y - v0.Y);
|
|
||||||
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, cAnim, bData.W, cData.W, alpha);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (p.Y < v1.Y)
|
if (p.Y < v1.Y)
|
||||||
{
|
blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData };
|
||||||
const float alpha = p.Y / v1.Y;
|
|
||||||
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, cAnim, aData.W, cData.W, alpha);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData };
|
||||||
const float alpha = (p.Y - v1.Y) / (v0.Y - v1.Y);
|
|
||||||
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, cAnim, bAnim, cData.W, bData.W, alpha);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY;
|
||||||
|
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, blendData.AnimA, blendData.AnimB, blendData.AnimAd->W, blendData.AnimBd->W, alpha);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1505,10 +1584,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
|
|
||||||
// Blend two animations
|
// Blend two animations
|
||||||
{
|
{
|
||||||
const float alpha = Math::Saturate(bucket.TransitionPosition / blendDuration);
|
const float alpha = bucket.TransitionPosition / blendDuration;
|
||||||
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
|
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
|
||||||
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
|
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
|
||||||
|
|
||||||
value = Blend(node, valueA, valueB, alpha, mode);
|
value = Blend(node, valueA, valueB, alpha, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1614,22 +1692,23 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
|
|
||||||
// Enter to the first state pointed by the Entry node (without transitions)
|
// Enter to the first state pointed by the Entry node (without transitions)
|
||||||
bucket.CurrentState = data.Graph->GetRootNode();
|
bucket.CurrentState = data.Graph->GetRootNode();
|
||||||
bucket.ActiveTransition = nullptr;
|
InitStateTransition(context, bucket);
|
||||||
bucket.TransitionPosition = 0.0f;
|
|
||||||
|
|
||||||
// Reset all state buckets pof the graphs and nodes included inside the state machine
|
// Reset all state buckets of the graphs and nodes included inside the state machine
|
||||||
ResetBuckets(context, data.Graph);
|
ResetBuckets(context, data.Graph);
|
||||||
}
|
}
|
||||||
#define END_TRANSITION() \
|
#define END_TRANSITION() \
|
||||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
|
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
|
||||||
bucket.CurrentState = bucket.ActiveTransition->Destination; \
|
bucket.CurrentState = bucket.ActiveTransition->Destination; \
|
||||||
bucket.ActiveTransition = nullptr; \
|
InitStateTransition(context, bucket)
|
||||||
bucket.TransitionPosition = 0.0f
|
|
||||||
|
context.NodePath.Push(node->ID);
|
||||||
|
|
||||||
// Update the active transition
|
// Update the active transition
|
||||||
if (bucket.ActiveTransition)
|
if (bucket.ActiveTransition)
|
||||||
{
|
{
|
||||||
bucket.TransitionPosition += context.DeltaTime;
|
bucket.TransitionPosition += context.DeltaTime;
|
||||||
|
ASSERT(bucket.CurrentState);
|
||||||
|
|
||||||
// Check for transition end
|
// Check for transition end
|
||||||
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
|
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
|
||||||
@@ -1637,38 +1716,70 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
END_TRANSITION();
|
END_TRANSITION();
|
||||||
}
|
}
|
||||||
// Check for transition interruption
|
// Check for transition interruption
|
||||||
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
|
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking) &&
|
||||||
|
EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule) &&
|
||||||
|
bucket.ActiveTransition->RuleGraph)
|
||||||
{
|
{
|
||||||
const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
|
// Execute transition rule
|
||||||
if (bucket.ActiveTransition->RuleGraph && !useDefaultRule)
|
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
|
||||||
|
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||||
{
|
{
|
||||||
// Execute transition rule
|
bool cancelTransition = false;
|
||||||
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
|
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
||||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
|
||||||
{
|
{
|
||||||
bool cancelTransition = false;
|
cancelTransition = true;
|
||||||
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Blend back to the source state (remove currently applied delta and rewind transition)
|
||||||
|
bucket.TransitionPosition -= context.DeltaTime;
|
||||||
|
bucket.TransitionPosition -= context.DeltaTime;
|
||||||
|
if (bucket.TransitionPosition <= ZeroTolerance)
|
||||||
{
|
{
|
||||||
cancelTransition = true;
|
cancelTransition = true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Blend back to the source state (remove currently applied delta and rewind transition)
|
|
||||||
bucket.TransitionPosition -= context.DeltaTime;
|
|
||||||
bucket.TransitionPosition -= context.DeltaTime;
|
|
||||||
if (bucket.TransitionPosition <= ZeroTolerance)
|
|
||||||
{
|
|
||||||
cancelTransition = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cancelTransition)
|
|
||||||
{
|
|
||||||
// Go back to the source state
|
|
||||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
|
||||||
bucket.ActiveTransition = nullptr;
|
|
||||||
bucket.TransitionPosition = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (cancelTransition)
|
||||||
|
{
|
||||||
|
// Go back to the source state
|
||||||
|
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
||||||
|
InitStateTransition(context, bucket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionSourceState))
|
||||||
|
{
|
||||||
|
// Try to interrupt with any other transition in the source state (except the current transition)
|
||||||
|
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.CurrentState, bucket.ActiveTransition->Destination))
|
||||||
|
{
|
||||||
|
// Change active transition to the interrupted one
|
||||||
|
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
||||||
|
{
|
||||||
|
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
|
||||||
|
bucket.BaseTransition = bucket.ActiveTransition;
|
||||||
|
bucket.BaseTransitionState = bucket.CurrentState;
|
||||||
|
bucket.BaseTransitionPosition = bucket.TransitionPosition;
|
||||||
|
}
|
||||||
|
bucket.ActiveTransition = transition;
|
||||||
|
bucket.TransitionPosition = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionDestinationState))
|
||||||
|
{
|
||||||
|
// Try to interrupt with any other transition in the destination state (except the transition back to the current state if exists)
|
||||||
|
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.ActiveTransition->Destination, bucket.CurrentState))
|
||||||
|
{
|
||||||
|
// Change active transition to the interrupted one
|
||||||
|
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
||||||
|
{
|
||||||
|
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
|
||||||
|
bucket.BaseTransition = bucket.ActiveTransition;
|
||||||
|
bucket.BaseTransitionState = bucket.CurrentState;
|
||||||
|
bucket.BaseTransitionPosition = bucket.TransitionPosition;
|
||||||
|
}
|
||||||
|
bucket.CurrentState = bucket.ActiveTransition->Destination;
|
||||||
|
bucket.ActiveTransition = transition;
|
||||||
|
bucket.TransitionPosition = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1697,25 +1808,38 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sample the current state
|
if (bucket.BaseTransitionState)
|
||||||
const auto currentState = SampleState(bucket.CurrentState);
|
{
|
||||||
value = currentState;
|
// Sample the other state (eg. when blending from interrupted state to the another state from the old destination)
|
||||||
|
value = SampleState(context, bucket.BaseTransitionState);
|
||||||
|
if (bucket.BaseTransition)
|
||||||
|
{
|
||||||
|
// Evaluate the base pose from the time when transition was interrupted
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Sample the current state
|
||||||
|
value = SampleState(context, bucket.CurrentState);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle active transition blending
|
// Handle active transition blending
|
||||||
if (bucket.ActiveTransition)
|
if (bucket.ActiveTransition)
|
||||||
{
|
{
|
||||||
// Sample the active transition destination state
|
// Sample the active transition destination state
|
||||||
const auto destinationState = SampleState(bucket.ActiveTransition->Destination);
|
const auto destinationState = SampleState(context, bucket.ActiveTransition->Destination);
|
||||||
|
|
||||||
// Perform blending
|
// Perform blending
|
||||||
const float alpha = Math::Saturate(bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration);
|
const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration;
|
||||||
value = Blend(node, currentState, destinationState, alpha, bucket.ActiveTransition->BlendMode);
|
value = Blend(node, value, destinationState, alpha, bucket.ActiveTransition->BlendMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bucket
|
|
||||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||||
|
context.NodePath.Pop();
|
||||||
#undef END_TRANSITION
|
#undef END_TRANSITION
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Entry
|
// Entry
|
||||||
@@ -2102,6 +2226,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
auto& slot = slots[bucket.Index];
|
auto& slot = slots[bucket.Index];
|
||||||
Animation* anim = slot.Animation;
|
Animation* anim = slot.Animation;
|
||||||
ASSERT(slot.Animation && slot.Animation->IsLoaded());
|
ASSERT(slot.Animation && slot.Animation->IsLoaded());
|
||||||
|
if (slot.Reset)
|
||||||
|
{
|
||||||
|
// Start from the begining
|
||||||
|
slot.Reset = false;
|
||||||
|
bucket.TimePosition = 0.0f;
|
||||||
|
}
|
||||||
const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed;
|
const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed;
|
||||||
const float length = anim->GetLength();
|
const float length = anim->GetLength();
|
||||||
const bool loop = bucket.LoopsLeft != 0;
|
const bool loop = bucket.LoopsLeft != 0;
|
||||||
@@ -2130,7 +2260,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
// Blend out
|
// Blend out
|
||||||
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
||||||
bucket.BlendOutPosition += deltaTime;
|
bucket.BlendOutPosition += deltaTime;
|
||||||
const float alpha = Math::Saturate(bucket.BlendOutPosition / slot.BlendOutTime);
|
const float alpha = bucket.BlendOutPosition / slot.BlendOutTime;
|
||||||
value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic);
|
value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic);
|
||||||
}
|
}
|
||||||
else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime)
|
else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime)
|
||||||
@@ -2138,7 +2268,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
// Blend in
|
// Blend in
|
||||||
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
||||||
bucket.BlendInPosition += deltaTime;
|
bucket.BlendInPosition += deltaTime;
|
||||||
const float alpha = Math::Saturate(bucket.BlendInPosition / slot.BlendInTime);
|
const float alpha = bucket.BlendInPosition / slot.BlendInTime;
|
||||||
value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic);
|
value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -2163,7 +2293,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
|
|
||||||
void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value)
|
void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value)
|
||||||
{
|
{
|
||||||
auto& context = Context.Get();
|
auto& context = *Context.Get();
|
||||||
if (context.ValueCache.TryGet(boxBase, value))
|
if (context.ValueCache.TryGet(boxBase, value))
|
||||||
return;
|
return;
|
||||||
switch (node->TypeID)
|
switch (node->TypeID)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ AudioSource::AudioSource(const SpawnParams& params)
|
|||||||
, _minDistance(1000.0f)
|
, _minDistance(1000.0f)
|
||||||
, _loop(false)
|
, _loop(false)
|
||||||
, _playOnStart(false)
|
, _playOnStart(false)
|
||||||
|
, _startTime(0.0f)
|
||||||
, _allowSpatialization(true)
|
, _allowSpatialization(true)
|
||||||
{
|
{
|
||||||
Clip.Changed.Bind<AudioSource, &AudioSource::OnClipChanged>(this);
|
Clip.Changed.Bind<AudioSource, &AudioSource::OnClipChanged>(this);
|
||||||
@@ -71,6 +72,11 @@ void AudioSource::SetPlayOnStart(bool value)
|
|||||||
_playOnStart = value;
|
_playOnStart = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioSource::SetStartTime(float value)
|
||||||
|
{
|
||||||
|
_startTime = value;
|
||||||
|
}
|
||||||
|
|
||||||
void AudioSource::SetMinDistance(float value)
|
void AudioSource::SetMinDistance(float value)
|
||||||
{
|
{
|
||||||
value = Math::Max(0.0f, value);
|
value = Math::Max(0.0f, value);
|
||||||
@@ -361,6 +367,7 @@ void AudioSource::Serialize(SerializeStream& stream, const void* otherObj)
|
|||||||
SERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
|
SERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
|
||||||
SERIALIZE_MEMBER(Loop, _loop);
|
SERIALIZE_MEMBER(Loop, _loop);
|
||||||
SERIALIZE_MEMBER(PlayOnStart, _playOnStart);
|
SERIALIZE_MEMBER(PlayOnStart, _playOnStart);
|
||||||
|
SERIALIZE_MEMBER(StartTime, _startTime);
|
||||||
SERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
|
SERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,6 +384,7 @@ void AudioSource::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
|
|||||||
DESERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
|
DESERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
|
||||||
DESERIALIZE_MEMBER(Loop, _loop);
|
DESERIALIZE_MEMBER(Loop, _loop);
|
||||||
DESERIALIZE_MEMBER(PlayOnStart, _playOnStart);
|
DESERIALIZE_MEMBER(PlayOnStart, _playOnStart);
|
||||||
|
DESERIALIZE_MEMBER(StartTime, _startTime);
|
||||||
DESERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
|
DESERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
|
||||||
DESERIALIZE(Clip);
|
DESERIALIZE(Clip);
|
||||||
}
|
}
|
||||||
@@ -540,5 +548,7 @@ void AudioSource::BeginPlay(SceneBeginData* data)
|
|||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
Play();
|
Play();
|
||||||
|
if (GetStartTime() > 0)
|
||||||
|
SetTime(GetStartTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ private:
|
|||||||
float _dopplerFactor = 1.0f;
|
float _dopplerFactor = 1.0f;
|
||||||
bool _loop;
|
bool _loop;
|
||||||
bool _playOnStart;
|
bool _playOnStart;
|
||||||
|
float _startTime;
|
||||||
bool _allowSpatialization;
|
bool _allowSpatialization;
|
||||||
bool _clipChanged = false;
|
bool _clipChanged = false;
|
||||||
|
|
||||||
@@ -148,11 +149,25 @@ public:
|
|||||||
return _playOnStart;
|
return _playOnStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the time (in seconds) at which the audio clip starts playing if Play On Start is enabled.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes = "EditorOrder(51), DefaultValue(0.0f), Limit(0, float.MaxValue, 0.01f), EditorDisplay(\"Audio Source\", \"Start Time\"), VisibleIf(nameof(PlayOnStart))")
|
||||||
|
FORCE_INLINE float GetStartTime() const
|
||||||
|
{
|
||||||
|
return _startTime;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the audio clip should auto play on game start.
|
/// Determines whether the audio clip should auto play on game start.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_PROPERTY() void SetPlayOnStart(bool value);
|
API_PROPERTY() void SetPlayOnStart(bool value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the time (in seconds) at which the audio clip starts playing if Play On Start is enabled.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() void SetStartTime(float value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the minimum distance at which audio attenuation starts. When the listener is closer to the source than this value, audio is heard at full volume. Once farther away the audio starts attenuating.
|
/// Gets the minimum distance at which audio attenuation starts. When the listener is closer to the source than this value, audio is heard at full volume. Once farther away the audio starts attenuating.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ namespace ALC
|
|||||||
{
|
{
|
||||||
AudioBackend::Listener::TransformChanged(listener);
|
AudioBackend::Listener::TransformChanged(listener);
|
||||||
|
|
||||||
const Vector3 velocity = listener->GetVelocity();
|
const Float3 velocity = listener->GetVelocity();
|
||||||
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
||||||
alListenerf(AL_GAIN, Audio::GetVolume());
|
alListenerf(AL_GAIN, Audio::GetVolume());
|
||||||
}
|
}
|
||||||
@@ -319,8 +319,6 @@ void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
|
|||||||
ALC::RebuildContexts(false);
|
ALC::RebuildContexts(false);
|
||||||
#else
|
#else
|
||||||
AudioBackend::Listener::TransformChanged(listener);
|
AudioBackend::Listener::TransformChanged(listener);
|
||||||
const Vector3 velocity = listener->GetVelocity();
|
|
||||||
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
|
||||||
alListenerf(AL_GAIN, Audio::GetVolume());
|
alListenerf(AL_GAIN, Audio::GetVolume());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -336,7 +334,7 @@ void AudioBackendOAL::Listener_VelocityChanged(AudioListener* listener)
|
|||||||
{
|
{
|
||||||
ALC_GET_LISTENER_CONTEXT(listener)
|
ALC_GET_LISTENER_CONTEXT(listener)
|
||||||
|
|
||||||
const Vector3 velocity = listener->GetVelocity();
|
const Float3 velocity = listener->GetVelocity();
|
||||||
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,15 +342,15 @@ void AudioBackendOAL::Listener_TransformChanged(AudioListener* listener)
|
|||||||
{
|
{
|
||||||
ALC_GET_LISTENER_CONTEXT(listener)
|
ALC_GET_LISTENER_CONTEXT(listener)
|
||||||
|
|
||||||
const Vector3 position = listener->GetPosition();
|
const Float3 position = listener->GetPosition();
|
||||||
const Quaternion orientation = listener->GetOrientation();
|
const Quaternion orientation = listener->GetOrientation();
|
||||||
const Vector3 flipX(-1, 1, 1);
|
const Float3 flipX(-1, 1, 1);
|
||||||
const Vector3 alOrientation[2] =
|
const Float3 alOrientation[2] =
|
||||||
{
|
{
|
||||||
// Forward
|
// Forward
|
||||||
orientation * Vector3::Forward * flipX,
|
orientation * Float3::Forward * flipX,
|
||||||
// Up
|
// Up
|
||||||
orientation * Vector3::Up * flipX
|
orientation * Float3::Up * flipX
|
||||||
};
|
};
|
||||||
|
|
||||||
alListenerfv(AL_ORIENTATION, (float*)alOrientation);
|
alListenerfv(AL_ORIENTATION, (float*)alOrientation);
|
||||||
|
|||||||
@@ -413,10 +413,10 @@ bool Animation::Save(const StringView& path)
|
|||||||
MemoryWriteStream stream(4096);
|
MemoryWriteStream stream(4096);
|
||||||
|
|
||||||
// Info
|
// Info
|
||||||
stream.WriteInt32(102);
|
stream.WriteInt32(103);
|
||||||
stream.WriteDouble(Data.Duration);
|
stream.WriteDouble(Data.Duration);
|
||||||
stream.WriteDouble(Data.FramesPerSecond);
|
stream.WriteDouble(Data.FramesPerSecond);
|
||||||
stream.WriteBool(Data.EnableRootMotion);
|
stream.WriteByte((byte)Data.RootMotionFlags);
|
||||||
stream.WriteString(Data.RootNodeName, 13);
|
stream.WriteString(Data.RootNodeName, 13);
|
||||||
|
|
||||||
// Animation channels
|
// Animation channels
|
||||||
@@ -532,17 +532,22 @@ Asset::LoadResult Animation::load()
|
|||||||
int32 headerVersion = *(int32*)stream.GetPositionHandle();
|
int32 headerVersion = *(int32*)stream.GetPositionHandle();
|
||||||
switch (headerVersion)
|
switch (headerVersion)
|
||||||
{
|
{
|
||||||
case 100:
|
case 103:
|
||||||
case 101:
|
|
||||||
case 102:
|
|
||||||
{
|
|
||||||
stream.ReadInt32(&headerVersion);
|
stream.ReadInt32(&headerVersion);
|
||||||
stream.ReadDouble(&Data.Duration);
|
stream.ReadDouble(&Data.Duration);
|
||||||
stream.ReadDouble(&Data.FramesPerSecond);
|
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);
|
stream.ReadString(&Data.RootNodeName, 13);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
stream.ReadDouble(&Data.Duration);
|
stream.ReadDouble(&Data.Duration);
|
||||||
stream.ReadDouble(&Data.FramesPerSecond);
|
stream.ReadDouble(&Data.FramesPerSecond);
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ void AnimationGraph::OnDependencyModified(BinaryAsset* asset)
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop)
|
bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, bool loop, bool rootMotion)
|
||||||
{
|
{
|
||||||
if (!IsVirtual())
|
if (!IsVirtual())
|
||||||
{
|
{
|
||||||
@@ -89,7 +89,7 @@ bool AnimationGraph::InitAsAnimation(SkinnedModel* baseModel, Animation* anim, b
|
|||||||
rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1);
|
rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1);
|
||||||
rootNode.ID = 1;
|
rootNode.ID = 1;
|
||||||
rootNode.Values.Resize(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.Resize(1);
|
||||||
rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void);
|
rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void);
|
||||||
auto& animNode = graph.Nodes[1];
|
auto& animNode = graph.Nodes[1];
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ public:
|
|||||||
/// <param name="baseModel">The base model asset.</param>
|
/// <param name="baseModel">The base model asset.</param>
|
||||||
/// <param name="anim">The animation to play.</param>
|
/// <param name="anim">The animation to play.</param>
|
||||||
/// <param name="loop">True if play animation in a loop.</param>
|
/// <param name="loop">True if play animation in a loop.</param>
|
||||||
|
/// <param name="rootMotion">True if apply root motion. Otherwise it will be ignored.</param>
|
||||||
/// <returns>True if failed, otherwise false.</returns>
|
/// <returns>True if failed, otherwise false.</returns>
|
||||||
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to load surface graph from the asset.
|
/// Tries to load surface graph from the asset.
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
|
|||||||
Locker.Lock();
|
Locker.Lock();
|
||||||
|
|
||||||
// Try fast lookup
|
// Try fast lookup
|
||||||
FlaxStorage* result;
|
FlaxStorage* storage;
|
||||||
if (!StorageMap.TryGet(path, result))
|
if (!StorageMap.TryGet(path, storage))
|
||||||
{
|
{
|
||||||
// Detect storage type and create object
|
// Detect storage type and create object
|
||||||
const bool isPackage = path.EndsWith(StringView(PACKAGE_FILES_EXTENSION));
|
const bool isPackage = path.EndsWith(StringView(PACKAGE_FILES_EXTENSION));
|
||||||
@@ -67,39 +67,42 @@ FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, b
|
|||||||
{
|
{
|
||||||
auto package = New<FlaxPackage>(path);
|
auto package = New<FlaxPackage>(path);
|
||||||
Packages.Add(package);
|
Packages.Add(package);
|
||||||
result = package;
|
storage = package;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto file = New<FlaxFile>(path);
|
auto file = New<FlaxFile>(path);
|
||||||
Files.Add(file);
|
Files.Add(file);
|
||||||
result = file;
|
storage = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register storage container
|
// 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();
|
Locker.Unlock();
|
||||||
|
|
||||||
if (loadIt)
|
if (loadIt)
|
||||||
{
|
{
|
||||||
// Initialize storage container
|
// Initialize storage container
|
||||||
result->LockChunks();
|
storage->LockChunks();
|
||||||
const bool loadFailed = result->Load();
|
const bool loadFailed = storage->Load();
|
||||||
result->UnlockChunks();
|
storage->UnlockChunks();
|
||||||
if (loadFailed)
|
if (loadFailed)
|
||||||
{
|
{
|
||||||
LOG(Error, "Failed to load {0}.", path);
|
LOG(Error, "Failed to load {0}.", path);
|
||||||
Locker.Lock();
|
Locker.Lock();
|
||||||
StorageMap.Remove(path);
|
StorageMap.Remove(path);
|
||||||
if (result->IsPackage())
|
if (storage->IsPackage())
|
||||||
Packages.Remove((FlaxPackage*)result);
|
Packages.Remove((FlaxPackage*)storage);
|
||||||
else
|
else
|
||||||
Files.Remove((FlaxFile*)result);
|
Files.Remove((FlaxFile*)storage);
|
||||||
Locker.Unlock();
|
Locker.Unlock();
|
||||||
Delete(result);
|
result = nullptr;
|
||||||
return nullptr;
|
Delete(storage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,13 @@ FlaxStorage::~FlaxStorage()
|
|||||||
|
|
||||||
#if USE_EDITOR
|
#if USE_EDITOR
|
||||||
// Ensure to close any outstanding file handles to prevent file locking in case it failed to load
|
// Ensure to close any outstanding file handles to prevent file locking in case it failed to load
|
||||||
_file.DeleteAll();
|
Array<FileReadStream*> streams;
|
||||||
|
_file.GetValues(streams);
|
||||||
|
for (FileReadStream* stream : streams)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
Delete(stream);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1264,7 +1270,6 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if ASSETS_LOADING_EXTRA_VERIFICATION
|
#if ASSETS_LOADING_EXTRA_VERIFICATION
|
||||||
|
|
||||||
// Validate loaded header (asset ID and type ID must be the same)
|
// Validate loaded header (asset ID and type ID must be the same)
|
||||||
if (e.ID != data.Header.ID)
|
if (e.ID != data.Header.ID)
|
||||||
{
|
{
|
||||||
@@ -1274,7 +1279,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());
|
LOG(Error, "Loading asset header data mismatch! Expected Type Name: {0}, loaded header: {1}.\nSource: {2}", e.TypeName, data.Header.ToString(), ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -1337,7 +1341,14 @@ bool FlaxStorage::CloseFileHandles()
|
|||||||
return true; // Failed, someone is still accessing the file
|
return true; // Failed, someone is still accessing the file
|
||||||
|
|
||||||
// Close file handles (from all threads)
|
// Close file handles (from all threads)
|
||||||
_file.DeleteAll();
|
Array<FileReadStream*> streams;
|
||||||
|
_file.GetValues(streams);
|
||||||
|
for (FileReadStream* stream : streams)
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
Delete(stream);
|
||||||
|
}
|
||||||
|
_file.Clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ protected:
|
|||||||
CriticalSection _loadLocker;
|
CriticalSection _loadLocker;
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
ThreadLocalObject<FileReadStream> _file;
|
ThreadLocal<FileReadStream*> _file;
|
||||||
Array<FlaxChunk*> _chunks;
|
Array<FlaxChunk*> _chunks;
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
|
|||||||
@@ -58,17 +58,17 @@ public:
|
|||||||
return _storage != nullptr;
|
return _storage != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE bool operator ==(const FlaxStorageReference& other) const
|
FORCE_INLINE bool operator==(const FlaxStorageReference& other) const
|
||||||
{
|
{
|
||||||
return _storage == other._storage;
|
return _storage == other._storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE bool operator !=(const FlaxStorageReference& other) const
|
FORCE_INLINE bool operator!=(const FlaxStorageReference& other) const
|
||||||
{
|
{
|
||||||
return _storage != other._storage;
|
return _storage != other._storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE FlaxStorage* operator ->() const
|
FORCE_INLINE FlaxStorage* operator->() const
|
||||||
{
|
{
|
||||||
return _storage;
|
return _storage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ CreateAssetResult CreateAnimationGraph::Create(CreateAssetContext& context)
|
|||||||
rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1);
|
rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1);
|
||||||
rootNode.ID = 1;
|
rootNode.ID = 1;
|
||||||
rootNode.Values.Resize(1);
|
rootNode.Values.Resize(1);
|
||||||
rootNode.Values[0] = (int32)RootMotionMode::NoExtraction;
|
rootNode.Values[0] = (int32)RootMotionExtraction::NoExtraction;
|
||||||
rootNode.Boxes.Resize(1);
|
rootNode.Boxes.Resize(1);
|
||||||
rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void);
|
rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void);
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,14 @@
|
|||||||
#include "Engine/Threading/ThreadLocal.h"
|
#include "Engine/Threading/ThreadLocal.h"
|
||||||
|
|
||||||
// Use a cached storage for the sorting (one per thread to reduce locking)
|
// Use a cached storage for the sorting (one per thread to reduce locking)
|
||||||
ThreadLocal<Sorting::SortingStack> SortingStacks;
|
ThreadLocal<Sorting::SortingStack*> SortingStacks;
|
||||||
|
|
||||||
Sorting::SortingStack& Sorting::SortingStack::Get()
|
Sorting::SortingStack& Sorting::SortingStack::Get()
|
||||||
{
|
{
|
||||||
return SortingStacks.Get();
|
SortingStack*& stack = SortingStacks.Get();
|
||||||
|
if (!stack)
|
||||||
|
stack = New<SortingStack>();
|
||||||
|
return *stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sorting::SortingStack::SortingStack()
|
Sorting::SortingStack::SortingStack()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The layers names.
|
/// The layers names.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)]
|
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true, Display = CollectionAttribute.DisplayType.Inline)]
|
||||||
public string[] Layers = new string[32];
|
public string[] Layers = new string[32];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -56,13 +56,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace FlaxEngine
|
namespace FlaxEngine
|
||||||
{
|
{
|
||||||
/// <summary>
|
partial struct Matrix3x3 : IEquatable<Matrix3x3>, IFormattable
|
||||||
/// Represents a 3x3 Matrix ( contains only Scale and Rotation ).
|
|
||||||
/// </summary>
|
|
||||||
[Serializable]
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
|
||||||
// ReSharper disable once InconsistentNaming
|
|
||||||
public struct Matrix3x3 : IEquatable<Matrix3x3>, IFormattable
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The size of the <see cref="Matrix3x3"/> type, in bytes.
|
/// The size of the <see cref="Matrix3x3"/> type, in bytes.
|
||||||
@@ -135,9 +129,7 @@ namespace FlaxEngine
|
|||||||
/// <param name="value">The value that will be assigned to all components.</param>
|
/// <param name="value">The value that will be assigned to all components.</param>
|
||||||
public Matrix3x3(float value)
|
public Matrix3x3(float value)
|
||||||
{
|
{
|
||||||
M11 = M12 = M13 =
|
M11 = M12 = M13 = M21 = M22 = M23 = M31 = M32 = M33 = value;
|
||||||
M21 = M22 = M23 =
|
|
||||||
M31 = M32 = M33 = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a 3x3 mathematical matrix.
|
/// Represents a 3x3 mathematical matrix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_STRUCT(InBuild) struct FLAXENGINE_API Matrix3x3
|
API_STRUCT() struct FLAXENGINE_API Matrix3x3
|
||||||
{
|
{
|
||||||
|
DECLARE_SCRIPTING_TYPE_MINIMAL(Matrix3x3);
|
||||||
public:
|
public:
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,22 +12,22 @@ const int32 CachedDaysToMonth[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273,
|
|||||||
DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond)
|
DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER(Validate(year, month, day, hour, minute, second, millisecond));
|
ASSERT_LOW_LAYER(Validate(year, month, day, hour, minute, second, millisecond));
|
||||||
int32 totalDays = 0;
|
int32 daysSum = 0;
|
||||||
if (month > 2 && IsLeapYear(year))
|
if (month > 2 && IsLeapYear(year))
|
||||||
totalDays++;
|
daysSum++;
|
||||||
year--;
|
year--;
|
||||||
month--;
|
month--;
|
||||||
totalDays += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
|
daysSum += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
|
||||||
Ticks = totalDays * Constants::TicksPerDay
|
Ticks = daysSum * TimeSpan::TicksPerDay
|
||||||
+ hour * Constants::TicksPerHour
|
+ hour * TimeSpan::TicksPerHour
|
||||||
+ minute * Constants::TicksPerMinute
|
+ minute * TimeSpan::TicksPerMinute
|
||||||
+ second * Constants::TicksPerSecond
|
+ second * TimeSpan::TicksPerSecond
|
||||||
+ millisecond * Constants::TicksPerMillisecond;
|
+ millisecond * TimeSpan::TicksPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime DateTime::GetDate() const
|
DateTime DateTime::GetDate() const
|
||||||
{
|
{
|
||||||
return DateTime(Ticks - Ticks % Constants::TicksPerDay);
|
return DateTime(Ticks - Ticks % TimeSpan::TicksPerDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DateTime::GetDate(int32& year, int32& month, int32& day) const
|
void DateTime::GetDate(int32& year, int32& month, int32& day) const
|
||||||
@@ -35,8 +35,7 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
|
|||||||
// Based on:
|
// Based on:
|
||||||
// Fliegel, H. F. and van Flandern, T. C.,
|
// Fliegel, H. F. and van Flandern, T. C.,
|
||||||
// Communications of the ACM, Vol. 11, No. 10 (October 1968).
|
// Communications of the ACM, Vol. 11, No. 10 (October 1968).
|
||||||
|
int32 l = Math::FloorToInt((float)(GetDate().GetJulianDay() + 0.5)) + 68569;
|
||||||
int32 l = Math::FloorToInt(static_cast<float>(GetJulianDay() + 0.5)) + 68569;
|
|
||||||
const int32 n = 4 * l / 146097;
|
const int32 n = 4 * l / 146097;
|
||||||
l = l - (146097 * n + 3) / 4;
|
l = l - (146097 * n + 3) / 4;
|
||||||
int32 i = 4000 * (l + 1) / 1461001;
|
int32 i = 4000 * (l + 1) / 1461001;
|
||||||
@@ -46,7 +45,6 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
|
|||||||
l = j / 11;
|
l = j / 11;
|
||||||
j = j + 2 - 12 * l;
|
j = j + 2 - 12 * l;
|
||||||
i = 100 * (n - 49) + i + l;
|
i = 100 * (n - 49) + i + l;
|
||||||
|
|
||||||
year = i;
|
year = i;
|
||||||
month = j;
|
month = j;
|
||||||
day = k;
|
day = k;
|
||||||
@@ -61,7 +59,7 @@ int32 DateTime::GetDay() const
|
|||||||
|
|
||||||
DayOfWeek DateTime::GetDayOfWeek() const
|
DayOfWeek DateTime::GetDayOfWeek() const
|
||||||
{
|
{
|
||||||
return static_cast<DayOfWeek>((Ticks / Constants::TicksPerDay) % 7);
|
return static_cast<DayOfWeek>((Ticks / TimeSpan::TicksPerDay) % 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::GetDayOfYear() const
|
int32 DateTime::GetDayOfYear() const
|
||||||
@@ -75,7 +73,7 @@ int32 DateTime::GetDayOfYear() const
|
|||||||
|
|
||||||
int32 DateTime::GetHour() const
|
int32 DateTime::GetHour() const
|
||||||
{
|
{
|
||||||
return static_cast<int32>(Ticks / Constants::TicksPerHour % 24);
|
return static_cast<int32>(Ticks / TimeSpan::TicksPerHour % 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::GetHour12() const
|
int32 DateTime::GetHour12() const
|
||||||
@@ -90,7 +88,7 @@ int32 DateTime::GetHour12() const
|
|||||||
|
|
||||||
double DateTime::GetJulianDay() const
|
double DateTime::GetJulianDay() const
|
||||||
{
|
{
|
||||||
return 1721425.5 + static_cast<double>(Ticks) / Constants::TicksPerDay;
|
return 1721425.5 + static_cast<double>(Ticks) / TimeSpan::TicksPerDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
double DateTime::GetModifiedJulianDay() const
|
double DateTime::GetModifiedJulianDay() const
|
||||||
@@ -100,12 +98,12 @@ double DateTime::GetModifiedJulianDay() const
|
|||||||
|
|
||||||
int32 DateTime::GetMillisecond() const
|
int32 DateTime::GetMillisecond() const
|
||||||
{
|
{
|
||||||
return static_cast<int32>(Ticks / Constants::TicksPerMillisecond % 1000);
|
return static_cast<int32>(Ticks / TimeSpan::TicksPerMillisecond % 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::GetMinute() const
|
int32 DateTime::GetMinute() const
|
||||||
{
|
{
|
||||||
return static_cast<int32>(Ticks / Constants::TicksPerMinute % 60);
|
return static_cast<int32>(Ticks / TimeSpan::TicksPerMinute % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::GetMonth() const
|
int32 DateTime::GetMonth() const
|
||||||
@@ -122,12 +120,12 @@ MonthOfYear DateTime::GetMonthOfYear() const
|
|||||||
|
|
||||||
int32 DateTime::GetSecond() const
|
int32 DateTime::GetSecond() const
|
||||||
{
|
{
|
||||||
return static_cast<int32>(Ticks / Constants::TicksPerSecond % 60);
|
return static_cast<int32>(Ticks / TimeSpan::TicksPerSecond % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan DateTime::GetTimeOfDay() const
|
TimeSpan DateTime::GetTimeOfDay() const
|
||||||
{
|
{
|
||||||
return TimeSpan(Ticks % Constants::TicksPerDay);
|
return TimeSpan(Ticks % TimeSpan::TicksPerDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::GetYear() const
|
int32 DateTime::GetYear() const
|
||||||
@@ -137,11 +135,6 @@ int32 DateTime::GetYear() const
|
|||||||
return year;
|
return year;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 DateTime::ToUnixTimestamp() const
|
|
||||||
{
|
|
||||||
return static_cast<int32>((Ticks - DateTime(1970, 1, 1).Ticks) / Constants::TicksPerSecond);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 DateTime::DaysInMonth(int32 year, int32 month)
|
int32 DateTime::DaysInMonth(int32 year, int32 month)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((month >= 1) && (month <= 12));
|
ASSERT_LOW_LAYER((month >= 1) && (month <= 12));
|
||||||
@@ -155,16 +148,6 @@ int32 DateTime::DaysInYear(int32 year)
|
|||||||
return IsLeapYear(year) ? 366 : 365;
|
return IsLeapYear(year) ? 366 : 365;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime DateTime::FromJulianDay(double julianDay)
|
|
||||||
{
|
|
||||||
return DateTime(static_cast<int64>((julianDay - 1721425.5) * Constants::TicksPerDay));
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime DateTime::FromUnixTimestamp(int32 unixTime)
|
|
||||||
{
|
|
||||||
return DateTime(1970, 1, 1) + TimeSpan(static_cast<int64>(unixTime) * Constants::TicksPerSecond);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DateTime::IsLeapYear(int32 year)
|
bool DateTime::IsLeapYear(int32 year)
|
||||||
{
|
{
|
||||||
if ((year % 4) == 0)
|
if ((year % 4) == 0)
|
||||||
@@ -176,7 +159,7 @@ bool DateTime::IsLeapYear(int32 year)
|
|||||||
|
|
||||||
DateTime DateTime::MaxValue()
|
DateTime DateTime::MaxValue()
|
||||||
{
|
{
|
||||||
return DateTime(3652059 * Constants::TicksPerDay - 1);
|
return DateTime(3652059 * TimeSpan::TicksPerDay - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime DateTime::Now()
|
DateTime DateTime::Now()
|
||||||
|
|||||||
@@ -199,11 +199,6 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int32 GetYear() const;
|
int32 GetYear() const;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets this date as the number of seconds since the Unix Epoch (January 1st of 1970).
|
|
||||||
/// </summary>
|
|
||||||
int32 ToUnixTimestamp() const;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the number of days in the year and month.
|
/// Gets the number of days in the year and month.
|
||||||
@@ -220,20 +215,6 @@ public:
|
|||||||
/// <returns>The number of days.</returns>
|
/// <returns>The number of days.</returns>
|
||||||
static int32 DaysInYear(int32 year);
|
static int32 DaysInYear(int32 year);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the proleptic Gregorian date for the given Julian Day.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="julianDay">The Julian Day.</param>
|
|
||||||
/// <returns>Gregorian date and time.</returns>
|
|
||||||
static DateTime FromJulianDay(double julianDay);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the date from Unix time (seconds from midnight 1970-01-01).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="unixTime">The Unix time (seconds from midnight 1970-01-01).</param>
|
|
||||||
/// <returns>The Gregorian date and time.</returns>
|
|
||||||
static DateTime FromUnixTimestamp(int32 unixTime);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified year is a leap year.
|
/// Determines whether the specified year is a leap year.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,38 +6,53 @@
|
|||||||
TimeSpan TimeSpan::FromDays(double days)
|
TimeSpan TimeSpan::FromDays(double days)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((days >= MinValue().GetTotalDays()) && (days <= MaxValue().GetTotalDays()));
|
ASSERT_LOW_LAYER((days >= MinValue().GetTotalDays()) && (days <= MaxValue().GetTotalDays()));
|
||||||
return TimeSpan(static_cast<int64>(days * Constants::TicksPerDay));
|
return TimeSpan(static_cast<int64>(days * TicksPerDay));
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan TimeSpan::FromHours(double hours)
|
TimeSpan TimeSpan::FromHours(double hours)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((hours >= MinValue().GetTotalHours()) && (hours <= MaxValue().GetTotalHours()));
|
ASSERT_LOW_LAYER((hours >= MinValue().GetTotalHours()) && (hours <= MaxValue().GetTotalHours()));
|
||||||
return TimeSpan(static_cast<int64>(hours * Constants::TicksPerHour));
|
return TimeSpan(static_cast<int64>(hours * TicksPerHour));
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan TimeSpan::FromMilliseconds(double milliseconds)
|
TimeSpan TimeSpan::FromMilliseconds(double milliseconds)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((milliseconds >= MinValue().GetTotalMilliseconds()) && (milliseconds <= MaxValue().GetTotalMilliseconds()));
|
ASSERT_LOW_LAYER((milliseconds >= MinValue().GetTotalMilliseconds()) && (milliseconds <= MaxValue().GetTotalMilliseconds()));
|
||||||
return TimeSpan(static_cast<int64>(milliseconds * Constants::TicksPerMillisecond));
|
return TimeSpan(static_cast<int64>(milliseconds * TicksPerMillisecond));
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan TimeSpan::FromMinutes(double minutes)
|
TimeSpan TimeSpan::FromMinutes(double minutes)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((minutes >= MinValue().GetTotalMinutes()) && (minutes <= MaxValue().GetTotalMinutes()));
|
ASSERT_LOW_LAYER((minutes >= MinValue().GetTotalMinutes()) && (minutes <= MaxValue().GetTotalMinutes()));
|
||||||
return TimeSpan(static_cast<int64>(minutes * Constants::TicksPerMinute));
|
return TimeSpan(static_cast<int64>(minutes * TicksPerMinute));
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan TimeSpan::FromSeconds(double seconds)
|
TimeSpan TimeSpan::FromSeconds(double seconds)
|
||||||
{
|
{
|
||||||
ASSERT_LOW_LAYER((seconds >= MinValue().GetTotalSeconds()) && (seconds <= MaxValue().GetTotalSeconds()));
|
ASSERT_LOW_LAYER((seconds >= MinValue().GetTotalSeconds()) && (seconds <= MaxValue().GetTotalSeconds()));
|
||||||
return TimeSpan(static_cast<int64>(seconds * Constants::TicksPerSecond));
|
return TimeSpan(static_cast<int64>(seconds * TicksPerSecond));
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan TimeSpan::MaxValue()
|
||||||
|
{
|
||||||
|
return TimeSpan(9223372036854775807);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan TimeSpan::MinValue()
|
||||||
|
{
|
||||||
|
return TimeSpan(-9223372036854775807 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan TimeSpan::Zero()
|
||||||
|
{
|
||||||
|
return TimeSpan(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimeSpan::Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds)
|
void TimeSpan::Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds)
|
||||||
{
|
{
|
||||||
const int64 totalMs = 1000 * (60 * 60 * 24 * (int64)days + 60 * 60 * (int64)hours + 60 * (int64)minutes + (int64)seconds) + (int64)milliseconds;
|
const int64 totalMs = 1000 * (60 * 60 * 24 * (int64)days + 60 * 60 * (int64)hours + 60 * (int64)minutes + (int64)seconds) + (int64)milliseconds;
|
||||||
ASSERT_LOW_LAYER((totalMs >= MinValue().GetTotalMilliseconds()) && (totalMs <= MaxValue().GetTotalMilliseconds()));
|
ASSERT_LOW_LAYER((totalMs >= MinValue().GetTotalMilliseconds()) && (totalMs <= MaxValue().GetTotalMilliseconds()));
|
||||||
Ticks = totalMs * Constants::TicksPerMillisecond;
|
Ticks = totalMs * TicksPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
String TimeSpan::ToString() const
|
String TimeSpan::ToString() const
|
||||||
|
|||||||
@@ -6,32 +6,30 @@
|
|||||||
#include "Engine/Core/Formatting.h"
|
#include "Engine/Core/Formatting.h"
|
||||||
#include "Engine/Core/Templates.h"
|
#include "Engine/Core/Templates.h"
|
||||||
|
|
||||||
namespace Constants
|
|
||||||
{
|
|
||||||
// The number of timespan ticks per day.
|
|
||||||
const int64 TicksPerDay = 864000000000;
|
|
||||||
|
|
||||||
// The number of timespan ticks per hour.
|
|
||||||
const int64 TicksPerHour = 36000000000;
|
|
||||||
|
|
||||||
// The number of timespan ticks per millisecond.
|
|
||||||
const int64 TicksPerMillisecond = 10000;
|
|
||||||
|
|
||||||
// The number of timespan ticks per minute.
|
|
||||||
const int64 TicksPerMinute = 600000000;
|
|
||||||
|
|
||||||
// The number of timespan ticks per second.
|
|
||||||
const int64 TicksPerSecond = 10000000;
|
|
||||||
|
|
||||||
// The number of timespan ticks per week.
|
|
||||||
const int64 TicksPerWeek = 6048000000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the difference between two dates and times.
|
/// Represents the difference between two dates and times.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API TimeSpan
|
API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API TimeSpan
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
// The number of timespan ticks per day.
|
||||||
|
static constexpr int64 TicksPerDay = 864000000000;
|
||||||
|
|
||||||
|
// The number of timespan ticks per hour.
|
||||||
|
static constexpr int64 TicksPerHour = 36000000000;
|
||||||
|
|
||||||
|
// The number of timespan ticks per millisecond.
|
||||||
|
static constexpr int64 TicksPerMillisecond = 10000;
|
||||||
|
|
||||||
|
// The number of timespan ticks per minute.
|
||||||
|
static constexpr int64 TicksPerMinute = 600000000;
|
||||||
|
|
||||||
|
// The number of timespan ticks per second.
|
||||||
|
static constexpr int64 TicksPerSecond = 10000000;
|
||||||
|
|
||||||
|
// The number of timespan ticks per week.
|
||||||
|
static constexpr int64 TicksPerWeek = 6048000000000;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time span in 100 nanoseconds resolution.
|
/// Time span in 100 nanoseconds resolution.
|
||||||
@@ -170,7 +168,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetDays() const
|
FORCE_INLINE int32 GetDays() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerDay);
|
return (int32)(Ticks / TicksPerDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -186,7 +184,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetHours() const
|
FORCE_INLINE int32 GetHours() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerHour % 24);
|
return (int32)(Ticks / TicksPerHour % 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -194,7 +192,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetMilliseconds() const
|
FORCE_INLINE int32 GetMilliseconds() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerMillisecond % 1000);
|
return (int32)(Ticks / TicksPerMillisecond % 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -202,7 +200,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetMinutes() const
|
FORCE_INLINE int32 GetMinutes() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerMinute % 60);
|
return (int32)(Ticks / TicksPerMinute % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -210,7 +208,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE int32 GetSeconds() const
|
FORCE_INLINE int32 GetSeconds() const
|
||||||
{
|
{
|
||||||
return (int32)(Ticks / Constants::TicksPerSecond % 60);
|
return (int32)(Ticks / TicksPerSecond % 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -218,7 +216,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalDays() const
|
FORCE_INLINE double GetTotalDays() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerDay;
|
return (double)Ticks / TicksPerDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -226,7 +224,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalHours() const
|
FORCE_INLINE double GetTotalHours() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerHour;
|
return (double)Ticks / TicksPerHour;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -234,7 +232,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalMilliseconds() const
|
FORCE_INLINE double GetTotalMilliseconds() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerMillisecond;
|
return (double)Ticks / TicksPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -242,7 +240,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE double GetTotalMinutes() const
|
FORCE_INLINE double GetTotalMinutes() const
|
||||||
{
|
{
|
||||||
return (double)Ticks / Constants::TicksPerMinute;
|
return (double)Ticks / TicksPerMinute;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -250,7 +248,7 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FORCE_INLINE float GetTotalSeconds() const
|
FORCE_INLINE float GetTotalSeconds() const
|
||||||
{
|
{
|
||||||
return static_cast<float>(Ticks) / Constants::TicksPerSecond;
|
return static_cast<float>(Ticks) / TicksPerSecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -293,29 +291,17 @@ public:
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the maximum time span value.
|
/// Returns the maximum time span value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The time span.</returns>
|
static TimeSpan MaxValue();
|
||||||
static TimeSpan MaxValue()
|
|
||||||
{
|
|
||||||
return TimeSpan(9223372036854775807);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the minimum time span value.
|
/// Returns the minimum time span value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The time span.</returns>
|
static TimeSpan MinValue();
|
||||||
static TimeSpan MinValue()
|
|
||||||
{
|
|
||||||
return TimeSpan(-9223372036854775807 - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the zero time span value.
|
/// Returns the zero time span value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The time span.</returns>
|
static TimeSpan Zero();
|
||||||
static TimeSpan Zero()
|
|
||||||
{
|
|
||||||
return TimeSpan(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds);
|
void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds);
|
||||||
|
|||||||
@@ -139,6 +139,24 @@ VariantType::VariantType(const StringAnsiView& typeName)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// Aliases
|
||||||
|
if (typeName == "FlaxEngine.Vector2")
|
||||||
|
{
|
||||||
|
new(this) VariantType(Vector2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeName == "FlaxEngine.Vector3")
|
||||||
|
{
|
||||||
|
new(this) VariantType(Vector3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeName == "FlaxEngine.Vector4")
|
||||||
|
{
|
||||||
|
new(this) VariantType(Vector4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check case for array
|
// Check case for array
|
||||||
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
|
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
|
||||||
@@ -3985,15 +4003,32 @@ void Variant::CopyStructure(void* src)
|
|||||||
{
|
{
|
||||||
if (AsBlob.Data && src)
|
if (AsBlob.Data && src)
|
||||||
{
|
{
|
||||||
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(Type.TypeName));
|
const StringAnsiView typeName(Type.TypeName);
|
||||||
|
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
|
||||||
if (typeHandle)
|
if (typeHandle)
|
||||||
{
|
{
|
||||||
auto& type = typeHandle.GetType();
|
auto& type = typeHandle.GetType();
|
||||||
type.Struct.Copy(AsBlob.Data, src);
|
type.Struct.Copy(AsBlob.Data, src);
|
||||||
}
|
}
|
||||||
|
#if USE_CSHARP
|
||||||
|
else if (const auto mclass = Scripting::FindClass(typeName))
|
||||||
|
{
|
||||||
|
// Fallback to C#-only types
|
||||||
|
MCore::Thread::Attach();
|
||||||
|
if (MANAGED_GC_HANDLE && mclass->IsValueType())
|
||||||
|
{
|
||||||
|
MObject* instance = MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE);
|
||||||
|
void* data = MCore::Object::Unbox(instance);
|
||||||
|
Platform::MemoryCopy(data, src, mclass->GetInstanceSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Platform::MemoryCopy(AsBlob.Data, src, AsBlob.Length);
|
if (typeName.Length() != 0)
|
||||||
|
{
|
||||||
|
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,9 +72,6 @@ void EngineService::OnInit()
|
|||||||
|
|
||||||
// Init services from front to back
|
// Init services from front to back
|
||||||
auto& services = GetServices();
|
auto& services = GetServices();
|
||||||
#if TRACY_ENABLE
|
|
||||||
Char nameBuffer[100];
|
|
||||||
#endif
|
|
||||||
for (int32 i = 0; i < services.Count(); i++)
|
for (int32 i = 0; i < services.Count(); i++)
|
||||||
{
|
{
|
||||||
const auto service = services[i];
|
const auto service = services[i];
|
||||||
@@ -82,6 +79,7 @@ void EngineService::OnInit()
|
|||||||
#if TRACY_ENABLE
|
#if TRACY_ENABLE
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
int32 nameBufferLength = 0;
|
int32 nameBufferLength = 0;
|
||||||
|
Char nameBuffer[100];
|
||||||
for (int32 j = 0; j < name.Length(); j++)
|
for (int32 j = 0; j < name.Length(); j++)
|
||||||
if (name[j] != ' ')
|
if (name[j] != ' ')
|
||||||
nameBuffer[nameBufferLength++] = name[j];
|
nameBuffer[nameBufferLength++] = name[j];
|
||||||
@@ -114,6 +112,18 @@ void EngineService::OnDispose()
|
|||||||
const auto service = services[i];
|
const auto service = services[i];
|
||||||
if (service->IsInitialized)
|
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->IsInitialized = false;
|
||||||
service->Dispose();
|
service->Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
|
|||||||
{
|
{
|
||||||
Time = UnscaledTime = TimeSpan::Zero();
|
Time = UnscaledTime = TimeSpan::Zero();
|
||||||
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
|
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
|
||||||
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
|
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
|
||||||
LastBegin = currentTime - LastLength;
|
LastBegin = currentTime - LastLength;
|
||||||
LastEnd = currentTime;
|
LastEnd = currentTime;
|
||||||
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
|
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
|
||||||
@@ -76,7 +76,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
|
|||||||
void Time::TickData::OnReset(float targetFps, double currentTime)
|
void Time::TickData::OnReset(float targetFps, double currentTime)
|
||||||
{
|
{
|
||||||
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
|
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
|
||||||
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
|
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
|
||||||
LastBegin = currentTime - LastLength;
|
LastBegin = currentTime - LastLength;
|
||||||
LastEnd = currentTime;
|
LastEnd = currentTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,6 +236,27 @@ API_ENUM(Attributes="Flags") enum class ShadowsCastingMode
|
|||||||
|
|
||||||
DECLARE_ENUM_OPERATORS(ShadowsCastingMode);
|
DECLARE_ENUM_OPERATORS(ShadowsCastingMode);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The partitioning mode for shadow cascades.
|
||||||
|
/// </summary>
|
||||||
|
API_ENUM() enum class PartitionMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Internally defined cascade splits.
|
||||||
|
/// </summary>
|
||||||
|
Manual = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logarithmic cascade splits.
|
||||||
|
/// </summary>
|
||||||
|
Logarithmic = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PSSM cascade splits.
|
||||||
|
/// </summary>
|
||||||
|
PSSM = 2,
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Identifies expected GPU resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU.
|
/// Identifies expected GPU resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -911,10 +911,10 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Info
|
// 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.Duration);
|
||||||
stream->WriteDouble(anim.FramesPerSecond);
|
stream->WriteDouble(anim.FramesPerSecond);
|
||||||
stream->WriteBool(anim.EnableRootMotion);
|
stream->WriteByte((byte)anim.RootMotionFlags);
|
||||||
stream->WriteString(anim.RootNodeName, 13);
|
stream->WriteString(anim.RootNodeName, 13);
|
||||||
|
|
||||||
// Animation channels
|
// Animation channels
|
||||||
@@ -928,6 +928,12 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
|
|||||||
Serialization::Serialize(*stream, channel.Scale);
|
Serialization::Serialize(*stream, channel.Scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animation events
|
||||||
|
stream->WriteInt32(0);
|
||||||
|
|
||||||
|
// Nested animations
|
||||||
|
stream->WriteInt32(0);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public:
|
|||||||
FORCE_INLINE SkeletonNode& RootNode()
|
FORCE_INLINE SkeletonNode& RootNode()
|
||||||
{
|
{
|
||||||
ASSERT(Nodes.HasItems());
|
ASSERT(Nodes.HasItems());
|
||||||
return Nodes[0];
|
return Nodes.Get()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,52 +100,24 @@ public:
|
|||||||
FORCE_INLINE const SkeletonNode& RootNode() const
|
FORCE_INLINE const SkeletonNode& RootNode() const
|
||||||
{
|
{
|
||||||
ASSERT(Nodes.HasItems());
|
ASSERT(Nodes.HasItems());
|
||||||
return Nodes[0];
|
return Nodes.Get()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange.
|
/// Swaps the contents of object with the other object without copy operation. Performs fast internal data exchange.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Swap(SkeletonData& other)
|
void Swap(SkeletonData& other);
|
||||||
{
|
|
||||||
Nodes.Swap(other.Nodes);
|
|
||||||
Bones.Swap(other.Bones);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 FindNode(const StringView& name) const
|
Transform GetNodeTransform(int32 nodeIndex) const;
|
||||||
{
|
void SetNodeTransform(int32 nodeIndex, const Transform& value);
|
||||||
for (int32 i = 0; i < Nodes.Count(); i++)
|
|
||||||
{
|
|
||||||
if (Nodes[i].Name == name)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 FindBone(int32 nodeIndex) const
|
int32 FindNode(const StringView& name) const;
|
||||||
{
|
int32 FindBone(int32 nodeIndex) const;
|
||||||
for (int32 i = 0; i < Bones.Count(); i++)
|
|
||||||
{
|
|
||||||
if (Bones[i].NodeIndex == nodeIndex)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64 GetMemoryUsage() 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases data.
|
/// Releases data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Dispose()
|
void Dispose();
|
||||||
{
|
|
||||||
Nodes.Resize(0);
|
|
||||||
Bones.Resize(0);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,69 @@
|
|||||||
#include "Engine/Threading/Task.h"
|
#include "Engine/Threading/Task.h"
|
||||||
#include "Engine/Threading/Threading.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)
|
void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere)
|
||||||
{
|
{
|
||||||
_model = model;
|
_model = model;
|
||||||
|
|||||||
@@ -1359,7 +1359,7 @@ bool Actor::IsPrefabRoot() const
|
|||||||
Actor* Actor::FindActor(const StringView& name) const
|
Actor* Actor::FindActor(const StringView& name) const
|
||||||
{
|
{
|
||||||
Actor* result = nullptr;
|
Actor* result = nullptr;
|
||||||
if (StringUtils::Compare(*_name, *name) == 0)
|
if (_name == name)
|
||||||
{
|
{
|
||||||
result = const_cast<Actor*>(this);
|
result = const_cast<Actor*>(this);
|
||||||
}
|
}
|
||||||
@@ -1393,7 +1393,7 @@ Actor* Actor::FindActor(const MClass* type) const
|
|||||||
Actor* Actor::FindActor(const MClass* type, const StringView& name) const
|
Actor* Actor::FindActor(const MClass* type, const StringView& name) const
|
||||||
{
|
{
|
||||||
CHECK_RETURN(type, nullptr);
|
CHECK_RETURN(type, nullptr);
|
||||||
if (GetClass()->IsSubClassOf(type) && StringUtils::Compare(*_name, *name) == 0)
|
if (GetClass()->IsSubClassOf(type) && _name == name)
|
||||||
return const_cast<Actor*>(this);
|
return const_cast<Actor*>(this);
|
||||||
for (auto child : Children)
|
for (auto child : Children)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -225,6 +225,17 @@ void AnimatedModel::SetMasterPoseModel(AnimatedModel* masterPose)
|
|||||||
_masterPose->AnimationUpdated.Bind<AnimatedModel, &AnimatedModel::OnAnimationUpdated>(this);
|
_masterPose->AnimationUpdated.Bind<AnimatedModel, &AnimatedModel::OnAnimationUpdated>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Array<AnimGraphTraceEvent>& AnimatedModel::GetTraceEvents() const
|
||||||
|
{
|
||||||
|
#if !BUILD_RELEASE
|
||||||
|
if (!GetEnableTracing())
|
||||||
|
{
|
||||||
|
LOG(Warning, "Accessing AnimatedModel.TraceEvents with tracing disabled.");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return GraphInstance.TraceEvents;
|
||||||
|
}
|
||||||
|
|
||||||
#define CHECK_ANIM_GRAPH_PARAM_ACCESS() \
|
#define CHECK_ANIM_GRAPH_PARAM_ACCESS() \
|
||||||
if (!AnimationGraph) \
|
if (!AnimationGraph) \
|
||||||
{ \
|
{ \
|
||||||
@@ -494,6 +505,7 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani
|
|||||||
if (slot.Animation == anim && slot.Name == slotName)
|
if (slot.Animation == anim && slot.Name == slotName)
|
||||||
{
|
{
|
||||||
slot.Animation = nullptr;
|
slot.Animation = nullptr;
|
||||||
|
slot.Reset = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,6 +259,27 @@ public:
|
|||||||
/// <param name="masterPose">The master pose actor to use.</param>
|
/// <param name="masterPose">The master pose actor to use.</param>
|
||||||
API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose);
|
API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables extracting animation playback insights for debugging or custom scripting.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetEnableTracing() const
|
||||||
|
{
|
||||||
|
return GraphInstance.EnableTracing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables extracting animation playback insights for debugging or custom scripting.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() void SetEnableTracing(bool value)
|
||||||
|
{
|
||||||
|
GraphInstance.EnableTracing = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the trace events from the last animation update. Valid only when EnableTracing is active.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="HideInEditor, NoSerialize") const Array<AnimGraphTraceEvent>& GetTraceEvents() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the anim graph instance parameters collection.
|
/// Gets the anim graph instance parameters collection.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user