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

```txt:動作環境
C++ Builder XE4
```

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

### やろうとしていること

- 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](https://qiita-image-store.s3.amazonaws.com/0/32870/5604505b-77cb-564f-daf3-07f66a98008d.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](https://qiita-image-store.s3.amazonaws.com/0/32870/e604c624-eb4c-74d4-9573-1da34b28cde6.png)

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

```cpp
	// 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](https://qiita-image-store.s3.amazonaws.com/0/32870/feda59f4-da88-c2eb-3179-2984714f5ca2.png)

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

### 制約とバグ

**<font color=red>ただし、上記の処理により、2度未満の範囲指定では図示できない</font>**

**<font color=red>２つの角度範囲が2度未満でoverlapする時にその部分が白色になる</font>**