8 years later, much learning, abandoned projects, some success, many failures. Here we are once again. I have gotten client/server physics running, but with some issues which I hope others can give me guidance on. Let me start with a video..
https://youtu.be/NzFPlARgWZ0
I have a simulation running on both the client and the server, the server sends updates to the client about rigid bodies once every 333ms. You can see from the video that things like to bounce around when they collide. The client simulation is fighting the updates from the server simulation.
Here is what gets sent over from the server to clients:
Code: Select all
void MeshSceneNode::Tick(std::chrono::time_point<std::chrono::steady_clock> CurTime)
{
if (LastUpdate + std::chrono::milliseconds(333) < CurTime)
{
btTransform Trans = _RigidBody->getWorldTransform();
btVector3 Origin = Trans.getOrigin();
btVector3 LinearVelocity = _RigidBody->getLinearVelocity();
btVector3 AngularVelocity = _RigidBody->getAngularVelocity();
for (auto& Client : WorldEngine::NetCode::ConnectedClients)
{
KNet::NetPacket_Send* Pkt = Client.first->GetFreePacket((uint8_t)WorldEngine::NetCode::OPID::Update_SceneNode);
if (Pkt)
{
Pkt->write<uintmax_t>(GetNodeID()); // SceneNode ID
Pkt->write<float>(Origin.x()); // Position - X
Pkt->write<float>(Origin.y()); // Position - Y
Pkt->write<float>(Origin.z()); // Position - Z
Pkt->write<float>(Origin.w()); // Position - W
Pkt->write<float>(Trans.getRotation().getX()); // Rotation - X
Pkt->write<float>(Trans.getRotation().getY()); // Rotation - Y
Pkt->write<float>(Trans.getRotation().getZ()); // Rotation - Z
Pkt->write<float>(Trans.getRotation().getW()); // Rotation - W
Pkt->write<float>(LinearVelocity.x()); // LinearVelocity - X
Pkt->write<float>(LinearVelocity.y()); // LinearVelocity - Y
Pkt->write<float>(LinearVelocity.z()); // LinearVelocity - Z
Pkt->write<float>(LinearVelocity.w()); // LinearVelocity - W
Pkt->write<float>(AngularVelocity.x()); // AngularVelocity - X
Pkt->write<float>(AngularVelocity.y()); // AngularVelocity - Y
Pkt->write<float>(AngularVelocity.z()); // AngularVelocity - Z
Pkt->write<float>(AngularVelocity.w()); // AngularVelocity - W
//
//
// TODO: This will work for now.. But if clients connect in on a different NetPoint then this will not suffice..
WorldEngine::NetCode::Point->SendPacket(Pkt);
}
}
LastUpdate = CurTime;
}
}
We're sending the Origin, Rotation, Linear Velocity, and Angular Velocity.
Perhaps this is not the best way to grab these values? Perhaps I'm grabbing the wrong values or there are more values that I need to grab aside from these?
On the client side, we update the values immediately when we receive them:
Code: Select all
//
// Update SceneNode
else if (OperationID == WorldEngine::NetCode::OPID::Update_SceneNode)
{
uintmax_t NodeID;
_Packet->read<uintmax_t>(NodeID);
TriangleMeshSceneNode* Node = static_cast<TriangleMeshSceneNode*>(WorldEngine::SceneGraph::SceneNodes[NodeID]);
//
if (Node)
{
btTransform Trans = Node->GetWorldTransform();;
//
btVector3 Origin;
_Packet->read<float>(Origin.m_floats[0]);
_Packet->read<float>(Origin.m_floats[1]);
_Packet->read<float>(Origin.m_floats[2]);
_Packet->read<float>(Origin.m_floats[3]);
Trans.setOrigin(Origin);
//
float RotX, RotY, RotZ, RotW;
_Packet->read<float>(RotX);
_Packet->read<float>(RotY);
_Packet->read<float>(RotZ);
_Packet->read<float>(RotW);
Trans.setRotation(btQuaternion(RotX, RotY, RotZ, RotW));
//
btVector3 LinearVelocity;
_Packet->read<float>(LinearVelocity.m_floats[0]);
_Packet->read<float>(LinearVelocity.m_floats[1]);
_Packet->read<float>(LinearVelocity.m_floats[2]);
_Packet->read<float>(LinearVelocity.m_floats[3]);
btVector3 AngularVelocity;
_Packet->read<float>(AngularVelocity.m_floats[0]);
_Packet->read<float>(AngularVelocity.m_floats[1]);
_Packet->read<float>(AngularVelocity.m_floats[2]);
_Packet->read<float>(AngularVelocity.m_floats[3]);
//
Node->NetUpdate(Trans);
//Node->NetUpdate(Origin, Rotation, LinearVelocity, AngularVelocity);
}
}
The NetUpdate functions from the end of the previous code block are as follows:
Code: Select all
void NetUpdate(btVector3 Origin, btVector3 Rotation, btVector3 LinearVelocity, btVector3 AngularVelocity)
{
btTransform Trans = _RigidBody->getWorldTransform();
Trans.setOrigin(Origin);
Trans.setRotation(btQuaternion(Rotation.x(), Rotation.y(), Rotation.z()));
_RigidBody->setWorldTransform(Trans);
_RigidBody->setLinearVelocity(LinearVelocity);
_RigidBody->setAngularVelocity(AngularVelocity);
bNeedsUpdate[0] = true;
bNeedsUpdate[1] = true;
bNeedsUpdate[2] = true;
}
void NetUpdate(btTransform Trans)
{
_RigidBody->setWorldTransform(Trans);
bNeedsUpdate[0] = true;
bNeedsUpdate[1] = true;
bNeedsUpdate[2] = true;
}
So you can see, we directly update the rigid body via its setWorldTransform() function. If I could cache this updated btTransform inside the object, and use it with the btMotionState then I would prefer to do it there, but if I'm correct, the motion state is only for receiving updates when a rigid body moves, not the other way around? Nonetheless,
perhaps I'm updating these values incorrectly, or at the wrong time?
The other thought is that I'm simply snapping to the updated values, and it might be better to smoothly interpolate into the updated values? I don't think this accounts for how jumpy/explodey the rigid bodies are when they come in contact..
And maybe none of my ideas are going in the right direction, someone else might know of a better way to synchronize things?