Merge remote-tracking branch 'origin/master' into 1.7
This commit is contained in:
@@ -16,6 +16,7 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
/// <inheritdoc />
|
||||
protected override OptionType[] Options => new[]
|
||||
{
|
||||
new OptionType("null", null),
|
||||
new OptionType("Texture", typeof(TextureBrush)),
|
||||
new OptionType("Sprite", typeof(SpriteBrush)),
|
||||
new OptionType("GPU Texture", typeof(GPUTextureBrush)),
|
||||
|
||||
@@ -158,7 +158,9 @@ namespace FlaxEditor.CustomEditors.Editors
|
||||
if (comboBox.SelectedIndex != -1)
|
||||
{
|
||||
var option = _options[comboBox.SelectedIndex];
|
||||
value = option.Creator(option.Type);
|
||||
if (option.Type != null)
|
||||
value = option.Creator(option.Type);
|
||||
|
||||
}
|
||||
SetValue(value);
|
||||
RebuildLayoutOnRefresh();
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
|
||||
private List<ContextMenu> _menus = new List<ContextMenu>();
|
||||
private List<SingleSelectGroupItem> _items = new List<SingleSelectGroupItem>();
|
||||
private bool _hasSelected = false;
|
||||
private SingleSelectGroupItem _selectedItem;
|
||||
|
||||
public T Selected
|
||||
@@ -31,7 +32,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
set
|
||||
{
|
||||
var index = _items.FindIndex(x => x.Value.Equals(value));
|
||||
if (index != -1 && !_selectedItem.Value.Equals(value))
|
||||
if (index != -1 && (!_hasSelected || !_selectedItem.Value.Equals(value)))
|
||||
{
|
||||
SetSelected(_items[index]);
|
||||
}
|
||||
@@ -70,7 +71,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
if (item.Tooltip != null)
|
||||
btn.TooltipText = item.Tooltip;
|
||||
item.Buttons.Add(btn);
|
||||
if (item.Equals(_selectedItem))
|
||||
if (_hasSelected && item.Equals(_selectedItem))
|
||||
btn.Checked = true;
|
||||
}
|
||||
|
||||
@@ -82,6 +83,7 @@ namespace FlaxEditor.GUI.ContextMenu
|
||||
btn.Checked = false;
|
||||
}
|
||||
_selectedItem = item;
|
||||
_hasSelected = true;
|
||||
|
||||
SelectedChanged?.Invoke(item.Value);
|
||||
item.Selected?.Invoke();
|
||||
|
||||
@@ -12,6 +12,7 @@ enum class NetworkMessageIDs : uint8
|
||||
ObjectReplicate,
|
||||
ObjectReplicatePart,
|
||||
ObjectSpawn,
|
||||
ObjectSpawnPart,
|
||||
ObjectDespawn,
|
||||
ObjectRole,
|
||||
ObjectRpc,
|
||||
@@ -30,6 +31,7 @@ public:
|
||||
static void OnNetworkMessageObjectReplicate(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectReplicatePart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectSpawnPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
static void OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
|
||||
#define NETWORK_PROTOCOL_VERSION 2
|
||||
#define NETWORK_PROTOCOL_VERSION 3
|
||||
|
||||
float NetworkManager::NetworkFPS = 60.0f;
|
||||
NetworkPeer* NetworkManager::Peer = nullptr;
|
||||
@@ -131,6 +131,7 @@ namespace
|
||||
NetworkInternal::OnNetworkMessageObjectReplicate,
|
||||
NetworkInternal::OnNetworkMessageObjectReplicatePart,
|
||||
NetworkInternal::OnNetworkMessageObjectSpawn,
|
||||
NetworkInternal::OnNetworkMessageObjectSpawnPart,
|
||||
NetworkInternal::OnNetworkMessageObjectDespawn,
|
||||
NetworkInternal::OnNetworkMessageObjectRole,
|
||||
NetworkInternal::OnNetworkMessageObjectRpc,
|
||||
|
||||
@@ -66,8 +66,17 @@ PACK_STRUCT(struct NetworkMessageObjectSpawn
|
||||
{
|
||||
NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawn;
|
||||
uint32 OwnerClientId;
|
||||
uint32 OwnerSpawnId; // Unique for peer who spawned it and matches OwnerSpawnId inside following part messages
|
||||
Guid PrefabId;
|
||||
uint16 ItemsCount;
|
||||
uint16 ItemsCount; // Total items count
|
||||
uint8 UseParts : 1; // True if spawn message is header-only and all items come in the separate parts
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct NetworkMessageObjectSpawnPart
|
||||
{
|
||||
NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawnPart;
|
||||
uint32 OwnerClientId;
|
||||
uint32 OwnerSpawnId;
|
||||
});
|
||||
|
||||
PACK_STRUCT(struct NetworkMessageObjectSpawnItem
|
||||
@@ -173,6 +182,12 @@ struct SpawnItem
|
||||
NetworkObjectRole Role;
|
||||
};
|
||||
|
||||
struct SpawnItemParts
|
||||
{
|
||||
NetworkMessageObjectSpawn MsgData;
|
||||
Array<NetworkMessageObjectSpawnItem> Items;
|
||||
};
|
||||
|
||||
struct SpawnGroup
|
||||
{
|
||||
Array<SpawnItem*, InlinedAllocation<8>> Items;
|
||||
@@ -198,6 +213,7 @@ namespace
|
||||
CriticalSection ObjectsLock;
|
||||
HashSet<NetworkReplicatedObject> Objects;
|
||||
Array<ReplicateItem> ReplicationParts;
|
||||
Array<SpawnItemParts> SpawnParts;
|
||||
Array<SpawnItem> SpawnQueue;
|
||||
Array<DespawnItem> DespawnQueue;
|
||||
Array<RpcItem> RpcQueue;
|
||||
@@ -213,6 +229,7 @@ namespace
|
||||
Dictionary<StringAnsiView, StringAnsi*> CSharpCachedNames;
|
||||
#endif
|
||||
Array<Guid> DespawnedObjects;
|
||||
uint32 SpawnId = 0;
|
||||
}
|
||||
|
||||
class NetworkReplicationService : public EngineService
|
||||
@@ -258,7 +275,7 @@ NetworkReplicatedObject* ResolveObject(Guid objectId)
|
||||
return it != Objects.End() ? &it->Item : nullptr;
|
||||
}
|
||||
|
||||
NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, char objectTypeName[128])
|
||||
NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const char objectTypeName[128])
|
||||
{
|
||||
// Lookup object
|
||||
NetworkReplicatedObject* obj = ResolveObject(objectId);
|
||||
@@ -398,8 +415,33 @@ FORCE_INLINE void GetNetworkName(char buffer[128], const StringAnsiView& name)
|
||||
buffer[name.Length()] = 0;
|
||||
}
|
||||
|
||||
void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg)
|
||||
{
|
||||
ScriptingObject* obj = e->Object.Get();
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
const auto& item = it->Item;
|
||||
|
||||
// Add object into spawn message
|
||||
NetworkMessageObjectSpawnItem msgDataItem;
|
||||
msgDataItem.ObjectId = item.ObjectId;
|
||||
msgDataItem.ParentId = item.ParentId;
|
||||
if (NetworkManager::IsClient())
|
||||
{
|
||||
// Remap local client object ids into server ids
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ObjectId, &msgDataItem.ObjectId);
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ParentId, &msgDataItem.ParentId);
|
||||
}
|
||||
msgDataItem.PrefabObjectID = Guid::Empty;
|
||||
auto* objScene = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (objScene && objScene->HasPrefabLink())
|
||||
msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID();
|
||||
GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname);
|
||||
msg.WriteStructure(msgDataItem);
|
||||
}
|
||||
|
||||
void SendObjectSpawnMessage(const SpawnGroup& group, const Array<NetworkClient*>& clients)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
const bool isClient = NetworkManager::IsClient();
|
||||
auto* peer = NetworkManager::Peer;
|
||||
NetworkMessage msg = peer->BeginSendMessage();
|
||||
@@ -415,37 +457,56 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array<NetworkClient*>
|
||||
|
||||
// Setup clients that should receive this spawn message
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
const auto& item = it->Item;
|
||||
BuildCachedTargets(clients, item.TargetClientIds);
|
||||
}
|
||||
msg.WriteStructure(msgData);
|
||||
for (SpawnItem* e : group.Items)
|
||||
{
|
||||
ScriptingObject* obj = e->Object.Get();
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
|
||||
// Add object into spawn message
|
||||
NetworkMessageObjectSpawnItem msgDataItem;
|
||||
msgDataItem.ObjectId = item.ObjectId;
|
||||
msgDataItem.ParentId = item.ParentId;
|
||||
// Network Peer has fixed size of messages so split spawn message into parts if there are too many objects to fit at once
|
||||
msgData.OwnerSpawnId = ++SpawnId;
|
||||
msgData.UseParts = msg.BufferSize - msg.Position < group.Items.Count() * sizeof(NetworkMessageObjectSpawnItem);
|
||||
msg.WriteStructure(msgData);
|
||||
if (msgData.UseParts)
|
||||
{
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
|
||||
// Send spawn items in separate parts
|
||||
NetworkMessageObjectSpawnPart msgDataPart;
|
||||
msgDataPart.OwnerClientId = msgData.OwnerClientId;
|
||||
msgDataPart.OwnerSpawnId = msgData.OwnerSpawnId;
|
||||
uint16 itemIndex = 0;
|
||||
constexpr uint32 spawnItemMaxSize = sizeof(uint16) + sizeof(NetworkMessageObjectSpawnItem); // Index + Data
|
||||
while (itemIndex < msgData.ItemsCount)
|
||||
{
|
||||
// Remap local client object ids into server ids
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ObjectId, &msgDataItem.ObjectId);
|
||||
IdsRemappingTable.KeyOf(msgDataItem.ParentId, &msgDataItem.ParentId);
|
||||
msg = peer->BeginSendMessage();
|
||||
msg.WriteStructure(msgDataPart);
|
||||
|
||||
// Write as many items as possible into this message
|
||||
while (msg.Position + spawnItemMaxSize <= msg.BufferSize && itemIndex < msgData.ItemsCount)
|
||||
{
|
||||
msg.WriteUInt16(itemIndex);
|
||||
SetupObjectSpawnMessageItem(group.Items[itemIndex], msg);
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
}
|
||||
msgDataItem.PrefabObjectID = Guid::Empty;
|
||||
auto* objScene = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (objScene && objScene->HasPrefabLink())
|
||||
msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID();
|
||||
GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname);
|
||||
msg.WriteStructure(msgDataItem);
|
||||
}
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
{
|
||||
// Send all spawn items within the spawn message
|
||||
for (SpawnItem* e : group.Items)
|
||||
SetupObjectSpawnMessageItem(e, msg);
|
||||
if (isClient)
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg);
|
||||
else
|
||||
peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets);
|
||||
}
|
||||
}
|
||||
|
||||
void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkClient* excludedClient = nullptr)
|
||||
@@ -655,6 +716,235 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b
|
||||
DirtyObjectImpl(item, obj);
|
||||
}
|
||||
|
||||
void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMessageObjectSpawnItem* msgDataItems)
|
||||
{
|
||||
ScopeLock lock(ObjectsLock);
|
||||
|
||||
// Check if that object has been already spawned
|
||||
auto& rootItem = msgDataItems[0];
|
||||
NetworkReplicatedObject* root = ResolveObject(rootItem.ObjectId, rootItem.ParentId, rootItem.ObjectTypeName);
|
||||
if (root)
|
||||
{
|
||||
// Object already exists locally so just synchronize the ownership (and mark as spawned)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
NetworkReplicatedObject* e = ResolveObject(msgDataItem.ObjectId, msgDataItem.ParentId, msgDataItem.ObjectTypeName);
|
||||
auto& item = *e;
|
||||
item.Spawned = true;
|
||||
if (NetworkManager::IsClient())
|
||||
{
|
||||
// Server always knows the best so update ownership of the existing object
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
if (item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
{
|
||||
if (Hierarchy)
|
||||
Hierarchy->AddObject(item.Object);
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
}
|
||||
}
|
||||
else if (item.OwnerClientId != msgData.OwnerClientId)
|
||||
{
|
||||
// Other client spawned object with a different owner
|
||||
// TODO: send reply message to inform about proper object ownership that client
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Recreate object locally (spawn only root)
|
||||
Actor* prefabInstance = nullptr;
|
||||
Array<ScriptingObject*> objects;
|
||||
if (msgData.PrefabId.IsValid())
|
||||
{
|
||||
const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId);
|
||||
Actor* parentActor = parent && parent->Object && parent->Object->Is<Actor>() ? parent->Object.As<Actor>() : nullptr;
|
||||
if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
// Reuse parent object as prefab instance
|
||||
prefabInstance = parentActor;
|
||||
}
|
||||
else if ((parentActor = Scripting::TryFindObject<Actor>(rootItem.ParentId)))
|
||||
{
|
||||
// Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it)
|
||||
for (Actor* child : parentActor->Children)
|
||||
{
|
||||
if (child->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
if (Objects.Contains(child->GetID()))
|
||||
{
|
||||
ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID);
|
||||
if (Objects.Contains(obj->GetID()))
|
||||
{
|
||||
// Other instance with already spawned network object
|
||||
obj = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reuse already spawned object within a parent
|
||||
prefabInstance = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!prefabInstance)
|
||||
{
|
||||
// Spawn prefab
|
||||
auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer);
|
||||
if (!prefab)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr);
|
||||
if (!prefabInstance)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve objects from prefab instance
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString());
|
||||
Delete(prefabInstance);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
}
|
||||
}
|
||||
else if (msgData.ItemsCount == 1)
|
||||
{
|
||||
// Spawn object
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName));
|
||||
return;
|
||||
}
|
||||
objects.Add(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Spawn objects
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName));
|
||||
for (ScriptingObject* e : objects)
|
||||
Delete(e);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
if (i != 0)
|
||||
{
|
||||
// Link hierarchy of spawned objects before calling any networking code for them
|
||||
if (auto sceneObject = ScriptingObject::Cast<SceneObject>(obj))
|
||||
{
|
||||
Actor* parent = nullptr;
|
||||
for (int32 j = 0; j < i; j++)
|
||||
{
|
||||
if (msgDataItems[j].ObjectId == msgDataItem.ParentId)
|
||||
{
|
||||
parent = ScriptingObject::Cast<Actor>(objects[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parent)
|
||||
sceneObject->SetParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all newly spawned objects
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
if (!obj->IsRegistered())
|
||||
obj->RegisterObject();
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
|
||||
// Add object to the list
|
||||
NetworkReplicatedObject item;
|
||||
item.Object = obj;
|
||||
item.AsNetworkObject = ScriptingObject::ToInterface<INetworkObject>(obj);
|
||||
item.ObjectId = obj->GetID();
|
||||
item.ParentId = parent ? parent->ObjectId : Guid::Empty;
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
if (item.OwnerClientId == NetworkManager::LocalClientId)
|
||||
{
|
||||
// Upgrade ownership automatically (eg. server spawned object that local client should own)
|
||||
item.Role = NetworkObjectRole::OwnedAuthoritative;
|
||||
}
|
||||
item.Spawned = true;
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty);
|
||||
Objects.Add(MoveTemp(item));
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
|
||||
// Boost future lookups by using indirection
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgDataItem.ObjectId, item.ToString(), obj->GetType().ToString());
|
||||
IdsRemappingTable.Add(msgDataItem.ObjectId, item.ObjectId);
|
||||
}
|
||||
|
||||
// Spawn all newly spawned objects (ensure to have valid ownership hierarchy set before spawning object)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
|
||||
// Automatic parenting for scene objects
|
||||
auto sceneObject = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (sceneObject)
|
||||
{
|
||||
if (parent && parent->Object.Get() && parent->Object->Is<Actor>())
|
||||
sceneObject->SetParent(parent->Object.As<Actor>());
|
||||
else if (auto* parentActor = Scripting::TryFindObject<Actor>(msgDataItem.ParentId))
|
||||
sceneObject->SetParent(parentActor);
|
||||
else if (msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
#if USE_NETWORK_REPLICATOR_LOG
|
||||
// Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client)
|
||||
AssetInfo assetInfo;
|
||||
if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName != TEXT("FlaxEngine.SceneAsset"))
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (!parent && msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkSpawn();
|
||||
}
|
||||
|
||||
// TODO: if we're server then spawn this object further on other clients (use TargetClientIds for that object - eg. object spawned by client on client for certain set of other clients only)
|
||||
}
|
||||
|
||||
NetworkRpcParams::NetworkRpcParams(const NetworkStream* stream)
|
||||
: SenderId(stream->SenderId)
|
||||
{
|
||||
@@ -1388,7 +1678,7 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
auto& e = ReplicationParts[i];
|
||||
if (e.PartsLeft > 0)
|
||||
{
|
||||
// TODO: remove replication items after some TTL to prevent memory leaks
|
||||
// TODO: remove replication items after some TTL to reduce memory usage
|
||||
continue;
|
||||
}
|
||||
ScriptingObject* obj = e.Object.Get();
|
||||
@@ -1408,6 +1698,8 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove items from SpawnParts after some TTL to reduce memory usage
|
||||
|
||||
// Replicate all owned networked objects with other clients or server
|
||||
if (!CachedReplicationResult)
|
||||
CachedReplicationResult = New<NetworkReplicationHierarchyUpdateResult>();
|
||||
@@ -1655,234 +1947,60 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectSpawn msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem));
|
||||
if (msgData.ItemsCount == 0)
|
||||
return;
|
||||
ScopeLock lock(ObjectsLock);
|
||||
|
||||
// Check if that object has been already spawned
|
||||
auto& rootItem = msgDataItems[0];
|
||||
NetworkReplicatedObject* root = ResolveObject(rootItem.ObjectId, rootItem.ParentId, rootItem.ObjectTypeName);
|
||||
if (root)
|
||||
if (msgData.UseParts)
|
||||
{
|
||||
// Object already exists locally so just synchronize the ownership (and mark as spawned)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
NetworkReplicatedObject* e = ResolveObject(msgDataItem.ObjectId, msgDataItem.ParentId, msgDataItem.ObjectTypeName);
|
||||
auto& item = *e;
|
||||
item.Spawned = true;
|
||||
if (NetworkManager::IsClient())
|
||||
{
|
||||
// Server always knows the best so update ownership of the existing object
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
if (item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
{
|
||||
if (Hierarchy)
|
||||
Hierarchy->AddObject(item.Object);
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
}
|
||||
}
|
||||
else if (item.OwnerClientId != msgData.OwnerClientId)
|
||||
{
|
||||
// Other client spawned object with a different owner
|
||||
// TODO: send reply message to inform about proper object ownership that client
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Recreate object locally (spawn only root)
|
||||
Actor* prefabInstance = nullptr;
|
||||
Array<ScriptingObject*> objects;
|
||||
if (msgData.PrefabId.IsValid())
|
||||
{
|
||||
const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId);
|
||||
Actor* parentActor = parent && parent->Object && parent->Object->Is<Actor>() ? parent->Object.As<Actor>() : nullptr;
|
||||
if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
// Reuse parent object as prefab instance
|
||||
prefabInstance = parentActor;
|
||||
}
|
||||
else if ((parentActor = Scripting::TryFindObject<Actor>(rootItem.ParentId)))
|
||||
{
|
||||
// Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it)
|
||||
for (Actor* child : parentActor->Children)
|
||||
{
|
||||
if (child->GetPrefabID() == msgData.PrefabId)
|
||||
{
|
||||
if (Objects.Contains(child->GetID()))
|
||||
{
|
||||
ScriptingObject* obj = FindPrefabObject(child, rootItem.PrefabObjectID);
|
||||
if (Objects.Contains(obj->GetID()))
|
||||
{
|
||||
// Other instance with already spawned network object
|
||||
obj = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reuse already spawned object within a parent
|
||||
prefabInstance = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!prefabInstance)
|
||||
{
|
||||
// Spawn prefab
|
||||
auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer);
|
||||
if (!prefab)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr);
|
||||
if (!prefabInstance)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve objects from prefab instance
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString());
|
||||
Delete(prefabInstance);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
}
|
||||
}
|
||||
else if (msgData.ItemsCount == 1)
|
||||
{
|
||||
// Spawn object
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(rootItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(rootItem.ObjectTypeName));
|
||||
return;
|
||||
}
|
||||
objects.Add(obj);
|
||||
// Allocate spawn message parts collecting
|
||||
auto& parts = SpawnParts.AddOne();
|
||||
parts.MsgData = msgData;
|
||||
parts.Items.Resize(msgData.ItemsCount);
|
||||
for (auto& item : parts.Items)
|
||||
item.ObjectId = Guid::Empty; // Mark as not yet received
|
||||
}
|
||||
else
|
||||
{
|
||||
// Spawn objects
|
||||
objects.Resize(msgData.ItemsCount);
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
const ScriptingTypeHandle objectType = Scripting::FindScriptingType(msgDataItem.ObjectTypeName);
|
||||
ScriptingObject* obj = ScriptingObject::NewObject(objectType);
|
||||
if (!obj)
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgDataItem.ObjectTypeName));
|
||||
for (ScriptingObject* e : objects)
|
||||
Delete(e);
|
||||
return;
|
||||
}
|
||||
objects[i] = obj;
|
||||
if (i != 0)
|
||||
{
|
||||
// Link hierarchy of spawned objects before calling any networking code for them
|
||||
if (auto sceneObject = ScriptingObject::Cast<SceneObject>(obj))
|
||||
{
|
||||
Actor* parent = nullptr;
|
||||
for (int32 j = 0; j < i; j++)
|
||||
{
|
||||
if (msgDataItems[j].ObjectId == msgDataItem.ParentId)
|
||||
{
|
||||
parent = ScriptingObject::Cast<Actor>(objects[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parent)
|
||||
sceneObject->SetParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem));
|
||||
InvokeObjectSpawn(msgData, msgDataItems);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all newly spawned objects
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
void NetworkInternal::OnNetworkMessageObjectSpawnPart(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
NetworkMessageObjectSpawnPart msgData;
|
||||
event.Message.ReadStructure(msgData);
|
||||
int32 spawnPartsIndex;
|
||||
for (spawnPartsIndex = 0; spawnPartsIndex < SpawnParts.Count(); spawnPartsIndex++)
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
if (!obj->IsRegistered())
|
||||
obj->RegisterObject();
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
|
||||
// Add object to the list
|
||||
NetworkReplicatedObject item;
|
||||
item.Object = obj;
|
||||
item.AsNetworkObject = ScriptingObject::ToInterface<INetworkObject>(obj);
|
||||
item.ObjectId = obj->GetID();
|
||||
item.ParentId = parent ? parent->ObjectId : Guid::Empty;
|
||||
item.OwnerClientId = msgData.OwnerClientId;
|
||||
item.Role = NetworkObjectRole::Replicated;
|
||||
if (item.OwnerClientId == NetworkManager::LocalClientId)
|
||||
{
|
||||
// Upgrade ownership automatically (eg. server spawned object that local client should own)
|
||||
item.Role = NetworkObjectRole::OwnedAuthoritative;
|
||||
}
|
||||
item.Spawned = true;
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty);
|
||||
Objects.Add(MoveTemp(item));
|
||||
if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative)
|
||||
Hierarchy->AddObject(obj);
|
||||
|
||||
// Boost future lookups by using indirection
|
||||
NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgDataItem.ObjectId, item.ToString(), obj->GetType().ToString());
|
||||
IdsRemappingTable.Add(msgDataItem.ObjectId, item.ObjectId);
|
||||
// Find spawn parts container that matches this spawn message (unique pair of sender and id assigned by sender)
|
||||
const auto& e = SpawnParts.Get()[spawnPartsIndex];
|
||||
if (e.MsgData.OwnerClientId == msgData.OwnerClientId && e.MsgData.OwnerSpawnId == msgData.OwnerSpawnId)
|
||||
break;
|
||||
}
|
||||
|
||||
// Spawn all newly spawned objects (ensure to have valid ownership hierarchy set before spawning object)
|
||||
for (int32 i = 0; i < msgData.ItemsCount; i++)
|
||||
if (spawnPartsIndex >= SpawnParts.Count())
|
||||
{
|
||||
auto& msgDataItem = msgDataItems[i];
|
||||
ScriptingObject* obj = objects[i];
|
||||
auto it = Objects.Find(obj->GetID());
|
||||
auto& item = it->Item;
|
||||
const NetworkReplicatedObject* parent = ResolveObject(msgDataItem.ParentId);
|
||||
// Invalid part or data, ignore it
|
||||
return;
|
||||
}
|
||||
auto& spawnParts = SpawnParts.Get()[spawnPartsIndex];
|
||||
|
||||
// Automatic parenting for scene objects
|
||||
auto sceneObject = ScriptingObject::Cast<SceneObject>(obj);
|
||||
if (sceneObject)
|
||||
{
|
||||
if (parent && parent->Object.Get() && parent->Object->Is<Actor>())
|
||||
sceneObject->SetParent(parent->Object.As<Actor>());
|
||||
else if (auto* parentActor = Scripting::TryFindObject<Actor>(msgDataItem.ParentId))
|
||||
sceneObject->SetParent(parentActor);
|
||||
else if (msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
#if USE_NETWORK_REPLICATOR_LOG
|
||||
// Ignore case when parent object in a message was a scene (eg. that is already unloaded on a client)
|
||||
AssetInfo assetInfo;
|
||||
if (!Content::GetAssetInfo(msgDataItem.ParentId, assetInfo) || assetInfo.TypeName != TEXT("FlaxEngine.SceneAsset"))
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (!parent && msgDataItem.ParentId.IsValid())
|
||||
{
|
||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} as parent to spawned object", msgDataItem.ParentId.ToString());
|
||||
}
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkSpawn();
|
||||
// Read all items from this part
|
||||
constexpr uint32 spawnItemMaxSize = sizeof(uint16) + sizeof(NetworkMessageObjectSpawnItem); // Index + Data
|
||||
while (event.Message.Position + spawnItemMaxSize <= event.Message.BufferSize)
|
||||
{
|
||||
const uint16 itemIndex = event.Message.ReadUInt16();
|
||||
event.Message.ReadStructure(spawnParts.Items[itemIndex]);
|
||||
}
|
||||
|
||||
// TODO: if we're server then spawn this object further on other clients (use TargetClientIds for that object - eg. object spawned by client on client for certain set of other clients only)
|
||||
// Invoke spawning if we've got all items
|
||||
for (auto& e : spawnParts.Items)
|
||||
{
|
||||
if (!e.ObjectId.IsValid())
|
||||
return;
|
||||
}
|
||||
InvokeObjectSpawn(spawnParts.MsgData, spawnParts.Items.Get());
|
||||
SpawnParts.RemoveAt(spawnPartsIndex);
|
||||
}
|
||||
|
||||
void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)
|
||||
|
||||
@@ -40,6 +40,31 @@ const Array<WheeledVehicle::Wheel>& WheeledVehicle::GetWheels() const
|
||||
|
||||
void WheeledVehicle::SetWheels(const Array<Wheel>& value)
|
||||
{
|
||||
#if WITH_VEHICLE
|
||||
// Don't recreate whole vehicle when some wheel properties are only changed (eg. suspension)
|
||||
if (_actor && _vehicle && _wheels.Count() == value.Count() && _wheelsData.Count() == value.Count())
|
||||
{
|
||||
bool softUpdate = true;
|
||||
for (int32 wheelIndex = 0; wheelIndex < value.Count(); wheelIndex++)
|
||||
{
|
||||
auto& oldWheel = _wheels.Get()[wheelIndex];
|
||||
auto& newWheel = value.Get()[wheelIndex];
|
||||
if (oldWheel.Type != newWheel.Type ||
|
||||
Math::NotNearEqual(oldWheel.SuspensionForceOffset, newWheel.SuspensionForceOffset) ||
|
||||
oldWheel.Collider != newWheel.Collider)
|
||||
{
|
||||
softUpdate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (softUpdate)
|
||||
{
|
||||
_wheels = value;
|
||||
PhysicsBackend::UpdateVehicleWheels(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
_wheels = value;
|
||||
Setup();
|
||||
}
|
||||
@@ -51,6 +76,10 @@ WheeledVehicle::EngineSettings WheeledVehicle::GetEngine() const
|
||||
|
||||
void WheeledVehicle::SetEngine(const EngineSettings& value)
|
||||
{
|
||||
#if WITH_VEHICLE
|
||||
if (_vehicle)
|
||||
PhysicsBackend::SetVehicleEngine(_vehicle, &value);
|
||||
#endif
|
||||
_engine = value;
|
||||
}
|
||||
|
||||
@@ -61,6 +90,10 @@ WheeledVehicle::DifferentialSettings WheeledVehicle::GetDifferential() const
|
||||
|
||||
void WheeledVehicle::SetDifferential(const DifferentialSettings& value)
|
||||
{
|
||||
#if WITH_VEHICLE
|
||||
if (_vehicle)
|
||||
PhysicsBackend::SetVehicleDifferential(_vehicle, &value);
|
||||
#endif
|
||||
_differential = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <ThirdParty/PhysX/extensions/PxFixedJoint.h>
|
||||
#include <ThirdParty/PhysX/extensions/PxSphericalJoint.h>
|
||||
#if WITH_VEHICLE
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
#include "Engine/Physics/Actors/WheeledVehicle.h"
|
||||
#include <ThirdParty/PhysX/vehicle/PxVehicleSDK.h>
|
||||
#include <ThirdParty/PhysX/vehicle/PxVehicleUpdate.h>
|
||||
@@ -1351,7 +1352,7 @@ void PhysicsBackend::EndSimulateScene(void* scene)
|
||||
int32 wheelsCount = 0;
|
||||
for (auto wheelVehicle : scenePhysX->WheelVehicles)
|
||||
{
|
||||
if (!wheelVehicle->IsActiveInHierarchy())
|
||||
if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation())
|
||||
continue;
|
||||
auto drive = (PxVehicleWheels*)wheelVehicle->_vehicle;
|
||||
ASSERT(drive);
|
||||
@@ -1566,7 +1567,7 @@ void PhysicsBackend::EndSimulateScene(void* scene)
|
||||
for (int32 i = 0, ii = 0; i < scenePhysX->WheelVehicles.Count(); i++)
|
||||
{
|
||||
auto wheelVehicle = scenePhysX->WheelVehicles[i];
|
||||
if (!wheelVehicle->IsActiveInHierarchy())
|
||||
if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation())
|
||||
continue;
|
||||
auto drive = (PxVehicleWheels*)scenePhysX->WheelVehicles[ii]->_vehicle;
|
||||
auto& perVehicle = WheelVehiclesResultsPerVehicle[ii];
|
||||
@@ -1587,7 +1588,7 @@ void PhysicsBackend::EndSimulateScene(void* scene)
|
||||
for (int32 i = 0, ii = 0; i < scenePhysX->WheelVehicles.Count(); i++)
|
||||
{
|
||||
auto wheelVehicle = scenePhysX->WheelVehicles[i];
|
||||
if (!wheelVehicle->IsActiveInHierarchy())
|
||||
if (!wheelVehicle->IsActiveInHierarchy() || !wheelVehicle->GetEnableSimulation())
|
||||
continue;
|
||||
auto drive = WheelVehiclesCache[ii];
|
||||
auto& perVehicle = WheelVehiclesResultsPerVehicle[ii];
|
||||
@@ -3058,8 +3059,20 @@ int32 PhysicsBackend::MoveController(void* controller, void* shape, const Vector
|
||||
|
||||
#if WITH_VEHICLE
|
||||
|
||||
bool SortWheels(WheeledVehicle::Wheel const& a, WheeledVehicle::Wheel const& b)
|
||||
{
|
||||
return (int32)a.Type < (int32)b.Type;
|
||||
}
|
||||
|
||||
void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
{
|
||||
// TODO: handle PxVehicleDrive4WWheelOrder internally rather than sorting wheels directly on the vehicle
|
||||
if (actor->_driveType == WheeledVehicle::DriveTypes::Drive4W)
|
||||
{
|
||||
// Drive4W requires wheels to match order from PxVehicleDrive4WWheelOrder enum
|
||||
Sorting::QuickSort(actor->_wheels.Get(), actor->_wheels.Count(), SortWheels);
|
||||
}
|
||||
|
||||
// Get wheels
|
||||
Array<WheeledVehicle::Wheel*, FixedAllocation<PX_MAX_NB_WHEELS>> wheels;
|
||||
for (auto& wheel : actor->_wheels)
|
||||
@@ -3104,10 +3117,7 @@ void* PhysicsBackend::CreateVehicle(WheeledVehicle* actor)
|
||||
// Initialize wheels simulation data
|
||||
PxVec3 offsets[PX_MAX_NB_WHEELS];
|
||||
for (int32 i = 0; i < wheels.Count(); i++)
|
||||
{
|
||||
auto& wheel = *wheels[i];
|
||||
offsets[i] = C2P(wheel.Collider->GetLocalPosition());
|
||||
}
|
||||
offsets[i] = C2P(wheels[i]->Collider->GetLocalPosition());
|
||||
PxF32 sprungMasses[PX_MAX_NB_WHEELS];
|
||||
const float mass = actorPhysX->getMass();
|
||||
// TODO: get gravityDirection from scenePhysX->Scene->getGravity()
|
||||
@@ -3351,12 +3361,163 @@ void PhysicsBackend::DestroyVehicle(void* vehicle, int32 driveType)
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor)
|
||||
{
|
||||
auto drive = (PxVehicleWheels*)actor->_vehicle;
|
||||
PxVehicleWheelsSimData* wheelsSimData = &drive->mWheelsSimData;
|
||||
for (uint32 i = 0; i < wheelsSimData->getNbWheels(); i++)
|
||||
{
|
||||
auto& wheel = actor->_wheels[i];
|
||||
|
||||
// Update suspension data
|
||||
PxVehicleSuspensionData suspensionData = wheelsSimData->getSuspensionData(i);
|
||||
const float suspensionFrequency = 7.0f;
|
||||
suspensionData.mMaxCompression = wheel.SuspensionMaxRaise;
|
||||
suspensionData.mMaxDroop = wheel.SuspensionMaxDrop;
|
||||
suspensionData.mSpringStrength = Math::Square(suspensionFrequency) * suspensionData.mSprungMass;
|
||||
suspensionData.mSpringDamperRate = wheel.SuspensionDampingRate * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass);
|
||||
wheelsSimData->setSuspensionData(i, suspensionData);
|
||||
|
||||
// Update tire data
|
||||
PxVehicleTireData tire;
|
||||
int32 tireIndex = WheelTireTypes.Find(wheel.TireFrictionScale);
|
||||
if (tireIndex == -1)
|
||||
{
|
||||
// New tire type
|
||||
tireIndex = WheelTireTypes.Count();
|
||||
WheelTireTypes.Add(wheel.TireFrictionScale);
|
||||
WheelTireFrictionsDirty = true;
|
||||
}
|
||||
tire.mType = tireIndex;
|
||||
tire.mLatStiffX = wheel.TireLateralMax;
|
||||
tire.mLatStiffY = wheel.TireLateralStiffness;
|
||||
tire.mLongitudinalStiffnessPerUnitGravity = wheel.TireLongitudinalStiffness;
|
||||
wheelsSimData->setTireData(i, tire);
|
||||
|
||||
// Update wheel data
|
||||
PxVehicleWheelData wheelData;
|
||||
wheelData.mMass = wheel.Mass;
|
||||
wheelData.mRadius = wheel.Radius;
|
||||
wheelData.mWidth = wheel.Width;
|
||||
wheelData.mMOI = 0.5f * wheelData.mMass * Math::Square(wheelData.mRadius);
|
||||
wheelData.mDampingRate = M2ToCm2(wheel.DampingRate);
|
||||
wheelData.mMaxSteer = wheel.MaxSteerAngle * DegreesToRadians;
|
||||
wheelData.mMaxBrakeTorque = M2ToCm2(wheel.MaxBrakeTorque);
|
||||
wheelData.mMaxHandBrakeTorque = M2ToCm2(wheel.MaxHandBrakeTorque);
|
||||
wheelsSimData->setWheelData(i, wheelData);
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleEngine(void* vehicle, const void* value)
|
||||
{
|
||||
auto drive = (PxVehicleDrive*)vehicle;
|
||||
auto& engine = *(const WheeledVehicle::EngineSettings*)value;
|
||||
switch (drive->getVehicleType())
|
||||
{
|
||||
case PxVehicleTypes::eDRIVE4W:
|
||||
{
|
||||
auto drive4W = (PxVehicleDrive4W*)drive;
|
||||
PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData;
|
||||
PxVehicleEngineData engineData;
|
||||
engineData.mMOI = M2ToCm2(engine.MOI);
|
||||
engineData.mPeakTorque = M2ToCm2(engine.MaxTorque);
|
||||
engineData.mMaxOmega = RpmToRadPerS(engine.MaxRotationSpeed);
|
||||
engineData.mDampingRateFullThrottle = M2ToCm2(0.15f);
|
||||
engineData.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f);
|
||||
engineData.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f);
|
||||
driveSimData.setEngineData(engineData);
|
||||
break;
|
||||
}
|
||||
case PxVehicleTypes::eDRIVENW:
|
||||
{
|
||||
auto drive4W = (PxVehicleDriveNW*)drive;
|
||||
PxVehicleDriveSimDataNW& driveSimData = drive4W->mDriveSimData;
|
||||
PxVehicleEngineData engineData;
|
||||
engineData.mMOI = M2ToCm2(engine.MOI);
|
||||
engineData.mPeakTorque = M2ToCm2(engine.MaxTorque);
|
||||
engineData.mMaxOmega = RpmToRadPerS(engine.MaxRotationSpeed);
|
||||
engineData.mDampingRateFullThrottle = M2ToCm2(0.15f);
|
||||
engineData.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f);
|
||||
engineData.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f);
|
||||
driveSimData.setEngineData(engineData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleDifferential(void* vehicle, const void* value)
|
||||
{
|
||||
auto drive = (PxVehicleDrive*)vehicle;
|
||||
auto& differential = *(const WheeledVehicle::DifferentialSettings*)value;
|
||||
switch (drive->getVehicleType())
|
||||
{
|
||||
case PxVehicleTypes::eDRIVE4W:
|
||||
{
|
||||
auto drive4W = (PxVehicleDrive4W*)drive;
|
||||
PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData;
|
||||
PxVehicleDifferential4WData differential4WData;
|
||||
differential4WData.mType = (PxVehicleDifferential4WData::Enum)differential.Type;
|
||||
differential4WData.mFrontRearSplit = differential.FrontRearSplit;
|
||||
differential4WData.mFrontLeftRightSplit = differential.FrontLeftRightSplit;
|
||||
differential4WData.mRearLeftRightSplit = differential.RearLeftRightSplit;
|
||||
differential4WData.mCentreBias = differential.CentreBias;
|
||||
differential4WData.mFrontBias = differential.FrontBias;
|
||||
differential4WData.mRearBias = differential.RearBias;
|
||||
driveSimData.setDiffData(differential4WData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value)
|
||||
{
|
||||
auto drive = (PxVehicleDrive*)vehicle;
|
||||
auto& gearbox = *(const WheeledVehicle::GearboxSettings*)value;
|
||||
drive->mDriveDynData.setUseAutoGears(gearbox.AutoGear);
|
||||
drive->mDriveDynData.setAutoBoxSwitchTime(Math::Max(gearbox.SwitchTime, 0.0f));
|
||||
switch (drive->getVehicleType())
|
||||
{
|
||||
case PxVehicleTypes::eDRIVE4W:
|
||||
{
|
||||
auto drive4W = (PxVehicleDrive4W*)drive;
|
||||
PxVehicleDriveSimData4W& driveSimData = drive4W->mDriveSimData;
|
||||
|
||||
// Gears
|
||||
PxVehicleGearsData gears;
|
||||
gears.mSwitchTime = Math::Max(gearbox.SwitchTime, 0.0f);
|
||||
driveSimData.setGearsData(gears);
|
||||
|
||||
// Auto Box
|
||||
PxVehicleAutoBoxData autoBox;
|
||||
driveSimData.setAutoBoxData(autoBox);
|
||||
|
||||
// Clutch
|
||||
PxVehicleClutchData clutch;
|
||||
clutch.mStrength = M2ToCm2(gearbox.ClutchStrength);
|
||||
driveSimData.setClutchData(clutch);
|
||||
break;
|
||||
}
|
||||
case PxVehicleTypes::eDRIVENW:
|
||||
{
|
||||
auto drive4W = (PxVehicleDriveNW*)drive;
|
||||
PxVehicleDriveSimDataNW& driveSimData = drive4W->mDriveSimData;
|
||||
|
||||
// Gears
|
||||
PxVehicleGearsData gears;
|
||||
gears.mSwitchTime = Math::Max(gearbox.SwitchTime, 0.0f);
|
||||
driveSimData.setGearsData(gears);
|
||||
|
||||
// Auto Box
|
||||
PxVehicleAutoBoxData autoBox;
|
||||
driveSimData.setAutoBoxData(autoBox);
|
||||
|
||||
// Clutch
|
||||
PxVehicleClutchData clutch;
|
||||
clutch.mStrength = M2ToCm2(gearbox.ClutchStrength);
|
||||
driveSimData.setClutchData(clutch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 PhysicsBackend::GetVehicleTargetGear(void* vehicle)
|
||||
|
||||
@@ -261,6 +261,9 @@ public:
|
||||
// Vehicles
|
||||
static void* CreateVehicle(class WheeledVehicle* actor);
|
||||
static void DestroyVehicle(void* vehicle, int32 driveType);
|
||||
static void UpdateVehicleWheels(WheeledVehicle* actor);
|
||||
static void SetVehicleEngine(void* vehicle, const void* value);
|
||||
static void SetVehicleDifferential(void* vehicle, const void* value);
|
||||
static void SetVehicleGearbox(void* vehicle, const void* value);
|
||||
static int32 GetVehicleTargetGear(void* vehicle);
|
||||
static void SetVehicleTargetGear(void* vehicle, int32 value);
|
||||
|
||||
@@ -732,6 +732,18 @@ void PhysicsBackend::DestroyVehicle(void* vehicle, int32 driveType)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::UpdateVehicleWheels(WheeledVehicle* actor)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleEngine(void* vehicle, const void* value)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleDifferential(void* vehicle, const void* value)
|
||||
{
|
||||
}
|
||||
|
||||
void PhysicsBackend::SetVehicleGearbox(void* vehicle, const void* value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -10,6 +10,48 @@ namespace FlaxEngine.GUI
|
||||
/// <seealso cref="FlaxEngine.GUI.Control" />
|
||||
public class ProgressBar : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// The method used to effect the bar.
|
||||
/// </summary>
|
||||
public enum BarMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Stretch the bar.
|
||||
/// </summary>
|
||||
Stretch,
|
||||
|
||||
/// <summary>
|
||||
/// Clip the bar.
|
||||
/// </summary>
|
||||
Clip,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The origin to move the progress bar to.
|
||||
/// </summary>
|
||||
public enum BarOrigin
|
||||
{
|
||||
/// <summary>
|
||||
/// Move the bar horizontally to the left.
|
||||
/// </summary>
|
||||
HorizontalLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Move the bar horizontally to the right.
|
||||
/// </summary>
|
||||
HorizontalRight,
|
||||
|
||||
/// <summary>
|
||||
/// Move the bar vertically up.
|
||||
/// </summary>
|
||||
VerticalTop,
|
||||
|
||||
/// <summary>
|
||||
/// Move the bar vertically down.
|
||||
/// </summary>
|
||||
VerticalBottom,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value.
|
||||
/// </summary>
|
||||
@@ -41,6 +83,18 @@ namespace FlaxEngine.GUI
|
||||
/// </summary>
|
||||
public bool UseSmoothing => !Mathf.IsZero(SmoothingScale);
|
||||
|
||||
/// <summary>
|
||||
/// The method used to effect the bar.
|
||||
/// </summary>
|
||||
[EditorOrder(41), Tooltip("The method used to effect the bar.")]
|
||||
public BarMethod Method = BarMethod.Stretch;
|
||||
|
||||
/// <summary>
|
||||
/// The origin or where the bar decreases to.
|
||||
/// </summary>
|
||||
[EditorOrder(42), Tooltip("The origin or where the bar decreases to.")]
|
||||
public BarOrigin Origin = BarOrigin.HorizontalLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum value.
|
||||
/// </summary>
|
||||
@@ -168,12 +222,44 @@ namespace FlaxEngine.GUI
|
||||
float progressNormalized = (_current - _minimum) / _maximum;
|
||||
if (progressNormalized > 0.001f)
|
||||
{
|
||||
var barRect = new Rectangle(0, 0, Width * progressNormalized, Height);
|
||||
BarMargin.ShrinkRectangle(ref barRect);
|
||||
if (BarBrush != null)
|
||||
BarBrush.Draw(barRect, BarColor);
|
||||
else
|
||||
Render2D.FillRectangle(barRect, BarColor);
|
||||
Rectangle barRect = new Rectangle(0, 0, Width * progressNormalized, Height);
|
||||
switch (Origin)
|
||||
{
|
||||
case BarOrigin.HorizontalLeft:
|
||||
break;
|
||||
case BarOrigin.HorizontalRight:
|
||||
barRect = new Rectangle(Width - Width * progressNormalized, 0, Width * progressNormalized, Height);
|
||||
break;
|
||||
case BarOrigin.VerticalTop:
|
||||
barRect = new Rectangle(0, 0, Width, Height * progressNormalized);
|
||||
break;
|
||||
case BarOrigin.VerticalBottom:
|
||||
barRect = new Rectangle(0, Height - Height * progressNormalized, Width, Height * progressNormalized);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
switch (Method)
|
||||
{
|
||||
case BarMethod.Stretch:
|
||||
BarMargin.ShrinkRectangle(ref barRect);
|
||||
if (BarBrush != null)
|
||||
BarBrush.Draw(barRect, BarColor);
|
||||
else
|
||||
Render2D.FillRectangle(barRect, BarColor);
|
||||
break;
|
||||
case BarMethod.Clip:
|
||||
var rect = new Rectangle(0, 0, Width, Height);
|
||||
BarMargin.ShrinkRectangle(ref rect);
|
||||
Render2D.PushClip(ref barRect);
|
||||
if (BarBrush != null)
|
||||
BarBrush.Draw(rect, BarColor);
|
||||
else
|
||||
Render2D.FillRectangle(rect, BarColor);
|
||||
Render2D.PopClip();
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user