// SPDX-FileCopyrightText: 2023 Erin Catto
// SPDX-License-Identifier: MIT

#include "draw.h"
#include "sample.h"
#include "settings.h"

#include "box2d/box2d.h"
#include "box2d/collision.h"
#include "box2d/math_functions.h"

#include <GLFW/glfw3.h>
#include <imgui.h>

constexpr int SIMPLEX_CAPACITY = 20;

class ShapeDistance : public Sample
{
public:
	enum ShapeType
	{
		e_point,
		e_segment,
		e_triangle,
		e_box
	};

	explicit ShapeDistance( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { 0.0f, 0.0f };
			g_camera.m_zoom = 3.0f;
		}

		m_point = b2Vec2_zero;
		m_segment = { { -0.5f, 0.0f }, { 0.5f, 0.0f } };

		{
			b2Vec2 points[3] = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, { 0.0f, 1.0f } };
			b2Hull hull = b2ComputeHull( points, 3 );
			m_triangle = b2MakePolygon( &hull, 0.0f );
		}

		m_box = b2MakeBox( 0.5f, 0.5f );

		m_transform = { { 1.5f, -1.5f }, b2Rot_identity };
		m_angle = 0.0f;

		m_cache = b2_emptyDistanceCache;
		m_simplexCount = 0;
		m_startPoint = { 0.0f, 0.0f };
		m_basePosition = { 0.0f, 0.0f };
		m_baseAngle = 0.0f;

		m_dragging = false;
		m_rotating = false;
		m_showIndices = false;
		m_useCache = false;
		m_drawSimplex = false;

		m_typeA = e_box;
		m_typeB = e_box;
		m_radiusA = 0.0f;
		m_radiusB = 0.0f;

		m_proxyA = MakeProxy( m_typeA, m_radiusA );
		m_proxyB = MakeProxy( m_typeB, m_radiusB );
	}

	b2DistanceProxy MakeProxy( ShapeType type, float radius )
	{
		b2DistanceProxy proxy = {};
		proxy.radius = radius;

		switch ( type )
		{
			case e_point:
				proxy.points[0] = b2Vec2_zero;
				proxy.count = 1;
				break;

			case e_segment:
				proxy.points[0] = m_segment.point1;
				proxy.points[1] = m_segment.point2;
				proxy.count = 2;
				break;

			case e_triangle:
				proxy.points[0] = m_triangle.vertices[0];
				proxy.points[1] = m_triangle.vertices[1];
				proxy.points[2] = m_triangle.vertices[2];
				proxy.count = 3;
				break;

			case e_box:
				proxy.points[0] = m_box.vertices[0];
				proxy.points[1] = m_box.vertices[1];
				proxy.points[2] = m_box.vertices[2];
				proxy.points[3] = m_box.vertices[3];
				proxy.count = 4;
				break;

			default:
				assert( false );
		}

		return proxy;
	}

	void DrawShape( ShapeType type, b2Transform transform, float radius, b2HexColor color )
	{
		switch ( type )
		{
			case e_point:
			{
				b2Vec2 p = b2TransformPoint( transform, m_point );
				if ( radius > 0.0f )
				{
					g_draw.DrawCircle( p, radius, color );
				}
				else
				{
					g_draw.DrawPoint( p, 5.0f, color );
				}
			}
			break;

			case e_segment:
			{
				b2Vec2 p1 = b2TransformPoint( transform, m_segment.point1 );
				b2Vec2 p2 = b2TransformPoint( transform, m_segment.point2 );

				if ( radius > 0.0f )
				{
					g_draw.DrawCapsule( p1, p2, radius, color );
				}
				else
				{
					g_draw.DrawSegment( p1, p2, color );
				}
			}
			break;

			case e_triangle:
				g_draw.DrawSolidPolygon( transform, m_triangle.vertices, 3, radius, color );
				break;

			case e_box:
				g_draw.DrawSolidPolygon( transform, m_box.vertices, 4, radius, color );
				break;

			default:
				assert( false );
		}
	}

	void UpdateUI() override
	{
		float height = 310.0f;
		ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
		ImGui::SetNextWindowSize( ImVec2( 240.0f, height ) );

		ImGui::Begin( "Shape Distance", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize );

		const char* shapeTypes[] = { "point", "segment", "triangle", "box" };
		int shapeType = int( m_typeA );
		if ( ImGui::Combo( "shape A", &shapeType, shapeTypes, IM_ARRAYSIZE( shapeTypes ) ) )
		{
			m_typeA = ShapeType( shapeType );
			m_proxyA = MakeProxy( m_typeA, m_radiusA );
		}

		if ( ImGui::SliderFloat( "radius A", &m_radiusA, 0.0f, 0.5f, "%.2f" ) )
		{
			m_proxyA.radius = m_radiusA;
		}

		shapeType = int( m_typeB );
		if ( ImGui::Combo( "shape B", &shapeType, shapeTypes, IM_ARRAYSIZE( shapeTypes ) ) )
		{
			m_typeB = ShapeType( shapeType );
			m_proxyB = MakeProxy( m_typeB, m_radiusB );
		}

		if ( ImGui::SliderFloat( "radius B", &m_radiusB, 0.0f, 0.5f, "%.2f" ) )
		{
			m_proxyB.radius = m_radiusB;
		}

		ImGui::Separator();

		ImGui::SliderFloat( "x offset", &m_transform.p.x, -2.0f, 2.0f, "%.2f" );
		ImGui::SliderFloat( "y offset", &m_transform.p.y, -2.0f, 2.0f, "%.2f" );

		if ( ImGui::SliderFloat( "angle", &m_angle, -b2_pi, b2_pi, "%.2f" ) )
		{
			m_transform.q = b2MakeRot( m_angle );
		}

		ImGui::Separator();

		ImGui::Checkbox( "show indices", &m_showIndices );
		ImGui::Checkbox( "use cache", &m_useCache );

		ImGui::Separator();

		if ( ImGui::Checkbox( "draw simplex", &m_drawSimplex ) )
		{
			m_simplexIndex = 0;
		}

		if ( m_drawSimplex )
		{
			ImGui::SliderInt( "index", &m_simplexIndex, 0, m_simplexCount - 1 );
			m_simplexIndex = b2ClampInt( m_simplexIndex, 0, m_simplexCount - 1 );
		}

		ImGui::End();
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			if ( mods == 0 && m_rotating == false )
			{
				m_dragging = true;
				m_startPoint = p;
				m_basePosition = m_transform.p;
			}
			else if ( mods == GLFW_MOD_SHIFT && m_dragging == false )
			{
				m_rotating = true;
				m_startPoint = p;
				m_baseAngle = m_angle;
			}
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_dragging = false;
			m_rotating = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		if ( m_dragging )
		{
			m_transform.p.x = m_basePosition.x + 0.5f * ( p.x - m_startPoint.x );
			m_transform.p.y = m_basePosition.y + 0.5f * ( p.y - m_startPoint.y );
		}
		else if ( m_rotating )
		{
			float dx = p.x - m_startPoint.x;
			m_angle = b2ClampFloat( m_baseAngle + 1.0f * dx, -b2_pi, b2_pi );
			m_transform.q = b2MakeRot( m_angle );
		}
	}

	static b2Vec2 Weight2( float a1, b2Vec2 w1, float a2, b2Vec2 w2 )
	{
		return { a1 * w1.x + a2 * w2.x, a1 * w1.y + a2 * w2.y };
	}

	static b2Vec2 Weight3( float a1, b2Vec2 w1, float a2, b2Vec2 w2, float a3, b2Vec2 w3 )
	{
		return { a1 * w1.x + a2 * w2.x + a3 * w3.x, a1 * w1.y + a2 * w2.y + a3 * w3.y };
	}

	void ComputeSimplexWitnessPoints( b2Vec2* a, b2Vec2* b, const b2Simplex* s )
	{
		switch ( s->count )
		{
			case 0:
				assert( false );
				break;

			case 1:
				*a = s->v1.wA;
				*b = s->v1.wB;
				break;

			case 2:
				*a = Weight2( s->v1.a, s->v1.wA, s->v2.a, s->v2.wA );
				*b = Weight2( s->v1.a, s->v1.wB, s->v2.a, s->v2.wB );
				break;

			case 3:
				*a = Weight3( s->v1.a, s->v1.wA, s->v2.a, s->v2.wA, s->v3.a, s->v3.wA );
				*b = *a;
				break;

			default:
				assert( false );
				break;
		}
	}

	void Step( Settings& ) override
	{
		b2DistanceInput input;
		input.proxyA = m_proxyA;
		input.proxyB = m_proxyB;
		input.transformA = b2Transform_identity;
		input.transformB = m_transform;
		input.useRadii = m_radiusA > 0.0f || m_radiusB > 0.0f;

		if ( m_useCache == false )
		{
			m_cache.count = 0;
		}

		b2DistanceOutput output = b2ShapeDistance( &m_cache, &input, m_simplexes, SIMPLEX_CAPACITY );

		m_simplexCount = output.simplexCount;

		DrawShape( m_typeA, b2Transform_identity, m_radiusA, b2_colorAqua );
		DrawShape( m_typeB, m_transform, m_radiusB, b2_colorBisque );

		if ( m_drawSimplex )
		{
			b2Simplex* simplex = m_simplexes + m_simplexIndex;
			b2SimplexVertex* vertices[3] = { &simplex->v1, &simplex->v2, &simplex->v3 };

			if ( m_simplexIndex > 0 )
			{
				// The first recorded simplex does not have valid barycentric coordinates
				b2Vec2 pointA, pointB;
				ComputeSimplexWitnessPoints( &pointA, &pointB, simplex );

				g_draw.DrawSegment( pointA, pointB, b2_colorWhite );
				g_draw.DrawPoint( pointA, 5.0f, b2_colorWhite );
				g_draw.DrawPoint( pointB, 5.0f, b2_colorWhite );
			}

			b2HexColor colors[3] = { b2_colorRed, b2_colorGreen, b2_colorBlue };

			for ( int i = 0; i < simplex->count; ++i )
			{
				b2SimplexVertex* vertex = vertices[i];
				g_draw.DrawPoint( vertex->wA, 5.0f, colors[i] );
				g_draw.DrawPoint( vertex->wB, 5.0f, colors[i] );
			}
		}
		else
		{
			g_draw.DrawSegment( output.pointA, output.pointB, b2_colorWhite );
			g_draw.DrawPoint( output.pointA, 5.0f, b2_colorWhite );
			g_draw.DrawPoint( output.pointB, 5.0f, b2_colorWhite );
		}

		if ( m_showIndices )
		{
			for ( int i = 0; i < m_proxyA.count; ++i )
			{
				b2Vec2 p = m_proxyA.points[i];
				g_draw.DrawString( p, " %d", i );
			}

			for ( int i = 0; i < m_proxyB.count; ++i )
			{
				b2Vec2 p = b2TransformPoint( m_transform, m_proxyB.points[i] );
				g_draw.DrawString( p, " %d", i );
			}
		}

		g_draw.DrawString( 5, m_textLine, "mouse button 1: drag" );
		m_textLine += m_textIncrement;
		g_draw.DrawString( 5, m_textLine, "mouse button 1 + shift: rotate" );
		m_textLine += m_textIncrement;
		g_draw.DrawString( 5, m_textLine, "distance = %.2f, iterations = %d", output.distance, output.iterations );
		m_textLine += m_textIncrement;

		if ( m_cache.count == 1 )
		{
			g_draw.DrawString( 5, m_textLine, "cache = {%d}, {%d}", m_cache.indexA[0], m_cache.indexB[0] );
		}
		else if ( m_cache.count == 2 )
		{
			g_draw.DrawString( 5, m_textLine, "cache = {%d, %d}, {%d, %d}", m_cache.indexA[0], m_cache.indexA[1],
							   m_cache.indexB[0], m_cache.indexB[1] );
		}
		else if ( m_cache.count == 3 )
		{
			g_draw.DrawString( 5, m_textLine, "cache = {%d, %d, %d}, {%d, %d, %d}", m_cache.indexA[0], m_cache.indexA[1],
							   m_cache.indexA[2], m_cache.indexB[0], m_cache.indexB[1], m_cache.indexB[2] );
		}
		m_textLine += m_textIncrement;
	}

	static Sample* Create( Settings& settings )
	{
		return new ShapeDistance( settings );
	}

	b2Polygon m_box;
	b2Polygon m_triangle;
	b2Vec2 m_point;
	b2Segment m_segment;

	ShapeType m_typeA;
	ShapeType m_typeB;
	float m_radiusA;
	float m_radiusB;
	b2DistanceProxy m_proxyA;
	b2DistanceProxy m_proxyB;

	b2DistanceCache m_cache;
	b2Simplex m_simplexes[SIMPLEX_CAPACITY];
	int m_simplexCount;
	int m_simplexIndex;

	b2Transform m_transform;
	float m_angle;

	b2Vec2 m_basePosition;
	b2Vec2 m_startPoint;
	float m_baseAngle;

	bool m_dragging;
	bool m_rotating;
	bool m_showIndices;
	bool m_useCache;
	bool m_drawSimplex;
};

static int sampleShapeDistance = RegisterSample( "Collision", "Shape Distance", ShapeDistance::Create );

enum UpdateType
{
	Update_Incremental = 0,
	Update_FullRebuild = 1,
	Update_PartialRebuild = 2,
};

struct Proxy
{
	b2AABB box;
	b2AABB fatBox;
	b2Vec2 position;
	b2Vec2 width;
	int proxyId;
	int rayStamp;
	int queryStamp;
	bool moved;
};

static bool QueryCallback( int32_t proxyId, int32_t userData, void* context );
static float RayCallback( const b2RayCastInput* input, int32_t proxyId, int32_t userData, void* context );

// Tests the Box2D bounding volume hierarchy (BVH). The dynamic tree
// can be used independently as a spatial data structure.
class DynamicTree : public Sample
{
public:
	explicit DynamicTree( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { 500.0f, 500.0f };
			g_camera.m_zoom = 25.0f * 21.0f;
		}

		m_fill = 0.25f;
		m_moveFraction = 0.05f;
		m_moveDelta = 0.1f;
		m_proxies = nullptr;
		m_proxyCount = 0;
		m_proxyCapacity = 0;
		m_ratio = 5.0f;
		m_grid = 1.0f;

		m_moveBuffer = nullptr;
		m_moveCount = 0;

		m_rowCount = g_sampleDebug ? 100 : 1000;
		m_columnCount = g_sampleDebug ? 100 : 1000;
		memset( &m_tree, 0, sizeof( m_tree ) );
		BuildTree();
		m_timeStamp = 0;
		m_updateType = Update_Incremental;

		m_startPoint = { 0.0f, 0.0f };
		m_endPoint = { 0.0f, 0.0f };
		m_queryDrag = false;
		m_rayDrag = false;
		m_validate = true;
	}

	~DynamicTree() override
	{
		free( m_proxies );
		free( m_moveBuffer );
		b2DynamicTree_Destroy( &m_tree );
	}

	void BuildTree()
	{
		b2DynamicTree_Destroy( &m_tree );
		free( m_proxies );
		free( m_moveBuffer );

		m_proxyCapacity = m_rowCount * m_columnCount;
		m_proxies = static_cast<Proxy*>( malloc( m_proxyCapacity * sizeof( Proxy ) ) );
		m_proxyCount = 0;

		m_moveBuffer = static_cast<int*>( malloc( m_proxyCapacity * sizeof( int ) ) );
		m_moveCount = 0;

		float y = -4.0f;

		bool isStatic = false;
		m_tree = b2DynamicTree_Create();

		const b2Vec2 aabbMargin = { 0.1f, 0.1f };

		for ( int i = 0; i < m_rowCount; ++i )
		{
			float x = -40.0f;

			for ( int j = 0; j < m_columnCount; ++j )
			{
				float fillTest = RandomFloat( 0.0f, 1.0f );
				if ( fillTest <= m_fill )
				{
					assert( m_proxyCount <= m_proxyCapacity );
					Proxy* p = m_proxies + m_proxyCount;
					p->position = { x, y };

					float ratio = RandomFloat( 1.0f, m_ratio );
					float width = RandomFloat( 0.1f, 0.5f );
					if ( RandomFloat() > 0.0f )
					{
						p->width.x = ratio * width;
						p->width.y = width;
					}
					else
					{
						p->width.x = width;
						p->width.y = ratio * width;
					}

					p->box.lowerBound = { x, y };
					p->box.upperBound = { x + p->width.x, y + p->width.y };
					p->fatBox.lowerBound = b2Sub( p->box.lowerBound, aabbMargin );
					p->fatBox.upperBound = b2Add( p->box.upperBound, aabbMargin );

					p->proxyId = b2DynamicTree_CreateProxy( &m_tree, p->fatBox, b2_defaultCategoryBits, m_proxyCount );
					p->rayStamp = -1;
					p->queryStamp = -1;
					p->moved = false;
					++m_proxyCount;
				}

				x += m_grid;
			}

			y += m_grid;
		}
	}

	void UpdateUI() override
	{
		float height = 320.0f;
		ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
		ImGui::SetNextWindowSize( ImVec2( 200.0f, height ) );

		ImGui::Begin( "Dynamic Tree", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize );

		ImGui::PushItemWidth( 100.0f );

		bool changed = false;
		if ( ImGui::SliderInt( "rows", &m_rowCount, 0, 1000, "%d" ) )
		{
			changed = true;
		}

		if ( ImGui::SliderInt( "columns", &m_columnCount, 0, 1000, "%d" ) )
		{
			changed = true;
		}

		if ( ImGui::SliderFloat( "fill", &m_fill, 0.0f, 1.0f, "%.2f" ) )
		{
			changed = true;
		}

		if ( ImGui::SliderFloat( "grid", &m_grid, 0.5f, 2.0f, "%.2f" ) )
		{
			changed = true;
		}

		if ( ImGui::SliderFloat( "ratio", &m_ratio, 1.0f, 10.0f, "%.2f" ) )
		{
			changed = true;
		}

		if ( ImGui::SliderFloat( "move", &m_moveFraction, 0.0f, 1.0f, "%.2f" ) )
		{
		}

		if ( ImGui::SliderFloat( "delta", &m_moveDelta, 0.0f, 1.0f, "%.2f" ) )
		{
		}

		if ( ImGui::RadioButton( "Incremental", m_updateType == Update_Incremental ) )
		{
			m_updateType = Update_Incremental;
			changed = true;
		}

		if ( ImGui::RadioButton( "Full Rebuild", m_updateType == Update_FullRebuild ) )
		{
			m_updateType = Update_FullRebuild;
			changed = true;
		}

		if ( ImGui::RadioButton( "Partial Rebuild", m_updateType == Update_PartialRebuild ) )
		{
			m_updateType = Update_PartialRebuild;
			changed = true;
		}

		ImGui::Separator();

		ImGui::Text( "mouse button 1: ray cast" );
		ImGui::Text( "mouse button 1 + shift: query" );

		ImGui::PopItemWidth();
		ImGui::End();

		if ( changed )
		{
			BuildTree();
		}
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			if ( mods == 0 && m_queryDrag == false )
			{
				m_rayDrag = true;
				m_startPoint = p;
				m_endPoint = p;
			}
			else if ( mods == GLFW_MOD_SHIFT && m_rayDrag == false )
			{
				m_queryDrag = true;
				m_startPoint = p;
				m_endPoint = p;
			}
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_queryDrag = false;
			m_rayDrag = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		m_endPoint = p;
	}

	void Step( Settings& ) override
	{
		if ( m_queryDrag )
		{
			b2AABB box = { b2Min( m_startPoint, m_endPoint ), b2Max( m_startPoint, m_endPoint ) };
			b2DynamicTree_Query( &m_tree, box, b2_defaultMaskBits, QueryCallback, this );

			g_draw.DrawAABB( box, b2_colorWhite );
		}

		// m_startPoint = {-1.0f, 0.5f};
		// m_endPoint = {7.0f, 0.5f};

		if ( m_rayDrag )
		{
			b2RayCastInput input = { m_startPoint, b2Sub( m_endPoint, m_startPoint ), 1.0f };
			b2DynamicTree_RayCast( &m_tree, &input, b2_defaultMaskBits, RayCallback, this );

			g_draw.DrawSegment( m_startPoint, m_endPoint, b2_colorWhite );
			g_draw.DrawPoint( m_startPoint, 5.0f, b2_colorGreen );
			g_draw.DrawPoint( m_endPoint, 5.0f, b2_colorRed );
		}

		b2HexColor c = b2_colorBlue;
		b2HexColor qc = b2_colorGreen;

		const b2Vec2 aabbMargin = { 0.1f, 0.1f };

		for ( int i = 0; i < m_proxyCount; ++i )
		{
			Proxy* p = m_proxies + i;

			if ( p->queryStamp == m_timeStamp || p->rayStamp == m_timeStamp )
			{
				g_draw.DrawAABB( p->box, qc );
			}
			else
			{
				g_draw.DrawAABB( p->box, c );
			}

			float moveTest = RandomFloat( 0.0f, 1.0f );
			if ( m_moveFraction > moveTest )
			{
				float dx = m_moveDelta * RandomFloat();
				float dy = m_moveDelta * RandomFloat();

				p->position.x += dx;
				p->position.y += dy;

				p->box.lowerBound.x = p->position.x + dx;
				p->box.lowerBound.y = p->position.y + dy;
				p->box.upperBound.x = p->position.x + dx + p->width.x;
				p->box.upperBound.y = p->position.y + dy + p->width.y;

				if ( b2AABB_Contains( p->fatBox, p->box ) == false )
				{
					p->fatBox.lowerBound = b2Sub( p->box.lowerBound, aabbMargin );
					p->fatBox.upperBound = b2Add( p->box.upperBound, aabbMargin );
					p->moved = true;
				}
				else
				{
					p->moved = false;
				}
			}
			else
			{
				p->moved = false;
			}
		}

		switch ( m_updateType )
		{
			case Update_Incremental:
			{
				b2Timer timer = b2CreateTimer();
				for ( int i = 0; i < m_proxyCount; ++i )
				{
					Proxy* p = m_proxies + i;
					if ( p->moved )
					{
						b2DynamicTree_MoveProxy( &m_tree, p->proxyId, p->fatBox );
					}
				}
				float ms = b2GetMilliseconds( &timer );
				g_draw.DrawString( 5, m_textLine, "incremental : %.3f ms", ms );
				m_textLine += m_textIncrement;
			}
			break;

			case Update_FullRebuild:
			{
				for ( int i = 0; i < m_proxyCount; ++i )
				{
					Proxy* p = m_proxies + i;
					if ( p->moved )
					{
						b2DynamicTree_EnlargeProxy( &m_tree, p->proxyId, p->fatBox );
					}
				}

				b2Timer timer = b2CreateTimer();
				int boxCount = b2DynamicTree_Rebuild( &m_tree, true );
				float ms = b2GetMilliseconds( &timer );
				g_draw.DrawString( 5, m_textLine, "full build %d : %.3f ms", boxCount, ms );
				m_textLine += m_textIncrement;
			}
			break;

			case Update_PartialRebuild:
			{
				for ( int i = 0; i < m_proxyCount; ++i )
				{
					Proxy* p = m_proxies + i;
					if ( p->moved )
					{
						b2DynamicTree_EnlargeProxy( &m_tree, p->proxyId, p->fatBox );
					}
				}

				b2Timer timer = b2CreateTimer();
				int boxCount = b2DynamicTree_Rebuild( &m_tree, false );
				float ms = b2GetMilliseconds( &timer );
				g_draw.DrawString( 5, m_textLine, "partial rebuild %d : %.3f ms", boxCount, ms );
				m_textLine += m_textIncrement;
			}
			break;

			default:
				break;
		}

		int height = b2DynamicTree_GetHeight( &m_tree );
		float areaRatio = b2DynamicTree_GetAreaRatio( &m_tree );

		int hmin = (int)( ceilf( logf( (float)m_proxyCount ) / logf( 2.0f ) - 1.0f ) );
		g_draw.DrawString( 5, m_textLine, "proxies = %d, height = %d, hmin = %d, area ratio = %.1f", m_proxyCount, height, hmin,
						   areaRatio );
		m_textLine += m_textIncrement;

		b2DynamicTree_Validate( &m_tree );

		m_timeStamp += 1;
	}

	static Sample* Create( Settings& settings )
	{
		return new DynamicTree( settings );
	}

	b2DynamicTree m_tree;
	int m_rowCount, m_columnCount;
	Proxy* m_proxies;
	int* m_moveBuffer;
	int m_moveCount;
	int m_proxyCapacity;
	int m_proxyCount;
	int m_timeStamp;
	int m_updateType;
	float m_fill;
	float m_moveFraction;
	float m_moveDelta;
	float m_ratio;
	float m_grid;

	b2Vec2 m_startPoint;
	b2Vec2 m_endPoint;

	bool m_rayDrag;
	bool m_queryDrag;
	bool m_validate;
};

static bool QueryCallback( int proxyId, int userData, void* context )
{
	DynamicTree* sample = static_cast<DynamicTree*>( context );
	Proxy* proxy = sample->m_proxies + userData;
	assert( proxy->proxyId == proxyId );
	proxy->queryStamp = sample->m_timeStamp;
	return true;
}

static float RayCallback( const b2RayCastInput* input, int proxyId, int userData, void* context )
{
	DynamicTree* sample = static_cast<DynamicTree*>( context );
	Proxy* proxy = sample->m_proxies + userData;
	assert( proxy->proxyId == proxyId );
	proxy->rayStamp = sample->m_timeStamp;
	return input->maxFraction;
}

static int sampleDynamicTree = RegisterSample( "Collision", "Dynamic Tree", DynamicTree::Create );

class RayCast : public Sample
{
public:
	explicit RayCast( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { 0.0f, 20.0f };
			g_camera.m_zoom = 17.5f;
		}

		m_circle = { { 0.0f, 0.0f }, 2.0f };
		m_capsule = { { -1.0f, 1.0f }, { 1.0f, -1.0f }, 1.5f };
		m_box = b2MakeBox( 2.0f, 2.0f );

		b2Vec2 vertices[3] = { { -2.0f, 0.0f }, { 2.0f, 0.0f }, { 2.0f, 3.0f } };
		b2Hull hull = b2ComputeHull( vertices, 3 );
		m_triangle = b2MakePolygon( &hull, 0.0f );

		m_segment = { { -3.0f, 0.0f }, { 3.0f, 0.0 } };

		m_transform = b2Transform_identity;
		m_angle = 0.0f;

		m_basePosition = { 0.0f, 0.0f };
		m_baseAngle = 0.0f;
		m_startPosition = { 0.0f, 0.0f };

		m_rayStart = { 0.0f, 30.0f };
		m_rayEnd = { 0.0f, 0.0f };

		m_rayDrag = false;
		m_translating = false;
		m_rotating = false;

		m_showFraction = false;
	}

	void UpdateUI() override
	{
		float height = 230.0f;
		ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
		ImGui::SetNextWindowSize( ImVec2( 200.0f, height ) );

		ImGui::Begin( "Ray-cast", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize );

		ImGui::PushItemWidth( 100.0f );

		ImGui::SliderFloat( "x offset", &m_transform.p.x, -2.0f, 2.0f, "%.2f" );
		ImGui::SliderFloat( "y offset", &m_transform.p.y, -2.0f, 2.0f, "%.2f" );

		if ( ImGui::SliderFloat( "angle", &m_angle, -b2_pi, b2_pi, "%.2f" ) )
		{
			m_transform.q = b2MakeRot( m_angle );
		}

		// if (ImGui::SliderFloat("ray radius", &m_rayRadius, 0.0f, 1.0f, "%.1f"))
		//{
		// }

		ImGui::Checkbox( "show fraction", &m_showFraction );

		if ( ImGui::Button( "Reset" ) )
		{
			m_transform = b2Transform_identity;
			m_angle = 0.0f;
		}

		ImGui::Separator();

		ImGui::Text( "mouse btn 1: ray cast" );
		ImGui::Text( "mouse btn 1 + shft: translate" );
		ImGui::Text( "mouse btn 1 + ctrl: rotate" );

		ImGui::PopItemWidth();

		ImGui::End();
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_startPosition = p;

			if ( mods == 0 )
			{
				m_rayStart = p;
				m_rayDrag = true;
			}
			else if ( mods == GLFW_MOD_SHIFT )
			{
				m_translating = true;
				m_basePosition = m_transform.p;
			}
			else if ( mods == GLFW_MOD_CONTROL )
			{
				m_rotating = true;
				m_baseAngle = m_angle;
			}
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_rayDrag = false;
			m_rotating = false;
			m_translating = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		if ( m_rayDrag )
		{
			m_rayEnd = p;
		}
		else if ( m_translating )
		{
			m_transform.p.x = m_basePosition.x + 0.5f * ( p.x - m_startPosition.x );
			m_transform.p.y = m_basePosition.y + 0.5f * ( p.y - m_startPosition.y );
		}
		else if ( m_rotating )
		{
			float dx = p.x - m_startPosition.x;
			m_angle = b2ClampFloat( m_baseAngle + 0.5f * dx, -b2_pi, b2_pi );
			m_transform.q = b2MakeRot( m_angle );
		}
	}

	void DrawRay( const b2CastOutput* output )
	{
		b2Vec2 p1 = m_rayStart;
		b2Vec2 p2 = m_rayEnd;
		b2Vec2 d = b2Sub( p2, p1 );

		if ( output->hit )
		{
			b2Vec2 p = b2MulAdd( p1, output->fraction, d );
			g_draw.DrawSegment( p1, p, b2_colorWhite );
			g_draw.DrawPoint( p1, 5.0f, b2_colorGreen );
			g_draw.DrawPoint( output->point, 5.0f, b2_colorWhite );

			b2Vec2 n = b2MulAdd( p, 1.0f, output->normal );
			g_draw.DrawSegment( p, n, b2_colorViolet );

			// if (m_rayRadius > 0.0f)
			//{
			//	g_draw.DrawCircle(p1, m_rayRadius, b2_colorGreen);
			//	g_draw.DrawCircle(p, m_rayRadius, b2_colorRed);
			// }

			if ( m_showFraction )
			{
				b2Vec2 ps = { p.x + 0.05f, p.y - 0.02f };
				g_draw.DrawString( ps, "%.2f", output->fraction );
			}
		}
		else
		{
			g_draw.DrawSegment( p1, p2, b2_colorWhite );
			g_draw.DrawPoint( p1, 5.0f, b2_colorGreen );
			g_draw.DrawPoint( p2, 5.0f, b2_colorRed );

			// if (m_rayRadius > 0.0f)
			//{
			//	g_draw.DrawCircle(p1, m_rayRadius, b2_colorGreen);
			//	g_draw.DrawCircle(p2, m_rayRadius, b2_colorRed);
			// }
		}
	}

	void Step( Settings& ) override
	{
		b2Vec2 offset = { -20.0f, 20.0f };
		b2Vec2 increment = { 10.0f, 0.0f };

		b2HexColor color1 = b2_colorYellow;

		b2CastOutput output = { 0 };
		float maxFraction = 1.0f;

		// circle
		{
			b2Transform transform = { b2Add( m_transform.p, offset ), m_transform.q };
			g_draw.DrawSolidCircle( transform, m_circle.center, m_circle.radius, color1 );

			b2Vec2 start = b2InvTransformPoint( transform, m_rayStart );
			b2Vec2 translation = b2InvRotateVector( transform.q, b2Sub( m_rayEnd, m_rayStart ) );
			b2RayCastInput input = { start, translation, maxFraction };

			b2CastOutput localOutput = b2RayCastCircle( &input, &m_circle );
			if ( localOutput.hit )
			{
				output = localOutput;
				output.point = b2TransformPoint( transform, localOutput.point );
				output.normal = b2RotateVector( transform.q, localOutput.normal );
				maxFraction = localOutput.fraction;
			}

			offset = b2Add( offset, increment );
		}

		// capsule
		{
			b2Transform transform = { b2Add( m_transform.p, offset ), m_transform.q };
			b2Vec2 v1 = b2TransformPoint( transform, m_capsule.center1 );
			b2Vec2 v2 = b2TransformPoint( transform, m_capsule.center2 );
			g_draw.DrawSolidCapsule( v1, v2, m_capsule.radius, color1 );

			b2Vec2 start = b2InvTransformPoint( transform, m_rayStart );
			b2Vec2 translation = b2InvRotateVector( transform.q, b2Sub( m_rayEnd, m_rayStart ) );
			b2RayCastInput input = { start, translation, maxFraction };

			b2CastOutput localOutput = b2RayCastCapsule( &input, &m_capsule );
			if ( localOutput.hit )
			{
				output = localOutput;
				output.point = b2TransformPoint( transform, localOutput.point );
				output.normal = b2RotateVector( transform.q, localOutput.normal );
				maxFraction = localOutput.fraction;
			}

			offset = b2Add( offset, increment );
		}

		// box
		{
			b2Transform transform = { b2Add( m_transform.p, offset ), m_transform.q };
			g_draw.DrawSolidPolygon( transform, m_box.vertices, m_box.count, 0.0f, color1 );

			b2Vec2 start = b2InvTransformPoint( transform, m_rayStart );
			b2Vec2 translation = b2InvRotateVector( transform.q, b2Sub( m_rayEnd, m_rayStart ) );
			b2RayCastInput input = { start, translation, maxFraction };

			b2CastOutput localOutput = b2RayCastPolygon( &input, &m_box );
			if ( localOutput.hit )
			{
				output = localOutput;
				output.point = b2TransformPoint( transform, localOutput.point );
				output.normal = b2RotateVector( transform.q, localOutput.normal );
				maxFraction = localOutput.fraction;
			}

			offset = b2Add( offset, increment );
		}

		// triangle
		{
			b2Transform transform = { b2Add( m_transform.p, offset ), m_transform.q };
			g_draw.DrawSolidPolygon( transform, m_triangle.vertices, m_triangle.count, 0.0f, color1 );

			b2Vec2 start = b2InvTransformPoint( transform, m_rayStart );
			b2Vec2 translation = b2InvRotateVector( transform.q, b2Sub( m_rayEnd, m_rayStart ) );
			b2RayCastInput input = { start, translation, maxFraction };

			b2CastOutput localOutput = b2RayCastPolygon( &input, &m_triangle );
			if ( localOutput.hit )
			{
				output = localOutput;
				output.point = b2TransformPoint( transform, localOutput.point );
				output.normal = b2RotateVector( transform.q, localOutput.normal );
				maxFraction = localOutput.fraction;
			}

			offset = b2Add( offset, increment );
		}

		// segment
		{
			b2Transform transform = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Vec2 p1 = b2TransformPoint( transform, m_segment.point1 );
			b2Vec2 p2 = b2TransformPoint( transform, m_segment.point2 );
			g_draw.DrawSegment( p1, p2, color1 );

			b2Vec2 start = b2InvTransformPoint( transform, m_rayStart );
			b2Vec2 translation = b2InvRotateVector( transform.q, b2Sub( m_rayEnd, m_rayStart ) );
			b2RayCastInput input = { start, translation, maxFraction };

			b2CastOutput localOutput = b2RayCastSegment( &input, &m_segment, false );
			if ( localOutput.hit )
			{
				output = localOutput;
				output.point = b2TransformPoint( transform, localOutput.point );
				output.normal = b2RotateVector( transform.q, localOutput.normal );
				maxFraction = localOutput.fraction;
			}

			offset = b2Add( offset, increment );
		}

		DrawRay( &output );
	}

	static Sample* Create( Settings& settings )
	{
		return new RayCast( settings );
	}

	b2Polygon m_box;
	b2Polygon m_triangle;
	b2Circle m_circle;
	b2Capsule m_capsule;
	b2Segment m_segment;

	b2Transform m_transform;
	float m_angle;

	b2Vec2 m_rayStart;
	b2Vec2 m_rayEnd;

	b2Vec2 m_basePosition;
	float m_baseAngle;

	b2Vec2 m_startPosition;

	bool m_rayDrag;
	bool m_translating;
	bool m_rotating;
	bool m_showFraction;
};

static int sampleIndex = RegisterSample( "Collision", "Ray Cast", RayCast::Create );

// This shows how to filter a specific shape using using data.
struct ShapeUserData
{
	int index;
	bool ignore;
};

// Context for ray cast callbacks. Do what you want with this.
struct RayCastContext
{
	b2Vec2 points[3];
	b2Vec2 normals[3];
	float fractions[3];
	int count;
};

// This callback finds the closest hit. This is the most common callback used in games.
static float RayCastClosestCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context )
{
	RayCastContext* rayContext = (RayCastContext*)context;

	ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId );
	if ( userData != nullptr && userData->ignore )
	{
		// By returning -1, we instruct the calling code to ignore this shape and
		// continue the ray-cast to the next shape.
		return -1.0f;
	}

	rayContext->points[0] = point;
	rayContext->normals[0] = normal;
	rayContext->fractions[0] = fraction;
	rayContext->count = 1;

	// By returning the current fraction, we instruct the calling code to clip the ray and
	// continue the ray-cast to the next shape. WARNING: do not assume that shapes
	// are reported in order. However, by clipping, we can always get the closest shape.
	return fraction;
}

// This callback finds any hit. For this type of query we are usually just checking for obstruction,
// so the hit data is not relevant.
// NOTE: shape hits are not ordered, so this may not return the closest hit
static float RayCastAnyCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context )
{
	RayCastContext* rayContext = (RayCastContext*)context;

	ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId );
	if ( userData != nullptr && userData->ignore )
	{
		// By returning -1, we instruct the calling code to ignore this shape and
		// continue the ray-cast to the next shape.
		return -1.0f;
	}

	rayContext->points[0] = point;
	rayContext->normals[0] = normal;
	rayContext->fractions[0] = fraction;
	rayContext->count = 1;

	// At this point we have a hit, so we know the ray is obstructed.
	// By returning 0, we instruct the calling code to terminate the ray-cast.
	return 0.0f;
}

// This ray cast collects multiple hits along the ray.
// The shapes are not necessary reported in order, so we might not capture
// the closest shape.
// NOTE: shape hits are not ordered, so this may return hits in any order. This means that
// if you limit the number of results, you may discard the closest hit. You can see this
// behavior in the sample.
static float RayCastMultipleCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context )
{
	RayCastContext* rayContext = (RayCastContext*)context;

	ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId );
	if ( userData != nullptr && userData->ignore )
	{
		// By returning -1, we instruct the calling code to ignore this shape and
		// continue the ray-cast to the next shape.
		return -1.0f;
	}

	int count = rayContext->count;
	assert( count < 3 );

	rayContext->points[count] = point;
	rayContext->normals[count] = normal;
	rayContext->fractions[count] = fraction;
	rayContext->count = count + 1;

	if ( rayContext->count == 3 )
	{
		// At this point the buffer is full.
		// By returning 0, we instruct the calling code to terminate the ray-cast.
		return 0.0f;
	}

	// By returning 1, we instruct the caller to continue without clipping the ray.
	return 1.0f;
}

// This ray cast collects multiple hits along the ray and sorts them.
static float RayCastSortedCallback( b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void* context )
{
	RayCastContext* rayContext = (RayCastContext*)context;

	ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId );
	if ( userData != nullptr && userData->ignore )
	{
		// By returning -1, we instruct the calling code to ignore this shape and
		// continue the ray-cast to the next shape.
		return -1.0f;
	}

	int count = rayContext->count;
	assert( count <= 3 );

	int index = 3;
	while ( fraction < rayContext->fractions[index - 1] )
	{
		index -= 1;

		if ( index == 0 )
		{
			break;
		}
	}

	if ( index == 3 )
	{
		// not closer, continue but tell the caller not to consider fractions further than the largest fraction acquired
		// this only happens once the buffer is full
		assert( rayContext->count == 3 );
		assert( rayContext->fractions[2] <= 1.0f );
		return rayContext->fractions[2];
	}

	for ( int j = 2; j > index; --j )
	{
		rayContext->points[j] = rayContext->points[j - 1];
		rayContext->normals[j] = rayContext->normals[j - 1];
		rayContext->fractions[j] = rayContext->fractions[j - 1];
	}

	rayContext->points[index] = point;
	rayContext->normals[index] = normal;
	rayContext->fractions[index] = fraction;
	rayContext->count = count < 3 ? count + 1 : 3;

	if ( rayContext->count == 3 )
	{
		return rayContext->fractions[2];
	}

	// By returning 1, we instruct the caller to continue without clipping the ray.
	return 1.0f;
}

class RayCastWorld : public Sample
{
public:
	enum Mode
	{
		e_any = 0,
		e_closest = 1,
		e_multiple = 2,
		e_sorted = 3
	};

	enum CastType
	{
		e_rayCast = 0,
		e_circleCast = 1,
		e_capsuleCast = 2,
		e_polygonCast = 3
	};

	enum
	{
		e_maxCount = 64
	};

	explicit RayCastWorld( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { 2.0f, 14.0f };
			g_camera.m_zoom = 25.0f * 0.75f;
		}

		// Ground body
		{
			b2BodyDef bodyDef = b2DefaultBodyDef();
			b2BodyId groundId = b2CreateBody( m_worldId, &bodyDef );

			b2ShapeDef shapeDef = b2DefaultShapeDef();
			b2Segment segment = { { -40.0f, 0.0f }, { 40.0f, 0.0f } };
			b2CreateSegmentShape( groundId, &shapeDef, &segment );
		}

		{
			b2Vec2 vertices[3] = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, { 0.0f, 1.5f } };
			b2Hull hull = b2ComputeHull( vertices, 3 );
			m_polygons[0] = b2MakePolygon( &hull, 0.0f );
		}

		{
			b2Vec2 vertices[3] = { { -0.1f, 0.0f }, { 0.1f, 0.0f }, { 0.0f, 1.5f } };
			b2Hull hull = b2ComputeHull( vertices, 3 );
			m_polygons[1] = b2MakePolygon( &hull, 0.0f );
			m_polygons[1].radius = 0.5f;
		}

		{
			float w = 1.0f;
			float b = w / ( 2.0f + sqrtf( 2.0f ) );
			float s = sqrtf( 2.0f ) * b;

			b2Vec2 vertices[8] = { { 0.5f * s, 0.0f }, { 0.5f * w, b },		 { 0.5f * w, b + s }, { 0.5f * s, w },
								   { -0.5f * s, w },   { -0.5f * w, b + s }, { -0.5f * w, b },	  { -0.5f * s, 0.0f } };

			b2Hull hull = b2ComputeHull( vertices, 8 );
			m_polygons[2] = b2MakePolygon( &hull, 0.0f );
		}

		m_polygons[3] = b2MakeBox( 0.5f, 0.5f );
		m_capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, 0.25f };
		m_circle = { { 0.0f, 0.0f }, 0.5f };
		m_segment = { { -1.0f, 0.0f }, { 1.0f, 0.0f } };

		m_bodyIndex = 0;

		for ( int i = 0; i < e_maxCount; ++i )
		{
			m_bodyIds[i] = b2_nullBodyId;
		}

		m_mode = e_closest;
		m_ignoreIndex = 7;

		m_castType = e_rayCast;
		m_castRadius = 0.5f;

		m_rayStart = { -20.0f, 10.0f };
		m_rayEnd = { 20.0f, 10.0f };
		m_dragging = false;

		m_angle = 0.0f;
		m_baseAngle = 0.0f;
		m_angleAnchor = { 0.0f, 0.0f };
		m_rotating = false;

		m_simple = false;
	}

	void Create( int index )
	{
		if ( B2_IS_NON_NULL( m_bodyIds[m_bodyIndex] ) )
		{
			b2DestroyBody( m_bodyIds[m_bodyIndex] );
			m_bodyIds[m_bodyIndex] = b2_nullBodyId;
		}

		float x = RandomFloat( -20.0f, 20.0f );
		float y = RandomFloat( 0.0f, 20.0f );

		b2BodyDef bodyDef = b2DefaultBodyDef();
		bodyDef.position = { x, y };
		bodyDef.rotation = b2MakeRot( RandomFloat( -b2_pi, b2_pi ) );

		m_bodyIds[m_bodyIndex] = b2CreateBody( m_worldId, &bodyDef );

		b2ShapeDef shapeDef = b2DefaultShapeDef();
		shapeDef.userData = m_userData + m_bodyIndex;
		m_userData[m_bodyIndex].ignore = false;
		if ( m_bodyIndex == m_ignoreIndex )
		{
			m_userData[m_bodyIndex].ignore = true;
		}

		if ( index < 4 )
		{
			b2CreatePolygonShape( m_bodyIds[m_bodyIndex], &shapeDef, m_polygons + index );
		}
		else if ( index == 4 )
		{
			b2CreateCircleShape( m_bodyIds[m_bodyIndex], &shapeDef, &m_circle );
		}
		else if ( index == 5 )
		{
			b2CreateCapsuleShape( m_bodyIds[m_bodyIndex], &shapeDef, &m_capsule );
		}
		else
		{
			b2CreateSegmentShape( m_bodyIds[m_bodyIndex], &shapeDef, &m_segment );
		}

		m_bodyIndex = ( m_bodyIndex + 1 ) % e_maxCount;
	}

	void CreateN( int index, int count )
	{
		for ( int i = 0; i < count; ++i )
		{
			Create( index );
		}
	}

	void DestroyBody()
	{
		for ( int i = 0; i < e_maxCount; ++i )
		{
			if ( B2_IS_NON_NULL( m_bodyIds[i] ) )
			{
				b2DestroyBody( m_bodyIds[i] );
				m_bodyIds[i] = b2_nullBodyId;
				return;
			}
		}
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			if ( mods == 0 && m_rotating == false )
			{
				m_rayStart = p;
				m_rayEnd = p;
				m_dragging = true;
			}
			else if ( mods == GLFW_MOD_SHIFT && m_dragging == false )
			{
				m_rotating = true;
				m_angleAnchor = p;
				m_baseAngle = m_angle;
			}
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_dragging = false;
			m_rotating = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		if ( m_dragging )
		{
			m_rayEnd = p;
		}
		else if ( m_rotating )
		{
			float dx = p.x - m_angleAnchor.x;
			m_angle = m_baseAngle + 1.0f * dx;
		}
	}

	void UpdateUI() override
	{
		float height = 300.0f;
		ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
		ImGui::SetNextWindowSize( ImVec2( 200.0f, height ) );

		ImGui::Begin( "Ray-cast World", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize );

		ImGui::Checkbox( "Simple", &m_simple );

		if ( m_simple == false )
		{
			const char* castTypes[] = { "Ray", "Circle", "Capsule", "Polygon" };
			int castType = int( m_castType );
			if ( ImGui::Combo( "Type", &castType, castTypes, IM_ARRAYSIZE( castTypes ) ) )
			{
				m_castType = CastType( castType );
			}

			if ( m_castType != e_rayCast )
			{
				ImGui::SliderFloat( "Radius", &m_castRadius, 0.0f, 2.0f, "%.1f" );
			}

			const char* modes[] = { "Any", "Closest", "Multiple", "Sorted" };
			int mode = int( m_mode );
			if ( ImGui::Combo( "Mode", &mode, modes, IM_ARRAYSIZE( modes ) ) )
			{
				m_mode = Mode( mode );
			}
		}

		if ( ImGui::Button( "Polygon 1" ) )
			Create( 0 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Poly1" ) )
			CreateN( 0, 10 );

		if ( ImGui::Button( "Polygon 2" ) )
			Create( 1 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Poly2" ) )
			CreateN( 1, 10 );

		if ( ImGui::Button( "Polygon 3" ) )
			Create( 2 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Poly3" ) )
			CreateN( 2, 10 );

		if ( ImGui::Button( "Box" ) )
			Create( 3 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Box" ) )
			CreateN( 3, 10 );

		if ( ImGui::Button( "Circle" ) )
			Create( 4 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Circle" ) )
			CreateN( 4, 10 );

		if ( ImGui::Button( "Capsule" ) )
			Create( 5 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Capsule" ) )
			CreateN( 5, 10 );

		if ( ImGui::Button( "Segment" ) )
			Create( 6 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Segment" ) )
			CreateN( 6, 10 );

		if ( ImGui::Button( "Destroy Shape" ) )
		{
			DestroyBody();
		}

		ImGui::End();
	}

	void Step( Settings& settings ) override
	{
		Sample::Step( settings );

		g_draw.DrawString( 5, m_textLine, "Click left mouse button and drag to modify ray cast" );
		m_textLine += m_textIncrement;
		g_draw.DrawString( 5, m_textLine, "Shape 7 is intentionally ignored by the ray" );
		m_textLine += m_textIncrement;

		m_textLine += m_textIncrement;

		b2HexColor color1 = b2_colorGreen;
		b2HexColor color2 = b2_colorGray8;
		b2HexColor color3 = b2_colorMagenta;

		b2Vec2 rayTranslation = b2Sub( m_rayEnd, m_rayStart );

		if ( m_simple )
		{
			g_draw.DrawString( 5, m_textLine, "Simple closest point ray cast" );
			m_textLine += m_textIncrement;

			// This version doesn't have a callback, but it doesn't skip the ignored shape
			b2RayResult result = b2World_CastRayClosest( m_worldId, m_rayStart, rayTranslation, b2DefaultQueryFilter() );

			if ( result.hit == true )
			{
				b2Vec2 c = b2MulAdd( m_rayStart, result.fraction, rayTranslation );
				g_draw.DrawPoint( result.point, 5.0f, color1 );
				g_draw.DrawSegment( m_rayStart, c, color2 );
				b2Vec2 head = b2MulAdd( result.point, 0.5f, result.normal );
				g_draw.DrawSegment( result.point, head, color3 );
			}
			else
			{
				g_draw.DrawSegment( m_rayStart, m_rayEnd, color2 );
			}
		}
		else
		{
			switch ( m_mode )
			{
				case e_any:
					g_draw.DrawString( 5, m_textLine, "Cast mode: any - check for obstruction - unsorted" );
					break;

				case e_closest:
					g_draw.DrawString( 5, m_textLine, "Cast mode: closest - find closest shape along the cast" );
					break;

				case e_multiple:
					g_draw.DrawString( 5, m_textLine, "Cast mode: multiple - gather up to 3 shapes - unsorted" );
					break;

				case e_sorted:
					g_draw.DrawString( 5, m_textLine, "Cast mode: sorted - gather up to 3 shapes sorted by closeness" );
					break;
			}

			m_textLine += m_textIncrement;

			b2CastResultFcn* fcns[] = { RayCastAnyCallback, RayCastClosestCallback, RayCastMultipleCallback,
										RayCastSortedCallback };
			b2CastResultFcn* modeFcn = fcns[m_mode];

			RayCastContext context = { 0 };

			// Must initialize fractions for sorting
			context.fractions[0] = FLT_MAX;
			context.fractions[1] = FLT_MAX;
			context.fractions[2] = FLT_MAX;

			b2Circle circle = { { 0.0f, 0.0f }, m_castRadius };
			b2Capsule capsule = { { -0.25f, 0.0f }, { 0.25f, 0.0f }, m_castRadius };
			b2Polygon box = b2MakeRoundedBox( 0.25f, 0.5f, m_castRadius );
			b2Transform transform = { m_rayStart, b2MakeRot( m_angle ) };

			switch ( m_castType )
			{
				case e_rayCast:
					b2World_CastRay( m_worldId, m_rayStart, rayTranslation, b2DefaultQueryFilter(), modeFcn, &context );
					break;

				case e_circleCast:
					b2World_CastCircle( m_worldId, &circle, transform, rayTranslation, b2DefaultQueryFilter(), modeFcn,
										&context );
					break;

				case e_capsuleCast:
					b2World_CastCapsule( m_worldId, &capsule, transform, rayTranslation, b2DefaultQueryFilter(), modeFcn,
										 &context );
					break;

				case e_polygonCast:
					b2World_CastPolygon( m_worldId, &box, transform, rayTranslation, b2DefaultQueryFilter(), modeFcn, &context );
					break;
			}

			if ( context.count > 0 )
			{
				assert( context.count <= 3 );
				b2HexColor colors[3] = { b2_colorRed, b2_colorGreen, b2_colorBlue };
				for ( int i = 0; i < context.count; ++i )
				{
					b2Vec2 c = b2MulAdd( m_rayStart, context.fractions[i], rayTranslation );
					b2Vec2 p = context.points[i];
					b2Vec2 n = context.normals[i];
					g_draw.DrawPoint( p, 5.0f, colors[i] );
					g_draw.DrawSegment( m_rayStart, c, color2 );
					b2Vec2 head = b2MulAdd( p, 0.5f, n );
					g_draw.DrawSegment( p, head, color3 );

					b2Vec2 t = b2MulSV( context.fractions[i], rayTranslation );

					if ( m_castType == e_circleCast )
					{
						g_draw.DrawCircle( b2Add( m_rayStart, t ), m_castRadius, b2_colorYellow );
					}
					else if ( m_castType == e_capsuleCast )
					{
						b2Vec2 p1 = b2Add( b2TransformPoint( transform, capsule.center1 ), t );
						b2Vec2 p2 = b2Add( b2TransformPoint( transform, capsule.center2 ), t );
						g_draw.DrawCapsule( p1, p2, m_castRadius, b2_colorYellow );
					}
					else if ( m_castType == e_polygonCast )
					{
						b2Transform shiftedTransform = { b2Add( transform.p, t ), transform.q };
						g_draw.DrawSolidPolygon( shiftedTransform, box.vertices, box.count, box.radius, b2_colorYellow );
					}
				}
			}
			else
			{
				g_draw.DrawSegment( m_rayStart, m_rayEnd, color2 );

				if ( m_castType == e_circleCast )
				{
					g_draw.DrawCircle( b2Add( m_rayStart, rayTranslation ), m_castRadius, b2_colorGray );
				}
				else if ( m_castType == e_capsuleCast )
				{
					b2Vec2 p1 = b2Add( b2TransformPoint( transform, capsule.center1 ), rayTranslation );
					b2Vec2 p2 = b2Add( b2TransformPoint( transform, capsule.center2 ), rayTranslation );
					g_draw.DrawCapsule( p1, p2, m_castRadius, b2_colorYellow );
				}
				else if ( m_castType == e_polygonCast )
				{
					b2Transform shiftedTransform = { b2Add( transform.p, rayTranslation ), transform.q };
					g_draw.DrawSolidPolygon( shiftedTransform, box.vertices, box.count, box.radius, b2_colorYellow );
				}
			}
		}

		g_draw.DrawPoint( m_rayStart, 5.0f, b2_colorGreen );

		if ( B2_IS_NON_NULL( m_bodyIds[m_ignoreIndex] ) )
		{
			b2Vec2 p = b2Body_GetPosition( m_bodyIds[m_ignoreIndex] );
			p.x -= 0.2f;
			g_draw.DrawString( p, "ign" );
		}
	}

	static Sample* Create( Settings& settings )
	{
		return new RayCastWorld( settings );
	}

	int m_bodyIndex;
	b2BodyId m_bodyIds[e_maxCount];
	ShapeUserData m_userData[e_maxCount];
	b2Polygon m_polygons[4];
	b2Capsule m_capsule;
	b2Circle m_circle;
	b2Segment m_segment;

	bool m_simple;

	int m_mode;
	int m_ignoreIndex;

	CastType m_castType;
	float m_castRadius;

	b2Vec2 m_angleAnchor;
	float m_baseAngle;
	float m_angle;
	bool m_rotating;

	b2Vec2 m_rayStart;
	b2Vec2 m_rayEnd;
	bool m_dragging;
};

static int sampleRayCastWorld = RegisterSample( "Collision", "Ray Cast World", RayCastWorld::Create );

class OverlapWorld : public Sample
{
public:
	enum
	{
		e_circleShape = 0,
		e_capsuleShape = 1,
		e_boxShape = 2
	};

	enum
	{
		e_maxCount = 64,
		e_maxDoomed = 16,
	};

	static bool OverlapResultFcn( b2ShapeId shapeId, void* context )
	{
		ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId );
		if ( userData != nullptr && userData->ignore )
		{
			// continue the query
			return true;
		}

		OverlapWorld* sample = (OverlapWorld*)context;

		if ( sample->m_doomCount < e_maxDoomed )
		{
			int index = sample->m_doomCount;
			sample->m_doomIds[index] = shapeId;
			sample->m_doomCount += 1;
		}

		// continue the query
		return true;
	}

	explicit OverlapWorld( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { 0.0f, 10.0f };
			g_camera.m_zoom = 25.0f * 0.7f;
		}

		{
			b2Vec2 vertices[3] = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, { 0.0f, 1.5f } };
			b2Hull hull = b2ComputeHull( vertices, 3 );
			m_polygons[0] = b2MakePolygon( &hull, 0.0f );
		}

		{
			b2Vec2 vertices[3] = { { -0.1f, 0.0f }, { 0.1f, 0.0f }, { 0.0f, 1.5f } };
			b2Hull hull = b2ComputeHull( vertices, 3 );
			m_polygons[1] = b2MakePolygon( &hull, 0.0f );
		}

		{
			float w = 1.0f;
			float b = w / ( 2.0f + sqrtf( 2.0f ) );
			float s = sqrtf( 2.0f ) * b;

			b2Vec2 vertices[8] = { { 0.5f * s, 0.0f }, { 0.5f * w, b },		 { 0.5f * w, b + s }, { 0.5f * s, w },
								   { -0.5f * s, w },   { -0.5f * w, b + s }, { -0.5f * w, b },	  { -0.5f * s, 0.0f } };

			b2Hull hull = b2ComputeHull( vertices, 8 );
			m_polygons[2] = b2MakePolygon( &hull, 0.0f );
		}

		m_polygons[3] = b2MakeBox( 0.5f, 0.5f );
		m_capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0f }, 0.25f };
		m_circle = { { 0.0f, 0.0f }, 0.5f };
		m_segment = { { -1.0f, 0.0f }, { 1.0f, 0.0f } };

		m_bodyIndex = 0;

		for ( int i = 0; i < e_maxCount; ++i )
		{
			m_bodyIds[i] = b2_nullBodyId;
		}

		m_ignoreIndex = 7;

		m_shapeType = e_circleShape;

		m_queryCircle = { { 0.0f, 0.0f }, 1.0f };
		m_queryCapsule = { { -1.0f, 0.0f }, { 1.0f, 0.0f }, 0.5f };
		m_queryBox = b2MakeBox( 2.0f, 0.5f );

		m_position = { 0.0f, 10.0f };
		m_angle = 0.0f;
		m_dragging = false;
		m_rotating = false;

		m_doomCount = 0;

		CreateN( 0, 10 );
	}

	void Create( int index )
	{
		if ( B2_IS_NON_NULL( m_bodyIds[m_bodyIndex] ) )
		{
			b2DestroyBody( m_bodyIds[m_bodyIndex] );
			m_bodyIds[m_bodyIndex] = b2_nullBodyId;
		}

		float x = RandomFloat( -20.0f, 20.0f );
		float y = RandomFloat( 0.0f, 20.0f );

		b2BodyDef bodyDef = b2DefaultBodyDef();
		bodyDef.position = { x, y };
		bodyDef.rotation = b2MakeRot( RandomFloat( -b2_pi, b2_pi ) );

		m_bodyIds[m_bodyIndex] = b2CreateBody( m_worldId, &bodyDef );

		b2ShapeDef shapeDef = b2DefaultShapeDef();
		shapeDef.userData = m_userData + m_bodyIndex;
		m_userData[m_bodyIndex].index = m_bodyIndex;
		m_userData[m_bodyIndex].ignore = false;
		if ( m_bodyIndex == m_ignoreIndex )
		{
			m_userData[m_bodyIndex].ignore = true;
		}

		if ( index < 4 )
		{
			b2CreatePolygonShape( m_bodyIds[m_bodyIndex], &shapeDef, m_polygons + index );
		}
		else if ( index == 4 )
		{
			b2CreateCircleShape( m_bodyIds[m_bodyIndex], &shapeDef, &m_circle );
		}
		else if ( index == 5 )
		{
			b2CreateCapsuleShape( m_bodyIds[m_bodyIndex], &shapeDef, &m_capsule );
		}
		else
		{
			b2CreateSegmentShape( m_bodyIds[m_bodyIndex], &shapeDef, &m_segment );
		}

		m_bodyIndex = ( m_bodyIndex + 1 ) % e_maxCount;
	}

	void CreateN( int index, int count )
	{
		for ( int i = 0; i < count; ++i )
		{
			Create( index );
		}
	}

	void DestroyBody()
	{
		for ( int i = 0; i < e_maxCount; ++i )
		{
			if ( B2_IS_NON_NULL( m_bodyIds[i] ) )
			{
				b2DestroyBody( m_bodyIds[i] );
				m_bodyIds[i] = b2_nullBodyId;
				return;
			}
		}
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			if ( mods == 0 && m_rotating == false )
			{
				m_dragging = true;
				m_position = p;
			}
			else if ( mods == GLFW_MOD_SHIFT && m_dragging == false )
			{
				m_rotating = true;
				m_startPosition = p;
				m_baseAngle = m_angle;
			}
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_dragging = false;
			m_rotating = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		if ( m_dragging )
		{
			m_position = p;
		}
		else if ( m_rotating )
		{
			float dx = p.x - m_startPosition.x;
			m_angle = m_baseAngle + 1.0f * dx;
		}
	}

	void UpdateUI() override
	{
		float height = 330.0f;
		ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
		ImGui::SetNextWindowSize( ImVec2( 140.0f, height ) );

		ImGui::Begin( "Overlap World", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize );

		if ( ImGui::Button( "Polygon 1" ) )
			Create( 0 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Poly1" ) )
			CreateN( 0, 10 );

		if ( ImGui::Button( "Polygon 2" ) )
			Create( 1 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Poly2" ) )
			CreateN( 1, 10 );

		if ( ImGui::Button( "Polygon 3" ) )
			Create( 2 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Poly3" ) )
			CreateN( 2, 10 );

		if ( ImGui::Button( "Box" ) )
			Create( 3 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Box" ) )
			CreateN( 3, 10 );

		if ( ImGui::Button( "Circle" ) )
			Create( 4 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Circle" ) )
			CreateN( 4, 10 );

		if ( ImGui::Button( "Capsule" ) )
			Create( 5 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Capsule" ) )
			CreateN( 5, 10 );

		if ( ImGui::Button( "Segment" ) )
			Create( 6 );
		ImGui::SameLine();
		if ( ImGui::Button( "10x##Segment" ) )
			CreateN( 6, 10 );

		if ( ImGui::Button( "Destroy Shape" ) )
		{
			DestroyBody();
		}

		ImGui::Separator();
		ImGui::Text( "Overlap Shape" );
		ImGui::RadioButton( "Circle##Overlap", &m_shapeType, e_circleShape );
		ImGui::RadioButton( "Capsule##Overlap", &m_shapeType, e_capsuleShape );
		ImGui::RadioButton( "Box##Overlap", &m_shapeType, e_boxShape );

		ImGui::End();
	}

	void Step( Settings& settings ) override
	{
		Sample::Step( settings );

		g_draw.DrawString( 5, m_textLine, "left mouse button: drag query shape" );
		m_textLine += m_textIncrement;
		g_draw.DrawString( 5, m_textLine, "left mouse button + shift: rotate query shape" );
		m_textLine += m_textIncrement;

		m_doomCount = 0;

		b2Transform transform = { m_position, b2MakeRot( m_angle ) };

		if ( m_shapeType == e_circleShape )
		{
			b2World_OverlapCircle( m_worldId, &m_queryCircle, transform, b2DefaultQueryFilter(), OverlapWorld::OverlapResultFcn,
								   this );
			g_draw.DrawCircle( transform.p, m_queryCircle.radius, b2_colorWhite );
		}
		else if ( m_shapeType == e_capsuleShape )
		{
			b2World_OverlapCapsule( m_worldId, &m_queryCapsule, transform, b2DefaultQueryFilter(), OverlapWorld::OverlapResultFcn,
									this );
			b2Vec2 p1 = b2TransformPoint( transform, m_queryCapsule.center1 );
			b2Vec2 p2 = b2TransformPoint( transform, m_queryCapsule.center2 );
			g_draw.DrawCapsule( p1, p2, m_queryCapsule.radius, b2_colorWhite );
		}
		else if ( m_shapeType == e_boxShape )
		{
			b2World_OverlapPolygon( m_worldId, &m_queryBox, transform, b2DefaultQueryFilter(), OverlapWorld::OverlapResultFcn,
									this );
			b2Vec2 points[b2_maxPolygonVertices] = { 0 };
			for ( int i = 0; i < m_queryBox.count; ++i )
			{
				points[i] = b2TransformPoint( transform, m_queryBox.vertices[i] );
			}
			g_draw.DrawPolygon( points, m_queryBox.count, b2_colorWhite );
		}

		if ( B2_IS_NON_NULL( m_bodyIds[m_ignoreIndex] ) )
		{
			b2Vec2 p = b2Body_GetPosition( m_bodyIds[m_ignoreIndex] );
			p.x -= 0.2f;
			g_draw.DrawString( p, "skip" );
		}

		for ( int i = 0; i < m_doomCount; ++i )
		{
			b2ShapeId shapeId = m_doomIds[i];
			ShapeUserData* userData = (ShapeUserData*)b2Shape_GetUserData( shapeId );
			if ( userData == nullptr )
			{
				continue;
			}

			int index = userData->index;
			assert( 0 <= index && index < e_maxCount );
			assert( B2_IS_NON_NULL( m_bodyIds[index] ) );

			b2DestroyBody( m_bodyIds[index] );
			m_bodyIds[index] = b2_nullBodyId;
		}
	}

	static Sample* Create( Settings& settings )
	{
		return new OverlapWorld( settings );
	}

	int m_bodyIndex;
	b2BodyId m_bodyIds[e_maxCount];
	ShapeUserData m_userData[e_maxCount];
	b2Polygon m_polygons[4];
	b2Capsule m_capsule;
	b2Circle m_circle;
	b2Segment m_segment;
	int m_ignoreIndex;

	b2ShapeId m_doomIds[e_maxDoomed];
	int m_doomCount;

	b2Circle m_queryCircle;
	b2Capsule m_queryCapsule;
	b2Polygon m_queryBox;

	int m_shapeType;
	b2Transform m_transform;

	b2Vec2 m_startPosition;

	b2Vec2 m_position;
	b2Vec2 m_basePosition;
	float m_angle;
	float m_baseAngle;

	bool m_dragging;
	bool m_rotating;
};

static int sampleOverlapWorld = RegisterSample( "Collision", "Overlap World", OverlapWorld::Create );

// Tests manifolds and contact points
class Manifold : public Sample
{
public:
	explicit Manifold( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			// g_camera.m_center = {1.8f, 15.0f};
			g_camera.m_center = { 1.8f, 0.0f };
			g_camera.m_zoom = 25.0f * 0.45f;
		}

		m_smgroxCache1 = b2_emptyDistanceCache;
		m_smgroxCache2 = b2_emptyDistanceCache;
		m_smgcapCache1 = b2_emptyDistanceCache;
		m_smgcapCache2 = b2_emptyDistanceCache;

		m_transform = b2Transform_identity;
		m_angle = 0.0f;
		m_round = 0.1f;

		m_startPoint = { 0.0f, 0.0f };
		m_basePosition = { 0.0f, 0.0f };
		m_baseAngle = 0.0f;

		m_dragging = false;
		m_rotating = false;
		m_showIds = false;
		m_showSeparation = false;
		m_showAnchors = false;
		m_enableCaching = true;

		b2Vec2 points[3] = { { -0.1f, -0.5f }, { 0.1f, -0.5f }, { 0.0f, 0.5f } };
		m_wedge = b2ComputeHull( points, 3 );
	}

	void UpdateUI() override
	{
		float height = 300.0f;
		ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
		ImGui::SetNextWindowSize( ImVec2( 240.0f, height ) );

		ImGui::Begin( "Manifold", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize );

		ImGui::SliderFloat( "x offset", &m_transform.p.x, -2.0f, 2.0f, "%.2f" );
		ImGui::SliderFloat( "y offset", &m_transform.p.y, -2.0f, 2.0f, "%.2f" );

		if ( ImGui::SliderFloat( "angle", &m_angle, -b2_pi, b2_pi, "%.2f" ) )
		{
			m_transform.q = b2MakeRot( m_angle );
		}

		ImGui::SliderFloat( "round", &m_round, 0.0f, 0.4f, "%.1f" );
		ImGui::Checkbox( "show ids", &m_showIds );
		ImGui::Checkbox( "show separation", &m_showSeparation );
		ImGui::Checkbox( "show anchors", &m_showAnchors );
		ImGui::Checkbox( "enable caching", &m_enableCaching );

		if ( ImGui::Button( "Reset" ) )
		{
			m_transform = b2Transform_identity;
			m_angle = 0.0f;
		}

		ImGui::Separator();

		ImGui::Text( "mouse button 1: drag" );
		ImGui::Text( "mouse button 1 + shift: rotate" );

		ImGui::End();
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			if ( mods == 0 && m_rotating == false )
			{
				m_dragging = true;
				m_startPoint = p;
				m_basePosition = m_transform.p;
			}
			else if ( mods == GLFW_MOD_SHIFT && m_dragging == false )
			{
				m_rotating = true;
				m_startPoint = p;
				m_baseAngle = m_angle;
			}
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_dragging = false;
			m_rotating = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		if ( m_dragging )
		{
			m_transform.p.x = m_basePosition.x + 0.5f * ( p.x - m_startPoint.x );
			m_transform.p.y = m_basePosition.y + 0.5f * ( p.y - m_startPoint.y );
		}
		else if ( m_rotating )
		{
			float dx = p.x - m_startPoint.x;
			m_angle = b2ClampFloat( m_baseAngle + 1.0f * dx, -b2_pi, b2_pi );
			m_transform.q = b2MakeRot( m_angle );
		}
	}

	void DrawManifold( const b2Manifold* manifold, b2Vec2 origin1, b2Vec2 origin2 )
	{
		for ( int i = 0; i < manifold->pointCount; ++i )
		{
			const b2ManifoldPoint* mp = manifold->points + i;

			b2Vec2 p1 = mp->point;
			b2Vec2 p2 = b2MulAdd( p1, 0.5f, manifold->normal );
			g_draw.DrawSegment( p1, p2, b2_colorWhite );

			if ( m_showAnchors )
			{
				g_draw.DrawPoint( b2Add( origin1, mp->anchorA ), 5.0f, b2_colorRed );
				g_draw.DrawPoint( b2Add( origin2, mp->anchorB ), 5.0f, b2_colorGreen );
			}
			else
			{
				g_draw.DrawPoint( p1, 5.0f, b2_colorBlue );
			}

			if ( m_showIds )
			{
				// uint32_t indexA = mp->id >> 8;
				// uint32_t indexB = 0xFF & mp->id;
				b2Vec2 p = { p1.x + 0.05f, p1.y - 0.02f };
				g_draw.DrawString( p, "0x%04x", mp->id );
			}

			if ( m_showSeparation )
			{
				b2Vec2 p = { p1.x + 0.05f, p1.y + 0.03f };
				g_draw.DrawString( p, "%.3f", mp->separation );
			}
		}
	}

	void Step( Settings& ) override
	{
		b2Vec2 offset = { -10.0f, -5.0f };
		b2Vec2 increment = { 4.0f, 0.0f };

		b2HexColor color1 = b2_colorAquamarine;
		b2HexColor color2 = b2_colorMagenta;

		if ( m_enableCaching == false )
		{
			m_smgroxCache1 = b2_emptyDistanceCache;
			m_smgroxCache2 = b2_emptyDistanceCache;
			m_smgcapCache1 = b2_emptyDistanceCache;
			m_smgcapCache2 = b2_emptyDistanceCache;
		}

		// circle-circle
		{
			b2Circle circle1 = { { 0.0f, 0.0f }, 0.5f };
			b2Circle circle2 = { { 0.0f, 0.0f }, 1.0f };

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollideCircles( &circle1, transform1, &circle2, transform2 );

			g_draw.DrawSolidCircle( transform1, circle1.center, circle1.radius, color1 );
			g_draw.DrawSolidCircle( transform2, circle2.center, circle2.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// capsule-circle
		{
			b2Capsule capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0 }, 0.25f };
			b2Circle circle = { { 0.0f, 0.0f }, 0.5f };

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollideCapsuleAndCircle( &capsule, transform1, &circle, transform2 );

			b2Vec2 v1 = b2TransformPoint( transform1, capsule.center1 );
			b2Vec2 v2 = b2TransformPoint( transform1, capsule.center2 );
			g_draw.DrawSolidCapsule( v1, v2, capsule.radius, color1 );

			g_draw.DrawSolidCircle( transform2, circle.center, circle.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// segment-circle
		{
			b2Segment segment = { { -1.0f, 0.0f }, { 1.0f, 0.0 } };
			b2Circle circle = { { 0.0f, 0.0f }, 0.5f };

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollideSegmentAndCircle( &segment, transform1, &circle, transform2 );

			b2Vec2 p1 = b2TransformPoint( transform1, segment.point1 );
			b2Vec2 p2 = b2TransformPoint( transform1, segment.point2 );
			g_draw.DrawSegment( p1, p2, color1 );

			g_draw.DrawSolidCircle( transform2, circle.center, circle.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// box-circle
		{
			b2Circle circle = { { 0.0f, 0.0f }, 0.5f };
			b2Polygon box = b2MakeSquare( 0.5f );
			box.radius = m_round;

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollidePolygonAndCircle( &box, transform1, &circle, transform2 );

			g_draw.DrawSolidPolygon( transform1, box.vertices, box.count, m_round, color1 );
			g_draw.DrawSolidCircle( transform2, circle.center, circle.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// capsule-capsule
		{
			b2Capsule capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0 }, 0.25f };

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollideCapsules( &capsule, transform1, &capsule, transform2 );

			b2Vec2 v1 = b2TransformPoint( transform1, capsule.center1 );
			b2Vec2 v2 = b2TransformPoint( transform1, capsule.center2 );
			g_draw.DrawSolidCapsule( v1, v2, capsule.radius, color1 );

			v1 = b2TransformPoint( transform2, capsule.center1 );
			v2 = b2TransformPoint( transform2, capsule.center2 );
			g_draw.DrawSolidCapsule( v1, v2, capsule.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// box-capsule
		{
			b2Capsule capsule = { { -0.4f, 0.0f }, { -0.1f, 0.0f }, 0.1f };
			b2Polygon box = b2MakeOffsetBox( 0.25f, 1.0f, { 1.0f, -1.0f }, 0.25f * b2_pi );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollidePolygonAndCapsule( &box, transform1, &capsule, transform2 );

			g_draw.DrawSolidPolygon( transform1, box.vertices, box.count, box.radius, color1 );

			b2Vec2 v1 = b2TransformPoint( transform2, capsule.center1 );
			b2Vec2 v2 = b2TransformPoint( transform2, capsule.center2 );
			g_draw.DrawSolidCapsule( v1, v2, capsule.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// segment-capsule
		{
			b2Segment segment = { { -1.0f, 0.0f }, { 1.0f, 0.0 } };
			b2Capsule capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0 }, 0.25f };

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollideSegmentAndCapsule( &segment, transform1, &capsule, transform2 );

			b2Vec2 p1 = b2TransformPoint( transform1, segment.point1 );
			b2Vec2 p2 = b2TransformPoint( transform1, segment.point2 );
			g_draw.DrawSegment( p1, p2, color1 );

			p1 = b2TransformPoint( transform2, capsule.center1 );
			p2 = b2TransformPoint( transform2, capsule.center2 );
			g_draw.DrawSolidCapsule( p1, p2, capsule.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		offset = { -10.0f, 0.0f };

		// box-box
		{
			b2Polygon box1 = b2MakeBox( 2.0f, 0.1f );
			b2Polygon box = b2MakeSquare( 0.25f );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };
			// b2Transform transform2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}};

			b2Manifold m = b2CollidePolygons( &box1, transform1, &box, transform2 );

			g_draw.DrawSolidPolygon( transform1, box1.vertices, box1.count, box1.radius, color1 );
			g_draw.DrawSolidPolygon( transform2, box.vertices, box.count, box.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// box-rox
		{
			b2Polygon box = b2MakeSquare( 0.5f );
			float h = 0.5f - m_round;
			b2Polygon rox = b2MakeRoundedBox( h, h, m_round );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };
			// b2Transform transform2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}};

			b2Manifold m = b2CollidePolygons( &box, transform1, &rox, transform2 );

			g_draw.DrawSolidPolygon( transform1, box.vertices, box.count, box.radius, color1 );
			g_draw.DrawSolidPolygon( transform2, rox.vertices, rox.count, rox.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// rox-rox
		{
			float h = 0.5f - m_round;
			b2Polygon rox = b2MakeRoundedBox( h, h, m_round );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };
			// b2Transform transform1 = {{6.48024225f, 2.07872653f}, {-0.938356698f, 0.345668465f}};
			// b2Transform transform2 = {{5.52862263f, 2.51146317f}, {-0.859374702f, -0.511346340f}};

			b2Manifold m = b2CollidePolygons( &rox, transform1, &rox, transform2 );

			g_draw.DrawSolidPolygon( transform1, rox.vertices, rox.count, rox.radius, color1 );
			g_draw.DrawSolidPolygon( transform2, rox.vertices, rox.count, rox.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// segment-rox
		{
			b2Segment segment = { { -1.0f, 0.0f }, { 1.0f, 0.0 } };
			float h = 0.5f - m_round;
			b2Polygon rox = b2MakeRoundedBox( h, h, m_round );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };
			// b2Transform transform2 = {b2Add({-1.44583416f, 0.397352695f}, offset), m_transform.q};

			b2Manifold m = b2CollideSegmentAndPolygon( &segment, transform1, &rox, transform2 );

			b2Vec2 p1 = b2TransformPoint( transform1, segment.point1 );
			b2Vec2 p2 = b2TransformPoint( transform1, segment.point2 );
			g_draw.DrawSegment( p1, p2, color1 );
			g_draw.DrawSolidPolygon( transform2, rox.vertices, rox.count, rox.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// wox-wox
		{
			b2Polygon wox = b2MakePolygon( &m_wedge, m_round );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };
			// b2Transform transform2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}};

			b2Manifold m = b2CollidePolygons( &wox, transform1, &wox, transform2 );

			g_draw.DrawSolidPolygon( transform1, wox.vertices, wox.count, wox.radius, color1 );
			g_draw.DrawSolidPolygon( transform1, wox.vertices, wox.count, 0.0f, color1 );
			g_draw.DrawSolidPolygon( transform2, wox.vertices, wox.count, wox.radius, color2 );
			g_draw.DrawSolidPolygon( transform2, wox.vertices, wox.count, 0.0f, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		// wox-wox
		{
			b2Vec2 p1s[3] = { { 0.175740838, 0.224936664 }, { -0.301293969, 0.194021404 }, { -0.105151534, -0.432157338 } };
			b2Vec2 p2s[3] = { { -0.427884758, -0.225028217 }, { 0.0566576123, -0.128772855 }, { 0.176625848, 0.338923335 } };

			b2Hull h1 = b2ComputeHull( p1s, 3 );
			b2Hull h2 = b2ComputeHull( p2s, 3 );
			b2Polygon w1 = b2MakePolygon( &h1, 0.158798501 );
			b2Polygon w2 = b2MakePolygon( &h2, 0.205900759 );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };
			// b2Transform transform2 = {b2Add({0.0f, -0.1f}, offset), {0.0f, 1.0f}};

			b2Manifold m = b2CollidePolygons( &w1, transform1, &w2, transform2 );

			g_draw.DrawSolidPolygon( transform1, w1.vertices, w1.count, w1.radius, color1 );
			g_draw.DrawSolidPolygon( transform1, w1.vertices, w1.count, 0.0f, color1 );
			g_draw.DrawSolidPolygon( transform2, w2.vertices, w2.count, w2.radius, color2 );
			g_draw.DrawSolidPolygon( transform2, w2.vertices, w2.count, 0.0f, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset = b2Add( offset, increment );
		}

		offset = { -10.0f, 5.0f };

		// smooth-segment vs circle
		{
			b2SmoothSegment segment = { { 2.0f, 1.0f }, { { 1.0f, 1.0f }, { -1.0f, 0.0f } }, { -2.0f, 0.0f }, -1 };
			b2Circle circle = { { 0.0f, 0.0f }, 0.5f };

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m = b2CollideSmoothSegmentAndCircle( &segment, transform1, &circle, transform2 );

			b2Vec2 g1 = b2TransformPoint( transform1, segment.ghost1 );
			b2Vec2 g2 = b2TransformPoint( transform1, segment.ghost2 );
			b2Vec2 p1 = b2TransformPoint( transform1, segment.segment.point1 );
			b2Vec2 p2 = b2TransformPoint( transform1, segment.segment.point2 );
			g_draw.DrawSegment( g1, p1, b2_colorLightGray );
			g_draw.DrawSegment( p1, p2, color1 );
			g_draw.DrawSegment( p2, g2, b2_colorLightGray );
			g_draw.DrawSolidCircle( transform2, circle.center, circle.radius, color2 );

			DrawManifold( &m, transform1.p, transform2.p );

			offset.x += 2.0f * increment.x;
		}

		// smooth-segment vs rounded polygon
		{
			b2SmoothSegment segment1 = { { 2.0f, 1.0f }, { { 1.0f, 1.0f }, { -1.0f, 0.0f } }, { -2.0f, 0.0f }, -1 };
			b2SmoothSegment segment2 = { { 3.0f, 1.0f }, { { 2.0f, 1.0f }, { 1.0f, 1.0f } }, { -1.0f, 0.0f }, -1 };
			// b2SmoothSegment segment1 = {{2.0f, 0.0f}, {{1.0f, 0.0f}, {-1.0f, 0.0f}}, {-2.0f, 0.0f}, -1};
			// b2SmoothSegment segment2 = {{3.0f, 0.0f}, {{2.0f, 0.0f}, {1.0f, 0.0f}}, {-1.0f, 0.0f}, -1};
			// b2SmoothSegment segment1 = {{0.5f, 1.0f}, {{0.0f, 2.0f}, {-0.5f, 1.0f}}, {-1.0f, 0.0f}, -1};
			// b2SmoothSegment segment2 = {{1.0f, 0.0f}, {{0.5f, 1.0f}, {0.0f, 2.0f}}, {-0.5f, 1.0f}, -1};
			float h = 0.5f - m_round;
			b2Polygon rox = b2MakeRoundedBox( h, h, m_round );

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m1 = b2CollideSmoothSegmentAndPolygon( &segment1, transform1, &rox, transform2, &m_smgroxCache1 );
			b2Manifold m2 = b2CollideSmoothSegmentAndPolygon( &segment2, transform1, &rox, transform2, &m_smgroxCache2 );

			{
				b2Vec2 g1 = b2TransformPoint( transform1, segment1.ghost1 );
				b2Vec2 g2 = b2TransformPoint( transform1, segment1.ghost2 );
				b2Vec2 p1 = b2TransformPoint( transform1, segment1.segment.point1 );
				b2Vec2 p2 = b2TransformPoint( transform1, segment1.segment.point2 );
				g_draw.DrawSegment( p1, p2, color1 );
				g_draw.DrawPoint( p1, 4.0f, color1 );
				g_draw.DrawPoint( p2, 4.0f, color1 );
				g_draw.DrawSegment( p2, g2, b2_colorLightGray );
			}

			{
				b2Vec2 g1 = b2TransformPoint( transform1, segment2.ghost1 );
				b2Vec2 g2 = b2TransformPoint( transform1, segment2.ghost2 );
				b2Vec2 p1 = b2TransformPoint( transform1, segment2.segment.point1 );
				b2Vec2 p2 = b2TransformPoint( transform1, segment2.segment.point2 );
				g_draw.DrawSegment( g1, p1, b2_colorLightGray );
				g_draw.DrawSegment( p1, p2, color1 );
				g_draw.DrawPoint( p1, 4.0f, color1 );
				g_draw.DrawPoint( p2, 4.0f, color1 );
			}

			g_draw.DrawSolidPolygon( transform2, rox.vertices, rox.count, rox.radius, color2 );
			g_draw.DrawPoint( b2TransformPoint( transform2, rox.centroid ), 5.0f, b2_colorGainsboro );

			DrawManifold( &m1, transform1.p, transform2.p );
			DrawManifold( &m2, transform1.p, transform2.p );

			offset.x += 2.0f * increment.x;
		}

		// smooth-segment vs capsule
		{
			b2SmoothSegment segment1 = { { 2.0f, 1.0f }, { { 1.0f, 1.0f }, { -1.0f, 0.0f } }, { -2.0f, 0.0f }, -1 };
			b2SmoothSegment segment2 = { { 3.0f, 1.0f }, { { 2.0f, 1.0f }, { 1.0f, 1.0f } }, { -1.0f, 0.0f }, -1 };
			b2Capsule capsule = { { -0.5f, 0.0f }, { 0.5f, 0.0 }, 0.25f };

			b2Transform transform1 = { offset, b2Rot_identity };
			b2Transform transform2 = { b2Add( m_transform.p, offset ), m_transform.q };

			b2Manifold m1 = b2CollideSmoothSegmentAndCapsule( &segment1, transform1, &capsule, transform2, &m_smgcapCache1 );
			b2Manifold m2 = b2CollideSmoothSegmentAndCapsule( &segment2, transform1, &capsule, transform2, &m_smgcapCache2 );

			{
				b2Vec2 g1 = b2TransformPoint( transform1, segment1.ghost1 );
				b2Vec2 g2 = b2TransformPoint( transform1, segment1.ghost2 );
				b2Vec2 p1 = b2TransformPoint( transform1, segment1.segment.point1 );
				b2Vec2 p2 = b2TransformPoint( transform1, segment1.segment.point2 );
				// g_draw.DrawSegment(g1, p1, b2_colorLightGray);
				g_draw.DrawSegment( p1, p2, color1 );
				g_draw.DrawPoint( p1, 4.0f, color1 );
				g_draw.DrawPoint( p2, 4.0f, color1 );
				g_draw.DrawSegment( p2, g2, b2_colorLightGray );
			}

			{
				b2Vec2 g1 = b2TransformPoint( transform1, segment2.ghost1 );
				b2Vec2 g2 = b2TransformPoint( transform1, segment2.ghost2 );
				b2Vec2 p1 = b2TransformPoint( transform1, segment2.segment.point1 );
				b2Vec2 p2 = b2TransformPoint( transform1, segment2.segment.point2 );
				g_draw.DrawSegment( g1, p1, b2_colorLightGray );
				g_draw.DrawSegment( p1, p2, color1 );
				g_draw.DrawPoint( p1, 4.0f, color1 );
				g_draw.DrawPoint( p2, 4.0f, color1 );
				// g_draw.DrawSegment(p2, g2, b2_colorLightGray);
			}

			b2Vec2 p1 = b2TransformPoint( transform2, capsule.center1 );
			b2Vec2 p2 = b2TransformPoint( transform2, capsule.center2 );
			g_draw.DrawSolidCapsule( p1, p2, capsule.radius, color2 );

			g_draw.DrawPoint( b2Lerp( p1, p2, 0.5f ), 5.0f, b2_colorGainsboro );

			DrawManifold( &m1, transform1.p, transform2.p );
			DrawManifold( &m2, transform1.p, transform2.p );

			offset.x += 2.0f * increment.x;
		}
	}

	static Sample* Create( Settings& settings )
	{
		return new Manifold( settings );
	}

	b2DistanceCache m_smgroxCache1;
	b2DistanceCache m_smgroxCache2;
	b2DistanceCache m_smgcapCache1;
	b2DistanceCache m_smgcapCache2;

	b2Hull m_wedge;

	b2Transform m_transform;
	float m_angle;
	float m_round;

	b2Vec2 m_basePosition;
	b2Vec2 m_startPoint;
	float m_baseAngle;

	bool m_dragging;
	bool m_rotating;
	bool m_showIds;
	bool m_showAnchors;
	bool m_showSeparation;
	bool m_enableCaching;
};

static int sampleManifoldIndex = RegisterSample( "Collision", "Manifold", Manifold::Create );

class SmoothManifold : public Sample
{
public:
	enum ShapeType
	{
		e_circleShape = 0,
		e_boxShape
	};

	explicit SmoothManifold( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { 2.0f, 20.0f };
			g_camera.m_zoom = 21.0f;
		}

		m_shapeType = e_boxShape;
		m_transform = { { 0.0f, 20.0f }, b2Rot_identity };
		m_angle = 0.0f;
		m_round = 0.0f;

		m_startPoint = { 0.0f, 00.0f };
		m_basePosition = { 0.0f, 0.0f };
		m_baseAngle = 0.0f;

		m_dragging = false;
		m_rotating = false;
		m_showIds = false;
		m_showAnchors = false;
		m_showSeparation = false;

		// https://betravis.github.io/shape-tools/path-to-polygon/
		m_count = 36;

		b2Vec2 points[36];
		points[0] = { -20.58325, 14.54175 };
		points[1] = { -21.90625, 15.8645 };
		points[2] = { -24.552, 17.1875 };
		points[3] = { -27.198, 11.89575 };
		points[4] = { -29.84375, 15.8645 };
		points[5] = { -29.84375, 21.15625 };
		points[6] = { -25.875, 23.802 };
		points[7] = { -20.58325, 25.125 };
		points[8] = { -25.875, 29.09375 };
		points[9] = { -20.58325, 31.7395 };
		points[10] = { -11.0089998, 23.2290001 };
		points[11] = { -8.67700005, 21.15625 };
		points[12] = { -6.03125, 21.15625 };
		points[13] = { -7.35424995, 29.09375 };
		points[14] = { -3.38549995, 29.09375 };
		points[15] = { 1.90625, 30.41675 };
		points[16] = { 5.875, 17.1875 };
		points[17] = { 11.16675, 25.125 };
		points[18] = { 9.84375, 29.09375 };
		points[19] = { 13.8125, 31.7395 };
		points[20] = { 21.75, 30.41675 };
		points[21] = { 28.3644981, 26.448 };
		points[22] = { 25.71875, 18.5105 };
		points[23] = { 24.3957481, 13.21875 };
		points[24] = { 17.78125, 11.89575 };
		points[25] = { 15.1355, 7.92700005 };
		points[26] = { 5.875, 9.25 };
		points[27] = { 1.90625, 11.89575 };
		points[28] = { -3.25, 11.89575 };
		points[29] = { -3.25, 9.9375 };
		points[30] = { -4.70825005, 9.25 };
		points[31] = { -8.67700005, 9.25 };
		points[32] = { -11.323, 11.89575 };
		points[33] = { -13.96875, 11.89575 };
		points[34] = { -15.29175, 14.54175 };
		points[35] = { -19.2605, 14.54175 };

		m_segments = (b2SmoothSegment*)malloc( m_count * sizeof( b2SmoothSegment ) );

		for ( int i = 0; i < m_count; ++i )
		{
			int i0 = i > 0 ? i - 1 : m_count - 1;
			int i1 = i;
			int i2 = i1 < m_count - 1 ? i1 + 1 : 0;
			int i3 = i2 < m_count - 1 ? i2 + 1 : 0;

			b2Vec2 g1 = points[i0];
			b2Vec2 p1 = points[i1];
			b2Vec2 p2 = points[i2];
			b2Vec2 g2 = points[i3];

			m_segments[i] = { g1, { p1, p2 }, g2, -1 };
		}
	}

	virtual ~SmoothManifold() override
	{
		free( m_segments );
	}

	void UpdateUI() override
	{
		float height = 290.0f;
		ImGui::SetNextWindowPos( ImVec2( 10.0f, g_camera.m_height - height - 50.0f ), ImGuiCond_Once );
		ImGui::SetNextWindowSize( ImVec2( 180.0f, height ) );

		ImGui::Begin( "Smooth Manifold", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize );
		ImGui::PushItemWidth( 100.0f );

		{
			const char* shapeTypes[] = { "Circle", "Box" };
			int shapeType = int( m_shapeType );
			ImGui::Combo( "Shape", &shapeType, shapeTypes, IM_ARRAYSIZE( shapeTypes ) );
			m_shapeType = ShapeType( shapeType );
		}

		ImGui::SliderFloat( "x Offset", &m_transform.p.x, -2.0f, 2.0f, "%.2f" );
		ImGui::SliderFloat( "y Offset", &m_transform.p.y, -2.0f, 2.0f, "%.2f" );

		if ( ImGui::SliderFloat( "Angle", &m_angle, -b2_pi, b2_pi, "%.2f" ) )
		{
			m_transform.q = b2MakeRot( m_angle );
		}

		ImGui::SliderFloat( "Round", &m_round, 0.0f, 0.4f, "%.1f" );
		ImGui::Checkbox( "Show Ids", &m_showIds );
		ImGui::Checkbox( "Show Separation", &m_showSeparation );
		ImGui::Checkbox( "Show Anchors", &m_showAnchors );

		if ( ImGui::Button( "Reset" ) )
		{
			m_transform = b2Transform_identity;
			m_angle = 0.0f;
		}

		ImGui::Separator();

		ImGui::Text( "mouse button 1: drag" );
		ImGui::Text( "mouse button 1 + shift: rotate" );

		ImGui::PopItemWidth();
		ImGui::End();
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			if ( mods == 0 && m_rotating == false )
			{
				m_dragging = true;
				m_startPoint = p;
				m_basePosition = m_transform.p;
			}
			else if ( mods == GLFW_MOD_SHIFT && m_dragging == false )
			{
				m_rotating = true;
				m_startPoint = p;
				m_baseAngle = m_angle;
			}
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_dragging = false;
			m_rotating = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		if ( m_dragging )
		{
			m_transform.p.x = m_basePosition.x + ( p.x - m_startPoint.x );
			m_transform.p.y = m_basePosition.y + ( p.y - m_startPoint.y );
		}
		else if ( m_rotating )
		{
			float dx = p.x - m_startPoint.x;
			m_angle = b2ClampFloat( m_baseAngle + 1.0f * dx, -b2_pi, b2_pi );
			m_transform.q = b2MakeRot( m_angle );
		}
	}

	void DrawManifold( const b2Manifold* manifold )
	{
		for ( int i = 0; i < manifold->pointCount; ++i )
		{
			const b2ManifoldPoint* mp = manifold->points + i;

			b2Vec2 p1 = mp->point;
			b2Vec2 p2 = b2MulAdd( p1, 0.5f, manifold->normal );
			g_draw.DrawSegment( p1, p2, b2_colorWhite );

			if ( m_showAnchors )
			{
				g_draw.DrawPoint( p1, 5.0f, b2_colorGreen );
			}
			else
			{
				g_draw.DrawPoint( p1, 5.0f, b2_colorGreen );
			}

			if ( m_showIds )
			{
				// uint32_t indexA = mp->id >> 8;
				// uint32_t indexB = 0xFF & mp->id;
				b2Vec2 p = { p1.x + 0.05f, p1.y - 0.02f };
				g_draw.DrawString( p, "0x%04x", mp->id );
			}

			if ( m_showSeparation )
			{
				b2Vec2 p = { p1.x + 0.05f, p1.y + 0.03f };
				g_draw.DrawString( p, "%.3f", mp->separation );
			}
		}
	}

	void Step( Settings& ) override
	{
		b2HexColor color1 = b2_colorYellow;
		b2HexColor color2 = b2_colorMagenta;

		b2Transform transform1 = b2Transform_identity;
		b2Transform transform2 = m_transform;

		for ( int i = 0; i < m_count; ++i )
		{
			const b2SmoothSegment* segment = m_segments + i;
			b2Vec2 p1 = b2TransformPoint( transform1, segment->segment.point1 );
			b2Vec2 p2 = b2TransformPoint( transform1, segment->segment.point2 );
			g_draw.DrawSegment( p1, p2, color1 );
			g_draw.DrawPoint( p1, 4.0f, color1 );
		}

		// smooth-segment vs circle
		if ( m_shapeType == e_circleShape )
		{
			b2Circle circle = { { 0.0f, 0.0f }, 0.5f };
			g_draw.DrawSolidCircle( transform2, circle.center, circle.radius, color2 );

			for ( int i = 0; i < m_count; ++i )
			{
				const b2SmoothSegment* segment = m_segments + i;
				b2Manifold m = b2CollideSmoothSegmentAndCircle( segment, transform1, &circle, transform2 );
				DrawManifold( &m );
			}
		}
		else if ( m_shapeType == e_boxShape )
		{
			float h = 0.5f - m_round;
			b2Polygon rox = b2MakeRoundedBox( h, h, m_round );
			g_draw.DrawSolidPolygon( transform2, rox.vertices, rox.count, rox.radius, color2 );

			for ( int i = 0; i < m_count; ++i )
			{
				const b2SmoothSegment* segment = m_segments + i;
				b2DistanceCache cache = {};
				b2Manifold m = b2CollideSmoothSegmentAndPolygon( segment, transform1, &rox, transform2, &cache );
				DrawManifold( &m );
			}
		}
	}

	static Sample* Create( Settings& settings )
	{
		return new SmoothManifold( settings );
	}

	ShapeType m_shapeType;

	b2SmoothSegment* m_segments;
	int m_count;

	b2Transform m_transform;
	float m_angle;
	float m_round;

	b2Vec2 m_basePosition;
	b2Vec2 m_startPoint;
	float m_baseAngle;

	bool m_dragging;
	bool m_rotating;
	bool m_showIds;
	bool m_showAnchors;
	bool m_showSeparation;
};

static int sampleSmoothManifoldIndex = RegisterSample( "Collision", "Smooth Manifold", SmoothManifold::Create );

class ShapeCast : public Sample
{
public:
	enum
	{
		e_vertexCount = 8
	};

	explicit ShapeCast( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { -1.5f, 1.0f };
			g_camera.m_zoom = 25.0f * 0.2f;
		}

#if 0
		// box swept against a triangle
		m_vAs[0] = {-0.5f, 1.0f};
		m_vAs[1] = {0.5f, 1.0f};
		m_vAs[2] = {0.0f, 0.0f};
		m_countA = 3;
		m_radiusA = 0.0f;

		m_vBs[0] = {-0.5f, -0.5f};
		m_vBs[1] = {0.5f, -0.5f};
		m_vBs[2] = {0.5f, 0.5f};
		m_vBs[3] = {-0.5f, 0.5f};
		m_countB = 4;
		m_radiusB = 0.0f;

		m_transformA.p = {0.0f, 0.25f};
		m_transformA.q = b2Rot_identity;
		m_transformB.p = {-4.0f, 0.0f};
		m_transformB.q = b2Rot_identity;
		m_translationB = {8.0f, 0.0f};
#elif 1
		// box swept against a segment
		m_vAs[0] = { -2.0f, 0.0f };
		m_vAs[1] = { 2.0f, 0.0f };
		m_countA = 2;
		m_radiusA = 0.0f;

		m_vBs[0] = { -0.25f, -0.25f };
		m_vBs[1] = { 0.25f, -0.25f };
		m_vBs[2] = { 0.25f, 0.25f };
		m_vBs[3] = { -0.25f, 0.25f };
		m_countB = 4;
		m_radiusB = 0.25f;

		m_transformA.p = { 0.0f, 0.0 };
		m_transformA.q = b2MakeRot( 0.25f * b2_pi );
		m_transformB.p = { -8.0f, 0.0f };
		m_transformB.q = b2Rot_identity;
		m_translationB = { 8.0f, 0.0f };
#elif 0
		// A point swept against a box
		m_vAs[0] = { -0.5f, -0.5f };
		m_vAs[1] = { 0.5f, -0.5f };
		m_vAs[2] = { 0.5f, 0.5f };
		m_vAs[3] = { -0.5f, 0.5f };
		m_countA = 4;
		m_radiusA = 0.0f;

		m_vBs[0] = { 0.0f, 0.0f };
		m_countB = 1;
		m_radiusB = 0.0f;

		m_transformA.p = { 0.0f, 0.0f };
		m_transformA.q = b2Rot_identity;
		m_transformB.p = { -1.0f, 0.0f };
		m_transformB.q = b2Rot_identity;
		m_translationB = { 1.0f, 0.0f };
#elif 0
		m_vAs[0] = { 0.0f, 0.0f };
		m_countA = 1;
		m_radiusA = 0.5f;

		m_vBs[0] = { 0.0f, 0.0f };
		m_countB = 1;
		m_radiusB = 0.5f;

		m_transformA.p = { 0.0f, 0.25f };
		m_transformA.q = b2Rot_identity;
		m_transformB.p = { -4.0f, 0.0f };
		m_transformB.q = b2Rot_identity;
		m_translationB = { 8.0f, 0.0f };
#else
		m_vAs[0] = { 0.0f, 0.0f };
		m_vAs[1] = { 2.0f, 0.0f };
		m_countA = 2;
		m_radiusA = 0.0f;

		m_vBs[0] = { 0.0f, 0.0f };
		m_countB = 1;
		m_radiusB = 0.25f;

		// Initial overlap
		m_transformA.p = b2Vec2_zero;
		m_transformA.q = b2Rot_identity;
		m_transformB.p = { -0.244360745f, 0.05999358f };
		m_transformB.q = b2Rot_identity;
		m_translationB = { 0.0f, 0.0399999991f };
#endif

		m_rayDrag = false;
	}

	static Sample* Create( Settings& settings )
	{
		return new ShapeCast( settings );
	}

	void MouseDown( b2Vec2 p, int button, int mods ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_transformB.p = p;
			m_rayDrag = true;
		}
	}

	void MouseUp( b2Vec2, int button ) override
	{
		if ( button == GLFW_MOUSE_BUTTON_1 )
		{
			m_rayDrag = false;
		}
	}

	void MouseMove( b2Vec2 p ) override
	{
		if ( m_rayDrag )
		{
			m_translationB = b2Sub( p, m_transformB.p );
		}
	}

	void Step( Settings& settings ) override
	{
		Sample::Step( settings );

		b2ShapeCastPairInput input = { 0 };
		input.proxyA = b2MakeProxy( m_vAs, m_countA, m_radiusA );
		input.proxyB = b2MakeProxy( m_vBs, m_countB, m_radiusB );
		input.transformA = m_transformA;
		input.transformB = m_transformB;
		input.translationB = m_translationB;
		input.maxFraction = 1.0f;

		b2CastOutput output = b2ShapeCast( &input );

		b2Transform transformB2;
		transformB2.q = m_transformB.q;
		transformB2.p = b2MulAdd( m_transformB.p, output.fraction, input.translationB );

		b2DistanceInput distanceInput;
		distanceInput.proxyA = b2MakeProxy( m_vAs, m_countA, m_radiusA );
		distanceInput.proxyB = b2MakeProxy( m_vBs, m_countB, m_radiusB );
		distanceInput.transformA = m_transformA;
		distanceInput.transformB = transformB2;
		distanceInput.useRadii = false;
		b2DistanceCache distanceCache;
		distanceCache.count = 0;
		b2DistanceOutput distanceOutput = b2ShapeDistance( &distanceCache, &distanceInput, nullptr, 0 );

		g_draw.DrawString( 5, m_textLine, "hit = %s, iters = %d, lambda = %g, distance = %g", output.hit ? "true" : "false",
						   output.iterations, output.fraction, distanceOutput.distance );
		m_textLine += m_textIncrement;

		b2Vec2 vertices[b2_maxPolygonVertices];

		for ( int i = 0; i < m_countA; ++i )
		{
			vertices[i] = b2TransformPoint( m_transformA, m_vAs[i] );
		}

		if ( m_countA == 1 )
		{
			if ( m_radiusA > 0.0f )
			{
				g_draw.DrawCircle( vertices[0], m_radiusA, b2_colorGray9 );
			}
			else
			{
				g_draw.DrawPoint( vertices[0], 5.0f, b2_colorGray9 );
			}
		}
		else
		{
			g_draw.DrawSolidPolygon( b2Transform_identity, vertices, m_countA, m_radiusA, b2_colorGray9 );
		}

		for ( int i = 0; i < m_countB; ++i )
		{
			vertices[i] = b2TransformPoint( m_transformB, m_vBs[i] );
		}

		if ( m_countB == 1 )
		{
			if ( m_radiusB > 0.0f )
			{
				g_draw.DrawCircle( vertices[0], m_radiusB, b2_colorGreen );
			}
			else
			{
				g_draw.DrawPoint( vertices[0], 5.0f, b2_colorGreen );
			}
		}
		else
		{
			g_draw.DrawSolidPolygon( b2Transform_identity, vertices, m_countB, m_radiusB, b2_colorGreen );
		}

		for ( int i = 0; i < m_countB; ++i )
		{
			vertices[i] = b2TransformPoint( transformB2, m_vBs[i] );
		}

		if ( m_countB == 1 )
		{
			if ( m_radiusB > 0.0f )
			{
				g_draw.DrawCircle( vertices[0], m_radiusB, b2_colorOrange );
			}
			else
			{
				g_draw.DrawPoint( vertices[0], 5.0f, b2_colorOrange );
			}
		}
		else
		{
			g_draw.DrawSolidPolygon( b2Transform_identity, vertices, m_countB, m_radiusB, b2_colorOrange );
		}

		if ( output.hit )
		{
			b2Vec2 p1 = output.point;
			g_draw.DrawPoint( p1, 10.0f, b2_colorRed );
			b2Vec2 p2 = b2MulAdd( p1, 1.0f, output.normal );
			g_draw.DrawSegment( p1, p2, b2_colorRed );
		}

		g_draw.DrawSegment( m_transformB.p, b2Add( m_transformB.p, m_translationB ), b2_colorGray );
	}

	b2Vec2 m_vAs[b2_maxPolygonVertices];
	int m_countA;
	float m_radiusA;

	b2Vec2 m_vBs[b2_maxPolygonVertices];
	int m_countB;
	float m_radiusB;

	b2Transform m_transformA;
	b2Transform m_transformB;
	b2Vec2 m_translationB;
	bool m_rayDrag;
};

static int sampleShapeCast = RegisterSample( "Collision", "Shape Cast", ShapeCast::Create );

class TimeOfImpact : public Sample
{
public:
	explicit TimeOfImpact( Settings& settings )
		: Sample( settings )
	{
		if ( settings.restart == false )
		{
			g_camera.m_center = { 0.6f, 2.0f };
			g_camera.m_zoom = 25.0f * 0.18f;
		}
	}

	static Sample* Create( Settings& settings )
	{
		return new TimeOfImpact( settings );
	}

	void Step( Settings& settings ) override
	{
		Sample::Step( settings );

		b2Sweep sweepA = { b2Vec2_zero, { 0.0f, 0.0f }, { 0.0f, 0.0f }, b2Rot_identity, b2Rot_identity };
		b2Sweep sweepB = { b2Vec2_zero, { 2.0f, 4.0f }, { 2.0f, 4.0f }, b2Rot_identity, b2MakeRot( -0.25f * b2_pi ) };

		b2TOIInput input;
		input.proxyA = b2MakeProxy( m_verticesA, m_countA, 0.0f );
		input.proxyB = b2MakeProxy( m_verticesB, m_countB, 0.0f );
		input.sweepA = sweepA;
		input.sweepB = sweepB;
		input.tMax = 1.0f;

		b2TOIOutput output = b2TimeOfImpact( &input );

		g_draw.DrawString( 5, m_textLine, "toi = %g", output.t );
		m_textLine += m_textIncrement;

		// g_draw.DrawString(5, m_textLine, "max toi iters = %d, max root iters = %d", b2_toiMaxIters,
		//                        b2_toiMaxRootIters);
		m_textLine += m_textIncrement;

		b2Vec2 vertices[b2_maxPolygonVertices];

		// Draw A
		b2Transform transformA = b2GetSweepTransform( &sweepA, 0.0f );
		for ( int i = 0; i < m_countA; ++i )
		{
			vertices[i] = b2TransformPoint( transformA, m_verticesA[i] );
		}
		g_draw.DrawPolygon( vertices, m_countA, b2_colorGray );

		// Draw B at t = 0
		b2Transform transformB = b2GetSweepTransform( &sweepB, 0.0f );
		for ( int i = 0; i < m_countB; ++i )
		{
			vertices[i] = b2TransformPoint( transformB, m_verticesB[i] );
		}
		g_draw.DrawPolygon( vertices, m_countB, b2_colorGreen );

		// Draw B at t = hit_time
		transformB = b2GetSweepTransform( &sweepB, output.t );
		for ( int i = 0; i < m_countB; ++i )
		{
			vertices[i] = b2TransformPoint( transformB, m_verticesB[i] );
		}
		g_draw.DrawPolygon( vertices, m_countB, b2_colorOrange );

		// Draw B at t = 1
		transformB = b2GetSweepTransform( &sweepB, 1.0f );
		for ( int i = 0; i < m_countB; ++i )
		{
			vertices[i] = b2TransformPoint( transformB, m_verticesB[i] );
		}
		g_draw.DrawPolygon( vertices, m_countB, b2_colorRed );

		if ( output.state == b2_toiStateHit )
		{
			b2DistanceInput dinput;
			dinput.proxyA = input.proxyA;
			dinput.proxyB = input.proxyB;
			dinput.transformA = b2GetSweepTransform( &sweepA, output.t );
			dinput.transformB = b2GetSweepTransform( &sweepB, output.t );
			dinput.useRadii = false;
			b2DistanceCache cache = { 0 };
			b2DistanceOutput doutput = b2ShapeDistance( &cache, &dinput, nullptr, 0 );
			g_draw.DrawString( 5, m_textLine, "distance = %g", doutput.distance );
			m_textLine += m_textIncrement;
		}

#if 0
		for (float t = 0.0f; t < 1.0f; t += 0.1f)
		{
			transformB = b2GetSweepTransform(&sweepB, t);
			for (int i = 0; i < m_countB; ++i)
			{
				vertices[i] = b2TransformPoint(transformB, m_verticesB[i]);
			}
			g_draw.DrawPolygon(vertices, m_countB, {0.3f, 0.3f, 0.3f});
		}
#endif
	}

	b2Vec2 m_verticesA[4] = { { -1.0f, -1.0f }, { 1.0f, -1.0f }, { 1.0f, 5.0f }, { -1.0f, 5.0f } };
	b2Vec2 m_verticesB[4] = { { -0.5f, -4.0f }, { 0.0f, -4.0f }, { 0.0f, 0.0f }, { -0.5f, 0.0f } };
	int m_countA = ARRAY_COUNT( m_verticesA );
	int m_countB = ARRAY_COUNT( m_verticesB );
};

static int sampleTimeOfImpact = RegisterSample( "Collision", "Time of Impact", TimeOfImpact::Create );
