From 311dad7b824d9b9c539aeb0bfd3c068e31a6ea20 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 9 Feb 2021 16:04:47 +0100 Subject: [PATCH] Add Spline Model --- .../MaterialTemplates/Deformable.shader | 32 +- Source/Editor/SceneGraph/Actors/SplineNode.cs | 29 ++ Source/Engine/Content/Assets/Material.cpp | 6 +- .../Materials/DeformableMaterialShader.cpp | 9 +- Source/Engine/Level/Actors/SplineModel.cpp | 473 ++++++++++++++++++ Source/Engine/Level/Actors/SplineModel.h | 116 +++++ 6 files changed, 639 insertions(+), 26 deletions(-) create mode 100644 Source/Engine/Level/Actors/SplineModel.cpp create mode 100644 Source/Engine/Level/Actors/SplineModel.h diff --git a/Content/Editor/MaterialTemplates/Deformable.shader b/Content/Editor/MaterialTemplates/Deformable.shader index 43afa566a..044f7184c 100644 --- a/Content/Editor/MaterialTemplates/Deformable.shader +++ b/Content/Editor/MaterialTemplates/Deformable.shader @@ -19,7 +19,7 @@ float3 ViewDir; float TimeParam; float4 ViewInfo; float4 ScreenSize; -float3 WorldInvScale; +float3 Dummy0; float WorldDeterminantSign; float MeshMinZ; float Segment; @@ -148,9 +148,17 @@ MaterialInput GetMaterialInput(PixelInput input) // Removes the scale vector from the local to world transformation matrix float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld) { - localToWorld[0] *= WorldInvScale.x; - localToWorld[1] *= WorldInvScale.y; - localToWorld[2] *= WorldInvScale.z; + // Extract per axis scales from localToWorld transform + float scaleX = length(localToWorld[0]); + float scaleY = length(localToWorld[1]); + float scaleZ = length(localToWorld[2]); + float3 invScale = float3( + scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, + scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, + scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); + localToWorld[0] *= invScale.x; + localToWorld[1] *= invScale.y; + localToWorld[2] *= invScale.z; return localToWorld; } @@ -278,6 +286,7 @@ VertexOutput VS_SplineModel(ModelInput input) // Apply local transformation of the geometry before deformation float3 position = mul(float4(input.Position, 1), LocalMatrix).xyz; + float4x4 world = LocalMatrix; // Apply spline curve deformation float splineAlpha = saturate((position.z - MeshMinZ) / (MeshMaxZ - MeshMinZ)); @@ -285,9 +294,12 @@ VertexOutput VS_SplineModel(ModelInput input) position.z = splineAlpha; float3x4 splineMatrix = float3x4(SplineDeformation[splineIndex * 3], SplineDeformation[splineIndex * 3 + 1], SplineDeformation[splineIndex * 3 + 2]); position = mul(splineMatrix, float4(position, 1)); + float4x3 splineMatrixT = transpose(splineMatrix); + world = mul(world, float4x4(float4(splineMatrixT[0], 0), float4(splineMatrixT[1], 0), float4(splineMatrixT[2], 0), float4(splineMatrixT[3], 1))); // Compute world space vertex position output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz; + world = mul(world, WorldMatrix); // Compute clip space position output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix); @@ -300,7 +312,7 @@ VertexOutput VS_SplineModel(ModelInput input) // Calculate tanget space to world space transformation matrix for unit vectors float3x3 tangentToLocal = CalcTangentToLocal(input); - float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)WorldMatrix); + float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world); float3x3 tangentToWorld = mul(tangentToLocal, localToWorld); output.Geometry.WorldNormal = tangentToWorld[2]; output.Geometry.WorldTangent.xyz = tangentToWorld[0]; @@ -335,16 +347,6 @@ VertexOutput VS_SplineModel(ModelInput input) return output; } -// Vertex Shader function for Depth Pass -META_VS(true, FEATURE_LEVEL_ES2) -META_VS_IN_ELEMENT(POSITION, 0, R32G32B32_FLOAT, 0, 0, PER_VERTEX, 0, true) -float4 VS_Depth(ModelInput_PosOnly input) : SV_Position -{ - float3 worldPosition = mul(float4(input.Position.xyz, 1), WorldMatrix).xyz; - float4 position = mul(float4(worldPosition, 1), ViewProjectionMatrix); - return position; -} - #if USE_DITHERED_LOD_TRANSITION void ClipLODTransition(PixelInput input) diff --git a/Source/Editor/SceneGraph/Actors/SplineNode.cs b/Source/Editor/SceneGraph/Actors/SplineNode.cs index 179f3267e..1eeb55511 100644 --- a/Source/Editor/SceneGraph/Actors/SplineNode.cs +++ b/Source/Editor/SceneGraph/Actors/SplineNode.cs @@ -1,6 +1,7 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; +using FlaxEditor.GUI.ContextMenu; using FlaxEditor.Modules; using FlaxEngine; using FlaxEngine.Json; @@ -178,6 +179,11 @@ namespace FlaxEditor.SceneGraph.Actors } } + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + public static SceneGraphNode Create(StateData state) { var data = JsonSerializer.Deserialize(state.State); @@ -239,6 +245,11 @@ namespace FlaxEditor.SceneGraph.Actors DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false); } + public override void OnContextMenu(ContextMenu contextMenu) + { + ParentNode.OnContextMenu(contextMenu); + } + public override void OnDispose() { _node = null; @@ -292,6 +303,24 @@ namespace FlaxEditor.SceneGraph.Actors spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f)); } + /// + public override void OnContextMenu(ContextMenu contextMenu) + { + base.OnContextMenu(contextMenu); + + contextMenu.AddButton("Add spline model", OnAddSplineMode); + } + + private void OnAddSplineMode() + { + var actor = new SplineModel + { + StaticFlags = Actor.StaticFlags, + Transform = Actor.Transform, + }; + Editor.Instance.SceneEditing.Spawn(actor, Actor); + } + /// public override void OnDispose() { diff --git a/Source/Engine/Content/Assets/Material.cpp b/Source/Engine/Content/Assets/Material.cpp index 2930aff24..4d1e5d283 100644 --- a/Source/Engine/Content/Assets/Material.cpp +++ b/Source/Engine/Content/Assets/Material.cpp @@ -390,12 +390,12 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) // Prepare auto& info = _shaderHeader.Material.Info; - const bool isSurfaceOrTerrain = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain; + const bool isSurfaceOrTerrainOrDeformable = info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Terrain || info.Domain == MaterialDomain::Deformable; const bool useCustomData = info.ShadingModel == MaterialShadingModel::Subsurface || info.ShadingModel == MaterialShadingModel::Foliage; const bool useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle; const bool useTess = info.TessellationMode != TessellationMethod::None && - RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrain; + RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable; const bool useDistortion = (info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) && info.BlendMode != MaterialBlendMode::Opaque && @@ -457,7 +457,7 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options) options.Macros.Add({ "IS_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] }); options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 1 : 0] }); options.Macros.Add({ "USE_FORWARD", Numbers[useForward ? 1 : 0] }); - options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrain && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); + options.Macros.Add({ "USE_DEFERRED", Numbers[isSurfaceOrTerrainOrDeformable && info.BlendMode == MaterialBlendMode::Opaque ? 1 : 0] }); options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] }); #endif } diff --git a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp index 5adb18470..dafe13d46 100644 --- a/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/DeformableMaterialShader.cpp @@ -26,7 +26,7 @@ PACK_STRUCT(struct DeformableMaterialShaderData { float TimeParam; Vector4 ViewInfo; Vector4 ScreenSize; - Vector3 WorldInvScale; + Vector3 Dummy0; float WorldDeterminantSign; float MeshMinZ; float Segment; @@ -79,13 +79,6 @@ void DeformableMaterialShader::Bind(BindParameters& params) materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds(); materialData->ViewInfo = view.ViewInfo; materialData->ScreenSize = view.ScreenSize; - const float scaleX = Vector3(drawCall.World.M11, drawCall.World.M12, drawCall.World.M13).Length(); - const float scaleY = Vector3(drawCall.World.M21, drawCall.World.M22, drawCall.World.M23).Length(); - const float scaleZ = Vector3(drawCall.World.M31, drawCall.World.M32, drawCall.World.M33).Length(); - materialData->WorldInvScale = Vector3( - scaleX > 0.00001f ? 1.0f / scaleX : 0.0f, - scaleY > 0.00001f ? 1.0f / scaleY : 0.0f, - scaleZ > 0.00001f ? 1.0f / scaleZ : 0.0f); materialData->WorldDeterminantSign = drawCall.WorldDeterminantSign; materialData->Segment = drawCall.Deformable.Segment; materialData->ChunksPerSegment = drawCall.Deformable.ChunksPerSegment; diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp new file mode 100644 index 000000000..7380bf920 --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -0,0 +1,473 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "SplineModel.h" +#include "Spline.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Core/Math/Matrix3x4.h" +#include "Engine/Serialization/Serialization.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTools.h" +#include "Engine/Profiler/ProfilerCPU.h" +#if USE_EDITOR +#include "Editor/Editor.h" +#endif + +#define SPLINE_RESOLUTION 32.0f + +SplineModel::SplineModel(const SpawnParams& params) + : ModelInstanceActor(params) +{ + Model.Changed.Bind(this); + Model.Loaded.Bind(this); +} + +SplineModel::~SplineModel() +{ + SAFE_DELETE_GPU_RESOURCE(_deformationBuffer); + if (_deformationBufferData) + Allocator::Free(_deformationBufferData); +} + +float SplineModel::GetQuality() const +{ + return _quality; +} + +void SplineModel::SetQuality(float value) +{ + value = Math::Clamp(value, 0.0f, 100.0f); + if (Math::NearEqual(value, _quality)) + return; + _quality = value; + OnSplineUpdated(); +} + +float SplineModel::GetBoundsScale() const +{ + return _boundsScale; +} + +void SplineModel::SetBoundsScale(float value) +{ + if (Math::NearEqual(_boundsScale, value)) + return; + _boundsScale = value; + OnSplineUpdated(); +} + +int32 SplineModel::GetLODBias() const +{ + return static_cast(_lodBias); +} + +void SplineModel::SetLODBias(int32 value) +{ + _lodBias = static_cast(Math::Clamp(value, -100, 100)); +} + +int32 SplineModel::GetForcedLOD() const +{ + return static_cast(_forcedLod); +} + +void SplineModel::SetForcedLOD(int32 value) +{ + _forcedLod = static_cast(Math::Clamp(value, -1, 100)); +} + +void SplineModel::OnModelChanged() +{ + Entries.Release(); + + if (Model && !Model->IsLoaded()) + { + OnSplineUpdated(); + } +} + +void SplineModel::OnModelLoaded() +{ + Entries.SetupIfInvalid(Model); + + OnSplineUpdated(); +} + +void SplineModel::OnSplineUpdated() +{ + // Skip updates when actor is disabled or something is missing + if (!_spline || !Model || !Model->IsLoaded() || !IsActiveInHierarchy() || !IsDuringPlay() || _spline->GetSplinePointsCount() < 2) + { + _box = BoundingBox(_transform.Translation, _transform.Translation); + BoundingSphere::FromBox(_box, _sphere); + return; + } + PROFILE_CPU(); + + // Setup model instances over the spline segments + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + const Transform splineTransform = _spline->GetTransform(); + _instances.Resize(segments, false); + BoundingBox localModelBounds; + { + auto& meshes = Model->LODs[0].Meshes; + Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum; + Vector3 corners[8]; + for (int32 j = 0; j < meshes.Count(); j++) + { + const auto& mesh = meshes[j]; + mesh.GetCorners(corners); + + for (int32 i = 0; i < 8; i++) + { + //Vector3::Transform(corners[i], localTransform, tmp); + //_localTransform.LocalToWorld(corners[i], tmp); + + // Transform mesh corner using local spline model transformation but use double-precision to prevent issues when rotating model + tmp = corners[i] * _localTransform.Scale; + double rotation[4] = { (double)_localTransform.Orientation.X, (double)_localTransform.Orientation.Y, (double)_localTransform.Orientation.Z, (double)_localTransform.Orientation.W }; + const double length = sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]); + const double inv = 1.0 / length; + rotation[0] *= inv; + rotation[1] *= inv; + rotation[2] *= inv; + rotation[3] *= inv; + double pos[3] = { (double)tmp.X, (double)tmp.Y, (double)tmp.Z }; + const double x = rotation[0] + rotation[0]; + const double y = rotation[1] + rotation[1]; + const double z = rotation[2] + rotation[2]; + const double wx = rotation[3] * x; + const double wy = rotation[3] * y; + const double wz = rotation[3] * z; + const double xx = rotation[0] * x; + const double xy = rotation[0] * y; + const double xz = rotation[0] * z; + const double yy = rotation[1] * y; + const double yz = rotation[1] * z; + const double zz = rotation[2] * z; + tmp = Vector3( + (float)(pos[0] * (1.0 - yy - zz) + pos[1] * (xy - wz) + pos[2] * (xz + wy)), + (float)(pos[0] * (xy + wz) + pos[1] * (1.0 - xx - zz) + pos[2] * (yz - wx)), + (float)(pos[0] * (xz - wy) + pos[1] * (yz + wx) + pos[2] * (1.0 - xx - yy))); + Vector3::Add(tmp, _localTransform.Translation, tmp); + + min = Vector3::Min(min, tmp); + max = Vector3::Max(max, tmp); + } + } + localModelBounds = BoundingBox(min, max); + } + _meshMinZ = localModelBounds.Minimum.Z; + _meshMaxZ = localModelBounds.Maximum.Z; + Transform chunkLocal, chunkWorld, leftTangent, rightTangent; + Array segmentPoints; + segmentPoints.Resize(chunksPerSegment); + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + + // Find maximum scale over the segment spline and collect the segment positions for bounds + segmentPoints.Clear(); + segmentPoints.Add(end.Value.Translation); + float maxScale = end.Value.Scale.GetAbsolute().MaxValue(); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, chunkLocal); + splineTransform.LocalToWorld(chunkLocal, chunkWorld); + segmentPoints.Add(chunkWorld.Translation); + maxScale = Math::Max(maxScale, chunkWorld.Scale.GetAbsolute().MaxValue()); + } + maxScale = Math::Max(maxScale, _localTransform.Scale.GetAbsolute().MaxValue()); + BoundingSphere::FromPoints(segmentPoints.Get(), segmentPoints.Count(), instance.Sphere); + instance.Sphere.Center += _localTransform.Translation; + instance.Sphere.Radius *= maxScale * _boundsScale; + } + + // Update deformation buffer during next drawing + _deformationDirty = true; + + // Update bounds + _sphere = _instances.First().Sphere; + for (int32 i = 1; i < _instances.Count(); i++) + BoundingSphere::Merge(_sphere, _instances[i].Sphere, _sphere); + BoundingBox::FromSphere(_sphere, _box); +} + +void SplineModel::UpdateDeformationBuffer() +{ + PROFILE_CPU(); + + // Deformation buffer contains precomputed matrices for each chunk of the spline segment (packed with transposed float3x4 matrix) + _deformationDirty = false; + if (!_deformationBuffer) + _deformationBuffer = GPUDevice::Instance->CreateBuffer(GetName()); + const auto& keyframes = _spline->Curve.GetKeyframes(); + const int32 segments = keyframes.Count() - 1; + const int32 chunksPerSegment = Math::Clamp(Math::CeilToInt(SPLINE_RESOLUTION * _quality), 2, 1024); + const int32 count = (chunksPerSegment * segments + 1) * 3; + const uint32 size = count * sizeof(Vector4); + if (_deformationBuffer->GetSize() != size) + { + if (_deformationBufferData) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } + if (_deformationBuffer->Init(GPUBufferDescription::Typed(count, PixelFormat::R32G32B32A32_Float, false, IsTransformStatic() ? GPUResourceUsage::Default : GPUResourceUsage::Dynamic))) + { + LOG(Error, "Failed to initialize the spline model {0} deformation buffer.", ToString()); + return; + } + } + if (!_deformationBufferData) + _deformationBufferData = Allocator::Allocate(size); + _chunksPerSegment = (float)chunksPerSegment; + + // Update pre-calculated matrices for spline chunks + auto ptr = (Matrix3x4*)_deformationBufferData; + const float chunksPerSegmentInv = 1.0f / (float)chunksPerSegment; + Matrix m; + Transform transform, leftTangent, rightTangent; + for (int32 segment = 0; segment < segments; segment++) + { + auto& instance = _instances[segment]; + const auto& start = keyframes[segment]; + const auto& end = keyframes[segment + 1]; + const float length = end.Time - start.Time; + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + for (int32 chunk = 0; chunk < chunksPerSegment; chunk++) + { + const float alpha = (float)chunk * chunksPerSegmentInv; + + // Evaluate transformation at the curve + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + + // Apply spline direction (from position 1st derivative) + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + + // Write transform into deformation buffer + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + ptr++; + } + instance.RotDeterminant = m.RotDeterminant(); + } + + // Add last transformation to prevent issues when sampling spline deformation buffer with alpha=1 + { + const auto& start = keyframes[segments - 1]; + const auto& end = keyframes[segments]; + const float length = end.Time - start.Time; + const float alpha = 1.0f - ZeroTolerance; // Offset to prevent zero derivative at the end of the curve + AnimationUtils::GetTangent(start.Value, start.TangentOut, length, leftTangent); + AnimationUtils::GetTangent(end.Value, end.TangentIn, length, rightTangent); + AnimationUtils::Bezier(start.Value, leftTangent, rightTangent, end.Value, alpha, transform); + Vector3 direction; + AnimationUtils::BezierFirstDerivative(start.Value.Translation, leftTangent.Translation, rightTangent.Translation, end.Value.Translation, alpha, direction); + direction.Normalize(); + Quaternion orientation; + if (direction.IsZero()) + orientation = Quaternion::Identity; + else if (Vector3::Dot(direction, Vector3::Up) >= 0.999f) + Quaternion::RotationAxis(Vector3::Left, PI_HALF, orientation); + else + Quaternion::LookRotation(direction, Vector3::Cross(Vector3::Cross(direction, Vector3::Up), direction), orientation); + transform.Orientation = orientation * transform.Orientation; + transform.GetWorld(m); + ptr->SetMatrixTranspose(m); + } + + // Flush data with GPU + auto context = GPUDevice::Instance->GetMainContext(); + context->UpdateBuffer(_deformationBuffer, _deformationBufferData, size); + + // Static splines are rarely updated so release scratch memory + if (IsTransformStatic()) + { + Allocator::Free(_deformationBufferData); + _deformationBufferData = nullptr; + } +} + +void SplineModel::OnParentChanged() +{ + if (_spline) + { + _spline->SplineUpdated.Unbind(this); + } + + // Base + Actor::OnParentChanged(); + + _spline = Cast(_parent); + if (_spline) + { + _spline->SplineUpdated.Bind(this); + } + + OnSplineUpdated(); +} + +bool SplineModel::HasContentLoaded() const +{ + return (Model == nullptr || Model->IsLoaded()) && Entries.HasContentLoaded(); +} + +void SplineModel::Draw(RenderContext& renderContext) +{ + const DrawPass actorDrawModes = (DrawPass)(DrawModes & renderContext.View.Pass); + if (!_spline || !Model || !Model->IsLoaded() || !Model->CanBeRendered() || actorDrawModes == DrawPass::None) + return; + auto model = Model.Get(); + if (!Entries.IsValidFor(model)) + Entries.Setup(model); + + // Build mesh deformation buffer for the whole spline + if (_deformationDirty) + UpdateDeformationBuffer(); + + // Draw all segments + DrawCall drawCall; + drawCall.InstanceCount = 1; + drawCall.IndirectArgsBuffer = nullptr; + drawCall.IndirectArgsOffset = 0; + drawCall.Deformable.SplineDeformation = _deformationBuffer; + drawCall.Deformable.ChunksPerSegment = _chunksPerSegment; + drawCall.Deformable.MeshMinZ = _meshMinZ; + drawCall.Deformable.MeshMaxZ = _meshMaxZ; + drawCall.Deformable.GeometrySize = _box.GetSize(); + drawCall.PerInstanceRandom = GetPerInstanceRandom(); + _localTransform.GetWorld(drawCall.Deformable.LocalMatrix); + const Transform splineTransform = _spline->GetTransform(); + splineTransform.GetWorld(drawCall.World); + drawCall.ObjectPosition = drawCall.World.GetTranslation() + drawCall.Deformable.LocalMatrix.GetTranslation(); + const float worldDeterminantSign = drawCall.World.RotDeterminant() * drawCall.Deformable.LocalMatrix.RotDeterminant(); + for (int32 segment = 0; segment < _instances.Count(); segment++) + { + auto& instance = _instances[segment]; + if (!renderContext.View.CullingFrustum.Intersects(instance.Sphere)) + continue; + drawCall.Deformable.Segment = (float)segment; + + // Select a proper LOD index (model may be culled) + int32 lodIndex; + if (_forcedLod != -1) + { + lodIndex = _forcedLod; + } + else + { + lodIndex = RenderTools::ComputeModelLOD(model, instance.Sphere.Center, instance.Sphere.Radius, renderContext); + if (lodIndex == -1) + continue; + } + lodIndex += _lodBias + renderContext.View.ModelLODBias; + lodIndex = model->ClampLODIndex(lodIndex); + + // Draw + const auto& lod = model->LODs[lodIndex]; + for (int32 i = 0; i < lod.Meshes.Count(); i++) + { + const auto mesh = &lod.Meshes[i]; + + // Cache data + const auto& entry = Entries[mesh->GetMaterialSlotIndex()]; + if (!entry.Visible || !mesh->IsInitialized()) + continue; + const MaterialSlot& slot = model->MaterialSlots[mesh->GetMaterialSlotIndex()]; + + // Check if skip rendering + const auto shadowsMode = static_cast(entry.ShadowsMode & slot.ShadowsMode); + const auto drawModes = static_cast(actorDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode)); + if (drawModes == DrawPass::None) + continue; + + // Select material + MaterialBase* material; + if (entry.Material && entry.Material->IsLoaded()) + material = entry.Material; + else if (slot.Material && slot.Material->IsLoaded()) + material = slot.Material; + else + material = nullptr; + if (!material || !material->IsDeformable()) + continue; + + // Submit draw call + mesh->GetDrawCallGeometry(drawCall); + drawCall.Material = material; + drawCall.WorldDeterminantSign = Math::FloatSelect(worldDeterminantSign * instance.RotDeterminant, 1, -1); + renderContext.List->AddDrawCall(drawModes, _staticFlags, drawCall, entry.ReceiveDecals); + } + } +} + +void SplineModel::DrawGeneric(RenderContext& renderContext) +{ + Draw(renderContext); +} + +bool SplineModel::IntersectsItself(const Ray& ray, float& distance, Vector3& normal) +{ + return false; +} + +void SplineModel::Serialize(SerializeStream& stream, const void* otherObj) +{ + // Base + ModelInstanceActor::Serialize(stream, otherObj); + + SERIALIZE_GET_OTHER_OBJ(SplineModel); + + SERIALIZE_MEMBER(Quality, _quality); + SERIALIZE_MEMBER(BoundsScale, _boundsScale); + SERIALIZE_MEMBER(LODBias, _lodBias); + SERIALIZE_MEMBER(ForcedLOD, _forcedLod); + SERIALIZE(Model); + SERIALIZE(DrawModes); + + stream.JKEY("Buffer"); + stream.Object(&Entries, other ? &other->Entries : nullptr); +} + +void SplineModel::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + // Base + ModelInstanceActor::Deserialize(stream, modifier); + + DESERIALIZE_MEMBER(Quality, _quality); + DESERIALIZE_MEMBER(BoundsScale, _boundsScale); + DESERIALIZE_MEMBER(LODBias, _lodBias); + DESERIALIZE_MEMBER(ForcedLOD, _forcedLod); + DESERIALIZE(Model); + DESERIALIZE(DrawModes); + + Entries.DeserializeIfExists(stream, "Buffer", modifier); +} + +void SplineModel::OnTransformChanged() +{ + // Base + ModelInstanceActor::OnTransformChanged(); + + OnSplineUpdated(); +} diff --git a/Source/Engine/Level/Actors/SplineModel.h b/Source/Engine/Level/Actors/SplineModel.h new file mode 100644 index 000000000..a7d53af49 --- /dev/null +++ b/Source/Engine/Level/Actors/SplineModel.h @@ -0,0 +1,116 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "ModelInstanceActor.h" +#include "Engine/Content/Assets/Model.h" + +class Spline; + +/// +/// Renders model over the spline segments. +/// +API_CLASS() class FLAXENGINE_API SplineModel : public ModelInstanceActor +{ +DECLARE_SCENE_OBJECT(SplineModel); +private: + + struct Instance + { + BoundingSphere Sphere; + float RotDeterminant; + }; + + float _boundsScale = 1.0f, _quality = 1.0f; + char _lodBias = 0; + char _forcedLod = -1; + bool _deformationDirty = false; + Array _instances; + Spline* _spline = nullptr; + GPUBuffer* _deformationBuffer = nullptr; + void* _deformationBufferData = nullptr; + float _chunksPerSegment, _meshMinZ, _meshMaxZ; + +public: + + ~SplineModel(); + + /// + /// The model asset to draw. + /// + API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")") + AssetReference Model; + + /// + /// The draw passes to use for rendering this object. + /// + API_FIELD(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")") + DrawPass DrawModes = DrawPass::Default; + + /// + /// Gets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY(Attributes="EditorOrder(11), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0.1f, 100.0f, 0.1f)") + float GetQuality() const; + + /// + /// Sets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance. + /// + API_PROPERTY() void SetQuality(float value); + + /// + /// Gets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY(Attributes="EditorOrder(12), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0, 10.0f, 0.1f)") + float GetBoundsScale() const; + + /// + /// Sets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds. + /// + API_PROPERTY() void SetBoundsScale(float value); + + /// + /// Gets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Model\", \"LOD Bias\")") + int32 GetLODBias() const; + + /// + /// Sets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality. + /// + API_PROPERTY() void SetLODBias(int32 value); + + /// + /// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Model\", \"Forced LOD\")") + int32 GetForcedLOD() const; + + /// + /// Sets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature. + /// + API_PROPERTY() void SetForcedLOD(int32 value); + +private: + + void OnModelChanged(); + void OnModelLoaded(); + void OnSplineUpdated(); + void UpdateDeformationBuffer(); + +public: + + // [ModelInstanceActor] + bool HasContentLoaded() const override; + void Draw(RenderContext& renderContext) override; + void DrawGeneric(RenderContext& renderContext) override; + bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override; + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; + void OnParentChanged() override; + +protected: + + // [ModelInstanceActor] + void OnTransformChanged() override; +};