X-Y座標系と円座標系

最近Kindleで購入した書籍『JavaScriptゲームプログラミング 知っておきたい数学と物理の基本』から、2-1-1 X-Y座標系と円座標系をやってみました。

JavaScriptゲームプログラミング 知っておきたい数学と物理の基本 (Future Coders(NextPublishing))

JavaScriptゲームプログラミング 知っておきたい数学と物理の基本 (Future Coders(NextPublishing))

極座標系から三角関数を用いて直交座標系を簡単に求められますよという内容になります。三角形の内角θによってXをcosθ、Yをsinθで求めることができます。その関係性をHTML上のCanvasに表現しています。

実行例

実行している動画になります。スライダーを動かすと角度に応じて、XとYが変化していく様子が確認できます。

youtu.be

TypeScriptでのコード

そのままJavaScriptコードを転記すると丸パクリになってしまうので、TypeScriptで実装してみました。

実装及びデバッグ環境は以下の通りです。

  • Windows10
  • VisualStudio2017 + TypeScriptテンプレートプロジェクト
  • IIS Express + Chorome
// TypeScript側のコード
class Trig
{
    private ctx: CanvasRenderingContext2D = null;

    init(): void
    {
        var canvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById("graph");
        this.ctx = canvas.getContext("2d");
        this.ctx.font = "24px sans-serif";
        this.paint(0);
    }

    update(): void
    {
        var degree: string = (<HTMLInputElement>document.getElementById("theta")).value;
        document.getElementById("degree").textContent = degree;
        this.paint(+degree);
    }

    drawLine(x0: number, y0: number, x1: number, y1: number, color: string): void
    {
        this.ctx.strokeStyle = color;
        this.ctx.beginPath();
        this.ctx.moveTo(x0, y0);
        this.ctx.lineTo(x1, y1);
        this.ctx.closePath();
        this.ctx.stroke();
    }

    paint(degree: number): void
    {
        this.ctx.fillStyle = "white";
        this.ctx.fillRect(0, 0, 600, 600);
        this.ctx.save();
        this.ctx.translate(300, 300);

        // 座標軸
        this.ctx.strokeStyle
        this.drawLine(0, -300, 0, 300, "black");
        this.drawLine(-300, 0, 300, 0, "black");

        var s0: number = Math.sin(degree * Math.PI / 180);
        var c0: number = Math.cos(degree * Math.PI / 180);
        var c: number = c0 * 200;
        var s: number = s0 * -200;

        this.drawLine(0, 0, c, s, "red");
        this.ctx.arc(0, 0, 200, 0, Math.PI * 2);
        this.ctx.stroke();

        // ラベル
        this.drawLine(c, s, c, 0, "blue");
        this.ctx.strokeText("con:" + c0.toFixed(3), c - 10, 20);
        this.drawLine(c, s, 0, s, "green");
        this.ctx.strokeText("sin:" + s0.toFixed(3), -40, s - 10);

        this.ctx.restore();
    }
}

var trig = new Trig();
window.onload = () =>
{
    trig.init();

    document.getElementById("theta").onchange = function ()
    {
        trig.update();
    }
};
// HTML側のコード
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Trig Function Graph</title>
    <script src="app.js"></script>
</head>

<body>
    <input id="theta" type="range" min="0" max="360" value="0"/>中心角=<span id="degree">0</span><br/>
    <canvas id="graph" width="600" height="600"></canvas>
</body>

</html>

C++/Cocos2d-xでの実装

次にC++/Cocos2d-xで同じ内容を実装してみました。

動かしたときの表示は以下のようになります。少し見ずらい場合リンク先で確認ください。

youtu.be

以下コードになります。同じ実装ですが少し長めになります。

// HelloWorldScene.h
#pragma once

#include "cocos2d.h"
#include "MessageDialog.h"
#include "HpBar.h"

class HelloWorld : public cocos2d::Layer
{
    // 各Nodeへアクセスするためのタグ
    static const int TAG_HYPO;
    static const int TAG_SIN_LINE;
    static const int TAG_COS_LINE;
    static const int TAG_DEGREE_TEXT;
    static const int TAG_SIN_LABEL;
    static const int TAG_COS_LABEL;
    
    int degree = 0; // 角度

public:
    // いつもの
    static cocos2d::Scene* createScene();
    virtual bool init();
    void menuCloseCallback(cocos2d::Ref* pSender);
    CREATE_FUNC(HelloWorld);

    // 描画の更新
    void paint(double xCenter, double yCenter);
};


class NodeUtil
{
public:
    // タグに対応するノードがあれば削除
    static void RemoveNodeBag(cocos2d::Node* parent, int tag)
    {
        cocos2d::Node* target = parent->getChildByTag(tag);
        if (target == nullptr)
        {
            return;
        }

        target->removeFromParentAndCleanup(true);
    }
};
// HelloWorldScene.cpp
//#pragma execution_character_set("utf-8")

#include <stdlib.h>
#include <vector>
#include "HelloWorldScene.h"
#include <cmath>

using namespace cocos2d;
using namespace cocos2d::ui;

const int HelloWorld::TAG_HYPO = 0;
const int HelloWorld::TAG_SIN_LINE = 1;
const int HelloWorld::TAG_COS_LINE = 2;
const int HelloWorld::TAG_DEGREE_TEXT = 3;
const int HelloWorld::TAG_SIN_LABEL = 4;
const int HelloWorld::TAG_COS_LABEL = 5;

Scene* HelloWorld::createScene()
{
    // 'scene' is an autorelease object
    auto scene = Scene::create();

    // 'layer' is an autorelease object
    auto layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if (!Layer::init())
    {
        return false;
    }

    Size visibleSize = Director::getInstance()->getVisibleSize();

    double xCenter = visibleSize.width / 2;
    double yCenter = visibleSize.height / 2;

    Label* degreeLabel = Label::create();
    degreeLabel->setPosition(Vec2(xCenter, yCenter + 170));
    degreeLabel->setTag(TAG_DEGREE_TEXT);
    degreeLabel->setString("中心角 = " + std::to_string(this->degree) + "度");
    this->addChild(degreeLabel);

    // X軸
    DrawNode* xLine = DrawNode::create();
    xLine->drawSegment(Vec2(xCenter - 150, yCenter), Vec2(xCenter + 150, yCenter), 0.25, Color4F::WHITE);
    this->addChild(xLine);

    // Y軸
    DrawNode* yLine = DrawNode::create();
    yLine->drawSegment(Vec2(xCenter, yCenter - 150), Vec2(xCenter, yCenter + 150), 0.25, Color4F::WHITE);
    this->addChild(yLine);

    // 円
    DrawNode* circle = DrawNode::create();
    circle->drawCircle(Vec2(xCenter, yCenter), 125, 0, 360, false, 1, 1, Color4F::RED);
    this->addChild(circle);

    Label* sinLabel = Label::create();
    sinLabel->setColor(Color3B::GREEN);
    sinLabel->setTag(TAG_SIN_LABEL);
    sinLabel->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
    this->addChild(sinLabel);

    Label* cosLabel = Label::create();
    cosLabel->setColor(Color3B::BLUE);
    cosLabel->setTag(TAG_COS_LABEL);
    cosLabel->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
    this->addChild(cosLabel);

    this->paint(xCenter, yCenter);

    // タッチしたときの動作
    // 画面の右半分をタップで角度を+1度、左半分をタップは-1度
    EventListenerTouchOneByOne* evListener = EventListenerTouchOneByOne::create();
    evListener->onTouchBegan = [this, xCenter, yCenter](Touch* touch, Event* event)
    {
        if (touch->getLocation().x > Director::getInstance()->getVisibleSize().width / 2)
        {
            this->degree++;
        }
        else
        {
            this->degree--;
        }

        this->paint(xCenter, yCenter);

        return false;
    };

    _eventDispatcher->addEventListenerWithSceneGraphPriority(evListener, this);

    return true;
}

// 表示している各要素の更新
void HelloWorld::paint(double xCenter, double yCenter)
{
    double s0 = sin(CC_DEGREES_TO_RADIANS(this->degree));
    double c0 = cos(CC_DEGREES_TO_RADIANS(this->degree));
    double s = s0 * 125;
    double c = c0 * 125;

    // 新しい斜辺を配置
    NodeUtil::RemoveNodeBag(this, TAG_HYPO);
    DrawNode* hypo = DrawNode::create();
    hypo->drawSegment(Vec2(xCenter, yCenter), Vec2(xCenter + c, yCenter + s), 0.25, Color4F::RED);
    hypo->setTag(TAG_HYPO);
    this->addChild(hypo);

    // Sinを示す緑色の線
    NodeUtil::RemoveNodeBag(this, TAG_SIN_LINE);
    DrawNode* sinLine = DrawNode::create();
    sinLine->drawSegment(Vec2(xCenter, yCenter + s),
        Vec2(xCenter + c, yCenter + s), 0.25, Color4F::GREEN);
    sinLine->setTag(TAG_SIN_LINE);
    this->addChild(sinLine);

    // Cosを示す青色の線
    NodeUtil::RemoveNodeBag(this, TAG_COS_LINE);
    DrawNode* cosLine = DrawNode::create();
    sinLine->drawSegment(Vec2(xCenter + c, yCenter),
        Vec2(xCenter + c, yCenter + s), 0.25, Color4F::BLUE);
    cosLine->setTag(TAG_COS_LINE);
    this->addChild(cosLine);

    Label* degreeLabel = (Label*)this->getChildByTag(TAG_DEGREE_TEXT);
    degreeLabel->setString("中心角 = " + std::to_string(this->degree) + "度");

    int dec, sign;
    Label* sinLabel = (Label*)this->getChildByTag(TAG_SIN_LABEL);
    sinLabel->setPosition(Vec2(xCenter, yCenter + s + 5));
    sinLabel->setString("sin:" + std::to_string(s0));

    Label* cosLabel = (Label*)this->getChildByTag(TAG_COS_LABEL);
    cosLabel->setPosition(Vec2(xCenter + c + 5, yCenter));
    cosLabel->setString("sin:" + std::to_string(c0));
}

これ、自分がポンコツってだけだと思いますが、C++は3倍くらいコード書かないといけない羽目になりました。