Merge remote-tracking branch 'origin/master' into 1.8
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -129,6 +129,8 @@ public:
|
||||
UseDefaultRule = 4,
|
||||
InterruptionRuleRechecking = 8,
|
||||
InterruptionInstant = 16,
|
||||
InterruptionSourceState = 32,
|
||||
InterruptionDestinationState = 64,
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -200,6 +202,21 @@ struct FLAXENGINE_API AnimGraphSlot
|
||||
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>
|
||||
/// The animation graph instance data storage. Required to update the animation graph.
|
||||
/// </summary>
|
||||
@@ -241,7 +258,10 @@ public:
|
||||
uint64 LastUpdateFrame;
|
||||
AnimGraphNode* CurrentState;
|
||||
AnimGraphStateTransition* ActiveTransition;
|
||||
AnimGraphStateTransition* BaseTransition;
|
||||
AnimGraphNode* BaseTransitionState;
|
||||
float TransitionPosition;
|
||||
float BaseTransitionPosition;
|
||||
};
|
||||
|
||||
struct SlotBucket
|
||||
@@ -358,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
|
||||
{
|
||||
@@ -837,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);
|
||||
|
||||
@@ -866,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);
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
@@ -1101,6 +1131,17 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
const float alpha = Math::Saturate((float)tryGetValue(node->GetBox(3), node->Values[0]));
|
||||
auto mask = node->Assets[0].As<SkeletonMask>();
|
||||
auto maskAssetBox = node->GetBox(4); // 4 is the id of skeleton mask parameter node.
|
||||
|
||||
// Check if have some mask asset connected with the mask node
|
||||
if (maskAssetBox->HasConnection())
|
||||
{
|
||||
const Value assetBoxValue = tryGetValue(maskAssetBox, Value::Null);
|
||||
|
||||
// Use the mask connected with this node instead of default mask asset
|
||||
if (assetBoxValue != Value::Null)
|
||||
mask = (SkeletonMask*)assetBoxValue.AsAsset;
|
||||
}
|
||||
|
||||
// Only A or missing/invalid mask
|
||||
if (Math::NearEqual(alpha, 0.0f, ANIM_GRAPH_BLEND_THRESHOLD) || mask == nullptr || mask->WaitForLoaded())
|
||||
@@ -1501,10 +1542,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);
|
||||
}
|
||||
|
||||
@@ -1610,22 +1650,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)
|
||||
@@ -1633,38 +1672,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1693,9 +1764,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)
|
||||
@@ -1704,14 +1789,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
|
||||
@@ -2132,7 +2215,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)
|
||||
@@ -2140,7 +2223,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;
|
||||
|
||||
@@ -19,6 +19,7 @@ AudioSource::AudioSource(const SpawnParams& params)
|
||||
, _minDistance(1000.0f)
|
||||
, _loop(false)
|
||||
, _playOnStart(false)
|
||||
, _startTime(0.0f)
|
||||
, _allowSpatialization(true)
|
||||
{
|
||||
Clip.Changed.Bind<AudioSource, &AudioSource::OnClipChanged>(this);
|
||||
@@ -71,6 +72,11 @@ void AudioSource::SetPlayOnStart(bool value)
|
||||
_playOnStart = value;
|
||||
}
|
||||
|
||||
void AudioSource::SetStartTime(float value)
|
||||
{
|
||||
_startTime = value;
|
||||
}
|
||||
|
||||
void AudioSource::SetMinDistance(float value)
|
||||
{
|
||||
value = Math::Max(0.0f, value);
|
||||
@@ -361,6 +367,7 @@ void AudioSource::Serialize(SerializeStream& stream, const void* otherObj)
|
||||
SERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
|
||||
SERIALIZE_MEMBER(Loop, _loop);
|
||||
SERIALIZE_MEMBER(PlayOnStart, _playOnStart);
|
||||
SERIALIZE_MEMBER(StartTime, _startTime);
|
||||
SERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
|
||||
}
|
||||
|
||||
@@ -377,6 +384,7 @@ void AudioSource::Deserialize(DeserializeStream& stream, ISerializeModifier* mod
|
||||
DESERIALIZE_MEMBER(DopplerFactor, _dopplerFactor);
|
||||
DESERIALIZE_MEMBER(Loop, _loop);
|
||||
DESERIALIZE_MEMBER(PlayOnStart, _playOnStart);
|
||||
DESERIALIZE_MEMBER(StartTime, _startTime);
|
||||
DESERIALIZE_MEMBER(AllowSpatialization, _allowSpatialization);
|
||||
DESERIALIZE(Clip);
|
||||
}
|
||||
@@ -540,5 +548,7 @@ void AudioSource::BeginPlay(SceneBeginData* data)
|
||||
return;
|
||||
#endif
|
||||
Play();
|
||||
if (GetStartTime() > 0)
|
||||
SetTime(GetStartTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ private:
|
||||
float _dopplerFactor = 1.0f;
|
||||
bool _loop;
|
||||
bool _playOnStart;
|
||||
float _startTime;
|
||||
bool _allowSpatialization;
|
||||
bool _clipChanged = false;
|
||||
|
||||
@@ -148,11 +149,25 @@ public:
|
||||
return _playOnStart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the time (in seconds) at which the audio clip starts playing if Play On Start is enabled.
|
||||
/// </summary>
|
||||
API_PROPERTY(Attributes = "EditorOrder(51), DefaultValue(0.0f), Limit(0, float.MaxValue, 0.01f), EditorDisplay(\"Audio Source\", \"Start Time\"), VisibleIf(nameof(PlayOnStart))")
|
||||
FORCE_INLINE float GetStartTime() const
|
||||
{
|
||||
return _startTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the audio clip should auto play on game start.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetPlayOnStart(bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Determines the time (in seconds) at which the audio clip starts playing if Play On Start is enabled.
|
||||
/// </summary>
|
||||
API_PROPERTY() void SetStartTime(float value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum distance at which audio attenuation starts. When the listener is closer to the source than this value, audio is heard at full volume. Once farther away the audio starts attenuating.
|
||||
/// </summary>
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace ALC
|
||||
{
|
||||
AudioBackend::Listener::TransformChanged(listener);
|
||||
|
||||
const Vector3 velocity = listener->GetVelocity();
|
||||
const Float3 velocity = listener->GetVelocity();
|
||||
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
||||
alListenerf(AL_GAIN, Audio::GetVolume());
|
||||
}
|
||||
@@ -319,8 +319,6 @@ void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
|
||||
ALC::RebuildContexts(false);
|
||||
#else
|
||||
AudioBackend::Listener::TransformChanged(listener);
|
||||
const Vector3 velocity = listener->GetVelocity();
|
||||
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
||||
alListenerf(AL_GAIN, Audio::GetVolume());
|
||||
#endif
|
||||
}
|
||||
@@ -336,7 +334,7 @@ void AudioBackendOAL::Listener_VelocityChanged(AudioListener* listener)
|
||||
{
|
||||
ALC_GET_LISTENER_CONTEXT(listener)
|
||||
|
||||
const Vector3 velocity = listener->GetVelocity();
|
||||
const Float3 velocity = listener->GetVelocity();
|
||||
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
|
||||
}
|
||||
|
||||
@@ -344,15 +342,15 @@ void AudioBackendOAL::Listener_TransformChanged(AudioListener* listener)
|
||||
{
|
||||
ALC_GET_LISTENER_CONTEXT(listener)
|
||||
|
||||
const Vector3 position = listener->GetPosition();
|
||||
const Float3 position = listener->GetPosition();
|
||||
const Quaternion orientation = listener->GetOrientation();
|
||||
const Vector3 flipX(-1, 1, 1);
|
||||
const Vector3 alOrientation[2] =
|
||||
const Float3 flipX(-1, 1, 1);
|
||||
const Float3 alOrientation[2] =
|
||||
{
|
||||
// Forward
|
||||
orientation * Vector3::Forward * flipX,
|
||||
orientation * Float3::Forward * flipX,
|
||||
// Up
|
||||
orientation * Vector3::Up * flipX
|
||||
orientation * Float3::Up * flipX
|
||||
};
|
||||
|
||||
alListenerfv(AL_ORIENTATION, (float*)alOrientation);
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace FlaxEditor.Content.Settings
|
||||
/// <summary>
|
||||
/// The layers names.
|
||||
/// </summary>
|
||||
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true)]
|
||||
[EditorOrder(10), EditorDisplay("Layers", EditorDisplayAttribute.InlineStyle), Collection(ReadOnly = true, Display = CollectionAttribute.DisplayType.Inline)]
|
||||
public string[] Layers = new string[32];
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -56,13 +56,7 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a 3x3 Matrix ( contains only Scale and Rotation ).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public struct Matrix3x3 : IEquatable<Matrix3x3>, IFormattable
|
||||
partial struct Matrix3x3 : IEquatable<Matrix3x3>, IFormattable
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the <see cref="Matrix3x3"/> type, in bytes.
|
||||
@@ -135,9 +129,7 @@ namespace FlaxEngine
|
||||
/// <param name="value">The value that will be assigned to all components.</param>
|
||||
public Matrix3x3(float value)
|
||||
{
|
||||
M11 = M12 = M13 =
|
||||
M21 = M22 = M23 =
|
||||
M31 = M32 = M33 = value;
|
||||
M11 = M12 = M13 = M21 = M22 = M23 = M31 = M32 = M33 = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
/// <summary>
|
||||
/// Represents a 3x3 mathematical matrix.
|
||||
/// </summary>
|
||||
API_STRUCT(InBuild) struct FLAXENGINE_API Matrix3x3
|
||||
API_STRUCT() struct FLAXENGINE_API Matrix3x3
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(Matrix3x3);
|
||||
public:
|
||||
union
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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) \
|
||||
{ \
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -16,7 +16,7 @@ public:
|
||||
/// <summary>
|
||||
/// The list of the string localization tables used by the game.
|
||||
/// </summary>
|
||||
API_FIELD()
|
||||
API_FIELD(Attributes="Collection(Display = CollectionAttribute.DisplayType.Inline)")
|
||||
Array<AssetReference<LocalizedStringTable>> LocalizedStringTables;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -300,6 +300,44 @@ void RigidBody::ClosestPoint(const Vector3& position, Vector3& result) const
|
||||
}
|
||||
}
|
||||
|
||||
void RigidBody::AddMovement(const Vector3& translation, const Quaternion& rotation)
|
||||
{
|
||||
// filter rotation according to constraints
|
||||
Quaternion allowedRotation;
|
||||
if (EnumHasAllFlags(GetConstraints(), RigidbodyConstraints::LockRotation))
|
||||
allowedRotation = Quaternion::Identity;
|
||||
else
|
||||
{
|
||||
Float3 euler = rotation.GetEuler();
|
||||
if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockRotationX))
|
||||
euler.X = 0;
|
||||
if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockRotationY))
|
||||
euler.Y = 0;
|
||||
if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockRotationZ))
|
||||
euler.Z = 0;
|
||||
allowedRotation = Quaternion::Euler(euler);
|
||||
}
|
||||
|
||||
// filter translation according to the constraints
|
||||
auto allowedTranslation = translation;
|
||||
if (EnumHasAllFlags(GetConstraints(), RigidbodyConstraints::LockPosition))
|
||||
allowedTranslation = Vector3::Zero;
|
||||
else
|
||||
{
|
||||
if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockPositionX))
|
||||
allowedTranslation.X = 0;
|
||||
if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockPositionY))
|
||||
allowedTranslation.Y = 0;
|
||||
if (EnumHasAnyFlags(GetConstraints(), RigidbodyConstraints::LockPositionZ))
|
||||
allowedTranslation.Z = 0;
|
||||
}
|
||||
Transform t;
|
||||
t.Translation = _transform.Translation + allowedTranslation;
|
||||
t.Orientation = _transform.Orientation * allowedRotation;
|
||||
t.Scale = _transform.Scale;
|
||||
SetTransform(t);
|
||||
}
|
||||
|
||||
void RigidBody::OnCollisionEnter(const Collision& c)
|
||||
{
|
||||
CollisionEnter(c);
|
||||
|
||||
@@ -486,6 +486,7 @@ public:
|
||||
// [Actor]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
|
||||
void AddMovement(const Vector3& translation, const Quaternion& rotation) override;
|
||||
|
||||
// [IPhysicsActor]
|
||||
void* GetPhysicsActor() const override;
|
||||
|
||||
@@ -3312,7 +3312,6 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
// Create vehicle drive
|
||||
auto drive4W = PxVehicleDrive4W::allocate(wheels.Count());
|
||||
drive4W->setup(PhysX, actorPhysX, *wheelsSimData, driveSimData, Math::Max(wheels.Count() - 4, 0));
|
||||
drive4W->setToRestState();
|
||||
drive4W->mDriveDynData.forceGearChange(PxVehicleGearsData::eFIRST);
|
||||
drive4W->mDriveDynData.setUseAutoGears(gearbox.AutoGear);
|
||||
vehicle = drive4W;
|
||||
@@ -3355,7 +3354,6 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
// Create vehicle drive
|
||||
auto driveNW = PxVehicleDriveNW::allocate(wheels.Count());
|
||||
driveNW->setup(PhysX, actorPhysX, *wheelsSimData, driveSimData, wheels.Count());
|
||||
driveNW->setToRestState();
|
||||
driveNW->mDriveDynData.forceGearChange(PxVehicleGearsData::eFIRST);
|
||||
driveNW->mDriveDynData.setUseAutoGears(gearbox.AutoGear);
|
||||
vehicle = driveNW;
|
||||
@@ -3366,7 +3364,6 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
// Create vehicle drive
|
||||
auto driveNo = PxVehicleNoDrive::allocate(wheels.Count());
|
||||
driveNo->setup(PhysX, actorPhysX, *wheelsSimData);
|
||||
driveNo->setToRestState();
|
||||
vehicle = driveNo;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -203,8 +203,11 @@ void Physics::Simulate(float dt)
|
||||
|
||||
void Physics::CollectResults()
|
||||
{
|
||||
if (DefaultScene)
|
||||
DefaultScene->CollectResults();
|
||||
for (PhysicsScene* scene : Scenes)
|
||||
{
|
||||
if (scene->GetAutoSimulation())
|
||||
scene->CollectResults();
|
||||
}
|
||||
}
|
||||
|
||||
bool Physics::IsDuringSimulation()
|
||||
|
||||
@@ -324,7 +324,19 @@ void WindowsWindow::SetBorderless(bool isBorderless, bool maximized)
|
||||
lStyle |= WS_OVERLAPPED | WS_SYSMENU | WS_BORDER | WS_CAPTION;
|
||||
|
||||
SetWindowLong(_handle, GWL_STYLE, lStyle);
|
||||
SetWindowPos(_handle, nullptr, 0, 0, (int)_settings.Size.X, (int)_settings.Size.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
const Float2 clientSize = GetClientSize();
|
||||
const Float2 desktopSize = Platform::GetDesktopSize();
|
||||
// Move window and half size if it is larger than desktop size
|
||||
if (clientSize.X >= desktopSize.X && clientSize.Y >= desktopSize.Y)
|
||||
{
|
||||
const Float2 halfSize = desktopSize * 0.5f;
|
||||
const Float2 middlePos = halfSize * 0.5f;
|
||||
SetWindowPos(_handle, nullptr, (int)middlePos.X, (int)middlePos.Y, (int)halfSize.X, (int)halfSize.Y, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWindowPos(_handle, nullptr, 0, 0, (int)clientSize.X, (int)clientSize.Y, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
if (maximized)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,32 @@ namespace FlaxEngine
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Class)]
|
||||
public sealed class CollectionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The display type for collections.
|
||||
/// </summary>
|
||||
public enum DisplayType
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays the default display type.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Displays a header.
|
||||
/// </summary>
|
||||
Header,
|
||||
|
||||
/// <summary>
|
||||
/// Displays inline.
|
||||
/// </summary>
|
||||
Inline,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display type.
|
||||
/// </summary>
|
||||
public DisplayType Display;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this collection is read-only. If <c>true</c>, applications using this collection should not allow to add or remove items.
|
||||
/// </summary>
|
||||
|
||||
@@ -606,14 +606,15 @@ namespace FlaxEngine.GUI
|
||||
_popup.LostFocus += DestroyPopup;
|
||||
|
||||
// Show dropdown popup
|
||||
var locationRootSpace = Location + new Float2(0, Height);
|
||||
var locationRootSpace = Location + new Float2(0, Height - Height * (1 - Scale.Y) / 2);
|
||||
var parent = Parent;
|
||||
while (parent != null && parent != root)
|
||||
{
|
||||
locationRootSpace = parent.PointToParent(ref locationRootSpace);
|
||||
parent = parent.Parent;
|
||||
}
|
||||
_popup.Location = locationRootSpace;
|
||||
_popup.Scale = Scale;
|
||||
_popup.Location = locationRootSpace - new Float2(0, _popup.Height * (1 - _popup.Scale.Y) / 2);
|
||||
_popup.Parent = root;
|
||||
_popup.Focus();
|
||||
_popup.StartMouseCapture();
|
||||
|
||||
@@ -132,6 +132,22 @@ namespace FlaxEngine.GUI
|
||||
if (_alwaysShowScrollbars != value)
|
||||
{
|
||||
_alwaysShowScrollbars = value;
|
||||
switch (_scrollBars)
|
||||
{
|
||||
case ScrollBars.None:
|
||||
break;
|
||||
case ScrollBars.Horizontal:
|
||||
HScrollBar.Visible = value;
|
||||
break;
|
||||
case ScrollBars.Vertical:
|
||||
VScrollBar.Visible = value;
|
||||
break;
|
||||
case ScrollBars.Both:
|
||||
HScrollBar.Visible = value;
|
||||
VScrollBar.Visible = value;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
PerformLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user