Merge branch 'master' into physics3d-material

This commit is contained in:
Lynix 2017-12-14 18:15:35 +01:00
commit e302667c21
11 changed files with 161 additions and 31 deletions

View File

@ -19,7 +19,7 @@ namespace Ndk
friend class PhysicsSystem3D; friend class PhysicsSystem3D;
public: public:
PhysicsComponent3D() = default; inline PhysicsComponent3D();
PhysicsComponent3D(const PhysicsComponent3D& physics); PhysicsComponent3D(const PhysicsComponent3D& physics);
~PhysicsComponent3D() = default; ~PhysicsComponent3D() = default;
@ -28,6 +28,7 @@ namespace Ndk
void AddTorque(const Nz::Vector3f& torque, Nz::CoordSys coordSys = Nz::CoordSys_Global); void AddTorque(const Nz::Vector3f& torque, Nz::CoordSys coordSys = Nz::CoordSys_Global);
void EnableAutoSleep(bool autoSleep); void EnableAutoSleep(bool autoSleep);
void EnableNodeSynchronization(bool nodeSynchronization);
Nz::Boxf GetAABB() const; Nz::Boxf GetAABB() const;
Nz::Vector3f GetAngularDamping() const; Nz::Vector3f GetAngularDamping() const;
@ -43,6 +44,7 @@ namespace Ndk
bool IsAutoSleepEnabled() const; bool IsAutoSleepEnabled() const;
bool IsMoveable() const; bool IsMoveable() const;
bool IsNodeSynchronizationEnabled() const;
bool IsSleeping() const; bool IsSleeping() const;
void SetAngularDamping(const Nz::Vector3f& angularDamping); void SetAngularDamping(const Nz::Vector3f& angularDamping);
@ -60,7 +62,10 @@ namespace Ndk
static ComponentIndex componentIndex; static ComponentIndex componentIndex;
private: private:
void ApplyPhysicsState(Nz::RigidBody3D& rigidBody) const;
void CopyPhysicsState(const Nz::RigidBody3D& rigidBody);
Nz::RigidBody3D& GetRigidBody(); Nz::RigidBody3D& GetRigidBody();
const Nz::RigidBody3D& GetRigidBody() const;
void OnAttached() override; void OnAttached() override;
void OnComponentAttached(BaseComponent& component) override; void OnComponentAttached(BaseComponent& component) override;
@ -70,7 +75,20 @@ namespace Ndk
void OnEntityDisabled() override; void OnEntityDisabled() override;
void OnEntityEnabled() override; void OnEntityEnabled() override;
struct PendingPhysObjectStates
{
Nz::Vector3f angularDamping;
Nz::Vector3f massCenter;
bool autoSleep;
bool valid = false;
float gravityFactor;
float linearDamping;
float mass;
};
std::unique_ptr<Nz::RigidBody3D> m_object; std::unique_ptr<Nz::RigidBody3D> m_object;
PendingPhysObjectStates m_pendingStates;
bool m_nodeSynchronizationEnabled;
}; };
} }

View File

@ -7,16 +7,21 @@
namespace Ndk namespace Ndk
{ {
inline PhysicsComponent3D::PhysicsComponent3D() :
m_nodeSynchronizationEnabled(true)
{
}
/*! /*!
* \brief Constructs a PhysicsComponent3D object by copy semantic * \brief Constructs a PhysicsComponent3D object by copy semantic
* *
* \param physics PhysicsComponent3D to copy * \param physics PhysicsComponent3D to copy
*/ */
inline PhysicsComponent3D::PhysicsComponent3D(const PhysicsComponent3D& physics) :
inline PhysicsComponent3D::PhysicsComponent3D(const PhysicsComponent3D& physics) m_nodeSynchronizationEnabled(physics.m_nodeSynchronizationEnabled)
{ {
// No copy of physical object (because we only create it when attached to an entity) // We can't make a copy of the RigidBody3D, as we are not attached yet (and will possibly be attached to another world)
NazaraUnused(physics); CopyPhysicsState(physics.GetRigidBody());
} }
/*! /*!
@ -75,7 +80,6 @@ namespace Ndk
* *
* \remark Produces a NazaraAssert if the physics object is invalid * \remark Produces a NazaraAssert if the physics object is invalid
*/ */
inline void PhysicsComponent3D::EnableAutoSleep(bool autoSleep) inline void PhysicsComponent3D::EnableAutoSleep(bool autoSleep)
{ {
NazaraAssert(m_object, "Invalid physics object"); NazaraAssert(m_object, "Invalid physics object");
@ -83,6 +87,22 @@ namespace Ndk
m_object->EnableAutoSleep(autoSleep); m_object->EnableAutoSleep(autoSleep);
} }
/*!
* \brief Enables position/rotation synchronization with the NodeComponent
*
* By default, at every update of the PhysicsSystem3D, the NodeComponent's position and rotation (if any) will be synchronized with
* the values of the PhysicsComponent3D. This function allows to enable/disable this behavior on a per-entity basis.
*
* \param nodeSynchronization Should synchronization occur between NodeComponent and PhysicsComponent3D
*/
inline void PhysicsComponent3D::EnableNodeSynchronization(bool nodeSynchronization)
{
m_nodeSynchronizationEnabled = nodeSynchronization;
if (m_entity)
m_entity->Invalidate();
}
/*! /*!
* \brief Gets the AABB of the physics object * \brief Gets the AABB of the physics object
* \return AABB of the object * \return AABB of the object
@ -258,13 +278,23 @@ namespace Ndk
return m_object->IsMoveable(); return m_object->IsMoveable();
} }
/*!
* \brief Checks if position & rotation are synchronized with NodeComponent
* \return true If synchronization is enabled
*
* \see EnableNodeSynchronization
*/
inline bool PhysicsComponent3D::IsNodeSynchronizationEnabled() const
{
return m_nodeSynchronizationEnabled;
}
/*! /*!
* \brief Checks whether the entity is currently sleeping * \brief Checks whether the entity is currently sleeping
* \return true If it is the case * \return true If it is the case
* *
* \remark Produces a NazaraAssert if the physics object is invalid * \remark Produces a NazaraAssert if the physics object is invalid
*/ */
inline bool PhysicsComponent3D::IsSleeping() const inline bool PhysicsComponent3D::IsSleeping() const
{ {
NazaraAssert(m_object, "Invalid physics object"); NazaraAssert(m_object, "Invalid physics object");
@ -353,7 +383,8 @@ namespace Ndk
inline void PhysicsComponent3D::SetMass(float mass) inline void PhysicsComponent3D::SetMass(float mass)
{ {
NazaraAssert(m_object, "Invalid physics object"); NazaraAssert(m_object, "Invalid physics object");
NazaraAssert(mass > 0.f, "Mass should be positive"); NazaraAssert(mass >= 0.f, "Mass must be positive and finite");
NazaraAssert(std::isfinite(mass), "Mass must be positive and finite");
m_object->SetMass(mass); m_object->SetMass(mass);
} }
@ -430,13 +461,44 @@ namespace Ndk
m_object->SetRotation(rotation); m_object->SetRotation(rotation);
} }
inline void PhysicsComponent3D::ApplyPhysicsState(Nz::RigidBody3D& rigidBody) const
{
assert(m_pendingStates.valid);
rigidBody.EnableAutoSleep(m_pendingStates.autoSleep);
rigidBody.SetAngularDamping(m_pendingStates.angularDamping);
rigidBody.SetGravityFactor(m_pendingStates.gravityFactor);
rigidBody.SetLinearDamping(m_pendingStates.linearDamping);
rigidBody.SetMass(m_pendingStates.mass);
rigidBody.SetMassCenter(m_pendingStates.massCenter);
}
inline void PhysicsComponent3D::CopyPhysicsState(const Nz::RigidBody3D& rigidBody)
{
m_pendingStates.autoSleep = rigidBody.IsAutoSleepEnabled();
m_pendingStates.angularDamping = rigidBody.GetAngularDamping();
m_pendingStates.gravityFactor = rigidBody.GetGravityFactor();
m_pendingStates.linearDamping = rigidBody.GetLinearDamping();
m_pendingStates.mass = rigidBody.GetMass();
m_pendingStates.massCenter = rigidBody.GetMassCenter(Nz::CoordSys_Local);
m_pendingStates.valid = true;
}
/*! /*!
* \brief Gets the underlying physics object * \brief Gets the underlying physics object
* \return A reference to the physics object * \return A reference to the physics object
*/ */
inline Nz::RigidBody3D& PhysicsComponent3D::GetRigidBody() inline Nz::RigidBody3D& PhysicsComponent3D::GetRigidBody()
{ {
return *m_object.get(); return *m_object.get();
} }
/*!
* \brief Gets the underlying physics object
* \return A reference to the physics object
*/
inline const Nz::RigidBody3D& PhysicsComponent3D::GetRigidBody() const
{
return *m_object.get();
}
} }

View File

@ -41,8 +41,12 @@ namespace Ndk
matrix.MakeIdentity(); matrix.MakeIdentity();
m_object = std::make_unique<Nz::RigidBody3D>(&world, geom, matrix); m_object = std::make_unique<Nz::RigidBody3D>(&world, geom, matrix);
m_object->SetMass(1.f);
m_object->SetUserdata(reinterpret_cast<void*>(static_cast<std::ptrdiff_t>(m_entity->GetId()))); m_object->SetUserdata(reinterpret_cast<void*>(static_cast<std::ptrdiff_t>(m_entity->GetId())));
if (m_pendingStates.valid)
ApplyPhysicsState(*m_object);
else
m_object->SetMass(1.f);
} }
/*! /*!
@ -58,6 +62,7 @@ namespace Ndk
if (IsComponent<CollisionComponent3D>(component)) if (IsComponent<CollisionComponent3D>(component))
{ {
NazaraAssert(m_object, "Invalid object"); NazaraAssert(m_object, "Invalid object");
m_object->SetGeom(static_cast<CollisionComponent3D&>(component).GetGeom()); m_object->SetGeom(static_cast<CollisionComponent3D&>(component).GetGeom());
} }
} }
@ -75,6 +80,7 @@ namespace Ndk
if (IsComponent<CollisionComponent3D>(component)) if (IsComponent<CollisionComponent3D>(component))
{ {
NazaraAssert(m_object, "Invalid object"); NazaraAssert(m_object, "Invalid object");
m_object->SetGeom(Nz::NullCollider3D::New()); m_object->SetGeom(Nz::NullCollider3D::New());
} }
} }
@ -85,7 +91,11 @@ namespace Ndk
void PhysicsComponent3D::OnDetached() void PhysicsComponent3D::OnDetached()
{ {
m_object.reset(); if (m_object)
{
CopyPhysicsState(*m_object);
m_object.reset();
}
} }
void PhysicsComponent3D::OnEntityDestruction() void PhysicsComponent3D::OnEntityDestruction()

View File

@ -47,16 +47,20 @@ namespace Ndk
void PhysicsSystem3D::OnEntityValidation(Entity* entity, bool justAdded) void PhysicsSystem3D::OnEntityValidation(Entity* entity, bool justAdded)
{ {
// It's possible our entity got revalidated because of the addition/removal of a PhysicsComponent3D if (entity->HasComponent<PhysicsComponent3D>())
if (!justAdded)
{ {
// We take the opposite array from which the entity should belong to if (entity->GetComponent<PhysicsComponent3D>().IsNodeSynchronizationEnabled())
auto& entities = (entity->HasComponent<PhysicsComponent3D>()) ? m_staticObjects : m_dynamicObjects; m_dynamicObjects.Insert(entity);
entities.Remove(entity); else
} m_dynamicObjects.Remove(entity);
auto& entities = (entity->HasComponent<PhysicsComponent3D>()) ? m_dynamicObjects : m_staticObjects; m_staticObjects.Remove(entity);
entities.Insert(entity); }
else
{
m_dynamicObjects.Remove(entity);
m_staticObjects.Insert(entity);
}
if (!m_world) if (!m_world)
CreatePhysWorld(); CreatePhysWorld();

View File

@ -64,7 +64,7 @@ namespace Nz
NAZARA_CORE_API friend std::ostream& operator<<(std::ostream& o, const Id& id); NAZARA_CORE_API friend std::ostream& operator<<(std::ostream& o, const Id& id);
private: private:
Id(ThreadImpl* thread); explicit Id(ThreadImpl* thread);
ThreadImpl* m_id = nullptr; ThreadImpl* m_id = nullptr;
}; };

View File

@ -1,4 +1,4 @@
// Copyright (C) 2017 Jérôme Leclercq // Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Engine - Network module" // This file is part of the "Nazara Engine - Network module"
// For conditions of distribution and use, see copyright notice in Config.hpp // For conditions of distribution and use, see copyright notice in Config.hpp
@ -8,6 +8,7 @@ namespace Nz
{ {
inline ENetPeer::ENetPeer(ENetHost* host, UInt16 peerId) : inline ENetPeer::ENetPeer(ENetHost* host, UInt16 peerId) :
m_host(host), m_host(host),
m_state(ENetPeerState::Disconnected),
m_incomingSessionID(0xFF), m_incomingSessionID(0xFF),
m_outgoingSessionID(0xFF), m_outgoingSessionID(0xFF),
m_incomingPeerID(peerId), m_incomingPeerID(peerId),

View File

@ -7,6 +7,7 @@
#include <Nazara/Core/Error.hpp> #include <Nazara/Core/Error.hpp>
#include <Nazara/Core/HardwareInfo.hpp> #include <Nazara/Core/HardwareInfo.hpp>
#include <Nazara/Core/MovablePtr.hpp> #include <Nazara/Core/MovablePtr.hpp>
#include <ostream>
#if defined(NAZARA_PLATFORM_WINDOWS) #if defined(NAZARA_PLATFORM_WINDOWS)
#include <Nazara/Core/Win32/ThreadImpl.hpp> #include <Nazara/Core/Win32/ThreadImpl.hpp>

View File

@ -48,7 +48,8 @@ namespace Nz
NazaraAssert(!IsRegistered(socket), "Socket is already registered"); NazaraAssert(!IsRegistered(socket), "Socket is already registered");
epoll_event entry; epoll_event entry;
entry.events = 0; std::memset(&entry, 0, sizeof(epoll_event));
entry.data.fd = socket; entry.data.fd = socket;
if (eventFlags & SocketPollEvent_Read) if (eventFlags & SocketPollEvent_Read)

View File

@ -25,6 +25,8 @@ namespace Nz
NazaraAssert(handle != InvalidHandle, "Invalid handle"); NazaraAssert(handle != InvalidHandle, "Invalid handle");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
socklen_t bufferLength = sizeof(sockaddr_in); socklen_t bufferLength = sizeof(sockaddr_in);
SocketHandle newClient = accept(handle, reinterpret_cast<sockaddr*>(&nameBuffer), &bufferLength); SocketHandle newClient = accept(handle, reinterpret_cast<sockaddr*>(&nameBuffer), &bufferLength);
@ -376,7 +378,9 @@ namespace Nz
NazaraAssert(handle != InvalidHandle, "Invalid handle"); NazaraAssert(handle != InvalidHandle, "Invalid handle");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
socklen_t bufferLength = sizeof(sockaddr_in); std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
socklen_t bufferLength = sizeof(nameBuffer.size());
if (getpeername(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) if (getpeername(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR)
{ {
@ -416,6 +420,8 @@ namespace Nz
NazaraAssert(handle != InvalidHandle, "Invalid handle"); NazaraAssert(handle != InvalidHandle, "Invalid handle");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
socklen_t bufferLength = sizeof(sockaddr_in); socklen_t bufferLength = sizeof(sockaddr_in);
if (getsockname(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) if (getsockname(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR)
@ -509,6 +515,8 @@ namespace Nz
NazaraAssert(buffer && length > 0, "Invalid buffer"); NazaraAssert(buffer && length > 0, "Invalid buffer");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
socklen_t bufferLength = static_cast<socklen_t>(nameBuffer.size()); socklen_t bufferLength = static_cast<socklen_t>(nameBuffer.size());
IpAddress senderIp; IpAddress senderIp;
@ -580,6 +588,8 @@ namespace Nz
msgHdr.msg_iovlen = static_cast<int>(bufferCount); msgHdr.msg_iovlen = static_cast<int>(bufferCount);
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
if (from) if (from)
{ {
msgHdr.msg_name = nameBuffer.data(); msgHdr.msg_name = nameBuffer.data();

View File

@ -33,6 +33,8 @@ namespace Nz
NazaraAssert(handle != InvalidHandle, "Invalid handle"); NazaraAssert(handle != InvalidHandle, "Invalid handle");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
int bufferLength = static_cast<int>(nameBuffer.size()); int bufferLength = static_cast<int>(nameBuffer.size());
SocketHandle newClient = accept(handle, reinterpret_cast<sockaddr*>(&nameBuffer), &bufferLength); SocketHandle newClient = accept(handle, reinterpret_cast<sockaddr*>(&nameBuffer), &bufferLength);
@ -392,6 +394,8 @@ namespace Nz
NazaraAssert(handle != InvalidHandle, "Invalid handle"); NazaraAssert(handle != InvalidHandle, "Invalid handle");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
int bufferLength = static_cast<int>(nameBuffer.size()); int bufferLength = static_cast<int>(nameBuffer.size());
if (getpeername(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) if (getpeername(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR)
@ -413,6 +417,8 @@ namespace Nz
NazaraAssert(handle != InvalidHandle, "Invalid handle"); NazaraAssert(handle != InvalidHandle, "Invalid handle");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
int bufferLength = static_cast<int>(nameBuffer.size()); int bufferLength = static_cast<int>(nameBuffer.size());
if (getsockname(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR) if (getsockname(handle, reinterpret_cast<sockaddr*>(nameBuffer.data()), &bufferLength) == SOCKET_ERROR)
@ -537,6 +543,8 @@ namespace Nz
NazaraAssert(buffer && length > 0, "Invalid buffer"); NazaraAssert(buffer && length > 0, "Invalid buffer");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
int bufferLength = static_cast<int>(nameBuffer.size()); int bufferLength = static_cast<int>(nameBuffer.size());
IpAddress senderIp; IpAddress senderIp;
@ -592,6 +600,8 @@ namespace Nz
NazaraAssert(buffers && bufferCount > 0, "Invalid buffers"); NazaraAssert(buffers && bufferCount > 0, "Invalid buffers");
IpAddressImpl::SockAddrBuffer nameBuffer; IpAddressImpl::SockAddrBuffer nameBuffer;
std::fill(nameBuffer.begin(), nameBuffer.end(), 0);
int bufferLength = static_cast<int>(nameBuffer.size()); int bufferLength = static_cast<int>(nameBuffer.size());
IpAddress senderIp; IpAddress senderIp;

View File

@ -6,6 +6,7 @@
#include <Nazara/Physics3D/PhysWorld3D.hpp> #include <Nazara/Physics3D/PhysWorld3D.hpp>
#include <Newton/Newton.h> #include <Newton/Newton.h>
#include <algorithm> #include <algorithm>
#include <cmath>
#include <Nazara/Physics3D/Debug.hpp> #include <Nazara/Physics3D/Debug.hpp>
namespace Nz namespace Nz
@ -31,6 +32,7 @@ namespace Nz
m_body = NewtonCreateDynamicBody(m_world->GetHandle(), m_geom->GetHandle(m_world), m_matrix); m_body = NewtonCreateDynamicBody(m_world->GetHandle(), m_geom->GetHandle(m_world), m_matrix);
NewtonBodySetUserData(m_body, this); NewtonBodySetUserData(m_body, this);
NewtonBodySetTransformCallback(m_body, &TransformCallback);
} }
RigidBody3D::RigidBody3D(const RigidBody3D& object) : RigidBody3D::RigidBody3D(const RigidBody3D& object) :
@ -47,6 +49,7 @@ namespace Nz
m_body = NewtonCreateDynamicBody(m_world->GetHandle(), m_geom->GetHandle(m_world), m_matrix); m_body = NewtonCreateDynamicBody(m_world->GetHandle(), m_geom->GetHandle(m_world), m_matrix);
NewtonBodySetUserData(m_body, this); NewtonBodySetUserData(m_body, this);
NewtonBodySetTransformCallback(m_body, &TransformCallback);
SetMass(object.m_mass); SetMass(object.m_mass);
SetAngularDamping(object.GetAngularDamping()); SetAngularDamping(object.GetAngularDamping());
@ -303,16 +306,27 @@ namespace Nz
void RigidBody3D::SetMass(float mass) void RigidBody3D::SetMass(float mass)
{ {
NazaraAssert(mass >= 0.f, "Mass must be positive and finite");
NazaraAssert(std::isfinite(mass), "Mass must be positive and finite");
if (m_mass > 0.f) if (m_mass > 0.f)
{ {
// If we already have a mass, we already have an inertial matrix as well, just rescale it if (mass > 0.f)
float Ix, Iy, Iz; {
NewtonBodyGetMassMatrix(m_body, &m_mass, &Ix, &Iy, &Iz); // If we already have a mass, we already have an inertial matrix as well, just rescale it
float Ix, Iy, Iz;
NewtonBodyGetMassMatrix(m_body, &m_mass, &Ix, &Iy, &Iz);
float scale = mass/m_mass; float scale = mass / m_mass;
NewtonBodySetMassMatrix(m_body, mass, Ix*scale, Iy*scale, Iz*scale); NewtonBodySetMassMatrix(m_body, mass, Ix*scale, Iy*scale, Iz*scale);
}
else
{
NewtonBodySetMassMatrix(m_body, 0.f, 0.f, 0.f, 0.f);
NewtonBodySetForceAndTorqueCallback(m_body, nullptr);
}
} }
else if (mass > 0.f) else
{ {
Vector3f inertia, origin; Vector3f inertia, origin;
m_geom->ComputeInertialMatrix(&inertia, &origin); m_geom->ComputeInertialMatrix(&inertia, &origin);
@ -320,7 +334,6 @@ namespace Nz
NewtonBodySetCentreOfMass(m_body, &origin.x); NewtonBodySetCentreOfMass(m_body, &origin.x);
NewtonBodySetMassMatrix(m_body, mass, inertia.x*mass, inertia.y*mass, inertia.z*mass); NewtonBodySetMassMatrix(m_body, mass, inertia.x*mass, inertia.y*mass, inertia.z*mass);
NewtonBodySetForceAndTorqueCallback(m_body, &ForceAndTorqueCallback); NewtonBodySetForceAndTorqueCallback(m_body, &ForceAndTorqueCallback);
NewtonBodySetTransformCallback(m_body, &TransformCallback);
} }
m_mass = mass; m_mass = mass;