動作環境
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);
}
}
結果
- 水色: 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]
にすると正常に塗りつぶしがされない。
修正としては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);
}
塗りつぶしをする時の位置が、動径方向の線に重なって塗りつぶしできていなかったようだ。
制約とバグ
ただし、上記の処理により、2度未満の範囲指定では図示できない
2つの角度範囲が2度未満でoverlapする時にその部分が白色になる