// // Copyright (c) 2008-2017 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "../Core/Context.h" #include "../Physics/PhysicsUtils.h" #include "../Physics/RigidBody.h" #include "../Physics/PhysicsWorld.h" #include "../Scene/Scene.h" #include "../IO/Log.h" #include "../Physics/RaycastVehicle.h" #include #include namespace Atomic { struct RaycastVehicleData { RaycastVehicleData() { vehicleRayCaster_ = 0; vehicle_ = 0; added_ = false; } ~RaycastVehicleData() { if (vehicleRayCaster_) { delete vehicleRayCaster_; } vehicleRayCaster_ = 0; if (vehicle_) { if (physWorld_ && added_) { btDynamicsWorld* pbtDynWorld = physWorld_->GetWorld(); if (pbtDynWorld) pbtDynWorld->removeAction(vehicle_); added_ = false; } delete vehicle_; } vehicle_ = 0; } btRaycastVehicle* Get() { return vehicle_; } void Init(Scene* scene, RigidBody* body, bool enabled) { int rightIndex = 0; int upIndex = 1; int forwardIndex = 2; PhysicsWorld* pPhysWorld = scene->GetComponent(); btDynamicsWorld* pbtDynWorld = pPhysWorld->GetWorld(); if (!pbtDynWorld) return; // Delete old vehicle & action first if (vehicleRayCaster_) delete vehicleRayCaster_; if (vehicle_) { if (added_) pbtDynWorld->removeAction(vehicle_); delete vehicle_; } vehicleRayCaster_ = new btDefaultVehicleRaycaster(pbtDynWorld); btRigidBody* bthullBody = body->GetBody(); vehicle_ = new btRaycastVehicle(tuning_, bthullBody, vehicleRayCaster_); if (enabled) { pbtDynWorld->addAction(vehicle_); added_ = true; } vehicle_->setCoordinateSystem(rightIndex, upIndex, forwardIndex); physWorld_ = pPhysWorld; } void SetEnabled(bool enabled) { if (!physWorld_ || !vehicle_) return; btDynamicsWorld* pbtDynWorld = physWorld_->GetWorld(); if (!pbtDynWorld) return; if (enabled && !added_) { pbtDynWorld->addAction(vehicle_); added_ = true; } else if (!enabled && added_) { pbtDynWorld->removeAction(vehicle_); added_ = false; } } WeakPtr physWorld_; btVehicleRaycaster* vehicleRayCaster_; btRaycastVehicle* vehicle_; btRaycastVehicle::btVehicleTuning tuning_; bool added_; }; RaycastVehicle::RaycastVehicle(Context* context) : LogicComponent(context) { // fixed update() for inputs and post update() to sync wheels for rendering SetUpdateEventMask(USE_FIXEDUPDATE | USE_FIXEDPOSTUPDATE | USE_POSTUPDATE); vehicleData_ = new RaycastVehicleData(); wheelNodes_.Clear(); activate_ = false; inAirRPM_ = 0.0f; maxSideSlipSpeed_ = 4.0f; } RaycastVehicle::~RaycastVehicle() { delete vehicleData_; wheelNodes_.Clear(); } const char* wheelElementNames[] = { "Number of wheels", " Wheel node id", " Wheel direction", " Wheel axle", " Wheel rest length", " Wheel radius", " Wheel is front wheel", " Steering", " Connection point vector", " Original rotation", " Cumulative skid info", " Side skip speed", " Grounded", " Contact position", " Contact normal", " Suspension stiffness", " Damping relaxation", " Damping compression", " Friction slip", " Roll influence", " Engine force", " Brake", 0 }; void RaycastVehicle::RegisterObject(Context* context) { context->RegisterFactory(); ATOMIC_MIXED_ACCESSOR_VARIANT_VECTOR_STRUCTURE_ATTRIBUTE("Wheel data", GetWheelDataAttr, SetWheelDataAttr, VariantVector, Variant::emptyVariantVector, wheelElementNames, AM_DEFAULT); ATOMIC_ATTRIBUTE("Maximum side slip threshold", float, maxSideSlipSpeed_, 4.0f, AM_DEFAULT); ATOMIC_ATTRIBUTE("RPM for wheel motors in air (0=calculate)", float, inAirRPM_, 0.0f, AM_DEFAULT); } void RaycastVehicle::OnSetEnabled() { if (vehicleData_) vehicleData_->SetEnabled(IsEnabledEffective()); } void RaycastVehicle::ApplyAttributes() { int index = 0; hullBody_ = node_->GetOrCreateComponent(); Scene* scene = GetScene(); vehicleData_->Init(scene, hullBody_, IsEnabledEffective()); VariantVector& value = loadedWheelData_; int numObjects = value[index++].GetInt(); int wheelIndex = 0; origRotation_.Clear(); skidInfoCumulative_.Clear(); wheelSideSlipSpeed_.Clear(); for (int i = 0; i < numObjects; i++) { int node_id = value[index++].GetInt(); Vector3 direction = value[index++].GetVector3(); Vector3 axle = value[index++].GetVector3(); float restLength = value[index++].GetFloat(); float radius = value[index++].GetFloat(); bool isFrontWheel = value[index++].GetBool(); float steering = value[index++].GetFloat(); Vector3 connectionPoint = value[index++].GetVector3(); Quaternion origRotation = value[index++].GetQuaternion(); float skidInfoC = value[index++].GetFloat(); float sideSlipSpeed = value[index++].GetFloat(); bool isContact = value[index++].GetBool(); Vector3 contactPosition = value[index++].GetVector3(); Vector3 contactNormal = value[index++].GetVector3(); float suspensionStiffness = value[index++].GetFloat(); float dampingRelaxation = value[index++].GetFloat(); float dampingCompression = value[index++].GetFloat(); float frictionSlip = value[index++].GetFloat(); float rollInfluence = value[index++].GetFloat(); float engineForce = value[index++].GetFloat(); float brake = value[index++].GetFloat(); float skidInfo = value[index++].GetFloat(); Node* wheelNode = GetScene()->GetNode(node_id); if (!wheelNode) { ATOMIC_LOGERROR("RaycastVehicle: Incorrect node id = " + String(node_id) + " index: " + String(index)); continue; } btRaycastVehicle* vehicle = vehicleData_->Get(); int id = GetNumWheels(); btVector3 connectionPointCS0(connectionPoint.x_, connectionPoint.y_, connectionPoint.z_); btVector3 wheelDirectionCS0(direction.x_, direction.y_, direction.z_); btVector3 wheelAxleCS(axle.x_, axle.y_, axle.z_); btWheelInfo& wheel = vehicle->addWheel(connectionPointCS0, wheelDirectionCS0, wheelAxleCS, restLength, radius, vehicleData_->tuning_, isFrontWheel); wheelNodes_.Push(wheelNode); origRotation_.Push(origRotation); skidInfoCumulative_.Push(skidInfoC); wheelSideSlipSpeed_.Push(sideSlipSpeed); SetSteeringValue(wheelIndex, steering); wheel.m_raycastInfo.m_isInContact = isContact; wheel.m_raycastInfo.m_contactNormalWS = btVector3(contactNormal.x_, contactNormal.y_, contactNormal.z_); wheel.m_raycastInfo.m_contactPointWS = btVector3(contactPosition.x_, contactPosition.y_, contactPosition.z_); wheel.m_suspensionStiffness = suspensionStiffness; wheel.m_wheelsDampingRelaxation = dampingRelaxation; wheel.m_wheelsDampingCompression = dampingCompression; wheel.m_frictionSlip = frictionSlip; wheel.m_rollInfluence = rollInfluence; wheel.m_engineForce = engineForce; wheel.m_brake = brake; wheel.m_skidInfo = skidInfo; wheelIndex++; } ATOMIC_LOGDEBUG("maxSideSlipSpeed_ value: " + String(maxSideSlipSpeed_)); ATOMIC_LOGDEBUG("loaded items: " + String(index)); ATOMIC_LOGDEBUG("loaded wheels: " + String(GetNumWheels())); } void RaycastVehicle::Init() { hullBody_ = node_->GetOrCreateComponent(); Scene* scene = GetScene(); vehicleData_->Init(scene, hullBody_, IsEnabledEffective()); } void RaycastVehicle::FixedUpdate(float timeStep) { btRaycastVehicle* vehicle = vehicleData_->Get(); for (int i = 0; i < GetNumWheels(); i++) { btWheelInfo whInfo = vehicle->getWheelInfo(i); if (whInfo.m_engineForce != 0.0f || whInfo.m_steering != 0.0f) { hullBody_->Activate(); break; } } } void RaycastVehicle::PostUpdate(float timeStep) { btRaycastVehicle* vehicle = vehicleData_->Get(); for (int i = 0; i < GetNumWheels(); i++) { vehicle->updateWheelTransform(i, true); btTransform transform = vehicle->getWheelTransformWS(i); Vector3 origin = ToVector3(transform.getOrigin()); Quaternion qRot = ToQuaternion(transform.getRotation()); Node* pWheel = wheelNodes_[i]; pWheel->SetWorldPosition(origin); pWheel->SetWorldRotation(qRot * origRotation_[i]); } } void RaycastVehicle::FixedPostUpdate(float timeStep) { btRaycastVehicle* vehicle = vehicleData_->Get(); Vector3 velocity = hullBody_->GetLinearVelocity(); for (int i = 0; i < GetNumWheels(); i++) { btWheelInfo& whInfo = vehicle->getWheelInfo(i); if (!WheelIsGrounded(i) && GetEngineForce(i) != 0.0f) { float delta; if (inAirRPM_ != 0.0f) { delta = inAirRPM_ * timeStep / 60.0f; } else { delta = 8.0f * GetEngineForce(i) * timeStep / (hullBody_->GetMass() * GetWheelRadius(i)); } if (Abs(whInfo.m_deltaRotation) < Abs(delta)) { whInfo.m_rotation += delta - whInfo.m_deltaRotation; whInfo.m_deltaRotation = delta; } if (skidInfoCumulative_[i] > 0.05f) { skidInfoCumulative_[i] -= 0.002; } } else { skidInfoCumulative_[i] = GetWheelSkidInfo(i); } wheelSideSlipSpeed_[i] = Abs(ToVector3(whInfo.m_raycastInfo.m_wheelAxleWS).DotProduct(velocity)); if (wheelSideSlipSpeed_[i] > maxSideSlipSpeed_) { skidInfoCumulative_[i] = Clamp(skidInfoCumulative_[i], 0.0f, 0.89f); } } } void RaycastVehicle::SetMaxSideSlipSpeed(float speed) { maxSideSlipSpeed_ = speed; } float RaycastVehicle::GetMaxSideSlipSpeed() const { return maxSideSlipSpeed_; } void RaycastVehicle::SetWheelSkidInfoCumulative(int wheel, float skid) { skidInfoCumulative_[wheel] = skid; } float RaycastVehicle::GetWheelSkidInfoCumulative(int wheel) const { return skidInfoCumulative_[wheel]; } void RaycastVehicle::AddWheel(Node* wheelNode, Vector3 wheelDirection, Vector3 wheelAxle, float restLength, float wheelRadius, bool frontWheel) { btRaycastVehicle* vehicle = vehicleData_->Get(); int id = GetNumWheels(); Vector3 connectionPoint = wheelNode->GetWorldPosition() - node_->GetWorldPosition(); btVector3 connectionPointCS0(connectionPoint.x_, connectionPoint.y_, connectionPoint.z_); btVector3 wheelDirectionCS0(wheelDirection.x_, wheelDirection.y_, wheelDirection.z_); btVector3 wheelAxleCS(wheelAxle.x_, wheelAxle.y_, wheelAxle.z_); btWheelInfo& wheel = vehicle->addWheel(connectionPointCS0, wheelDirectionCS0, wheelAxleCS, restLength, wheelRadius, vehicleData_->tuning_, frontWheel); wheelNodes_.Push(wheelNode); origRotation_.Push(wheelNode->GetWorldRotation()); skidInfoCumulative_.Push(1.0f); wheelSideSlipSpeed_.Push(0.0f); wheel.m_raycastInfo.m_isInContact = false; } void RaycastVehicle::ResetSuspension() { btRaycastVehicle* vehicle = vehicleData_->Get(); vehicle->resetSuspension(); } void RaycastVehicle::UpdateWheelTransform(int wheel, bool interpolated) { btRaycastVehicle* vehicle = vehicleData_->Get(); vehicle->updateWheelTransform(wheel, interpolated); } Vector3 RaycastVehicle::GetWheelPosition(int wheel) { btRaycastVehicle* vehicle = vehicleData_->Get(); btTransform transform = vehicle->getWheelTransformWS(wheel); Vector3 origin = ToVector3(transform.getOrigin()); return origin; } Quaternion RaycastVehicle::GetWheelRotation(int wheel) { btRaycastVehicle* vehicle = vehicleData_->Get(); btTransform transform = vehicle->getWheelTransformWS(wheel); Quaternion rotation = ToQuaternion(transform.getRotation()); return rotation; } Vector3 RaycastVehicle::GetWheelConnectionPoint(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return ToVector3(whInfo.m_chassisConnectionPointCS); } void RaycastVehicle::SetSteeringValue(int wheel, float steeringValue) { btRaycastVehicle* vehicle = vehicleData_->Get(); vehicle->setSteeringValue(steeringValue, wheel); } float RaycastVehicle::GetSteeringValue(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_steering; } void RaycastVehicle::SetWheelSuspensionStiffness(int wheel, float stiffness) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_suspensionStiffness = stiffness; } float RaycastVehicle::GetWheelSuspensionStiffness(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_suspensionStiffness; } void RaycastVehicle::SetWheelDampingRelaxation(int wheel, float damping) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_wheelsDampingRelaxation = damping; } float RaycastVehicle::GetWheelDampingRelaxation(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_wheelsDampingRelaxation; } void RaycastVehicle::SetWheelDampingCompression(int wheel, float compression) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_wheelsDampingCompression = compression; } float RaycastVehicle::GetWheelDampingCompression(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_wheelsDampingCompression; } void RaycastVehicle::SetWheelFrictionSlip(int wheel, float slip) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_frictionSlip = slip; } float RaycastVehicle::GetWheelFrictionSlip(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_frictionSlip; } void RaycastVehicle::SetWheelRollInfluence(int wheel, float rollInfluence) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_rollInfluence = rollInfluence; } Vector3 RaycastVehicle::GetContactPosition(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return ToVector3(whInfo.m_raycastInfo.m_contactPointWS); } Vector3 RaycastVehicle::GetContactNormal(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return ToVector3(whInfo.m_raycastInfo.m_contactNormalWS); } float RaycastVehicle::GetWheelSideSlipSpeed(int wheel) const { return wheelSideSlipSpeed_[wheel]; } float RaycastVehicle::GetWheelRollInfluence(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_rollInfluence; } void RaycastVehicle::SetWheelRadius(int wheel, float wheelRadius) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_wheelsRadius = wheelRadius; } float RaycastVehicle::GetWheelRadius(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_wheelsRadius; } void RaycastVehicle::SetEngineForce(int wheel, float force) { btRaycastVehicle* vehicle = vehicleData_->Get(); vehicle->applyEngineForce(force, wheel); } float RaycastVehicle::GetEngineForce(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_engineForce; } void RaycastVehicle::SetBrake(int wheel, float force) { btRaycastVehicle* vehicle = vehicleData_->Get(); vehicle->setBrake(force, wheel); } float RaycastVehicle::GetBrake(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_brake; } int RaycastVehicle::GetNumWheels() const { btRaycastVehicle* vehicle = vehicleData_->Get(); return vehicle->getNumWheels(); } Node* RaycastVehicle::GetWheelNode(int wheel) const { return wheelNodes_[wheel]; } void RaycastVehicle::SetMaxSuspensionTravel(int wheel, float maxSuspensionTravel) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_maxSuspensionTravelCm = maxSuspensionTravel; } float RaycastVehicle::GetMaxSuspensionTravel(int wheel) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_maxSuspensionTravelCm; } void RaycastVehicle::SetWheelDirection(int wheel, Vector3 direction) { btVector3 dir(direction.x_, direction.y_, direction.z_); btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_wheelDirectionCS = dir; } Vector3 RaycastVehicle::GetWheelDirection(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return ToVector3(whInfo.m_wheelDirectionCS); } void RaycastVehicle::SetWheelAxle(int wheel, Vector3 axle) { btVector3 ax(axle.x_, axle.y_, axle.z_); btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_wheelAxleCS = ax; } Vector3 RaycastVehicle::GetWheelAxle(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return ToVector3(whInfo.m_wheelAxleCS); } void RaycastVehicle::SetWheelRestLength(int wheel, float length) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_suspensionRestLength1 = length; } float RaycastVehicle::GetWheelRestLength(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_suspensionRestLength1; } void RaycastVehicle::SetWheelSkidInfo(int wheel, float factor) { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); whInfo.m_skidInfo = factor; } float RaycastVehicle::GetWheelSkidInfo(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_skidInfo; } bool RaycastVehicle::IsFrontWheel(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo& whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_bIsFrontWheel; } bool RaycastVehicle::WheelIsGrounded(int wheel) const { btRaycastVehicle* vehicle = vehicleData_->Get(); btWheelInfo whInfo = vehicle->getWheelInfo(wheel); return whInfo.m_raycastInfo.m_isInContact; } void RaycastVehicle::SetInAirRPM(float rpm) { inAirRPM_ = rpm; } float RaycastVehicle::GetInAirRPM() const { return inAirRPM_; } void RaycastVehicle::ResetWheels() { ResetSuspension(); for (int i = 0; i < GetNumWheels(); i++) { UpdateWheelTransform(i, true); Vector3 origin = GetWheelPosition(i); Node* wheelNode = GetWheelNode(i); wheelNode->SetWorldPosition(origin); } } VariantVector RaycastVehicle::GetWheelDataAttr() const { VariantVector ret; ret.Reserve(GetNumWheels() * 22 + 1); ret.Push(GetNumWheels()); for (int i = 0; i < GetNumWheels(); i++) { Node* wNode = GetWheelNode(i); int node_id = wNode->GetID(); ATOMIC_LOGDEBUG("RaycastVehicle: Saving node id = " + String(node_id)); ret.Push(node_id); ret.Push(GetWheelDirection(i)); ret.Push(GetWheelAxle(i)); ret.Push(GetWheelRestLength(i)); ret.Push(GetWheelRadius(i)); ret.Push(IsFrontWheel(i)); ret.Push(GetSteeringValue(i)); ret.Push(GetWheelConnectionPoint(i)); ret.Push(origRotation_[i]); ret.Push(GetWheelSkidInfoCumulative(i)); ret.Push(GetWheelSideSlipSpeed(i)); ret.Push(WheelIsGrounded(i)); ret.Push(GetContactPosition(i)); ret.Push(GetContactNormal(i)); // 14 ret.Push(GetWheelSuspensionStiffness(i)); ret.Push(GetWheelDampingRelaxation(i)); ret.Push(GetWheelDampingCompression(i)); ret.Push(GetWheelFrictionSlip(i)); ret.Push(GetWheelRollInfluence(i)); ret.Push(GetEngineForce(i)); ret.Push(GetBrake(i)); ret.Push(GetWheelSkidInfo(i)); } ATOMIC_LOGDEBUG("RaycastVehicle: saved items: " + String(ret.Size())); ATOMIC_LOGDEBUG("maxSideSlipSpeed_ value save: " + String(maxSideSlipSpeed_)); return ret; } void RaycastVehicle::SetWheelDataAttr(const VariantVector& value) { if (!vehicleData_) { ATOMIC_LOGERROR("RaycastVehicle: vehicleData doesn't exist"); return; } if (value.Size() < 2) { ATOMIC_LOGERROR("RaycastVehicle: Incorrect vehicleData"); return; } loadedWheelData_ = value; } } // namespace Urho3D