@@ -1756,3 +1756,121 @@ class Wedge : public Sample
17561756};
17571757
17581758static int sampleWedge = RegisterSample( " Continuous" , " Wedge" , Wedge::Create );
1759+
1760+ // This shows how adjusting the CCD safety factor can engage continuous collision at
1761+ // lower speeds to avoid the slight overlap that happens with discrete collision detection.
1762+ // The best way to run this sample is:
1763+ // 1. press pause (P)
1764+ // 2. restart (R) or press the Drop button
1765+ // 3. then single step (O)
1766+ // Then look at the metrics and overlap. The color of the shape gets darker when it is using
1767+ // continuous collision detection.
1768+ class SafetyFactor : public Sample
1769+ {
1770+ public:
1771+ explicit SafetyFactor ( SampleContext* context )
1772+ : Sample( context )
1773+ {
1774+ if ( m_context->restart == false )
1775+ {
1776+ m_context->camera .center = { 0 .0f , 3 .0f };
1777+ m_context->camera .zoom = 6 .0f ;
1778+ }
1779+
1780+ {
1781+ b2BodyDef bodyDef = b2DefaultBodyDef ();
1782+ bodyDef.position = { 0 .0f , -1 .0f };
1783+ b2BodyId groundId = b2CreateBody ( m_worldId, &bodyDef );
1784+
1785+ b2ShapeDef shapeDef = b2DefaultShapeDef ();
1786+ b2Polygon box = b2MakeBox ( 20 .0f , 1 .0f );
1787+ b2CreatePolygonShape ( groundId, &shapeDef, &box );
1788+ }
1789+
1790+ m_bodyId = b2_nullBodyId;
1791+ m_extent = 0 .5f ;
1792+ m_height = 0 .3f ;
1793+ m_safetyFactor = 0 .1f ;
1794+ m_overlap = 0 .0f ;
1795+
1796+ Launch ();
1797+ }
1798+
1799+ void Launch ()
1800+ {
1801+ if ( B2_IS_NON_NULL ( m_bodyId ) )
1802+ {
1803+ b2DestroyBody ( m_bodyId );
1804+ }
1805+
1806+ b2BodyDef bodyDef = b2DefaultBodyDef ();
1807+ bodyDef.type = b2_dynamicBody;
1808+ bodyDef.position = { 0 .0f , m_height + m_extent };
1809+ bodyDef.safetyFactor = m_safetyFactor;
1810+ m_bodyId = b2CreateBody ( m_worldId, &bodyDef );
1811+
1812+ b2ShapeDef shapeDef = b2DefaultShapeDef ();
1813+ b2Polygon box = b2MakeSquare ( m_extent );
1814+ b2CreatePolygonShape ( m_bodyId, &shapeDef, &box );
1815+
1816+ m_overlap = 0 .0f ;
1817+ }
1818+
1819+ void Step () override
1820+ {
1821+ Sample::Step ();
1822+
1823+ if ( m_didStep )
1824+ {
1825+ // Contacts are computed at the beginning of the step, so the manifold holds the initial separation.
1826+ b2ContactData data;
1827+ int count = b2Body_GetContactData ( m_bodyId, &data, 1 );
1828+ if ( count == 1 )
1829+ {
1830+ for ( int j = 0 ; j < data.manifold .pointCount ; ++j )
1831+ {
1832+ m_overlap = b2MaxFloat ( m_overlap, -data.manifold .points [j].separation );
1833+ }
1834+ }
1835+ }
1836+
1837+ DrawLine ( m_draw, b2ToPos ( b2Vec2{ -3 .0f , -m_overlap } ), b2ToPos ( b2Vec2{ 3 .0f , -m_overlap } ), b2_colorRed );
1838+
1839+ float timeStep = m_context->hertz > 0 .0f ? 1 .0f / m_context->hertz : 0 .0f ;
1840+ float motion = timeStep * b2Length ( b2Body_GetLinearVelocity ( m_bodyId ) );
1841+
1842+ DrawScreenTextLine ( " actual movement = %.3f m" , motion );
1843+ DrawScreenTextLine ( " fast movement = %.3f m" , m_safetyFactor * m_extent );
1844+ DrawScreenTextLine ( " overlap = %.4f m" , m_overlap );
1845+ }
1846+
1847+ bool DrawControls () override
1848+ {
1849+ bool changed = false ;
1850+
1851+ ImGui::PushItemWidth ( 10 .0f * ImGui::GetFontSize () );
1852+ changed |= ImGui::SliderFloat ( " Height" , &m_height, 0 .0f , 2 .0f , " %.2f" );
1853+ changed |= ImGui::SliderFloat ( " Safety" , &m_safetyFactor, 0 .0f , 1 .0f , " %.2f" );
1854+ ImGui::PopItemWidth ();
1855+
1856+ if ( ImGui::Button ( " Drop" ) || changed )
1857+ {
1858+ Launch ();
1859+ }
1860+
1861+ return true ;
1862+ }
1863+
1864+ static Sample* Create ( SampleContext* context )
1865+ {
1866+ return new SafetyFactor ( context );
1867+ }
1868+
1869+ b2BodyId m_bodyId;
1870+ float m_extent;
1871+ float m_height;
1872+ float m_safetyFactor;
1873+ float m_overlap;
1874+ };
1875+
1876+ static int sampleSafetyFactor = RegisterSample( " Continuous" , " Safety Factor" , SafetyFactor::Create );
0 commit comments