/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */ #include "stdafx.h" #include "V8DataModel/PartOperation.h" #include "V8World/World.h" #include "V8World/Contact.h" #include "V8World/Joint.h" #include "V8World/WeldJoint.h" #include "V8World/ContactManagerSpatialHash.h" #include "V8World/ContactManager.h" #include "V8World/SendPhysics.h" #include "V8World/Mechanism.h" #include "V8World/CleanStage.h" #include "V8World/GroundStage.h" #include "V8World/TreeStage.h" #include "V8World/SpatialFilter.h" #include "V8World/AssemblyStage.h" #include "V8World/MovingAssemblyStage.h" #include "V8World/StepJointsStage.h" #include "V8World/HumanoidStage.h" #include "V8World/SleepStage.h" #include "V8World/SimulateStage.h" #include "V8World/JointBuilder.h" #include "V8World/Assembly.h" #include "V8World/Clump.h" #include "V8Kernel/Kernel.h" #include "V8Kernel/Constants.h" #include "v8kernel/Body.h" #include "Util/Profiling.h" #include "Util/Analytics.h" #include "Network/NetworkOwner.h" #include "v8datamodel/JointInstance.h" #include "v8datamodel/MegaCluster.h" #include "BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.h" #include "BulletCollision/CollisionDispatch/btCollisionDispatcher.h" #include "BulletCollision/CollisionDispatch/btCollisionWorld.h" #include "BulletCollision/GImpact/btGImpactCollisionAlgorithm.h" #include "rbx/Profiler.h" LOGVARIABLE(CyclicExecutiveWorldSteps, 0) LOGGROUP(Physics) DYNAMIC_FASTINTVARIABLE(BulletContactBreakThresholdPercent, 200) DYNAMIC_FASTINTVARIABLE(BulletContactBreakOrthogonalThresholdPercent, 200) DYNAMIC_FASTINTVARIABLE(BulletContactBreakOrthogonalThresholdActivatePercent, 200) DYNAMIC_FASTINTVARIABLE(WorldStepMax,30) DYNAMIC_FASTINTVARIABLE(WorldStepsOffsetAdjustRate,100) /* in thousands */ DYNAMIC_FASTINTVARIABLE(smoothnessReportThreshold,10000) DYNAMIC_FASTINTVARIABLE(MaxMissedWorldStepsRemembered, 12) DYNAMIC_FASTFLAGVARIABLE(CyclicExecutiveThrottlingCancelWorldStepAccum, false) DYNAMIC_FASTFLAG(MaterialPropertiesEnabled) FASTINTVARIABLE(PhysicsBulletManifoldPoolSize, 1024) SYNCHRONIZED_FASTFLAG(MaterialPropertiesNewIsDefault) SYNCHRONIZED_FASTFLAG(NewPhysicalPropertiesForcedOnAll) FASTFLAGVARIABLE(FixBulletGJKOptimization, false) FASTFLAGVARIABLE(FixBulletGJKOptimization2, false) FASTFLAG(PhysicsAnalyzerEnabled) LOGVARIABLE(WorldStepsBehind, 0) LOGVARIABLE(WorldStepsBehindG, 0) extern bool gUseTriangleNormalForConvexTriangle; extern bool gFixBulletGJKOptimization; extern bool gFixBulletGJKOptimization2; namespace RBX { EThrottle::EThrottleType EThrottle::globalDebugEThrottle = EThrottle::ThrottleDefaultAuto; // globalSwitch // Corresponds to these throttling ratios (for A/B, this is read as A steps throttled for every B Steps) // 0/8, 1/8, 1/4, 1/3, 1/2, 2/3, 3/4, 7/8, 15/16 int const EThrottle::throttleSetting[] = {100000000, 8, 4, 3, 2, -3, -4, -8, -16}; EThrottle::EThrottle() : requestedSkip(1) , throttleIndex(1) { if(globalDebugEThrottle == EThrottle::ThrottleDefaultAuto) usedSkip = 100000000; else usedSkip = 1; } bool EThrottle::increaseLoad(bool increase) { if(globalDebugEThrottle == EThrottle::ThrottleDefaultAuto) { if (increase) { if (throttleIndex > 0) { throttleIndex -= 1; return true; } } else { if (throttleIndex < 8) { throttleIndex += 1; return true; } } } else { if (increase) { if (requestedSkip > 1) { requestedSkip /= 2; return true; } } else { if (requestedSkip < 16) { requestedSkip *= 2; return true; } } } return false; } bool EThrottle::computeThrottle(int step) { switch (globalDebugEThrottle) { case EThrottle::ThrottleDefaultAuto: { usedSkip = throttleSetting[throttleIndex]; return ((usedSkip > 0) == ((step % usedSkip) == 0)); } case EThrottle::Skip2: { usedSkip = 2; return ((step % 2) != 0); } case EThrottle::Skip4: { usedSkip = 4; return ((step % 4) != 0); } case EThrottle::Skip8: { usedSkip = 8; return ((step % 8) != 0); } case EThrottle::Skip16: { usedSkip = 16; return ((step % 16) != 0); } case EThrottle::ThrottleAlways: { usedSkip = 100000000; return true; } case EThrottle::ThrottleDisabled: default: { usedSkip = 1; return false; } } } float EThrottle::getEnvironmentSpeed() const { if(globalDebugEThrottle == EThrottle::ThrottleDefaultAuto) return usedSkip > 0 ? (1.0f - (1.0f / usedSkip)) : -1.0f / usedSkip; else return 1.0f / usedSkip; } //////////////////////////////////////////////////////////////////////////////////// World::World() : #pragma warning(push) #pragma warning(disable: 4355) // 'this' : used in base member initializer list contactManager(new ContactManager(this)), cleanStage(new CleanStage(NULL, this)), #pragma warning(pop) sendPhysics( new SendPhysics() ), worldSteps(0), worldStepId(0), worldStepAccumulated(4.0f), //Initialize 4 step so that we can run WorldSteps 240Hz consistently on the first run inStepCode(false), inJointNotification(NULL), numContacts(0), numJoints(0), numLinkCalls(0), groundPrimitive( new Primitive(Geometry::GEOMETRY_BLOCK) ), profilingWorldStep(new Profiling::CodeProfiler("World Step")), profilingBreak(new Profiling::CodeProfiler("Break Time")), profilingAssembly(new Profiling::CodeProfiler("Assembler Time")), profilingFilter(new Profiling::CodeProfiler("Filter Time")), profilingUiStep(new Profiling::CodeProfiler("UI Step")), frmThrottle(1.0f), bulletDispatcher(NULL), bulletCollisionConfiguration(NULL), UIDGenerator( 0 ), physicsAnalyzerEnabled( false ), fallenPartDestroyHeight(-500.0f), physicalMaterialsMode(PhysicalPropertiesMode_Default), errorCount( 0.0), passCount(0.0), lastNumWorldSteps(0), lastSendTimeStamp(Time::nowFast()), lastFrameTimeStamp(Time::nowFast()), worldStepOffset(0.0), frameinfosSize(0.0), frameinfosTarget(0.0), targetDelayTenths(0.0), infosSizeTenths(0.0), maxDelta(0.0), frameinfosCount(0.0) { groundPrimitive->setAnchoredProperty(true); groundPrimitive->setWorld(this); cleanStage->onPrimitiveAdded(groundPrimitive); std::vector worldProfilers; loadProfilers(worldProfilers); // set up collision world, collision configs, etc btDefaultCollisionConstructionInfo info; info.m_defaultMaxPersistentManifoldPoolSize = FInt::PhysicsBulletManifoldPoolSize; bulletCollisionConfiguration = new btDefaultCollisionConfiguration(info); bulletDispatcher = new btCollisionDispatcher(bulletCollisionConfiguration); FASTLOG1(FLog::Physics, "Set bulletDispatcher to: %p", bulletDispatcher); btGImpactCollisionAlgorithm::registerAlgorithm(bulletDispatcher); // This fixes Bullet collision normal between convexes and triangle meshes to be exactly the same as triangle normal gUseTriangleNormalForConvexTriangle = true; if (FFlag::FixBulletGJKOptimization) { gFixBulletGJKOptimization = true; } if (FFlag::FixBulletGJKOptimization2) { gFixBulletGJKOptimization2 = true; } } World::~World() { cleanStage->onPrimitiveRemoving(groundPrimitive); RBXASSERT(groundPrimitive->getNumEdges() == 0); groundPrimitive->setWorld(NULL); RBXASSERT(numJoints == 0); RBXASSERT(numContacts == 0); RBXASSERT(primitives.size() == 0); RBXASSERT(breakableJoints.empty()); delete groundPrimitive; delete sendPhysics; delete cleanStage; delete contactManager; if (bulletDispatcher) delete bulletDispatcher; if (bulletCollisionConfiguration) delete bulletCollisionConfiguration; FASTLOG1(FLog::Physics, "Remove bulletDispatcher to: %p", bulletDispatcher); } void World::loadProfilers(std::vector& worldProfilers) const { worldProfilers.push_back(profilingBreak.get()); worldProfilers.push_back(profilingAssembly.get()); worldProfilers.push_back(profilingFilter.get()); worldProfilers.push_back(profilingUiStep.get()); worldProfilers.push_back(getContactManager()->profilingBroadphase.get()); worldProfilers.push_back(getSleepStage()->profilingCollision.get()); worldProfilers.push_back(getSleepStage()->profilingJointSleep.get()); worldProfilers.push_back(getSleepStage()->profilingWake.get()); worldProfilers.push_back(getSleepStage()->profilingSleep.get()); worldProfilers.push_back(getStepJointsStage()->profilingJointUpdate.get()); worldProfilers.push_back(getKernel()->profilingKernelBodies.get()); worldProfilers.push_back(getKernel()->profilingKernelConnectors.get()); } TreeStage* World::getTreeStage() { return rbx_static_cast(cleanStage->findStage(IStage::TREE_STAGE)); } SpatialFilter* World::getSpatialFilter() { return rbx_static_cast(cleanStage->findStage(IStage::SPATIAL_FILTER)); } const SpatialFilter* World::getSpatialFilter() const { return rbx_static_cast(cleanStage->findStage(IStage::SPATIAL_FILTER)); } AssemblyStage* World::getAssemblyStage() { return rbx_static_cast(cleanStage->findStage(IStage::ASSEMBLY_STAGE)); } const AssemblyStage* World::getAssemblyStage() const { return rbx_static_cast(cleanStage->findStage(IStage::ASSEMBLY_STAGE)); } MovingAssemblyStage* World::getMovingAssemblyStage() { return rbx_static_cast(cleanStage->findStage(IStage::MOVING_ASSEMBLY_STAGE)); } StepJointsStage* World::getStepJointsStage() { return rbx_static_cast(cleanStage->findStage(IStage::STEP_JOINTS_STAGE)); } const StepJointsStage* World::getStepJointsStage() const { return rbx_static_cast(cleanStage->findStage(IStage::STEP_JOINTS_STAGE)); } GroundStage* World::getGroundStage() { return rbx_static_cast(cleanStage->findStage(IStage::GROUND_STAGE)); } HumanoidStage* World::getHumanoidStage() { return rbx_static_cast(cleanStage->findStage(IStage::HUMANOID_STAGE)); } SleepStage* World::getSleepStage() { return rbx_static_cast(cleanStage->findStage(IStage::SLEEP_STAGE)); } const SleepStage* World::getSleepStage() const { return rbx_static_cast(cleanStage->findStage(IStage::SLEEP_STAGE)); } SimulateStage* World::getSimulateStage() { return rbx_static_cast(cleanStage->findStage(IStage::SIMULATE_STAGE)); } const Kernel* World::getKernel() const { return cleanStage->getKernel(); } Kernel* World::getKernel() { return cleanStage->getKernel(); } SendPhysics* World::getSendPhysics() { return sendPhysics; } SimSendFilter& World::getSimSendFilter() { return getSpatialFilter()->getSimSendFilter(); } int World::getNumBodies() const { return getKernel()->numBodies(); } int World::getNumPoints() const { return getKernel()->numPoints(); } int World::getNumConstraints() const { return getKernel()->numConnectors(); } int World::getMetric(IWorldStage::MetricType metricType) const { return cleanStage->getMetric(metricType); } int World::getNumHashNodes() const { return contactManager->getSpatialHash()->getNodesOut(); } int World::getMaxBucketSize() const { return contactManager->getSpatialHash()->getMaxBucket(); } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// // // onChanged Functions // void World::ticklePrimitive(Primitive* p, bool recursive) { if (!p) return; RBXASSERT(p->getWorld()); Assembly* a = p->getAssembly(); if (a) { getSleepStage()->onExternalTickleAssembly(a, recursive); } } Assembly* World::onPrimitiveEngineChanging(Primitive* p) { RBXASSERT(p->getWorld()); RBXASSERT(p->getAssembly()); RBXASSERT(p->inPipeline()); RBXASSERT(!inStepCode); return getAssemblyStage()->onEngineChanging(p); } void World::onPrimitiveEngineChanged(Assembly* changing) { RBXASSERT(changing); getAssemblyStage()->onEngineChanged(changing); } void World::onPrimitiveFixedChanging(Primitive* p) { RBXASSERT(p->getWorld()); RBXASSERT(!inStepCode); getGroundStage()->onPrimitiveFixedChanging(p); } void World::onPrimitiveFixedChanged(Primitive* p) { RBXASSERT(p->getWorld()); RBXASSERT(!inStepCode); getGroundStage()->onPrimitiveFixedChanged(p); } void World::onPrimitivePreventCollideChanged(Primitive* p) { ticklePrimitive(p, false); RBXASSERT(p->getWorld()); Contact* c = p->getFirstContact(); while (c) { Primitive* other = c->otherPrimitive(p); ticklePrimitive(other, false); c = p->getNextContact(c); } } void World::onPrimitiveContactParametersChanged(Primitive* p) { RBXASSERT(p->getWorld()); Contact* c = p->getFirstContact(); while (c) { c->onPrimitiveContactParametersChanged(); c = p->getNextContact(c); } } void World::onPrimitiveExtentsChanged(Primitive* p) { RBXASSERT(p->getWorld()); RBXASSERT(!inStepCode); contactManager->onPrimitiveExtentsChanged(p); } void World::onAssemblyInSimluationStage(Assembly* a) { getMovingAssemblyStage()->removeMovingGroundedAssembly(a); } void World::onPrimitiveGeometryChanged(Primitive* p) { RBXASSERT(p->getWorld()); RBXASSERT(!inStepCode); contactManager->onPrimitiveGeometryChanged(p); } void World::onJointPrimitiveNulling(Joint* j, Primitive* p) { cleanStage->onJointPrimitiveNulling(j, p); } void World::onJointPrimitiveSet(Joint* j, Primitive* p) { cleanStage->onJointPrimitiveSet(j, p); } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// void World::assemble() { RBXPROFILER_SCOPE("Physics", "assemble"); getTreeStage()->assemble(); } bool World::isAssembled() { return getTreeStage()->isAssembled(); } void World::setFRMThrottle(int value) { frmThrottle = value; } void World::sendClumpChangedMessage(Primitive* childPrim) { getTreeStage()->sendClumpChangedMessage(childPrim); } static void NotifyMovingRef(Assembly& assembly) { assembly.notifyMovedFromInternalPhysics(); } static void NotifyMoving(Assembly* assembly) { assembly->notifyMovedFromInternalPhysics(); } template void notifyMovingPrimitives(const Set& assemblies) { for_each(assemblies.begin(), assemblies.end(), NotifyMoving); } void World::notifyMovingAssemblies() { RBXPROFILER_SCOPE("Physics", "notifyMovingAssemblies"); RBX::Profiling::Mark mark(*profilingAssembly, false); //StandardOut::singleton()->printf(MESSAGE_INFO, "MovingDynamic %d, Humanoid %d, MovingGrounded %d", getSimulateStage()->getMovingDynamicAssembliesSize(), getHumanoidStage()->getMovingHumanoidAssemblies().size(), getMovingAssemblyStage()->getMovingGroundedAssembliesSize()); std::for_each(getSimulateStage()->getRealTimeAssembliesBegin(), getSimulateStage()->getRealtimeAssembliesEnd(), NotifyMovingRef); std::for_each(getSimulateStage()->getMovingDynamicAssembliesBegin(), getSimulateStage()->getMovingDynamicAssembliesEnd(), NotifyMovingRef); notifyMovingPrimitives( getMovingAssemblyStage()->getMovingGroundedAssemblies() ); notifyMovingPrimitives( getMovingAssemblyStage()->getMovingAnimatedAssemblies() ); notifyMovingPrimitives( getHumanoidStage()->getMovingHumanoidAssemblies() ); } void World::uiStep(bool longStep, double distributedGameTime) { RBXPROFILER_SCOPE("Physics", "uiStep"); RBXASSERT(isAssembled()); // testing - assemble in RBXASSERT(!inStepCode); if (longStep) { RBX::Profiling::Mark mark(*profilingBreak, false); doBreakJoints(); // out of step code here } inStepCode = true; if (longStep) { RBX::Profiling::Mark mark(*profilingAssembly, false); assemble(); } { RBX::Profiling::Mark mark(*profilingFilter, false); getSpatialFilter()->filterStep(); } { RBX::Profiling::Mark mark(*profilingUiStep, false); getMovingAssemblyStage()->jointsStepUi(distributedGameTime); // update joints } inStepCode = false; RBXASSERT(isAssembled()); } // 240 Hz void World::doWorldStep(bool throttling, int uiStepId, int numThreads, boost::uint64_t debugTime) { RBXPROFILER_SCOPE("Physics", "doWorldStep"); inStepCode = true; RBXASSERT(isAssembled()); // Assembly - Tree Stage if( FFlag::PhysicsAnalyzerEnabled && getKernel()->getUsingPGSSolver() ) { getKernel()->pgsSolver.setInconsistentConstraintDetectorEnabled( physicsAnalyzerEnabled ); if( physicsAnalyzerEnabled && getKernel()->pgsSolver.getPhysicsAnalyzerBreakOnIssue() ) { // Stop updating if there are inconsistencies if( getKernel()->pgsSolver.getInconsistentBodies().size() > 0 ) { inStepCode = false; return; } } } getSleepStage()->stepSleepStage(worldStepId, uiStepId, throttling); // update contacts, sleeping, awake getStepJointsStage()->jointsStepWorld(); // update joints getKernel()->step(throttling, numThreads, debugTime); // solve inStepCode = false; { RBXPROFILER_SCOPE("Physics", "updateBroadphase"); RBX::Profiling::Mark mark(*contactManager->profilingBroadphase, false); std::for_each( getSimulateStage()->getRealTimeAssembliesBegin(), getSimulateStage()->getRealtimeAssembliesEnd(), boost::bind(&ContactManager::onAssemblyMovedFromStep, contactManager, _1) ); if (!throttling) { std::for_each( getSimulateStage()->getMovingDynamicAssembliesBegin(), getSimulateStage()->getMovingDynamicAssembliesEnd(), boost::bind(&ContactManager::onAssemblyMovedFromStep, contactManager, _1) ); // old Pre_step is here - updates Extents } } RBXASSERT(isAssembled()); // Assembly - Tree Stage } int World::getUiStepId() { return worldStepId / Constants::worldStepsPerUiStep(); } int World::getLongUiStepId() { return worldStepId / Constants::worldStepsPerLongUiStep(); } float World::getUpdateExpectedStepDelta() { Time::Interval deltaTime; int mdws = getWorldStepId(); if (lastNumWorldSteps != 0) { double deltaWorldSteps = mdws - lastNumWorldSteps; // constant deltaTime = lastFrameTimeStamp - lastSendTimeStamp ; // constant double expectedWorldSteps = (deltaTime.seconds() * Constants::worldStepsPerSec()); //constant worldStepOffset += expectedWorldSteps - deltaWorldSteps; worldStepOffset = std::max((double)-DFInt::WorldStepMax ,std::min(worldStepOffset, (double)DFInt::WorldStepMax)); // now drift toward 0; double adjust = (double)(DFInt::WorldStepsOffsetAdjustRate)/1000.0; if(worldStepOffset > adjust) worldStepOffset -= adjust; else if(worldStepOffset < -adjust) worldStepOffset += adjust; else worldStepOffset = 0.0; FASTLOG1F(FLog::WorldStepsBehind, "DELTA WorldSteps : %d", (float)deltaWorldSteps); FASTLOG1F(FLog::WorldStepsBehind, "Expected WorldSteps : %d", (float)expectedWorldSteps); FASTLOG1F(FLog::WorldStepsBehind, "dt: %f", (float)(deltaTime.seconds())); FASTLOG3F(FLog::WorldStepsBehindG, "Number of WorldSteps behind, wsps, deltaTime: %f %f %f", (float)worldStepOffset, (float)deltaWorldSteps, (float)expectedWorldSteps); } if(mdws != getWorldStepId()) { StandardOut::singleton()->printf(MESSAGE_INFO, "getWorldStepId changed %d %d", mdws, getWorldStepId()); } lastNumWorldSteps = getWorldStepId(); lastSendTimeStamp = lastFrameTimeStamp; return worldStepOffset / Constants::worldStepsPerSec(); } int World::updateStepsRequiredForCyclicExecutive(float desiredInterval) { if (!DataModel::throttleAt30Fps) { worldSteps = 8; return worldSteps; } float worldStepsFloat = desiredInterval * (float)Constants::worldStepsPerSec() + worldStepAccumulated; float worldStepsFloor = ::floorf( worldStepsFloat ); float maxWorldStepsAllowedPerDMStep; if (DFFlag::CyclicExecutiveThrottlingCancelWorldStepAccum) { // When under heavy throttling where each WorldStep takes a long time, // we can get into a state where ROBLOX wants to run 15 physics WorldSteps in 1 frame, // and even with max throttling we will still have to run at least 2, which could potentially // hinder Rendering framerate maxWorldStepsAllowedPerDMStep = (eThrottle.getEnvironmentSpeed() > 0.4f) ? (float)DFInt::MaxMissedWorldStepsRemembered : 4.0f; } else { maxWorldStepsAllowedPerDMStep = (float)DFInt::MaxMissedWorldStepsRemembered; } worldStepsFloor = std::min( worldStepsFloor, maxWorldStepsAllowedPerDMStep); // For perf reasons clamp max steps to 1/20th of a second at 240hz worldStepAccumulated = worldStepsFloat - worldStepsFloor; worldStepAccumulated = std::min( worldStepAccumulated, 1.99999f ); // Drop remaining steps in response to spikes. Even allowing this much accumulation // results allows for intervals where as high as 62.99s simulation has occurred. if( worldStepsFloor <= 0.0f) { return 0.0f; } worldSteps = (int)worldStepsFloor; FASTLOG1(FLog::CyclicExecutiveWorldSteps, "World Steps this Iteration: %d", worldSteps); return worldSteps; } float World::step(bool longStep, double distributedGameTime, float desiredInterval, int numThreads) { RBXASSERT(!inStepCode); RBXASSERT(isAssembled()); // testing - assemble in // Update bullet Extern Globals with Dynamic FastInts gContactBreakingThreshold = DFInt::BulletContactBreakThresholdPercent/100.0 * 0.02; gContactThresholdOrthogonalFactor = DFInt::BulletContactBreakOrthogonalThresholdPercent/100.0; gContactThresholdOrthogonalActivateFactor = DFInt::BulletContactBreakOrthogonalThresholdActivatePercent/100.0; lastFrameTimeStamp = TaskScheduler::singleton().lastCyclcTimestamp; // // On Mac there is nothing equivalent of _CrtCheckMemory to validate the state of the heap // Instead use this on XCode http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html #ifdef _WIN32 RBXASSERT_IF_VALIDATING(_CrtCheckMemory() != 0); #endif // RBXASSERT(desiredInterval > 0.01); // less than 100 FPS // RBXASSERT(desiredInterval < 0.10); // more than 10 FPS // RBXASSERT((worldStepId % Constants::worldStepsPerUiStep()) == 0); RBX::Profiling::Mark mark(*profilingWorldStep, true, true); if( !RBX::TaskScheduler::singleton().isCyclicExecutive() ) { worldSteps = std::max(1, Math::iFloor(desiredInterval * Constants::worldStepsPerSec())); } uiStep(longStep, distributedGameTime); if (longStep) { for (std::set::iterator i = getMovingAssemblyStage()->getMovingGroundedAssembliesBegin(); i != getMovingAssemblyStage()->getMovingGroundedAssembliesEnd(); i++) { RBX::Profiling::Mark mark(*contactManager->profilingBroadphase, false); RBXASSERT(!((*i)->SimulateStageHook::is_linked())); RBXASSERT((*i)->getCurrentStage()->getStageType() != IStage::HUMANOID_STAGE); contactManager->onAssemblyMovedFromStep(**i); } } //Time startTime = Time::now(); // longStepId is only used for touch signal now const int longStepId = getLongUiStepId(); static const boost::posix_time::ptime epoch = boost::posix_time::min_date_time; boost::uint64_t debugTime = ( boost::posix_time::microsec_clock::universal_time() - epoch ).total_milliseconds(); for (int j = 0; j < worldSteps; j++) { bool throttling = eThrottle.computeThrottle(worldStepId); doWorldStep(throttling, longStepId, numThreads, debugTime); worldStepId++; } if (longStep) { // notify moving for renderer notifyMovingAssemblies(); } if(passCount > (double)DFInt::smoothnessReportThreshold) { sendAnalytics(); } #ifdef _WIN32 RBXASSERT_IF_VALIDATING(_CrtCheckMemory() != 0); #endif return worldSteps * Constants::worldDt(); } void World::reportTouchInfo(const TouchInfo& info) { touchReporting.append(info); } void World::reportTouchInfo(Primitive* p0, Primitive* p1, World::TouchInfo::Type T) { const TouchInfo info = { NULL, NULL, shared_from(PartInstance::fromPrimitive(p0)), shared_from(PartInstance::fromPrimitive(p1)), T }; touchReporting.append(info); } void World::onPrimitiveCollided(Primitive* p0, Primitive* p1) { primitiveCollideSignal(std::make_pair(p0, p1)); } void AppendFallen(Primitive* p, G3D::Array* fallen) { fallen->append(p); }; void World::computeFallen(G3D::Array& fallen) const { RBXASSERT(fallen.size() == 0); const SleepStage* sleepStage = getSleepStage(); RBXASSERT(sleepStage); const SleepStage::AssemblySet& awake = sleepStage->getAwakeAssemblies(); for (SleepStage::AssemblySet::const_iterator cIt = awake.begin(); cIt != awake.end(); ++cIt) { Assembly* assembly = *cIt; Vector3 pos = assembly->getAssemblyPrimitive()->getCoordinateFrame().translation; if ((pos.y < fallenPartDestroyHeight) || Math::isNanInfVector3(pos)) { assembly->visitPrimitives(boost::bind(&AppendFallen, _1, &fallen)); } } } void World::insertJoint(Joint* j) { assertNotInStep(); // Re-entrancy problem - please capture and show complete stack to David B. RBXASSERT(inJointNotification == NULL); Primitive* p0 = j->getPrimitive(0); Primitive* p1 = j->getPrimitive(1); // don't destroy additional joints (beyond the first one) if terrain is one of the parts in the joint pair if (p0 && p1 && (p0 != p1)) { if( p0->getGeometryType() != Geometry::GEOMETRY_MEGACLUSTER && p1->getGeometryType() != Geometry::GEOMETRY_MEGACLUSTER ) { if (Joint* alreadyHere = Primitive::getJoint(p0, p1)) { if (!Joint::isKernelJoint(alreadyHere) && !Joint::isKernelJoint(j) && !Joint::isSpringJoint(alreadyHere) && !Joint::isSpringJoint(j)) // allow multiple kernel joints with other joints { destroyJoint(alreadyHere); // will fire a notification } } } } Primitive* unGroundedPrimIfGroundedExists = NULL; std::vector combiningRoots; bool isKernelJoint = Joint::isKernelJoint(j); if (!isKernelJoint) { gatherMechDataPreJoin(j, unGroundedPrimIfGroundedExists, combiningRoots); } cleanStage->onEdgeAdded(j); if (!isKernelJoint) { postInsertJointSignal(j, unGroundedPrimIfGroundedExists, combiningRoots); } numJoints++; if (j->isBreakable()) { int ok = breakableJoints.insert(j).second; RBXASSERT(ok); } if (Joint::isKinematicJoint(j)) j->notifyMoved(); } // TODO: performance issue here with lots of glue joints // void World::removeFromBreakable(Joint* j) { if (j->isBreakable()) { int num = breakableJoints.erase(j); RBXASSERT(num == 1); } else { RBXASSERT_SLOW(breakableJoints.find(j) == breakableJoints.end()); } } void World::removeJoint(Joint* j) { assertNotInStep(); removeFromBreakable(j); removeAnimatedJointFromMovingAssemblyStage(j); std::vector prim0Roots; std::vector prim1Roots; bool isKernelJoint = Joint::isKernelJoint(j); if (!isKernelJoint) { gatherMechDataPreSplit(j, prim0Roots, prim1Roots); } cleanStage->onEdgeRemoving(j); if (!isKernelJoint) { postRemoveJointSignal(j, prim0Roots, prim1Roots); } numJoints--; } void World::gatherMechDataPreJoin(Joint *j, Primitive*& unGroundedPrim, std::vector& combiningRoots) { Primitive* prim0 = j->getPrimitive(0); Primitive* prim1 = j->getPrimitive(1); if (prim0 && prim1) { Primitive* rootPrim0 = prim0->getRootMovingPrimitive(); Primitive* rootPrim1 = prim1->getRootMovingPrimitive(); if (rootPrim0 && rootPrim1) { if (rootPrim0 == rootPrim1) { combiningRoots.push_back(rootPrim0); } else { combiningRoots.push_back(rootPrim0); combiningRoots.push_back(rootPrim1); } } else if (Primitive* unGroundedRootPrim = rootPrim0 ? rootPrim0 : rootPrim1) { unGroundedPrim = rootPrim0 ? prim0 : prim1; // If the rootMovingPrimitive is also the mechanismRoot then we know it's safe to clear it's data because ownership // transfer is very simple in this case. // If it's not a mechanismRoot, then it has to either be descendant of the Joint's primitive OR be the primitive itself // Otherwise, the rootMovingPrimitive will still have the same ownership settings after the Join event. if ( unGroundedRootPrim == unGroundedPrim->getMechRoot() || unGroundedPrim->isAncestorOf(unGroundedRootPrim) || unGroundedRootPrim == unGroundedPrim) { combiningRoots.push_back(unGroundedRootPrim); } } } } void World::gatherMechDataPreSplit(Joint* j, std::vector& prim0ChildRoots, std::vector& prim1ChildRoots) { // We have to know if either of the Primitives is a parent of the other Primitive. // This is so that we can figure out which Ancestor check requires exclusivity when // populating the primOwnershipData containers. bool p0ParentOfp1 = false; bool p1ParentOfp0 = false; Primitive* prim0 = j->getPrimitive(0); Primitive* prim1 = j->getPrimitive(1); Primitive* rootMovingPrim0 = NULL; Primitive* rootMovingPrim1 = NULL; if (prim0 && prim1) { p0ParentOfp1 = prim0->isAncestorOf(prim1); p1ParentOfp0 = prim1->isAncestorOf(prim0); rootMovingPrim0 = prim0->getRootMovingPrimitive(); rootMovingPrim1 = prim1->getRootMovingPrimitive(); if (rootMovingPrim0 && rootMovingPrim1) { // both rootMovingPrim's existing implies SIMPLE logic for ownership transfer. // Lets only use Container 0, since container 1 will have redundant date prim0ChildRoots.push_back(rootMovingPrim0); return; } } for (int i = 0; i < 2; i++ ) { // Select the correct containers std::vector& primChildRoots = (i == 0) ? prim0ChildRoots : prim1ChildRoots; bool parentOfOtherPrim = (i == 0) ? p0ParentOfp1 : p1ParentOfp0; if (Primitive* prim = (i == 0) ? prim0 : prim1) { if (Primitive* rootPrim = prim->getMechRoot()) { Assembly* rootAssembly = rootPrim->getAssembly(); for (int k = 0; k < rootAssembly->numChildren(); k++) { Assembly* childOfRoot = rootAssembly->getTypedChild(k); if (Mechanism::isMovingAssemblyRoot(childOfRoot)) { Primitive* rootOfAssembly = childOfRoot->getAssemblyPrimitive(); Primitive* otherPrim = (i == 1) ? prim0 : prim1; if (prim->isAncestorOf(rootOfAssembly) && !(parentOfOtherPrim && otherPrim->isAncestorOf(rootOfAssembly))) { primChildRoots.push_back(rootOfAssembly); } } } } } } } void World::notifyMoved(Primitive* p) { if (p == NULL) return; p->getOwner()->notifyMoved(); for (int i = 0; i < p->numChildren(); ++i) { Primitive* child = p->getTypedChild(i); if (!Assembly::isAssemblyRootPrimitive(child)) { notifyMoved(child); } } } void World::jointCoordsChanged(Joint* j) { Primitive* parent = rbx_static_cast(j->getParentSpanningNode()); Primitive* child = rbx_static_cast(j->getChildSpanningNode()); if (parent && child && (parent != child)) { if (Joint::isRigidJoint(j)) { RigidJoint* r = rbx_static_cast(j); child->getBody()->setMeInParent(r->getChildInParent(parent, child)); notifyMoved(parent); sendClumpChangedMessage(child); } else { if (Joint::isMotorJoint(j)) { RBXASSERT(parent); child->getBody()->setMeInParent(j->resetLink()); notifyMoved(parent); } } } } void World::destroyJoint(Joint* j) { RBXASSERT(j); inJointNotification = j; autoDestroySignal(j); inJointNotification = NULL; } void World::insertContact(Contact* c) { // RBXASSERT(!inStepCode); cleanStage->onEdgeAdded(c); numContacts++; RBXASSERT( getKernel() != 0 ); if (getKernel() && getKernel()->getUsingPGSSolver()) { getKernel()->pgsSolver.addContactManifold( c->getPrimitive(0)->getBody()->getUID(), c->getPrimitive(1)->getBody()->getUID() ); } } // Note - should only be called by the contact manager void World::destroyContact(Contact* c) { if (getKernel() && getKernel()->getUsingPGSSolver()) { getKernel()->pgsSolver.removeContactManifold( c->getPrimitive(0)->getBody()->getUID(), c->getPrimitive(1)->getBody()->getUID() ); } RBXASSERT(!inStepCode); cleanStage->onEdgeRemoving(c); delete c; numContacts--; } static bool canSkipJoinAllForPrimitive(Primitive* p) { if (p->getGeometry() && p->getGeometry()->isTerrain()) return true; for (int i = 0; i < NORM_UNDEFINED; ++i) if (!IsNoSurface(p->getSurfaceType(static_cast(i)))) return false; return true; } void World::joinAll() { // RBXASSERT(numJoints == 0); for (int i = 0; i < primitives.size(); ++i) { // CSG PHYSICS LOADING HACK PartInstance* part = PartInstance::fromPrimitive(primitives[i]); if(PartOperation* partOp = RBX::Instance::fastDynamicCast(part)) { partOp->trySetPhysicsData(); } // CSG PHYSICS LOADING HACK if (!canSkipJoinAllForPrimitive(primitives[i])) { createAutoJoints(primitives[i]); } } assemble(); } void World::insertPrimitive(Primitive* p) { RBXASSERT(!inStepCode); RBXASSERT(!p->getAssembly()); RBXASSERT(!p->inPipeline()); RBXASSERT(!p->getFirstEdge()); RBXASSERT(!p->getNumEdges()); RBXASSERT(!p->getWorld()); // id's will start at 1, reserving 0 for an invalid value UIDGenerator++; p->getBody()->setUID( UIDGenerator ); primitiveIndexation[ UIDGenerator ] = p; primitives.fastAppend(p); p->setWorld(this); p->updateMassValues(getUsingNewPhysicalProperties()); cleanStage->onPrimitiveAdded(p); contactManager->onPrimitiveAdded(p); // contacts added here RBXASSERT(!inStepCode); } void World::removePrimitive(Primitive* p, bool isStreamRemove) { RBXASSERT(!inStepCode); RBXASSERT(p->inPipeline()); RBXASSERT(p->getWorld()); if( getKernel() && getKernel()->getUsingPGSSolver() ) { getKernel()->pgsSolver.clearBodyCache(p->getBody()->getUID()); } destroyAutoJoints(p, NULL, !isStreamRemove, !isStreamRemove); contactManager->onPrimitiveRemoved(p); // should destroy all contacts here cleanStage->onPrimitiveRemoving(p); primitives.fastRemove(p); primitiveIndexation.erase( primitiveIndexation.find(p->getBody()->getUID()) ); p->setWorld(NULL); p->getBody()->setUID( 0 ); RBXASSERT(p->getFirstEdge() == NULL); RBXASSERT(p->getNumEdges() == 0); RBXASSERT(!inStepCode); RBXASSERT(!p->getAssembly()); RBXASSERT(!p->inPipeline()); RBXASSERT(!p->getWorld()); } Primitive* World::getPrimitiveFromBodyUID( boost::uint64_t uid ) const { const auto it = primitiveIndexation.find( uid ); if( it == primitiveIndexation.end() ) { return NULL; } return it->second; } bool doNotIgnore(Primitive* other, std::set* ignoreGroup, std::set* joinGroup) { if (ignoreGroup) return ignoreGroup->find(other) == ignoreGroup->end(); if (joinGroup) return joinGroup->find(other) != joinGroup->end(); return true; } void World::destroyAutoJoints(Primitive* p, std::set* ignoreGroup, bool includeExplicit, bool includeAuto) { RBXASSERT(!inStepCode); // some scripts might be listening to signals as a result of a joint remove, turning this function recursive // we keep tempJoint not as a local variable to avoid having to realloc memory, this list is used like a global stack, // each time we enter the function we add more joints to the stack and make sure they are processed and cleared before exiting. int startIndex = tempJoints.size(); for (int i = 0; i < p->getNumJoints(); ++i) { Joint* destroyJ = p->getJoint(i); RBXASSERT(destroyJ); if (destroyJ && ((includeAuto && Joint::isAutoJoint(destroyJ)) || (includeExplicit && Joint::isManualJoint(destroyJ)))) { IJointOwner* jointOwner = destroyJ->getJointOwner(); RBXASSERT(jointOwner); if (jointOwner) { tempJoints.push_back(shared_from(static_cast(jointOwner))); } } } for (int i = startIndex; i < tempJoints.size(); ++i) { Joint* destroyJ = tempJoints[i]->getJoint(); RBXASSERT(destroyJ); RBXASSERT(destroyJ->links(p)); Primitive* other = destroyJ->otherPrimitive(p); if (doNotIgnore(other, ignoreGroup, NULL)) { destroyJoint(destroyJ); } } if((tempJoints.size() - startIndex) != 0) tempJoints.remove(startIndex, tempJoints.size() - startIndex); RBXASSERT(startIndex == tempJoints.size()); } void World::destroyTerrainWeldJointsNoTouch(Primitive* megaClusterPrim, Primitive* touchingPrim) { std::vector destroyTheseJoints; int touching = -1; for (int j = 0; j < touchingPrim->getNumJoints(); j++) { Joint* destroyJ = touchingPrim->getJoint(j); if (touchingPrim->getJointOther(j) == megaClusterPrim && Joint::isManualJoint(destroyJ)) { if (touching < 0) touching = ManualWeldJoint::isTouchingTerrain(megaClusterPrim, touchingPrim); if (!touching) destroyTheseJoints.push_back(destroyJ); } } for (unsigned int i = 0; i < destroyTheseJoints.size(); i++) destroyJoint(destroyTheseJoints[i]); } void World::destroyTerrainWeldJointsWithEmptyCells(Primitive* megaClusterPrim, const SpatialRegion::Id& region, Primitive* touchingPrim) { Voxel::Grid* grid = static_cast(megaClusterPrim->getOwner())->getVoxelGrid(); std::vector destroyTheseJoints; for (int j = 0; j < touchingPrim->getNumJoints(); j++) { Joint* destroyJ = touchingPrim->getJoint(j); if (touchingPrim->getJointOther(j) == megaClusterPrim && Joint::isManualJoint(destroyJ)) { Vector3int16 cellPos = static_cast(destroyJ)->getCell(); if (SpatialRegion::regionContainingVoxel(cellPos) == region && grid->getCell(cellPos).solid.getBlock() == Voxel::CELL_BLOCK_Empty) destroyTheseJoints.push_back(destroyJ); } } for( unsigned int i = 0; i < destroyTheseJoints.size(); i++ ) destroyJoint(destroyTheseJoints[i]); } // note - leaves contacts intact, but in a non-active state void World::destroyAutoJoints(Primitive* p, bool includeExplicit) { destroyAutoJoints(p, NULL, includeExplicit); } void World::destroyAutoJointsToWorld(const G3D::Array& primitives) // ignores joints between them { RBXASSERT(!inStepCode); std::set ignore; for (int i = 0; i < primitives.size(); ++i) { ignore.insert(primitives[i]); } for (int i = 0; i < primitives.size(); ++i) { destroyAutoJoints(primitives[i], &ignore); } } void World::createAutoJoints(Primitive* p, std::set* ignoreGroup, std::set* joinGroup) { RBXASSERT(!inStepCode); numLinkCalls++; tempPrimitives.fastClear(); contactManager->getPrimitivesTouchingExtents(p->getFastFuzzyExtents(), p, 0, tempPrimitives); // For each other primitive for (int i = 0; i < tempPrimitives.size(); ++i) { Primitive* other = tempPrimitives[i]; if ( doNotIgnore(other, ignoreGroup, joinGroup) && (!Primitive::getJoint(p, other)) ) { if (Joint* joint = JointBuilder::canJoin(p, other)) { insertJoint(joint); inJointNotification = joint; autoJoinSignal(joint); inJointNotification = NULL; } } } } void World::createAutoJoints(Primitive* p) { createAutoJoints(p, NULL, NULL); } void World::createAutoJointsToWorld(const G3D::Array& primitives) // ignores joints between them { RBXASSERT(!inStepCode); std::set ignore; for (int i = 0; i < primitives.size(); ++i) { ignore.insert(primitives[i]); } for (int i = 0; i < primitives.size(); ++i) { createAutoJoints(primitives[i], &ignore, NULL); } } void World::createAutoJointsToPrimitives(const G3D::Array& primitives) // only join each other in this group { RBXASSERT(!inStepCode); std::set group; for (int i = 0; i < primitives.size(); ++i) { group.insert(primitives[i]); } for (int i = 0; i < primitives.size(); ++i) { createAutoJoints(primitives[i], NULL, &group); } } void World::doBreakJoints() { std::set::iterator it = breakableJoints.begin(); while (it != breakableJoints.end()) { Joint* j = *it; it++; if (j->inKernel() && j->isBroken()) { destroyJoint(j); // removing joint while iterating } } } void World::addAnimatedJointToMovingAssemblyStage(Joint* j) { getMovingAssemblyStage()->addAnimatedJoint(j); } void World::removeAnimatedJointFromMovingAssemblyStage(Joint* j) { getMovingAssemblyStage()->removeAnimatedJoint(j); } bool World::getUsingPGSSolver() { RBXASSERT(usingPGSSolver == getKernel()->getUsingPGSSolver()); return usingPGSSolver; } void World::setUsingPGSSolver(bool usePGS) { usingPGSSolver = usePGS; getKernel()->setUsingPGSSolver(usePGS); } void World::setUserId( int id ) { getKernel()->pgsSolver.setUserId( id ); } void World::setPhysicalPropertiesMode(PhysicalPropertiesMode mode) { physicalMaterialsMode = mode; } bool World::getUsingNewPhysicalProperties() const { if (SFFlag::getNewPhysicalPropertiesForcedOnAll()) return true; PhysicalPropertiesMode legacyMode = SFFlag::getMaterialPropertiesNewIsDefault() ? PhysicalPropertiesMode_Default : PhysicalPropertiesMode_NewPartProperties; return (DFFlag::MaterialPropertiesEnabled) ? (getPhysicalPropertiesMode() >= legacyMode) : false; } void World::sendAnalytics(void) { if(passCount > 0.0) { double stopsperk = errorCount * (double)DFInt::smoothnessReportThreshold / passCount; RBX::Analytics::EphemeralCounter::reportStats("SmoothnessHumanoidStopsperK", (int)stopsperk, false); passCount = 0.0; errorCount = 0.0; } if(frameinfosCount > 0.0) { RBX::Analytics::EphemeralCounter::reportStats("SmoothnessHumanoidFrameinfosSize", (int)( .5 + frameinfosSize/frameinfosCount), false); RBX::Analytics::EphemeralCounter::reportStats("SmoothnessHumanoidFrameinfosTarget", (int)( .5 + frameinfosTarget/frameinfosCount), false); RBX::Analytics::EphemeralCounter::reportStats("SmoothnessHumanoidTargetDelayTenths", (int)( .5 + targetDelayTenths/frameinfosCount), false); RBX::Analytics::EphemeralCounter::reportStats("SmoothnessHumanoidFrameinfosSizeTenths", (int)( .5 + infosSizeTenths/frameinfosCount), false); RBX::Analytics::EphemeralCounter::reportStats("SmoothnessHumanoidMaxDelta", (int)( .5 + maxDelta/frameinfosCount), false); frameinfosSize = 0.0; frameinfosTarget = 0.0; targetDelayTenths = 0.0; infosSizeTenths = 0.0; maxDelta = 0.0; frameinfosCount = 0.0; } } void World::addFrameinfosStat(double fis, double fit, double tdt, double ist, double md, double fic) { frameinfosSize += fis; frameinfosTarget += fit; targetDelayTenths += tdt; infosSizeTenths += ist; maxDelta += md; frameinfosCount += fic; } } // namespace