Merge branch 'master' into vehicle-physics

This commit is contained in:
ruan
2024-01-21 09:02:18 -04:00
55 changed files with 901 additions and 323 deletions

View File

@@ -99,10 +99,7 @@ void BlendPoseBucketInit(AnimGraphInstanceData::Bucket& bucket)
void StateMachineBucketInit(AnimGraphInstanceData::Bucket& bucket)
{
bucket.StateMachine.LastUpdateFrame = 0;
bucket.StateMachine.CurrentState = nullptr;
bucket.StateMachine.ActiveTransition = nullptr;
bucket.StateMachine.TransitionPosition = 0.0f;
Platform::MemoryClear(&bucket.StateMachine, sizeof(bucket.StateMachine));
}
void SlotBucketInit(AnimGraphInstanceData::Bucket& bucket)

View File

@@ -39,6 +39,7 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32
void AnimGraphInstanceData::Clear()
{
ClearState();
Slots.Clear();
Parameters.Resize(0);
}
@@ -55,7 +56,7 @@ void AnimGraphInstanceData::ClearState()
RootMotion = Transform::Identity;
State.Resize(0);
NodesPose.Resize(0);
Slots.Clear();
TraceEvents.Clear();
}
void AnimGraphInstanceData::Invalidate()
@@ -238,6 +239,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
}
for (auto& e : data.ActiveEvents)
e.Hit = false;
data.TraceEvents.Clear();
// Init empty nodes data
context.EmptyNodes.RootMotion = Transform::Identity;
@@ -282,6 +284,20 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
}
}
}
#if !BUILD_RELEASE
{
// Perform sanity check on nodes pose to prevent crashes due to NaNs
bool anyInvalid = animResult->RootMotion.IsNanOrInfinity();
for (int32 i = 0; i < animResult->Nodes.Count(); i++)
anyInvalid |= animResult->Nodes.Get()[i].IsNanOrInfinity();
if (anyInvalid)
{
LOG(Error, "Animated Model pose contains NaNs due to animations sampling/blending bug.");
context.Data = nullptr;
return;
}
}
#endif
SkeletonData* animResultSkeleton = &skeleton;
// Retarget animation when using output pose from other skeleton

View File

@@ -129,6 +129,8 @@ public:
UseDefaultRule = 4,
InterruptionRuleRechecking = 8,
InterruptionInstant = 16,
InterruptionSourceState = 32,
InterruptionDestinationState = 64,
};
public:
@@ -197,6 +199,22 @@ struct FLAXENGINE_API AnimGraphSlot
float BlendOutTime = 0.0f;
int32 LoopCount = 0;
bool Pause = false;
bool Reset = false;
};
/// <summary>
/// The animation graph state container for a single node playback trace (eg. animation sample info or state transition). Can be used by Anim Graph debugging or custom scripting.
/// </summary>
API_STRUCT() struct FLAXENGINE_API AnimGraphTraceEvent
{
DECLARE_SCRIPTING_TYPE_MINIMAL(AnimGraphTraceEvent);
// Contextual asset used. For example, sampled animation.
API_FIELD() Asset* Asset = nullptr;
// Generic value contextual to playback type (eg. animation sample position).
API_FIELD() float Value = 0;
// Identifier of the node in the graph.
API_FIELD() uint32 NodeId = 0;
};
/// <summary>
@@ -240,7 +258,10 @@ public:
uint64 LastUpdateFrame;
AnimGraphNode* CurrentState;
AnimGraphStateTransition* ActiveTransition;
AnimGraphStateTransition* BaseTransition;
AnimGraphNode* BaseTransitionState;
float TransitionPosition;
float BaseTransitionPosition;
};
struct SlotBucket
@@ -357,6 +378,12 @@ public:
/// </summary>
void InvokeAnimEvents();
public:
// Anim Graph logic tracing feature that allows to collect insights of animations sampling and skeleton poses operations.
bool EnableTracing = false;
// Trace events collected when using EnableTracing option.
Array<AnimGraphTraceEvent> TraceEvents;
private:
struct OutgoingEvent
{
@@ -836,7 +863,7 @@ public:
}
/// <summary>
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children).
/// Resets all the state bucket used by the given graph including sub-graphs (total). Can be used to reset the animation state of the nested graph (including children).
/// </summary>
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
@@ -865,5 +892,7 @@ private:
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
Variant SampleState(AnimGraphNode* state);
void InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition = nullptr);
AnimGraphStateTransition* UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState = nullptr);
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
};

View File

@@ -219,6 +219,16 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
const float animPos = GetAnimSamplePos(length, anim, pos, speed);
const float animPrevPos = GetAnimSamplePos(length, anim, prevPos, speed);
// Add to trace
auto& context = Context.Get();
if (context.Data->EnableTracing)
{
auto& trace = context.Data->TraceEvents.AddOne();
trace.Asset = anim;
trace.Value = animPos;
trace.NodeId = node->ID;
}
// Evaluate nested animations
bool hasNested = false;
if (anim->NestedAnims.Count() != 0)
@@ -460,10 +470,12 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
{
ANIM_GRAPH_PROFILE_EVENT("Blend Pose");
if (isnan(alpha) || isinf(alpha))
alpha = 0;
alpha = Math::Saturate(alpha);
alpha = AlphaBlend::Process(alpha, alphaMode);
const auto nodes = node->GetNodes(this);
auto nodesA = static_cast<AnimGraphImpulse*>(poseA.AsPointer);
auto nodesB = static_cast<AnimGraphImpulse*>(poseB.AsPointer);
if (!ANIM_GRAPH_IS_VALID_PTR(poseA))
@@ -484,32 +496,40 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
{
// Prepare
auto& data = state->Data.State;
if (data.Graph == nullptr || data.Graph->GetRootNode() == nullptr)
{
// Invalid state graph
return Value::Null;
}
ANIM_GRAPH_PROFILE_EVENT("Evaluate State");
// Evaluate state
auto rootNode = data.Graph->GetRootNode();
auto result = eatBox((Node*)rootNode, &rootNode->Boxes[0]);
return result;
return eatBox((Node*)rootNode, &rootNode->Boxes[0]);
}
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
void AnimGraphExecutor::InitStateTransition(AnimGraphContext& context, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, AnimGraphStateTransition* transition)
{
// Reset transiton
stateMachineBucket.ActiveTransition = transition;
stateMachineBucket.TransitionPosition = 0.0f;
// End base transition
if (stateMachineBucket.BaseTransition)
{
ResetBuckets(context, stateMachineBucket.BaseTransitionState->Data.State.Graph);
stateMachineBucket.BaseTransition = nullptr;
stateMachineBucket.BaseTransitionState = nullptr;
stateMachineBucket.BaseTransitionPosition = 0.0f;
}
}
AnimGraphStateTransition* AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphNode* state, AnimGraphNode* ignoreState)
{
int32 transitionIndex = 0;
const AnimGraphNode::StateBaseData& stateData = state->Data.State;
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
{
const uint16 idx = stateData.Transitions[transitionIndex];
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
auto& transition = stateMachineData.Graph->StateTransitions[idx];
if (transition.Destination == stateMachineBucket.CurrentState)
if (transition.Destination == state || transition.Destination == ignoreState)
{
// Ignore transition to the current state
transitionIndex++;
@@ -517,7 +537,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
}
// Evaluate source state transition data (position, length, etc.)
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
const Value sourceStatePtr = SampleState(state);
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
{
@@ -538,6 +558,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
if (transition.RuleGraph && !useDefaultRule)
{
// Execute transition rule
ANIM_GRAPH_PROFILE_EVENT("Rule");
auto rootNode = transition.RuleGraph->GetRootNode();
ASSERT(rootNode);
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
@@ -560,10 +581,7 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
canEnter = true;
if (canEnter)
{
// Start transition
stateMachineBucket.ActiveTransition = &transition;
stateMachineBucket.TransitionPosition = 0.0f;
break;
return &transition;
}
// Skip after Solo transition
@@ -573,6 +591,18 @@ void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const
transitionIndex++;
}
// No transition
return nullptr;
}
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
{
AnimGraphStateTransition* transition = UpdateStateTransitions(context, stateMachineData, stateMachineBucket.CurrentState);
if (transition)
{
InitStateTransition(context, stateMachineBucket, transition);
}
}
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
@@ -1367,33 +1397,29 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
// Use 1D blend if points are on the same line (degenerated triangle)
// TODO: simplify this code
struct BlendData
{
float AlphaX, AlphaY;
Animation* AnimA, *AnimB;
const Float4* AnimAd, *AnimBd;
};
BlendData blendData;
if (v1.Y >= v0.Y)
{
if (p.Y < v0.Y && v1.Y >= v0.Y)
{
const float alpha = p.Y / v0.Y;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, bAnim, aData.W, bData.W, alpha);
}
blendData = { p.Y, v0.Y, aAnim, bAnim, &aData, &bData };
else
{
const float alpha = (p.Y - v0.Y) / (v1.Y - v0.Y);
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, bAnim, cAnim, bData.W, cData.W, alpha);
}
blendData = { p.Y - v0.Y, v1.Y - v0.Y, bAnim, cAnim, &bData, &cData };
}
else
{
if (p.Y < v1.Y)
{
const float alpha = p.Y / v1.Y;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, aAnim, cAnim, aData.W, cData.W, alpha);
}
blendData = { p.Y, v1.Y, aAnim, cAnim, &aData, &cData };
else
{
const float alpha = (p.Y - v1.Y) / (v0.Y - v1.Y);
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, cAnim, bAnim, cData.W, bData.W, alpha);
}
blendData = { p.Y - v1.Y, v0.Y - v1.Y, cAnim, bAnim, &cData, &bData };
}
const float alpha = Math::IsZero(blendData.AlphaY) ? 0.0f : blendData.AlphaX / blendData.AlphaY;
value = SampleAnimationsWithBlend(node, loop, data.Length, startTimePos, bucket.TimePosition, newTimePos, blendData.AnimA, blendData.AnimB, blendData.AnimAd->W, blendData.AnimBd->W, alpha);
}
else
{
@@ -1505,10 +1531,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Blend two animations
{
const float alpha = Math::Saturate(bucket.TransitionPosition / blendDuration);
const float alpha = bucket.TransitionPosition / blendDuration;
const auto valueA = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + bucket.PreviousBlendPoseIndex), Value::Null);
const auto valueB = tryGetValue(node->GetBox(FirstBlendPoseBoxIndex + poseIndex), Value::Null);
value = Blend(node, valueA, valueB, alpha, mode);
}
@@ -1614,22 +1639,21 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Enter to the first state pointed by the Entry node (without transitions)
bucket.CurrentState = data.Graph->GetRootNode();
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
InitStateTransition(context, bucket);
// Reset all state buckets pof the graphs and nodes included inside the state machine
// Reset all state buckets of the graphs and nodes included inside the state machine
ResetBuckets(context, data.Graph);
}
#define END_TRANSITION() \
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
bucket.CurrentState = bucket.ActiveTransition->Destination; \
bucket.ActiveTransition = nullptr; \
bucket.TransitionPosition = 0.0f
InitStateTransition(context, bucket)
// Update the active transition
if (bucket.ActiveTransition)
{
bucket.TransitionPosition += context.DeltaTime;
ASSERT(bucket.CurrentState);
// Check for transition end
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
@@ -1637,38 +1661,70 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
END_TRANSITION();
}
// Check for transition interruption
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking) &&
EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule) &&
bucket.ActiveTransition->RuleGraph)
{
const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
if (bucket.ActiveTransition->RuleGraph && !useDefaultRule)
// Execute transition rule
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
{
// Execute transition rule
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
bool cancelTransition = false;
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
{
bool cancelTransition = false;
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
cancelTransition = true;
}
else
{
// Blend back to the source state (remove currently applied delta and rewind transition)
bucket.TransitionPosition -= context.DeltaTime;
bucket.TransitionPosition -= context.DeltaTime;
if (bucket.TransitionPosition <= ZeroTolerance)
{
cancelTransition = true;
}
else
{
// Blend back to the source state (remove currently applied delta and rewind transition)
bucket.TransitionPosition -= context.DeltaTime;
bucket.TransitionPosition -= context.DeltaTime;
if (bucket.TransitionPosition <= ZeroTolerance)
{
cancelTransition = true;
}
}
if (cancelTransition)
{
// Go back to the source state
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
bucket.ActiveTransition = nullptr;
bucket.TransitionPosition = 0.0f;
}
}
if (cancelTransition)
{
// Go back to the source state
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
InitStateTransition(context, bucket);
}
}
}
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionSourceState))
{
// Try to interrupt with any other transition in the source state (except the current transition)
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.CurrentState, bucket.ActiveTransition->Destination))
{
// Change active transition to the interrupted one
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
{
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
bucket.BaseTransition = bucket.ActiveTransition;
bucket.BaseTransitionState = bucket.CurrentState;
bucket.BaseTransitionPosition = bucket.TransitionPosition;
}
bucket.ActiveTransition = transition;
bucket.TransitionPosition = 0.0f;
}
}
if (bucket.ActiveTransition && !bucket.BaseTransition && EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionDestinationState))
{
// Try to interrupt with any other transition in the destination state (except the transition back to the current state if exists)
if (AnimGraphStateTransition* transition = UpdateStateTransitions(context, data, bucket.ActiveTransition->Destination, bucket.CurrentState))
{
// Change active transition to the interrupted one
if (EnumHasNoneFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
{
// Cache the current blending state to be used as a base when blending towards new destination state (seamless blending after interruption)
bucket.BaseTransition = bucket.ActiveTransition;
bucket.BaseTransitionState = bucket.CurrentState;
bucket.BaseTransitionPosition = bucket.TransitionPosition;
}
bucket.CurrentState = bucket.ActiveTransition->Destination;
bucket.ActiveTransition = transition;
bucket.TransitionPosition = 0.0f;
}
}
}
@@ -1697,9 +1753,23 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
}
}
// Sample the current state
const auto currentState = SampleState(bucket.CurrentState);
value = currentState;
if (bucket.BaseTransitionState)
{
// Sample the other state (eg. when blending from interrupted state to the another state from the old destination)
value = SampleState(bucket.BaseTransitionState);
if (bucket.BaseTransition)
{
// Evaluate the base pose from the time when transition was interrupted
const auto destinationState = SampleState(bucket.BaseTransition->Destination);
const float alpha = bucket.BaseTransitionPosition / bucket.BaseTransition->BlendDuration;
value = Blend(node, value, destinationState, alpha, bucket.BaseTransition->BlendMode);
}
}
else
{
// Sample the current state
value = SampleState(bucket.CurrentState);
}
// Handle active transition blending
if (bucket.ActiveTransition)
@@ -1708,14 +1778,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
const auto destinationState = SampleState(bucket.ActiveTransition->Destination);
// Perform blending
const float alpha = Math::Saturate(bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration);
value = Blend(node, currentState, destinationState, alpha, bucket.ActiveTransition->BlendMode);
const float alpha = bucket.TransitionPosition / bucket.ActiveTransition->BlendDuration;
value = Blend(node, value, destinationState, alpha, bucket.ActiveTransition->BlendMode);
}
// Update bucket
bucket.LastUpdateFrame = context.CurrentFrameIndex;
#undef END_TRANSITION
break;
}
// Entry
@@ -2102,6 +2170,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
auto& slot = slots[bucket.Index];
Animation* anim = slot.Animation;
ASSERT(slot.Animation && slot.Animation->IsLoaded());
if (slot.Reset)
{
// Start from the begining
slot.Reset = false;
bucket.TimePosition = 0.0f;
}
const float deltaTime = slot.Pause ? 0.0f : context.DeltaTime * slot.Speed;
const float length = anim->GetLength();
const bool loop = bucket.LoopsLeft != 0;
@@ -2130,7 +2204,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Blend out
auto input = tryGetValue(node->GetBox(1), Value::Null);
bucket.BlendOutPosition += deltaTime;
const float alpha = Math::Saturate(bucket.BlendOutPosition / slot.BlendOutTime);
const float alpha = bucket.BlendOutPosition / slot.BlendOutTime;
value = Blend(node, value, input, alpha, AlphaBlendMode::HermiteCubic);
}
else if (bucket.LoopsDone == 0 && slot.BlendInTime > 0.0f && bucket.BlendInPosition < slot.BlendInTime)
@@ -2138,7 +2212,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
// Blend in
auto input = tryGetValue(node->GetBox(1), Value::Null);
bucket.BlendInPosition += deltaTime;
const float alpha = Math::Saturate(bucket.BlendInPosition / slot.BlendInTime);
const float alpha = bucket.BlendInPosition / slot.BlendInTime;
value = Blend(node, input, value, alpha, AlphaBlendMode::HermiteCubic);
}
break;

View File

@@ -12,22 +12,22 @@ const int32 CachedDaysToMonth[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273,
DateTime::DateTime(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second, int32 millisecond)
{
ASSERT_LOW_LAYER(Validate(year, month, day, hour, minute, second, millisecond));
int32 totalDays = 0;
int32 daysSum = 0;
if (month > 2 && IsLeapYear(year))
totalDays++;
daysSum++;
year--;
month--;
totalDays += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
Ticks = totalDays * Constants::TicksPerDay
+ hour * Constants::TicksPerHour
+ minute * Constants::TicksPerMinute
+ second * Constants::TicksPerSecond
+ millisecond * Constants::TicksPerMillisecond;
daysSum += year * 365 + year / 4 - year / 100 + year / 400 + CachedDaysToMonth[month] + day - 1;
Ticks = daysSum * TimeSpan::TicksPerDay
+ hour * TimeSpan::TicksPerHour
+ minute * TimeSpan::TicksPerMinute
+ second * TimeSpan::TicksPerSecond
+ millisecond * TimeSpan::TicksPerMillisecond;
}
DateTime DateTime::GetDate() const
{
return DateTime(Ticks - Ticks % Constants::TicksPerDay);
return DateTime(Ticks - Ticks % TimeSpan::TicksPerDay);
}
void DateTime::GetDate(int32& year, int32& month, int32& day) const
@@ -35,8 +35,7 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
// Based on:
// Fliegel, H. F. and van Flandern, T. C.,
// Communications of the ACM, Vol. 11, No. 10 (October 1968).
int32 l = Math::FloorToInt(static_cast<float>(GetJulianDay() + 0.5)) + 68569;
int32 l = Math::FloorToInt((float)(GetDate().GetJulianDay() + 0.5)) + 68569;
const int32 n = 4 * l / 146097;
l = l - (146097 * n + 3) / 4;
int32 i = 4000 * (l + 1) / 1461001;
@@ -46,7 +45,6 @@ void DateTime::GetDate(int32& year, int32& month, int32& day) const
l = j / 11;
j = j + 2 - 12 * l;
i = 100 * (n - 49) + i + l;
year = i;
month = j;
day = k;
@@ -61,7 +59,7 @@ int32 DateTime::GetDay() const
DayOfWeek DateTime::GetDayOfWeek() const
{
return static_cast<DayOfWeek>((Ticks / Constants::TicksPerDay) % 7);
return static_cast<DayOfWeek>((Ticks / TimeSpan::TicksPerDay) % 7);
}
int32 DateTime::GetDayOfYear() const
@@ -75,7 +73,7 @@ int32 DateTime::GetDayOfYear() const
int32 DateTime::GetHour() const
{
return static_cast<int32>(Ticks / Constants::TicksPerHour % 24);
return static_cast<int32>(Ticks / TimeSpan::TicksPerHour % 24);
}
int32 DateTime::GetHour12() const
@@ -90,7 +88,7 @@ int32 DateTime::GetHour12() const
double DateTime::GetJulianDay() const
{
return 1721425.5 + static_cast<double>(Ticks) / Constants::TicksPerDay;
return 1721425.5 + static_cast<double>(Ticks) / TimeSpan::TicksPerDay;
}
double DateTime::GetModifiedJulianDay() const
@@ -100,12 +98,12 @@ double DateTime::GetModifiedJulianDay() const
int32 DateTime::GetMillisecond() const
{
return static_cast<int32>(Ticks / Constants::TicksPerMillisecond % 1000);
return static_cast<int32>(Ticks / TimeSpan::TicksPerMillisecond % 1000);
}
int32 DateTime::GetMinute() const
{
return static_cast<int32>(Ticks / Constants::TicksPerMinute % 60);
return static_cast<int32>(Ticks / TimeSpan::TicksPerMinute % 60);
}
int32 DateTime::GetMonth() const
@@ -122,12 +120,12 @@ MonthOfYear DateTime::GetMonthOfYear() const
int32 DateTime::GetSecond() const
{
return static_cast<int32>(Ticks / Constants::TicksPerSecond % 60);
return static_cast<int32>(Ticks / TimeSpan::TicksPerSecond % 60);
}
TimeSpan DateTime::GetTimeOfDay() const
{
return TimeSpan(Ticks % Constants::TicksPerDay);
return TimeSpan(Ticks % TimeSpan::TicksPerDay);
}
int32 DateTime::GetYear() const
@@ -137,11 +135,6 @@ int32 DateTime::GetYear() const
return year;
}
int32 DateTime::ToUnixTimestamp() const
{
return static_cast<int32>((Ticks - DateTime(1970, 1, 1).Ticks) / Constants::TicksPerSecond);
}
int32 DateTime::DaysInMonth(int32 year, int32 month)
{
ASSERT_LOW_LAYER((month >= 1) && (month <= 12));
@@ -155,16 +148,6 @@ int32 DateTime::DaysInYear(int32 year)
return IsLeapYear(year) ? 366 : 365;
}
DateTime DateTime::FromJulianDay(double julianDay)
{
return DateTime(static_cast<int64>((julianDay - 1721425.5) * Constants::TicksPerDay));
}
DateTime DateTime::FromUnixTimestamp(int32 unixTime)
{
return DateTime(1970, 1, 1) + TimeSpan(static_cast<int64>(unixTime) * Constants::TicksPerSecond);
}
bool DateTime::IsLeapYear(int32 year)
{
if ((year % 4) == 0)
@@ -176,7 +159,7 @@ bool DateTime::IsLeapYear(int32 year)
DateTime DateTime::MaxValue()
{
return DateTime(3652059 * Constants::TicksPerDay - 1);
return DateTime(3652059 * TimeSpan::TicksPerDay - 1);
}
DateTime DateTime::Now()

View File

@@ -199,11 +199,6 @@ public:
/// </summary>
int32 GetYear() const;
/// <summary>
/// Gets this date as the number of seconds since the Unix Epoch (January 1st of 1970).
/// </summary>
int32 ToUnixTimestamp() const;
public:
/// <summary>
/// Gets the number of days in the year and month.
@@ -220,20 +215,6 @@ public:
/// <returns>The number of days.</returns>
static int32 DaysInYear(int32 year);
/// <summary>
/// Returns the proleptic Gregorian date for the given Julian Day.
/// </summary>
/// <param name="julianDay">The Julian Day.</param>
/// <returns>Gregorian date and time.</returns>
static DateTime FromJulianDay(double julianDay);
/// <summary>
/// Returns the date from Unix time (seconds from midnight 1970-01-01).
/// </summary>
/// <param name="unixTime">The Unix time (seconds from midnight 1970-01-01).</param>
/// <returns>The Gregorian date and time.</returns>
static DateTime FromUnixTimestamp(int32 unixTime);
/// <summary>
/// Determines whether the specified year is a leap year.
/// </summary>

View File

@@ -6,38 +6,53 @@
TimeSpan TimeSpan::FromDays(double days)
{
ASSERT_LOW_LAYER((days >= MinValue().GetTotalDays()) && (days <= MaxValue().GetTotalDays()));
return TimeSpan(static_cast<int64>(days * Constants::TicksPerDay));
return TimeSpan(static_cast<int64>(days * TicksPerDay));
}
TimeSpan TimeSpan::FromHours(double hours)
{
ASSERT_LOW_LAYER((hours >= MinValue().GetTotalHours()) && (hours <= MaxValue().GetTotalHours()));
return TimeSpan(static_cast<int64>(hours * Constants::TicksPerHour));
return TimeSpan(static_cast<int64>(hours * TicksPerHour));
}
TimeSpan TimeSpan::FromMilliseconds(double milliseconds)
{
ASSERT_LOW_LAYER((milliseconds >= MinValue().GetTotalMilliseconds()) && (milliseconds <= MaxValue().GetTotalMilliseconds()));
return TimeSpan(static_cast<int64>(milliseconds * Constants::TicksPerMillisecond));
return TimeSpan(static_cast<int64>(milliseconds * TicksPerMillisecond));
}
TimeSpan TimeSpan::FromMinutes(double minutes)
{
ASSERT_LOW_LAYER((minutes >= MinValue().GetTotalMinutes()) && (minutes <= MaxValue().GetTotalMinutes()));
return TimeSpan(static_cast<int64>(minutes * Constants::TicksPerMinute));
return TimeSpan(static_cast<int64>(minutes * TicksPerMinute));
}
TimeSpan TimeSpan::FromSeconds(double seconds)
{
ASSERT_LOW_LAYER((seconds >= MinValue().GetTotalSeconds()) && (seconds <= MaxValue().GetTotalSeconds()));
return TimeSpan(static_cast<int64>(seconds * Constants::TicksPerSecond));
return TimeSpan(static_cast<int64>(seconds * TicksPerSecond));
}
TimeSpan TimeSpan::MaxValue()
{
return TimeSpan(9223372036854775807);
}
TimeSpan TimeSpan::MinValue()
{
return TimeSpan(-9223372036854775807 - 1);
}
TimeSpan TimeSpan::Zero()
{
return TimeSpan(0);
}
void TimeSpan::Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds)
{
const int64 totalMs = 1000 * (60 * 60 * 24 * (int64)days + 60 * 60 * (int64)hours + 60 * (int64)minutes + (int64)seconds) + (int64)milliseconds;
ASSERT_LOW_LAYER((totalMs >= MinValue().GetTotalMilliseconds()) && (totalMs <= MaxValue().GetTotalMilliseconds()));
Ticks = totalMs * Constants::TicksPerMillisecond;
Ticks = totalMs * TicksPerMillisecond;
}
String TimeSpan::ToString() const

View File

@@ -6,32 +6,30 @@
#include "Engine/Core/Formatting.h"
#include "Engine/Core/Templates.h"
namespace Constants
{
// The number of timespan ticks per day.
const int64 TicksPerDay = 864000000000;
// The number of timespan ticks per hour.
const int64 TicksPerHour = 36000000000;
// The number of timespan ticks per millisecond.
const int64 TicksPerMillisecond = 10000;
// The number of timespan ticks per minute.
const int64 TicksPerMinute = 600000000;
// The number of timespan ticks per second.
const int64 TicksPerSecond = 10000000;
// The number of timespan ticks per week.
const int64 TicksPerWeek = 6048000000000;
}
/// <summary>
/// Represents the difference between two dates and times.
/// </summary>
API_STRUCT(InBuild, Namespace="System") struct FLAXENGINE_API TimeSpan
{
public:
// The number of timespan ticks per day.
static constexpr int64 TicksPerDay = 864000000000;
// The number of timespan ticks per hour.
static constexpr int64 TicksPerHour = 36000000000;
// The number of timespan ticks per millisecond.
static constexpr int64 TicksPerMillisecond = 10000;
// The number of timespan ticks per minute.
static constexpr int64 TicksPerMinute = 600000000;
// The number of timespan ticks per second.
static constexpr int64 TicksPerSecond = 10000000;
// The number of timespan ticks per week.
static constexpr int64 TicksPerWeek = 6048000000000;
public:
/// <summary>
/// Time span in 100 nanoseconds resolution.
@@ -170,7 +168,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetDays() const
{
return (int32)(Ticks / Constants::TicksPerDay);
return (int32)(Ticks / TicksPerDay);
}
/// <summary>
@@ -186,7 +184,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetHours() const
{
return (int32)(Ticks / Constants::TicksPerHour % 24);
return (int32)(Ticks / TicksPerHour % 24);
}
/// <summary>
@@ -194,7 +192,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetMilliseconds() const
{
return (int32)(Ticks / Constants::TicksPerMillisecond % 1000);
return (int32)(Ticks / TicksPerMillisecond % 1000);
}
/// <summary>
@@ -202,7 +200,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetMinutes() const
{
return (int32)(Ticks / Constants::TicksPerMinute % 60);
return (int32)(Ticks / TicksPerMinute % 60);
}
/// <summary>
@@ -210,7 +208,7 @@ public:
/// </summary>
FORCE_INLINE int32 GetSeconds() const
{
return (int32)(Ticks / Constants::TicksPerSecond % 60);
return (int32)(Ticks / TicksPerSecond % 60);
}
/// <summary>
@@ -218,7 +216,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalDays() const
{
return (double)Ticks / Constants::TicksPerDay;
return (double)Ticks / TicksPerDay;
}
/// <summary>
@@ -226,7 +224,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalHours() const
{
return (double)Ticks / Constants::TicksPerHour;
return (double)Ticks / TicksPerHour;
}
/// <summary>
@@ -234,7 +232,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalMilliseconds() const
{
return (double)Ticks / Constants::TicksPerMillisecond;
return (double)Ticks / TicksPerMillisecond;
}
/// <summary>
@@ -242,7 +240,7 @@ public:
/// </summary>
FORCE_INLINE double GetTotalMinutes() const
{
return (double)Ticks / Constants::TicksPerMinute;
return (double)Ticks / TicksPerMinute;
}
/// <summary>
@@ -250,7 +248,7 @@ public:
/// </summary>
FORCE_INLINE float GetTotalSeconds() const
{
return static_cast<float>(Ticks) / Constants::TicksPerSecond;
return static_cast<float>(Ticks) / TicksPerSecond;
}
public:
@@ -293,29 +291,17 @@ public:
/// <summary>
/// Returns the maximum time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan MaxValue()
{
return TimeSpan(9223372036854775807);
}
static TimeSpan MaxValue();
/// <summary>
/// Returns the minimum time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan MinValue()
{
return TimeSpan(-9223372036854775807 - 1);
}
static TimeSpan MinValue();
/// <summary>
/// Returns the zero time span value.
/// </summary>
/// <returns>The time span.</returns>
static TimeSpan Zero()
{
return TimeSpan(0);
}
static TimeSpan Zero();
private:
void Set(int32 days, int32 hours, int32 minutes, int32 seconds, int32 milliseconds);

View File

@@ -139,6 +139,24 @@ VariantType::VariantType(const StringAnsiView& typeName)
return;
}
}
{
// Aliases
if (typeName == "FlaxEngine.Vector2")
{
new(this) VariantType(Vector2);
return;
}
if (typeName == "FlaxEngine.Vector3")
{
new(this) VariantType(Vector3);
return;
}
if (typeName == "FlaxEngine.Vector4")
{
new(this) VariantType(Vector4);
return;
}
}
// Check case for array
if (typeName.EndsWith(StringAnsiView("[]"), StringSearchCase::CaseSensitive))
@@ -3985,15 +4003,32 @@ void Variant::CopyStructure(void* src)
{
if (AsBlob.Data && src)
{
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(StringAnsiView(Type.TypeName));
const StringAnsiView typeName(Type.TypeName);
const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName);
if (typeHandle)
{
auto& type = typeHandle.GetType();
type.Struct.Copy(AsBlob.Data, src);
}
#if USE_CSHARP
else if (const auto mclass = Scripting::FindClass(typeName))
{
// Fallback to C#-only types
MCore::Thread::Attach();
if (MANAGED_GC_HANDLE && mclass->IsValueType())
{
MObject* instance = MCore::GCHandle::GetTarget(MANAGED_GC_HANDLE);
void* data = MCore::Object::Unbox(instance);
Platform::MemoryCopy(data, src, mclass->GetInstanceSize());
}
}
#endif
else
{
Platform::MemoryCopy(AsBlob.Data, src, AsBlob.Length);
if (typeName.Length() != 0)
{
LOG(Warning, "Missing scripting type \'{0}\'", String(typeName));
}
}
}
}

View File

@@ -67,7 +67,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
{
Time = UnscaledTime = TimeSpan::Zero();
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
LastBegin = currentTime - LastLength;
LastEnd = currentTime;
NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0;
@@ -76,7 +76,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime)
void Time::TickData::OnReset(float targetFps, double currentTime)
{
DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero();
LastLength = static_cast<double>(DeltaTime.Ticks) / Constants::TicksPerSecond;
LastLength = static_cast<double>(DeltaTime.Ticks) / TimeSpan::TicksPerSecond;
LastBegin = currentTime - LastLength;
LastEnd = currentTime;
}

View File

@@ -236,6 +236,27 @@ API_ENUM(Attributes="Flags") enum class ShadowsCastingMode
DECLARE_ENUM_OPERATORS(ShadowsCastingMode);
/// <summary>
/// The partitioning mode for shadow cascades.
/// </summary>
API_ENUM() enum class PartitionMode
{
/// <summary>
/// Internally defined cascade splits.
/// </summary>
Manual = 0,
/// <summary>
/// Logarithmic cascade splits.
/// </summary>
Logarithmic = 1,
/// <summary>
/// PSSM cascade splits.
/// </summary>
PSSM = 2,
};
/// <summary>
/// Identifies expected GPU resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU.
/// </summary>

View File

@@ -1359,7 +1359,7 @@ bool Actor::IsPrefabRoot() const
Actor* Actor::FindActor(const StringView& name) const
{
Actor* result = nullptr;
if (StringUtils::Compare(*_name, *name) == 0)
if (_name == name)
{
result = const_cast<Actor*>(this);
}
@@ -1393,7 +1393,7 @@ Actor* Actor::FindActor(const MClass* type) const
Actor* Actor::FindActor(const MClass* type, const StringView& name) const
{
CHECK_RETURN(type, nullptr);
if (GetClass()->IsSubClassOf(type) && StringUtils::Compare(*_name, *name) == 0)
if (GetClass()->IsSubClassOf(type) && _name == name)
return const_cast<Actor*>(this);
for (auto child : Children)
{

View File

@@ -225,6 +225,17 @@ void AnimatedModel::SetMasterPoseModel(AnimatedModel* masterPose)
_masterPose->AnimationUpdated.Bind<AnimatedModel, &AnimatedModel::OnAnimationUpdated>(this);
}
const Array<AnimGraphTraceEvent>& AnimatedModel::GetTraceEvents() const
{
#if !BUILD_RELEASE
if (!GetEnableTracing())
{
LOG(Warning, "Accessing AnimatedModel.TraceEvents with tracing disabled.");
}
#endif
return GraphInstance.TraceEvents;
}
#define CHECK_ANIM_GRAPH_PARAM_ACCESS() \
if (!AnimationGraph) \
{ \
@@ -494,6 +505,7 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani
if (slot.Animation == anim && slot.Name == slotName)
{
slot.Animation = nullptr;
slot.Reset = true;
break;
}
}

View File

@@ -259,6 +259,27 @@ public:
/// <param name="masterPose">The master pose actor to use.</param>
API_FUNCTION() void SetMasterPoseModel(AnimatedModel* masterPose);
/// <summary>
/// Enables extracting animation playback insights for debugging or custom scripting.
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize") bool GetEnableTracing() const
{
return GraphInstance.EnableTracing;
}
/// <summary>
/// Enables extracting animation playback insights for debugging or custom scripting.
/// </summary>
API_PROPERTY() void SetEnableTracing(bool value)
{
GraphInstance.EnableTracing = value;
}
/// <summary>
/// Gets the trace events from the last animation update. Valid only when EnableTracing is active.
/// </summary>
API_PROPERTY(Attributes="HideInEditor, NoSerialize") const Array<AnimGraphTraceEvent>& GetTraceEvents() const;
public:
/// <summary>
/// Gets the anim graph instance parameters collection.

View File

@@ -41,6 +41,12 @@ void DirectionalLight::Draw(RenderContext& renderContext)
data.RenderedVolumetricFog = 0;
data.ShadowsMode = ShadowsMode;
data.CascadeCount = CascadeCount;
data.Cascade1Spacing = Cascade1Spacing;
data.Cascade2Spacing = Cascade2Spacing;
data.Cascade3Spacing = Cascade3Spacing;
data.Cascade4Spacing = Cascade4Spacing;
data.PartitionMode = PartitionMode;
data.ContactShadowsLength = ContactShadowsLength;
data.StaticFlags = GetStaticFlags();
data.ID = GetID();
@@ -56,6 +62,12 @@ void DirectionalLight::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_GET_OTHER_OBJ(DirectionalLight);
SERIALIZE(CascadeCount);
SERIALIZE(Cascade1Spacing);
SERIALIZE(Cascade2Spacing);
SERIALIZE(Cascade3Spacing);
SERIALIZE(Cascade4Spacing);
SERIALIZE(PartitionMode);
}
void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
@@ -64,6 +76,12 @@ void DirectionalLight::Deserialize(DeserializeStream& stream, ISerializeModifier
LightWithShadow::Deserialize(stream, modifier);
DESERIALIZE(CascadeCount);
DESERIALIZE(Cascade1Spacing);
DESERIALIZE(Cascade2Spacing);
DESERIALIZE(Cascade3Spacing);
DESERIALIZE(Cascade4Spacing);
DESERIALIZE(PartitionMode);
}
bool DirectionalLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)

View File

@@ -12,12 +12,42 @@ class FLAXENGINE_API DirectionalLight : public LightWithShadow
{
DECLARE_SCENE_OBJECT(DirectionalLight);
public:
/// <summary>
/// The partitioning mode for the shadow cascades.
/// </summary>
API_FIELD(Attributes = "EditorOrder(64), DefaultValue(PartitionMode.Manual), EditorDisplay(\"Shadow\")")
PartitionMode PartitionMode = PartitionMode::Manual;
/// <summary>
/// The number of cascades used for slicing the range of depth covered by the light during shadow rendering. Values are 1, 2 or 4 cascades; a typical scene uses 4 cascades.
/// </summary>
API_FIELD(Attributes="EditorOrder(65), DefaultValue(4), Limit(1, 4), EditorDisplay(\"Shadow\")")
int32 CascadeCount = 4;
/// <summary>
/// Percentage of the shadow distance used by the first cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(66), DefaultValue(0.05f), VisibleIf(nameof(ShowCascade1)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade1Spacing = 0.05f;
/// <summary>
/// Percentage of the shadow distance used by the second cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(67), DefaultValue(0.15f), VisibleIf(nameof(ShowCascade2)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade2Spacing = 0.15f;
/// <summary>
/// Percentage of the shadow distance used by the third cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(68), DefaultValue(0.50f), VisibleIf(nameof(ShowCascade3)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade3Spacing = 0.50f;
/// <summary>
/// Percentage of the shadow distance used by the fourth cascade.
/// </summary>
API_FIELD(Attributes = "EditorOrder(69), DefaultValue(1.0f), VisibleIf(nameof(ShowCascade4)), Limit(0, 1, 0.001f), EditorDisplay(\"Shadow\")")
float Cascade4Spacing = 1.0f;
public:
// [LightWithShadow]
void Draw(RenderContext& renderContext) override;

View File

@@ -0,0 +1,10 @@
namespace FlaxEngine
{
public partial class DirectionalLight
{
bool ShowCascade1 => CascadeCount >= 1 && PartitionMode == PartitionMode.Manual;
bool ShowCascade2 => CascadeCount >= 2 && PartitionMode == PartitionMode.Manual;
bool ShowCascade3 => CascadeCount >= 3 && PartitionMode == PartitionMode.Manual;
bool ShowCascade4 => CascadeCount >= 4 && PartitionMode == PartitionMode.Manual;
}
}

View File

@@ -43,7 +43,7 @@ API_STRUCT(Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkConfi
/// </summary>
/// <remarks>Object is managed by the created network peer (will be deleted on peer shutdown).</remarks>
API_FIELD()
ScriptingObject* NetworkDriver;
ScriptingObject* NetworkDriver = nullptr;
/// <summary>
/// The upper limit on how many peers can join when we're listening.

View File

@@ -2151,6 +2151,18 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
NetworkMessageObjectRpc msgData;
event.Message.ReadStructure(msgData);
ScopeLock lock(ObjectsLock);
// Find RPC info
NetworkRpcName name;
name.First = Scripting::FindScriptingType(msgData.RpcTypeName);
name.Second = msgData.RpcName;
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
if (!info)
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId);
return;
}
NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName);
if (e)
{
@@ -2159,17 +2171,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
if (!obj)
return;
// Find RPC info
NetworkRpcName name;
name.First = Scripting::FindScriptingType(msgData.RpcTypeName);
name.Second = msgData.RpcName;
const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name);
if (!info)
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId);
return;
}
// Validate RPC
if (info->Server && NetworkManager::IsClient())
{
@@ -2192,7 +2194,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie
// Execute RPC
info->Execute(obj, stream, info->Tag);
}
else
else if(info->Channel != static_cast<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(NetworkChannelType::UnreliableOrdered))
{
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName));
}

View File

@@ -2414,7 +2414,13 @@ void PhysicsBackend::SetRigidActorPose(void* actor, const Vector3& position, con
if (kinematic)
{
auto actorPhysX = (PxRigidDynamic*)actor;
actorPhysX->setKinematicTarget(trans);
if (actorPhysX->getActorFlags() & PxActorFlag::eDISABLE_SIMULATION)
{
// Ensures the disabled kinematic actor ends up in the correct pose after enabling simulation
actorPhysX->setGlobalPose(trans, wakeUp);
}
else
actorPhysX->setKinematicTarget(trans);
}
else
{

View File

@@ -517,7 +517,7 @@ DateTime AndroidFileSystem::GetFileLastEditTime(const StringView& path)
const StringAsANSI<> pathANSI(*path, path.Length());
if (stat(pathANSI.Get(), &fileInfo) == -1)
return DateTime::MinValue();
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
const DateTime UnixEpoch(1970, 1, 1);
return UnixEpoch + timeSinceEpoch;
}

View File

@@ -498,7 +498,7 @@ DateTime AppleFileSystem::GetFileLastEditTime(const StringView& path)
return DateTime::MinValue();
}
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
return UnixEpoch + timeSinceEpoch;
}

View File

@@ -657,7 +657,7 @@ DateTime LinuxFileSystem::GetFileLastEditTime(const StringView& path)
return DateTime::MinValue();
}
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
return UnixEpoch + timeSinceEpoch;
}

View File

@@ -137,7 +137,7 @@ DateTime UnixFile::GetLastWriteTime() const
{
return DateTime::MinValue();
}
const TimeSpan timeSinceEpoch(0, 0, fileInfo.st_mtime);
const TimeSpan timeSinceEpoch(0, 0, 0, fileInfo.st_mtime);
const DateTime unixEpoch(1970, 1, 1);
return unixEpoch + timeSinceEpoch;
}

View File

@@ -46,6 +46,12 @@ struct RendererDirectionalLightData
float ShadowsDistance;
int32 CascadeCount;
float Cascade1Spacing;
float Cascade2Spacing;
float Cascade3Spacing;
float Cascade4Spacing;
PartitionMode PartitionMode;
float ContactShadowsLength;
ShadowsCastingMode ShadowsMode;

View File

@@ -247,19 +247,12 @@ void ShadowsPass::SetupLight(RenderContext& renderContext, RenderContextBatch& r
minDistance = cameraNear;
maxDistance = cameraNear + shadowsDistance;
// TODO: expose partition mode?
enum class PartitionMode
{
Manual = 0,
Logarithmic = 1,
PSSM = 2,
};
PartitionMode partitionMode = PartitionMode::Manual;
PartitionMode partitionMode = light.PartitionMode;
float pssmFactor = 0.5f;
float splitDistance0 = 0.05f;
float splitDistance1 = 0.15f;
float splitDistance2 = 0.50f;
float splitDistance3 = 1.00f;
float splitDistance0 = light.Cascade1Spacing;
float splitDistance1 = Math::Max(splitDistance0, light.Cascade2Spacing);
float splitDistance2 = Math::Max(splitDistance1, light.Cascade3Spacing);
float splitDistance3 = Math::Max(splitDistance2, light.Cascade4Spacing);
// Compute the split distances based on the partitioning mode
if (partitionMode == PartitionMode::Manual)

View File

@@ -5,11 +5,11 @@ using System;
namespace FlaxEngine
{
/// <summary>
/// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type.
/// Shows property/field in the editor only if the specified member has a given value. Can be used to hide properties based on other properties (also private properties). The given member has to be bool type. Multiple VisibleIf attributes can be added for additional conditions to be met.
/// </summary>
/// <seealso cref="System.Attribute" />
[Serializable]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
public sealed class VisibleIfAttribute : Attribute
{
/// <summary>

View File

@@ -0,0 +1,26 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#include "Engine/Core/Types/DateTime.h"
#include <ThirdParty/catch2/catch.hpp>
TEST_CASE("DateTime")
{
SECTION("Test Convertion")
{
constexpr int year = 2023;
constexpr int month = 12;
constexpr int day = 16;
constexpr int hour = 23;
constexpr int minute = 50;
constexpr int second = 13;
constexpr int millisecond = 5;
const DateTime dt1(year, month, day, hour, minute, second, millisecond);
CHECK(dt1.GetYear() == year);
CHECK(dt1.GetMonth() == month);
CHECK(dt1.GetDay() == day);
CHECK(dt1.GetHour() == hour);
CHECK(dt1.GetMinute() == minute);
CHECK(dt1.GetSecond() == second);
CHECK(dt1.GetMillisecond() == millisecond);
}
}

View File

@@ -584,7 +584,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
{
// Procedural Texture Sample
textureBox->Cache = writeLocal(Value::InitForZero(ValueType::Float4), node);
createGradients(node);
auto proceduralSample = String::Format(TEXT(
" {{\n"
" float3 weights;\n"
@@ -613,19 +612,19 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
" uv1 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex1)) * 43758.5453);\n"
" uv2 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex2)) * 43758.5453);\n"
" uv3 = {0} + frac(sin(mul(float2x2(127.1, 311.7, 269.5, 183.3), vertex3)) * 43758.5453);\n"
" float4 tex1 = {1}.SampleGrad({4}, uv1, {2}, {3}, {6}) * weights.x;\n"
" float4 tex2 = {1}.SampleGrad({4}, uv2, {2}, {3}, {6}) * weights.y;\n"
" float4 tex3 = {1}.SampleGrad({4}, uv3, {2}, {3}, {6}) * weights.z;\n"
" {5} = tex1 + tex2 + tex3;\n"
" float2 fdx = ddx({0});\n"
" float2 fdy = ddy({0});\n"
" float4 tex1 = {1}.SampleGrad({2}, uv1, fdx, fdy, {4}) * weights.x;\n"
" float4 tex2 = {1}.SampleGrad({2}, uv2, fdx, fdy, {4}) * weights.y;\n"
" float4 tex3 = {1}.SampleGrad({2}, uv3, fdx, fdy, {4}) * weights.z;\n"
" {3} = tex1 + tex2 + tex3;\n"
" }}\n"
),
uvs.Value, // {0}
texture.Value, // {1}
_ddx.Value, // {2}
_ddy.Value, // {3}
samplerName, // {4}
textureBox->Cache.Value, // {5}
offset.Value // {6}
samplerName, // {2}
textureBox->Cache.Value, // {3}
offset.Value // {4}
);
_writer.Write(*proceduralSample);

View File

@@ -374,6 +374,7 @@ void ModelTool::Options::Serialize(SerializeStream& stream, const void* otherObj
SERIALIZE(InstanceToImportAs);
SERIALIZE(ImportTextures);
SERIALIZE(RestoreMaterialsOnReimport);
SERIALIZE(SkipExistingMaterialsOnReimport);
SERIALIZE(GenerateSDF);
SERIALIZE(SDFResolution);
SERIALIZE(SplitObjects);
@@ -422,6 +423,7 @@ void ModelTool::Options::Deserialize(DeserializeStream& stream, ISerializeModifi
DESERIALIZE(InstanceToImportAs);
DESERIALIZE(ImportTextures);
DESERIALIZE(RestoreMaterialsOnReimport);
DESERIALIZE(SkipExistingMaterialsOnReimport);
DESERIALIZE(GenerateSDF);
DESERIALIZE(SDFResolution);
DESERIALIZE(SplitObjects);
@@ -1154,6 +1156,18 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option
continue;
}
// Skip any materials that already exist from the model.
// This allows the use of "import as material instances" without material properties getting overridden on each import.
if (options.SkipExistingMaterialsOnReimport)
{
AssetInfo info;
if (Content::GetAssetInfo(assetPath, info))
{
material.AssetID = info.ID;
continue;
}
}
if (options.ImportMaterialsAsInstances)
{
// Create material instance

View File

@@ -260,17 +260,20 @@ public:
API_FIELD(Attributes="EditorOrder(400), EditorDisplay(\"Materials\"), VisibleIf(nameof(ShowGeometry))")
bool ImportMaterials = true;
// 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))")
API_FIELD(Attributes = "EditorOrder(401), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterials)), VisibleIf(nameof(ShowGeometry))")
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))")
API_FIELD(Attributes = "EditorOrder(402), EditorDisplay(\"Materials\"), VisibleIf(nameof(ImportMaterialsAsInstances)), VisibleIf(nameof(ShowGeometry))")
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))")
bool ImportTextures = true;
// If checked, the importer will try to keep the model's current material slots, instead of importing materials from the source file.
API_FIELD(Attributes="EditorOrder(420), EditorDisplay(\"Materials\", \"Keep Material Slots on Reimport\"), VisibleIf(nameof(ShowGeometry))")
// 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))")
bool RestoreMaterialsOnReimport = true;
// If checked, the importer will not reimport any material from this model which already exist in the sub-asset folder.
API_FIELD(Attributes = "EditorOrder(421), EditorDisplay(\"Materials\", \"Skip Existing Materials\"), VisibleIf(nameof(ShowGeometry))")
bool SkipExistingMaterialsOnReimport = true;
public: // SDF

View File

@@ -685,9 +685,9 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
case 36:
{
// Get value with structure data
Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
if (!node->GetBox(0)->HasConnection())
return;
Variant structureValue = eatBox(node, node->GetBox(0)->FirstConnection());
// Find type
const StringView typeName(node->Values[0]);
@@ -741,6 +741,12 @@ void VisjectExecutor::ProcessGroupPacking(Box* box, Node* node, Value& value)
return;
}
const ScriptingType& type = typeHandle.GetType();
if (structureValue.Type.Type != VariantType::Structure) // If structureValue is eg. Float we can try to cast it to a required structure type
{
VariantType typeVariantType(typeNameAnsiView);
if (Variant::CanCast(structureValue, typeVariantType))
structureValue = Variant::Cast(structureValue, typeVariantType);
}
structureValue.InvertInline(); // Extract any Float3/Int32 into Structure type from inlined format
const ScriptingTypeHandle structureValueTypeHandle = Scripting::FindScriptingType(structureValue.Type.GetTypeName());
if (structureValue.Type.Type != VariantType::Structure || typeHandle != structureValueTypeHandle)