チュートリアル:Point、Line、Rectangle クラスラス
Point、Line、Rectangleクラスを使用して幾何計算を簡素化します。
レベル: 初級
プラットフォーム: Windows, macOS, Linux, iOS, Android
クラス: Point, Line, Rectangle, Path, Random, Range
はじめに
このチュートリアルのデモプロジェクトをこちらからダウンロードしてください:PIP | ZIP。プロジェクトを解凍し、最初のヘッダファイルを Projucer で開いてください。
この手順でサポートが必要な場合は、チュートリアル:Projucer Part 1: Projucer を始めようを参照してください。
デモプロジェクト
デモプロジェクトはシンプルな描画操作を実行します。デフォルトの状態では、左上隅に小さなオレンジ色の正方形がある灰色の背景を描画します:

線と矩形を描画するさまざまな方法と、Point、Line、Rectangleクラスが JUCE での描画操作(およびコンポーネントの位置)についてどのように考えを簡素化できるかを見ていきます。
Rectangle の基本
グラフィックス描画とコンポーネントレイアウトコードの大部分は矩形を扱う必要があります。このチュートリアルは、塗りつぶされた正方形のシンプルな描画から始まります。次に、Point、Line、Rectangleクラスが描画操作にどのように役立つかを探ります。これらの技術は子コンポーネントの配置(Component::resized()関数内)にも適用できます。paint()関数のスターターコードは以下のとおりです:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
g.setColour (juce::Colours::orange);
g.fillRect (10, 10, 40, 40);
}
ここでは矩形の境界をGraphics::fillRect()関数に別々の整数として直接指定しています:
g.fillRect (10, // x
10, // y
40, // width
40); // height
これは簡単ですが、境界をRectangleオブジェクトとして指定すると、矩形の操作がはるかに簡単になります。
Rectangle と Point クラスの使用
これらの別々の座標、幅、高さの値をRectangleオブジェクトで置き換えるのは簡単です。以下のようになります[1]:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
juce::Rectangle<int> area (10, 10, 40, 40); // [1]
g.setColour (juce::Colours::orange);
g.fillRect (area);
}
これは正方形を描画する非常に似た方法ですが、元のサイズを維持しながらコンポーネントの境界内で正方形を簡単に移動できるようになりました。上記で見られるように、Rectangleクラスはテンプレートクラスです。この例ではintテンプレートパラメータを使用しています。JUCE の描画コードでは、一般的にRectangle<int>またはRectangle<float>オブジェクトを使用します。PointとLineクラスもテンプレートクラスであり、同様に一般的にintまたはfloatテンプレートパラメータを使用します。
矩形を作成する他の方法もあります。例えば、幅と高さを指定する代わりに、矩形の角として使用したい 2 つの点がある場合があります。Pointクラスと異なるRectangleコンストラクタを使用できます:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
g.setColour (juce::Colours::orange);
juce::Rectangle<int> area (juce::Point<int> (10, 10),
juce::Point<int> (50, 50));
g.fillRect (area);
}
この技術の素晴らしいところは、任意の 2 つの点を指定できることです。これらの点は矩形の左上と右下の角を表す必要はありません。したがって、同等のものは:
juce::Rectangle<int> area (juce::Point<int> (10, 50),
juce::Point<int> (50, 10));
Point と Path クラスの使用
実際に、Pathオブジェクトを使用すると、各角を定義する 4 つの点で矩形を指定できます:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
g.setColour (juce::Colours::orange);
juce::Path path;
path.startNewSubPath (juce::Point<float> (10, 10));
path.lineTo (juce::Point<float> (50, 10));
path.lineTo (juce::Point<float> (50, 50));
path.lineTo (juce::Point<float> (10, 50));
path.closeSubPath();
g.fillPath (path);
}
この場合、Pathクラスが使用を要求するため、Point<float>クラスを使用する必要があります。ただし、Point<int>::toFloat()と Point<float>::toInt()関数を使用して 2 つのタイプの点を変換できます。
演習:パス内の点を変更して他の四角形の形状を作成してみてください。
パスに直接矩形を追加することもできます:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
g.setColour (juce::Colours::orange);
juce::Path path;
juce::Rectangle<float> area (10, 10, 40, 40);
path.addRectangle (area);
g.fillPath (path);
}
この場合、Rectangle<int>オブジェクトを使用できます。Pathクラスが浮動小数点バージョンに変換してくれるからです。
矩形内の点のテスト
Rectangleオブジェクトのもう 1 つの便利な機能は、指定された点が含まれているかどうかをテストできることです。これをテストするために、マウスをクリックするたびにコンポーネントを再描画するようにします。これを行うには、以下の関数を追加します:
void mouseDown (const juce::MouseEvent&) override
{
repaint();
}
次に、ランダムに矩形と点を生成するpaint()関数を書きましょう。そして、矩形を描画し、点の周りに小さな矩形を配置します。この小さな矩形は、ランダムに生成された点が大きな矩形内にあるかどうかに応じて異なる色で描画されます。paint()関数は以前と同様に開始しますが、いくつかのランダムな値を生成するため、システムRandomオブジェクトへの参照もキャッシュしましょう。
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
g.setColour (juce::Colours::orange);
auto& random = juce::Random::getSystemRandom();
次にランダムな矩形を作成します。以下のコードを追加します:
juce::Range<int> rectRange (20, getWidth() / 2);
juce::Rectangle<int> rectArea (random.nextInt (rectRange),
random.nextInt (rectRange),
random.nextInt (rectRange),
random.nextInt (rectRange));
g.drawRect (rectArea, 2);
Rangeオブジェクトを使用して、矩形の各要素のランダム値の範囲を制限します。また、厚さ 2 ポイントで矩形を描画(塗りつぶしではなく)します。小さな矩形を描画するには、別のRectangleオブジェクトが必要です。Rectangleコンストラクタに 2 つの引数だけを使用すると、ゼロ位置(0, 0)に指定された幅と高さ(その順序で)のRectangleオブジェクトが作成されます。この行を追加します:
juce::Rectangle<int> pointArea (10, 10);
次にランダムに点を生成し、pointArea矩形の中心をその点に配置します。以下のコードを追加します:
juce::Point<int> point (random.nextInt (juce::Range<int> (0, getWidth())),
random.nextInt (juce::Range<int> (0, getHeight())));
pointArea.setCentre (point);
これはRectangleクラスのもう 1 つの便利な機能を示しています。矩形の中心を配置できます。これは、矩形の位置を左上隅として考えるよりも望ましい場合があります。次に、Rectangle::contains()関数を使用して、pointオブジェクトがrectAreaオブジェクトの境界内にあるかどうかを判断できます。このコードを追加します:
g.setColour (rectArea.contains (point) ? juce::Colours::limegreen
: juce::Colours::cornflowerblue);
g.fillRect (pointArea);
アプリケーションを実行すると、以下のスクリーンショットのようになるはずです。コンポーネントをクリックして再描画させることを忘れないでください:

この例のコードはデモプロジェクトのPointLineRectangleTutorial_02.hファイルにあります。
線の扱い
線の描画と扱いも同様に簡単です。以下のコードは対角線を描画します:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
g.setColour (juce::Colours::orange);
juce::Line<float> line (juce::Point<float> (10, 10),
juce::Point<float> (50, 50));
g.drawLine (line, 2.0f);
}
線の交差
Lineクラスは線の交差テストも実行できます。これをテストするために、いくつかのランダムに生成された線を生成し、これらの線のいずれかが他の線と交差する点に円を描画します。まず、背景を設定して乱数を生成する準備をするpaint()関数を設定しましょう:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
g.setColour (juce::Colours::orange);
auto& random = juce::Random::getSystemRandom();
次に、ランダムな線を生成し、描画するだけでなく、配列に格納します。以下のコードを追加します:
juce::Range<int> lineRange (0, getWidth());
juce::Array<juce::Line<float>> lines;
auto numLines = 10;
for (auto i = 0; i < numLines; ++i)
{
juce::Line<float> line ((float) random.nextInt (lineRange),
(float) random.nextInt (lineRange),
(float) random.nextInt (lineRange),
(float) random.nextInt (lineRange));
lines.add (line);
g.drawLine (line, 2.0f);
}
次に色を変更し、円を描画するための境界として使用する正方形を準備します。以下を追加します:
g.setColour (juce::Colours::palegreen);
juce::Rectangle<float> pointArea (8, 8);
最後に、Line::intersects()関数を使用して、他の線との交差をチェックしながら線の配列を反復します。そしてpointArea矩形の中心をこの点に移動し、円を描画します。これを行うには、以下のコードを追加します:
for (auto lineI : lines)
{
for (auto lineJ : lines)
{
if (lines.indexOf (lineI) != lines.indexOf (lineJ))
{
juce::Point<float> intersection;
if (lineI.intersects (lineJ, intersection)) // [2]
{
pointArea.setCentre (intersection);
g.fillEllipse (pointArea);
}
}
}
}
}
交差をチェックするコード[2]は、Line::intersects()関数を呼び出します。これは線が実際に交差する場合にtrueを返すだけでなく、intersection引数に交差する点も返します。
アプリケーションを実行してください。マウスクリックに応答して再描画するコードを残していれば、コンポーネントをクリックして新しい線のセットを生成できます。

この例のコードはデモプロジェクトのPointLineRectangleTutorial_03.hファイルにあります。
Line::intersects()関数が返すboolをチェックしなかった場合、または代わりにLine::getIntersection()を使用した場合、線が各方向に無限の長さに延長された場合に交差するであろう場所に点が描画されます。例えば、以下のコードを見てください:
//..
if (lines.indexOf (lineI) != lines.indexOf (lineJ))
{
juce::Point<float> intersection;
pointArea.setCentre (lineI.getIntersection (lineJ));
g.fillEllipse (pointArea);
}
//..
これにより、以下のスクリーンショットのようなものが生成されます:

演習:この例のコードの最後のfor()ループはシンプルですが、理想的ではありません。問題は、各線を他のすべての線に対して2 回チェックすることです。各ペアの線を 1 回だけチェックするようにこのコードを書き直してください。この書き直しの一部としてif (lines.indexOf (lineI) != lines.indexOf (lineJ))文を削除できるはずです。
矩形の操作
次に、実行できる矩形の操作をもう少し見てみましょう。始める前に、これから何度も行うランダムな色を生成するシンプルな関数を追加しましょう(チュートリアル:Random クラスを参照):
static juce::Colour getRandomColour()
{
auto& random = juce::Random::getSystemRandom();
return juce::Colour ((juce::uint8) random.nextInt (256),
(juce::uint8) random.nextInt (256),
(juce::uint8) random.nextInt (256));
}
次に、Rectangleクラスを使用して矩形を描画するコードを拡張し、対角線パターンで 10 個の正方形を描画し、ランダムに生成された色を使用します:
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
juce::Rectangle<int> area (10, 10, 40, 40);
auto numSquares = 10;
for (auto i = 0; i < numSquares; ++i)
{
g.setColour (getRandomColour());
g.fillRect (area);
area.translate (30, 30); // [3]
}
}
Rectangle::translate()関数[3]は、提供された水平および垂直オフセットだけ指定された矩形を移動します。結果は以下のスクリーンショットのようになるはずです:

以下は、次の描画操作の前に矩形をリサイズするコードの拡張です。さらに、平行移動は矩形の幅と高さに等しくなるように実行されます。
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
juce::Rectangle<int> area (10, 10, 40, 40);
auto& random = juce::Random::getSystemRandom();
auto numSquares = 10;
for (auto i = 0; i < numSquares; ++i)
{
g.setColour (getRandomColour());
g.fillRect (area);
area.translate (area.getWidth(), area.getHeight()); // [4]
area.setSize (random.nextInt (juce::Range<int> (20, 40)), // width
random.nextInt (juce::Range<int> (20, 40))); // height
}
}
これにより、矩形が角で「結合」されます。以下のスクリーンショットに示されています:

Rectangle::translate()関数を使用する代わりに、加算を使用して矩形を平行移動するために点を「追加」することもできます。これは、上記の[4]を以下の行で置き換えることができることを意味します:
area += juce::Point<int> (area.getWidth(), area.getHeight());
矩形の交差
矩形が重なる場合、Rectangle::getIntersection()関数を使用して交差領域を判断できます。以下の例では、シリーズ内の現在の矩形と次の矩形の間の交差矩形を計算します。これを明確にするために、シリーズ内の各矩形のアウトラインを描画し、塗りつぶされた矩形として描画することで交差領域を強調します。
void paint (juce::Graphics& g) override
{
g.fillAll (juce::Colours::darkgrey);
juce::Rectangle<int> area (10, 10, 40, 40);
auto& random = juce::Random::getSystemRandom();
juce::Range<int> rectRandomRange (20, 40);
auto numSquares = 10;
for (auto i = 0; i < numSquares; ++i)
{
auto nextArea = area + juce::Point<int> (random.nextInt (rectRandomRange), // [5]
random.nextInt (rectRandomRange));
g.setColour (getRandomColour());
g.fillRect (area.getIntersection (nextArea)); // [6]
g.setColour (getRandomColour());
g.drawRect (area, 2); // [7]
area = nextArea;
}
}
矩形をオフセットするために+演算子を使用していることに注意してください[5]。また、矩形のアウトラインを描画する前に、最初に交差領域を塗りつぶします[6] [7]。これにより、最後の交差領域は最終的な対応する矩形なしで描画されます。

この最終例のコードはデモプロジェクトのPointLineRectangleTutorial_04.hファイルにあります。
まとめ
このチュートリアルでは、テンプレートクラスPoint、Line、Rectangleを紹介しました。特に以下の技術をカバーしました:
- Rectangleオブジェクトの作成と操作、および描画コードでの使用。
- 角の点または 2 つの矩形の交差からRectangleオブジェクトを作成する。
- 矩形を含むPathオブジェクトの作成。
- Lineオブジェクトの作成、線の描画、線が交差する点の検索。