Add Spline Model
This commit is contained in:
@@ -19,7 +19,7 @@ float3 ViewDir;
|
|||||||
float TimeParam;
|
float TimeParam;
|
||||||
float4 ViewInfo;
|
float4 ViewInfo;
|
||||||
float4 ScreenSize;
|
float4 ScreenSize;
|
||||||
float3 WorldInvScale;
|
float3 Dummy0;
|
||||||
float WorldDeterminantSign;
|
float WorldDeterminantSign;
|
||||||
float MeshMinZ;
|
float MeshMinZ;
|
||||||
float Segment;
|
float Segment;
|
||||||
@@ -148,9 +148,17 @@ MaterialInput GetMaterialInput(PixelInput input)
|
|||||||
// Removes the scale vector from the local to world transformation matrix
|
// Removes the scale vector from the local to world transformation matrix
|
||||||
float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld)
|
float3x3 RemoveScaleFromLocalToWorld(float3x3 localToWorld)
|
||||||
{
|
{
|
||||||
localToWorld[0] *= WorldInvScale.x;
|
// Extract per axis scales from localToWorld transform
|
||||||
localToWorld[1] *= WorldInvScale.y;
|
float scaleX = length(localToWorld[0]);
|
||||||
localToWorld[2] *= WorldInvScale.z;
|
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;
|
return localToWorld;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +286,7 @@ VertexOutput VS_SplineModel(ModelInput input)
|
|||||||
|
|
||||||
// Apply local transformation of the geometry before deformation
|
// Apply local transformation of the geometry before deformation
|
||||||
float3 position = mul(float4(input.Position, 1), LocalMatrix).xyz;
|
float3 position = mul(float4(input.Position, 1), LocalMatrix).xyz;
|
||||||
|
float4x4 world = LocalMatrix;
|
||||||
|
|
||||||
// Apply spline curve deformation
|
// Apply spline curve deformation
|
||||||
float splineAlpha = saturate((position.z - MeshMinZ) / (MeshMaxZ - MeshMinZ));
|
float splineAlpha = saturate((position.z - MeshMinZ) / (MeshMaxZ - MeshMinZ));
|
||||||
@@ -285,9 +294,12 @@ VertexOutput VS_SplineModel(ModelInput input)
|
|||||||
position.z = splineAlpha;
|
position.z = splineAlpha;
|
||||||
float3x4 splineMatrix = float3x4(SplineDeformation[splineIndex * 3], SplineDeformation[splineIndex * 3 + 1], SplineDeformation[splineIndex * 3 + 2]);
|
float3x4 splineMatrix = float3x4(SplineDeformation[splineIndex * 3], SplineDeformation[splineIndex * 3 + 1], SplineDeformation[splineIndex * 3 + 2]);
|
||||||
position = mul(splineMatrix, float4(position, 1));
|
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
|
// Compute world space vertex position
|
||||||
output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz;
|
output.Geometry.WorldPosition = mul(float4(position, 1), WorldMatrix).xyz;
|
||||||
|
world = mul(world, WorldMatrix);
|
||||||
|
|
||||||
// Compute clip space position
|
// Compute clip space position
|
||||||
output.Position = mul(float4(output.Geometry.WorldPosition, 1), ViewProjectionMatrix);
|
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
|
// Calculate tanget space to world space transformation matrix for unit vectors
|
||||||
float3x3 tangentToLocal = CalcTangentToLocal(input);
|
float3x3 tangentToLocal = CalcTangentToLocal(input);
|
||||||
float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)WorldMatrix);
|
float3x3 localToWorld = RemoveScaleFromLocalToWorld((float3x3)world);
|
||||||
float3x3 tangentToWorld = mul(tangentToLocal, localToWorld);
|
float3x3 tangentToWorld = mul(tangentToLocal, localToWorld);
|
||||||
output.Geometry.WorldNormal = tangentToWorld[2];
|
output.Geometry.WorldNormal = tangentToWorld[2];
|
||||||
output.Geometry.WorldTangent.xyz = tangentToWorld[0];
|
output.Geometry.WorldTangent.xyz = tangentToWorld[0];
|
||||||
@@ -335,16 +347,6 @@ VertexOutput VS_SplineModel(ModelInput input)
|
|||||||
return output;
|
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
|
#if USE_DITHERED_LOD_TRANSITION
|
||||||
|
|
||||||
void ClipLODTransition(PixelInput input)
|
void ClipLODTransition(PixelInput input)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using FlaxEditor.GUI.ContextMenu;
|
||||||
using FlaxEditor.Modules;
|
using FlaxEditor.Modules;
|
||||||
using FlaxEngine;
|
using FlaxEngine;
|
||||||
using FlaxEngine.Json;
|
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)
|
public static SceneGraphNode Create(StateData state)
|
||||||
{
|
{
|
||||||
var data = JsonSerializer.Deserialize<Data>(state.State);
|
var data = JsonSerializer.Deserialize<Data>(state.State);
|
||||||
@@ -239,6 +245,11 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false);
|
DebugDraw.DrawSphere(new BoundingSphere(pos, 5.0f), Color.YellowGreen, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnContextMenu(ContextMenu contextMenu)
|
||||||
|
{
|
||||||
|
ParentNode.OnContextMenu(contextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnDispose()
|
public override void OnDispose()
|
||||||
{
|
{
|
||||||
_node = null;
|
_node = null;
|
||||||
@@ -292,6 +303,24 @@ namespace FlaxEditor.SceneGraph.Actors
|
|||||||
spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f));
|
spline.AddSplineLocalPoint(new Vector3(0, 0, 100.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void OnDispose()
|
public override void OnDispose()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -390,12 +390,12 @@ void Material::InitCompilationOptions(ShaderCompilationOptions& options)
|
|||||||
|
|
||||||
// Prepare
|
// Prepare
|
||||||
auto& info = _shaderHeader.Material.Info;
|
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 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 useForward = ((info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable) && info.BlendMode != MaterialBlendMode::Opaque) || info.Domain == MaterialDomain::Particle;
|
||||||
const bool useTess =
|
const bool useTess =
|
||||||
info.TessellationMode != TessellationMethod::None &&
|
info.TessellationMode != TessellationMethod::None &&
|
||||||
RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrain;
|
RenderTools::CanSupportTessellation(options.Profile) && isSurfaceOrTerrainOrDeformable;
|
||||||
const bool useDistortion =
|
const bool useDistortion =
|
||||||
(info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) &&
|
(info.Domain == MaterialDomain::Surface || info.Domain == MaterialDomain::Deformable || info.Domain == MaterialDomain::Particle) &&
|
||||||
info.BlendMode != MaterialBlendMode::Opaque &&
|
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_PARTICLE", Numbers[info.Domain == MaterialDomain::Particle ? 1 : 0] });
|
||||||
options.Macros.Add({ "IS_DEFORMABLE", Numbers[info.Domain == MaterialDomain::Deformable ? 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_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] });
|
options.Macros.Add({ "USE_DISTORTION", Numbers[useDistortion ? 1 : 0] });
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ PACK_STRUCT(struct DeformableMaterialShaderData {
|
|||||||
float TimeParam;
|
float TimeParam;
|
||||||
Vector4 ViewInfo;
|
Vector4 ViewInfo;
|
||||||
Vector4 ScreenSize;
|
Vector4 ScreenSize;
|
||||||
Vector3 WorldInvScale;
|
Vector3 Dummy0;
|
||||||
float WorldDeterminantSign;
|
float WorldDeterminantSign;
|
||||||
float MeshMinZ;
|
float MeshMinZ;
|
||||||
float Segment;
|
float Segment;
|
||||||
@@ -79,13 +79,6 @@ void DeformableMaterialShader::Bind(BindParameters& params)
|
|||||||
materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds();
|
materialData->TimeParam = Time::Draw.UnscaledTime.GetTotalSeconds();
|
||||||
materialData->ViewInfo = view.ViewInfo;
|
materialData->ViewInfo = view.ViewInfo;
|
||||||
materialData->ScreenSize = view.ScreenSize;
|
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->WorldDeterminantSign = drawCall.WorldDeterminantSign;
|
||||||
materialData->Segment = drawCall.Deformable.Segment;
|
materialData->Segment = drawCall.Deformable.Segment;
|
||||||
materialData->ChunksPerSegment = drawCall.Deformable.ChunksPerSegment;
|
materialData->ChunksPerSegment = drawCall.Deformable.ChunksPerSegment;
|
||||||
|
|||||||
473
Source/Engine/Level/Actors/SplineModel.cpp
Normal file
473
Source/Engine/Level/Actors/SplineModel.cpp
Normal file
@@ -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<SplineModel, &SplineModel::OnModelChanged>(this);
|
||||||
|
Model.Loaded.Bind<SplineModel, &SplineModel::OnModelLoaded>(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<int32>(_lodBias);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SplineModel::SetLODBias(int32 value)
|
||||||
|
{
|
||||||
|
_lodBias = static_cast<char>(Math::Clamp(value, -100, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 SplineModel::GetForcedLOD() const
|
||||||
|
{
|
||||||
|
return static_cast<int32>(_forcedLod);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SplineModel::SetForcedLOD(int32 value)
|
||||||
|
{
|
||||||
|
_forcedLod = static_cast<char>(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<Vector3> 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<SplineModel, &SplineModel::OnSplineUpdated>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base
|
||||||
|
Actor::OnParentChanged();
|
||||||
|
|
||||||
|
_spline = Cast<Spline>(_parent);
|
||||||
|
if (_spline)
|
||||||
|
{
|
||||||
|
_spline->SplineUpdated.Bind<SplineModel, &SplineModel::OnSplineUpdated>(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<ShadowsCastingMode>(entry.ShadowsMode & slot.ShadowsMode);
|
||||||
|
const auto drawModes = static_cast<DrawPass>(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();
|
||||||
|
}
|
||||||
116
Source/Engine/Level/Actors/SplineModel.h
Normal file
116
Source/Engine/Level/Actors/SplineModel.h
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders model over the spline segments.
|
||||||
|
/// </summary>
|
||||||
|
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<Instance> _instances;
|
||||||
|
Spline* _spline = nullptr;
|
||||||
|
GPUBuffer* _deformationBuffer = nullptr;
|
||||||
|
void* _deformationBufferData = nullptr;
|
||||||
|
float _chunksPerSegment, _meshMinZ, _meshMaxZ;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
~SplineModel();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The model asset to draw.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(Attributes="EditorOrder(20), DefaultValue(null), EditorDisplay(\"Model\")")
|
||||||
|
AssetReference<Model> Model;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The draw passes to use for rendering this object.
|
||||||
|
/// </summary>
|
||||||
|
API_FIELD(Attributes="EditorOrder(15), DefaultValue(DrawPass.Default), EditorDisplay(\"Model\")")
|
||||||
|
DrawPass DrawModes = DrawPass::Default;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="EditorOrder(11), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0.1f, 100.0f, 0.1f)")
|
||||||
|
float GetQuality() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the spline model quality scale. Higher values improve the spline representation (better tessellation) but reduce performance.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() void SetQuality(float value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="EditorOrder(12), DefaultValue(1.0f), EditorDisplay(\"Model\"), Limit(0, 10.0f, 0.1f)")
|
||||||
|
float GetBoundsScale() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the model bounds scale. It is useful when using Position Offset to animate the vertices of the object outside of its bounds.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() void SetBoundsScale(float value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="EditorOrder(40), DefaultValue(0), Limit(-100, 100, 0.1f), EditorDisplay(\"Model\", \"LOD Bias\")")
|
||||||
|
int32 GetLODBias() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the model Level Of Detail bias value. Allows to increase or decrease rendered model quality.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY() void SetLODBias(int32 value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature.
|
||||||
|
/// </summary>
|
||||||
|
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(-1), Limit(-1, 100, 0.1f), EditorDisplay(\"Model\", \"Forced LOD\")")
|
||||||
|
int32 GetForcedLOD() const;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the model forced Level Of Detail index. Allows to bind the given model LOD to show. Value -1 disables this feature.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user