diff --git a/tests/cpp-tests/CMakeLists.txt b/tests/cpp-tests/CMakeLists.txt index dc5d345b8c..3a3029b153 100644 --- a/tests/cpp-tests/CMakeLists.txt +++ b/tests/cpp-tests/CMakeLists.txt @@ -358,13 +358,87 @@ if(WINDOWS OR MACOSX OR LINUX) endif() list(APPEND GAME_HEADER - Classes/Box2DTest/Box2dTest.h + Classes/Box2DTest/Box2dTest.h + ) + +if(WINDOWS) + list(APPEND GAME_HEADER + Classes/Box2DTestBed/Test.h + Classes/Box2DTestBed/Box2DTestBed.h + Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.h ) +endif() list(APPEND GAME_SOURCE - Classes/Box2DTest/Box2dTest.cpp - ) + Classes/Box2DTest/Box2dTest.cpp +) +if(WINDOWS) + list(APPEND GAME_HEADER + Classes/Box2DTestBed/Box2DTestBed.cpp + Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.cpp + Classes/Box2DTestBed/Test.cpp + + Classes/Box2DTestBed/tests/add_pair.cpp + Classes/Box2DTestBed/tests/apply_force.cpp + Classes/Box2DTestBed/tests/body_types.cpp + Classes/Box2DTestBed/tests/box_stack.cpp + Classes/Box2DTestBed/tests/breakable.cpp + Classes/Box2DTestBed/tests/bridge.cpp + Classes/Box2DTestBed/tests/bullet_test.cpp + Classes/Box2DTestBed/tests/cantilever.cpp + Classes/Box2DTestBed/tests/car.cpp + Classes/Box2DTestBed/tests/chain.cpp + Classes/Box2DTestBed/tests/chain_problem.cpp + Classes/Box2DTestBed/tests/character_collision.cpp + Classes/Box2DTestBed/tests/circle_stack.cpp + Classes/Box2DTestBed/tests/collision_filtering.cpp + Classes/Box2DTestBed/tests/collision_processing.cpp + Classes/Box2DTestBed/tests/compound_shapes.cpp + Classes/Box2DTestBed/tests/confined.cpp + Classes/Box2DTestBed/tests/continuous_test.cpp + Classes/Box2DTestBed/tests/convex_hull.cpp + Classes/Box2DTestBed/tests/conveyor_belt.cpp + Classes/Box2DTestBed/tests/distance_joint.cpp + Classes/Box2DTestBed/tests/distance_test.cpp + Classes/Box2DTestBed/tests/dominos.cpp + Classes/Box2DTestBed/tests/dump_loader.cpp + Classes/Box2DTestBed/tests/dynamic_tree.cpp + Classes/Box2DTestBed/tests/edge_shapes.cpp + Classes/Box2DTestBed/tests/edge_test.cpp + Classes/Box2DTestBed/tests/friction.cpp + Classes/Box2DTestBed/tests/gear_joint.cpp + Classes/Box2DTestBed/tests/heavy1.cpp + Classes/Box2DTestBed/tests/heavy2.cpp + Classes/Box2DTestBed/tests/mobile_balanced.cpp + Classes/Box2DTestBed/tests/mobile_unbalanced.cpp + Classes/Box2DTestBed/tests/motor_joint.cpp + Classes/Box2DTestBed/tests/pinball.cpp + Classes/Box2DTestBed/tests/platformer.cpp + Classes/Box2DTestBed/tests/polygon_collision.cpp + Classes/Box2DTestBed/tests/polygon_shapes.cpp + Classes/Box2DTestBed/tests/prismatic_joint.cpp + Classes/Box2DTestBed/tests/pulley_joint.cpp + Classes/Box2DTestBed/tests/pyramid.cpp + Classes/Box2DTestBed/tests/ray_cast.cpp + Classes/Box2DTestBed/tests/restitution.cpp + Classes/Box2DTestBed/tests/revolute_joint.cpp + Classes/Box2DTestBed/tests/rope.cpp + Classes/Box2DTestBed/tests/sensor.cpp + Classes/Box2DTestBed/tests/shape_cast.cpp + Classes/Box2DTestBed/tests/shape_editing.cpp + Classes/Box2DTestBed/tests/skier.cpp + Classes/Box2DTestBed/tests/slider_crank_1.cpp + Classes/Box2DTestBed/tests/slider_crank_2.cpp + Classes/Box2DTestBed/tests/theo_jansen.cpp + Classes/Box2DTestBed/tests/tiles.cpp + Classes/Box2DTestBed/tests/time_of_impact.cpp + Classes/Box2DTestBed/tests/tumbler.cpp + Classes/Box2DTestBed/tests/web.cpp + Classes/Box2DTestBed/tests/wheel_joint.cpp + Classes/Box2DTestBed/tests/wrecking_ball.cpp + ) +endif() list(APPEND GAME_HEADER Classes/Physics3DTest/Physics3DTest.h Classes/NavMeshTest/NavMeshTest.h diff --git a/tests/cpp-tests/Classes/Box2DTestBed/Box2DTestBed.cpp b/tests/cpp-tests/Classes/Box2DTestBed/Box2DTestBed.cpp new file mode 100644 index 0000000000..23d61499cf --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/Box2DTestBed.cpp @@ -0,0 +1,280 @@ +/**************************************************************************** + * Copyright (c) 2021 @aismann; Peter Eismann, Germany; dreifrankensoft + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "Box2DTestBed.h" +#include "CCPhysicsDebugNodeBox2D.h" +#include "test.h" + //#include "renderer/CCRenderer.h" + +USING_NS_CC; + +#define kAccelerometerFrequency 30 +#define FRAMES_BETWEEN_PRESSES_FOR_DOUBLE_CLICK 10 + +extern int g_testCount; + +Settings settings; + + +enum +{ + kTagBox2DNode, +}; + +extern cocos2d::DrawNode* drawBox2D; + + +Box2DTestBedTests::Box2DTestBedTests() +{ + for (int entryId = 0; entryId < g_testCount; ++entryId) + { + addTestCase(g_testEntries[entryId].name, [entryId]() { + return Box2DTestBed::createWithEntryID(entryId); + }); + } +} + +//------------------------------------------------------------------ +// +// Box2dTestBed +// +//------------------------------------------------------------------ + +Box2DTestBed::Box2DTestBed() +{ + +} + +Box2DTestBed::~Box2DTestBed() +{ + _eventDispatcher->removeEventListener(_touchListener); +} + +Box2DTestBed* Box2DTestBed::createWithEntryID(int entryId) +{ + auto layer = new (std::nothrow) Box2DTestBed(); + layer->initWithEntryID(entryId); + layer->autorelease(); + + return layer; +} + +bool Box2DTestBed::initWithEntryID(int entryId) +{ + if (!TestCase::init()) + { + return false; + } + auto director = Director::getInstance(); + Vec2 visibleOrigin = director->getVisibleOrigin(); + Size visibleSize = director->getVisibleSize(); + + m_entryID = entryId; + + Box2DView* view = Box2DView::viewWithEntryID(entryId); + addChild(view, 0, kTagBox2DNode); + view->setScale(15); + view->setAnchorPoint(Vec2(0, 0)); + view->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 3); + auto label = Label::createWithTTF(view->title().c_str(), "fonts/arial.ttf", 28); + addChild(label, 1); + label->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height - 50); + + // Adds touch event listener + auto listener = EventListenerTouchOneByOne::create(); + listener->setSwallowTouches(true); + + listener->onTouchBegan = CC_CALLBACK_2(Box2DTestBed::onTouchBegan, this); + listener->onTouchMoved = CC_CALLBACK_2(Box2DTestBed::onTouchMoved, this); + + _eventDispatcher->addEventListenerWithFixedPriority(listener, 1); + + _touchListener = listener; + + addChild(drawBox2D, 100); + //this->createResetButton(); + + return true; +} + +bool Box2DTestBed::onTouchBegan(Touch* touch, Event* event) +{ + return true; +} + +void Box2DTestBed::onTouchMoved(Touch* touch, Event* event) +{ + auto diff = touch->getDelta(); + auto node = getChildByTag(kTagBox2DNode); + auto currentPos = node->getPosition(); + node->setPosition(currentPos + diff); +} + +//void Box2DTestBed::createResetButton() { +// auto reset = MenuItemImage::create("Images/r1.png", "Images/r2.png", CC_CALLBACK_1(Box2DTestBed::reset, this)); +// auto menu = Menu::create(reset, nullptr); +// menu->setPosition(VisibleRect::center().x, VisibleRect::bottom().y + 40); +// this->addChild(menu, -1); +//} +// +//void Box2DTestBed::reset(Ref* sender) { +// getTestSuite()->restartCurrTest(); +//} +// +//void Box2DTestBed::onEnter() { +// TestCase::onEnter(); +// // physicsDebugNodeOffset = VisibleRect::center(); +// //physicsDebugNodeOffset.y += 20; +// //ChipmunkDemoMessageString = ""; +// //label->setString(""); +//} + +//------------------------------------------------------------------ +// +// Box2DView +// +//------------------------------------------------------------------ +Box2DView::Box2DView(void) +{ +} + +Box2DView* Box2DView::viewWithEntryID(int entryId) +{ + Box2DView* pView = new (std::nothrow) Box2DView(); + + pView->initWithEntryID(entryId); + pView->autorelease(); + + return pView; +} + +bool Box2DView::initWithEntryID(int entryId) +{ + m_entry = g_testEntries + entryId; + m_test = m_entry->createFcn(); + + + // Adds Touch Event Listener + auto listener = EventListenerTouchOneByOne::create(); + listener->setSwallowTouches(true); + + listener->onTouchBegan = CC_CALLBACK_2(Box2DView::onTouchBegan, this); + listener->onTouchMoved = CC_CALLBACK_2(Box2DView::onTouchMoved, this); + listener->onTouchEnded = CC_CALLBACK_2(Box2DView::onTouchEnded, this); + + _eventDispatcher->addEventListenerWithFixedPriority(listener, -10); + _touchListener = listener; + + auto keyboardListener = EventListenerKeyboard::create(); + keyboardListener->onKeyPressed = CC_CALLBACK_2(Box2DView::onKeyPressed, this); + keyboardListener->onKeyReleased = CC_CALLBACK_2(Box2DView::onKeyReleased, this); + _eventDispatcher->addEventListenerWithFixedPriority(keyboardListener, -11); + _keyboardListener = keyboardListener; + + return true; +} + +std::string Box2DView::title() const +{ + std::string title = std::string(m_entry->category) + std::string(":") + std::string(m_entry->name); + return title; +} + + +void Box2DView::draw(Renderer* renderer, const Mat4& transform, uint32_t flags) +{ + Layer::draw(renderer, transform, flags); + + _customCmd.init(_globalZOrder, transform, flags); + _customCmd.func = CC_CALLBACK_0(Box2DView::onDraw, this, transform, flags); + renderer->addCommand(&_customCmd); + Director* director = Director::getInstance(); +} + +void Box2DView::onDraw(const Mat4& transform, uint32_t flags) +{ + Director* director = Director::getInstance(); + CCASSERT(nullptr != director, "Director is null when setting matrix stack"); + director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); + director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, transform); + + // GL::enableVertexAttribs( cocos2d::GL::VERTEX_ATTRIB_FLAG_POSITION ); + + drawBox2D->clear(); + m_test->Step(&settings); + m_test->m_world->DebugDraw(); + CHECK_GL_ERROR_DEBUG(); + + director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); +} + +Box2DView::~Box2DView() +{ + // Removes Touch Event Listener + _eventDispatcher->removeEventListener(_touchListener); + _eventDispatcher->removeEventListener(_keyboardListener); + delete m_test; +} + +bool Box2DView::onTouchBegan(Touch* touch, Event* event) +{ + auto touchLocation = touch->getLocation(); + + auto nodePosition = convertToNodeSpace(touchLocation); + log("Box2DView::onTouchBegan, pos: %f,%f -> %f,%f", touchLocation.x, touchLocation.y, nodePosition.x, nodePosition.y); + + return m_test->MouseDown(b2Vec2(nodePosition.x, nodePosition.y)); +} + +void Box2DView::onTouchMoved(Touch* touch, Event* event) +{ + auto touchLocation = touch->getLocation(); + auto nodePosition = convertToNodeSpace(touchLocation); + + log("Box2DView::onTouchMoved, pos: %f,%f -> %f,%f", touchLocation.x, touchLocation.y, nodePosition.x, nodePosition.y); + + m_test->MouseMove(b2Vec2(nodePosition.x, nodePosition.y)); +} + +void Box2DView::onTouchEnded(Touch* touch, Event* event) +{ + auto touchLocation = touch->getLocation(); + auto nodePosition = convertToNodeSpace(touchLocation); + + log("Box2DView::onTouchEnded, pos: %f,%f -> %f,%f", touchLocation.x, touchLocation.y, nodePosition.x, nodePosition.y); + + m_test->MouseUp(b2Vec2(nodePosition.x, nodePosition.y)); +} + +void Box2DView::onKeyPressed(EventKeyboard::KeyCode code, Event* event) +{ + log("Box2dView:onKeyPressed, keycode: %d", static_cast(code)); + m_test->Keyboard(static_cast(code)); +} + +void Box2DView::onKeyReleased(EventKeyboard::KeyCode code, Event* event) +{ + log("onKeyReleased, keycode: %d", static_cast(code)); + m_test->KeyboardUp(static_cast(code)); +} diff --git a/tests/cpp-tests/Classes/Box2DTestBed/Box2DTestBed.h b/tests/cpp-tests/Classes/Box2DTestBed/Box2DTestBed.h new file mode 100644 index 0000000000..7ed0a8df80 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/Box2DTestBed.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * Copyright (c) 2021 @aismann; Peter Eismann, Germany; dreifrankensoft + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#ifndef _BOX2D_VIEW_H_ +#define _BOX2D_VIEW_H_ + +#include "../BaseTest.h" +#include "renderer/CCCustomCommand.h" + +DEFINE_TEST_SUITE(Box2DTestBedTests); + +class Box2DTestBed : public TestCase +{ +public: + static Box2DTestBed* createWithEntryID(int entryId); + + Box2DTestBed(); + virtual ~Box2DTestBed(); + + //void onEnter() override; + //void createResetButton(); + //void reset(cocos2d::Ref* sender); + + bool initWithEntryID(int entryId); + + bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event); + void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event); + + cocos2d::DrawNode* draw = NULL; +private: + int m_entryID; + cocos2d::EventListenerTouchOneByOne* _touchListener; +}; + +struct TestEntry; +class Test; +class Box2DView : public cocos2d::Layer +{ + cocos2d::EventListenerTouchOneByOne* _touchListener; + cocos2d::EventListenerKeyboard* _keyboardListener; + TestEntry* m_entry; + Test* m_test; + int m_entryID; + +public: + Box2DView(void); + virtual ~Box2DView(void); + + bool initWithEntryID(int entryId); + std::string title() const; + virtual void draw(cocos2d::Renderer* renderer, const cocos2d::Mat4& transform, uint32_t flags) override; + + // virtual void registerWithTouchDispatcher(); + bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event)override; + void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event)override; + void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event)override; + + void onKeyPressed(cocos2d::EventKeyboard::KeyCode code, cocos2d::Event* event)override; + void onKeyReleased(cocos2d::EventKeyboard::KeyCode code, cocos2d::Event* event)override; + //virtual void accelerometer(UIAccelerometer* accelerometer, cocos2d::Acceleration* acceleration); + + static Box2DView* viewWithEntryID(int entryId); + + + +protected: + void onDraw(const cocos2d::Mat4& transform, uint32_t flags); + + cocos2d::CallbackCommand _customCmd; +}; + +#endif diff --git a/tests/cpp-tests/Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.cpp b/tests/cpp-tests/Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.cpp new file mode 100644 index 0000000000..f24c001d4e --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2021 @aismann; Peter Eismann, Germany; dreifrankensoft + * + * + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "CCPhysicsDebugNodeBox2D.h" +#include "cocos2d.h" +#include +#include +#include +#include "extensions/cocos-ext.h" + +USING_NS_CC; + +cocos2d::DrawNode* drawBox2D; + +// +//// halx99: since adxe init scene default camera at 'initWithXXX' function, only change design size at scene +//// construct is ok see also: https://github.com/adxeproject/adxe/commit/581a7921554c09746616759d5a5ca6ce9d3eaa22 +//auto director = Director::getInstance(); +//auto glview = director->getOpenGLView(); +//Size designSize(960 * 0.85, 640 * 0.85); +//glview->setDesignResolutionSize(designSize.width, designSize.height, ResolutionPolicy::NO_BORDER); +cocos2d::Vec2 physicsDebugNodeOffset = { 260, 70 }; + +GLESDebugDraw::GLESDebugDraw() + : mRatio( 10.0f ) +{ + this->initShader(); + drawBP = DrawNode::create(); + drawBox2D = drawBP; + +} + +GLESDebugDraw::GLESDebugDraw(float ratio ) + : mRatio( ratio ) +{ + this->initShader(); +} + +void GLESDebugDraw::initShader( void ) +{ + //mShaderProgram = GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_U_COLOR); + //mColorLocation = glGetUniformLocation( mShaderProgram->getProgram(), "u_color"); +} + +void GLESDebugDraw::DrawPolygon(const b2Vec2* verts, int vertexCount, const b2Color& color) +{ + if (!drawBP) return; + + Vec2* vec = new (std::nothrow) Vec2[vertexCount]; + for (size_t i = 0; i < vertexCount; i++) { + vec[i] = Vec2(verts[i].x * mRatio, verts[i].y * mRatio) + physicsDebugNodeOffset; + } + drawBP->drawPolygon(vec, vertexCount, Color4F(color.r, color.g, color.b, color.a), 1, Color4F(color.r, color.g, color.b, color.a)); +} + +void GLESDebugDraw::DrawSolidPolygon(const b2Vec2* verts, int vertexCount, const b2Color& color) +{ + if (!drawBP) return; + + Vec2* vec = new (std::nothrow) Vec2[vertexCount]; + for (size_t i = 0; i < vertexCount; i++) { + vec[i] = Vec2(verts[i].x * mRatio, verts[i].y * mRatio) + physicsDebugNodeOffset; + } + drawBP->drawPolygon(vec, vertexCount, Color4F(color.r / 2, color.g / 2, color.b / 2, color.a), 0.2f, Color4F(color.r, color.g, color.b, color.a)); + //drawBP->drawSolidPoly(vec, vertexCount, Color4F(color.r, color.g, color.b, color.a)); +} + +void GLESDebugDraw::DrawCircle(const b2Vec2& center, float radius, const b2Color& color) +{ + if (!drawBP) return; + + drawBP->drawCircle(Vec2(center.x * mRatio, center.y * mRatio) + physicsDebugNodeOffset, radius* mRatio, CC_DEGREES_TO_RADIANS(0), 30, true, 1.0f, + 1.0f, Color4F(color.r, color.g, color.b, color.a)); +} + +void GLESDebugDraw::DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color) +{ + if (!drawBP) return; + + // DrawSolidCircle Maybe have to fix later + drawBP->drawCircle(Vec2(center.x * mRatio, center.y * mRatio) + physicsDebugNodeOffset, radius* mRatio, CC_DEGREES_TO_RADIANS(0), 20, true, 1.0f, 1.0f, Color4F(color.r, color.g, color.b, color.a)); +} + +void GLESDebugDraw::DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color) +{ + if (!drawBP) return; + + drawBP->drawLine(Vec2(p1.x * mRatio, p1.y * mRatio) + physicsDebugNodeOffset, Vec2(p2.x * mRatio, p2.y * mRatio) + physicsDebugNodeOffset, Color4F(color.r, color.g, color.b, color.a)); +} + +void GLESDebugDraw::DrawTransform(const b2Transform& xf) +{ + CCLOG("======== DrawTransform ==========="); + + b2Vec2 p1 = xf.p, p2; + const float k_axisScale = 0.4f; + p2 = p1 + k_axisScale * xf.q.GetXAxis(); + DrawSegment(p1, p2, b2Color(1,0,0)); + + p2 = p1 + k_axisScale * xf.q.GetYAxis(); + DrawSegment(p1,p2,b2Color(0,1,0)); +} + +void GLESDebugDraw::DrawPoint(const b2Vec2& p, float size, const b2Color& color) +{ + if (!drawBP) return; + + drawBP->drawPoint(Vec2(p.x * mRatio, p.y * mRatio) + physicsDebugNodeOffset, size, Color4F(color.r, color.g, color.b, color.a)); +} + +void GLESDebugDraw::DrawString(int x, int y, const char *string, ...) +{ +// NSLog(@"DrawString: unsupported: %s", string); + //printf(string); + /* Unsupported as yet. Could replace with bitmap font renderer at a later date */ +} + +void GLESDebugDraw::DrawAABB(b2AABB* aabb, const b2Color& color) +{ + CCLOG("======== DrawAABB ==========="); + if (!drawBP) return; + + b2Vec2 p1 = aabb->lowerBound; + b2Vec2 p2 = b2Vec2(aabb->upperBound.x, aabb->lowerBound.y); + b2Vec2 p3 = aabb->upperBound; + b2Vec2 p4 = b2Vec2(aabb->lowerBound.x, aabb->upperBound.y); + + Vec2 verts[] = { + Vec2(p1.x * mRatio, p1.y * mRatio) + physicsDebugNodeOffset , + Vec2(p2.x * mRatio, p2.y * mRatio) + physicsDebugNodeOffset , + Vec2(p3.x * mRatio, p3.y * mRatio) + physicsDebugNodeOffset , + Vec2(p4.x * mRatio, p4.y * mRatio) + physicsDebugNodeOffset , + }; + drawBP->drawSolidPoly(verts, sizeof(verts) / sizeof(verts[0]), Color4F(color.r, color.g, color.b, color.a)); +} diff --git a/tests/cpp-tests/Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.h b/tests/cpp-tests/Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.h new file mode 100644 index 0000000000..226ae8d293 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/CCPhysicsDebugNodeBox2D.h @@ -0,0 +1,65 @@ +/* +* Copyright (c) 2021 @aismann; Peter Eismann, Germany; dreifrankensoft +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef __PHYSICSNODES_DEBUGNODE_BOX2D_H__ +#define __PHYSICSNODES_DEBUGNODE_BOX2D_H__ + +#include "Box2D/Box2D.h" +#include "cocos2d.h" + +struct b2AABB; + +extern cocos2d::DrawNode* drawBox2D; + +// This class implements debug drawing callbacks that are invoked +// inside b2World::Step. +class GLESDebugDraw : public b2Draw +{ + float mRatio; + // cocos2d::g* mShaderProgram; + GLint mColorLocation; + + cocos2d::DrawNode* drawBP = NULL; + + void initShader( void ); +public: + GLESDebugDraw(); + + GLESDebugDraw(float ratio ); + + virtual void DrawPolygon(const b2Vec2* vertices, int vertexCount, const b2Color& color); + + virtual void DrawSolidPolygon(const b2Vec2* vertices, int vertexCount, const b2Color& color); + + virtual void DrawCircle(const b2Vec2& center, float radius, const b2Color& color); + + virtual void DrawSolidCircle(const b2Vec2& center, float radius, const b2Vec2& axis, const b2Color& color); + + virtual void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color); + + virtual void DrawTransform(const b2Transform& xf); + + virtual void DrawPoint(const b2Vec2& p, float size, const b2Color& color); + + virtual void DrawString(int x, int y, const char* string, ...); + + virtual void DrawAABB(b2AABB* aabb, const b2Color& color); +}; + + +#endif diff --git a/tests/cpp-tests/Classes/Box2DTestBed/Test.cpp b/tests/cpp-tests/Classes/Box2DTestBed/Test.cpp new file mode 100644 index 0000000000..0f002b8c9c --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/Test.cpp @@ -0,0 +1,478 @@ +/* +* Copyright (c) 2006-2009 Erin Catto http://www.box2d.org +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "test.h" +#include "CCPhysicsDebugNodeBox2D.h" + +#include + +void DestructionListener::SayGoodbye(b2Joint* joint) +{ + if (test->m_mouseJoint == joint) + { + test->m_mouseJoint = nullptr; + } + else + { + test->JointDestroyed(joint); + } +} + +Test::Test() +{ + b2Vec2 gravity; + gravity.Set(0.0f, -10.0f); + m_world = new b2World(gravity); + m_bomb = nullptr; + m_textLine = 30; + m_mouseJoint = nullptr; + m_pointCount = 0; + + m_destructionListener.test = this; + m_world->SetDestructionListener(&m_destructionListener); + m_world->SetContactListener(this); + m_world->SetDebugDraw(&m_debugDraw); + + m_bombSpawning = false; + + m_stepCount = 0; + + b2BodyDef bodyDef; + m_groundBody = m_world->CreateBody(&bodyDef); + + memset(&m_maxProfile, 0, sizeof(b2Profile)); + memset(&m_totalProfile, 0, sizeof(b2Profile)); +} + +Test::~Test() +{ + // By deleting the world, we delete the bomb, mouse joint, etc. + delete m_world; + m_world = nullptr; +} + +void Test::PreSolve(b2Contact* contact, const b2Manifold* oldManifold) +{ + const b2Manifold* manifold = contact->GetManifold(); + + if (manifold->pointCount == 0) + { + return; + } + + b2Fixture* fixtureA = contact->GetFixtureA(); + b2Fixture* fixtureB = contact->GetFixtureB(); + + b2PointState state1[b2_maxManifoldPoints], state2[b2_maxManifoldPoints]; + b2GetPointStates(state1, state2, oldManifold, manifold); + + b2WorldManifold worldManifold; + contact->GetWorldManifold(&worldManifold); + + for (int32 i = 0; i < manifold->pointCount && m_pointCount < k_maxContactPoints; ++i) + { + ContactPoint* cp = m_points + m_pointCount; + cp->fixtureA = fixtureA; + cp->fixtureB = fixtureB; + cp->position = worldManifold.points[i]; + cp->normal = worldManifold.normal; + cp->state = state2[i]; + cp->normalImpulse = manifold->points[i].normalImpulse; + cp->tangentImpulse = manifold->points[i].tangentImpulse; + cp->separation = worldManifold.separations[i]; + ++m_pointCount; + } +} + +void Test::DrawTitle(const char *string) +{ + m_debugDraw.DrawString(5, DRAW_STRING_NEW_LINE, string); + m_textLine = 2 * DRAW_STRING_NEW_LINE; +} + +class QueryCallback : public b2QueryCallback +{ +public: + QueryCallback(const b2Vec2& point) + { + m_point = point; + m_fixture = nullptr; + } + + bool ReportFixture(b2Fixture* fixture) + { + b2Body* body = fixture->GetBody(); + if (body->GetType() == b2_dynamicBody) + { + bool inside = fixture->TestPoint(m_point); + if (inside) + { + m_fixture = fixture; + + // We are done, terminate the query. + return false; + } + } + + // Continue the query. + return true; + } + + b2Vec2 m_point; + b2Fixture* m_fixture; +}; + +bool Test::MouseDown(const b2Vec2& p) +{ + m_mouseWorld = p; + + if (m_mouseJoint != nullptr) + { + return false; + } + + // Make a small box. + b2AABB aabb; + b2Vec2 d; + d.Set(0.001f, 0.001f); + aabb.lowerBound = p - d; + aabb.upperBound = p + d; + + // Query the world for overlapping shapes. + QueryCallback callback(p); + m_world->QueryAABB(&callback, aabb); + + if (callback.m_fixture) + { + b2Body* body = callback.m_fixture->GetBody(); + b2MouseJointDef md; + md.bodyA = m_groundBody; + md.bodyB = body; + md.target = p; + md.maxForce = 1000.0f * body->GetMass(); + m_mouseJoint = (b2MouseJoint*)m_world->CreateJoint(&md); + body->SetAwake(true); + return true; + } + + return false; +} + +void Test::SpawnBomb(const b2Vec2& worldPt) +{ + m_bombSpawnPoint = worldPt; + m_bombSpawning = true; +} + +void Test::CompleteBombSpawn(const b2Vec2& p) +{ + if (m_bombSpawning == false) + { + return; + } + + const float multiplier = 30.0f; + b2Vec2 vel = m_bombSpawnPoint - p; + vel *= multiplier; + LaunchBomb(m_bombSpawnPoint,vel); + m_bombSpawning = false; +} + +void Test::ShiftMouseDown(const b2Vec2& p) +{ + m_mouseWorld = p; + + if (m_mouseJoint != nullptr) + { + return; + } + + SpawnBomb(p); +} + +void Test::MouseUp(const b2Vec2& p) +{ + if (m_mouseJoint) + { + m_world->DestroyJoint(m_mouseJoint); + m_mouseJoint = nullptr; + } + + if (m_bombSpawning) + { + CompleteBombSpawn(p); + } +} + +void Test::MouseMove(const b2Vec2& p) +{ + m_mouseWorld = p; + + if (m_mouseJoint) + { + m_mouseJoint->SetTarget(p); + } +} + +void Test::LaunchBomb() +{ + b2Vec2 p(RandomFloat(-15.0f, 15.0f), 30.0f); + b2Vec2 v = -5.0f * p; + LaunchBomb(p, v); +} + +void Test::LaunchBomb(const b2Vec2& position, const b2Vec2& velocity) +{ + if (m_bomb) + { + m_world->DestroyBody(m_bomb); + m_bomb = nullptr; + } + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = position; + bd.bullet = true; + m_bomb = m_world->CreateBody(&bd); + m_bomb->SetLinearVelocity(velocity); + + b2CircleShape circle; + circle.m_radius = 0.3f; + + b2FixtureDef fd; + fd.shape = &circle; + fd.density = 20.0f; + fd.restitution = 0.0f; + + b2Vec2 minV = position - b2Vec2(0.3f,0.3f); + b2Vec2 maxV = position + b2Vec2(0.3f,0.3f); + + b2AABB aabb; + aabb.lowerBound = minV; + aabb.upperBound = maxV; + + m_bomb->CreateFixture(&fd); +} + +void Test::Step(Settings* settings) +{ + float timeStep = settings->hz > 0.0f ? 1.0f / settings->hz : float(0.0f); + + if (settings->pause) + { + if (settings->singleStep) + { + settings->singleStep = 0; + } + else + { + timeStep = 0.0f; + } + + m_debugDraw.DrawString(5, m_textLine, "****PAUSED****"); + m_textLine += DRAW_STRING_NEW_LINE; + } + + uint32 flags = 0; + flags += settings->drawShapes * b2Draw::e_shapeBit; + flags += settings->drawJoints * b2Draw::e_jointBit; + flags += settings->drawAABBs * b2Draw::e_aabbBit; + flags += settings->drawCOMs * b2Draw::e_centerOfMassBit; + m_debugDraw.SetFlags(flags); + + m_world->SetAllowSleeping(settings->enableSleep > 0); + m_world->SetWarmStarting(settings->enableWarmStarting > 0); + m_world->SetContinuousPhysics(settings->enableContinuous > 0); + m_world->SetSubStepping(settings->enableSubStepping > 0); + + m_pointCount = 0; + + m_world->Step(timeStep, settings->velocityIterations, settings->positionIterations); + + m_world->DebugDraw(); + + if (timeStep > 0.0f) + { + ++m_stepCount; + } + + if (settings->drawStats) + { + int32 bodyCount = m_world->GetBodyCount(); + int32 contactCount = m_world->GetContactCount(); + int32 jointCount = m_world->GetJointCount(); + m_debugDraw.DrawString(5, m_textLine, "bodies/contacts/joints = %d/%d/%d", bodyCount, contactCount, jointCount); + m_textLine += DRAW_STRING_NEW_LINE; + + int32 proxyCount = m_world->GetProxyCount(); + int32 height = m_world->GetTreeHeight(); + int32 balance = m_world->GetTreeBalance(); + float quality = m_world->GetTreeQuality(); + m_debugDraw.DrawString(5, m_textLine, "proxies/height/balance/quality = %d/%d/%d/%g", proxyCount, height, balance, quality); + m_textLine += DRAW_STRING_NEW_LINE; + } + + // Track maximum profile times + { + const b2Profile& p = m_world->GetProfile(); + m_maxProfile.step = b2Max(m_maxProfile.step, p.step); + m_maxProfile.collide = b2Max(m_maxProfile.collide, p.collide); + m_maxProfile.solve = b2Max(m_maxProfile.solve, p.solve); + m_maxProfile.solveInit = b2Max(m_maxProfile.solveInit, p.solveInit); + m_maxProfile.solveVelocity = b2Max(m_maxProfile.solveVelocity, p.solveVelocity); + m_maxProfile.solvePosition = b2Max(m_maxProfile.solvePosition, p.solvePosition); + m_maxProfile.solveTOI = b2Max(m_maxProfile.solveTOI, p.solveTOI); + m_maxProfile.broadphase = b2Max(m_maxProfile.broadphase, p.broadphase); + + m_totalProfile.step += p.step; + m_totalProfile.collide += p.collide; + m_totalProfile.solve += p.solve; + m_totalProfile.solveInit += p.solveInit; + m_totalProfile.solveVelocity += p.solveVelocity; + m_totalProfile.solvePosition += p.solvePosition; + m_totalProfile.solveTOI += p.solveTOI; + m_totalProfile.broadphase += p.broadphase; + } + + if (settings->drawProfile) + { + const b2Profile& p = m_world->GetProfile(); + + b2Profile aveProfile; + memset(&aveProfile, 0, sizeof(b2Profile)); + if (m_stepCount > 0) + { + float scale = 1.0f / m_stepCount; + aveProfile.step = scale * m_totalProfile.step; + aveProfile.collide = scale * m_totalProfile.collide; + aveProfile.solve = scale * m_totalProfile.solve; + aveProfile.solveInit = scale * m_totalProfile.solveInit; + aveProfile.solveVelocity = scale * m_totalProfile.solveVelocity; + aveProfile.solvePosition = scale * m_totalProfile.solvePosition; + aveProfile.solveTOI = scale * m_totalProfile.solveTOI; + aveProfile.broadphase = scale * m_totalProfile.broadphase; + } + + m_debugDraw.DrawString(5, m_textLine, "step [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.step, aveProfile.step, m_maxProfile.step); + m_textLine += DRAW_STRING_NEW_LINE; + m_debugDraw.DrawString(5, m_textLine, "collide [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.collide, aveProfile.collide, m_maxProfile.collide); + m_textLine += DRAW_STRING_NEW_LINE; + m_debugDraw.DrawString(5, m_textLine, "solve [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.solve, aveProfile.solve, m_maxProfile.solve); + m_textLine += DRAW_STRING_NEW_LINE; + m_debugDraw.DrawString(5, m_textLine, "solve init [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.solveInit, aveProfile.solveInit, m_maxProfile.solveInit); + m_textLine += DRAW_STRING_NEW_LINE; + m_debugDraw.DrawString(5, m_textLine, "solve velocity [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.solveVelocity, aveProfile.solveVelocity, m_maxProfile.solveVelocity); + m_textLine += DRAW_STRING_NEW_LINE; + m_debugDraw.DrawString(5, m_textLine, "solve position [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.solvePosition, aveProfile.solvePosition, m_maxProfile.solvePosition); + m_textLine += DRAW_STRING_NEW_LINE; + m_debugDraw.DrawString(5, m_textLine, "solveTOI [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.solveTOI, aveProfile.solveTOI, m_maxProfile.solveTOI); + m_textLine += DRAW_STRING_NEW_LINE; + m_debugDraw.DrawString(5, m_textLine, "broad-phase [ave] (max) = %5.2f [%6.2f] (%6.2f)", p.broadphase, aveProfile.broadphase, m_maxProfile.broadphase); + m_textLine += DRAW_STRING_NEW_LINE; + } + + if (m_mouseJoint) + { + b2Vec2 p1 = m_mouseJoint->GetAnchorB(); + b2Vec2 p2 = m_mouseJoint->GetTarget(); + + b2Color c; + c.Set(0.0f, 1.0f, 0.0f); + m_debugDraw.DrawPoint(p1, 4.0f, c); + m_debugDraw.DrawPoint(p2, 4.0f, c); + + c.Set(0.8f, 0.8f, 0.8f); + m_debugDraw.DrawSegment(p1, p2, c); + } + + if (m_bombSpawning) + { + b2Color c; + c.Set(0.0f, 0.0f, 1.0f); + m_debugDraw.DrawPoint(m_bombSpawnPoint, 4.0f, c); + + c.Set(0.8f, 0.8f, 0.8f); + m_debugDraw.DrawSegment(m_mouseWorld, m_bombSpawnPoint, c); + } + + if (settings->drawContactPoints) + { + const float k_impulseScale = 0.1f; + const float k_axisScale = 0.3f; + + for (int32 i = 0; i < m_pointCount; ++i) + { + ContactPoint* point = m_points + i; + + if (point->state == b2_addState) + { + // Add + m_debugDraw.DrawPoint(point->position, 10.0f, b2Color(0.3f, 0.95f, 0.3f)); + } + else if (point->state == b2_persistState) + { + // Persist + m_debugDraw.DrawPoint(point->position, 5.0f, b2Color(0.3f, 0.3f, 0.95f)); + } + + if (settings->drawContactNormals == 1) + { + b2Vec2 p1 = point->position; + b2Vec2 p2 = p1 + k_axisScale * point->normal; + m_debugDraw.DrawSegment(p1, p2, b2Color(0.9f, 0.9f, 0.9f)); + } + else if (settings->drawContactImpulse == 1) + { + b2Vec2 p1 = point->position; + b2Vec2 p2 = p1 + k_impulseScale * point->normalImpulse * point->normal; + m_debugDraw.DrawSegment(p1, p2, b2Color(0.9f, 0.9f, 0.3f)); + } + + if (settings->drawFrictionImpulse == 1) + { + b2Vec2 tangent = b2Cross(point->normal, 1.0f); + b2Vec2 p1 = point->position; + b2Vec2 p2 = p1 + k_impulseScale * point->tangentImpulse * tangent; + m_debugDraw.DrawSegment(p1, p2, b2Color(0.9f, 0.9f, 0.3f)); + } + } + } +} + +void Test::ShiftOrigin(const b2Vec2& newOrigin) +{ + m_world->ShiftOrigin(newOrigin); +} + + + +TestEntry g_testEntries[MAX_TESTS] = { {nullptr} }; +int g_testCount = 0; + +int RegisterTest(const char* category, const char* name, TestCreateFcn* fcn) +{ + int index = g_testCount; + if (index < MAX_TESTS) + { + g_testEntries[index] = { category, name, fcn }; + ++g_testCount; + return index; + } + + return -1; +} diff --git a/tests/cpp-tests/Classes/Box2DTestBed/Test.h b/tests/cpp-tests/Classes/Box2DTestBed/Test.h new file mode 100644 index 0000000000..05a8045632 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/Test.h @@ -0,0 +1,206 @@ +/* +* Copyright (c) 2006-2009 Erin Catto http://www.box2d.org +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef TEST_H +#define TEST_H + +#include "Box2D/Box2D.h" +#include "CCPhysicsDebugNodeBox2D.h" + +#include + +class Test; +struct Settings; + + + +#define RAND_LIMIT 32767 +#define DRAW_STRING_NEW_LINE 25 + +/// Random number in range [-1,1] +inline float RandomFloat() +{ + float r = (float)(std::rand() & (RAND_LIMIT)); + r /= RAND_LIMIT; + r = 2.0f * r - 1.0f; + return r; +} + +/// Random floating point number in range [lo, hi] +inline float RandomFloat(float lo, float hi) +{ + float r = (float)(std::rand() & (RAND_LIMIT)); + r /= RAND_LIMIT; + r = (hi - lo) * r + lo; + return r; +} + +/// Test settings. Some can be controlled in the GUI. +struct Settings +{ + Settings() + { + viewCenter.Set(0.0f, 20.0f); + hz = 60.0f; + velocityIterations = 8; + positionIterations = 3; + drawShapes = 1; + drawJoints = 1; + drawAABBs = 0; + drawContactPoints = 0; + drawContactNormals = 0; + drawContactImpulse = 0; + drawFrictionImpulse = 0; + drawCOMs = 0; + drawStats = 0; + drawProfile = 0; + enableWarmStarting = 1; + enableContinuous = 1; + enableSubStepping = 0; + enableSleep = 1; + pause = 0; + singleStep = 0; + } + + b2Vec2 viewCenter; + float hz; + int32 velocityIterations; + int32 positionIterations; + int32 drawShapes; + int32 drawJoints; + int32 drawAABBs; + int32 drawContactPoints; + int32 drawContactNormals; + int32 drawContactImpulse; + int32 drawFrictionImpulse; + int32 drawCOMs; + int32 drawStats; + int32 drawProfile; + int32 enableWarmStarting; + int32 enableContinuous; + int32 enableSubStepping; + int32 enableSleep; + int32 pause; + int32 singleStep; +}; + + +// This is called when a joint in the world is implicitly destroyed +// because an attached body is destroyed. This gives us a chance to +// nullify the mouse joint. +class DestructionListener : public b2DestructionListener +{ +public: + void SayGoodbye(b2Fixture* fixture) { B2_NOT_USED(fixture); } + void SayGoodbye(b2Joint* joint); + + Test* test; +}; + +const int32 k_maxContactPoints = 2048; + +struct ContactPoint +{ + b2Fixture* fixtureA; + b2Fixture* fixtureB; + b2Vec2 normal; + b2Vec2 position; + b2PointState state; + float normalImpulse; + float tangentImpulse; + float separation; +}; + +class Test : public b2ContactListener +{ +public: + + Test(); + virtual ~Test(); + + void DrawTitle(const char *string); + virtual void Step(Settings* settings); + virtual void Keyboard(unsigned char key) { B2_NOT_USED(key); } + virtual void KeyboardUp(unsigned char key) { B2_NOT_USED(key); } + void ShiftMouseDown(const b2Vec2& p); + virtual bool MouseDown(const b2Vec2& p); + virtual void MouseUp(const b2Vec2& p); + void MouseMove(const b2Vec2& p); + void LaunchBomb(); + void LaunchBomb(const b2Vec2& position, const b2Vec2& velocity); + + void SpawnBomb(const b2Vec2& worldPt); + void CompleteBombSpawn(const b2Vec2& p); + + // Let derived tests know that a joint was destroyed. + virtual void JointDestroyed(b2Joint* joint) { B2_NOT_USED(joint); } + + // Callbacks for derived classes. + virtual void BeginContact(b2Contact* contact) { B2_NOT_USED(contact); } + virtual void EndContact(b2Contact* contact) { B2_NOT_USED(contact); } + virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold); + virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) + { + B2_NOT_USED(contact); + B2_NOT_USED(impulse); + } + + void ShiftOrigin(const b2Vec2& newOrigin); + +protected: + friend class DestructionListener; + friend class BoundaryListener; + friend class ContactListener; + friend class Box2DView; + + b2Body* m_groundBody; + b2AABB m_worldAABB; + ContactPoint m_points[k_maxContactPoints]; + int32 m_pointCount; + DestructionListener m_destructionListener; + GLESDebugDraw m_debugDraw; + int32 m_textLine; + b2World* m_world; + b2Body* m_bomb; + b2MouseJoint* m_mouseJoint; + b2Vec2 m_bombSpawnPoint; + bool m_bombSpawning; + b2Vec2 m_mouseWorld; + int32 m_stepCount; + + b2Profile m_maxProfile; + b2Profile m_totalProfile; +}; + +typedef Test* TestCreateFcn(); + +int RegisterTest(const char* category, const char* name, TestCreateFcn* fcn); + +// +struct TestEntry +{ + const char* category; + const char* name; + TestCreateFcn* createFcn; +}; + +#define MAX_TESTS 256 +extern TestEntry g_testEntries[MAX_TESTS]; +extern int g_testCount; + +#endif diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/add_pair.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/add_pair.cpp new file mode 100644 index 0000000000..b9a1330ccc --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/add_pair.cpp @@ -0,0 +1,73 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class AddPair : public Test +{ +public: + + AddPair() + { + m_world->SetGravity(b2Vec2(0.0f,0.0f)); + { + b2CircleShape shape; + shape.m_p.SetZero(); + shape.m_radius = 0.1f; + + float minX = -6.0f; + float maxX = 0.0f; + float minY = 4.0f; + float maxY = 6.0f; + + for (int32 i = 0; i < 400; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = b2Vec2(RandomFloat(minX,maxX),RandomFloat(minY,maxY)); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 0.01f); + } + } + + { + b2PolygonShape shape; + shape.SetAsBox(1.5f, 1.5f); + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-40.0f,5.0f); + bd.bullet = true; + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 1.0f); + body->SetLinearVelocity(b2Vec2(10.0f, 0.0f)); + } + } + + static Test* Create() + { + return new AddPair; + } +}; + + +static int testIndex = RegisterTest("Benchmark", "Add Pair", AddPair::Create); + diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/apply_force.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/apply_force.cpp new file mode 100644 index 0000000000..3e41321f03 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/apply_force.cpp @@ -0,0 +1,203 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// This test shows how to apply forces and torques to a body. +// It also shows how to use the friction joint that can be useful +// for overhead games. +class ApplyForce : public Test +{ +public: + ApplyForce() + { + m_world->SetGravity(b2Vec2(0.0f, 0.0f)); + + const float k_restitution = 0.4f; + + b2Body* ground; + { + b2BodyDef bd; + bd.position.Set(0.0f, 20.0f); + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + + b2FixtureDef sd; + sd.shape = &shape; + sd.density = 0.0f; + sd.restitution = k_restitution; + + // Left vertical + shape.SetTwoSided(b2Vec2(-20.0f, -20.0f), b2Vec2(-20.0f, 20.0f)); + ground->CreateFixture(&sd); + + // Right vertical + shape.SetTwoSided(b2Vec2(20.0f, -20.0f), b2Vec2(20.0f, 20.0f)); + ground->CreateFixture(&sd); + + // Top horizontal + shape.SetTwoSided(b2Vec2(-20.0f, 20.0f), b2Vec2(20.0f, 20.0f)); + ground->CreateFixture(&sd); + + // Bottom horizontal + shape.SetTwoSided(b2Vec2(-20.0f, -20.0f), b2Vec2(20.0f, -20.0f)); + ground->CreateFixture(&sd); + } + + { + b2Transform xf1; + xf1.q.Set(0.3524f * b2_pi); + xf1.p = xf1.q.GetXAxis(); + + b2Vec2 vertices[3]; + vertices[0] = b2Mul(xf1, b2Vec2(-1.0f, 0.0f)); + vertices[1] = b2Mul(xf1, b2Vec2(1.0f, 0.0f)); + vertices[2] = b2Mul(xf1, b2Vec2(0.0f, 0.5f)); + + b2PolygonShape poly1; + poly1.Set(vertices, 3); + + b2FixtureDef sd1; + sd1.shape = &poly1; + sd1.density = 2.0f; + + b2Transform xf2; + xf2.q.Set(-0.3524f * b2_pi); + xf2.p = -xf2.q.GetXAxis(); + + vertices[0] = b2Mul(xf2, b2Vec2(-1.0f, 0.0f)); + vertices[1] = b2Mul(xf2, b2Vec2(1.0f, 0.0f)); + vertices[2] = b2Mul(xf2, b2Vec2(0.0f, 0.5f)); + + b2PolygonShape poly2; + poly2.Set(vertices, 3); + + b2FixtureDef sd2; + sd2.shape = &poly2; + sd2.density = 2.0f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + + bd.position.Set(0.0f, 3.0); + bd.angle = b2_pi; + bd.allowSleep = false; + m_body = m_world->CreateBody(&bd); + m_body->CreateFixture(&sd1); + m_body->CreateFixture(&sd2); + + float gravity = 10.0f; + float I = m_body->GetInertia(); + float mass = m_body->GetMass(); + + // Compute an effective radius that can be used to + // set the max torque for a friction joint + // For a circle: I = 0.5 * m * r * r ==> r = sqrt(2 * I / m) + float radius = b2Sqrt(2.0f * I / mass); + + b2FrictionJointDef jd; + jd.bodyA = ground; + jd.bodyB = m_body; + jd.localAnchorA.SetZero(); + jd.localAnchorB = m_body->GetLocalCenter(); + jd.collideConnected = true; + jd.maxForce = 0.5f * mass * gravity; + jd.maxTorque = 0.2f * mass * radius * gravity; + + m_world->CreateJoint(&jd); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.5f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + fd.friction = 0.3f; + + for (int i = 0; i < 10; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + + bd.position.Set(0.0f, 7.0f + 1.54f * i); + b2Body* body = m_world->CreateBody(&bd); + + body->CreateFixture(&fd); + + float gravity = 10.0f; + float I = body->GetInertia(); + float mass = body->GetMass(); + + // For a circle: I = 0.5 * m * r * r ==> r = sqrt(2 * I / m) + float radius = b2Sqrt(2.0f * I / mass); + + b2FrictionJointDef jd; + jd.localAnchorA.SetZero(); + jd.localAnchorB.SetZero(); + jd.bodyA = ground; + jd.bodyB = body; + jd.collideConnected = true; + jd.maxForce = mass * gravity; + jd.maxTorque = 0.1f * mass * radius * gravity; + + m_world->CreateJoint(&jd); + } + } + } + + void Step(Settings* settings) override + { + //g_debugDraw.DrawString(5, m_textLine, "Forward (W), Turn (A) and (D)"); + //m_textLine += m_textIncrement; + + //if (glfwGetKey(g_mainWindow, GLFW_KEY_W) == GLFW_PRESS) + //{ + // b2Vec2 f = m_body->GetWorldVector(b2Vec2(0.0f, -50.0f)); + // b2Vec2 p = m_body->GetWorldPoint(b2Vec2(0.0f, 3.0f)); + // m_body->ApplyForce(f, p, true); + //} + + //if (glfwGetKey(g_mainWindow, GLFW_KEY_A) == GLFW_PRESS) + //{ + // m_body->ApplyTorque(10.0f, true); + //} + + //if (glfwGetKey(g_mainWindow, GLFW_KEY_D) == GLFW_PRESS) + //{ + // m_body->ApplyTorque(-10.0f, true); + //} + + Test::Step(settings); + } + + static Test* Create() + { + return new ApplyForce; + } + + b2Body* m_body; +}; + +static int testIndex = RegisterTest("Forces", "Apply Force", ApplyForce::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/body_types.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/body_types.cpp new file mode 100644 index 0000000000..d6da0f1e7d --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/body_types.cpp @@ -0,0 +1,163 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class BodyTypes : public Test +{ +public: + BodyTypes() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-20.0f, 0.0f), b2Vec2(20.0f, 0.0f)); + + b2FixtureDef fd; + fd.shape = &shape; + + ground->CreateFixture(&fd); + } + + // Define attachment + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 3.0f); + m_attachment = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 2.0f); + m_attachment->CreateFixture(&shape, 2.0f); + } + + // Define platform + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-4.0f, 5.0f); + m_platform = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 4.0f, b2Vec2(4.0f, 0.0f), 0.5f * b2_pi); + + b2FixtureDef fd; + fd.shape = &shape; + fd.friction = 0.6f; + fd.density = 2.0f; + m_platform->CreateFixture(&fd); + + b2RevoluteJointDef rjd; + rjd.Initialize(m_attachment, m_platform, b2Vec2(0.0f, 5.0f)); + rjd.maxMotorTorque = 50.0f; + rjd.enableMotor = true; + m_world->CreateJoint(&rjd); + + b2PrismaticJointDef pjd; + pjd.Initialize(ground, m_platform, b2Vec2(0.0f, 5.0f), b2Vec2(1.0f, 0.0f)); + + pjd.maxMotorForce = 1000.0f; + pjd.enableMotor = true; + pjd.lowerTranslation = -10.0f; + pjd.upperTranslation = 10.0f; + pjd.enableLimit = true; + + m_world->CreateJoint(&pjd); + + m_speed = 3.0f; + } + + // Create a payload + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 8.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.75f, 0.75f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.friction = 0.6f; + fd.density = 2.0f; + + body->CreateFixture(&fd); + } + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_D: + // m_platform->SetType(b2_dynamicBody); + // break; + + // case GLFW_KEY_S: + // m_platform->SetType(b2_staticBody); + // break; + + // case GLFW_KEY_K: + // m_platform->SetType(b2_kinematicBody); + // m_platform->SetLinearVelocity(b2Vec2(-m_speed, 0.0f)); + // m_platform->SetAngularVelocity(0.0f); + // break; + // } + //} + + void Step(Settings* settings) override + { + // Drive the kinematic body. + if (m_platform->GetType() == b2_kinematicBody) + { + b2Vec2 p = m_platform->GetTransform().p; + b2Vec2 v = m_platform->GetLinearVelocity(); + + if ((p.x < -10.0f && v.x < 0.0f) || + (p.x > 10.0f && v.x > 0.0f)) + { + v.x = -v.x; + m_platform->SetLinearVelocity(v); + } + } + + Test::Step(settings); + + //g_debugDraw.DrawString(5, m_textLine, "Keys: (d) dynamic, (s) static, (k) kinematic"); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new BodyTypes; + } + + b2Body* m_attachment; + b2Body* m_platform; + float m_speed; +}; + +static int testIndex = RegisterTest("Examples", "Body Types", BodyTypes::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/box_stack.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/box_stack.cpp new file mode 100644 index 0000000000..08c3a476b8 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/box_stack.cpp @@ -0,0 +1,174 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +extern B2_API bool g_blockSolve; + +class BoxStack : public Test +{ +public: + + enum + { + e_columnCount = 1, + e_rowCount = 15 + //e_columnCount = 1, + //e_rowCount = 1 + }; + + BoxStack() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(b2Vec2(20.0f, 0.0f), b2Vec2(20.0f, 20.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + float xs[5] = {0.0f, -10.0f, -5.0f, 5.0f, 10.0f}; + + for (int32 j = 0; j < e_columnCount; ++j) + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.5f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + fd.friction = 0.3f; + + for (int i = 0; i < e_rowCount; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + + int32 n = j * e_rowCount + i; + b2Assert(n < e_rowCount * e_columnCount); + m_indices[n] = n; + bd.userData.pointer = n; + + float x = 0.0f; + //float x = RandomFloat(-0.02f, 0.02f); + //float x = i % 2 == 0 ? -0.01f : 0.01f; + bd.position.Set(xs[j] + x, 0.55f + 1.1f * i); + b2Body* body = m_world->CreateBody(&bd); + + m_bodies[n] = body; + + body->CreateFixture(&fd); + } + } + + m_bullet = NULL; + } + + //void Keyboard(int key) override + //{ + // //switch (key) + // //{ + // //case GLFW_KEY_COMMA: + // // if (m_bullet != NULL) + // // { + // // m_world->DestroyBody(m_bullet); + // // m_bullet = NULL; + // // } + + // // { + // // b2CircleShape shape; + // // shape.m_radius = 0.25f; + + // // b2FixtureDef fd; + // // fd.shape = &shape; + // // fd.density = 20.0f; + // // fd.restitution = 0.05f; + + // // b2BodyDef bd; + // // bd.type = b2_dynamicBody; + // // bd.bullet = true; + // // bd.position.Set(-31.0f, 5.0f); + + // // m_bullet = m_world->CreateBody(&bd); + // // m_bullet->CreateFixture(&fd); + + // // m_bullet->SetLinearVelocity(b2Vec2(400.0f, 0.0f)); + // // } + // // break; + // // + // // case GLFW_KEY_B: + // // g_blockSolve = !g_blockSolve; + // // break; + // //} + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + //g_debugDraw.DrawString(5, m_textLine, "Press: (,) to launch a bullet."); + //m_textLine += m_textIncrement; + //g_debugDraw.DrawString(5, m_textLine, "Blocksolve = %d", g_blockSolve); + if (m_stepCount == 300) + { + if (m_bullet != NULL) + { + m_world->DestroyBody(m_bullet); + m_bullet = NULL; + } + + { + b2CircleShape shape; + shape.m_radius = 0.25f; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + fd.restitution = 0.05f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.bullet = true; + bd.position.Set(-31.0f, 5.0f); + + m_bullet = m_world->CreateBody(&bd); + m_bullet->CreateFixture(&fd); + + m_bullet->SetLinearVelocity(b2Vec2(400.0f, 0.0f)); + } + } + } + + static Test* Create() + { + return new BoxStack; + } + + b2Body* m_bullet; + b2Body* m_bodies[e_rowCount * e_columnCount]; + int32 m_indices[e_rowCount * e_columnCount]; +}; + +static int testIndex = RegisterTest("Stacking", "Boxes", BoxStack::Create); \ No newline at end of file diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/breakable.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/breakable.cpp new file mode 100644 index 0000000000..0bdc948bcc --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/breakable.cpp @@ -0,0 +1,158 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// This is used to test sensor shapes. +class Breakable : public Test +{ +public: + + enum + { + e_count = 7 + }; + + Breakable() + { + // Ground body + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + // Breakable dynamic body + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 40.0f); + bd.angle = 0.25f * b2_pi; + m_body1 = m_world->CreateBody(&bd); + + m_shape1.SetAsBox(0.5f, 0.5f, b2Vec2(-0.5f, 0.0f), 0.0f); + m_piece1 = m_body1->CreateFixture(&m_shape1, 1.0f); + + m_shape2.SetAsBox(0.5f, 0.5f, b2Vec2(0.5f, 0.0f), 0.0f); + m_piece2 = m_body1->CreateFixture(&m_shape2, 1.0f); + } + + m_break = false; + m_broke = false; + } + + void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) override + { + if (m_broke) + { + // The body already broke. + return; + } + + // Should the body break? + int32 count = contact->GetManifold()->pointCount; + + float maxImpulse = 0.0f; + for (int32 i = 0; i < count; ++i) + { + maxImpulse = b2Max(maxImpulse, impulse->normalImpulses[i]); + } + + if (maxImpulse > 40.0f) + { + // Flag the body for breaking. + m_break = true; + } + } + + void Break() + { + // Create two bodies from one. + b2Body* body1 = m_piece1->GetBody(); + b2Vec2 center = body1->GetWorldCenter(); + + body1->DestroyFixture(m_piece2); + m_piece2 = NULL; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = body1->GetPosition(); + bd.angle = body1->GetAngle(); + + b2Body* body2 = m_world->CreateBody(&bd); + m_piece2 = body2->CreateFixture(&m_shape2, 1.0f); + + // Compute consistent velocities for new bodies based on + // cached velocity. + b2Vec2 center1 = body1->GetWorldCenter(); + b2Vec2 center2 = body2->GetWorldCenter(); + + b2Vec2 velocity1 = m_velocity + b2Cross(m_angularVelocity, center1 - center); + b2Vec2 velocity2 = m_velocity + b2Cross(m_angularVelocity, center2 - center); + + body1->SetAngularVelocity(m_angularVelocity); + body1->SetLinearVelocity(velocity1); + + body2->SetAngularVelocity(m_angularVelocity); + body2->SetLinearVelocity(velocity2); + } + + void Step(Settings* settings) override + { + if (m_break) + { + Break(); + m_broke = true; + m_break = false; + } + + // Cache velocities to improve movement on breakage. + if (m_broke == false) + { + m_velocity = m_body1->GetLinearVelocity(); + m_angularVelocity = m_body1->GetAngularVelocity(); + } + + Test::Step(settings); + } + + static Test* Create() + { + return new Breakable; + } + + b2Body* m_body1; + b2Vec2 m_velocity; + float m_angularVelocity; + b2PolygonShape m_shape1; + b2PolygonShape m_shape2; + b2Fixture* m_piece1; + b2Fixture* m_piece2; + + bool m_broke; + bool m_break; +}; + +static int testIndex = RegisterTest("Examples", "Breakable", Breakable::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/bridge.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/bridge.cpp new file mode 100644 index 0000000000..6fe65ebe91 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/bridge.cpp @@ -0,0 +1,128 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Bridge : public Test +{ +public: + + enum + { + e_count = 30 + }; + + Bridge() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + fd.friction = 0.2f; + + b2RevoluteJointDef jd; + + b2Body* prevBody = ground; + for (int32 i = 0; i < e_count; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-14.5f + 1.0f * i, 5.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + + b2Vec2 anchor(-15.0f + 1.0f * i, 5.0f); + jd.Initialize(prevBody, body, anchor); + m_world->CreateJoint(&jd); + + if (i == (e_count >> 1)) + { + m_middle = body; + } + prevBody = body; + } + + b2Vec2 anchor(-15.0f + 1.0f * e_count, 5.0f); + jd.Initialize(prevBody, ground, anchor); + m_world->CreateJoint(&jd); + } + + for (int32 i = 0; i < 2; ++i) + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.5f, 0.0f); + vertices[1].Set(0.5f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + + b2PolygonShape shape; + shape.Set(vertices, 3); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-8.0f + 8.0f * i, 12.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + } + + for (int32 i = 0; i < 3; ++i) + { + b2CircleShape shape; + shape.m_radius = 0.5f; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-6.0f + 6.0f * i, 10.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + } + } + + static Test* Create() + { + return new Bridge; + } + + b2Body* m_middle; +}; + +static int testIndex = RegisterTest("Joints", "Bridge", Bridge::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/bullet_test.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/bullet_test.cpp new file mode 100644 index 0000000000..f2ad34c77a --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/bullet_test.cpp @@ -0,0 +1,139 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class BulletTest : public Test +{ +public: + + BulletTest() + { + { + b2BodyDef bd; + bd.position.Set(0.0f, 0.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2EdgeShape edge; + + edge.SetTwoSided(b2Vec2(-10.0f, 0.0f), b2Vec2(10.0f, 0.0f)); + body->CreateFixture(&edge, 0.0f); + + b2PolygonShape shape; + shape.SetAsBox(0.2f, 1.0f, b2Vec2(0.5f, 1.0f), 0.0f); + body->CreateFixture(&shape, 0.0f); + } + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 4.0f); + + b2PolygonShape box; + box.SetAsBox(2.0f, 0.1f); + + m_body = m_world->CreateBody(&bd); + m_body->CreateFixture(&box, 1.0f); + + box.SetAsBox(0.25f, 0.25f); + + //m_x = RandomFloat(-1.0f, 1.0f); + m_x = 0.20352793f; + bd.position.Set(m_x, 10.0f); + bd.bullet = true; + + m_bullet = m_world->CreateBody(&bd); + m_bullet->CreateFixture(&box, 100.0f); + + m_bullet->SetLinearVelocity(b2Vec2(0.0f, -50.0f)); + } + } + + void Launch() + { + m_body->SetTransform(b2Vec2(0.0f, 4.0f), 0.0f); + m_body->SetLinearVelocity(b2Vec2_zero); + m_body->SetAngularVelocity(0.0f); + + m_x = RandomFloat(-1.0f, 1.0f); + m_bullet->SetTransform(b2Vec2(m_x, 10.0f), 0.0f); + m_bullet->SetLinearVelocity(b2Vec2(0.0f, -50.0f)); + m_bullet->SetAngularVelocity(0.0f); + + extern B2_API int32 b2_gjkCalls, b2_gjkIters, b2_gjkMaxIters; + extern B2_API int32 b2_toiCalls, b2_toiIters, b2_toiMaxIters; + extern B2_API int32 b2_toiRootIters, b2_toiMaxRootIters; + + b2_gjkCalls = 0; + b2_gjkIters = 0; + b2_gjkMaxIters = 0; + + b2_toiCalls = 0; + b2_toiIters = 0; + b2_toiMaxIters = 0; + b2_toiRootIters = 0; + b2_toiMaxRootIters = 0; + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + extern B2_API int32 b2_gjkCalls, b2_gjkIters, b2_gjkMaxIters; + extern B2_API int32 b2_toiCalls, b2_toiIters; + extern B2_API int32 b2_toiRootIters, b2_toiMaxRootIters; + + //if (b2_gjkCalls > 0) + //{ + // g_debugDraw.DrawString(5, m_textLine, "gjk calls = %d, ave gjk iters = %3.1f, max gjk iters = %d", + // b2_gjkCalls, b2_gjkIters / float(b2_gjkCalls), b2_gjkMaxIters); + // m_textLine += m_textIncrement; + //} + + //if (b2_toiCalls > 0) + //{ + // g_debugDraw.DrawString(5, m_textLine, "toi calls = %d, ave toi iters = %3.1f, max toi iters = %d", + // b2_toiCalls, b2_toiIters / float(b2_toiCalls), b2_toiMaxRootIters); + // m_textLine += m_textIncrement; + + // g_debugDraw.DrawString(5, m_textLine, "ave toi root iters = %3.1f, max toi root iters = %d", + // b2_toiRootIters / float(b2_toiCalls), b2_toiMaxRootIters); + // m_textLine += m_textIncrement; + //} + + if (m_stepCount % 60 == 0) + { + Launch(); + } + } + + static Test* Create() + { + return new BulletTest; + } + + b2Body* m_body; + b2Body* m_bullet; + float m_x; +}; + +static int testIndex = RegisterTest("Continuous", "Bullet Test", BulletTest::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/cantilever.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/cantilever.cpp new file mode 100644 index 0000000000..652e0f0629 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/cantilever.cpp @@ -0,0 +1,218 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// It is difficult to make a cantilever made of links completely rigid with weld joints. +// You will have to use a high number of iterations to make them stiff. +// So why not go ahead and use soft weld joints? They behave like a revolute +// joint with a rotational spring. +class Cantilever : public Test +{ +public: + + enum + { + e_count = 8 + }; + + Cantilever() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + + b2WeldJointDef jd; + + b2Body* prevBody = ground; + for (int32 i = 0; i < e_count; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-14.5f + 1.0f * i, 5.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + + b2Vec2 anchor(-15.0f + 1.0f * i, 5.0f); + jd.Initialize(prevBody, body, anchor); + m_world->CreateJoint(&jd); + + prevBody = body; + } + } + + { + b2PolygonShape shape; + shape.SetAsBox(1.0f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + + b2WeldJointDef jd; + float frequencyHz = 5.0f; + float dampingRatio = 0.7f; + + b2Body* prevBody = ground; + for (int32 i = 0; i < 3; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-14.0f + 2.0f * i, 15.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + + b2Vec2 anchor(-15.0f + 2.0f * i, 15.0f); + jd.Initialize(prevBody, body, anchor); + b2AngularStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_world->CreateJoint(&jd); + + prevBody = body; + } + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + + b2WeldJointDef jd; + + b2Body* prevBody = ground; + for (int32 i = 0; i < e_count; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-4.5f + 1.0f * i, 5.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + + if (i > 0) + { + b2Vec2 anchor(-5.0f + 1.0f * i, 5.0f); + jd.Initialize(prevBody, body, anchor); + m_world->CreateJoint(&jd); + } + + prevBody = body; + } + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + + b2WeldJointDef jd; + float frequencyHz = 8.0f; + float dampingRatio = 0.7f; + + b2Body* prevBody = ground; + for (int32 i = 0; i < e_count; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(5.5f + 1.0f * i, 10.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + + if (i > 0) + { + b2Vec2 anchor(5.0f + 1.0f * i, 10.0f); + jd.Initialize(prevBody, body, anchor); + + b2AngularStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, prevBody, body); + + m_world->CreateJoint(&jd); + } + + prevBody = body; + } + } + + for (int32 i = 0; i < 2; ++i) + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.5f, 0.0f); + vertices[1].Set(0.5f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + + b2PolygonShape shape; + shape.Set(vertices, 3); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-8.0f + 8.0f * i, 12.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + } + + for (int32 i = 0; i < 2; ++i) + { + b2CircleShape shape; + shape.m_radius = 0.5f; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-6.0f + 6.0f * i, 10.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + } + } + + static Test* Create() + { + return new Cantilever; + } + + b2Body* m_middle; +}; + +static int testIndex = RegisterTest("Joints", "Cantilever", Cantilever::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/car.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/car.cpp new file mode 100644 index 0000000000..13f3c198a0 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/car.cpp @@ -0,0 +1,284 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// This is a fun demo that shows off the wheel joint +class Car : public Test +{ +public: + Car() + { + m_speed = 50.0f; + + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 0.0f; + fd.friction = 0.6f; + + shape.SetTwoSided(b2Vec2(-20.0f, 0.0f), b2Vec2(20.0f, 0.0f)); + ground->CreateFixture(&fd); + + float hs[10] = {0.25f, 1.0f, 4.0f, 0.0f, 0.0f, -1.0f, -2.0f, -2.0f, -1.25f, 0.0f}; + + float x = 20.0f, y1 = 0.0f, dx = 5.0f; + + for (int32 i = 0; i < 10; ++i) + { + float y2 = hs[i]; + shape.SetTwoSided(b2Vec2(x, y1), b2Vec2(x + dx, y2)); + ground->CreateFixture(&fd); + y1 = y2; + x += dx; + } + + for (int32 i = 0; i < 10; ++i) + { + float y2 = hs[i]; + shape.SetTwoSided(b2Vec2(x, y1), b2Vec2(x + dx, y2)); + ground->CreateFixture(&fd); + y1 = y2; + x += dx; + } + + shape.SetTwoSided(b2Vec2(x, 0.0f), b2Vec2(x + 40.0f, 0.0f)); + ground->CreateFixture(&fd); + + x += 80.0f; + shape.SetTwoSided(b2Vec2(x, 0.0f), b2Vec2(x + 40.0f, 0.0f)); + ground->CreateFixture(&fd); + + x += 40.0f; + shape.SetTwoSided(b2Vec2(x, 0.0f), b2Vec2(x + 10.0f, 5.0f)); + ground->CreateFixture(&fd); + + x += 20.0f; + shape.SetTwoSided(b2Vec2(x, 0.0f), b2Vec2(x + 40.0f, 0.0f)); + ground->CreateFixture(&fd); + + x += 40.0f; + shape.SetTwoSided(b2Vec2(x, 0.0f), b2Vec2(x, 20.0f)); + ground->CreateFixture(&fd); + } + + // Teeter + { + b2BodyDef bd; + bd.position.Set(140.0f, 1.0f); + bd.type = b2_dynamicBody; + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape box; + box.SetAsBox(10.0f, 0.25f); + body->CreateFixture(&box, 1.0f); + + b2RevoluteJointDef jd; + jd.Initialize(ground, body, body->GetPosition()); + jd.lowerAngle = -8.0f * b2_pi / 180.0f; + jd.upperAngle = 8.0f * b2_pi / 180.0f; + jd.enableLimit = true; + m_world->CreateJoint(&jd); + + body->ApplyAngularImpulse(100.0f, true); + } + + // Bridge + { + int32 N = 20; + b2PolygonShape shape; + shape.SetAsBox(1.0f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + fd.friction = 0.6f; + + b2RevoluteJointDef jd; + + b2Body* prevBody = ground; + for (int32 i = 0; i < N; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(161.0f + 2.0f * i, -0.125f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + + b2Vec2 anchor(160.0f + 2.0f * i, -0.125f); + jd.Initialize(prevBody, body, anchor); + m_world->CreateJoint(&jd); + + prevBody = body; + } + + b2Vec2 anchor(160.0f + 2.0f * N, -0.125f); + jd.Initialize(prevBody, ground, anchor); + m_world->CreateJoint(&jd); + } + + // Boxes + { + b2PolygonShape box; + box.SetAsBox(0.5f, 0.5f); + + b2Body* body = NULL; + b2BodyDef bd; + bd.type = b2_dynamicBody; + + bd.position.Set(230.0f, 0.5f); + body = m_world->CreateBody(&bd); + body->CreateFixture(&box, 0.5f); + + bd.position.Set(230.0f, 1.5f); + body = m_world->CreateBody(&bd); + body->CreateFixture(&box, 0.5f); + + bd.position.Set(230.0f, 2.5f); + body = m_world->CreateBody(&bd); + body->CreateFixture(&box, 0.5f); + + bd.position.Set(230.0f, 3.5f); + body = m_world->CreateBody(&bd); + body->CreateFixture(&box, 0.5f); + + bd.position.Set(230.0f, 4.5f); + body = m_world->CreateBody(&bd); + body->CreateFixture(&box, 0.5f); + } + + // Car + { + b2PolygonShape chassis; + b2Vec2 vertices[8]; + vertices[0].Set(-1.5f, -0.5f); + vertices[1].Set(1.5f, -0.5f); + vertices[2].Set(1.5f, 0.0f); + vertices[3].Set(0.0f, 0.9f); + vertices[4].Set(-1.15f, 0.9f); + vertices[5].Set(-1.5f, 0.2f); + chassis.Set(vertices, 6); + + b2CircleShape circle; + circle.m_radius = 0.4f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 1.0f); + m_car = m_world->CreateBody(&bd); + m_car->CreateFixture(&chassis, 1.0f); + + b2FixtureDef fd; + fd.shape = &circle; + fd.density = 1.0f; + fd.friction = 0.9f; + + bd.position.Set(-1.0f, 0.35f); + m_wheel1 = m_world->CreateBody(&bd); + m_wheel1->CreateFixture(&fd); + + bd.position.Set(1.0f, 0.4f); + m_wheel2 = m_world->CreateBody(&bd); + m_wheel2->CreateFixture(&fd); + + b2WheelJointDef jd; + b2Vec2 axis(0.0f, 1.0f); + + float mass1 = m_wheel1->GetMass(); + float mass2 = m_wheel2->GetMass(); + + float hertz = 4.0f; + float dampingRatio = 0.7f; + float omega = 2.0f * b2_pi * hertz; + + jd.Initialize(m_car, m_wheel1, m_wheel1->GetPosition(), axis); + jd.motorSpeed = 0.0f; + jd.maxMotorTorque = 20.0f; + jd.enableMotor = true; + jd.stiffness = mass1 * omega * omega; + jd.damping = 2.0f * mass1 * dampingRatio * omega; + jd.lowerTranslation = -0.25f; + jd.upperTranslation = 0.25f; + jd.enableLimit = true; + m_spring1 = (b2WheelJoint*)m_world->CreateJoint(&jd); + + jd.Initialize(m_car, m_wheel2, m_wheel2->GetPosition(), axis); + jd.motorSpeed = 0.0f; + jd.maxMotorTorque = 10.0f; + jd.enableMotor = false; + jd.stiffness = mass2 * omega * omega; + jd.damping = 2.0f * mass2 * dampingRatio * omega; + jd.lowerTranslation = -0.25f; + jd.upperTranslation = 0.25f; + jd.enableLimit = true; + m_spring2 = (b2WheelJoint*)m_world->CreateJoint(&jd); + } + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_spring1->SetMotorSpeed(m_speed); + // break; + + // case GLFW_KEY_S: + // m_spring1->SetMotorSpeed(0.0f); + // break; + + // case GLFW_KEY_D: + // m_spring1->SetMotorSpeed(-m_speed); + // break; + // } + //} + + void Step(Settings* settings) override + { + //g_debugDraw.DrawString(5, m_textLine, "Keys: left = a, brake = s, right = d, hz down = q, hz up = e"); + //m_textLine += m_textIncrement; + + //g_camera.m_center.x = m_car->GetPosition().x; + Test::Step(settings); + } + + static Test* Create() + { + return new Car; + } + + b2Body* m_car; + b2Body* m_wheel1; + b2Body* m_wheel2; + + float m_speed; + b2WheelJoint* m_spring1; + b2WheelJoint* m_spring2; +}; + +static int testIndex = RegisterTest("Examples", "Car", Car::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/chain.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/chain.cpp new file mode 100644 index 0000000000..b4b1c2476d --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/chain.cpp @@ -0,0 +1,92 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +#define TEST_BAD_BODY 0 + +class Chain : public Test +{ +public: + Chain() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.6f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + fd.friction = 0.2f; + + b2RevoluteJointDef jd; + jd.collideConnected = false; + + const float y = 25.0f; + b2Body* prevBody = ground; + for (int32 i = 0; i < 30; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.5f + i, y); + b2Body* body = m_world->CreateBody(&bd); + +#if TEST_BAD_BODY == 1 + if (i == 10) + { + // Test zero density dynamic body + fd.density = 0.0f; + } + else + { + fd.density = 20.0f; + } +#endif + + body->CreateFixture(&fd); + + b2Vec2 anchor(float(i), y); + jd.Initialize(prevBody, body, anchor); + m_world->CreateJoint(&jd); + + prevBody = body; + } + } + } + + static Test* Create() + { + return new Chain; + } +}; + +static int testIndex = RegisterTest("Joints", "Chain", Chain::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/chain_problem.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/chain_problem.cpp new file mode 100644 index 0000000000..719370e381 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/chain_problem.cpp @@ -0,0 +1,94 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class ChainProblem : public Test +{ +public: + + ChainProblem() + { + { + b2Vec2 g(0.0f, -10.0f); + m_world->SetGravity(g); + b2Body** bodies = (b2Body**)b2Alloc(2 * sizeof(b2Body*)); + b2Joint** joints = (b2Joint**)b2Alloc(0 * sizeof(b2Joint*)); + { + b2BodyDef bd; + bd.type = b2BodyType(0); + bodies[0] = m_world->CreateBody(&bd); + + { + b2FixtureDef fd; + + b2Vec2 v1(0.0f, 1.0f); + b2Vec2 v2(0.0f, 0.0f); + b2Vec2 v3(4.0f, 0.0f); + + b2EdgeShape shape; + shape.SetTwoSided(v1, v2); + bodies[0]->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v2, v3); + bodies[0]->CreateFixture(&shape, 0.0f); + } + } + { + b2BodyDef bd; + bd.type = b2BodyType(2); + //bd.position.Set(6.033980250358582e-01f, 3.028350114822388e+00f); + bd.position.Set(1.0f, 3.0f); + bodies[1] = m_world->CreateBody(&bd); + + { + b2FixtureDef fd; + fd.friction = 0.2f; + fd.density = 10.0f; + b2PolygonShape shape; + b2Vec2 vs[8]; + vs[0].Set(0.5f, -3.0f); + vs[1].Set(0.5f, 3.0f); + vs[2].Set(-0.5f, 3.0f); + vs[3].Set(-0.5f, -3.0f); + shape.Set(vs, 4); + + fd.shape = &shape; + + bodies[1]->CreateFixture(&fd); + } + } + b2Free(joints); + b2Free(bodies); + joints = NULL; + bodies = NULL; + } + } + + static Test* Create() + { + return new ChainProblem; + } + +}; + +static int testIndex = RegisterTest("Bugs", "Chain Problem", ChainProblem::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/character_collision.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/character_collision.cpp new file mode 100644 index 0000000000..56ed91da75 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/character_collision.cpp @@ -0,0 +1,256 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +/// This is a test of typical character collision scenarios. This does not +/// show how you should implement a character in your application. +/// Instead this is used to test smooth collision on edge chains. +class CharacterCollision : public Test +{ +public: + CharacterCollision() + { + // Ground body + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-20.0f, 0.0f), b2Vec2(20.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + // Collinear edges with no adjacency information. + // This shows the problematic case where a box shape can hit + // an internal vertex. + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-8.0f, 1.0f), b2Vec2(-6.0f, 1.0f)); + ground->CreateFixture(&shape, 0.0f); + shape.SetTwoSided(b2Vec2(-6.0f, 1.0f), b2Vec2(-4.0f, 1.0f)); + ground->CreateFixture(&shape, 0.0f); + shape.SetTwoSided(b2Vec2(-4.0f, 1.0f), b2Vec2(-2.0f, 1.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + // Chain shape + { + b2BodyDef bd; + bd.angle = 0.25f * b2_pi; + b2Body* ground = m_world->CreateBody(&bd); + + b2Vec2 vs[4]; + vs[0].Set(5.0f, 7.0f); + vs[1].Set(6.0f, 8.0f); + vs[2].Set(7.0f, 8.0f); + vs[3].Set(8.0f, 7.0f); + b2ChainShape shape; + shape.CreateLoop(vs, 4); + ground->CreateFixture(&shape, 0.0f); + } + + // Square tiles. This shows that adjacency shapes may + // have non-smooth collision. There is no solution + // to this problem. + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(1.0f, 1.0f, b2Vec2(4.0f, 3.0f), 0.0f); + ground->CreateFixture(&shape, 0.0f); + shape.SetAsBox(1.0f, 1.0f, b2Vec2(6.0f, 3.0f), 0.0f); + ground->CreateFixture(&shape, 0.0f); + shape.SetAsBox(1.0f, 1.0f, b2Vec2(8.0f, 3.0f), 0.0f); + ground->CreateFixture(&shape, 0.0f); + } + + // Square made from an edge loop. Collision should be smooth. + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2Vec2 vs[4]; + vs[0].Set(-1.0f, 3.0f); + vs[1].Set(1.0f, 3.0f); + vs[2].Set(1.0f, 5.0f); + vs[3].Set(-1.0f, 5.0f); + b2ChainShape shape; + shape.CreateLoop(vs, 4); + ground->CreateFixture(&shape, 0.0f); + } + + // Edge loop. Collision should be smooth. + { + b2BodyDef bd; + bd.position.Set(-10.0f, 4.0f); + b2Body* ground = m_world->CreateBody(&bd); + + b2Vec2 vs[10]; + vs[0].Set(0.0f, 0.0f); + vs[1].Set(6.0f, 0.0f); + vs[2].Set(6.0f, 2.0f); + vs[3].Set(4.0f, 1.0f); + vs[4].Set(2.0f, 2.0f); + vs[5].Set(0.0f, 2.0f); + vs[6].Set(-2.0f, 2.0f); + vs[7].Set(-4.0f, 3.0f); + vs[8].Set(-6.0f, 2.0f); + vs[9].Set(-6.0f, 0.0f); + b2ChainShape shape; + shape.CreateLoop(vs, 10); + ground->CreateFixture(&shape, 0.0f); + } + + // Square character 1 + { + b2BodyDef bd; + bd.position.Set(-3.0f, 8.0f); + bd.type = b2_dynamicBody; + bd.fixedRotation = true; + bd.allowSleep = false; + + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.5f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + body->CreateFixture(&fd); + } + + // Square character 2 + { + b2BodyDef bd; + bd.position.Set(-5.0f, 5.0f); + bd.type = b2_dynamicBody; + bd.fixedRotation = true; + bd.allowSleep = false; + + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.25f, 0.25f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + body->CreateFixture(&fd); + } + + // Hexagon character + { + b2BodyDef bd; + bd.position.Set(-5.0f, 8.0f); + bd.type = b2_dynamicBody; + bd.fixedRotation = true; + bd.allowSleep = false; + + b2Body* body = m_world->CreateBody(&bd); + + float angle = 0.0f; + float delta = b2_pi / 3.0f; + b2Vec2 vertices[6]; + for (int32 i = 0; i < 6; ++i) + { + vertices[i].Set(0.5f * cosf(angle), 0.5f * sinf(angle)); + angle += delta; + } + + b2PolygonShape shape; + shape.Set(vertices, 6); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + body->CreateFixture(&fd); + } + + // Circle character + { + b2BodyDef bd; + bd.position.Set(3.0f, 5.0f); + bd.type = b2_dynamicBody; + bd.fixedRotation = true; + bd.allowSleep = false; + + b2Body* body = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 0.5f; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + body->CreateFixture(&fd); + } + + // Circle character + { + b2BodyDef bd; + bd.position.Set(-7.0f, 6.0f); + bd.type = b2_dynamicBody; + bd.allowSleep = false; + + m_character = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 0.25f; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + fd.friction = 1.0f; + m_character->CreateFixture(&fd); + } + } + + void Step(Settings* settings) override + { + b2Vec2 v = m_character->GetLinearVelocity(); + v.x = -5.0f; + m_character->SetLinearVelocity(v); + + Test::Step(settings); + //g_debugDraw.DrawString(5, m_textLine, "This tests various character collision shapes."); + //m_textLine += m_textIncrement; + //g_debugDraw.DrawString(5, m_textLine, "Limitation: square and hexagon can snag on aligned boxes."); + //m_textLine += m_textIncrement; + //g_debugDraw.DrawString(5, m_textLine, "Feature: edge chains have smooth collision inside and out."); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new CharacterCollision; + } + + b2Body* m_character; +}; + +static int testIndex = RegisterTest("Examples", "Character Collision", CharacterCollision::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/circle_stack.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/circle_stack.cpp new file mode 100644 index 0000000000..8af8ee03cf --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/circle_stack.cpp @@ -0,0 +1,89 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class CircleStack : public Test +{ +public: + + enum + { + e_count = 10 + }; + + CircleStack() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2CircleShape shape; + shape.m_radius = 1.0f; + + for (int32 i = 0; i < e_count; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0, 4.0f + 3.0f * i); + + m_bodies[i] = m_world->CreateBody(&bd); + + m_bodies[i]->CreateFixture(&shape, 1.0f); + + m_bodies[i]->SetLinearVelocity(b2Vec2(0.0f, -50.0f)); + } + } + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + //for (int32 i = 0; i < e_count; ++i) + //{ + // printf("%g ", m_bodies[i]->GetWorldCenter().y); + //} + + //for (int32 i = 0; i < e_count; ++i) + //{ + // printf("%g ", m_bodies[i]->GetLinearVelocity().y); + //} + + //printf("\n"); + } + + static Test* Create() + { + return new CircleStack; + } + + b2Body* m_bodies[e_count]; +}; + +static int testIndex = RegisterTest("Stacking", "Circles", CircleStack::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/collision_filtering.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/collision_filtering.cpp new file mode 100644 index 0000000000..a9c3349f6a --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/collision_filtering.cpp @@ -0,0 +1,179 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// This is a test of collision filtering. +// There is a triangle, a box, and a circle. +// There are 6 shapes. 3 large and 3 small. +// The 3 small ones always collide. +// The 3 large ones never collide. +// The boxes don't collide with triangles (except if both are small). +const int16 k_smallGroup = 1; +const int16 k_largeGroup = -1; + +const uint16 k_triangleCategory = 0x0002; +const uint16 k_boxCategory = 0x0004; +const uint16 k_circleCategory = 0x0008; + +const uint16 k_triangleMask = 0xFFFF; +const uint16 k_boxMask = 0xFFFF ^ k_triangleCategory; +const uint16 k_circleMask = 0xFFFF; + +class CollisionFiltering : public Test +{ +public: + CollisionFiltering() + { + // Ground body + { + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + + b2FixtureDef sd; + sd.shape = &shape; + sd.friction = 0.3f; + + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&sd); + } + + // Small triangle + b2Vec2 vertices[3]; + vertices[0].Set(-1.0f, 0.0f); + vertices[1].Set(1.0f, 0.0f); + vertices[2].Set(0.0f, 2.0f); + b2PolygonShape polygon; + polygon.Set(vertices, 3); + + b2FixtureDef triangleShapeDef; + triangleShapeDef.shape = &polygon; + triangleShapeDef.density = 1.0f; + + triangleShapeDef.filter.groupIndex = k_smallGroup; + triangleShapeDef.filter.categoryBits = k_triangleCategory; + triangleShapeDef.filter.maskBits = k_triangleMask; + + b2BodyDef triangleBodyDef; + triangleBodyDef.type = b2_dynamicBody; + triangleBodyDef.position.Set(-5.0f, 2.0f); + + b2Body* body1 = m_world->CreateBody(&triangleBodyDef); + body1->CreateFixture(&triangleShapeDef); + + // Large triangle (recycle definitions) + vertices[0] *= 2.0f; + vertices[1] *= 2.0f; + vertices[2] *= 2.0f; + polygon.Set(vertices, 3); + triangleShapeDef.filter.groupIndex = k_largeGroup; + triangleBodyDef.position.Set(-5.0f, 6.0f); + triangleBodyDef.fixedRotation = true; // look at me! + + b2Body* body2 = m_world->CreateBody(&triangleBodyDef); + body2->CreateFixture(&triangleShapeDef); + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-5.0f, 10.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape p; + p.SetAsBox(0.5f, 1.0f); + body->CreateFixture(&p, 1.0f); + + b2PrismaticJointDef jd; + jd.bodyA = body2; + jd.bodyB = body; + jd.enableLimit = true; + jd.localAnchorA.Set(0.0f, 4.0f); + jd.localAnchorB.SetZero(); + jd.localAxisA.Set(0.0f, 1.0f); + jd.lowerTranslation = -1.0f; + jd.upperTranslation = 1.0f; + + m_world->CreateJoint(&jd); + } + + // Small box + polygon.SetAsBox(1.0f, 0.5f); + b2FixtureDef boxShapeDef; + boxShapeDef.shape = &polygon; + boxShapeDef.density = 1.0f; + boxShapeDef.restitution = 0.1f; + + boxShapeDef.filter.groupIndex = k_smallGroup; + boxShapeDef.filter.categoryBits = k_boxCategory; + boxShapeDef.filter.maskBits = k_boxMask; + + b2BodyDef boxBodyDef; + boxBodyDef.type = b2_dynamicBody; + boxBodyDef.position.Set(0.0f, 2.0f); + + b2Body* body3 = m_world->CreateBody(&boxBodyDef); + body3->CreateFixture(&boxShapeDef); + + // Large box (recycle definitions) + polygon.SetAsBox(2.0f, 1.0f); + boxShapeDef.filter.groupIndex = k_largeGroup; + boxBodyDef.position.Set(0.0f, 6.0f); + + b2Body* body4 = m_world->CreateBody(&boxBodyDef); + body4->CreateFixture(&boxShapeDef); + + // Small circle + b2CircleShape circle; + circle.m_radius = 1.0f; + + b2FixtureDef circleShapeDef; + circleShapeDef.shape = &circle; + circleShapeDef.density = 1.0f; + + circleShapeDef.filter.groupIndex = k_smallGroup; + circleShapeDef.filter.categoryBits = k_circleCategory; + circleShapeDef.filter.maskBits = k_circleMask; + + b2BodyDef circleBodyDef; + circleBodyDef.type = b2_dynamicBody; + circleBodyDef.position.Set(5.0f, 2.0f); + + b2Body* body5 = m_world->CreateBody(&circleBodyDef); + body5->CreateFixture(&circleShapeDef); + + // Large circle + circle.m_radius *= 2.0f; + circleShapeDef.filter.groupIndex = k_largeGroup; + circleBodyDef.position.Set(5.0f, 6.0f); + + b2Body* body6 = m_world->CreateBody(&circleBodyDef); + body6->CreateFixture(&circleShapeDef); + } + + static Test* Create() + { + return new CollisionFiltering; + } +}; + +static int testIndex = RegisterTest("Examples", "Collision Filtering", CollisionFiltering::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/collision_processing.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/collision_processing.cpp new file mode 100644 index 0000000000..1c12ca22c6 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/collision_processing.cpp @@ -0,0 +1,191 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +#include + +// This test shows collision processing and tests +// deferred body destruction. +class CollisionProcessing : public Test +{ +public: + CollisionProcessing() + { + // Ground body + { + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-50.0f, 0.0f), b2Vec2(50.0f, 0.0f)); + + b2FixtureDef sd; + sd.shape = &shape;; + + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&sd); + } + + float xLo = -5.0f, xHi = 5.0f; + float yLo = 2.0f, yHi = 35.0f; + + // Small triangle + b2Vec2 vertices[3]; + vertices[0].Set(-1.0f, 0.0f); + vertices[1].Set(1.0f, 0.0f); + vertices[2].Set(0.0f, 2.0f); + + b2PolygonShape polygon; + polygon.Set(vertices, 3); + + b2FixtureDef triangleShapeDef; + triangleShapeDef.shape = &polygon; + triangleShapeDef.density = 1.0f; + + b2BodyDef triangleBodyDef; + triangleBodyDef.type = b2_dynamicBody; + triangleBodyDef.position.Set(RandomFloat(xLo, xHi), RandomFloat(yLo, yHi)); + + b2Body* body1 = m_world->CreateBody(&triangleBodyDef); + body1->CreateFixture(&triangleShapeDef); + + // Large triangle (recycle definitions) + vertices[0] *= 2.0f; + vertices[1] *= 2.0f; + vertices[2] *= 2.0f; + polygon.Set(vertices, 3); + + triangleBodyDef.position.Set(RandomFloat(xLo, xHi), RandomFloat(yLo, yHi)); + + b2Body* body2 = m_world->CreateBody(&triangleBodyDef); + body2->CreateFixture(&triangleShapeDef); + + // Small box + polygon.SetAsBox(1.0f, 0.5f); + + b2FixtureDef boxShapeDef; + boxShapeDef.shape = &polygon; + boxShapeDef.density = 1.0f; + + b2BodyDef boxBodyDef; + boxBodyDef.type = b2_dynamicBody; + boxBodyDef.position.Set(RandomFloat(xLo, xHi), RandomFloat(yLo, yHi)); + + b2Body* body3 = m_world->CreateBody(&boxBodyDef); + body3->CreateFixture(&boxShapeDef); + + // Large box (recycle definitions) + polygon.SetAsBox(2.0f, 1.0f); + boxBodyDef.position.Set(RandomFloat(xLo, xHi), RandomFloat(yLo, yHi)); + + b2Body* body4 = m_world->CreateBody(&boxBodyDef); + body4->CreateFixture(&boxShapeDef); + + // Small circle + b2CircleShape circle; + circle.m_radius = 1.0f; + + b2FixtureDef circleShapeDef; + circleShapeDef.shape = &circle; + circleShapeDef.density = 1.0f; + + b2BodyDef circleBodyDef; + circleBodyDef.type = b2_dynamicBody; + circleBodyDef.position.Set(RandomFloat(xLo, xHi), RandomFloat(yLo, yHi)); + + b2Body* body5 = m_world->CreateBody(&circleBodyDef); + body5->CreateFixture(&circleShapeDef); + + // Large circle + circle.m_radius *= 2.0f; + circleBodyDef.position.Set(RandomFloat(xLo, xHi), RandomFloat(yLo, yHi)); + + b2Body* body6 = m_world->CreateBody(&circleBodyDef); + body6->CreateFixture(&circleShapeDef); + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + // We are going to destroy some bodies according to contact + // points. We must buffer the bodies that should be destroyed + // because they may belong to multiple contact points. + const int32 k_maxNuke = 6; + b2Body* nuke[k_maxNuke]; + int32 nukeCount = 0; + + // Traverse the contact results. Destroy bodies that + // are touching heavier bodies. + for (int32 i = 0; i < m_pointCount; ++i) + { + ContactPoint* point = m_points + i; + + b2Body* body1 = point->fixtureA->GetBody(); + b2Body* body2 = point->fixtureB->GetBody(); + float mass1 = body1->GetMass(); + float mass2 = body2->GetMass(); + + if (mass1 > 0.0f && mass2 > 0.0f) + { + if (mass2 > mass1) + { + nuke[nukeCount++] = body1; + } + else + { + nuke[nukeCount++] = body2; + } + + if (nukeCount == k_maxNuke) + { + break; + } + } + } + + // Sort the nuke array to group duplicates. + std::sort(nuke, nuke + nukeCount); + + // Destroy the bodies, skipping duplicates. + int32 i = 0; + while (i < nukeCount) + { + b2Body* b = nuke[i++]; + while (i < nukeCount && nuke[i] == b) + { + ++i; + } + + if (b != m_bomb) + { + m_world->DestroyBody(b); + } + } + } + + static Test* Create() + { + return new CollisionProcessing; + } +}; + +static int testIndex = RegisterTest("Examples", "Collision Processing", CollisionProcessing::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/compound_shapes.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/compound_shapes.cpp new file mode 100644 index 0000000000..02f3d28e89 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/compound_shapes.cpp @@ -0,0 +1,227 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +//#include "imgui/imgui.h" + +class CompoundShapes : public Test +{ +public: + CompoundShapes() + { + { + b2BodyDef bd; + bd.position.Set(0.0f, 0.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(50.0f, 0.0f), b2Vec2(-50.0f, 0.0f)); + + body->CreateFixture(&shape, 0.0f); + } + + // Table 1 + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-15.0f, 1.0f); + m_table1 = m_world->CreateBody(&bd); + + b2PolygonShape top; + top.SetAsBox(3.0f, 0.5f, b2Vec2(0.0f, 3.5f), 0.0f); + + b2PolygonShape leftLeg; + leftLeg.SetAsBox(0.5f, 1.5f, b2Vec2(-2.5f, 1.5f), 0.0f); + + b2PolygonShape rightLeg; + rightLeg.SetAsBox(0.5f, 1.5f, b2Vec2(2.5f, 1.5f), 0.0f); + + m_table1->CreateFixture(&top, 2.0f); + m_table1->CreateFixture(&leftLeg, 2.0f); + m_table1->CreateFixture(&rightLeg, 2.0f); + } + + // Table 2 + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-5.0f, 1.0f); + m_table2 = m_world->CreateBody(&bd); + + b2PolygonShape top; + top.SetAsBox(3.0f, 0.5f, b2Vec2(0.0f, 3.5f), 0.0f); + + b2PolygonShape leftLeg; + leftLeg.SetAsBox(0.5f, 2.0f, b2Vec2(-2.5f, 2.0f), 0.0f); + + b2PolygonShape rightLeg; + rightLeg.SetAsBox(0.5f, 2.0f, b2Vec2(2.5f, 2.0f), 0.0f); + + m_table2->CreateFixture(&top, 2.0f); + m_table2->CreateFixture(&leftLeg, 2.0f); + m_table2->CreateFixture(&rightLeg, 2.0f); + } + + // Spaceship 1 + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(5.0f, 1.0f); + m_ship1 = m_world->CreateBody(&bd); + + b2Vec2 vertices[3]; + + b2PolygonShape left; + vertices[0].Set(-2.0f, 0.0f); + vertices[1].Set(0.0f, 4.0f / 3.0f); + vertices[2].Set(0.0f, 4.0f); + left.Set(vertices, 3); + + b2PolygonShape right; + vertices[0].Set(2.0f, 0.0f); + vertices[1].Set(0.0f, 4.0f / 3.0f); + vertices[2].Set(0.0f, 4.0f); + right.Set(vertices, 3); + + m_ship1->CreateFixture(&left, 2.0f); + m_ship1->CreateFixture(&right, 2.0f); + } + + // Spaceship 2 + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(15.0f, 1.0f); + m_ship2 = m_world->CreateBody(&bd); + + b2Vec2 vertices[3]; + + b2PolygonShape left; + vertices[0].Set(-2.0f, 0.0f); + vertices[1].Set(1.0f, 2.0f); + vertices[2].Set(0.0f, 4.0f); + left.Set(vertices, 3); + + b2PolygonShape right; + vertices[0].Set(2.0f, 0.0f); + vertices[1].Set(-1.0f, 2.0f); + vertices[2].Set(0.0f, 4.0f); + right.Set(vertices, 3); + + m_ship2->CreateFixture(&left, 2.0f); + m_ship2->CreateFixture(&right, 2.0f); + } + } + + void Spawn() + { + // Table 1 obstruction + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = m_table1->GetPosition(); + bd.angle = m_table1->GetAngle(); + + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape box; + box.SetAsBox(4.0f, 0.1f, b2Vec2(0.0f, 3.0f), 0.0f); + + body->CreateFixture(&box, 2.0f); + } + + // Table 2 obstruction + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = m_table2->GetPosition(); + bd.angle = m_table2->GetAngle(); + + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape box; + box.SetAsBox(4.0f, 0.1f, b2Vec2(0.0f, 3.0f), 0.0f); + + body->CreateFixture(&box, 2.0f); + } + + // Ship 1 obstruction + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = m_ship1->GetPosition(); + bd.angle = m_ship1->GetAngle(); + bd.gravityScale = 0.0f; + + b2Body* body = m_world->CreateBody(&bd); + + b2CircleShape circle; + circle.m_radius = 0.5f; + circle.m_p.Set(0.0f, 2.0f); + + body->CreateFixture(&circle, 2.0f); + } + + // Ship 2 obstruction + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = m_ship2->GetPosition(); + bd.angle = m_ship2->GetAngle(); + bd.gravityScale = 0.0f; + + b2Body* body = m_world->CreateBody(&bd); + + b2CircleShape circle; + circle.m_radius = 0.5f; + circle.m_p.Set(0.0f, 2.0f); + + body->CreateFixture(&circle, 2.0f); + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(200.0f, 100.0f)); + // ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // if (ImGui::Button("Spawn")) + // { + // Spawn(); + // } + + // ImGui::End(); + //} + + static Test* Create() + { + return new CompoundShapes; + } + + b2Body* m_table1; + b2Body* m_table2; + b2Body* m_ship1; + b2Body* m_ship2; +}; + +static int testIndex = RegisterTest("Examples", "Compound Shapes", CompoundShapes::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/confined.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/confined.cpp new file mode 100644 index 0000000000..8f2afac831 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/confined.cpp @@ -0,0 +1,170 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Confined : public Test +{ +public: + + enum + { + e_columnCount = 0, + e_rowCount = 0 + }; + + Confined() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + + // Floor + shape.SetTwoSided(b2Vec2(-10.0f, 0.0f), b2Vec2(10.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + + // Left wall + shape.SetTwoSided(b2Vec2(-10.0f, 0.0f), b2Vec2(-10.0f, 20.0f)); + ground->CreateFixture(&shape, 0.0f); + + // Right wall + shape.SetTwoSided(b2Vec2(10.0f, 0.0f), b2Vec2(10.0f, 20.0f)); + ground->CreateFixture(&shape, 0.0f); + + // Roof + shape.SetTwoSided(b2Vec2(-10.0f, 20.0f), b2Vec2(10.0f, 20.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + float radius = 0.5f; + b2CircleShape shape; + shape.m_p.SetZero(); + shape.m_radius = radius; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + fd.friction = 0.1f; + + for (int32 j = 0; j < e_columnCount; ++j) + { + for (int i = 0; i < e_rowCount; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-10.0f + (2.1f * j + 1.0f + 0.01f * i) * radius, (2.0f * i + 1.0f) * radius); + b2Body* body = m_world->CreateBody(&bd); + + body->CreateFixture(&fd); + } + } + + m_world->SetGravity(b2Vec2(0.0f, 0.0f)); + } + + void CreateCircle() + { + float radius = 2.0f; + b2CircleShape shape; + shape.m_p.SetZero(); + shape.m_radius = radius; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + fd.friction = 0.0f; + + b2Vec2 p(RandomFloat(), 3.0f + RandomFloat()); + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = p; + //bd.allowSleep = false; + b2Body* body = m_world->CreateBody(&bd); + + body->CreateFixture(&fd); + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_C: + // CreateCircle(); + // break; + // } + //} + + void Step(Settings* settings) override + { + bool sleeping = true; + for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext()) + { + if (b->GetType() != b2_dynamicBody) + { + continue; + } + + if (b->IsAwake()) + { + sleeping = false; + } + } + + if (m_stepCount == 180) + { + m_stepCount += 0; + } + + //if (sleeping) + //{ + // CreateCircle(); + //} + + Test::Step(settings); + + for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext()) + { + if (b->GetType() != b2_dynamicBody) + { + continue; + } + + b2Vec2 p = b->GetPosition(); + if (p.x <= -10.0f || 10.0f <= p.x || p.y <= 0.0f || 20.0f <= p.y) + { + p.x += 0.0f; + } + } + + //g_debugDraw.DrawString(5, m_textLine, "Press 'c' to create a circle."); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new Confined; + } +}; + +static int testIndex = RegisterTest("Solver", "Confined", Confined::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/continuous_test.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/continuous_test.cpp new file mode 100644 index 0000000000..3369ab96b6 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/continuous_test.cpp @@ -0,0 +1,160 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class ContinuousTest : public Test +{ +public: + + ContinuousTest() + { + { + b2BodyDef bd; + bd.position.Set(0.0f, 0.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2EdgeShape edge; + + edge.SetTwoSided(b2Vec2(-10.0f, 0.0f), b2Vec2(10.0f, 0.0f)); + body->CreateFixture(&edge, 0.0f); + + b2PolygonShape shape; + shape.SetAsBox(0.2f, 1.0f, b2Vec2(0.5f, 1.0f), 0.0f); + body->CreateFixture(&shape, 0.0f); + } + +#if 1 + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 20.0f); + //bd.angle = 0.1f; + + b2PolygonShape shape; + shape.SetAsBox(2.0f, 0.1f); + + m_body = m_world->CreateBody(&bd); + m_body->CreateFixture(&shape, 1.0f); + + m_angularVelocity = RandomFloat(-50.0f, 50.0f); + //m_angularVelocity = 46.661274f; + m_body->SetLinearVelocity(b2Vec2(0.0f, -100.0f)); + m_body->SetAngularVelocity(m_angularVelocity); + } +#else + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 2.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_p.SetZero(); + shape.m_radius = 0.5f; + body->CreateFixture(&shape, 1.0f); + + bd.bullet = true; + bd.position.Set(0.0f, 10.0f); + body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 1.0f); + body->SetLinearVelocity(b2Vec2(0.0f, -100.0f)); + } +#endif + + extern B2_API int32 b2_gjkCalls, b2_gjkIters, b2_gjkMaxIters; + extern B2_API int32 b2_toiCalls, b2_toiIters; + extern B2_API int32 b2_toiRootIters, b2_toiMaxRootIters; + extern B2_API float b2_toiTime, b2_toiMaxTime; + + b2_gjkCalls = 0; b2_gjkIters = 0; b2_gjkMaxIters = 0; + b2_toiCalls = 0; b2_toiIters = 0; + b2_toiRootIters = 0; b2_toiMaxRootIters = 0; + b2_toiTime = 0.0f; b2_toiMaxTime = 0.0f; + } + + void Launch() + { + extern B2_API int32 b2_gjkCalls, b2_gjkIters, b2_gjkMaxIters; + extern B2_API int32 b2_toiCalls, b2_toiIters; + extern B2_API int32 b2_toiRootIters, b2_toiMaxRootIters; + extern B2_API float b2_toiTime, b2_toiMaxTime; + + b2_gjkCalls = 0; b2_gjkIters = 0; b2_gjkMaxIters = 0; + b2_toiCalls = 0; b2_toiIters = 0; + b2_toiRootIters = 0; b2_toiMaxRootIters = 0; + b2_toiTime = 0.0f; b2_toiMaxTime = 0.0f; + + m_body->SetTransform(b2Vec2(0.0f, 20.0f), 0.0f); + m_angularVelocity = RandomFloat(-50.0f, 50.0f); + m_body->SetLinearVelocity(b2Vec2(0.0f, -100.0f)); + m_body->SetAngularVelocity(m_angularVelocity); + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + extern B2_API int32 b2_gjkCalls, b2_gjkIters, b2_gjkMaxIters; + + if (b2_gjkCalls > 0) + { + //g_debugDraw.DrawString(5, m_textLine, "gjk calls = %d, ave gjk iters = %3.1f, max gjk iters = %d", + // b2_gjkCalls, b2_gjkIters / float(b2_gjkCalls), b2_gjkMaxIters); + //m_textLine += m_textIncrement; + } + + extern B2_API int32 b2_toiCalls, b2_toiIters; + extern B2_API int32 b2_toiRootIters, b2_toiMaxRootIters; + extern B2_API float b2_toiTime, b2_toiMaxTime; + + if (b2_toiCalls > 0) + { + //g_debugDraw.DrawString(5, m_textLine, "toi calls = %d, ave [max] toi iters = %3.1f [%d]", + // b2_toiCalls, b2_toiIters / float(b2_toiCalls), b2_toiMaxRootIters); + //m_textLine += m_textIncrement; + // + //g_debugDraw.DrawString(5, m_textLine, "ave [max] toi root iters = %3.1f [%d]", + // b2_toiRootIters / float(b2_toiCalls), b2_toiMaxRootIters); + //m_textLine += m_textIncrement; + + //g_debugDraw.DrawString(5, m_textLine, "ave [max] toi time = %.1f [%.1f] (microseconds)", + // 1000.0f * b2_toiTime / float(b2_toiCalls), 1000.0f * b2_toiMaxTime); + //m_textLine += m_textIncrement; + } + + if (m_stepCount % 60 == 0) + { + //Launch(); + } + } + + static Test* Create() + { + return new ContinuousTest; + } + + b2Body* m_body; + float m_angularVelocity; +}; + +static int testIndex = RegisterTest("Continuous", "Continuous Test", ContinuousTest::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/convex_hull.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/convex_hull.cpp new file mode 100644 index 0000000000..a1996f9f40 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/convex_hull.cpp @@ -0,0 +1,112 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class ConvexHull : public Test +{ +public: + enum + { + e_count = b2_maxPolygonVertices + }; + + ConvexHull() + { + Generate(); + m_auto = false; + } + + void Generate() + { + b2Vec2 lowerBound(-8.0f, -8.0f); + b2Vec2 upperBound(8.0f, 8.0f); + + for (int32 i = 0; i < e_count; ++i) + { + float x = 10.0f * RandomFloat(); + float y = 10.0f * RandomFloat(); + + // Clamp onto a square to help create collinearities. + // This will stress the convex hull algorithm. + b2Vec2 v(x, y); + v = b2Clamp(v, lowerBound, upperBound); + m_points[i] = v; + } + + m_count = e_count; + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_auto = !m_auto; + // break; + + // case GLFW_KEY_G: + // Generate(); + // break; + // } + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + + b2PolygonShape shape; + shape.Set(m_points, m_count); + + //g_debugDraw.DrawString(5, m_textLine, "Press g to generate a new random convex hull"); + //m_textLine += m_textIncrement; + + // g_debugDraw.DrawPolygon(shape.m_vertices, shape.m_count, b2Color(0.9f, 0.9f, 0.9f)); + + for (int32 i = 0; i < m_count; ++i) + { + //g_debugDraw.DrawPoint(m_points[i], 3.0f, b2Color(0.3f, 0.9f, 0.3f)); + //g_debugDraw.DrawString(m_points[i] + b2Vec2(0.05f, 0.05f), "%d", i); + } + + if (shape.Validate() == false) + { + m_textLine += 0; + } + + if (m_auto) + { + Generate(); + } + } + + static Test* Create() + { + return new ConvexHull; + } + + b2Vec2 m_points[b2_maxPolygonVertices]; + int32 m_count; + bool m_auto; +}; + +static int testIndex = RegisterTest("Geometry", "Convex Hull", ConvexHull::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/conveyor_belt.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/conveyor_belt.cpp new file mode 100644 index 0000000000..1ef99aa574 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/conveyor_belt.cpp @@ -0,0 +1,101 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class ConveyorBelt : public Test +{ +public: + + ConveyorBelt() + { + // Ground + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-20.0f, 0.0f), b2Vec2(20.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + // Platform + { + b2BodyDef bd; + bd.position.Set(-5.0f, 5.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(10.0f, 0.5f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.friction = 0.8f; + m_platform = body->CreateFixture(&fd); + } + + // Boxes + for (int32 i = 0; i < 5; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-10.0f + 2.0f * i, 7.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.5f); + body->CreateFixture(&shape, 20.0f); + } + } + + void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) override + { + Test::PreSolve(contact, oldManifold); + + b2Fixture* fixtureA = contact->GetFixtureA(); + b2Fixture* fixtureB = contact->GetFixtureB(); + + if (fixtureA == m_platform) + { + contact->SetTangentSpeed(5.0f); + } + + if (fixtureB == m_platform) + { + contact->SetTangentSpeed(-5.0f); + } + } + + void Step(Settings* settings) override + { + Test::Step(settings); + } + + static Test* Create() + { + return new ConveyorBelt; + } + + b2Fixture* m_platform; +}; + +static int testIndex = RegisterTest("Examples", "Conveyor Belt", ConveyorBelt::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/distance_joint.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/distance_joint.cpp new file mode 100644 index 0000000000..c7c478c927 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/distance_joint.cpp @@ -0,0 +1,123 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +//#include "imgui/imgui.h" + +// This tests distance joints, body destruction, and joint destruction. +class DistanceJoint : public Test +{ +public: + DistanceJoint() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.angularDamping = 0.1f; + + bd.position.Set(0.0f, 5.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.5f); + body->CreateFixture(&shape, 5.0f); + + m_hertz = 1.0f; + m_dampingRatio = 0.7f; + + b2DistanceJointDef jd; + jd.Initialize(ground, body, b2Vec2(0.0f, 15.0f), bd.position); + jd.collideConnected = true; + m_length = jd.length; + m_minLength = m_length; + m_maxLength = m_length; + b2LinearStiffness(jd.stiffness, jd.damping, m_hertz, m_dampingRatio, jd.bodyA, jd.bodyB); + m_joint = (b2DistanceJoint*)m_world->CreateJoint(&jd); + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(260.0f, 150.0f)); + // ImGui::Begin("Joint Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // if (ImGui::SliderFloat("Length", &m_length, 0.0f, 20.0f, "%.0f")) + // { + // m_length = m_joint->SetLength(m_length); + // } + + // if (ImGui::SliderFloat("Min Length", &m_minLength, 0.0f, 20.0f, "%.0f")) + // { + // m_minLength = m_joint->SetMinLength(m_minLength); + // } + + // if (ImGui::SliderFloat("Max Length", &m_maxLength, 0.0f, 20.0f, "%.0f")) + // { + // m_maxLength = m_joint->SetMaxLength(m_maxLength); + // } + + // if (ImGui::SliderFloat("Hertz", &m_hertz, 0.0f, 10.0f, "%.1f")) + // { + // float stiffness; + // float damping; + // b2LinearStiffness(stiffness, damping, m_hertz, m_dampingRatio, m_joint->GetBodyA(), m_joint->GetBodyB()); + // m_joint->SetStiffness(stiffness); + // m_joint->SetDamping(damping); + // } + + // if (ImGui::SliderFloat("Damping Ratio", &m_dampingRatio, 0.0f, 2.0f, "%.1f")) + // { + // float stiffness; + // float damping; + // b2LinearStiffness(stiffness, damping, m_hertz, m_dampingRatio, m_joint->GetBodyA(), m_joint->GetBodyB()); + // m_joint->SetStiffness(stiffness); + // m_joint->SetDamping(damping); + // } + + // ImGui::End(); + //} + + static Test* Create() + { + return new DistanceJoint; + } + + b2DistanceJoint* m_joint; + float m_length; + float m_minLength; + float m_maxLength; + float m_hertz; + float m_dampingRatio; +}; + +static int testIndex = RegisterTest("Joints", "Distance Joint", DistanceJoint::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/distance_test.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/distance_test.cpp new file mode 100644 index 0000000000..05954c5ca0 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/distance_test.cpp @@ -0,0 +1,139 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +#include "box2d/b2_distance.h" + +class DistanceTest : public Test +{ +public: + DistanceTest() + { + { + m_transformA.SetIdentity(); + m_transformA.p.Set(0.0f, -0.2f); + m_polygonA.SetAsBox(10.0f, 0.2f); + } + + { + m_positionB.Set(12.017401f, 0.13678508f); + m_angleB = -0.0109265f; + m_transformB.Set(m_positionB, m_angleB); + + m_polygonB.SetAsBox(2.0f, 0.1f); + } + } + + static Test* Create() + { + return new DistanceTest; + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + b2DistanceInput input; + input.proxyA.Set(&m_polygonA, 0); + input.proxyB.Set(&m_polygonB, 0); + input.transformA = m_transformA; + input.transformB = m_transformB; + input.useRadii = true; + b2SimplexCache cache; + cache.count = 0; + b2DistanceOutput output; + b2Distance(&output, &cache, &input); + + //g_debugDraw.DrawString(5, m_textLine, "distance = %g", output.distance); + //m_textLine += m_textIncrement; + + //g_debugDraw.DrawString(5, m_textLine, "iterations = %d", output.iterations); + //m_textLine += m_textIncrement; + + { + b2Color color(0.9f, 0.9f, 0.9f); + b2Vec2 v[b2_maxPolygonVertices]; + for (int32 i = 0; i < m_polygonA.m_count; ++i) + { + v[i] = b2Mul(m_transformA, m_polygonA.m_vertices[i]); + } + // g_debugDraw.DrawPolygon(v, m_polygonA.m_count, color); + + for (int32 i = 0; i < m_polygonB.m_count; ++i) + { + v[i] = b2Mul(m_transformB, m_polygonB.m_vertices[i]); + } + // g_debugDraw.DrawPolygon(v, m_polygonB.m_count, color); + } + + b2Vec2 x1 = output.pointA; + b2Vec2 x2 = output.pointB; + + //b2Color c1(1.0f, 0.0f, 0.0f); + //g_debugDraw.DrawPoint(x1, 4.0f, c1); + + //b2Color c2(1.0f, 1.0f, 0.0f); + //g_debugDraw.DrawPoint(x2, 4.0f, c2); + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_positionB.x -= 0.1f; + // break; + + // case GLFW_KEY_D: + // m_positionB.x += 0.1f; + // break; + + // case GLFW_KEY_S: + // m_positionB.y -= 0.1f; + // break; + + // case GLFW_KEY_W: + // m_positionB.y += 0.1f; + // break; + + // case GLFW_KEY_Q: + // m_angleB += 0.1f * b2_pi; + // break; + + // case GLFW_KEY_E: + // m_angleB -= 0.1f * b2_pi; + // break; + // } + + // m_transformB.Set(m_positionB, m_angleB); + //} + + b2Vec2 m_positionB; + float m_angleB; + + b2Transform m_transformA; + b2Transform m_transformB; + b2PolygonShape m_polygonA; + b2PolygonShape m_polygonB; +}; + +static int testIndex = RegisterTest("Geometry", "Distance Test", DistanceTest::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/dominos.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/dominos.cpp new file mode 100644 index 0000000000..bbbbb85da4 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/dominos.cpp @@ -0,0 +1,220 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Dominos : public Test +{ +public: + + Dominos() + { + b2Body* b1; + { + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + + b2BodyDef bd; + b1 = m_world->CreateBody(&bd); + b1->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(6.0f, 0.25f); + + b2BodyDef bd; + bd.position.Set(-1.5f, 10.0f); + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.1f, 1.0f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + fd.friction = 0.1f; + + for (int i = 0; i < 10; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-6.0f + 1.0f * i, 11.25f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&fd); + } + } + + { + b2PolygonShape shape; + shape.SetAsBox(7.0f, 0.25f, b2Vec2_zero, 0.3f); + + b2BodyDef bd; + bd.position.Set(1.0f, 6.0f); + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&shape, 0.0f); + } + + b2Body* b2; + { + b2PolygonShape shape; + shape.SetAsBox(0.25f, 1.5f); + + b2BodyDef bd; + bd.position.Set(-7.0f, 4.0f); + b2 = m_world->CreateBody(&bd); + b2->CreateFixture(&shape, 0.0f); + } + + b2Body* b3; + { + b2PolygonShape shape; + shape.SetAsBox(6.0f, 0.125f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-0.9f, 1.0f); + bd.angle = -0.15f; + + b3 = m_world->CreateBody(&bd); + b3->CreateFixture(&shape, 10.0f); + } + + b2RevoluteJointDef jd; + b2Vec2 anchor; + + anchor.Set(-2.0f, 1.0f); + jd.Initialize(b1, b3, anchor); + jd.collideConnected = true; + m_world->CreateJoint(&jd); + + b2Body* b4; + { + b2PolygonShape shape; + shape.SetAsBox(0.25f, 0.25f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-10.0f, 15.0f); + b4 = m_world->CreateBody(&bd); + b4->CreateFixture(&shape, 10.0f); + } + + anchor.Set(-7.0f, 15.0f); + jd.Initialize(b2, b4, anchor); + m_world->CreateJoint(&jd); + + b2Body* b5; + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(6.5f, 3.0f); + b5 = m_world->CreateBody(&bd); + + b2PolygonShape shape; + b2FixtureDef fd; + + fd.shape = &shape; + fd.density = 10.0f; + fd.friction = 0.1f; + + shape.SetAsBox(1.0f, 0.1f, b2Vec2(0.0f, -0.9f), 0.0f); + b5->CreateFixture(&fd); + + shape.SetAsBox(0.1f, 1.0f, b2Vec2(-0.9f, 0.0f), 0.0f); + b5->CreateFixture(&fd); + + shape.SetAsBox(0.1f, 1.0f, b2Vec2(0.9f, 0.0f), 0.0f); + b5->CreateFixture(&fd); + } + + anchor.Set(6.0f, 2.0f); + jd.Initialize(b1, b5, anchor); + m_world->CreateJoint(&jd); + + b2Body* b6; + { + b2PolygonShape shape; + shape.SetAsBox(1.0f, 0.1f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(6.5f, 4.1f); + b6 = m_world->CreateBody(&bd); + b6->CreateFixture(&shape, 30.0f); + } + + anchor.Set(7.5f, 4.0f); + jd.Initialize(b5, b6, anchor); + m_world->CreateJoint(&jd); + + b2Body* b7; + { + b2PolygonShape shape; + shape.SetAsBox(0.1f, 1.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(7.4f, 1.0f); + + b7 = m_world->CreateBody(&bd); + b7->CreateFixture(&shape, 10.0f); + } + + b2DistanceJointDef djd; + djd.bodyA = b3; + djd.bodyB = b7; + djd.localAnchorA.Set(6.0f, 0.0f); + djd.localAnchorB.Set(0.0f, -1.0f); + b2Vec2 d = djd.bodyB->GetWorldPoint(djd.localAnchorB) - djd.bodyA->GetWorldPoint(djd.localAnchorA); + djd.length = d.Length(); + + b2LinearStiffness(djd.stiffness, djd.damping, 1.0f, 1.0f, djd.bodyA, djd.bodyB); + m_world->CreateJoint(&djd); + + { + float radius = 0.2f; + + b2CircleShape shape; + shape.m_radius = radius; + + for (int32 i = 0; i < 4; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(5.9f + 2.0f * radius * i, 2.4f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 10.0f); + } + } + } + + static Test* Create() + { + return new Dominos; + } +}; + +static int testIndex = RegisterTest("Examples", "Dominos", Dominos::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/dump_loader.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/dump_loader.cpp new file mode 100644 index 0000000000..acedbdd54d --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/dump_loader.cpp @@ -0,0 +1,88 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// This test holds worlds dumped using b2World::Dump. +class DumpLoader : public Test +{ +public: + + DumpLoader() + { + b2ChainShape chainShape; + b2Vec2 vertices[] = {b2Vec2(-5,0), b2Vec2(5,0), b2Vec2(5,5), b2Vec2(4,1), b2Vec2(-4,1), b2Vec2(-5,5)}; + chainShape.CreateLoop(vertices, 6); + + b2FixtureDef groundFixtureDef; + groundFixtureDef.density = 0; + groundFixtureDef.shape = &chainShape; + + b2BodyDef groundBodyDef; + groundBodyDef.type = b2_staticBody; + + b2Body *groundBody = m_world->CreateBody(&groundBodyDef); + b2Fixture *groundBodyFixture = groundBody->CreateFixture(&groundFixtureDef); + + b2CircleShape ballShape; + ballShape.m_radius = 1; + + b2FixtureDef ballFixtureDef; + ballFixtureDef.restitution = 0.75f; + ballFixtureDef.density = 1; + ballFixtureDef.shape = &ballShape; + + b2BodyDef ballBodyDef; + ballBodyDef.type = b2BodyType::b2_dynamicBody; + ballBodyDef.position = b2Vec2(0, 10); + // ballBodyDef.angularDamping = 0.2f; + + m_ball = m_world->CreateBody(&ballBodyDef); + b2Fixture *ballFixture = m_ball->CreateFixture(&ballFixtureDef); + m_ball->ApplyForceToCenter(b2Vec2(-1000, -400), true); + } + + void Step(Settings* settings) override + { + b2Vec2 v = m_ball->GetLinearVelocity(); + float omega = m_ball->GetAngularVelocity(); + + b2MassData massData; + m_ball->GetMassData(&massData); + + float ke = 0.5f * massData.mass * b2Dot(v, v) + 0.5f * massData.I * omega * omega; + + //g_debugDraw.DrawString(5, m_textLine, "kinetic energy = %.6f", ke); + //m_textLine += m_textIncrement; + + Test::Step(settings); + } + + static Test* Create() + { + return new DumpLoader; + } + + b2Body* m_ball; +}; + +static int testIndex = RegisterTest("Bugs", "Dump Loader", DumpLoader::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/dynamic_tree.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/dynamic_tree.cpp new file mode 100644 index 0000000000..fc6f90a702 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/dynamic_tree.cpp @@ -0,0 +1,360 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class DynamicTree : public Test +{ +public: + + enum + { + e_actorCount = 128 + }; + + DynamicTree() + { + m_worldExtent = 15.0f; + m_proxyExtent = 0.5f; + + srand(888); + + for (int32 i = 0; i < e_actorCount; ++i) + { + Actor* actor = m_actors + i; + GetRandomAABB(&actor->aabb); + actor->proxyId = m_tree.CreateProxy(actor->aabb, actor); + } + + m_stepCount = 0; + + float h = m_worldExtent; + m_queryAABB.lowerBound.Set(-3.0f, -4.0f + h); + m_queryAABB.upperBound.Set(5.0f, 6.0f + h); + + m_rayCastInput.p1.Set(-5.0, 5.0f + h); + m_rayCastInput.p2.Set(7.0f, -4.0f + h); + //m_rayCastInput.p1.Set(0.0f, 2.0f + h); + //m_rayCastInput.p2.Set(0.0f, -2.0f + h); + m_rayCastInput.maxFraction = 1.0f; + + m_automated = false; + } + + static Test* Create() + { + return new DynamicTree; + } + + void Step(Settings* settings) override + { + B2_NOT_USED(settings); + + m_rayActor = NULL; + for (int32 i = 0; i < e_actorCount; ++i) + { + m_actors[i].fraction = 1.0f; + m_actors[i].overlap = false; + } + + if (m_automated == true) + { + int32 actionCount = b2Max(1, e_actorCount >> 2); + + for (int32 i = 0; i < actionCount; ++i) + { + Action(); + } + } + + Query(); + RayCast(); + + for (int32 i = 0; i < e_actorCount; ++i) + { + Actor* actor = m_actors + i; + if (actor->proxyId == b2_nullNode) + continue; + + b2Color c(0.9f, 0.9f, 0.9f); + if (actor == m_rayActor && actor->overlap) + { + c.Set(0.9f, 0.6f, 0.6f); + } + else if (actor == m_rayActor) + { + c.Set(0.6f, 0.9f, 0.6f); + } + else if (actor->overlap) + { + c.Set(0.6f, 0.6f, 0.9f); + } + + //g_debugDraw.DrawAABB(&actor->aabb, c); + } + + //b2Color c(0.7f, 0.7f, 0.7f); + //g_debugDraw.DrawAABB(&m_queryAABB, c); + + //g_debugDraw.DrawSegment(m_rayCastInput.p1, m_rayCastInput.p2, c); + + //b2Color c1(0.2f, 0.9f, 0.2f); + //b2Color c2(0.9f, 0.2f, 0.2f); + //g_debugDraw.DrawPoint(m_rayCastInput.p1, 6.0f, c1); + //g_debugDraw.DrawPoint(m_rayCastInput.p2, 6.0f, c2); + + if (m_rayActor) + { + //b2Color cr(0.2f, 0.2f, 0.9f); + //b2Vec2 p = m_rayCastInput.p1 + m_rayActor->fraction * (m_rayCastInput.p2 - m_rayCastInput.p1); + //g_debugDraw.DrawPoint(p, 6.0f, cr); + } + + { + //int32 height = m_tree.GetHeight(); + //g_debugDraw.DrawString(5, m_textLine, "dynamic tree height = %d", height); + //m_textLine += m_textIncrement; + } + + ++m_stepCount; + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_automated = !m_automated; + // break; + + // case GLFW_KEY_C: + // CreateProxy(); + // break; + + // case GLFW_KEY_D: + // DestroyProxy(); + // break; + + // case GLFW_KEY_M: + // MoveProxy(); + // break; + // } + //} + + bool QueryCallback(int32 proxyId) + { + Actor* actor = (Actor*)m_tree.GetUserData(proxyId); + actor->overlap = b2TestOverlap(m_queryAABB, actor->aabb); + return true; + } + + float RayCastCallback(const b2RayCastInput& input, int32 proxyId) + { + Actor* actor = (Actor*)m_tree.GetUserData(proxyId); + + b2RayCastOutput output; + bool hit = actor->aabb.RayCast(&output, input); + + if (hit) + { + m_rayCastOutput = output; + m_rayActor = actor; + m_rayActor->fraction = output.fraction; + return output.fraction; + } + + return input.maxFraction; + } + +private: + + struct Actor + { + b2AABB aabb; + float fraction; + bool overlap; + int32 proxyId; + }; + + void GetRandomAABB(b2AABB* aabb) + { + b2Vec2 w; w.Set(2.0f * m_proxyExtent, 2.0f * m_proxyExtent); + //aabb->lowerBound.x = -m_proxyExtent; + //aabb->lowerBound.y = -m_proxyExtent + m_worldExtent; + aabb->lowerBound.x = RandomFloat(-m_worldExtent, m_worldExtent); + aabb->lowerBound.y = RandomFloat(0.0f, 2.0f * m_worldExtent); + aabb->upperBound = aabb->lowerBound + w; + } + + void MoveAABB(b2AABB* aabb) + { + b2Vec2 d; + d.x = RandomFloat(-0.5f, 0.5f); + d.y = RandomFloat(-0.5f, 0.5f); + //d.x = 2.0f; + //d.y = 0.0f; + aabb->lowerBound += d; + aabb->upperBound += d; + + b2Vec2 c0 = 0.5f * (aabb->lowerBound + aabb->upperBound); + b2Vec2 min; min.Set(-m_worldExtent, 0.0f); + b2Vec2 max; max.Set(m_worldExtent, 2.0f * m_worldExtent); + b2Vec2 c = b2Clamp(c0, min, max); + + aabb->lowerBound += c - c0; + aabb->upperBound += c - c0; + } + + void CreateProxy() + { + for (int32 i = 0; i < e_actorCount; ++i) + { + int32 j = rand() % e_actorCount; + Actor* actor = m_actors + j; + if (actor->proxyId == b2_nullNode) + { + GetRandomAABB(&actor->aabb); + actor->proxyId = m_tree.CreateProxy(actor->aabb, actor); + return; + } + } + } + + void DestroyProxy() + { + for (int32 i = 0; i < e_actorCount; ++i) + { + int32 j = rand() % e_actorCount; + Actor* actor = m_actors + j; + if (actor->proxyId != b2_nullNode) + { + m_tree.DestroyProxy(actor->proxyId); + actor->proxyId = b2_nullNode; + return; + } + } + } + + void MoveProxy() + { + for (int32 i = 0; i < e_actorCount; ++i) + { + int32 j = rand() % e_actorCount; + Actor* actor = m_actors + j; + if (actor->proxyId == b2_nullNode) + { + continue; + } + + b2AABB aabb0 = actor->aabb; + MoveAABB(&actor->aabb); + b2Vec2 displacement = actor->aabb.GetCenter() - aabb0.GetCenter(); + m_tree.MoveProxy(actor->proxyId, actor->aabb, displacement); + return; + } + } + + void Action() + { + int32 choice = rand() % 20; + + switch (choice) + { + case 0: + CreateProxy(); + break; + + case 1: + DestroyProxy(); + break; + + default: + MoveProxy(); + } + } + + void Query() + { + m_tree.Query(this, m_queryAABB); + + for (int32 i = 0; i < e_actorCount; ++i) + { + if (m_actors[i].proxyId == b2_nullNode) + { + continue; + } + + bool overlap = b2TestOverlap(m_queryAABB, m_actors[i].aabb); + B2_NOT_USED(overlap); + b2Assert(overlap == m_actors[i].overlap); + } + } + + void RayCast() + { + m_rayActor = NULL; + + b2RayCastInput input = m_rayCastInput; + + // Ray cast against the dynamic tree. + m_tree.RayCast(this, input); + + // Brute force ray cast. + Actor* bruteActor = NULL; + b2RayCastOutput bruteOutput; + for (int32 i = 0; i < e_actorCount; ++i) + { + if (m_actors[i].proxyId == b2_nullNode) + { + continue; + } + + b2RayCastOutput output; + bool hit = m_actors[i].aabb.RayCast(&output, input); + if (hit) + { + bruteActor = m_actors + i; + bruteOutput = output; + input.maxFraction = output.fraction; + } + } + + if (bruteActor != NULL) + { + b2Assert(bruteOutput.fraction == m_rayCastOutput.fraction); + } + } + + float m_worldExtent; + float m_proxyExtent; + + b2DynamicTree m_tree; + b2AABB m_queryAABB; + b2RayCastInput m_rayCastInput; + b2RayCastOutput m_rayCastOutput; + Actor* m_rayActor; + Actor m_actors[e_actorCount]; + int32 m_stepCount; + bool m_automated; +}; + +static int testIndex = RegisterTest("Collision", "Dynamic Tree", DynamicTree::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/edge_shapes.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/edge_shapes.cpp new file mode 100644 index 0000000000..d0f3372d69 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/edge_shapes.cpp @@ -0,0 +1,253 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" + +class EdgeShapesCallback : public b2RayCastCallback +{ +public: + EdgeShapesCallback() + { + m_fixture = NULL; + } + + float ReportFixture(b2Fixture* fixture, const b2Vec2& point, + const b2Vec2& normal, float fraction) override + { + m_fixture = fixture; + m_point = point; + m_normal = normal; + + return fraction; + } + + b2Fixture* m_fixture; + b2Vec2 m_point; + b2Vec2 m_normal; +}; + +class EdgeShapes : public Test +{ +public: + + enum + { + e_maxBodies = 256 + }; + + EdgeShapes() + { + // Ground body + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + float x1 = -20.0f; + float y1 = 2.0f * cosf(x1 / 10.0f * b2_pi); + for (int32 i = 0; i < 80; ++i) + { + float x2 = x1 + 0.5f; + float y2 = 2.0f * cosf(x2 / 10.0f * b2_pi); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(x1, y1), b2Vec2(x2, y2)); + ground->CreateFixture(&shape, 0.0f); + + x1 = x2; + y1 = y2; + } + } + + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.5f, 0.0f); + vertices[1].Set(0.5f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + m_polygons[0].Set(vertices, 3); + } + + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.1f, 0.0f); + vertices[1].Set(0.1f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + m_polygons[1].Set(vertices, 3); + } + + { + float w = 1.0f; + float b = w / (2.0f + b2Sqrt(2.0f)); + float s = b2Sqrt(2.0f) * b; + + b2Vec2 vertices[8]; + vertices[0].Set(0.5f * s, 0.0f); + vertices[1].Set(0.5f * w, b); + vertices[2].Set(0.5f * w, b + s); + vertices[3].Set(0.5f * s, w); + vertices[4].Set(-0.5f * s, w); + vertices[5].Set(-0.5f * w, b + s); + vertices[6].Set(-0.5f * w, b); + vertices[7].Set(-0.5f * s, 0.0f); + + m_polygons[2].Set(vertices, 8); + } + + { + m_polygons[3].SetAsBox(0.5f, 0.5f); + } + + { + m_circle.m_radius = 0.5f; + } + + m_bodyIndex = 0; + memset(m_bodies, 0, sizeof(m_bodies)); + + m_angle = 0.0f; + } + + void Create(int32 index) + { + if (m_bodies[m_bodyIndex] != NULL) + { + m_world->DestroyBody(m_bodies[m_bodyIndex]); + m_bodies[m_bodyIndex] = NULL; + } + + b2BodyDef bd; + + float x = RandomFloat(-10.0f, 10.0f); + float y = RandomFloat(10.0f, 20.0f); + bd.position.Set(x, y); + bd.angle = RandomFloat(-b2_pi, b2_pi); + bd.type = b2_dynamicBody; + + if (index == 4) + { + bd.angularDamping = 0.02f; + } + + m_bodies[m_bodyIndex] = m_world->CreateBody(&bd); + + if (index < 4) + { + b2FixtureDef fd; + fd.shape = m_polygons + index; + fd.friction = 0.3f; + fd.density = 20.0f; + m_bodies[m_bodyIndex]->CreateFixture(&fd); + } + else + { + b2FixtureDef fd; + fd.shape = &m_circle; + fd.friction = 0.3f; + fd.density = 20.0f; + m_bodies[m_bodyIndex]->CreateFixture(&fd); + } + + m_bodyIndex = (m_bodyIndex + 1) % e_maxBodies; + } + + void DestroyBody() + { + for (int32 i = 0; i < e_maxBodies; ++i) + { + if (m_bodies[i] != NULL) + { + m_world->DestroyBody(m_bodies[i]); + m_bodies[i] = NULL; + return; + } + } + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_1: + // case GLFW_KEY_2: + // case GLFW_KEY_3: + // case GLFW_KEY_4: + // case GLFW_KEY_5: + // Create(key - GLFW_KEY_1); + // break; + + // case GLFW_KEY_D: + // DestroyBody(); + // break; + // } + //} + + void Step(Settings* settings) override + { + bool advanceRay = 0.1;// settings.m_pause == 0 || settings.m_singleStep; + + Test::Step(settings); + //g_debugDraw.DrawString(5, m_textLine, "Press 1-5 to drop stuff"); + //m_textLine += m_textIncrement; + + float L = 25.0f; + b2Vec2 point1(0.0f, 10.0f); + b2Vec2 d(L * cosf(m_angle), -L * b2Abs(sinf(m_angle))); + b2Vec2 point2 = point1 + d; + + EdgeShapesCallback callback; + + m_world->RayCast(&callback, point1, point2); + + //if (callback.m_fixture) + //{ + // g_debugDraw.DrawPoint(callback.m_point, 5.0f, b2Color(0.4f, 0.9f, 0.4f)); + + // g_debugDraw.DrawSegment(point1, callback.m_point, b2Color(0.8f, 0.8f, 0.8f)); + + // b2Vec2 head = callback.m_point + 0.5f * callback.m_normal; + // g_debugDraw.DrawSegment(callback.m_point, head, b2Color(0.9f, 0.9f, 0.4f)); + //} + //else + //{ + // g_debugDraw.DrawSegment(point1, point2, b2Color(0.8f, 0.8f, 0.8f)); + //} + + if (advanceRay) + { + m_angle += 0.25f * b2_pi / 180.0f; + } + } + + static Test* Create() + { + return new EdgeShapes; + } + + int32 m_bodyIndex; + b2Body* m_bodies[e_maxBodies]; + b2PolygonShape m_polygons[4]; + b2CircleShape m_circle; + + float m_angle; +}; + +static int testIndex = RegisterTest("Geometry", "Edge Shapes", EdgeShapes::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/edge_test.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/edge_test.cpp new file mode 100644 index 0000000000..49948cc659 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/edge_test.cpp @@ -0,0 +1,282 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +//#include "imgui/imgui.h" + +class EdgeTest : public Test +{ +public: + + EdgeTest() + { + b2Vec2 vertices[10] = + { + {10.0f, -4.0f}, + {10.0f, 0.0f}, + {6.0f, 0.0f}, + {4.0f, 2.0f}, + {2.0f, 0.0f}, + {-2.0f, 0.0f}, + {-6.0f, 0.0f}, + {-8.0f, -3.0f}, + {-10.0f, 0.0f}, + {-10.0f, -4.0f} + }; + + m_offset1.Set(0.0f, 8.0f); + m_offset2.Set(0.0f, 16.0f); + + { + b2Vec2 v1 = vertices[0] + m_offset1; + b2Vec2 v2 = vertices[1] + m_offset1; + b2Vec2 v3 = vertices[2] + m_offset1; + b2Vec2 v4 = vertices[3] + m_offset1; + b2Vec2 v5 = vertices[4] + m_offset1; + b2Vec2 v6 = vertices[5] + m_offset1; + b2Vec2 v7 = vertices[6] + m_offset1; + b2Vec2 v8 = vertices[7] + m_offset1; + b2Vec2 v9 = vertices[8] + m_offset1; + b2Vec2 v10 = vertices[9] + m_offset1; + + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + + shape.SetOneSided(v10, v1, v2, v3); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v1, v2, v3, v4); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v2, v3, v4, v5); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v3, v4, v5, v6); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v4, v5, v6, v7); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v5, v6, v7, v8); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v6, v7, v8, v9); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v7, v8, v9, v10); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v8, v9, v10, v1); + ground->CreateFixture(&shape, 0.0f); + + shape.SetOneSided(v9, v10, v1, v2); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2Vec2 v1 = vertices[0] + m_offset2; + b2Vec2 v2 = vertices[1] + m_offset2; + b2Vec2 v3 = vertices[2] + m_offset2; + b2Vec2 v4 = vertices[3] + m_offset2; + b2Vec2 v5 = vertices[4] + m_offset2; + b2Vec2 v6 = vertices[5] + m_offset2; + b2Vec2 v7 = vertices[6] + m_offset2; + b2Vec2 v8 = vertices[7] + m_offset2; + b2Vec2 v9 = vertices[8] + m_offset2; + b2Vec2 v10 = vertices[9] + m_offset2; + + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + + shape.SetTwoSided(v1, v2); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v2, v3); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v3, v4); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v4, v5); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v5, v6); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v6, v7); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v7, v8); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v8, v9); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v9, v10); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(v10, v1); + ground->CreateFixture(&shape, 0.0f); + } + + m_body1 = nullptr; + m_body2 = nullptr; + CreateBoxes(); + m_boxes = true; + } + + void CreateBoxes() + { + if (m_body1) + { + m_world->DestroyBody(m_body1); + m_body1 = nullptr; + } + + if (m_body2) + { + m_world->DestroyBody(m_body2); + m_body2 = nullptr; + } + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = b2Vec2(8.0f, 2.6f) + m_offset1; + bd.allowSleep = false; + m_body1 = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 1.0f); + + m_body1->CreateFixture(&shape, 1.0f); + } + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = b2Vec2(8.0f, 2.6f) + m_offset2; + bd.allowSleep = false; + m_body2 = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 1.0f); + + m_body2->CreateFixture(&shape, 1.0f); + } + } + + void CreateCircles() + { + if (m_body1) + { + m_world->DestroyBody(m_body1); + m_body1 = nullptr; + } + + if (m_body2) + { + m_world->DestroyBody(m_body2); + m_body2 = nullptr; + } + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = b2Vec2(-0.5f, 0.6f) + m_offset1; + bd.allowSleep = false; + m_body1 = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 0.5f; + + m_body1->CreateFixture(&shape, 1.0f); + } + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = b2Vec2(-0.5f, 0.6f) + m_offset2; + bd.allowSleep = false; + m_body2 = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 0.5f; + + m_body2->CreateFixture(&shape, 1.0f); + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(200.0f, 100.0f)); + // ImGui::Begin("Custom Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // if (ImGui::RadioButton("Boxes", m_boxes == true)) + // { + // CreateBoxes(); + // m_boxes = true; + // } + + // if (ImGui::RadioButton("Circles", m_boxes == false)) + // { + // CreateCircles(); + // m_boxes = false; + // } + + // ImGui::End(); + //} + + void Step(Settings* settings) override + { + //if (glfwGetKey(g_mainWindow, GLFW_KEY_A) == GLFW_PRESS) + //{ + // m_body1->ApplyForceToCenter(b2Vec2(-10.0f, 0.0f), true); + // m_body2->ApplyForceToCenter(b2Vec2(-10.0f, 0.0f), true); + //} + + //if (glfwGetKey(g_mainWindow, GLFW_KEY_D) == GLFW_PRESS) + //{ + // m_body1->ApplyForceToCenter(b2Vec2(10.0f, 0.0f), true); + // m_body2->ApplyForceToCenter(b2Vec2(10.0f, 0.0f), true); + //} + + Test::Step(settings); + } + + static Test* Create() + { + return new EdgeTest; + } + + b2Vec2 m_offset1, m_offset2; + b2Body* m_body1; + b2Body* m_body2; + bool m_boxes; +}; + +static int testIndex = RegisterTest("Geometry", "Edge Test", EdgeTest::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/friction.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/friction.cpp new file mode 100644 index 0000000000..aa5398f3a2 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/friction.cpp @@ -0,0 +1,127 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Friction : public Test +{ +public: + + Friction() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(13.0f, 0.25f); + + b2BodyDef bd; + bd.position.Set(-4.0f, 22.0f); + bd.angle = -0.25f; + + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.25f, 1.0f); + + b2BodyDef bd; + bd.position.Set(10.5f, 19.0f); + + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(13.0f, 0.25f); + + b2BodyDef bd; + bd.position.Set(4.0f, 14.0f); + bd.angle = 0.25f; + + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.25f, 1.0f); + + b2BodyDef bd; + bd.position.Set(-10.5f, 11.0f); + + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(13.0f, 0.25f); + + b2BodyDef bd; + bd.position.Set(-4.0f, 6.0f); + bd.angle = -0.25f; + + b2Body* ground = m_world->CreateBody(&bd); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.5f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 25.0f; + + float friction[5] = {0.75f, 0.5f, 0.35f, 0.1f, 0.0f}; + + for (int i = 0; i < 5; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-15.0f + 4.0f * i, 28.0f); + b2Body* body = m_world->CreateBody(&bd); + + fd.friction = friction[i]; + body->CreateFixture(&fd); + } + } + } + + static Test* Create() + { + return new Friction; + } +}; + +static int testIndex = RegisterTest("Forces", "Friction", Friction::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/gear_joint.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/gear_joint.cpp new file mode 100644 index 0000000000..fd73098e59 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/gear_joint.cpp @@ -0,0 +1,180 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class GearJoint : public Test +{ +public: + GearJoint() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(50.0f, 0.0f), b2Vec2(-50.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2CircleShape circle1; + circle1.m_radius = 1.0f; + + b2PolygonShape box; + box.SetAsBox(0.5f, 5.0f); + + b2CircleShape circle2; + circle2.m_radius = 2.0f; + + b2BodyDef bd1; + bd1.type = b2_staticBody; + bd1.position.Set(10.0f, 9.0f); + b2Body* body1 = m_world->CreateBody(&bd1); + body1->CreateFixture(&circle1, 5.0f); + + b2BodyDef bd2; + bd2.type = b2_dynamicBody; + bd2.position.Set(10.0f, 8.0f); + b2Body* body2 = m_world->CreateBody(&bd2); + body2->CreateFixture(&box, 5.0f); + + b2BodyDef bd3; + bd3.type = b2_dynamicBody; + bd3.position.Set(10.0f, 6.0f); + b2Body* body3 = m_world->CreateBody(&bd3); + body3->CreateFixture(&circle2, 5.0f); + + b2RevoluteJointDef jd1; + jd1.Initialize(body1, body2, bd1.position); + b2Joint* joint1 = m_world->CreateJoint(&jd1); + + b2RevoluteJointDef jd2; + jd2.Initialize(body2, body3, bd3.position); + b2Joint* joint2 = m_world->CreateJoint(&jd2); + + b2GearJointDef jd4; + jd4.bodyA = body1; + jd4.bodyB = body3; + jd4.joint1 = joint1; + jd4.joint2 = joint2; + jd4.ratio = circle2.m_radius / circle1.m_radius; + m_world->CreateJoint(&jd4); + } + + { + b2CircleShape circle1; + circle1.m_radius = 1.0f; + + b2CircleShape circle2; + circle2.m_radius = 2.0f; + + b2PolygonShape box; + box.SetAsBox(0.5f, 5.0f); + + b2BodyDef bd1; + bd1.type = b2_dynamicBody; + bd1.position.Set(-3.0f, 12.0f); + b2Body* body1 = m_world->CreateBody(&bd1); + body1->CreateFixture(&circle1, 5.0f); + + b2RevoluteJointDef jd1; + jd1.bodyA = ground; + jd1.bodyB = body1; + jd1.localAnchorA = ground->GetLocalPoint(bd1.position); + jd1.localAnchorB = body1->GetLocalPoint(bd1.position); + jd1.referenceAngle = body1->GetAngle() - ground->GetAngle(); + m_joint1 = (b2RevoluteJoint*)m_world->CreateJoint(&jd1); + + b2BodyDef bd2; + bd2.type = b2_dynamicBody; + bd2.position.Set(0.0f, 12.0f); + b2Body* body2 = m_world->CreateBody(&bd2); + body2->CreateFixture(&circle2, 5.0f); + + b2RevoluteJointDef jd2; + jd2.Initialize(ground, body2, bd2.position); + m_joint2 = (b2RevoluteJoint*)m_world->CreateJoint(&jd2); + + b2BodyDef bd3; + bd3.type = b2_dynamicBody; + bd3.position.Set(2.5f, 12.0f); + b2Body* body3 = m_world->CreateBody(&bd3); + body3->CreateFixture(&box, 5.0f); + + b2PrismaticJointDef jd3; + jd3.Initialize(ground, body3, bd3.position, b2Vec2(0.0f, 1.0f)); + jd3.lowerTranslation = -5.0f; + jd3.upperTranslation = 5.0f; + jd3.enableLimit = true; + + m_joint3 = (b2PrismaticJoint*)m_world->CreateJoint(&jd3); + + b2GearJointDef jd4; + jd4.bodyA = body1; + jd4.bodyB = body2; + jd4.joint1 = m_joint1; + jd4.joint2 = m_joint2; + jd4.ratio = circle2.m_radius / circle1.m_radius; + m_joint4 = (b2GearJoint*)m_world->CreateJoint(&jd4); + + b2GearJointDef jd5; + jd5.bodyA = body2; + jd5.bodyB = body3; + jd5.joint1 = m_joint2; + jd5.joint2 = m_joint3; + jd5.ratio = -1.0f / circle2.m_radius; + m_joint5 = (b2GearJoint*)m_world->CreateJoint(&jd5); + } + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + float ratio, value; + + ratio = m_joint4->GetRatio(); + value = m_joint1->GetJointAngle() + ratio * m_joint2->GetJointAngle(); + //g_debugDraw.DrawString(5, m_textLine, "theta1 + %4.2f * theta2 = %4.2f", (float) ratio, (float) value); + //m_textLine += m_textIncrement; + + ratio = m_joint5->GetRatio(); + value = m_joint2->GetJointAngle() + ratio * m_joint3->GetJointTranslation(); + //g_debugDraw.DrawString(5, m_textLine, "theta2 + %4.2f * delta = %4.2f", (float) ratio, (float) value); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new GearJoint; + } + + b2RevoluteJoint* m_joint1; + b2RevoluteJoint* m_joint2; + b2PrismaticJoint* m_joint3; + b2GearJoint* m_joint4; + b2GearJoint* m_joint5; +}; + +static int testIndex = RegisterTest("Joints", "Gear", GearJoint::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/heavy1.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/heavy1.cpp new file mode 100644 index 0000000000..ac98ac1f16 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/heavy1.cpp @@ -0,0 +1,61 @@ + // MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Heavy1 : public Test +{ +public: + + Heavy1() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 0.5f); + b2Body* body = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 0.5f; + body->CreateFixture(&shape, 10.0f); + + bd.position.Set(0.0f, 6.0f); + body = m_world->CreateBody(&bd); + shape.m_radius = 5.0f; + body->CreateFixture(&shape, 10.0f); + } + + static Test* Create() + { + return new Heavy1; + } +}; + +static int testIndex = RegisterTest("Solver", "Heavy 1", Heavy1::Create); \ No newline at end of file diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/heavy2.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/heavy2.cpp new file mode 100644 index 0000000000..9f2a96a810 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/heavy2.cpp @@ -0,0 +1,94 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Heavy2 : public Test +{ +public: + + Heavy2() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 2.5f); + b2Body* body = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 0.5f; + body->CreateFixture(&shape, 10.0f); + + bd.position.Set(0.0f, 3.5f); + body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 10.0f); + + m_heavy = NULL; + } + + void ToggleHeavy() + { + if (m_heavy) + { + m_world->DestroyBody(m_heavy); + m_heavy = NULL; + } + else + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 9.0f); + m_heavy = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 5.0f; + m_heavy->CreateFixture(&shape, 10.0f); + } + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_H: + // ToggleHeavy(); + // break; + // } + //} + + static Test* Create() + { + return new Heavy2; + } + + b2Body* m_heavy; +}; + +static int testIndex = RegisterTest("Solver", "Heavy 2", Heavy2::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/mobile_balanced.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/mobile_balanced.cpp new file mode 100644 index 0000000000..a802d1781e --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/mobile_balanced.cpp @@ -0,0 +1,108 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class MobileBalanced : public Test +{ +public: + + enum + { + e_depth = 4 + }; + + MobileBalanced() + { + b2Body* ground; + + // Create ground body. + { + b2BodyDef bodyDef; + bodyDef.position.Set(0.0f, 20.0f); + ground = m_world->CreateBody(&bodyDef); + } + + float a = 0.5f; + b2Vec2 h(0.0f, a); + + b2Body* root = AddNode(ground, b2Vec2_zero, 0, 3.0f, a); + + b2RevoluteJointDef jointDef; + jointDef.bodyA = ground; + jointDef.bodyB = root; + jointDef.localAnchorA.SetZero(); + jointDef.localAnchorB = h; + m_world->CreateJoint(&jointDef); + } + + b2Body* AddNode(b2Body* parent, const b2Vec2& localAnchor, int32 depth, float offset, float a) + { + float density = 20.0f; + b2Vec2 h(0.0f, a); + + b2Vec2 p = parent->GetPosition() + localAnchor - h; + + b2BodyDef bodyDef; + bodyDef.type = b2_dynamicBody; + bodyDef.position = p; + b2Body* body = m_world->CreateBody(&bodyDef); + + b2PolygonShape shape; + shape.SetAsBox(0.25f * a, a); + body->CreateFixture(&shape, density); + + if (depth == e_depth) + { + return body; + } + + shape.SetAsBox(offset, 0.25f * a, b2Vec2(0, -a), 0.0f); + body->CreateFixture(&shape, density); + + b2Vec2 a1 = b2Vec2(offset, -a); + b2Vec2 a2 = b2Vec2(-offset, -a); + b2Body* body1 = AddNode(body, a1, depth + 1, 0.5f * offset, a); + b2Body* body2 = AddNode(body, a2, depth + 1, 0.5f * offset, a); + + b2RevoluteJointDef jointDef; + jointDef.bodyA = body; + jointDef.localAnchorB = h; + + jointDef.localAnchorA = a1; + jointDef.bodyB = body1; + m_world->CreateJoint(&jointDef); + + jointDef.localAnchorA = a2; + jointDef.bodyB = body2; + m_world->CreateJoint(&jointDef); + + return body; + } + + static Test* Create() + { + return new MobileBalanced; + } +}; + +static int testIndex = RegisterTest("Solver", "Mobile Balanced", MobileBalanced::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/mobile_unbalanced.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/mobile_unbalanced.cpp new file mode 100644 index 0000000000..aac855d484 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/mobile_unbalanced.cpp @@ -0,0 +1,105 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class MobileUnbalanced : public Test +{ +public: + + enum + { + e_depth = 4 + }; + + MobileUnbalanced() + { + b2Body* ground; + + // Create ground body. + { + b2BodyDef bodyDef; + bodyDef.position.Set(0.0f, 20.0f); + ground = m_world->CreateBody(&bodyDef); + } + + float a = 0.5f; + b2Vec2 h(0.0f, a); + + b2Body* root = AddNode(ground, b2Vec2_zero, 0, 3.0f, a); + + b2RevoluteJointDef jointDef; + jointDef.bodyA = ground; + jointDef.bodyB = root; + jointDef.localAnchorA.SetZero(); + jointDef.localAnchorB = h; + m_world->CreateJoint(&jointDef); + } + + b2Body* AddNode(b2Body* parent, const b2Vec2& localAnchor, int32 depth, float offset, float a) + { + float density = 20.0f; + b2Vec2 h(0.0f, a); + + b2Vec2 p = parent->GetPosition() + localAnchor - h; + + b2BodyDef bodyDef; + bodyDef.type = b2_dynamicBody; + bodyDef.position = p; + b2Body* body = m_world->CreateBody(&bodyDef); + + b2PolygonShape shape; + shape.SetAsBox(0.25f * a, a); + body->CreateFixture(&shape, density); + + if (depth == e_depth) + { + return body; + } + + b2Vec2 a1 = b2Vec2(offset, -a); + b2Vec2 a2 = b2Vec2(-offset, -a); + b2Body* body1 = AddNode(body, a1, depth + 1, 0.5f * offset, a); + b2Body* body2 = AddNode(body, a2, depth + 1, 0.5f * offset, a); + + b2RevoluteJointDef jointDef; + jointDef.bodyA = body; + jointDef.localAnchorB = h; + + jointDef.localAnchorA = a1; + jointDef.bodyB = body1; + m_world->CreateJoint(&jointDef); + + jointDef.localAnchorA = a2; + jointDef.bodyB = body2; + m_world->CreateJoint(&jointDef); + + return body; + } + + static Test* Create() + { + return new MobileUnbalanced; + } +}; + +static int testIndex = RegisterTest("Solver", "Mobile Unbalanced", MobileUnbalanced::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/motor_joint.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/motor_joint.cpp new file mode 100644 index 0000000000..1356dc7192 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/motor_joint.cpp @@ -0,0 +1,118 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" + +/// This test shows how to use a motor joint. A motor joint +/// can be used to animate a dynamic body. With finite motor forces +/// the body can be blocked by collision with other bodies. +class MotorJoint : public Test +{ +public: + MotorJoint() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-20.0f, 0.0f), b2Vec2(20.0f, 0.0f)); + + b2FixtureDef fd; + fd.shape = &shape; + + ground->CreateFixture(&fd); + } + + // Define motorized body + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 8.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(2.0f, 0.5f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.friction = 0.6f; + fd.density = 2.0f; + body->CreateFixture(&fd); + + b2MotorJointDef mjd; + mjd.Initialize(ground, body); + mjd.maxForce = 1000.0f; + mjd.maxTorque = 1000.0f; + m_joint = (b2MotorJoint*)m_world->CreateJoint(&mjd); + } + + m_go = false; + m_time = 0.0f; + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_S: + // m_go = !m_go; + // break; + // } + //} + + void Step(Settings* settings) override + { + //if (m_go && settings.m_hertz > 0.0f) + //{ + // m_time += 1.0f / settings.m_hertz; + //} + + b2Vec2 linearOffset; + linearOffset.x = 6.0f * sinf(2.0f * m_time); + linearOffset.y = 8.0f + 4.0f * sinf(1.0f * m_time); + + float angularOffset = 4.0f * m_time; + + m_joint->SetLinearOffset(linearOffset); + m_joint->SetAngularOffset(angularOffset); + + //g_debugDraw.DrawPoint(linearOffset, 4.0f, b2Color(0.9f, 0.9f, 0.9f)); + + //Test::Step(settings); + //g_debugDraw.DrawString(5, m_textLine, "Keys: (s) pause"); + //m_textLine += 15; + } + + static Test* Create() + { + return new MotorJoint; + } + + b2MotorJoint* m_joint; + float m_time; + bool m_go; +}; + +static int testIndex = RegisterTest("Joints", "Motor Joint", MotorJoint::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/pinball.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/pinball.cpp new file mode 100644 index 0000000000..c90779d5ac --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/pinball.cpp @@ -0,0 +1,170 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +/// This tests bullet collision and provides an example of a gameplay scenario. +/// This also uses a loop shape. +class Pinball : public Test +{ +public: + Pinball() + { + // Ground body + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2Vec2 vs[5]; + vs[0].Set(-8.0f, 6.0f); + vs[1].Set(-8.0f, 20.0f); + vs[2].Set(8.0f, 20.0f); + vs[3].Set(8.0f, 6.0f); + vs[4].Set(0.0f, -2.0f); + + b2ChainShape loop; + loop.CreateLoop(vs, 5); + b2FixtureDef fd; + fd.shape = &loop; + fd.density = 0.0f; + ground->CreateFixture(&fd); + } + + // Flippers + { + b2Vec2 p1(-2.0f, 0.0f), p2(2.0f, 0.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + + bd.position = p1; + b2Body* leftFlipper = m_world->CreateBody(&bd); + + bd.position = p2; + b2Body* rightFlipper = m_world->CreateBody(&bd); + + b2PolygonShape box; + box.SetAsBox(1.75f, 0.1f); + + b2FixtureDef fd; + fd.shape = &box; + fd.density = 1.0f; + + leftFlipper->CreateFixture(&fd); + rightFlipper->CreateFixture(&fd); + + b2RevoluteJointDef jd; + jd.bodyA = ground; + jd.localAnchorB.SetZero(); + jd.enableMotor = true; + jd.maxMotorTorque = 1000.0f; + jd.enableLimit = true; + + jd.motorSpeed = 0.0f; + jd.localAnchorA = p1; + jd.bodyB = leftFlipper; + jd.lowerAngle = -30.0f * b2_pi / 180.0f; + jd.upperAngle = 5.0f * b2_pi / 180.0f; + m_leftJoint = (b2RevoluteJoint*)m_world->CreateJoint(&jd); + + jd.motorSpeed = 0.0f; + jd.localAnchorA = p2; + jd.bodyB = rightFlipper; + jd.lowerAngle = -5.0f * b2_pi / 180.0f; + jd.upperAngle = 30.0f * b2_pi / 180.0f; + m_rightJoint = (b2RevoluteJoint*)m_world->CreateJoint(&jd); + } + + // Circle character + { + b2BodyDef bd; + bd.position.Set(1.0f, 15.0f); + bd.type = b2_dynamicBody; + bd.bullet = true; + + m_ball = m_world->CreateBody(&bd); + + b2CircleShape shape; + shape.m_radius = 0.2f; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + m_ball->CreateFixture(&fd); + } + + m_button = false; + } + + void Step(Settings* settings) override + { + if (m_button) + { + m_leftJoint->SetMotorSpeed(20.0f); + m_rightJoint->SetMotorSpeed(-20.0f); + } + else + { + m_leftJoint->SetMotorSpeed(-10.0f); + m_rightJoint->SetMotorSpeed(10.0f); + } + + Test::Step(settings); + + //g_debugDraw.DrawString(5, m_textLine, "Press 'a' to control the flippers"); + //m_textLine += m_textIncrement; + + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_button = true; + // break; + // } + //} + + //void KeyboardUp(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_button = false; + // break; + // } + //} + + static Test* Create() + { + return new Pinball; + } + + b2RevoluteJoint* m_leftJoint; + b2RevoluteJoint* m_rightJoint; + b2Body* m_ball; + bool m_button; +}; + +static int testIndex = RegisterTest("Examples", "Pinball", Pinball::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/platformer.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/platformer.cpp new file mode 100644 index 0000000000..5d5b7ea207 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/platformer.cpp @@ -0,0 +1,133 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Platformer : public Test +{ +public: + + enum State + { + e_unknown, + e_above, + e_below + }; + + Platformer() + { + // Ground + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-20.0f, 0.0f), b2Vec2(20.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + // Platform + { + b2BodyDef bd; + bd.position.Set(0.0f, 10.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(3.0f, 0.5f); + m_platform = body->CreateFixture(&shape, 0.0f); + + m_bottom = 10.0f - 0.5f; + m_top = 10.0f + 0.5f; + } + + // Actor + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 12.0f); + b2Body* body = m_world->CreateBody(&bd); + + m_radius = 0.5f; + b2CircleShape shape; + shape.m_radius = m_radius; + m_character = body->CreateFixture(&shape, 20.0f); + + body->SetLinearVelocity(b2Vec2(0.0f, -50.0f)); + + m_state = e_unknown; + } + } + + void PreSolve(b2Contact* contact, const b2Manifold* oldManifold) override + { + Test::PreSolve(contact, oldManifold); + + b2Fixture* fixtureA = contact->GetFixtureA(); + b2Fixture* fixtureB = contact->GetFixtureB(); + + if (fixtureA != m_platform && fixtureA != m_character) + { + return; + } + + if (fixtureB != m_platform && fixtureB != m_character) + { + return; + } + +#if 1 + b2Vec2 position = m_character->GetBody()->GetPosition(); + + if (position.y < m_top + m_radius - 3.0f * b2_linearSlop) + { + contact->SetEnabled(false); + } +#else + b2Vec2 v = m_character->GetBody()->GetLinearVelocity(); + if (v.y > 0.0f) + { + contact->SetEnabled(false); + } +#endif + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + b2Vec2 v = m_character->GetBody()->GetLinearVelocity(); + // g_debugDraw.DrawString(5, m_textLine, "Character Linear Velocity: %f", v.y); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new Platformer; + } + + float m_radius, m_top, m_bottom; + State m_state; + b2Fixture* m_platform; + b2Fixture* m_character; +}; + +static int testIndex = RegisterTest("Examples", "Platformer", Platformer::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/polygon_collision.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/polygon_collision.cpp new file mode 100644 index 0000000000..299d618aeb --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/polygon_collision.cpp @@ -0,0 +1,127 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class PolygonCollision : public Test +{ +public: + PolygonCollision() + { + //{ + // m_polygonA.SetAsBox(0.2f, 0.4f); + // m_transformA.Set(b2Vec2(0.0f, 0.0f), 0.0f); + //} + + //{ + // m_polygonB.SetAsBox(0.5f, 0.5f); + // m_positionB.Set(19.345284f, 1.5632932f); + // m_angleB = 1.9160721f; + // m_transformB.Set(m_positionB, m_angleB); + //} + } + + static Test* Create() + { + return new PolygonCollision; + } + + void Step(Settings* settings) override + { + B2_NOT_USED(settings); + + b2Manifold manifold; +// b2CollidePolygons(&manifold, &m_polygonA, m_transformA, &m_polygonB, m_transformB); + + b2WorldManifold worldManifold; +// worldManifold.Initialize(&manifold, m_transformA, m_polygonA.m_radius, m_transformB, m_polygonB.m_radius); + + //g_debugDraw.DrawString(5, m_textLine, "point count = %d", manifold.pointCount); + //m_textLine += m_textIncrement; + + { + b2Color color(0.9f, 0.9f, 0.9f); + b2Vec2 v[b2_maxPolygonVertices]; + //for (int32 i = 0; i < m_polygonA.m_count; ++i) + //{ + // v[i] = b2Mul(m_transformA, m_polygonA.m_vertices[i]); + //} + // g_debugDraw.DrawPolygon(v, m_polygonA.m_count, color); + + //for (int32 i = 0; i < m_polygonB.m_count; ++i) + //{ + // v[i] = b2Mul(m_transformB, m_polygonB.m_vertices[i]); + //} + // g_debugDraw.DrawPolygon(v, m_polygonB.m_count, color); + } + + for (int32 i = 0; i < manifold.pointCount; ++i) + { + // g_debugDraw.DrawPoint(worldManifold.points[i], 4.0f, b2Color(0.9f, 0.3f, 0.3f)); + } + + Test::Step(settings); + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_positionB.x -= 0.1f; + // break; + + // case GLFW_KEY_D: + // m_positionB.x += 0.1f; + // break; + + // case GLFW_KEY_S: + // m_positionB.y -= 0.1f; + // break; + + // case GLFW_KEY_W: + // m_positionB.y += 0.1f; + // break; + + // case GLFW_KEY_Q: + // m_angleB += 0.1f * b2_pi; + // break; + + // case GLFW_KEY_E: + // m_angleB -= 0.1f * b2_pi; + // break; + // } + +// m_transformB.Set(m_positionB, m_angleB); + //} + + //b2PolygonShape m_polygonA; + //b2PolygonShape m_polygonB; + + //b2Transform m_transformA; + //b2Transform m_transformB; + +// b2Vec2 m_positionB; +// float m_angleB; +}; + +static int testIndex = RegisterTest("Geometry", "Polygon Collision", PolygonCollision::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/polygon_shapes.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/polygon_shapes.cpp new file mode 100644 index 0000000000..19228376a6 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/polygon_shapes.cpp @@ -0,0 +1,265 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +/// This tests stacking. It also shows how to use b2World::Query +/// and b2TestOverlap. + +/// This callback is called by b2World::QueryAABB. We find all the fixtures +/// that overlap an AABB. Of those, we use b2TestOverlap to determine which fixtures +/// overlap a circle. Up to 4 overlapped fixtures will be highlighted with a yellow border. +class PolygonShapesCallback : public b2QueryCallback +{ +public: + + enum + { + e_maxCount = 4 + }; + + PolygonShapesCallback() + { + m_count = 0; + } + + /// Called for each fixture found in the query AABB. + /// @return false to terminate the query. + bool ReportFixture(b2Fixture* fixture) override + { + if (m_count == e_maxCount) + { + return false; + } + + b2Body* body = fixture->GetBody(); + b2Shape* shape = fixture->GetShape(); + + bool overlap = b2TestOverlap(shape, 0, &m_circle, 0, body->GetTransform(), m_transform); + + if (overlap) + { + b2Color color(0.95f, 0.95f, 0.6f); + b2Vec2 center = body->GetWorldCenter(); + // g_debugDraw->DrawPoint(center, 5.0f, color); + ++m_count; + } + + return true; + } + + b2CircleShape m_circle; + b2Transform m_transform; + b2Draw* g_debugDraw; + int32 m_count; +}; + +class PolygonShapes : public Test +{ +public: + + enum + { + e_maxBodies = 256 + }; + + PolygonShapes() + { + // Ground body + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.5f, 0.0f); + vertices[1].Set(0.5f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + m_polygons[0].Set(vertices, 3); + } + + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.1f, 0.0f); + vertices[1].Set(0.1f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + m_polygons[1].Set(vertices, 3); + } + + { + float w = 1.0f; + float b = w / (2.0f + b2Sqrt(2.0f)); + float s = b2Sqrt(2.0f) * b; + + b2Vec2 vertices[8]; + vertices[0].Set(0.5f * s, 0.0f); + vertices[1].Set(0.5f * w, b); + vertices[2].Set(0.5f * w, b + s); + vertices[3].Set(0.5f * s, w); + vertices[4].Set(-0.5f * s, w); + vertices[5].Set(-0.5f * w, b + s); + vertices[6].Set(-0.5f * w, b); + vertices[7].Set(-0.5f * s, 0.0f); + + m_polygons[2].Set(vertices, 8); + } + + { + m_polygons[3].SetAsBox(0.5f, 0.5f); + } + + { + m_circle.m_radius = 0.5f; + } + + m_bodyIndex = 0; + memset(m_bodies, 0, sizeof(m_bodies)); + } + + void Create(int32 index) + { + if (m_bodies[m_bodyIndex] != NULL) + { + m_world->DestroyBody(m_bodies[m_bodyIndex]); + m_bodies[m_bodyIndex] = NULL; + } + + b2BodyDef bd; + bd.type = b2_dynamicBody; + + float x = RandomFloat(-2.0f, 2.0f); + bd.position.Set(x, 10.0f); + bd.angle = RandomFloat(-b2_pi, b2_pi); + + if (index == 4) + { + bd.angularDamping = 0.02f; + } + + m_bodies[m_bodyIndex] = m_world->CreateBody(&bd); + + if (index < 4) + { + b2FixtureDef fd; + fd.shape = m_polygons + index; + fd.density = 1.0f; + fd.friction = 0.3f; + m_bodies[m_bodyIndex]->CreateFixture(&fd); + } + else + { + b2FixtureDef fd; + fd.shape = &m_circle; + fd.density = 1.0f; + fd.friction = 0.3f; + + m_bodies[m_bodyIndex]->CreateFixture(&fd); + } + + m_bodyIndex = (m_bodyIndex + 1) % e_maxBodies; + } + + void DestroyBody() + { + for (int32 i = 0; i < e_maxBodies; ++i) + { + if (m_bodies[i] != NULL) + { + m_world->DestroyBody(m_bodies[i]); + m_bodies[i] = NULL; + return; + } + } + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_1: + // case GLFW_KEY_2: + // case GLFW_KEY_3: + // case GLFW_KEY_4: + // case GLFW_KEY_5: + // Create(key - GLFW_KEY_1); + // break; + + // case GLFW_KEY_A: + // for (int32 i = 0; i < e_maxBodies; i += 2) + // { + // if (m_bodies[i]) + // { + // bool enabled = m_bodies[i]->IsEnabled(); + // m_bodies[i]->SetEnabled(!enabled); + // } + // } + // break; + + // case GLFW_KEY_D: + // DestroyBody(); + // break; + // } + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + + PolygonShapesCallback callback; + callback.m_circle.m_radius = 2.0f; + callback.m_circle.m_p.Set(0.0f, 1.1f); + callback.m_transform.SetIdentity(); + // callback.g_debugDraw = &g_debugDraw; + + b2AABB aabb; + callback.m_circle.ComputeAABB(&aabb, callback.m_transform, 0); + + m_world->QueryAABB(&callback, aabb); + + b2Color color(0.4f, 0.7f, 0.8f); + //g_debugDraw.DrawCircle(callback.m_circle.m_p, callback.m_circle.m_radius, color); + + //g_debugDraw.DrawString(5, m_textLine, "Press 1-5 to drop stuff, maximum of %d overlaps detected", PolygonShapesCallback::e_maxCount); + //m_textLine += m_textIncrement; + //g_debugDraw.DrawString(5, m_textLine, "Press 'a' to enable/disable some bodies"); + //m_textLine += m_textIncrement; + //g_debugDraw.DrawString(5, m_textLine, "Press 'd' to destroy a body"); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new PolygonShapes; + } + + int32 m_bodyIndex; + b2Body* m_bodies[e_maxBodies]; + b2PolygonShape m_polygons[4]; + b2CircleShape m_circle; +}; + +static int testIndex = RegisterTest("Geometry", "Polygon Shapes", PolygonShapes::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/prismatic_joint.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/prismatic_joint.cpp new file mode 100644 index 0000000000..a0974b9a7f --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/prismatic_joint.cpp @@ -0,0 +1,118 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" +//#include "imgui/imgui.h" + +// Test the prismatic joint with limits and motor options. +class PrismaticJoint : public Test +{ +public: + PrismaticJoint() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + m_enableLimit = true; + m_enableMotor = false; + m_motorSpeed = 10.0f; + + { + b2PolygonShape shape; + shape.SetAsBox(1.0f, 1.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 10.0f); + bd.angle = 0.5f * b2_pi; + bd.allowSleep = false; + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 5.0f); + + b2PrismaticJointDef pjd; + + // Horizontal + pjd.Initialize(ground, body, bd.position, b2Vec2(1.0f, 0.0f)); + + pjd.motorSpeed = m_motorSpeed; + pjd.maxMotorForce = 10000.0f; + pjd.enableMotor = m_enableMotor; + pjd.lowerTranslation = -10.0f; + pjd.upperTranslation = 10.0f; + pjd.enableLimit = m_enableLimit; + + m_joint = (b2PrismaticJoint*)m_world->CreateJoint(&pjd); + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(200.0f, 100.0f)); + // ImGui::Begin("Joint Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // if (ImGui::Checkbox("Limit", &m_enableLimit)) + // { + // m_joint->EnableLimit(m_enableLimit); + // } + + // if (ImGui::Checkbox("Motor", &m_enableMotor)) + // { + // m_joint->EnableMotor(m_enableMotor); + // } + + // if (ImGui::SliderFloat("Speed", &m_motorSpeed, -100.0f, 100.0f, "%.0f")) + // { + // m_joint->SetMotorSpeed(m_motorSpeed); + // } + + // ImGui::End(); + //} + + void Step(Settings* settings) override + { + Test::Step(settings); +// float force = m_joint->GetMotorForce(settings.m_hertz); + //g_debugDraw.DrawString(5, m_textLine, "Motor Force = %4.0f", force); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new PrismaticJoint; + } + + b2PrismaticJoint* m_joint; + float m_motorSpeed; + bool m_enableMotor; + bool m_enableLimit; +}; + +static int testIndex = RegisterTest("Joints", "Prismatic", PrismaticJoint::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/pulley_joint.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/pulley_joint.cpp new file mode 100644 index 0000000000..e66c147519 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/pulley_joint.cpp @@ -0,0 +1,96 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class PulleyJoint : public Test +{ +public: + PulleyJoint() + { + float y = 16.0f; + float L = 12.0f; + float a = 1.0f; + float b = 2.0f; + + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2CircleShape circle; + circle.m_radius = 2.0f; + + circle.m_p.Set(-10.0f, y + b + L); + ground->CreateFixture(&circle, 0.0f); + + circle.m_p.Set(10.0f, y + b + L); + ground->CreateFixture(&circle, 0.0f); + } + + { + + b2PolygonShape shape; + shape.SetAsBox(a, b); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + + //bd.fixedRotation = true; + bd.position.Set(-10.0f, y); + b2Body* body1 = m_world->CreateBody(&bd); + body1->CreateFixture(&shape, 5.0f); + + bd.position.Set(10.0f, y); + b2Body* body2 = m_world->CreateBody(&bd); + body2->CreateFixture(&shape, 5.0f); + + b2PulleyJointDef pulleyDef; + b2Vec2 anchor1(-10.0f, y + b); + b2Vec2 anchor2(10.0f, y + b); + b2Vec2 groundAnchor1(-10.0f, y + b + L); + b2Vec2 groundAnchor2(10.0f, y + b + L); + pulleyDef.Initialize(body1, body2, groundAnchor1, groundAnchor2, anchor1, anchor2, 1.5f); + + m_joint1 = (b2PulleyJoint*)m_world->CreateJoint(&pulleyDef); + } + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + float ratio = m_joint1->GetRatio(); + float L = m_joint1->GetCurrentLengthA() + ratio * m_joint1->GetCurrentLengthB(); + //g_debugDraw.DrawString(5, m_textLine, "L1 + %4.2f * L2 = %4.2f", (float) ratio, (float) L); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new PulleyJoint; + } + + b2PulleyJoint* m_joint1; +}; + +static int testIndex = RegisterTest("Joints", "Pulley", PulleyJoint::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/pyramid.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/pyramid.cpp new file mode 100644 index 0000000000..a0a6c1b4e0 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/pyramid.cpp @@ -0,0 +1,92 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Pyramid : public Test +{ +public: + enum + { + e_count = 20 + }; + + Pyramid() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + float a = 0.5f; + b2PolygonShape shape; + shape.SetAsBox(a, a); + + b2Vec2 x(-7.0f, 0.75f); + b2Vec2 y; + b2Vec2 deltaX(0.5625f, 1.25f); + b2Vec2 deltaY(1.125f, 0.0f); + + for (int32 i = 0; i < e_count; ++i) + { + y = x; + + for (int32 j = i; j < e_count; ++j) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = y; + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 5.0f); + + y += deltaY; + } + + x += deltaX; + } + } + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + //b2DynamicTree* tree = &m_world->m_contactManager.m_broadPhase.m_tree; + + //if (m_stepCount == 400) + //{ + // tree->RebuildBottomUp(); + //} + } + + static Test* Create() + { + return new Pyramid; + } +}; + +static int testIndex = RegisterTest("Stacking", "Pyramid", Pyramid::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/ray_cast.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/ray_cast.cpp new file mode 100644 index 0000000000..bbf6f6a43f --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/ray_cast.cpp @@ -0,0 +1,479 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" +//#include "imgui/imgui.h" + +enum +{ + e_maxBodies = 256 +}; + +// This test demonstrates how to use the world ray-cast feature. +// NOTE: we are intentionally filtering one of the polygons, therefore +// the ray will always miss one type of polygon. + +// This callback finds the closest hit. Polygon 0 is filtered. +class RayCastClosestCallback : public b2RayCastCallback +{ +public: + RayCastClosestCallback() + { + m_hit = false; + } + + float ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float fraction) override + { + uintptr_t index = fixture->GetUserData().pointer; + if (index == 1) + { + // By returning -1, we instruct the calling code to ignore this fixture and + // continue the ray-cast to the next fixture. + return -1.0f; + } + + m_hit = true; + m_point = point; + m_normal = normal; + + // By returning the current fraction, we instruct the calling code to clip the ray and + // continue the ray-cast to the next fixture. WARNING: do not assume that fixtures + // are reported in order. However, by clipping, we can always get the closest fixture. + return fraction; + } + + bool m_hit; + b2Vec2 m_point; + b2Vec2 m_normal; +}; + +// This callback finds any hit. Polygon 0 is filtered. For this type of query we are usually +// just checking for obstruction, so the actual fixture and hit point are irrelevant. +class RayCastAnyCallback : public b2RayCastCallback +{ +public: + RayCastAnyCallback() + { + m_hit = false; + } + + float ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float) override + { + uintptr_t index = fixture->GetUserData().pointer; + if (index == 1) + { + // By returning -1, we instruct the calling code to ignore this fixture and + // continue the ray-cast to the next fixture. + return -1.0f; + } + + m_hit = true; + m_point = point; + m_normal = normal; + + // 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; + } + + bool m_hit; + b2Vec2 m_point; + b2Vec2 m_normal; +}; + +// This ray cast collects multiple hits along the ray. Polygon 0 is filtered. +// The fixtures are not necessary reported in order, so we might not capture +// the closest fixture. +class RayCastMultipleCallback : public b2RayCastCallback +{ +public: + enum + { + e_maxCount = 3 + }; + + RayCastMultipleCallback() + { + m_count = 0; + } + + float ReportFixture(b2Fixture* fixture, const b2Vec2& point, const b2Vec2& normal, float) override + { + uintptr_t index = fixture->GetUserData().pointer; + if (index == 1) + { + // By returning -1, we instruct the calling code to ignore this fixture and + // continue the ray-cast to the next fixture. + return -1.0f; + } + + b2Assert(m_count < e_maxCount); + + m_points[m_count] = point; + m_normals[m_count] = normal; + ++m_count; + + if (m_count == e_maxCount) + { + // 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; + } + + b2Vec2 m_points[e_maxCount]; + b2Vec2 m_normals[e_maxCount]; + int32 m_count; +}; + + +class RayCast : public Test +{ +public: + + enum Mode + { + e_any = 0, + e_closest = 1, + e_multiple = 2 + }; + + RayCast() + { + // Ground body + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.5f, 0.0f); + vertices[1].Set(0.5f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + m_polygons[0].Set(vertices, 3); + } + + { + b2Vec2 vertices[3]; + vertices[0].Set(-0.1f, 0.0f); + vertices[1].Set(0.1f, 0.0f); + vertices[2].Set(0.0f, 1.5f); + m_polygons[1].Set(vertices, 3); + } + + { + float w = 1.0f; + float b = w / (2.0f + b2Sqrt(2.0f)); + float s = b2Sqrt(2.0f) * b; + + b2Vec2 vertices[8]; + vertices[0].Set(0.5f * s, 0.0f); + vertices[1].Set(0.5f * w, b); + vertices[2].Set(0.5f * w, b + s); + vertices[3].Set(0.5f * s, w); + vertices[4].Set(-0.5f * s, w); + vertices[5].Set(-0.5f * w, b + s); + vertices[6].Set(-0.5f * w, b); + vertices[7].Set(-0.5f * s, 0.0f); + + m_polygons[2].Set(vertices, 8); + } + + { + m_polygons[3].SetAsBox(0.5f, 0.5f); + } + + { + m_circle.m_radius = 0.5f; + } + + { + m_edge.SetTwoSided(b2Vec2(-1.0f, 0.0f), b2Vec2(1.0f, 0.0f)); + } + + m_bodyIndex = 0; + memset(m_bodies, 0, sizeof(m_bodies)); + + m_degrees = 0.0f; + + m_mode = e_closest; + } + + void Create(int32 index) + { + if (m_bodies[m_bodyIndex] != NULL) + { + m_world->DestroyBody(m_bodies[m_bodyIndex]); + m_bodies[m_bodyIndex] = NULL; + } + + b2BodyDef bd; + + float x = RandomFloat(-10.0f, 10.0f); + float y = RandomFloat(0.0f, 20.0f); + bd.position.Set(x, y); + bd.angle = RandomFloat(-b2_pi, b2_pi); + + if (index == 4) + { + bd.angularDamping = 0.02f; + } + + m_bodies[m_bodyIndex] = m_world->CreateBody(&bd); + + if (index < 4) + { + b2FixtureDef fd; + fd.shape = m_polygons + index; + fd.friction = 0.3f; + fd.userData.pointer = index + 1; + m_bodies[m_bodyIndex]->CreateFixture(&fd); + } + else if (index < 5) + { + b2FixtureDef fd; + fd.shape = &m_circle; + fd.friction = 0.3f; + fd.userData.pointer = index + 1; + m_bodies[m_bodyIndex]->CreateFixture(&fd); + } + else + { + b2FixtureDef fd; + fd.shape = &m_edge; + fd.friction = 0.3f; + fd.userData.pointer = index + 1; + + m_bodies[m_bodyIndex]->CreateFixture(&fd); + } + + m_bodyIndex = (m_bodyIndex + 1) % e_maxBodies; + } + + void DestroyBody() + { + for (int32 i = 0; i < e_maxBodies; ++i) + { + if (m_bodies[i] != NULL) + { + m_world->DestroyBody(m_bodies[i]); + m_bodies[i] = NULL; + return; + } + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(210.0f, 285.0f)); + // ImGui::Begin("Ray-cast Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // if (ImGui::Button("Shape 1")) + // { + // Create(0); + // } + + // if (ImGui::Button("Shape 2")) + // { + // Create(1); + // } + + // if (ImGui::Button("Shape 3")) + // { + // Create(2); + // } + + // if (ImGui::Button("Shape 4")) + // { + // Create(3); + // } + + // if (ImGui::Button("Shape 5")) + // { + // Create(4); + // } + + // if (ImGui::Button("Shape 6")) + // { + // Create(5); + // } + + // if (ImGui::Button("Destroy Shape")) + // { + // DestroyBody(); + // } + + // ImGui::RadioButton("Any", &m_mode, e_any); + // ImGui::RadioButton("Closest", &m_mode, e_closest); + // ImGui::RadioButton("Multiple", &m_mode, e_multiple); + + // ImGui::SliderFloat("Angle", &m_degrees, 0.0f, 360.0f, "%.0f"); + + // ImGui::End(); + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + + //g_debugDraw.DrawString(5, m_textLine, "Shape 1 is intentionally ignored by the ray"); + //m_textLine += m_textIncrement; + switch (m_mode) + { + case e_closest: + // g_debugDraw.DrawString(5, m_textLine, "Ray-cast mode: closest - find closest fixture along the ray"); + break; + + case e_any: + // g_debugDraw.DrawString(5, m_textLine, "Ray-cast mode: any - check for obstruction"); + break; + + case e_multiple: + // g_debugDraw.DrawString(5, m_textLine, "Ray-cast mode: multiple - gather multiple fixtures"); + break; + } + +// m_textLine += m_textIncrement; + + float angle = b2_pi * m_degrees / 180.0f; + float L = 11.0f; + b2Vec2 point1(0.0f, 10.0f); + b2Vec2 d(L * cosf(angle), L * sinf(angle)); + b2Vec2 point2 = point1 + d; + + if (m_mode == e_closest) + { + RayCastClosestCallback callback; + m_world->RayCast(&callback, point1, point2); + + if (callback.m_hit) + { + // g_debugDraw.DrawPoint(callback.m_point, 5.0f, b2Color(0.4f, 0.9f, 0.4f)); + // g_debugDraw.DrawSegment(point1, callback.m_point, b2Color(0.8f, 0.8f, 0.8f)); + b2Vec2 head = callback.m_point + 0.5f * callback.m_normal; + // g_debugDraw.DrawSegment(callback.m_point, head, b2Color(0.9f, 0.9f, 0.4f)); + } + else + { + // g_debugDraw.DrawSegment(point1, point2, b2Color(0.8f, 0.8f, 0.8f)); + } + } + else if (m_mode == e_any) + { + RayCastAnyCallback callback; + m_world->RayCast(&callback, point1, point2); + + if (callback.m_hit) + { + // g_debugDraw.DrawPoint(callback.m_point, 5.0f, b2Color(0.4f, 0.9f, 0.4f)); + // g_debugDraw.DrawSegment(point1, callback.m_point, b2Color(0.8f, 0.8f, 0.8f)); + b2Vec2 head = callback.m_point + 0.5f * callback.m_normal; + // g_debugDraw.DrawSegment(callback.m_point, head, b2Color(0.9f, 0.9f, 0.4f)); + } + else + { + // g_debugDraw.DrawSegment(point1, point2, b2Color(0.8f, 0.8f, 0.8f)); + } + } + else if (m_mode == e_multiple) + { + RayCastMultipleCallback callback; + m_world->RayCast(&callback, point1, point2); + // g_debugDraw.DrawSegment(point1, point2, b2Color(0.8f, 0.8f, 0.8f)); + + for (int32 i = 0; i < callback.m_count; ++i) + { + b2Vec2 p = callback.m_points[i]; + b2Vec2 n = callback.m_normals[i]; + // g_debugDraw.DrawPoint(p, 5.0f, b2Color(0.4f, 0.9f, 0.4f)); + // g_debugDraw.DrawSegment(point1, p, b2Color(0.8f, 0.8f, 0.8f)); + b2Vec2 head = p + 0.5f * n; + // g_debugDraw.DrawSegment(p, head, b2Color(0.9f, 0.9f, 0.4f)); + } + } + +#if 0 + // This case was failing. + { + b2Vec2 vertices[4]; + //vertices[0].Set(-22.875f, -3.0f); + //vertices[1].Set(22.875f, -3.0f); + //vertices[2].Set(22.875f, 3.0f); + //vertices[3].Set(-22.875f, 3.0f); + + b2PolygonShape shape; + //shape.Set(vertices, 4); + shape.SetAsBox(22.875f, 3.0f); + + b2RayCastInput input; + input.p1.Set(10.2725f,1.71372f); + input.p2.Set(10.2353f,2.21807f); + //input.maxFraction = 0.567623f; + input.maxFraction = 0.56762173f; + + b2Transform xf; + xf.SetIdentity(); + xf.position.Set(23.0f, 5.0f); + + b2RayCastOutput output; + bool hit; + hit = shape.RayCast(&output, input, xf); + hit = false; + + b2Color color(1.0f, 1.0f, 1.0f); + b2Vec2 vs[4]; + for (int32 i = 0; i < 4; ++i) + { + vs[i] = b2Mul(xf, shape.m_vertices[i]); + } + + g_debugDraw.DrawPolygon(vs, 4, color); + g_debugDraw.DrawSegment(input.p1, input.p2, color); + } +#endif + } + + static Test* Create() + { + return new RayCast; + } + + int32 m_bodyIndex; + b2Body* m_bodies[e_maxBodies]; + b2PolygonShape m_polygons[4]; + b2CircleShape m_circle; + b2EdgeShape m_edge; + float m_degrees; + int32 m_mode; +}; + +static int testIndex = RegisterTest("Collision", "Ray Cast", RayCast::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/restitution.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/restitution.cpp new file mode 100644 index 0000000000..b32e47e1da --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/restitution.cpp @@ -0,0 +1,79 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// Note: even with a restitution of 1.0, there is some energy change +// due to position correction. +class Restitution : public Test +{ +public: + + Restitution() + { + const float threshold = 10.0f; + + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + + b2FixtureDef fd; + fd.shape = &shape; + fd.restitutionThreshold = threshold; + ground->CreateFixture(&fd); + } + + { + b2CircleShape shape; + shape.m_radius = 1.0f; + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 1.0f; + + float restitution[7] = { 0.0f, 0.1f, 0.3f, 0.5f, 0.75f, 0.9f, 1.0f }; + + for (int32 i = 0; i < 7; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-10.0f + 3.0f * i, 20.0f); + + b2Body* body = m_world->CreateBody(&bd); + + fd.restitution = restitution[i]; + fd.restitutionThreshold = threshold; + body->CreateFixture(&fd); + } + } + } + + static Test* Create() + { + return new Restitution; + } +}; + +static int testIndex = RegisterTest("Forces", "Restitution", Restitution::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/revolute_joint.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/revolute_joint.cpp new file mode 100644 index 0000000000..4555d06a83 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/revolute_joint.cpp @@ -0,0 +1,162 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" +//#include "imgui/imgui.h" + +class RevoluteJoint : public Test +{ +public: + RevoluteJoint() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + + b2FixtureDef fd; + fd.shape = &shape; + //fd.filter.categoryBits = 2; + + ground->CreateFixture(&fd); + } + + m_enableLimit = true; + m_enableMotor = false; + m_motorSpeed = 1.0f; + + { + b2PolygonShape shape; + shape.SetAsBox(0.25f, 3.0f, b2Vec2(0.0f, 3.0f), 0.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-10.0f, 20.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 5.0f); + + b2RevoluteJointDef jd; + jd.Initialize(ground, body, b2Vec2(-10.0f, 20.5f)); + jd.motorSpeed = m_motorSpeed; + jd.maxMotorTorque = 10000.0f; + jd.enableMotor = m_enableMotor; + jd.lowerAngle = -0.25f * b2_pi; + jd.upperAngle = 0.5f * b2_pi; + jd.enableLimit = m_enableLimit; + + m_joint1 = (b2RevoluteJoint*)m_world->CreateJoint(&jd); + } + + { + b2CircleShape circle_shape; + circle_shape.m_radius = 2.0f; + + b2BodyDef circle_bd; + circle_bd.type = b2_dynamicBody; + circle_bd.position.Set(5.0f, 30.0f); + + b2FixtureDef fd; + fd.density = 5.0f; + fd.filter.maskBits = 1; + fd.shape = &circle_shape; + + m_ball = m_world->CreateBody(&circle_bd); + m_ball->CreateFixture(&fd); + + b2PolygonShape polygon_shape; + polygon_shape.SetAsBox(10.0f, 0.5f, b2Vec2 (-10.0f, 0.0f), 0.0f); + + b2BodyDef polygon_bd; + polygon_bd.position.Set(20.0f, 10.0f); + polygon_bd.type = b2_dynamicBody; + polygon_bd.bullet = true; + b2Body* polygon_body = m_world->CreateBody(&polygon_bd); + polygon_body->CreateFixture(&polygon_shape, 2.0f); + + b2RevoluteJointDef jd; + jd.Initialize(ground, polygon_body, b2Vec2(19.0f, 10.0f)); + jd.lowerAngle = -0.25f * b2_pi; + jd.upperAngle = 0.0f * b2_pi; + jd.enableLimit = true; + jd.enableMotor = true; + jd.motorSpeed = 0.0f; + jd.maxMotorTorque = 10000.0f; + + m_joint2 = (b2RevoluteJoint*)m_world->CreateJoint(&jd); + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(200.0f, 100.0f)); + // ImGui::Begin("Joint Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // if (ImGui::Checkbox("Limit", &m_enableLimit)) + // { + // m_joint1->EnableLimit(m_enableLimit); + // } + + // if (ImGui::Checkbox("Motor", &m_enableMotor)) + // { + // m_joint1->EnableMotor(m_enableMotor); + // } + + // if (ImGui::SliderFloat("Speed", &m_motorSpeed, -20.0f, 20.0f, "%.0f")) + // { + // m_joint1->SetMotorSpeed(m_motorSpeed); + // } + + // ImGui::End(); + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + + // float torque1 = m_joint1->GetMotorTorque(settings->hz); + //g_debugDraw.DrawString(5, m_textLine, "Motor Torque 1= %4.0f", torque1); + //m_textLine += m_textIncrement; + + // float torque2 = m_joint2->GetMotorTorque(settings.hz); + //g_debugDraw.DrawString(5, m_textLine, "Motor Torque 2= %4.0f", torque2); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new RevoluteJoint; + } + + b2Body* m_ball; + b2RevoluteJoint* m_joint1; + b2RevoluteJoint* m_joint2; + float m_motorSpeed; + bool m_enableMotor; + bool m_enableLimit; +}; + +static int testIndex = RegisterTest("Joints", "Revolute", RevoluteJoint::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/rope.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/rope.cpp new file mode 100644 index 0000000000..7b7f5d447a --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/rope.cpp @@ -0,0 +1,286 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" +#include "box2d/b2_rope.h" +//#include "imgui/imgui.h" + +/// +class Rope : public Test +{ +public: + Rope() + { + const int32 N = 20; + const float L = 0.5f; + b2Vec2 vertices[N]; + float masses[N]; + + for (int32 i = 0; i < N; ++i) + { + vertices[i].Set(0.0f, L * (N - i)); + masses[i] = 1.0f; + } + masses[0] = 0.0f; + masses[1] = 0.0f; + + m_tuning1.bendHertz = 30.0f; + m_tuning1.bendDamping = 4.0f; + m_tuning1.bendStiffness = 1.0f; + m_tuning1.bendingModel = b2_pbdTriangleBendingModel; + m_tuning1.isometric = true; + + m_tuning1.stretchHertz = 30.0f; + m_tuning1.stretchDamping = 4.0f; + m_tuning1.stretchStiffness = 1.0f; + m_tuning1.stretchingModel = b2_pbdStretchingModel; + + m_tuning2.bendHertz = 30.0f; + m_tuning2.bendDamping = 0.7f; + m_tuning2.bendStiffness = 1.0f; + m_tuning2.bendingModel = b2_pbdHeightBendingModel; + m_tuning2.isometric = true; + + m_tuning2.stretchHertz = 30.0f; + m_tuning2.stretchDamping = 1.0f; + m_tuning2.stretchStiffness = 1.0f; + m_tuning2.stretchingModel = b2_pbdStretchingModel; + + m_position1.Set(-5.0f, 15.0f); + m_position2.Set(5.0f, 15.0f); + + b2RopeDef def; + def.vertices = vertices; + def.count = N; + def.gravity.Set(0.0f, -10.0f); + def.masses = masses; + + def.position = m_position1; + def.tuning = m_tuning1; + m_rope1.Create(def); + + def.position = m_position2; + def.tuning = m_tuning2; + m_rope2.Create(def); + + m_iterations1 = 8; + m_iterations2 = 8; + + m_speed = 10.0f; + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(200.0f, 700.0f)); + // ImGui::Begin("Tuning", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // ImGui::Separator(); + + // ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + // + // const ImGuiComboFlags comboFlags = 0; + // const char* bendModels[] = { "Spring", "PBD Ang", "XPBD Ang", "PBD Dist", "PBD Height", "PBD Triangle" }; + // const char* stretchModels[] = { "PBD", "XPBD" }; + + // ImGui::Text("Rope 1"); + // static int bendModel1 = m_tuning1.bendingModel; + // if (ImGui::BeginCombo("Bend Model##1", bendModels[bendModel1], comboFlags)) + // { + // for (int i = 0; i < IM_ARRAYSIZE(bendModels); ++i) + // { + // bool isSelected = (bendModel1 == i); + // if (ImGui::Selectable(bendModels[i], isSelected)) + // { + // bendModel1 = i; + // m_tuning1.bendingModel = b2BendingModel(i); + // } + + // if (isSelected) + // { + // ImGui::SetItemDefaultFocus(); + // } + // } + // ImGui::EndCombo(); + // } + + // ImGui::SliderFloat("Damping##B1", &m_tuning1.bendDamping, 0.0f, 4.0f, "%.1f"); + // ImGui::SliderFloat("Hertz##B1", &m_tuning1.bendHertz, 0.0f, 60.0f, "%.0f"); + // ImGui::SliderFloat("Stiffness##B1", &m_tuning1.bendStiffness, 0.0f, 1.0f, "%.1f"); + + // ImGui::Checkbox("Isometric##1", &m_tuning1.isometric); + // ImGui::Checkbox("Fixed Mass##1", &m_tuning1.fixedEffectiveMass); + // ImGui::Checkbox("Warm Start##1", &m_tuning1.warmStart); + + // static int stretchModel1 = m_tuning1.stretchingModel; + // if (ImGui::BeginCombo("Stretch Model##1", stretchModels[stretchModel1], comboFlags)) + // { + // for (int i = 0; i < IM_ARRAYSIZE(stretchModels); ++i) + // { + // bool isSelected = (stretchModel1 == i); + // if (ImGui::Selectable(stretchModels[i], isSelected)) + // { + // stretchModel1 = i; + // m_tuning1.stretchingModel = b2StretchingModel(i); + // } + + // if (isSelected) + // { + // ImGui::SetItemDefaultFocus(); + // } + // } + // ImGui::EndCombo(); + // } + + // ImGui::SliderFloat("Damping##S1", &m_tuning1.stretchDamping, 0.0f, 4.0f, "%.1f"); + // ImGui::SliderFloat("Hertz##S1", &m_tuning1.stretchHertz, 0.0f, 60.0f, "%.0f"); + // ImGui::SliderFloat("Stiffness##S1", &m_tuning1.stretchStiffness, 0.0f, 1.0f, "%.1f"); + + // ImGui::SliderInt("Iterations##1", &m_iterations1, 1, 100, "%d"); + + // ImGui::Separator(); + + // ImGui::Text("Rope 2"); + // static int bendModel2 = m_tuning2.bendingModel; + // if (ImGui::BeginCombo("Bend Model##2", bendModels[bendModel2], comboFlags)) + // { + // for (int i = 0; i < IM_ARRAYSIZE(bendModels); ++i) + // { + // bool isSelected = (bendModel2 == i); + // if (ImGui::Selectable(bendModels[i], isSelected)) + // { + // bendModel2 = i; + // m_tuning2.bendingModel = b2BendingModel(i); + // } + + // if (isSelected) + // { + // ImGui::SetItemDefaultFocus(); + // } + // } + // ImGui::EndCombo(); + // } + + // ImGui::SliderFloat("Damping##B2", &m_tuning2.bendDamping, 0.0f, 4.0f, "%.1f"); + // ImGui::SliderFloat("Hertz##B2", &m_tuning2.bendHertz, 0.0f, 60.0f, "%.0f"); + // ImGui::SliderFloat("Stiffness##B2", &m_tuning2.bendStiffness, 0.0f, 1.0f, "%.1f"); + + // ImGui::Checkbox("Isometric##2", &m_tuning2.isometric); + // ImGui::Checkbox("Fixed Mass##2", &m_tuning2.fixedEffectiveMass); + // ImGui::Checkbox("Warm Start##2", &m_tuning2.warmStart); + + // static int stretchModel2 = m_tuning2.stretchingModel; + // if (ImGui::BeginCombo("Stretch Model##2", stretchModels[stretchModel2], comboFlags)) + // { + // for (int i = 0; i < IM_ARRAYSIZE(stretchModels); ++i) + // { + // bool isSelected = (stretchModel2 == i); + // if (ImGui::Selectable(stretchModels[i], isSelected)) + // { + // stretchModel2 = i; + // m_tuning2.stretchingModel = b2StretchingModel(i); + // } + + // if (isSelected) + // { + // ImGui::SetItemDefaultFocus(); + // } + // } + // ImGui::EndCombo(); + // } + + // ImGui::SliderFloat("Damping##S2", &m_tuning2.stretchDamping, 0.0f, 4.0f, "%.1f"); + // ImGui::SliderFloat("Hertz##S2", &m_tuning2.stretchHertz, 0.0f, 60.0f, "%.0f"); + // ImGui::SliderFloat("Stiffness##S2", &m_tuning2.stretchStiffness, 0.0f, 1.0f, "%.1f"); + + // ImGui::SliderInt("Iterations##2", &m_iterations2, 1, 100, "%d"); + + // ImGui::Separator(); + + // ImGui::SliderFloat("Speed", &m_speed, 10.0f, 100.0f, "%.0f"); + + // if (ImGui::Button("Reset")) + // { + // m_position1.Set(-5.0f, 15.0f); + // m_position2.Set(5.0f, 15.0f); + // m_rope1.Reset(m_position1); + // m_rope2.Reset(m_position2); + // } + + // ImGui::PopItemWidth(); + + // ImGui::End(); + //} + + void Step(Settings* settings) override + { + float dt = 0.5; // settings.hz > 0.0f ? 1.0f / settings.hz : 0.0f; + + if (settings->pause == 1 && settings->singleStep == 0) + { + dt = 0.0f; + } + + //if (glfwGetKey(g_mainWindow, GLFW_KEY_COMMA) == GLFW_PRESS) + //{ + // m_position1.x -= m_speed * dt; + // m_position2.x -= m_speed * dt; + //} + + //if (glfwGetKey(g_mainWindow, GLFW_KEY_PERIOD) == GLFW_PRESS) + //{ + // m_position1.x += m_speed * dt; + // m_position2.x += m_speed * dt; + //} + + m_rope1.SetTuning(m_tuning1); + m_rope2.SetTuning(m_tuning2); + m_rope1.Step(dt, m_iterations1, m_position1); + m_rope2.Step(dt, m_iterations2, m_position2); + + Test::Step(settings); + + //m_rope1.Draw(&g_debugDraw); + //m_rope2.Draw(&g_debugDraw); + + //g_debugDraw.DrawString(5, m_textLine, "Press comma and period to move left and right"); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new Rope; + } + + b2Rope m_rope1; + b2Rope m_rope2; + b2RopeTuning m_tuning1; + b2RopeTuning m_tuning2; + int32 m_iterations1; + int32 m_iterations2; + b2Vec2 m_position1; + b2Vec2 m_position2; + float m_speed; +}; + +static int testIndex = RegisterTest("Rope", "Bending", Rope::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/sensor.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/sensor.cpp new file mode 100644 index 0000000000..c0038cd0fe --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/sensor.cpp @@ -0,0 +1,195 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +//#include "imgui/imgui.h" + +// This shows how to use sensor shapes. Sensors don't have collision, but report overlap events. +class Sensors : public Test +{ +public: + + enum + { + e_count = 7 + }; + + Sensors() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + { + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + +#if 0 + { + b2FixtureDef sd; + sd.SetAsBox(10.0f, 2.0f, b2Vec2(0.0f, 20.0f), 0.0f); + sd.isSensor = true; + m_sensor = ground->CreateFixture(&sd); + } +#else + { + b2CircleShape shape; + shape.m_radius = 5.0f; + shape.m_p.Set(0.0f, 10.0f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.isSensor = true; + m_sensor = ground->CreateFixture(&fd); + } +#endif + } + + { + b2CircleShape shape; + shape.m_radius = 1.0f; + + for (int32 i = 0; i < e_count; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-10.0f + 3.0f * i, 20.0f); + bd.userData.pointer = i; + + m_touching[i] = false; + m_bodies[i] = m_world->CreateBody(&bd); + + m_bodies[i]->CreateFixture(&shape, 1.0f); + } + } + + m_force = 100.0f; + } + + // Implement contact listener. + void BeginContact(b2Contact* contact) override + { + b2Fixture* fixtureA = contact->GetFixtureA(); + b2Fixture* fixtureB = contact->GetFixtureB(); + + if (fixtureA == m_sensor) + { + uintptr_t index = fixtureB->GetBody()->GetUserData().pointer; + if (index < e_count) + { + m_touching[index] = true; + } + } + + if (fixtureB == m_sensor) + { + uintptr_t index = fixtureA->GetBody()->GetUserData().pointer; + if (index < e_count) + { + m_touching[index] = true; + } + } + } + + // Implement contact listener. + void EndContact(b2Contact* contact) override + { + b2Fixture* fixtureA = contact->GetFixtureA(); + b2Fixture* fixtureB = contact->GetFixtureB(); + + if (fixtureA == m_sensor) + { + uintptr_t index = fixtureB->GetBody()->GetUserData().pointer; + if (index < e_count) + { + m_touching[index] = false; + } + } + + if (fixtureB == m_sensor) + { + uintptr_t index = fixtureA->GetBody()->GetUserData().pointer; + if (index < e_count) + { + m_touching[index] = false; + } + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(200.0f, 60.0f)); + // ImGui::Begin("Sensor Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // ImGui::SliderFloat("Force", &m_force, 0.0f, 2000.0f, "%.0f"); + + // ImGui::End(); + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + + // Traverse the contact results. Apply a force on shapes + // that overlap the sensor. + for (int32 i = 0; i < e_count; ++i) + { + if (m_touching[i] == false) + { + continue; + } + + b2Body* body = m_bodies[i]; + b2Body* ground = m_sensor->GetBody(); + + b2CircleShape* circle = (b2CircleShape*)m_sensor->GetShape(); + b2Vec2 center = ground->GetWorldPoint(circle->m_p); + + b2Vec2 position = body->GetPosition(); + + b2Vec2 d = center - position; + if (d.LengthSquared() < FLT_EPSILON * FLT_EPSILON) + { + continue; + } + + d.Normalize(); + b2Vec2 F = m_force * d; + body->ApplyForce(F, position, false); + } + } + + static Test* Create() + { + return new Sensors; + } + + b2Fixture* m_sensor; + b2Body* m_bodies[e_count]; + float m_force; + bool m_touching[e_count]; +}; + +static int testIndex = RegisterTest("Collision", "Sensors", Sensors::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/shape_cast.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/shape_cast.cpp new file mode 100644 index 0000000000..b54cda142d --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/shape_cast.cpp @@ -0,0 +1,193 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +#include "box2d/b2_distance.h" + +class ShapeCast : public Test +{ +public: + enum + { + e_vertexCount = 8 + }; + + ShapeCast() + { +#if 1 + m_vAs[0].Set(-0.5f, 1.0f); + m_vAs[1].Set(0.5f, 1.0f); + m_vAs[2].Set(0.0f, 0.0f); + m_countA = 3; + m_radiusA = b2_polygonRadius; + + m_vBs[0].Set(-0.5f, -0.5f); + m_vBs[1].Set(0.5f, -0.5f); + m_vBs[2].Set(0.5f, 0.5f); + m_vBs[3].Set(-0.5f, 0.5f); + m_countB = 4; + m_radiusB = b2_polygonRadius; + + m_transformA.p.Set(0.0f, 0.25f); + m_transformA.q.SetIdentity(); + m_transformB.p.Set(-4.0f, 0.0f); + m_transformB.q.SetIdentity(); + m_translationB.Set(8.0f, 0.0f); +#elif 0 + m_vAs[0].Set(0.0f, 0.0f); + m_countA = 1; + m_radiusA = 0.5f; + + m_vBs[0].Set(0.0f, 0.0f); + m_countB = 1; + m_radiusB = 0.5f; + + m_transformA.p.Set(0.0f, 0.25f); + m_transformA.q.SetIdentity(); + m_transformB.p.Set(-4.0f, 0.0f); + m_transformB.q.SetIdentity(); + m_translationB.Set(8.0f, 0.0f); +#else + m_vAs[0].Set(0.0f, 0.0f); + m_vAs[1].Set(2.0f, 0.0f); + m_countA = 2; + m_radiusA = b2_polygonRadius; + + m_vBs[0].Set(0.0f, 0.0f); + m_countB = 1; + m_radiusB = 0.25f; + + // Initial overlap + m_transformA.p.Set(0.0f, 0.0f); + m_transformA.q.SetIdentity(); + m_transformB.p.Set(-0.244360745f, 0.05999358f); + m_transformB.q.SetIdentity(); + m_translationB.Set(0.0f, 0.0399999991f); +#endif + } + + static Test* Create() + { + return new ShapeCast; + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + b2ShapeCastInput input; + input.proxyA.Set(m_vAs, m_countA, m_radiusA); + input.proxyB.Set(m_vBs, m_countB, m_radiusB); + input.transformA = m_transformA; + input.transformB = m_transformB; + input.translationB = m_translationB; + + b2ShapeCastOutput output; + bool hit = b2ShapeCast(&output, &input); + + b2Transform transformB2; + transformB2.q = m_transformB.q; + transformB2.p = m_transformB.p + output.lambda * input.translationB; + + b2DistanceInput distanceInput; + distanceInput.proxyA.Set(m_vAs, m_countA, m_radiusA); + distanceInput.proxyB.Set(m_vBs, m_countB, m_radiusB); + distanceInput.transformA = m_transformA; + distanceInput.transformB = transformB2; + distanceInput.useRadii = false; + b2SimplexCache simplexCache; + simplexCache.count = 0; + b2DistanceOutput distanceOutput; + + b2Distance(&distanceOutput, &simplexCache, &distanceInput); + + //g_debugDraw.DrawString(5, m_textLine, "hit = %s, iters = %d, lambda = %g, distance = %g", + // hit ? "true" : "false", output.iterations, output.lambda, distanceOutput.distance); + //m_textLine += m_textIncrement; + + b2Vec2 vertices[b2_maxPolygonVertices]; + + for (int32 i = 0; i < m_countA; ++i) + { + vertices[i] = b2Mul(m_transformA, m_vAs[i]); + } + + if (m_countA == 1) + { + // g_debugDraw.DrawCircle(vertices[0], m_radiusA, b2Color(0.9f, 0.9f, 0.9f)); + } + else + { + // g_debugDraw.DrawPolygon(vertices, m_countA, b2Color(0.9f, 0.9f, 0.9f)); + } + + for (int32 i = 0; i < m_countB; ++i) + { + vertices[i] = b2Mul(m_transformB, m_vBs[i]); + } + + if (m_countB == 1) + { + // g_debugDraw.DrawCircle(vertices[0], m_radiusB, b2Color(0.5f, 0.9f, 0.5f)); + } + else + { + // g_debugDraw.DrawPolygon(vertices, m_countB, b2Color(0.5f, 0.9f, 0.5f)); + } + + for (int32 i = 0; i < m_countB; ++i) + { + vertices[i] = b2Mul(transformB2, m_vBs[i]); + } + + if (m_countB == 1) + { + // g_debugDraw.DrawCircle(vertices[0], m_radiusB, b2Color(0.5f, 0.7f, 0.9f)); + } + else + { + // g_debugDraw.DrawPolygon(vertices, m_countB, b2Color(0.5f, 0.7f, 0.9f)); + } + + if (hit) + { + b2Vec2 p1 = output.point; + // g_debugDraw.DrawPoint(p1, 10.0f, b2Color(0.9f, 0.3f, 0.3f)); + b2Vec2 p2 = p1 + output.normal; + // g_debugDraw.DrawSegment(p1, p2, b2Color(0.9f, 0.3f, 0.3f)); + } + } + + b2Vec2 m_vAs[b2_maxPolygonVertices]; + int32 m_countA; + float m_radiusA; + + b2Vec2 m_vBs[b2_maxPolygonVertices]; + int32 m_countB; + float m_radiusB; + + b2Transform m_transformA; + b2Transform m_transformB; + b2Vec2 m_translationB; +}; + +static int testIndex = RegisterTest("Collision", "Shape Cast", ShapeCast::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/shape_editing.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/shape_editing.cpp new file mode 100644 index 0000000000..fcf96ed10a --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/shape_editing.cpp @@ -0,0 +1,108 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class ShapeEditing : public Test +{ +public: + + ShapeEditing() + { + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 10.0f); + m_body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(4.0f, 4.0f, b2Vec2(0.0f, 0.0f), 0.0f); + m_fixture1 = m_body->CreateFixture(&shape, 10.0f); + + m_fixture2 = NULL; + + m_sensor = false; + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_C: + // if (m_fixture2 == NULL) + // { + // b2CircleShape shape; + // shape.m_radius = 3.0f; + // shape.m_p.Set(0.5f, -4.0f); + // m_fixture2 = m_body->CreateFixture(&shape, 10.0f); + // m_body->SetAwake(true); + // } + // break; + + // case GLFW_KEY_D: + // if (m_fixture2 != NULL) + // { + // m_body->DestroyFixture(m_fixture2); + // m_fixture2 = NULL; + // m_body->SetAwake(true); + // } + // break; + + // case GLFW_KEY_S: + // if (m_fixture2 != NULL) + // { + // m_sensor = !m_sensor; + // m_fixture2->SetSensor(m_sensor); + // } + // break; + // } + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + //g_debugDraw.DrawString(5, m_textLine, "Press: (c) create a shape, (d) destroy a shape."); + //m_textLine += m_textIncrement; + //g_debugDraw.DrawString(5, m_textLine, "sensor = %d", m_sensor); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new ShapeEditing; + } + + b2Body* m_body; + b2Fixture* m_fixture1; + b2Fixture* m_fixture2; + bool m_sensor; +}; + +static int testIndex = RegisterTest("Examples", "Shape Editing", ShapeEditing::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/skier.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/skier.cpp new file mode 100644 index 0000000000..f0a0ae7ba6 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/skier.cpp @@ -0,0 +1,150 @@ +/* +Test case for collision/jerking issue. +*/ + +#include "../test.h" + +#include +#include + +class Skier : public Test +{ +public: + Skier() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + float const PlatformWidth = 8.0f; + + /* + First angle is from the horizontal and should be negative for a downward slope. + Second angle is relative to the preceding slope, and should be positive, creating a kind of + loose 'Z'-shape from the 3 edges. + If A1 = -10, then A2 <= ~1.5 will result in the collision glitch. + If A1 = -30, then A2 <= ~10.0 will result in the glitch. + */ + float const Angle1Degrees = -30.0f; + float const Angle2Degrees = 10.0f; + + /* + The larger the value of SlopeLength, the less likely the glitch will show up. + */ + float const SlopeLength = 2.0f; + + float const SurfaceFriction = 0.2f; + + // Convert to radians + float const Slope1Incline = -Angle1Degrees * b2_pi / 180.0f; + float const Slope2Incline = Slope1Incline - Angle2Degrees * b2_pi / 180.0f; + // + + m_platform_width = PlatformWidth; + + // Horizontal platform + b2Vec2 v1(-PlatformWidth, 0.0f); + b2Vec2 v2(0.0f, 0.0f); + b2Vec2 v3(SlopeLength * cosf(Slope1Incline), -SlopeLength * sinf(Slope1Incline)); + b2Vec2 v4(v3.x + SlopeLength * cosf(Slope2Incline), v3.y - SlopeLength * sinf(Slope2Incline)); + b2Vec2 v5(v4.x, v4.y - 1.0f); + + b2Vec2 vertices[5] = { v5, v4, v3, v2, v1 }; + + b2ChainShape shape; + shape.CreateLoop(vertices, 5); + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 0.0f; + fd.friction = SurfaceFriction; + + ground->CreateFixture(&fd); + } + + { + float const BodyWidth = 1.0f; + float const BodyHeight = 2.5f; + float const SkiLength = 3.0f; + + /* + Larger values for this seem to alleviate the issue to some extent. + */ + float const SkiThickness = 0.3f; + + float const SkiFriction = 0.0f; + float const SkiRestitution = 0.15f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + + float initial_y = BodyHeight / 2 + SkiThickness; + bd.position.Set(-m_platform_width / 2, initial_y); + + b2Body* skier = m_world->CreateBody(&bd); + + b2PolygonShape ski; + b2Vec2 verts[4]; + verts[0].Set(-SkiLength / 2 - SkiThickness, -BodyHeight / 2); + verts[1].Set(-SkiLength / 2, -BodyHeight / 2 - SkiThickness); + verts[2].Set(SkiLength / 2, -BodyHeight / 2 - SkiThickness); + verts[3].Set(SkiLength / 2 + SkiThickness, -BodyHeight / 2); + ski.Set(verts, 4); + + b2FixtureDef fd; + fd.density = 1.0f; + + fd.friction = SkiFriction; + fd.restitution = SkiRestitution; + + fd.shape = &ski; + skier->CreateFixture(&fd); + + skier->SetLinearVelocity(b2Vec2(0.5f, 0.0f)); + + m_skier = skier; + } + + //g_camera.m_center = b2Vec2(m_platform_width / 2.0f, 0.0f); + //g_camera.m_zoom = 0.4f; + //m_fixed_camera = true; + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_C: + // m_fixed_camera = !m_fixed_camera; + // if(m_fixed_camera) + // { + // g_camera.m_center = b2Vec2(m_platform_width / 2.0f, 0.0f); + // } + // break; + // } + //} + + void Step(Settings* settings) override + { + //g_debugDraw.DrawString(5, m_textLine, "Keys: c = Camera fixed/tracking"); + //m_textLine += m_textIncrement; + + if(!m_fixed_camera) + { +// g_camera.m_center = m_skier->GetPosition(); + } + + Test::Step(settings); + } + + static Test* Create() + { + return new Skier; + } + + b2Body* m_skier; + float m_platform_width; + bool m_fixed_camera; +}; + +static int testIndex = RegisterTest("Bugs", "Skier", Skier::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/slider_crank_1.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/slider_crank_1.cpp new file mode 100644 index 0000000000..d89a3c6daa --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/slider_crank_1.cpp @@ -0,0 +1,106 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// A basic slider crank created for GDC tutorial: Understanding Constraints +class SliderCrank1 : public Test +{ +public: + SliderCrank1() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + bd.position.Set(0.0f, 17.0f); + ground = m_world->CreateBody(&bd); + } + + { + b2Body* prevBody = ground; + + // Define crank. + { + b2PolygonShape shape; + shape.SetAsBox(4.0f, 1.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-8.0f, 20.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 2.0f); + + b2RevoluteJointDef rjd; + rjd.Initialize(prevBody, body, b2Vec2(-12.0f, 20.0f)); + m_world->CreateJoint(&rjd); + + prevBody = body; + } + + // Define connecting rod + { + b2PolygonShape shape; + shape.SetAsBox(8.0f, 1.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(4.0f, 20.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 2.0f); + + b2RevoluteJointDef rjd; + rjd.Initialize(prevBody, body, b2Vec2(-4.0f, 20.0f)); + m_world->CreateJoint(&rjd); + + prevBody = body; + } + + // Define piston + { + b2PolygonShape shape; + shape.SetAsBox(3.0f, 3.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.fixedRotation = true; + bd.position.Set(12.0f, 20.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 2.0f); + + b2RevoluteJointDef rjd; + rjd.Initialize(prevBody, body, b2Vec2(12.0f, 20.0f)); + m_world->CreateJoint(&rjd); + + b2PrismaticJointDef pjd; + pjd.Initialize(ground, body, b2Vec2(12.0f, 17.0f), b2Vec2(1.0f, 0.0f)); + m_world->CreateJoint(&pjd); + } + } + } + + static Test* Create() + { + return new SliderCrank1; + } +}; + +static int testIndex = RegisterTest("Examples", "Slider Crank 1", SliderCrank1::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/slider_crank_2.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/slider_crank_2.cpp new file mode 100644 index 0000000000..895506f11d --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/slider_crank_2.cpp @@ -0,0 +1,160 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" + +// A motor driven slider crank with joint friction. + +class SliderCrank2 : public Test +{ +public: + SliderCrank2() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2Body* prevBody = ground; + + // Define crank. + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 2.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 7.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 2.0f); + + b2RevoluteJointDef rjd; + rjd.Initialize(prevBody, body, b2Vec2(0.0f, 5.0f)); + rjd.motorSpeed = 1.0f * b2_pi; + rjd.maxMotorTorque = 10000.0f; + rjd.enableMotor = true; + m_joint1 = (b2RevoluteJoint*)m_world->CreateJoint(&rjd); + + prevBody = body; + } + + // Define follower. + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 4.0f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 13.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 2.0f); + + b2RevoluteJointDef rjd; + rjd.Initialize(prevBody, body, b2Vec2(0.0f, 9.0f)); + rjd.enableMotor = false; + m_world->CreateJoint(&rjd); + + prevBody = body; + } + + // Define piston + { + b2PolygonShape shape; + shape.SetAsBox(1.5f, 1.5f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.fixedRotation = true; + bd.position.Set(0.0f, 17.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 2.0f); + + b2RevoluteJointDef rjd; + rjd.Initialize(prevBody, body, b2Vec2(0.0f, 17.0f)); + m_world->CreateJoint(&rjd); + + b2PrismaticJointDef pjd; + pjd.Initialize(ground, body, b2Vec2(0.0f, 17.0f), b2Vec2(0.0f, 1.0f)); + + pjd.maxMotorForce = 1000.0f; + pjd.enableMotor = true; + + m_joint2 = (b2PrismaticJoint*)m_world->CreateJoint(&pjd); + } + + // Create a payload + { + b2PolygonShape shape; + shape.SetAsBox(1.5f, 1.5f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 23.0f); + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 2.0f); + } + } + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_F: + // m_joint2->EnableMotor(!m_joint2->IsMotorEnabled()); + // m_joint2->GetBodyB()->SetAwake(true); + // break; + + // case GLFW_KEY_M: + // m_joint1->EnableMotor(!m_joint1->IsMotorEnabled()); + // m_joint1->GetBodyB()->SetAwake(true); + // break; + // } + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + //g_debugDraw.DrawString(5, m_textLine, "Keys: (f) toggle friction, (m) toggle motor"); + //m_textLine += m_textIncrement; + //float torque = m_joint1->GetMotorTorque(settings.m_hertz); + //g_debugDraw.DrawString(5, m_textLine, "Motor Torque = %5.0f", (float) torque); + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new SliderCrank2; + } + + b2RevoluteJoint* m_joint1; + b2PrismaticJoint* m_joint2; +}; + +static int testIndex = RegisterTest("Examples", "Slider Crank 2", SliderCrank2::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/theo_jansen.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/theo_jansen.cpp new file mode 100644 index 0000000000..75057e4b15 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/theo_jansen.cpp @@ -0,0 +1,266 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Inspired by a contribution from roman_m +// Dimensions scooped from APE (http://www.cove.org/ape/index.htm) + +#include "../test.h" + +class TheoJansen : public Test +{ +public: + + void CreateLeg(float s, const b2Vec2& wheelAnchor) + { + b2Vec2 p1(5.4f * s, -6.1f); + b2Vec2 p2(7.2f * s, -1.2f); + b2Vec2 p3(4.3f * s, -1.9f); + b2Vec2 p4(3.1f * s, 0.8f); + b2Vec2 p5(6.0f * s, 1.5f); + b2Vec2 p6(2.5f * s, 3.7f); + + b2FixtureDef fd1, fd2; + fd1.filter.groupIndex = -1; + fd2.filter.groupIndex = -1; + fd1.density = 1.0f; + fd2.density = 1.0f; + + b2PolygonShape poly1, poly2; + + if (s > 0.0f) + { + b2Vec2 vertices[3]; + + vertices[0] = p1; + vertices[1] = p2; + vertices[2] = p3; + poly1.Set(vertices, 3); + + vertices[0] = b2Vec2_zero; + vertices[1] = p5 - p4; + vertices[2] = p6 - p4; + poly2.Set(vertices, 3); + } + else + { + b2Vec2 vertices[3]; + + vertices[0] = p1; + vertices[1] = p3; + vertices[2] = p2; + poly1.Set(vertices, 3); + + vertices[0] = b2Vec2_zero; + vertices[1] = p6 - p4; + vertices[2] = p5 - p4; + poly2.Set(vertices, 3); + } + + fd1.shape = &poly1; + fd2.shape = &poly2; + + b2BodyDef bd1, bd2; + bd1.type = b2_dynamicBody; + bd2.type = b2_dynamicBody; + bd1.position = m_offset; + bd2.position = p4 + m_offset; + + bd1.angularDamping = 10.0f; + bd2.angularDamping = 10.0f; + + b2Body* body1 = m_world->CreateBody(&bd1); + b2Body* body2 = m_world->CreateBody(&bd2); + + body1->CreateFixture(&fd1); + body2->CreateFixture(&fd2); + + { + b2DistanceJointDef jd; + + // Using a soft distance constraint can reduce some jitter. + // It also makes the structure seem a bit more fluid by + // acting like a suspension system. + float dampingRatio = 0.5f; + float frequencyHz = 10.0f; + + jd.Initialize(body1, body2, p2 + m_offset, p5 + m_offset); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_world->CreateJoint(&jd); + + jd.Initialize(body1, body2, p3 + m_offset, p4 + m_offset); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_world->CreateJoint(&jd); + + jd.Initialize(body1, m_wheel, p3 + m_offset, wheelAnchor + m_offset); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_world->CreateJoint(&jd); + + jd.Initialize(body2, m_wheel, p6 + m_offset, wheelAnchor + m_offset); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_world->CreateJoint(&jd); + } + + { + b2RevoluteJointDef jd; + jd.Initialize(body2, m_chassis, p4 + m_offset); + m_world->CreateJoint(&jd); + } + } + + TheoJansen() + { + m_offset.Set(0.0f, 8.0f); + m_motorSpeed = 2.0f; + m_motorOn = true; + b2Vec2 pivot(0.0f, 0.8f); + + // Ground + { + b2BodyDef bd; + b2Body* ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-50.0f, 0.0f), b2Vec2(50.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(b2Vec2(-50.0f, 0.0f), b2Vec2(-50.0f, 10.0f)); + ground->CreateFixture(&shape, 0.0f); + + shape.SetTwoSided(b2Vec2(50.0f, 0.0f), b2Vec2(50.0f, 10.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + // Balls + for (int32 i = 0; i < 40; ++i) + { + b2CircleShape shape; + shape.m_radius = 0.25f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(-40.0f + 2.0f * i, 0.5f); + + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 1.0f); + } + + // Chassis + { + b2PolygonShape shape; + shape.SetAsBox(2.5f, 1.0f); + + b2FixtureDef sd; + sd.density = 1.0f; + sd.shape = &shape; + sd.filter.groupIndex = -1; + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = pivot + m_offset; + m_chassis = m_world->CreateBody(&bd); + m_chassis->CreateFixture(&sd); + } + + { + b2CircleShape shape; + shape.m_radius = 1.6f; + + b2FixtureDef sd; + sd.density = 1.0f; + sd.shape = &shape; + sd.filter.groupIndex = -1; + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = pivot + m_offset; + m_wheel = m_world->CreateBody(&bd); + m_wheel->CreateFixture(&sd); + } + + { + b2RevoluteJointDef jd; + jd.Initialize(m_wheel, m_chassis, pivot + m_offset); + jd.collideConnected = false; + jd.motorSpeed = m_motorSpeed; + jd.maxMotorTorque = 400.0f; + jd.enableMotor = m_motorOn; + m_motorJoint = (b2RevoluteJoint*)m_world->CreateJoint(&jd); + } + + b2Vec2 wheelAnchor; + + wheelAnchor = pivot + b2Vec2(0.0f, -0.8f); + + CreateLeg(-1.0f, wheelAnchor); + CreateLeg(1.0f, wheelAnchor); + + m_wheel->SetTransform(m_wheel->GetPosition(), 120.0f * b2_pi / 180.0f); + CreateLeg(-1.0f, wheelAnchor); + CreateLeg(1.0f, wheelAnchor); + + m_wheel->SetTransform(m_wheel->GetPosition(), -120.0f * b2_pi / 180.0f); + CreateLeg(-1.0f, wheelAnchor); + CreateLeg(1.0f, wheelAnchor); + } + + void Step(Settings* settings) override + { + /* g_debugDraw.DrawString(5, m_textLine, "Keys: left = a, brake = s, right = d, toggle motor = m"); + m_textLine += m_textIncrement;*/ + + Test::Step(settings); + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_A: + // m_motorJoint->SetMotorSpeed(-m_motorSpeed); + // break; + + // case GLFW_KEY_S: + // m_motorJoint->SetMotorSpeed(0.0f); + // break; + + // case GLFW_KEY_D: + // m_motorJoint->SetMotorSpeed(m_motorSpeed); + // break; + + // case GLFW_KEY_M: + // m_motorJoint->EnableMotor(!m_motorJoint->IsMotorEnabled()); + // break; + // } + //} + + static Test* Create() + { + return new TheoJansen; + } + + b2Vec2 m_offset; + b2Body* m_chassis; + b2Body* m_wheel; + b2RevoluteJoint* m_motorJoint; + bool m_motorOn; + float m_motorSpeed; +}; + +static int testIndex = RegisterTest("Examples", "Theo Jansen", TheoJansen::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/tiles.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/tiles.cpp new file mode 100644 index 0000000000..4f344eaafb --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/tiles.cpp @@ -0,0 +1,159 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +/// This stress tests the dynamic tree broad-phase. This also shows that tile +/// based collision is _not_ smooth due to Box2D not knowing about adjacency. +class Tiles : public Test +{ +public: + enum + { + e_count = 20 + }; + + Tiles() + { + m_fixtureCount = 0; + b2Timer timer; + + { + float a = 0.5f; + b2BodyDef bd; + bd.position.y = -a; + b2Body* ground = m_world->CreateBody(&bd); + +#if 1 + int32 N = 200; + int32 M = 10; + b2Vec2 position; + position.y = 0.0f; + for (int32 j = 0; j < M; ++j) + { + position.x = -N * a; + for (int32 i = 0; i < N; ++i) + { + b2PolygonShape shape; + shape.SetAsBox(a, a, position, 0.0f); + ground->CreateFixture(&shape, 0.0f); + ++m_fixtureCount; + position.x += 2.0f * a; + } + position.y -= 2.0f * a; + } +#else + int32 N = 200; + int32 M = 10; + b2Vec2 position; + position.x = -N * a; + for (int32 i = 0; i < N; ++i) + { + position.y = 0.0f; + for (int32 j = 0; j < M; ++j) + { + b2PolygonShape shape; + shape.SetAsBox(a, a, position, 0.0f); + ground->CreateFixture(&shape, 0.0f); + position.y -= 2.0f * a; + } + position.x += 2.0f * a; + } +#endif + } + + { + float a = 0.5f; + b2PolygonShape shape; + shape.SetAsBox(a, a); + + b2Vec2 x(-7.0f, 0.75f); + b2Vec2 y; + b2Vec2 deltaX(0.5625f, 1.25f); + b2Vec2 deltaY(1.125f, 0.0f); + + for (int32 i = 0; i < e_count; ++i) + { + y = x; + + for (int32 j = i; j < e_count; ++j) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position = y; + + //if (i == 0 && j == 0) + //{ + // bd.allowSleep = false; + //} + //else + //{ + // bd.allowSleep = true; + //} + + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 5.0f); + ++m_fixtureCount; + y += deltaY; + } + + x += deltaX; + } + } + + m_createTime = timer.GetMilliseconds(); + } + + void Step(Settings* settings) override + { + const b2ContactManager& cm = m_world->GetContactManager(); + int32 height = cm.m_broadPhase.GetTreeHeight(); + int32 leafCount = cm.m_broadPhase.GetProxyCount(); + int32 minimumNodeCount = 2 * leafCount - 1; + float minimumHeight = ceilf(logf(float(minimumNodeCount)) / logf(2.0f)); + //g_debugDraw.DrawString(5, m_textLine, "dynamic tree height = %d, min = %d", height, int32(minimumHeight)); + //m_textLine += m_textIncrement; + + Test::Step(settings); + + //g_debugDraw.DrawString(5, m_textLine, "create time = %6.2f ms, fixture count = %d", + // m_createTime, m_fixtureCount); + //m_textLine += m_textIncrement; + + //b2DynamicTree* tree = &m_world->m_contactManager.m_broadPhase.m_tree; + + //if (m_stepCount == 400) + //{ + // tree->RebuildBottomUp(); + //} + } + + static Test* Create() + { + return new Tiles; + } + + int32 m_fixtureCount; + float m_createTime; +}; + +static int testIndex = RegisterTest("Benchmark", "Tiles", Tiles::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/time_of_impact.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/time_of_impact.cpp new file mode 100644 index 0000000000..b8fb9d8b61 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/time_of_impact.cpp @@ -0,0 +1,131 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +#include "box2d/b2_time_of_impact.h" + +class TimeOfImpact : public Test +{ +public: + TimeOfImpact() + { + m_shapeA.SetAsBox(25.0f, 5.0f); + m_shapeB.SetAsBox(2.5f, 2.5f); + } + + static Test* Create() + { + return new TimeOfImpact; + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + b2Sweep sweepA; + sweepA.c0.Set(24.0f, -60.0f); + sweepA.a0 = 2.95f; + sweepA.c = sweepA.c0; + sweepA.a = sweepA.a0; + sweepA.localCenter.SetZero(); + + b2Sweep sweepB; + sweepB.c0.Set(53.474274f, -50.252514f); + sweepB.a0 = 513.36676f; // - 162.0f * b2_pi; + sweepB.c.Set(54.595478f, -51.083473f); + sweepB.a = 513.62781f; // - 162.0f * b2_pi; + sweepB.localCenter.SetZero(); + + //sweepB.a0 -= 300.0f * b2_pi; + //sweepB.a -= 300.0f * b2_pi; + + b2TOIInput input; + input.proxyA.Set(&m_shapeA, 0); + input.proxyB.Set(&m_shapeB, 0); + input.sweepA = sweepA; + input.sweepB = sweepB; + input.tMax = 1.0f; + + b2TOIOutput output; + + b2TimeOfImpact(&output, &input); + + //g_debugDraw.DrawString(5, m_textLine, "toi = %g", output.t); + //m_textLine += m_textIncrement; + + extern B2_API int32 b2_toiMaxIters, b2_toiMaxRootIters; + //g_debugDraw.DrawString(5, m_textLine, "max toi iters = %d, max root iters = %d", b2_toiMaxIters, b2_toiMaxRootIters); + //m_textLine += m_textIncrement; + + b2Vec2 vertices[b2_maxPolygonVertices]; + + b2Transform transformA; + sweepA.GetTransform(&transformA, 0.0f); + for (int32 i = 0; i < m_shapeA.m_count; ++i) + { + vertices[i] = b2Mul(transformA, m_shapeA.m_vertices[i]); + } + // g_debugDraw.DrawPolygon(vertices, m_shapeA.m_count, b2Color(0.9f, 0.9f, 0.9f)); + + b2Transform transformB; + sweepB.GetTransform(&transformB, 0.0f); + + //b2Vec2 localPoint(2.0f, -0.1f); + + for (int32 i = 0; i < m_shapeB.m_count; ++i) + { + vertices[i] = b2Mul(transformB, m_shapeB.m_vertices[i]); + } + // g_debugDraw.DrawPolygon(vertices, m_shapeB.m_count, b2Color(0.5f, 0.9f, 0.5f)); + + sweepB.GetTransform(&transformB, output.t); + for (int32 i = 0; i < m_shapeB.m_count; ++i) + { + vertices[i] = b2Mul(transformB, m_shapeB.m_vertices[i]); + } + // g_debugDraw.DrawPolygon(vertices, m_shapeB.m_count, b2Color(0.5f, 0.7f, 0.9f)); + + sweepB.GetTransform(&transformB, 1.0f); + for (int32 i = 0; i < m_shapeB.m_count; ++i) + { + vertices[i] = b2Mul(transformB, m_shapeB.m_vertices[i]); + } + // g_debugDraw.DrawPolygon(vertices, m_shapeB.m_count, b2Color(0.9f, 0.5f, 0.5f)); + +#if 0 + for (float t = 0.0f; t < 1.0f; t += 0.1f) + { + sweepB.GetTransform(&transformB, t); + for (int32 i = 0; i < m_shapeB.m_count; ++i) + { + vertices[i] = b2Mul(transformB, m_shapeB.m_vertices[i]); + } + g_debugDraw.DrawPolygon(vertices, m_shapeB.m_count, b2Color(0.9f, 0.5f, 0.5f)); + } +#endif + } + + b2PolygonShape m_shapeA; + b2PolygonShape m_shapeB; +}; + +static int testIndex = RegisterTest("Collision", "Time of Impact", TimeOfImpact::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/tumbler.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/tumbler.cpp new file mode 100644 index 0000000000..ce360e02d0 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/tumbler.cpp @@ -0,0 +1,102 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +class Tumbler : public Test +{ +public: + + enum + { + e_count = 800 + }; + + Tumbler() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + } + + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.allowSleep = false; + bd.position.Set(0.0f, 10.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.5f, 10.0f, b2Vec2( 10.0f, 0.0f), 0.0); + body->CreateFixture(&shape, 5.0f); + shape.SetAsBox(0.5f, 10.0f, b2Vec2(-10.0f, 0.0f), 0.0); + body->CreateFixture(&shape, 5.0f); + shape.SetAsBox(10.0f, 0.5f, b2Vec2(0.0f, 10.0f), 0.0); + body->CreateFixture(&shape, 5.0f); + shape.SetAsBox(10.0f, 0.5f, b2Vec2(0.0f, -10.0f), 0.0); + body->CreateFixture(&shape, 5.0f); + + b2RevoluteJointDef jd; + jd.bodyA = ground; + jd.bodyB = body; + jd.localAnchorA.Set(0.0f, 10.0f); + jd.localAnchorB.Set(0.0f, 0.0f); + jd.referenceAngle = 0.0f; + jd.motorSpeed = 0.05f * b2_pi; + jd.maxMotorTorque = 1e8f; + jd.enableMotor = true; + m_joint = (b2RevoluteJoint*)m_world->CreateJoint(&jd); + } + + m_count = 0; + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + if (m_count < e_count) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 10.0f); + b2Body* body = m_world->CreateBody(&bd); + + b2PolygonShape shape; + shape.SetAsBox(0.125f, 0.125f); + body->CreateFixture(&shape, 1.0f); + + ++m_count; + } + } + + static Test* Create() + { + return new Tumbler; + } + + b2RevoluteJoint* m_joint; + int32 m_count; +}; + + static int testIndex = RegisterTest("Benchmark", "Tumbler", Tumbler::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/web.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/web.cpp new file mode 100644 index 0000000000..65e7cec9cb --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/web.cpp @@ -0,0 +1,218 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" + +// Test distance joints, body destruction, and joint destruction. +class Web : public Test +{ +public: + Web() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.5f); + + b2BodyDef bd; + bd.type = b2_dynamicBody; + + bd.position.Set(-5.0f, 5.0f); + m_bodies[0] = m_world->CreateBody(&bd); + m_bodies[0]->CreateFixture(&shape, 5.0f); + + bd.position.Set(5.0f, 5.0f); + m_bodies[1] = m_world->CreateBody(&bd); + m_bodies[1]->CreateFixture(&shape, 5.0f); + + bd.position.Set(5.0f, 15.0f); + m_bodies[2] = m_world->CreateBody(&bd); + m_bodies[2]->CreateFixture(&shape, 5.0f); + + bd.position.Set(-5.0f, 15.0f); + m_bodies[3] = m_world->CreateBody(&bd); + m_bodies[3]->CreateFixture(&shape, 5.0f); + + b2DistanceJointDef jd; + b2Vec2 p1, p2, d; + + float frequencyHz = 2.0f; + float dampingRatio = 0.0f; + + jd.bodyA = ground; + jd.bodyB = m_bodies[0]; + jd.localAnchorA.Set(-10.0f, 0.0f); + jd.localAnchorB.Set(-0.5f, -0.5f); + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[0] = m_world->CreateJoint(&jd); + + jd.bodyA = ground; + jd.bodyB = m_bodies[1]; + jd.localAnchorA.Set(10.0f, 0.0f); + jd.localAnchorB.Set(0.5f, -0.5f); + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[1] = m_world->CreateJoint(&jd); + + jd.bodyA = ground; + jd.bodyB = m_bodies[2]; + jd.localAnchorA.Set(10.0f, 20.0f); + jd.localAnchorB.Set(0.5f, 0.5f); + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[2] = m_world->CreateJoint(&jd); + + jd.bodyA = ground; + jd.bodyB = m_bodies[3]; + jd.localAnchorA.Set(-10.0f, 20.0f); + jd.localAnchorB.Set(-0.5f, 0.5f); + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[3] = m_world->CreateJoint(&jd); + + jd.bodyA = m_bodies[0]; + jd.bodyB = m_bodies[1]; + jd.localAnchorA.Set(0.5f, 0.0f); + jd.localAnchorB.Set(-0.5f, 0.0f);; + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[4] = m_world->CreateJoint(&jd); + + jd.bodyA = m_bodies[1]; + jd.bodyB = m_bodies[2]; + jd.localAnchorA.Set(0.0f, 0.5f); + jd.localAnchorB.Set(0.0f, -0.5f); + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[5] = m_world->CreateJoint(&jd); + + jd.bodyA = m_bodies[2]; + jd.bodyB = m_bodies[3]; + jd.localAnchorA.Set(-0.5f, 0.0f); + jd.localAnchorB.Set(0.5f, 0.0f); + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[6] = m_world->CreateJoint(&jd); + + jd.bodyA = m_bodies[3]; + jd.bodyB = m_bodies[0]; + jd.localAnchorA.Set(0.0f, -0.5f); + jd.localAnchorB.Set(0.0f, 0.5f); + p1 = jd.bodyA->GetWorldPoint(jd.localAnchorA); + p2 = jd.bodyB->GetWorldPoint(jd.localAnchorB); + d = p2 - p1; + jd.length = d.Length(); + b2LinearStiffness(jd.stiffness, jd.damping, frequencyHz, dampingRatio, jd.bodyA, jd.bodyB); + m_joints[7] = m_world->CreateJoint(&jd); + } + } + + //void Keyboard(int key) override + //{ + // switch (key) + // { + // case GLFW_KEY_B: + // for (int32 i = 0; i < 4; ++i) + // { + // if (m_bodies[i]) + // { + // m_world->DestroyBody(m_bodies[i]); + // m_bodies[i] = NULL; + // break; + // } + // } + // break; + + // case GLFW_KEY_J: + // for (int32 i = 0; i < 8; ++i) + // { + // if (m_joints[i]) + // { + // m_world->DestroyJoint(m_joints[i]); + // m_joints[i] = NULL; + // break; + // } + // } + // break; + // } + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + //g_debugDraw.DrawString(5, m_textLine, "Press: (b) to delete a body, (j) to delete a joint"); + //m_textLine += m_textIncrement; + } + + void JointDestroyed(b2Joint* joint) override + { + for (int32 i = 0; i < 8; ++i) + { + if (m_joints[i] == joint) + { + m_joints[i] = NULL; + break; + } + } + } + + static Test* Create() + { + return new Web; + } + + b2Body* m_bodies[4]; + b2Joint* m_joints[8]; +}; + +static int testIndex = RegisterTest("Examples", "Web", Web::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/wheel_joint.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/wheel_joint.cpp new file mode 100644 index 0000000000..dc6f448382 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/wheel_joint.cpp @@ -0,0 +1,126 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//#include "settings.h" +#include "../test.h" +//#include "imgui/imgui.h" + +// Test the wheel joint with motor, spring, and limit options. +class WheelJoint : public Test +{ +public: + WheelJoint() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + m_enableLimit = true; + m_enableMotor = false; + m_motorSpeed = 10.0f; + + { + b2CircleShape shape; + shape.m_radius = 2.0f; + + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.0f, 10.0f); + bd.allowSleep = false; + b2Body* body = m_world->CreateBody(&bd); + body->CreateFixture(&shape, 5.0f); + + b2WheelJointDef jd; + + // Horizontal + jd.Initialize(ground, body, bd.position, b2Vec2(0.0f, 1.0f)); + + jd.motorSpeed = m_motorSpeed; + jd.maxMotorTorque = 10000.0f; + jd.enableMotor = m_enableMotor; + jd.lowerTranslation = -3.0f; + jd.upperTranslation = 3.0f; + jd.enableLimit = m_enableLimit; + + float hertz = 1.0f; + float dampingRatio = 0.7f; + b2LinearStiffness(jd.stiffness, jd.damping, hertz, dampingRatio, ground, body); + + m_joint = (b2WheelJoint*)m_world->CreateJoint(&jd); + } + } + + void Step(Settings* settings) override + { + Test::Step(settings); + + // float torque = m_joint->GetMotorTorque(settings.hz); + //g_debugDraw.DrawString(5, m_textLine, "Motor Torque = %4.0f", torque); + //m_textLine += m_textIncrement; + + // b2Vec2 F = m_joint->GetReactionForce(settings.hz); + //g_debugDraw.DrawString(5, m_textLine, "Reaction Force = (%4.1f, %4.1f)", F.x, F.y); + //m_textLine += m_textIncrement; + } + + /*void UpdateUI() override + { + ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + ImGui::SetNextWindowSize(ImVec2(200.0f, 100.0f)); + ImGui::Begin("Joint Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + if (ImGui::Checkbox("Limit", &m_enableLimit)) + { + m_joint->EnableLimit(m_enableLimit); + } + + if (ImGui::Checkbox("Motor", &m_enableMotor)) + { + m_joint->EnableMotor(m_enableMotor); + } + + if (ImGui::SliderFloat("Speed", &m_motorSpeed, -100.0f, 100.0f, "%.0f")) + { + m_joint->SetMotorSpeed(m_motorSpeed); + } + + ImGui::End();*/ + //} + + static Test* Create() + { + return new WheelJoint; + } + + b2WheelJoint* m_joint; + float m_motorSpeed; + bool m_enableMotor; + bool m_enableLimit; +}; + +static int testIndex = RegisterTest("Joints", "Wheel", WheelJoint::Create); diff --git a/tests/cpp-tests/Classes/Box2DTestBed/tests/wrecking_ball.cpp b/tests/cpp-tests/Classes/Box2DTestBed/tests/wrecking_ball.cpp new file mode 100644 index 0000000000..66010df806 --- /dev/null +++ b/tests/cpp-tests/Classes/Box2DTestBed/tests/wrecking_ball.cpp @@ -0,0 +1,165 @@ +// MIT License + +// Copyright (c) 2019 Erin Catto + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "../test.h" +//#include "imgui/imgui.h" + +/// This test shows how a distance joint can be used to stabilize a chain of +/// bodies with a heavy payload. Notice that the distance joint just prevents +/// excessive stretching and has no other effect. +/// By disabling the distance joint you can see that the Box2D solver has trouble +/// supporting heavy bodies with light bodies. Try playing around with the +/// densities, time step, and iterations to see how they affect stability. +/// This test also shows how to use contact filtering. Filtering is configured +/// so that the payload does not collide with the chain. +class WreckingBall : public Test +{ +public: + WreckingBall() + { + b2Body* ground = NULL; + { + b2BodyDef bd; + ground = m_world->CreateBody(&bd); + + b2EdgeShape shape; + shape.SetTwoSided(b2Vec2(-40.0f, 0.0f), b2Vec2(40.0f, 0.0f)); + ground->CreateFixture(&shape, 0.0f); + } + + { + b2PolygonShape shape; + shape.SetAsBox(0.5f, 0.125f); + + b2FixtureDef fd; + fd.shape = &shape; + fd.density = 20.0f; + fd.friction = 0.2f; + fd.filter.categoryBits = 0x0001; + fd.filter.maskBits = 0xFFFF & ~0x0002; + + b2RevoluteJointDef jd; + jd.collideConnected = false; + + const int32 N = 10; + const float y = 15.0f; + m_distanceJointDef.localAnchorA.Set(0.0f, y); + + b2Body* prevBody = ground; + for (int32 i = 0; i < N; ++i) + { + b2BodyDef bd; + bd.type = b2_dynamicBody; + bd.position.Set(0.5f + 1.0f * i, y); + if (i == N - 1) + { + bd.position.Set(1.0f * i, y); + bd.angularDamping = 0.4f; + } + + b2Body* body = m_world->CreateBody(&bd); + + if (i == N - 1) + { + b2CircleShape circleShape; + circleShape.m_radius = 1.5f; + b2FixtureDef sfd; + sfd.shape = &circleShape; + sfd.density = 100.0f; + sfd.filter.categoryBits = 0x0002; + body->CreateFixture(&sfd); + } + else + { + body->CreateFixture(&fd); + } + + b2Vec2 anchor(float(i), y); + jd.Initialize(prevBody, body, anchor); + m_world->CreateJoint(&jd); + + prevBody = body; + } + + m_distanceJointDef.localAnchorB.SetZero(); + + float extraLength = 0.01f; + m_distanceJointDef.minLength = 0.0f; + m_distanceJointDef.maxLength = N - 1.0f + extraLength; + m_distanceJointDef.bodyB = prevBody; + } + + { + m_distanceJointDef.bodyA = ground; + m_distanceJoint = m_world->CreateJoint(&m_distanceJointDef); + m_stabilize = true; + } + } + + //void UpdateUI() override + //{ + // ImGui::SetNextWindowPos(ImVec2(10.0f, 100.0f)); + // ImGui::SetNextWindowSize(ImVec2(200.0f, 100.0f)); + // ImGui::Begin("Wrecking Ball Controls", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + + // if (ImGui::Checkbox("Stabilize", &m_stabilize)) + // { + // if (m_stabilize == true && m_distanceJoint == nullptr) + // { + // m_distanceJoint = m_world->CreateJoint(&m_distanceJointDef); + // } + // else if (m_stabilize == false && m_distanceJoint != nullptr) + // { + // m_world->DestroyJoint(m_distanceJoint); + // m_distanceJoint = nullptr; + // } + // } + + // ImGui::End(); + //} + + void Step(Settings* settings) override + { + Test::Step(settings); + + //if (m_distanceJoint) + //{ + // g_debugDraw.DrawString(5, m_textLine, "Distance Joint ON"); + //} + //else + //{ + // g_debugDraw.DrawString(5, m_textLine, "Distance Joint OFF"); + //} + //m_textLine += m_textIncrement; + } + + static Test* Create() + { + return new WreckingBall; + } + + b2DistanceJointDef m_distanceJointDef; + b2Joint* m_distanceJoint; + bool m_stabilize; +}; + +static int testIndex = RegisterTest("Examples", "Wrecking Ball", WreckingBall::Create); diff --git a/tests/cpp-tests/Classes/ChipmunkTestBed/demo/ChipmunkDemo.h_org b/tests/cpp-tests/Classes/ChipmunkTestBed/demo/ChipmunkDemo.h_org new file mode 100644 index 0000000000..7e3d3b33c1 --- /dev/null +++ b/tests/cpp-tests/Classes/ChipmunkTestBed/demo/ChipmunkDemo.h_org @@ -0,0 +1,68 @@ +/* Copyright (c) 2007 Scott Lembcke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "ChipmunkDebugDraw.h" + +typedef struct ChipmunkDemo ChipmunkDemo; + +typedef cpSpace *(*ChipmunkDemoInitFunc)(void); +typedef void (*ChipmunkDemoUpdateFunc)(cpSpace *space, double dt); +typedef void (*ChipmunkDemoDrawFunc)(cpSpace *space); +typedef void (*ChipmunkDemoDestroyFunc)(cpSpace *space); + +struct ChipmunkDemo { + const char *name; + double timestep; + + ChipmunkDemoInitFunc initFunc; + ChipmunkDemoUpdateFunc updateFunc; + ChipmunkDemoDrawFunc drawFunc; + + ChipmunkDemoDestroyFunc destroyFunc; +}; + +static inline cpFloat +frand(void) +{ + return (cpFloat)rand()/(cpFloat)RAND_MAX; +} + +static inline cpVect +frand_unit_circle(){ + cpVect v = cpv(frand()*2.0f - 1.0f, frand()*2.0f - 1.0f); + return (cpvlengthsq(v) < 1.0f ? v : frand_unit_circle()); +} + +extern int ChipmunkDemoTicks; +extern double ChipmunkDemoTime; +extern cpVect ChipmunkDemoKeyboard; +extern cpVect ChipmunkDemoMouse; +extern cpBool ChipmunkDemoRightClick; +extern cpBool ChipmunkDemoRightDown; + +extern char const *ChipmunkDemoMessageString; +void ChipmunkDemoPrintString(char const *fmt, ...); + +extern cpShapeFilter GRAB_FILTER; +extern cpShapeFilter NOT_GRABBABLE_FILTER; + +void ChipmunkDemoDefaultDrawImpl(cpSpace *space); +void ChipmunkDemoFreeSpaceChildren(cpSpace *space); diff --git a/tests/cpp-tests/Classes/controller.cpp b/tests/cpp-tests/Classes/controller.cpp index 2665f05f01..7f13888d6d 100644 --- a/tests/cpp-tests/Classes/controller.cpp +++ b/tests/cpp-tests/Classes/controller.cpp @@ -56,7 +56,9 @@ public: addTest("Box2D - Basic", []() { return new (std::nothrow) Box2DTests(); }); -// addTest("Box2D - TestBed", []() { return new (std::nothrow) Box2dTestBedSuite(); }); +#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) + addTest("Box2D - TestBed", []() { return new (std::nothrow) Box2DTestBedTests(); }); +#endif addTest("Chipmunk2D - Basic", []() { return new ChipmunkTests(); }); #if defined(CC_PLATFORM_PC) addTest("Chipmunk2D - TestBed", []() { return new ChipmunkTestBedTests(); }); diff --git a/tests/cpp-tests/Classes/tests.h b/tests/cpp-tests/Classes/tests.h index 64c38fbeb8..bb05d67b09 100644 --- a/tests/cpp-tests/Classes/tests.h +++ b/tests/cpp-tests/Classes/tests.h @@ -27,7 +27,7 @@ #include "Box2DTest/Box2dTest.h" -//#include "Box2DTestBed/Box2dView.h" +#include "Box2DTestBed/Box2DTestBed.h" #include "ChipmunkTest/ChipmunkTest.h"