Merge remote-tracking branch 'origin/1.12' into 1.12
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
#include "Loading/Tasks/LoadAssetTask.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/LogContext.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Physics/Physics.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Profiler/ProfilerMemory.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
@@ -703,6 +705,38 @@ void Asset::onUnload_MainThread()
|
||||
OnUnloaded(this);
|
||||
}
|
||||
|
||||
bool Asset::WaitForInitGraphics()
|
||||
{
|
||||
#define IS_GPU_NOT_READY() (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
|
||||
if (!IsInMainThread() && IS_GPU_NOT_READY())
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ZoneColor(TracyWaitZoneColor);
|
||||
int32 timeout = 1000;
|
||||
while (IS_GPU_NOT_READY() && timeout-- > 0)
|
||||
Platform::Sleep(1);
|
||||
if (IS_GPU_NOT_READY())
|
||||
return true;
|
||||
}
|
||||
#undef IS_GPU_NOT_READY
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Asset::WaitForInitPhysics()
|
||||
{
|
||||
if (!IsInMainThread() && !Physics::DefaultScene)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ZoneColor(TracyWaitZoneColor);
|
||||
int32 timeout = 1000;
|
||||
while (!Physics::DefaultScene && timeout-- > 0)
|
||||
Platform::Sleep(1);
|
||||
if (!Physics::DefaultScene)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool Asset::OnCheckSave(const StringView& path) const
|
||||
|
||||
@@ -285,6 +285,10 @@ protected:
|
||||
virtual void onRename(const StringView& newPath) = 0;
|
||||
#endif
|
||||
|
||||
// Utilities to ensure specific engine systems are initialized before loading asset (eg. assets can be loaded during engine startup).
|
||||
static bool WaitForInitGraphics();
|
||||
static bool WaitForInitPhysics();
|
||||
|
||||
public:
|
||||
// [ManagedScriptingObject]
|
||||
String ToString() const override;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "Engine/Content/Deprecated.h"
|
||||
#include "Engine/Content/Upgraders/ShaderAssetUpgrader.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Materials/MaterialShader.h"
|
||||
#include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h"
|
||||
@@ -157,16 +156,8 @@ Asset::LoadResult Material::load()
|
||||
FlaxChunk* materialParamsChunk;
|
||||
|
||||
// Wait for the GPU Device to be ready (eg. case when loading material before GPU init)
|
||||
#define IS_GPU_NOT_READY() (GPUDevice::Instance == nullptr || GPUDevice::Instance->GetState() != GPUDevice::DeviceState::Ready)
|
||||
if (!IsInMainThread() && IS_GPU_NOT_READY())
|
||||
{
|
||||
int32 timeout = 1000;
|
||||
while (IS_GPU_NOT_READY() && timeout-- > 0)
|
||||
Platform::Sleep(1);
|
||||
if (IS_GPU_NOT_READY())
|
||||
return LoadResult::InvalidData;
|
||||
}
|
||||
#undef IS_GPU_NOT_READY
|
||||
if (WaitForInitGraphics())
|
||||
return LoadResult::CannotLoadData;
|
||||
|
||||
// If engine was compiled with shaders compiling service:
|
||||
// - Material should be changed in need to convert it to the newer version (via Visject Surface)
|
||||
|
||||
@@ -19,10 +19,10 @@ Variant MaterialBase::GetParameterValue(const StringView& name)
|
||||
if (!IsLoaded() && WaitForLoaded())
|
||||
return Variant::Null;
|
||||
const auto param = Params.Get(name);
|
||||
if (IsMaterialInstance() && param && !param->IsOverride() && ((MaterialInstance*)this)->GetBaseMaterial())
|
||||
return ((MaterialInstance*)this)->GetBaseMaterial()->GetParameterValue(name);
|
||||
if (param)
|
||||
{
|
||||
return param->GetValue();
|
||||
}
|
||||
LOG(Warning, "Missing material parameter '{0}' in material {1}", String(name), ToString());
|
||||
return Variant::Null;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the material parameter value.
|
||||
/// </summary>
|
||||
/// <remarks>For material instances that inherit a base material, returned value might come from base material if the current one doesn't override it.</remarks>
|
||||
/// <param name="name">The parameter name.</param>
|
||||
/// <returns>The parameter value.</returns>
|
||||
API_FUNCTION() Variant GetParameterValue(const StringView& name);
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace
|
||||
|
||||
ContentStorageService ContentStorageServiceInstance;
|
||||
|
||||
TimeSpan ContentStorageManager::UnusedStorageLifetime = TimeSpan::FromSeconds(0.5f);
|
||||
TimeSpan ContentStorageManager::UnusedDataChunksLifetime = TimeSpan::FromSeconds(10);
|
||||
|
||||
FlaxStorageReference ContentStorageManager::GetStorage(const StringView& path, bool loadIt)
|
||||
|
||||
@@ -15,7 +15,12 @@ class FLAXENGINE_API ContentStorageManager
|
||||
{
|
||||
public:
|
||||
/// <summary>
|
||||
/// Auto-release timeout for unused asset chunks.
|
||||
/// Auto-release timeout for unused asset files.
|
||||
/// </summary>
|
||||
static TimeSpan UnusedStorageLifetime;
|
||||
|
||||
/// <summary>
|
||||
/// Auto-release timeout for unused asset data chunks.
|
||||
/// </summary>
|
||||
static TimeSpan UnusedDataChunksLifetime;
|
||||
|
||||
|
||||
@@ -286,14 +286,14 @@ FlaxStorage::LockData FlaxStorage::LockSafe()
|
||||
|
||||
uint32 FlaxStorage::GetRefCount() const
|
||||
{
|
||||
return (uint32)Platform::AtomicRead((intptr*)&_refCount);
|
||||
return (uint32)Platform::AtomicRead(&_refCount);
|
||||
}
|
||||
|
||||
bool FlaxStorage::ShouldDispose() const
|
||||
{
|
||||
return Platform::AtomicRead((intptr*)&_refCount) == 0 &&
|
||||
Platform::AtomicRead((intptr*)&_chunksLock) == 0 &&
|
||||
Platform::GetTimeSeconds() - _lastRefLostTime >= 0.5; // TTL in seconds
|
||||
return Platform::AtomicRead(&_refCount) == 0 &&
|
||||
Platform::AtomicRead(&_chunksLock) == 0 &&
|
||||
Platform::GetTimeSeconds() - _lastRefLostTime >= ContentStorageManager::UnusedStorageLifetime.GetTotalSeconds();
|
||||
}
|
||||
|
||||
uint32 FlaxStorage::GetMemoryUsage() const
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
#include "Engine/Level/Actors/EmptyActor.h"
|
||||
#include "Engine/Level/Actors/StaticModel.h"
|
||||
#include "Engine/Level/Actors/AnimatedModel.h"
|
||||
#include "Engine/Level/Prefabs/Prefab.h"
|
||||
#include "Engine/Level/Prefabs/PrefabManager.h"
|
||||
#include "Engine/Level/Scripts/ModelPrefab.h"
|
||||
@@ -82,6 +83,11 @@ bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
|
||||
|
||||
struct PrefabObject
|
||||
{
|
||||
enum
|
||||
{
|
||||
Model,
|
||||
SkinnedModel,
|
||||
} Type;
|
||||
int32 NodeIndex;
|
||||
String Name;
|
||||
String AssetPath;
|
||||
@@ -280,7 +286,7 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = -1;
|
||||
|
||||
// Import all of the objects recursive but use current model data to skip loading file again
|
||||
// Import all the objects recursive but use current model data to skip loading file again
|
||||
options.Cached = &cached;
|
||||
HashSet<String> objectNames;
|
||||
Function<bool(Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)> splitImport = [&context, &autoImportOutput, &objectNames](Options& splitOptions, const StringView& objectName, String& outputPath, MeshData* meshData)
|
||||
@@ -335,12 +341,24 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context)
|
||||
auto& group = meshesByName[groupIndex];
|
||||
|
||||
// Cache object options (nested sub-object import removes the meshes)
|
||||
prefabObject.NodeIndex = group.First()->NodeIndex;
|
||||
prefabObject.Name = group.First()->Name;
|
||||
MeshData* firstMesh = group.First();
|
||||
prefabObject.NodeIndex = firstMesh->NodeIndex;
|
||||
prefabObject.Name = firstMesh->Name;
|
||||
|
||||
splitOptions.Type = ModelTool::ModelType::Model;
|
||||
// Detect model type
|
||||
if ((firstMesh->BlendIndices.HasItems() && firstMesh->BlendWeights.HasItems()) || firstMesh->BlendShapes.HasItems())
|
||||
{
|
||||
splitOptions.Type = ModelTool::ModelType::SkinnedModel;
|
||||
prefabObject.Type = PrefabObject::SkinnedModel;
|
||||
}
|
||||
else
|
||||
{
|
||||
splitOptions.Type = ModelTool::ModelType::Model;
|
||||
prefabObject.Type = PrefabObject::Model;
|
||||
}
|
||||
|
||||
splitOptions.ObjectIndex = groupIndex;
|
||||
if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath, group.First()))
|
||||
if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath, firstMesh))
|
||||
{
|
||||
prefabObjects.Add(prefabObject);
|
||||
}
|
||||
@@ -734,24 +752,38 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M
|
||||
nodeActors.Clear();
|
||||
for (const PrefabObject& e : prefabObjects)
|
||||
{
|
||||
if (e.NodeIndex == nodeIndex)
|
||||
if (e.NodeIndex != nodeIndex)
|
||||
continue;
|
||||
Actor* a = nullptr;
|
||||
switch (e.Type)
|
||||
{
|
||||
case PrefabObject::Model:
|
||||
{
|
||||
auto* actor = New<StaticModel>();
|
||||
actor->SetName(e.Name);
|
||||
if (auto* model = Content::LoadAsync<Model>(e.AssetPath))
|
||||
{
|
||||
actor->Model = model;
|
||||
}
|
||||
nodeActors.Add(actor);
|
||||
a = actor;
|
||||
break;
|
||||
}
|
||||
case PrefabObject::SkinnedModel:
|
||||
{
|
||||
auto* actor = New<AnimatedModel>();
|
||||
if (auto* skinnedModel = Content::LoadAsync<SkinnedModel>(e.AssetPath))
|
||||
actor->SkinnedModel = skinnedModel;
|
||||
a = actor;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
a->SetName(e.Name);
|
||||
nodeActors.Add(a);
|
||||
}
|
||||
Actor* nodeActor = nodeActors.Count() == 1 ? nodeActors[0] : New<EmptyActor>();
|
||||
if (nodeActors.Count() > 1)
|
||||
{
|
||||
for (Actor* e : nodeActors)
|
||||
{
|
||||
e->SetParent(nodeActor);
|
||||
}
|
||||
}
|
||||
if (nodeActors.Count() != 1)
|
||||
{
|
||||
|
||||
@@ -155,6 +155,7 @@ void Screen::SetCursorLock(CursorLockMode mode)
|
||||
bool inRelativeMode = Input::Mouse->IsRelative();
|
||||
if (mode == CursorLockMode::Clipped)
|
||||
win->StartClippingCursor(bounds);
|
||||
#if PLATFORM_SDL
|
||||
else if (mode == CursorLockMode::Locked)
|
||||
{
|
||||
// Use mouse clip region to restrict the cursor in one spot
|
||||
@@ -162,6 +163,10 @@ void Screen::SetCursorLock(CursorLockMode mode)
|
||||
}
|
||||
else if (CursorLock == CursorLockMode::Locked || CursorLock == CursorLockMode::Clipped)
|
||||
win->EndClippingCursor();
|
||||
#else
|
||||
else if (CursorLock == CursorLockMode::Clipped)
|
||||
win->EndClippingCursor();
|
||||
#endif
|
||||
|
||||
// Enable relative mode when cursor is restricted
|
||||
if (mode != CursorLockMode::None)
|
||||
|
||||
@@ -88,9 +88,8 @@ void TerrainMaterialShader::Bind(BindParameters& params)
|
||||
}
|
||||
|
||||
// Bind terrain textures
|
||||
const auto heightmap = drawCall.Terrain.Patch->Heightmap->GetTexture();
|
||||
const auto splatmap0 = drawCall.Terrain.Patch->Splatmap[0] ? drawCall.Terrain.Patch->Splatmap[0]->GetTexture() : nullptr;
|
||||
const auto splatmap1 = drawCall.Terrain.Patch->Splatmap[1] ? drawCall.Terrain.Patch->Splatmap[1]->GetTexture() : nullptr;
|
||||
GPUTexture* heightmap, *splatmap0, *splatmap1;
|
||||
drawCall.Terrain.Patch->GetTextures(heightmap, splatmap0, splatmap1);
|
||||
context->BindSR(0, heightmap);
|
||||
context->BindSR(1, splatmap0);
|
||||
context->BindSR(2, splatmap1);
|
||||
|
||||
@@ -936,7 +936,9 @@ void InputService::Update()
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if PLATFORM_SDL
|
||||
WindowsManager::WindowsLocker.Unlock();
|
||||
#endif
|
||||
|
||||
// Send input events for the focused window
|
||||
for (const auto& e : InputEvents)
|
||||
@@ -990,6 +992,9 @@ void InputService::Update()
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if !PLATFORM_SDL
|
||||
WindowsManager::WindowsLocker.Unlock();
|
||||
#endif
|
||||
|
||||
// Skip if game has no focus to handle the input
|
||||
if (!Engine::HasGameViewportFocus())
|
||||
|
||||
@@ -448,8 +448,7 @@ void SceneObjectsFactory::PrefabSyncData::InitNewObjects()
|
||||
void SceneObjectsFactory::SetupPrefabInstances(Context& context, const PrefabSyncData& data)
|
||||
{
|
||||
PROFILE_CPU_NAMED("SetupPrefabInstances");
|
||||
const int32 count = data.Data.Size();
|
||||
ASSERT(count <= data.SceneObjects.Count());
|
||||
const int32 count = Math::Min<int32>(data.Data.Size(), data.SceneObjects.Count());
|
||||
Dictionary<Guid, Guid> parentIdsLookup;
|
||||
for (int32 i = 0; i < count; i++)
|
||||
{
|
||||
|
||||
@@ -209,6 +209,13 @@ void Collider::CreateShape()
|
||||
// Create shape
|
||||
const bool isTrigger = _isTrigger && CanBeTrigger();
|
||||
_shape = PhysicsBackend::CreateShape(this, shape, Material, IsActiveInHierarchy(), isTrigger);
|
||||
if (!_shape)
|
||||
{
|
||||
LOG(Error, "Failed to create physics shape for actor '{}'", GetNamePath());
|
||||
if (shape.Type == CollisionShape::Types::ConvexMesh && Float3(shape.ConvexMesh.Scale).MinValue() <= 0)
|
||||
LOG(Warning, "Convex Mesh colliders cannot have negative scale");
|
||||
return;
|
||||
}
|
||||
PhysicsBackend::SetShapeContactOffset(_shape, _contactOffset);
|
||||
UpdateLayerBits();
|
||||
}
|
||||
@@ -293,18 +300,20 @@ void Collider::BeginPlay(SceneBeginData* data)
|
||||
if (_shape == nullptr)
|
||||
{
|
||||
CreateShape();
|
||||
|
||||
// Check if parent is a rigidbody
|
||||
const auto rigidBody = dynamic_cast<RigidBody*>(GetParent());
|
||||
if (rigidBody && CanAttach(rigidBody))
|
||||
if (_shape)
|
||||
{
|
||||
// Attach to the rigidbody
|
||||
Attach(rigidBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Be a static collider
|
||||
CreateStaticActor();
|
||||
// Check if parent is a rigidbody
|
||||
const auto rigidBody = dynamic_cast<RigidBody*>(GetParent());
|
||||
if (rigidBody && CanAttach(rigidBody))
|
||||
{
|
||||
// Attach to the rigidbody
|
||||
Attach(rigidBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Be a static collider
|
||||
CreateStaticActor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,8 @@ Asset::LoadResult CollisionData::load()
|
||||
|
||||
CollisionData::LoadResult CollisionData::load(const SerializedOptions* options, byte* dataPtr, int32 dataSize)
|
||||
{
|
||||
if (WaitForInitPhysics())
|
||||
return LoadResult::CannotLoadData;
|
||||
PROFILE_MEM(Physics);
|
||||
|
||||
// Load options
|
||||
|
||||
@@ -1204,6 +1204,8 @@ void ScenePhysX::PreSimulateCloth(int32 i)
|
||||
PROFILE_MEM(PhysicsCloth);
|
||||
auto clothPhysX = ClothsList[i];
|
||||
auto& clothSettings = Cloths[clothPhysX];
|
||||
if (!clothSettings.Actor)
|
||||
return;
|
||||
|
||||
if (clothSettings.Actor->OnPreUpdate())
|
||||
{
|
||||
@@ -2686,10 +2688,13 @@ void* PhysicsBackend::CreateShape(PhysicsColliderActor* collider, const Collisio
|
||||
PxGeometryHolder geometryPhysX;
|
||||
GetShapeGeometry(geometry, geometryPhysX);
|
||||
PxShape* shapePhysX = PhysX->createShape(geometryPhysX.any(), materialsPhysX.Get(), materialsPhysX.Count(), true, shapeFlags);
|
||||
shapePhysX->userData = collider;
|
||||
if (shapePhysX)
|
||||
{
|
||||
shapePhysX->userData = collider;
|
||||
#if PHYSX_DEBUG_NAMING
|
||||
shapePhysX->setName("Shape");
|
||||
shapePhysX->setName("Shape");
|
||||
#endif
|
||||
}
|
||||
return shapePhysX;
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,27 @@ void RenderAntiAliasingPass(RenderContext& renderContext, GPUTexture* input, GPU
|
||||
}
|
||||
}
|
||||
|
||||
void RenderLightBuffer(const SceneRenderTask* task, GPUContext* context, RenderContext& renderContext, GPUTexture* lightBuffer, const GPUTextureDescription& tempDesc)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
|
||||
auto tempBuffer = RenderTargetPool::Get(tempDesc);
|
||||
RENDER_TARGET_POOL_SET_NAME(tempBuffer, "TempBuffer");
|
||||
EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer);
|
||||
PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT);
|
||||
context->ResetRenderTarget();
|
||||
if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
TAA::Instance()->Render(renderContext, tempBuffer, lightBuffer->View());
|
||||
Swap(lightBuffer, tempBuffer);
|
||||
}
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors(task->GetOutputViewport());
|
||||
context->Draw(tempBuffer);
|
||||
RenderTargetPool::Release(tempBuffer);
|
||||
}
|
||||
|
||||
bool Renderer::IsReady()
|
||||
{
|
||||
// Warm up first (state getters initialize content loading so do it for all first)
|
||||
@@ -350,10 +371,12 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
// Perform postFx volumes blending and query before rendering
|
||||
task->CollectPostFxVolumes(renderContext);
|
||||
renderContext.List->BlendSettings();
|
||||
auto aaMode = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::AntiAliasing) ? renderContext.List->Settings.AntiAliasing.Mode : AntialiasingMode::None;
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing && view.IsOrthographicProjection())
|
||||
aaMode = AntialiasingMode::None; // TODO: support TAA in ortho projection (see RenderView::Prepare to jitter projection matrix better)
|
||||
renderContext.List->Settings.AntiAliasing.Mode = aaMode;
|
||||
{
|
||||
auto aaMode = EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::AntiAliasing) ? renderContext.List->Settings.AntiAliasing.Mode : AntialiasingMode::None;
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing && view.IsOrthographicProjection())
|
||||
aaMode = AntialiasingMode::None; // TODO: support TAA in ortho projection (see RenderView::Prepare to jitter projection matrix better)
|
||||
renderContext.List->Settings.AntiAliasing.Mode = aaMode;
|
||||
}
|
||||
|
||||
// Initialize setup
|
||||
RenderSetup& setup = renderContext.List->Setup;
|
||||
@@ -375,7 +398,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
(ssrSettings.Intensity > ZeroTolerance && ssrSettings.TemporalEffect && EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::SSR)) ||
|
||||
renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing;
|
||||
}
|
||||
setup.UseTemporalAAJitter = aaMode == AntialiasingMode::TemporalAntialiasing;
|
||||
setup.UseTemporalAAJitter = renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing;
|
||||
setup.UseGlobalSurfaceAtlas = renderContext.View.Mode == ViewMode::GlobalSurfaceAtlas ||
|
||||
(EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::GI) && renderContext.List->Settings.GlobalIllumination.Mode == GlobalIlluminationMode::DDGI);
|
||||
setup.UseGlobalSDF = (graphicsSettings->EnableGlobalSDF && EnumHasAnyFlags(view.Flags, ViewFlags::GlobalSDF)) ||
|
||||
@@ -630,22 +653,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
}
|
||||
if (renderContext.View.Mode == ViewMode::LightBuffer)
|
||||
{
|
||||
auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext);
|
||||
auto tempBuffer = RenderTargetPool::Get(tempDesc);
|
||||
RENDER_TARGET_POOL_SET_NAME(tempBuffer, "TempBuffer");
|
||||
EyeAdaptationPass::Instance()->Render(renderContext, lightBuffer);
|
||||
PostProcessingPass::Instance()->Render(renderContext, lightBuffer, tempBuffer, colorGradingLUT);
|
||||
context->ResetRenderTarget();
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
TAA::Instance()->Render(renderContext, tempBuffer, lightBuffer->View());
|
||||
Swap(lightBuffer, tempBuffer);
|
||||
}
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors(task->GetOutputViewport());
|
||||
context->Draw(tempBuffer);
|
||||
RenderTargetPool::Release(tempBuffer);
|
||||
RenderLightBuffer(task, context, renderContext, lightBuffer, tempDesc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -656,11 +664,13 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
ReflectionsPass::Instance()->Render(renderContext, *lightBuffer);
|
||||
if (renderContext.View.Mode == ViewMode::Reflections)
|
||||
{
|
||||
context->ResetRenderTarget();
|
||||
context->SetRenderTarget(task->GetOutputView());
|
||||
context->SetViewportAndScissors(task->GetOutputViewport());
|
||||
context->Draw(lightBuffer);
|
||||
RenderTargetPool::Release(lightBuffer);
|
||||
renderContext.List->Settings.ToneMapping.Mode = ToneMappingMode::Neutral;
|
||||
renderContext.List->Settings.Bloom.Enabled = false;
|
||||
renderContext.List->Settings.LensFlares.Intensity = 0.0f;
|
||||
renderContext.List->Settings.CameraArtifacts.GrainAmount = 0.0f;
|
||||
renderContext.List->Settings.CameraArtifacts.ChromaticDistortion = 0.0f;
|
||||
renderContext.List->Settings.CameraArtifacts.VignetteIntensity = 0.0f;
|
||||
RenderLightBuffer(task, context, renderContext, lightBuffer, tempDesc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -716,7 +726,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
|
||||
renderContext.List->RunCustomPostFxPass(context, renderContext, PostProcessEffectLocation::BeforePostProcessingPass, frameBuffer, tempBuffer);
|
||||
|
||||
// Temporal Anti-Aliasing (goes before post processing)
|
||||
if (aaMode == AntialiasingMode::TemporalAntialiasing)
|
||||
if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing)
|
||||
{
|
||||
TAA::Instance()->Render(renderContext, frameBuffer, tempBuffer->View());
|
||||
Swap(frameBuffer, tempBuffer);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#define RAPIDJSON_NEW(x) New<x>
|
||||
#define RAPIDJSON_DELETE(x) Delete(x)
|
||||
#define RAPIDJSON_NOMEMBERITERATORCLASS
|
||||
#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseTrailingCommasFlag
|
||||
//#define RAPIDJSON_MALLOC(size) ::malloc(size)
|
||||
//#define RAPIDJSON_REALLOC(ptr, new_size) ::realloc(ptr, new_size)
|
||||
//#define RAPIDJSON_FREE(ptr) ::free(ptr)
|
||||
|
||||
@@ -8,13 +8,22 @@ using Flax.Build.NativeCpp;
|
||||
/// </summary>
|
||||
public class Terrain : EngineModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size.
|
||||
/// </summary>
|
||||
public static bool WithEditing = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Setup(BuildOptions options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
options.PrivateDependencies.Add("Physics");
|
||||
if (!WithEditing)
|
||||
{
|
||||
options.PublicDefinitions.Add("TERRAIN_EDITING=0");
|
||||
}
|
||||
|
||||
options.PrivateDependencies.Add("Physics");
|
||||
if (options.Target.IsEditor)
|
||||
{
|
||||
options.PrivateDependencies.Add("ContentImporters");
|
||||
|
||||
@@ -306,6 +306,16 @@ void Terrain::SetPhysicalMaterials(const Array<JsonAssetReference<PhysicalMateri
|
||||
}
|
||||
}
|
||||
|
||||
int32 Terrain::GetHeightmapSize() const
|
||||
{
|
||||
return GetChunkSize() * ChunksCountEdge + 1;
|
||||
}
|
||||
|
||||
float Terrain::GetPatchSize() const
|
||||
{
|
||||
return TERRAIN_UNITS_PER_VERTEX * ChunksCountEdge * GetChunkSize();
|
||||
}
|
||||
|
||||
TerrainPatch* Terrain::GetPatch(const Int2& patchCoord) const
|
||||
{
|
||||
return GetPatch(patchCoord.X, patchCoord.Y);
|
||||
|
||||
@@ -21,11 +21,14 @@ struct RenderView;
|
||||
// Amount of units per terrain geometry vertex (can be adjusted per terrain instance using non-uniform scale factor)
|
||||
#define TERRAIN_UNITS_PER_VERTEX 100.0f
|
||||
|
||||
// Enable/disable terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size.
|
||||
#ifndef TERRAIN_EDITING
|
||||
// Enables terrain editing and changing at runtime. If your game doesn't use procedural terrain in game then disable this option to reduce build size.
|
||||
#define TERRAIN_EDITING 1
|
||||
#endif
|
||||
|
||||
// Enable/disable terrain heightmap samples modification and gather. Used by the editor to modify the terrain with the brushes.
|
||||
#define TERRAIN_UPDATING 1
|
||||
// [Deprecated in 1.12, use TERRAIN_EDITING instead]
|
||||
#define TERRAIN_UPDATING (TERRAIN_EDITING)
|
||||
|
||||
// Enable/disable terrain physics collision drawing
|
||||
#define TERRAIN_USE_PHYSICS_DEBUG (USE_EDITOR && 1)
|
||||
@@ -240,6 +243,18 @@ public:
|
||||
return static_cast<int32>(_chunkSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the heightmap texture size (square) used by a single patch (shared by all chunks within that patch).
|
||||
/// </summary>
|
||||
/// <remarks>ChunkSize * ChunksCountEdge + 1</remarks>
|
||||
API_PROPERTY() int32 GetHeightmapSize() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the patch in world-units (square) without actor scale.
|
||||
/// </summary>
|
||||
/// <remarks>UnitsPerVertex * ChunksCountEdge * ChunkSize</remarks>
|
||||
API_PROPERTY() float GetPatchSize() const;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the terrain patches count. Each patch contains 16 chunks arranged into a 4x4 square.
|
||||
/// </summary>
|
||||
@@ -329,7 +344,6 @@ public:
|
||||
API_FUNCTION() void SetChunkOverrideMaterial(API_PARAM(Ref) const Int2& patchCoord, API_PARAM(Ref) const Int2& chunkCoord, MaterialBase* value);
|
||||
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
/// <summary>
|
||||
/// Setups the terrain patch using the specified heightmap data.
|
||||
/// </summary>
|
||||
@@ -352,10 +366,6 @@ public:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupPatchSplatMap(API_PARAM(Ref) const Int2& patchCoord, int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false);
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
#if TERRAIN_EDITING
|
||||
/// <summary>
|
||||
/// Setups the terrain. Clears the existing data.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#if TERRAIN_EDITING
|
||||
#include "Engine/Core/Math/Packed.h"
|
||||
#include "Engine/Core/Collections/ArrayExtensions.h"
|
||||
#include "Engine/Graphics/PixelFormatExtensions.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/RenderView.h"
|
||||
@@ -27,11 +28,6 @@
|
||||
#include "Editor/Editor.h"
|
||||
#include "Engine/ContentImporters/AssetsImportingManager.h"
|
||||
#endif
|
||||
#endif
|
||||
#if TERRAIN_EDITING || TERRAIN_UPDATING
|
||||
#include "Engine/Core/Collections/ArrayExtensions.h"
|
||||
#endif
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Debug/DebugDraw.h"
|
||||
#endif
|
||||
#if TERRAIN_USE_PHYSICS_DEBUG
|
||||
@@ -90,7 +86,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
|
||||
Splatmap[i] = nullptr;
|
||||
}
|
||||
_heightfield = nullptr;
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
_cachedHeightMap.Resize(0);
|
||||
_cachedHolesMask.Resize(0);
|
||||
_wasHeightModified = false;
|
||||
@@ -114,7 +110,7 @@ void TerrainPatch::Init(Terrain* terrain, int16 x, int16 z)
|
||||
|
||||
TerrainPatch::~TerrainPatch()
|
||||
{
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
SAFE_DELETE(_dataHeightmap);
|
||||
for (int32 i = 0; i < TERRAIN_MAX_SPLATMAPS_COUNT; i++)
|
||||
{
|
||||
@@ -134,6 +130,13 @@ RawDataAsset* TerrainPatch::GetHeightfield() const
|
||||
return _heightfield.Get();
|
||||
}
|
||||
|
||||
void TerrainPatch::GetTextures(GPUTexture*& heightmap, GPUTexture*& splatmap0, GPUTexture*& splatmap1) const
|
||||
{
|
||||
heightmap = Heightmap->GetTexture();
|
||||
splatmap0 = Splatmap[0] ? Splatmap[0]->GetTexture() : nullptr;
|
||||
splatmap1 = Splatmap[1] ? Splatmap[1]->GetTexture() : nullptr;
|
||||
}
|
||||
|
||||
void TerrainPatch::RemoveLightmap()
|
||||
{
|
||||
for (auto& chunk : Chunks)
|
||||
@@ -178,7 +181,7 @@ void TerrainPatch::UpdateTransform()
|
||||
_collisionVertices.Resize(0);
|
||||
}
|
||||
|
||||
#if TERRAIN_EDITING || TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
bool IsValidMaterial(const JsonAssetReference<PhysicalMaterial>& e)
|
||||
{
|
||||
@@ -217,7 +220,7 @@ struct TerrainDataUpdateInfo
|
||||
// When using physical materials, then get splatmaps data required for per-triangle material indices
|
||||
void GetSplatMaps()
|
||||
{
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
if (SplatMaps[0])
|
||||
return;
|
||||
if (UsePhysicalMaterials())
|
||||
@@ -1021,7 +1024,7 @@ bool TerrainPatch::SetupHeightMap(int32 heightMapLength, const float* heightMap,
|
||||
_terrain->UpdateBounds();
|
||||
_terrain->UpdateLayerBits();
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
// Invalidate cache
|
||||
_cachedHeightMap.Resize(0);
|
||||
_cachedHolesMask.Resize(0);
|
||||
@@ -1169,7 +1172,7 @@ bool TerrainPatch::SetupSplatMap(int32 index, int32 splatMapLength, const Color3
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
// Invalidate cache
|
||||
_cachedSplatMap[index].Resize(0);
|
||||
_wasSplatmapModified[index] = false;
|
||||
@@ -1191,7 +1194,7 @@ bool TerrainPatch::InitializeHeightMap()
|
||||
return SetupHeightMap(heightmap.Count(), heightmap.Get());
|
||||
}
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
|
||||
float* TerrainPatch::GetHeightmapData()
|
||||
{
|
||||
@@ -2631,7 +2634,7 @@ void TerrainPatch::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
}
|
||||
stream.EndArray();
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
SaveHeightData();
|
||||
SaveSplatData();
|
||||
#endif
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
struct RayCastHit;
|
||||
class TerrainMaterialShader;
|
||||
|
||||
#ifndef TERRAIN_EDITING
|
||||
#define TERRAIN_EDITING 1
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Represents single terrain patch made of 16 terrain chunks.
|
||||
/// </summary>
|
||||
@@ -34,7 +38,7 @@ private:
|
||||
void* _physicsHeightField;
|
||||
CriticalSection _collisionLocker;
|
||||
float _collisionScaleXZ;
|
||||
#if TERRAIN_UPDATING
|
||||
#if TERRAIN_EDITING
|
||||
Array<float> _cachedHeightMap;
|
||||
Array<byte> _cachedHolesMask;
|
||||
Array<Color32> _cachedSplatMap[TERRAIN_MAX_SPLATMAPS_COUNT];
|
||||
@@ -189,6 +193,8 @@ public:
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
void GetTextures(GPUTexture*& heightmap, GPUTexture*& splatmap0, GPUTexture*& splatmap1) const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Removes the lightmap data from the terrain patch.
|
||||
@@ -220,7 +226,7 @@ public:
|
||||
/// <param name="holesMask">The holes mask (optional). Normalized to 0-1 range values with holes mask per-vertex. Must match the heightmap dimensions.</param>
|
||||
/// <param name="forceUseVirtualStorage">If set to <c>true</c> patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, API_PARAM(Ref) const float* heightMap, API_PARAM(Ref) const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
|
||||
API_FUNCTION() bool SetupHeightMap(int32 heightMapLength, const float* heightMap, const byte* holesMask = nullptr, bool forceUseVirtualStorage = false);
|
||||
|
||||
/// <summary>
|
||||
/// Setups the terrain patch layer weights using the specified splatmaps data.
|
||||
@@ -230,14 +236,12 @@ public:
|
||||
/// <param name="splatMap">The splat map. Each array item contains 4 layer weights.</param>
|
||||
/// <param name="forceUseVirtualStorage">If set to <c>true</c> patch will use virtual storage by force. Otherwise it can use normal texture asset storage on drive (valid only during Editor). Runtime-created terrain can only use virtual storage (in RAM).</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, API_PARAM(Ref) const Color32* splatMap, bool forceUseVirtualStorage = false);
|
||||
#endif
|
||||
API_FUNCTION() bool SetupSplatMap(int32 index, int32 splatMapLength, const Color32* splatMap, bool forceUseVirtualStorage = false);
|
||||
|
||||
#if TERRAIN_UPDATING
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the heightmap data.
|
||||
/// Gets the raw pointer to the heightmap data. Array size is square of Terrain.HeightmapSize.
|
||||
/// </summary>
|
||||
/// <returns>The heightmap data.</returns>
|
||||
/// <returns>The heightmap data. Null if empty or failed to access it.</returns>
|
||||
API_FUNCTION() float* GetHeightmapData();
|
||||
|
||||
/// <summary>
|
||||
@@ -246,9 +250,9 @@ public:
|
||||
API_FUNCTION() void ClearHeightmapCache();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the holes mask data.
|
||||
/// Gets the raw pointer to the holes mask data. Array size is square of Terrain.HeightmapSize.
|
||||
/// </summary>
|
||||
/// <returns>The holes mask data.</returns>
|
||||
/// <returns>The holes mask data. Null if empty/unused or failed to access it.</returns>
|
||||
API_FUNCTION() byte* GetHolesMaskData();
|
||||
|
||||
/// <summary>
|
||||
@@ -257,10 +261,10 @@ public:
|
||||
API_FUNCTION() void ClearHolesMaskCache();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw pointer to the splat map data.
|
||||
/// Gets the raw pointer to the splat map data. Array size is square of Terrain.HeightmapSize.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the splatmap texture.</param>
|
||||
/// <returns>The splat map data.</returns>
|
||||
/// <returns>The splat map data. Null if empty/unused or failed to access it.</returns>
|
||||
API_FUNCTION() Color32* GetSplatMapData(int32 index);
|
||||
|
||||
/// <summary>
|
||||
@@ -280,7 +284,7 @@ public:
|
||||
/// <param name="modifiedOffset">The offset from the first row and column of the heightmap data (offset destination x and z start position).</param>
|
||||
/// <param name="modifiedSize">The size of the heightmap to modify (x and z). Amount of samples in each direction.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool ModifyHeightMap(API_PARAM(Ref) const float* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
|
||||
API_FUNCTION() bool ModifyHeightMap(const float* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the terrain patch holes mask with the given samples.
|
||||
@@ -289,7 +293,7 @@ public:
|
||||
/// <param name="modifiedOffset">The offset from the first row and column of the holes map data (offset destination x and z start position).</param>
|
||||
/// <param name="modifiedSize">The size of the holes map to modify (x and z). Amount of samples in each direction.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool ModifyHolesMask(API_PARAM(Ref) const byte* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
|
||||
API_FUNCTION() bool ModifyHolesMask(const byte* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the terrain patch splat map (layers mask) with the given samples.
|
||||
@@ -299,7 +303,7 @@ public:
|
||||
/// <param name="modifiedOffset">The offset from the first row and column of the splat map data (offset destination x and z start position).</param>
|
||||
/// <param name="modifiedSize">The size of the splat map to modify (x and z). Amount of samples in each direction.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION() bool ModifySplatMap(int32 index, API_PARAM(Ref) const Color32* samples, API_PARAM(Ref) const Int2& modifiedOffset, API_PARAM(Ref) const Int2& modifiedSize);
|
||||
API_FUNCTION() bool ModifySplatMap(int32 index, const Color32* samples, const Int2& modifiedOffset, const Int2& modifiedSize);
|
||||
|
||||
private:
|
||||
bool UpdateHeightData(struct TerrainDataUpdateInfo& info, const Int2& modifiedOffset, const Int2& modifiedSize, bool wasHeightRangeChanged, bool wasHeightChanged);
|
||||
|
||||
@@ -588,6 +588,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
|
||||
SERIALIZE(SloppyOptimization);
|
||||
SERIALIZE(LODTargetError);
|
||||
SERIALIZE(ImportMaterials);
|
||||
SERIALIZE(CreateEmptyMaterialSlots);
|
||||
SERIALIZE(ImportMaterialsAsInstances);
|
||||
SERIALIZE(InstanceToImportAs);
|
||||
SERIALIZE(ImportTextures);
|
||||
@@ -643,6 +644,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
|
||||
DESERIALIZE(SloppyOptimization);
|
||||
DESERIALIZE(LODTargetError);
|
||||
DESERIALIZE(ImportMaterials);
|
||||
DESERIALIZE(CreateEmptyMaterialSlots);
|
||||
DESERIALIZE(ImportMaterialsAsInstances);
|
||||
DESERIALIZE(InstanceToImportAs);
|
||||
DESERIALIZE(ImportTextures);
|
||||
@@ -1019,7 +1021,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
options.ImportTypes |= ImportDataTypes::Skeleton;
|
||||
break;
|
||||
case ModelType::Prefab:
|
||||
options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations;
|
||||
options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Skeleton | ImportDataTypes::Animations;
|
||||
if (options.ImportMaterials)
|
||||
options.ImportTypes |= ImportDataTypes::Materials;
|
||||
if (options.ImportTextures)
|
||||
@@ -1045,6 +1047,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
{
|
||||
for (auto& mesh : lod.Meshes)
|
||||
{
|
||||
if (mesh->BlendShapes.IsEmpty())
|
||||
continue;
|
||||
for (int32 blendShapeIndex = mesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--)
|
||||
{
|
||||
auto& blendShape = mesh->BlendShapes[blendShapeIndex];
|
||||
@@ -1209,7 +1213,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
for (int32 i = 0; i < meshesCount; i++)
|
||||
{
|
||||
const auto mesh = data.LODs[0].Meshes[i];
|
||||
if (mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty())
|
||||
|
||||
// If imported mesh has skeleton but no indices or weights then need to setup those (except in Prefab mode when we conditionally import meshes based on type)
|
||||
if ((mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty()) && data.Skeleton.Bones.HasItems() && (options.Type != ModelType::Prefab))
|
||||
{
|
||||
auto indices = Int4::Zero;
|
||||
auto weights = Float4::UnitX;
|
||||
@@ -1326,7 +1332,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
auto& texture = data.Textures[i];
|
||||
|
||||
// Auto-import textures
|
||||
if (autoImportOutput.IsEmpty() || EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Textures) || texture.FilePath.IsEmpty())
|
||||
if (autoImportOutput.IsEmpty() || EnumHasNoneFlags(options.ImportTypes, ImportDataTypes::Textures) || texture.FilePath.IsEmpty() || options.CreateEmptyMaterialSlots)
|
||||
continue;
|
||||
String assetPath = GetAdditionalImportPath(autoImportOutput, importedFileNames, StringUtils::GetFileNameWithoutExtension(texture.FilePath));
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
@@ -1384,6 +1390,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the steps this function performs become irrelevant when we're only creating slots.
|
||||
if (options.CreateEmptyMaterialSlots)
|
||||
continue;
|
||||
|
||||
if (options.ImportMaterialsAsInstances)
|
||||
{
|
||||
// Create material instance
|
||||
@@ -2021,12 +2031,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
#undef REMAP_VERTEX_BUFFER
|
||||
|
||||
// Remap blend shapes
|
||||
dstMesh->BlendShapes.Resize(srcMesh->BlendShapes.Count());
|
||||
dstMesh->BlendShapes.EnsureCapacity(srcMesh->BlendShapes.Count(), false);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < srcMesh->BlendShapes.Count(); blendShapeIndex++)
|
||||
{
|
||||
const auto& srcBlendShape = srcMesh->BlendShapes[blendShapeIndex];
|
||||
auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex];
|
||||
|
||||
BlendShape dstBlendShape;
|
||||
dstBlendShape.Name = srcBlendShape.Name;
|
||||
dstBlendShape.Weight = srcBlendShape.Weight;
|
||||
dstBlendShape.Vertices.EnsureCapacity(srcBlendShape.Vertices.Count());
|
||||
@@ -2035,17 +2044,12 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
auto v = srcBlendShape.Vertices[i];
|
||||
v.VertexIndex = remap[v.VertexIndex];
|
||||
if (v.VertexIndex != ~0u)
|
||||
{
|
||||
dstBlendShape.Vertices.Add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty blend shapes
|
||||
for (int32 blendShapeIndex = dstMesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--)
|
||||
{
|
||||
if (dstMesh->BlendShapes[blendShapeIndex].Vertices.IsEmpty())
|
||||
dstMesh->BlendShapes.RemoveAt(blendShapeIndex);
|
||||
// Add only valid blend shapes
|
||||
if (dstBlendShape.Vertices.HasItems())
|
||||
dstMesh->BlendShapes.Add(dstBlendShape);
|
||||
}
|
||||
|
||||
// Optimize generated LOD
|
||||
@@ -2092,6 +2096,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
|
||||
{
|
||||
for (auto& mesh : lod.Meshes)
|
||||
{
|
||||
if (mesh->BlendShapes.IsEmpty())
|
||||
continue;
|
||||
for (auto& blendShape : mesh->BlendShapes)
|
||||
{
|
||||
// Compute min/max for used vertex indices
|
||||
|
||||
@@ -311,16 +311,19 @@ public:
|
||||
public: // Materials
|
||||
|
||||
// If checked, the importer will create materials for model meshes as specified in the file.
|
||||
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(399), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
bool ImportMaterials = true;
|
||||
// If checked, the importer will create empty material slots for every material without importing materials nor textures.
|
||||
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
bool CreateEmptyMaterialSlots = false;
|
||||
// If checked, the importer will create the model's materials as instances of a base material.
|
||||
API_FIELD(Attributes="EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)")
|
||||
bool ImportMaterialsAsInstances = false;
|
||||
// The material used as the base material that will be instanced as the imported model's material.
|
||||
API_FIELD(Attributes="EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)")
|
||||
AssetReference<MaterialBase> InstanceToImportAs;
|
||||
// If checked, the importer will import texture files used by the model and any embedded texture resources.
|
||||
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
API_FIELD(Attributes="EditorOrder(410), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry)), VisibleIf(nameof(CreateEmptyMaterialSlots), true)")
|
||||
bool ImportTextures = true;
|
||||
// If checked, the importer will try to keep the model's current overridden material slots, instead of importing materials from the source file.
|
||||
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Overridden Materials\"), VisibleIf(nameof(ShowGeometry))")
|
||||
|
||||
@@ -414,6 +414,7 @@ bool TextureTool::UpdateTexture(GPUContext* context, GPUTexture* texture, int32
|
||||
Array<byte> tempData;
|
||||
if (textureFormat != dataFormat)
|
||||
{
|
||||
PROFILE_CPU_NAMED("ConvertTexture");
|
||||
auto dataSampler = PixelFormatSampler::Get(dataFormat);
|
||||
auto textureSampler = PixelFormatSampler::Get(textureFormat);
|
||||
if (!dataSampler || !textureSampler)
|
||||
|
||||
@@ -5,14 +5,14 @@ using System;
|
||||
namespace FlaxEngine.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Radial menu control that arranges child controls (of type Image) in a circle.
|
||||
/// Radial menu control that arranges child controls (of type <see cref="FlaxEngine.GUI.Image"/>) in a circle.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
public class RadialMenu : ContainerControl
|
||||
{
|
||||
private bool _materialIsDirty = true;
|
||||
private float _angle;
|
||||
private float _selectedSegment;
|
||||
private int _selectedSegment;
|
||||
private int _highlightSegment = -1;
|
||||
private MaterialBase _material;
|
||||
private MaterialInstance _materialInstance;
|
||||
@@ -27,7 +27,7 @@ namespace FlaxEngine.GUI
|
||||
private bool ShowMatProp => _material != null;
|
||||
|
||||
/// <summary>
|
||||
/// The material to use for menu background drawing.
|
||||
/// The material used for menu background drawing.
|
||||
/// </summary>
|
||||
[EditorOrder(1)]
|
||||
public MaterialBase Material
|
||||
@@ -44,7 +44,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the edge offset.
|
||||
/// Gets or sets the offset of the outer edge from the bounds of the Control.
|
||||
/// </summary>
|
||||
[EditorOrder(2), Range(0, 1)]
|
||||
public float EdgeOffset
|
||||
@@ -59,7 +59,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thickness.
|
||||
/// Gets or sets the thickness of the menu.
|
||||
/// </summary>
|
||||
[EditorOrder(3), Range(0, 1), VisibleIf(nameof(ShowMatProp))]
|
||||
public float Thickness
|
||||
@@ -74,7 +74,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets control background color (transparent color (alpha=0) means no background rendering).
|
||||
/// Gets or sets control background color (transparent color means no background rendering).
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public new Color BackgroundColor
|
||||
@@ -88,7 +88,7 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the highlight.
|
||||
/// Gets or sets the color of the outer edge highlight.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public Color HighlightColor
|
||||
@@ -130,19 +130,43 @@ namespace FlaxEngine.GUI
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The selected callback
|
||||
/// The material instance of <see cref="Material"/> used to draw the menu.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public MaterialInstance MaterialInstance => _materialInstance;
|
||||
|
||||
/// <summary>
|
||||
/// The selected callback.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Action<int> Selected;
|
||||
|
||||
/// <summary>
|
||||
/// The allow change selection when inside
|
||||
/// Invoked when the hovered segment is changed.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public Action<int> HoveredSelectionChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The selected segment.
|
||||
/// </summary>
|
||||
[HideInEditor]
|
||||
public int SelectedSegment => _selectedSegment;
|
||||
|
||||
/// <summary>
|
||||
/// Allows the selected to change when the mouse is moved in the empty center of the menu.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool AllowChangeSelectionWhenInside;
|
||||
|
||||
/// <summary>
|
||||
/// The center as button
|
||||
/// Allows the selected to change when the mouse is moved outside of the menu.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool AllowChangeSelectionWhenOutside;
|
||||
|
||||
/// <summary>
|
||||
/// Wether the center is a button.
|
||||
/// </summary>
|
||||
[VisibleIf(nameof(ShowMatProp))]
|
||||
public bool CenterAsButton;
|
||||
@@ -225,7 +249,7 @@ namespace FlaxEngine.GUI
|
||||
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
|
||||
var max = (1 - _edgeOffset) * USize * 0.5f;
|
||||
var val = ((USize * 0.5f) - location).Length;
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside || val > max && AllowChangeSelectionWhenOutside)
|
||||
{
|
||||
UpdateAngle(ref location);
|
||||
}
|
||||
@@ -276,7 +300,7 @@ namespace FlaxEngine.GUI
|
||||
var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f;
|
||||
var max = (1 - _edgeOffset) * USize * 0.5f;
|
||||
var val = ((USize * 0.5f) - location).Length;
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside)
|
||||
if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside || val > max && AllowChangeSelectionWhenOutside)
|
||||
{
|
||||
UpdateAngle(ref location);
|
||||
}
|
||||
@@ -347,6 +371,28 @@ namespace FlaxEngine.GUI
|
||||
base.PerformLayout(force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current angle and selected segment of the radial menu based on the specified location inside of the control.
|
||||
/// </summary>
|
||||
/// <param name="location">The position used to determine the angle and segment selection within the radial menu.</param>
|
||||
public void UpdateAngle(ref Float2 location)
|
||||
{
|
||||
float previousSelectedSegment = _selectedSegment;
|
||||
|
||||
var size = new Float2(USize);
|
||||
var p = (size * 0.5f) - location;
|
||||
var sa = (1.0f / _segmentCount) * Mathf.TwoPi;
|
||||
_angle = Mathf.Atan2(p.X, p.Y);
|
||||
_angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa;
|
||||
_selectedSegment = Mathf.RoundToInt((_angle < 0 ? Mathf.TwoPi + _angle : _angle) / sa);
|
||||
if (float.IsNaN(_angle) || float.IsInfinity(_angle))
|
||||
_angle = 0;
|
||||
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
|
||||
|
||||
if (previousSelectedSegment != _selectedSegment)
|
||||
HoveredSelectionChanged?.Invoke((int)_selectedSegment);
|
||||
}
|
||||
|
||||
private void UpdateSelectionColor()
|
||||
{
|
||||
Color color;
|
||||
@@ -368,20 +414,6 @@ namespace FlaxEngine.GUI
|
||||
_materialInstance.SetParameterValue("RadialMenu_SelectionColor", color);
|
||||
}
|
||||
|
||||
private void UpdateAngle(ref Float2 location)
|
||||
{
|
||||
var size = new Float2(USize);
|
||||
var p = (size * 0.5f) - location;
|
||||
var sa = (1.0f / _segmentCount) * Mathf.TwoPi;
|
||||
_angle = Mathf.Atan2(p.X, p.Y);
|
||||
_angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa;
|
||||
_selectedSegment = _angle;
|
||||
_selectedSegment = Mathf.RoundToInt((_selectedSegment < 0 ? Mathf.TwoPi + _selectedSegment : _selectedSegment) / sa);
|
||||
if (float.IsNaN(_angle) || float.IsInfinity(_angle))
|
||||
_angle = 0;
|
||||
_materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi);
|
||||
}
|
||||
|
||||
private static Float2 Rotate2D(Float2 point, float angle)
|
||||
{
|
||||
return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y,
|
||||
|
||||
Reference in New Issue
Block a user