LoginSignup
0
1

More than 5 years have passed since last update.

C++ builder / TCanvas > 2つの角度範囲の重なりを色で表示する実装 > v0.1, v0.2

Last updated at Posted at 2017-03-29
動作環境
C++ Builder XE4

関連 C++ builder / TCanvas > 4色合成して均等に分割した円のセクターとして描画する実装
関連 C > 幾何学 > ベクトルがある角度範囲に入っているか判定 > 反時計回りでの判断

やろうとしていること

  • 2つの角度(deg)を定義する
    • 例: A=[30, 150], B=[60, 170]
  • 円の中でAとBの重なりを色表示する
    • Aだけの角度はAの色
    • Bだけの角度はBの色
    • A,Bの角度は色合成

色はMicrosoft colorを使う
http://colorhunt.co/blog/google-microsoft-brand-colors/

v0.1 > バグ有り

code

とりあえず動作した、というコード。

Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>

//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE で管理されるコンポーネント
    TButton *Button1;
    TImage *Image1;
    void __fastcall Button1Click(TObject *Sender);
private:    // ユーザー宣言
    //
    void __fastcall drawRadialLine(double angle_deg);
    void __fastcall fillSector(double angle_deg, double shift_deg);
    void __fastcall drawOnImage(void);
    //
    TColor __fastcall getRgbColor(double angle_deg);
    void __fastcall assignColorList(double angle_deg, bool *destPtr);
    bool __fastcall isAngleInside(double target_deg, double start_deg, double end_deg);
public:     // ユーザー宣言
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
static const int kNumCompositeColor = 4; // 合成する色の数

// { 角度の定義関連
typedef struct tag_angle {
    double start_deg;
    double end_deg;
} ANGLE_t;
//
static const int kNumAngleRange = 2; // 角度の範囲の個数 {A, B}を例として
static ANGLE_t s_angleRanges[kNumAngleRange] = {
    { 30.0, 150.0 },
    { 60.0, 170.0 },
};
// } 角度の定義関連

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    drawOnImage();
}
//---------------------------------------------------------------------------
// { to calculate angles between 2 vectors
static float get_angle_clockwise_atan2(float Avec[2], float Bvec[2])
{
    float angle_rad = atan2(Avec[1], Avec[0]) - atan2(Bvec[1], Bvec[0]);
    float angle_deg = angle_rad * 180.0 / acos(-1.0);
    if (angle_deg >= 0.0) {
        return angle_deg;
    } else {
        return angle_deg + 360.0;
    }
}

static float get_angle_clockwise_wrapper(float x1, float y1, float x2, float y2)
{
    float Avec[2];
    float Bvec[2];

    Avec[0] = x1;
    Avec[1] = y1;
    Bvec[0] = x2;
    Bvec[1] = y2;
    return get_angle_clockwise_atan2(Avec, Bvec);
}

static bool isInside_counterClockWise(float avec[2], float minvec[2], float maxvec[2])
{
    float aAndMin = get_angle_clockwise_atan2(minvec, avec);
    float minAndMax = get_angle_clockwise_atan2(minvec, maxvec);

    return (aAndMin <= minAndMax);
}
// } to calculate angles between 2 vectors
//---------------------------------------------------------------------------

void __fastcall TForm1::drawRadialLine(double angle_deg)
{
    TRect R = Image1->ClientRect;

    double radius = (R.right - R.left) / 2;
    int center_x = (R.left + R.right) / 2;
    int center_y = (R.top + R.bottom) / 2;

    // for Pen->Width==1, fillSector() filled adjacent sectors by mistake
    Image1->Canvas->Pen->Width = 2;

    Image1->Canvas->MoveTo(center_x, center_y);

    int to_x = radius * Cos(angle_deg * M_PI / 180.0);
    int to_y = radius * Sin(angle_deg * M_PI / 180.0);
    to_x += center_x;
    to_y += center_y;
    Image1->Canvas->LineTo(to_x, to_y);
}

// Microsoft color
// @ http://colorhunt.co/blog/google-microsoft-brand-colors/
static const int baseColors[][3] = {
    { 0x00, 0xA1, 0xF1 }, // R, G, B
    { 0x7C, 0xBB, 0x00 }, // R, G, B
    { 0xFF, 0xBB, 0x00 }, // R, G, B
    { 0xF6, 0x53, 0x14 }, // R, G, B
};

bool __fastcall TForm1::isAngleInside(double target_deg, double start_deg, double end_deg)
{
    // check for clockwise argument

    float avec[2]; // a vector
    float startvec[2]; 
    float endvec[2];

    avec[0] = 1.0 * cos(target_deg * M_PI / 180.0);
    avec[1] = 1.0 * sin(target_deg * M_PI / 180.0);

    startvec[0] = 1.0 * cos(start_deg * M_PI / 180.0);
    startvec[1] = 1.0 * sin(start_deg * M_PI / 180.0);

    endvec[0] = 1.0 * cos(end_deg * M_PI / 180.0);
    endvec[1] = 1.0 * sin(end_deg * M_PI / 180.0);

    // for clockwise argument
    return isInside_counterClockWise(avec, /*minvec=*/endvec, /*maxvec=*/startvec);
}

void __fastcall TForm1::assignColorList(double angle_deg, bool *destPtr)
{
    if (destPtr == NULL) {
        return; // error
    }

    for(int idx = 0; idx < kNumCompositeColor; idx++) {
        ANGLE_t *angPtr = &s_angleRanges[idx];
        destPtr[idx] = isAngleInside(angle_deg, angPtr->start_deg, angPtr->end_deg);
    }
}

TColor __fastcall TForm1::getRgbColor(double angle_deg)
{
    int clrs[kNumCompositeColor] = { 0 };
    int numColor = 0;
    bool colorList[kNumCompositeColor] = { false }; // 4色のON/OFFのフラグ

    assignColorList(angle_deg, &colorList[0]);

    for(int midx = 0; midx < kNumCompositeColor; midx++) { // midx: member index
        if (colorList[midx]) {
            for (int rgbidx=0; rgbidx < 3; rgbidx++) { // R,G,B
                clrs[rgbidx] += baseColors[midx][rgbidx];
            }
            numColor++;
        }
    }

    if (numColor > 0) {
        for(int midx = 0; midx < kNumCompositeColor; midx++) {
            clrs[midx] = clrs[midx] / numColor;
        }
    }

    int R = clrs[0];
    int G = clrs[1];
    int B = clrs[2];
    return RGB(R, G, B);
}

void __fastcall TForm1::fillSector(double angle_deg, double shift_deg)
{
    TRect R = Image1->ClientRect;
    double radius = (R.right - R.left) / 2;

    radius = 0.7 * radius; // to get inside the sector (0.7: arbitrary)
    double wrk_deg = angle_deg + shift_deg; // to get inside the sector

    int center_x = (R.left + R.right) / 2;
    int center_y = (R.top + R.bottom) / 2;

    int pt_x = radius * Cos(wrk_deg * M_PI / 180.0);
    int pt_y = radius * Sin(wrk_deg * M_PI / 180.0);
    pt_x += center_x;
    pt_y += center_y;

    Image1->Canvas->Brush->Color = getRgbColor(wrk_deg);
    Image1->Canvas->FloodFill(pt_x, pt_y, clBlack, fsBorder);
}

void __fastcall TForm1::drawOnImage(void)
{
    // 1. draw circle
    TRect R = Image1->ClientRect;
    //                      X1,     Y1     X2       Y2
    Image1->Canvas->Ellipse(R.left, R.top, R.right, R.bottom);

    // 2. draw radial line
    for(int idx = 0; idx < kNumAngleRange; idx++) {
        drawRadialLine(s_angleRanges[idx].start_deg);
        drawRadialLine(s_angleRanges[idx].end_deg);
    }

    // 3. fill the sectors
    for(int idx = 0; idx < kNumAngleRange; idx++) {
        // 0.5 for [shift_deg] did not work
        fillSector(s_angleRanges[idx].start_deg, /* shift_deg=*/1.0);
        fillSector(s_angleRanges[idx].end_deg, /* shift_deg=*/1.0);
    }
}

結果

qiita.png

  • 水色: Aだけ
  • 薄い黄緑: Bだけ
  • それらの中間: A,B
  • 黒: AでもBでもない

trap

指定角度が角度範囲に含まれるかどうかのコードは以下で実装したものを使用。
http://qiita.com/7of9/items/fdb9e490251c69b5e2a6

はまったのは「時計回り」「反時計回り」の違い。

https://academo.org/demos/3d-vector-plotter/
で可視化して気づいた。

9ヵ月後にも定義が分かるようにしておかないと、はまる。

v0.2 > バグ対応

角度の範囲指定で、[90.0, 180.0][180.0, 270.0]にすると正常に塗りつぶしがされない。

qiita.png

修正としてはdrawOnImage()における以下における以下のshift_degの値を1.0から2.0にした。

    // 3. fill the sectors
    for(int idx = 0; idx < kNumAngleRange; idx++) {
        // [shift_deg] was tweaked by checking the result
        fillSector(s_angleRanges[idx].start_deg, /* shift_deg=*/1.0);
        fillSector(s_angleRanges[idx].end_deg, /* shift_deg=*/1.0);
    }

qiita.png

塗りつぶしをする時の位置が、動径方向の線に重なって塗りつぶしできていなかったようだ。

制約とバグ

ただし、上記の処理により、2度未満の範囲指定では図示できない

2つの角度範囲が2度未満でoverlapする時にその部分が白色になる

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1