需求
物理原理课需要制作关于单摆在在不同摆角下运动状态的课程报告,所以就想着做出一个有图形界面的单摆演示程序。由于图形界面只有cocos2d-学的还算可以,所以就用这个来做(实际上用MFC或者用WPF做效果会更好一点?……)
理论基础
周期求解
单摆在偏角不太大的情况(高中课本认为小于5°均可)下,单摆的运动可以近似地视为简谐运动。
周期公式
但是在摆角较大时该公式误差很大。所以需要引入其他的公式。
在刘凤祥《单摆运动周期的近似解》中得出一个近似周期公式
程序使用该公式求得单摆的近似周期。
瞬时角速度
推导出瞬时角速度的微分方程,然后用龙格库塔法求解,在程序中求得瞬时角速度。
直接上图……
有了瞬时角速度之后就可以用update函数移动摆球并表现在图形界面中。
程序代码
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
#include "ui/CocosGUI.h"
#include "Consts.h"
class HelloWorld : public cocos2d::Layer
{
public:
enum kState
{
kState_Stop,kState_Running,kState_Paused
};
HelloWorld();
~HelloWorld();
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(HelloWorld);
public:
void sliderEvent(cocos2d::Ref* sender,cocos2d::ui::Slider::EventType type);
void textFieldEvent(cocos2d::Ref* sender,cocos2d::ui::TextField::EventType type);
void StartTouchEvent(cocos2d::Ref* sender,cocos2d::ui::Widget::TouchEventType type);
void PauseTouchEvent(cocos2d::Ref* sender,cocos2d::ui::Widget::TouchEventType type);
public:
virtual void update(float delta);
protected:
cocos2d::CustomCommand _customCommand;
private:
double _startAngel,_l,_T;
int _halfT;
double _currentAngel,_currentW,_currentTime;
kState _state;
cocos2d::Vec2 _ballPos,_vertexPos,_graphVertexPos;
private:
cocos2d::ui::Text * m_TText;
cocos2d::ui::Text * m_WText;
cocos2d::ui::Text * m_AngelText;
cocos2d::ui::Text * m_CurrentAngelText;
cocos2d::ui::Slider * m_AngelSlider;
cocos2d::ui::TextField * m_LTextField;
cocos2d::ui::Button * m_PauseButton;
cocos2d::Sprite * m_ball;
cocos2d::DrawNode * m_drawNode;
cocos2d::DrawNode * m_graphNode;
private:
double getW();
double getT();
};
#endif // __HELLOWORLD_SCENE_H__
#include "HelloWorldScene.h"
#include "cocostudio/CocoStudio.h"
#include "ui/CocosGUI.h"
// draw
#include "renderer/CCRenderer.h"
#include "renderer/CCCustomCommand.h"
USING_NS_CC;
using namespace cocostudio::timeline;
using namespace cocos2d::ui;
HelloWorld::HelloWorld()
{
m_TText = nullptr;
m_WText = nullptr;
m_AngelText = nullptr;
m_CurrentAngelText = nullptr;
m_AngelSlider = nullptr;
m_LTextField = nullptr;
_vertexPos = Vec2(300,550);
_graphVertexPos = Vec2(300,120);
}
HelloWorld::~HelloWorld()
{
}
Scene* HelloWorld::createScene()
{
auto scene = Scene::create();
auto layer = HelloWorld::create();
scene->addChild(layer);
return scene;
}
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
// init variables
_state = kState_Stop;
// DrawNode
m_drawNode = DrawNode::create();
this->addChild(m_drawNode,10);
m_graphNode = DrawNode::create();
this->addChild(m_graphNode,9);
m_graphNode->drawLine(Vec2(0,120),Vec2(600,Color4F::RED);
m_graphNode->drawLine(Vec2(300,0),Vec2(300,240),Color4F::RED);
m_graphNode->drawPoint(_vertexPos,20,Color4F::BLUE);
// init UI
auto rootNode = CSLoader::createNode("MainScene.csb");
addChild(rootNode);
m_TText = static_cast<ui::Text*>(rootNode->getChildByName("Panel_RT")->getChildByName("Text_T_Data"));
m_WText = static_cast<ui::Text*>(rootNode->getChildByName("Panel_RT")->getChildByName("Text_W_Data"));
m_CurrentAngelText = static_cast<ui::Text*>(rootNode->getChildByName("Panel_RT")->getChildByName("Text_CurrentAngel_Data"));
m_AngelText = static_cast<ui::Text*>(rootNode->getChildByName("Panel_RB")->getChildByName("Text_Angel_Data"));
m_AngelSlider = static_cast<ui::Slider*>(rootNode->getChildByName("Panel_RB")->getChildByName("Slider_Angel"));
m_AngelSlider->setMaxPercent(90);
m_AngelSlider->addEventListener(CC_CALLBACK_2(HelloWorld::sliderEvent,this));
m_LTextField = static_cast<ui::TextField*>(rootNode->getChildByName("Panel_RB")->getChildByName("TextField_l"));
m_LTextField->addEventListener(CC_CALLBACK_2(HelloWorld::textFieldEvent,this));
auto startBtn = static_cast<ui::Button*>(rootNode->getChildByName("Panel_RB")->getChildByName("Button_Start"));
startBtn->addTouchEventListener(CC_CALLBACK_2(HelloWorld::StartTouchEvent,this));
m_PauseButton = static_cast<ui::Button*>(rootNode->getChildByName("Panel_RB")->getChildByName("Button_Pause"));
m_PauseButton->addTouchEventListener(CC_CALLBACK_2(HelloWorld::PauseTouchEvent,this));
// init Sprite
m_ball = Sprite::create("ball.png");
m_ball->setAnchorPoint(Vec2(0.5,0.5));
m_ball->setPosition(_vertexPos);
this->addChild(m_ball);
return true;
}
void HelloWorld::sliderEvent(Ref *pSender,cocos2d::ui::Slider::EventType type)
{
if(type == Slider::EventType::ON_PERCENTAGE_CHANGED)
{
Slider* slider = dynamic_cast<Slider*>(pSender);
int percent = slider->getPercent();
m_AngelText->setString(StringUtils::format("%d",percent));
_startAngel = (double)percent / 180 * PI;
_currentAngel = _startAngel;
_ballPos = Vec2(_vertexPos.x + _l * 100 * sin(_currentAngel),_vertexPos.y - _l * 100 * cos(_currentAngel));
m_ball->setPosition(_ballPos);
}
}
void HelloWorld::textFieldEvent(Ref *pSender,cocos2d::ui::TextField::EventType type)
{
switch (type)
{
case TextField::EventType::ATTACH_WITH_IME:
{
TextField* textField = dynamic_cast<TextField*>(pSender);
/* Size screenSize = Director::getInstance()->getWinSize(); textField->runAction(MoveTo::create(0.225f,Vec2(screenSize.width / 2.0f,screenSize.height / 2.0f + textField->getContentSize().height / 2.0f))); */
}
break;
case TextField::EventType::DETACH_WITH_IME:
{
TextField* textField = dynamic_cast<TextField*>(pSender);
/* Size screenSize = Director::getInstance()->getWinSize(); textField->runAction(MoveTo::create(0.175f,screenSize.height / 2.0f))); */
}
break;
case TextField::EventType::INSERT_TEXT:
{
TextField * textField = dynamic_cast<TextField*>(pSender);
std::string str = textField->getString();
int num = atoi(str.c_str());
log("%d",num);
_l = num;
}
break;
case TextField::EventType::DELETE_BACKWARD:
break;
default:
break;
}
}
void HelloWorld::StartTouchEvent(Ref *pSender,Widget::TouchEventType type)
{
switch (type)
{
case Widget::TouchEventType::BEGAN:
break;
case Widget::TouchEventType::MOVED:
break;
case Widget::TouchEventType::ENDED:
{
_currentAngel = _startAngel;
_currentW = 0;
_currentTime = 0;
_state = kState_Running;
m_PauseButton->setTitleText("PAUSE");
// set T
_T = getT();
m_TText->setText(StringUtils::format("%lf",_T));
this->scheduleUpdate();
}
break;
case Widget::TouchEventType::CANCELED:
break;
default:
break;
}
}
void HelloWorld::PauseTouchEvent(Ref *pSender,Widget::TouchEventType type)
{
switch (type)
{
case Widget::TouchEventType::BEGAN:
break;
case Widget::TouchEventType::MOVED:
break;
case Widget::TouchEventType::ENDED:
{
if(_state == kState_Running)
{
_state = kState_Paused;
this->unscheduleUpdate();
auto button = dynamic_cast<ui::Button*>(pSender);
button->setTitleText("RESUME");
}
else if(_state == kState_Paused)
{
_state = kState_Running;
this->scheduleUpdate();
auto button = dynamic_cast<ui::Button*>(pSender);
button->setTitleText("PAUSE");
}
}
break;
case Widget::TouchEventType::CANCELED:
break;
default:
break;
}
}
void HelloWorld::update(float delta)
{
// double W = getW();
// update data
double k1,k2,k3,k4,l1,l2,l3,l4;
{
k1 = _currentW;
l1 = -(G / _l) * sin(_currentAngel);
k2 = _currentW + delta * l1 / 2.0;
l2 = -(G / _l) * sin(_currentAngel + delta * k1 / 2.0);
k3 = _currentW + delta * l2 / 2.0;
l3 = -(G / _l) * sin(_currentAngel + delta * k2 / 2.0);
k4 = _currentW + delta * l3;
l4 = -(G / _l) *sin(_currentAngel * delta * k3);
_currentTime += (double)delta;
_currentAngel += delta * (k1 + 2 * k2 + 2 * k3 + k4) / 6.0;
log("%lf",_currentAngel);
_currentW += delta * (l1 + 2 * l2 + 2 * l3 + l4) / 6.0;
}
m_WText->setText(StringUtils::format("%.4lf",_currentW));
m_CurrentAngelText->setText(StringUtils::format("%.1lf",_currentAngel / PI * 180));
// draw
_ballPos = Vec2(_vertexPos.x + _l * 100 * sin(_currentAngel),_vertexPos.y - _l * 100 * cos(_currentAngel));
m_ball->setPosition(_ballPos);
m_drawNode->clear();
m_drawNode->drawLine(_vertexPos,_ballPos,Color4F::BLUE);
// graph
m_graphNode->drawPoint(Vec2(_graphVertexPos.x + _currentAngel * 200,_graphVertexPos.y + _currentW * 50),2,Color4F::RED);
}
double HelloWorld::getW()
{
double W;
W = sqrt(2 / _l*_l) * sqrt(9.8*_l*(cos(_currentAngel) - cos(_startAngel)));
if (_halfT % 2 == 0)
W = -W;
return W;
}
double HelloWorld::getT()
{
double T;
// double T1,T2,T3;
// T3 = sqrt(_l / G);
// T1 = 2 * PI * sqrt(_l / G);
// T2 = 1 - 0.062*_startAngel*_startAngel;
T = (2 * PI * sqrt(_l / G)) /
(1 - 0.062*_startAngel*_startAngel);
// T = T1 / T2;
return T;
}