9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Siv3DAdvent Calendar 2021

Day 12

今年書いて良かった関数

Posted at

今年書いた関数(と小さなクラス)で来年も使いそうなものをまとめました。

Rectを上下や左右に分割する

SplitUpDown は上下に、SplitLeftRight は左右に指定した比率で Rect を分割して返します。

template<class RectType>
std::pair<RectType, RectType> SplitUpDown(const RectType& rect, double t)
{
	const auto upHeight = static_cast<RectType::value_type>(rect.h * t);
	return std::make_pair(
		RectType{ rect.x, rect.y, rect.w, upHeight },
		RectType{ rect.x, rect.y + upHeight, rect.w, rect.h - upHeight }
	);
}

template<class RectType>
std::pair<RectType, RectType> SplitLeftRight(const RectType& rect, double t)
{
	const auto leftWidth = static_cast<RectType::value_type>(rect.w * t);
	return std::make_pair(
		RectType{ rect.x, rect.y, leftWidth, rect.h },
		RectType{ rect.x + leftWidth, rect.y, rect.w - leftWidth, rect.h }
	);
}

使い方

第2引数の比率は t : 1 - t で計算しているため、次のように 1.0 / 4.0 と指定すると 1 : 3 で分割されます。

const auto [upRect, downRect] = SplitUpDown(rect, 1.0 / 4.0);

普段何かツールを作る時はこれを使って画面レイアウトの計算をしています。以下のように Rect::stretched() で余白を挟みながら組むのがおすすめです。

void Main()
{
	const auto [upRect, downRect] = SplitUpDown(Scene::Rect().stretched(-10), 1.0 / 4.0);

	const auto [leftRect, rightRect] = SplitLeftRight(downRect, 1.0 / 3.0);

	while (System::Update())
	{
		upRect.stretched(-10).drawFrame();

		leftRect.stretched(-10).drawFrame();
		rightRect.stretched(-10).drawFrame();
	}
}

20211212-213451-878.png

角度付きの外接長方形

Rect に外接する長方形を指定した角度で求めます。

template<class RectType>
Quad RotatedBoundingRect(const RectType& rect, double angle)
{
	const double cosTheta = cos(angle);
	const double sinTheta = sin(angle);
	const Vec2 xAxis(cosTheta, sinTheta);
	const Vec2 yAxis(-sinTheta, cosTheta);

	const auto points = Array({ rect.tl(), rect.tr(), rect.bl(), rect.br() });
	const auto xs = points.map([&](const auto& p) { return xAxis.dot(p); }).sorted();
	const auto ys = points.map([&](const auto& p) { return yAxis.dot(p); }).sorted();

	const double width = xs.back() - xs.front();
	const double height = ys.back() - ys.front();

	return RectF(width, height).setCenter(rect.center()).rotatedAt(rect.center(), angle);
}

rotatedBoundingRect.gif

使い方

こんな感じで図形やテキストに網掛けをするときに使っていました。

Array<Vec2> Divide(const Line& line, double interval)
{
	const auto dir = line.vector().normalize();
	const auto length = line.length();

	return Array<Vec2>::IndexedGenerate(static_cast<size_t>(length / interval),
		[&](size_t i) { return line.begin + dir * interval * static_cast<int>(i); });
}

Image TextImage(const DrawableText& text, Color color0, Color color1, int interval = 10, double angle = -45_deg)
{
	const auto region = text.region();

	Image result(Ceil(region.size).asPoint(), Alpha(0));
	text.stamp(result, Vec2::Zero(), color0);

	const Quad quad = RotatedBoundingRect(region, angle).stretched(interval);

	const Vec2 vec = quad.p1 - quad.p0;

	for (const auto& p : Divide(Line(quad.p0, quad.p3), interval))
	{
		Line(p, p + vec).paint(result, static_cast<int>(interval * 0.5), color1);
	}

	return result;
}

void Main()
{
	Font font(64, Typeface::Heavy);

	const auto text = font(U"テキストへの網掛け");

	const auto texture = Texture(TextImage(text, Palette::White, Palette::Orange));

	while (System::Update())
	{
		texture.draw();
	}
}

20211212-212549-755.png

テキストの描画位置指定

テキストの右上や右下などを基準位置に指定して描画する関数です。

inline void DrawTopLeft(const DrawableText& text, const Vec2& topLeft, const ColorF& color = Palette::White)
{
	text.draw(topLeft, color);
}

inline void DrawTopCenter(const DrawableText& text, const Vec2& topCenter, const ColorF& color = Palette::White)
{
	text.draw(topCenter - text.region().topCenter(), color);
}

inline void DrawTopRight(const DrawableText& text, const Vec2& topRight, const ColorF& color = Palette::White)
{
	text.draw(topRight - text.region().tr(), color);
}

inline void DrawRightCenter(const DrawableText& text, const Vec2& rightCetner, const ColorF& color = Palette::White)
{
	text.draw(rightCetner - text.region().rightCenter(), color);
}

inline void DrawBottomRight(const DrawableText& text, const Vec2& bottomRight, const ColorF& color = Palette::White)
{
	text.draw(bottomRight - text.region().br(), color);
}

inline void DrawBottomCenter(const DrawableText& text, const Vec2& bottomCenter, const ColorF& color = Palette::White)
{
	text.draw(bottomCenter - text.region().bottomCenter(), color);
}

inline void DrawBottomLeft(const DrawableText& text, const Vec2& bottomLeft, const ColorF& color = Palette::White)
{
	text.draw(bottomLeft - text.region().bl(), color);
}

inline void DrawLeftCenter(const DrawableText& text, const Vec2& leftCenter, const ColorF& color = Palette::White)
{
	text.draw(leftCenter - text.region().leftCenter(), color);
}

20211211-161850-584.png

使い方

void Main()
{
	const Font small(16);
	const Font large(48);

	const auto pos = Scene::Center();

	while (System::Update())
	{
		DrawBottomRight(small(U"こういう"), pos);
		DrawTopRight(small(U"タイポグラフィに"), pos);
		DrawLeftCenter(large(U"便利"), pos);
	}
}

20211211-162005-505.png

テキストを省略する

指定した幅からはみ出た分のテキストを "..." で省略して返します。

String RegionTrimmedText(const DrawableText& drawableText, double width)
{
	if (drawableText.region().w < width)
	{
		return drawableText.text;
	}

	const auto& font = drawableText.font;
	const auto& text = drawableText.text;
	for (size_t i = 1; i <= text.length(); ++i)
	{
		const String trimmedText = text.substr(0, text.length() - i) + U"...";
		if (font(trimmedText).region().w < width)
		{
			return trimmedText;
		}
	}

	return U"";
}

使い方

void Main()
{
	const Font font(48);

	while (System::Update())
	{
		const auto width = Cursor::Pos().x;
		const auto text = RegionTrimmedText(font(U"あいうえおかきくけこさしすせそ"), width);
		font(text).draw();

		Line(width, 0, width, Scene::Height()).draw();
	}
}

trim.gif

複数区間の InvLerp

MultiInvLerp は連続した複数の区間に対する InvLerp を計算します。

#include <span>

template<typename T, size_t N>
struct MultiInvLerpImpl;

template<typename T>
struct MultiInvLerpImpl<T, 0> {};

template<typename T>
struct MultiInvLerpImpl<T, 1> {};

template<typename T>
struct MultiInvLerpImpl<T, 2>
{
	static auto get(T t, std::span<const T, 2> ts)
	{
		return std::make_tuple((t - ts[0]) / (ts[1] - ts[0]));
	}
};

template<typename T, size_t N>
struct MultiInvLerpImpl
{
	static auto get(T t, std::span<const T, N> ts)
	{
		return std::tuple_cat(
			std::make_tuple((t - ts[0]) / (ts[1] - ts[0])),
			MultiInvLerpImpl<T, N - 1>::get(t, ts.subspan<1, N - 1>())
		);
	}
};

template<typename T, typename... Ts>
auto MultiInvLerp(T t, Ts... ts)
{
	constexpr size_t N = sizeof...(Ts);
	static_assert(2 <= N, "2個以上の点を指定してください");
	const std::array<T, N> arr{ts...};
	return MultiInvLerpImpl<T, N>::get(t, arr);
}

template<typename T>
inline bool In01(T t)
{
	return T{ 0 } <= t && t <= T{ 1 };
}

使い方

第1引数に変数の値、第2引数以降に連続した区間を渡すと、変数の値をそれぞれの区間で InvLerp にかけた結果が返ってきます。

auto [t0, t1, t2] = MultiInvLerp(time, 0.0, 1.0, 5.0, 8.0);

MultiInvLerp.png

複数のモーションをつないだアニメーションを楽に書くために使いました。あと他のメリットとして、MultiInvLerp に渡す前の time にさらにイージングをかければ全体の緩急を付けたり速度を変えたりするのが簡単に書けます。

void Main()
{
	Stopwatch watch(StartImmediately::Yes);

	const auto pos = Scene::Center();

	const auto rect = RectF(Arg::center = Vec2::Zero(), 40, 40);

	const auto height = Scene::Center().y - rect.h * 0.5;

	while (System::Update())
	{
		const double sec = fmod(watch.sF(), 4.0);

		const auto [t0, t1, t2] = MultiInvLerp(sec, 0.0, 1.0, 3.0, 4.0);

		if (In01(t0))
		{
			rect.movedBy(pos + Math::Lerp(Vec2(0, height), Vec2(0, 0), EaseInOutExpo(t0))).draw();
		}
		if (In01(t1))
		{
			rect.rotated(3_tau * EaseInOutCubic(t1)).moveBy(pos).draw();
		}
		if (In01(t2))
		{
			rect.movedBy(pos + Math::Lerp(Vec2(0, 0), Vec2(0, height), EaseInOutExpo(t2))).draw();
		}
	}
}

multiInvLerp_.gif

ドラッグできるようにする

Draggable を継承させるか DragOp を持たせることでオブジェクトのドラッグ操作を実装します。

class Draggable
{
public:

	bool update()
	{
		if (isMouseOver() && MouseL.down())
		{
			m_clickedVec = getPos() - Cursor::Pos();
		}

		if (!MouseL.pressed())
		{
			m_clickedVec = none;
		}

		if (m_clickedVec)
		{
			setPos(Cursor::Pos() + m_clickedVec.value());
		}

		return m_clickedVec.has_value();
	}

	virtual bool isMouseOver() const = 0;
	virtual Vec2 getPos() const = 0;
	virtual void setPos(const Vec2&) = 0;

private:

	Optional<Vec2> m_clickedVec;
};

class DragOp : public Draggable
{
public:

	DragOp(
		std::function<bool()> isMouseOverFunc,
		std::function<Vec2()> getPosFunc,
		std::function<void(const Vec2&)> setPosFunc)
		: m_isMouseOver(isMouseOverFunc)
		, m_getPos(getPosFunc)
		, m_setPos(setPosFunc)
	{}

	bool isMouseOver() const override { return m_isMouseOver(); }
	Vec2 getPos() const override { return m_getPos(); }
	void setPos(const Vec2& pos) override { m_setPos(pos); }

	std::function<bool()> m_isMouseOver;
	std::function<Vec2()> m_getPos;
	std::function<void(const Vec2&)> m_setPos;
};

使い方

getPos()setPos() は同じ座標が対応していれば良いのでオブジェクトの左上でも中央でも大丈夫です。ただし、複数のオブジェクトが重なっていると一緒に動いてしまうので、ドラッグ対象が複数ある場合は update()true を返したら他の update() は呼ばないようにするといった対応が必要になります。

void Main()
{
	Circle circle(300, 200, 100);

	DragOp circleDrag(
		[&]() { return circle.mouseOver(); },
		[&]() { return circle.center; },
		[&](const Vec2& pos) { circle.center = pos; });

	while (System::Update())
	{
		circle.draw();

		circleDrag.update();
	}
}

drag.gif

定積分の計算

関数の定積分をガウス求積法により計算します。

template<size_t Degree, typename Func>
constexpr double Integrate(Func func, double a, double b)
{
	struct XW
	{
		constexpr XW(double x, double w) :x(x), w(w) {}
		double x, w;
	};

	const auto coeffs = std::make_tuple(
		/* 01 */ std::make_tuple(XW{0,2}),
		/* 02 */ std::make_tuple(XW{ -0.5773502691896257,1.0000000000000000 }, XW{ 0.5773502691896257,1.0000000000000000 }),
		/* 03 */ std::make_tuple(XW{ 0.0000000000000000,0.8888888888888888 }, XW{ -0.7745966692414834,0.5555555555555556 }, XW{ 0.7745966692414834,0.5555555555555556 }),
		/* 04 */ std::make_tuple(XW{ -0.3399810435848563,0.6521451548625461 }, XW{ 0.3399810435848563,0.6521451548625461 }, XW{ -0.8611363115940526,0.3478548451374538 }, XW{ 0.8611363115940526,0.3478548451374538 }),
		/* 05 */ std::make_tuple(XW{ 0.0000000000000000,0.5688888888888889 }, XW{ -0.5384693101056831,0.4786286704993665 }, XW{ 0.5384693101056831,0.4786286704993665 }, XW{ -0.9061798459386640,0.2369268850561891 }, XW{ 0.9061798459386640,0.2369268850561891 }),
		/* 06 */ std::make_tuple(XW{ 0.6612093864662645,0.3607615730481386 }, XW{ -0.6612093864662645,0.3607615730481386 }, XW{ -0.2386191860831969,0.4679139345726910 }, XW{ 0.2386191860831969,0.4679139345726910 }, XW{ -0.9324695142031521,0.1713244923791704 }, XW{ 0.9324695142031521,0.1713244923791704 }),
		/* 07 */ std::make_tuple(XW{ 0.0000000000000000,0.4179591836734694 }, XW{ 0.4058451513773972,0.3818300505051189 }, XW{ -0.4058451513773972,0.3818300505051189 }, XW{ -0.7415311855993945,0.2797053914892766 }, XW{ 0.7415311855993945,0.2797053914892766 }, XW{ -0.9491079123427585,0.1294849661688697 }, XW{ 0.9491079123427585,0.1294849661688697 }),
		/* 08 */ std::make_tuple(XW{ -0.1834346424956498,0.3626837833783620 }, XW{ 0.1834346424956498,0.3626837833783620 }, XW{ -0.5255324099163290,0.3137066458778873 }, XW{ 0.5255324099163290,0.3137066458778873 }, XW{ -0.7966664774136267,0.2223810344533745 }, XW{ 0.7966664774136267,0.2223810344533745 }, XW{ -0.9602898564975363,0.1012285362903763 }, XW{ 0.9602898564975363,0.1012285362903763 }),
		/* 09 */ std::make_tuple(XW{ 0.0000000000000000,0.3302393550012598 }, XW{ -0.8360311073266358,0.1806481606948574 }, XW{ 0.8360311073266358,0.1806481606948574 }, XW{ -0.9681602395076261,0.0812743883615744 }, XW{ 0.9681602395076261,0.0812743883615744 }, XW{ -0.3242534234038089,0.3123470770400029 }, XW{ 0.3242534234038089,0.3123470770400029 }, XW{ -0.6133714327005904,0.2606106964029354 }, XW{ 0.6133714327005904,0.2606106964029354 }),
		/* 10 */ std::make_tuple(XW{ -0.1488743389816312,0.2955242247147529 }, XW{ 0.1488743389816312,0.2955242247147529 }, XW{ -0.4333953941292472,0.2692667193099963 }, XW{ 0.4333953941292472,0.2692667193099963 }, XW{ -0.6794095682990244,0.2190863625159820 }, XW{ 0.6794095682990244,0.2190863625159820 }, XW{ -0.8650633666889845,0.1494513491505806 }, XW{ 0.8650633666889845,0.1494513491505806 }, XW{ -0.9739065285171717,0.0666713443086881 }, XW{ 0.9739065285171717,0.0666713443086881 }),
		/* 11 */ std::make_tuple(XW{ 0.0000000000000000,0.2729250867779006 }, XW{ -0.2695431559523450,0.2628045445102467 }, XW{ 0.2695431559523450,0.2628045445102467 }, XW{ -0.5190961292068118,0.2331937645919905 }, XW{ 0.5190961292068118,0.2331937645919905 }, XW{ -0.7301520055740494,0.1862902109277343 }, XW{ 0.7301520055740494,0.1862902109277343 }, XW{ -0.8870625997680953,0.1255803694649046 }, XW{ 0.8870625997680953,0.1255803694649046 }, XW{ -0.9782286581460570,0.0556685671161737 }, XW{ 0.9782286581460570,0.0556685671161737 }),
		/* 12 */ std::make_tuple(XW{ -0.1252334085114689,0.2491470458134028 }, XW{ 0.1252334085114689,0.2491470458134028 }, XW{ -0.3678314989981802,0.2334925365383548 }, XW{ 0.3678314989981802,0.2334925365383548 }, XW{ -0.5873179542866175,0.2031674267230659 }, XW{ 0.5873179542866175,0.2031674267230659 }, XW{ -0.7699026741943047,0.1600783285433462 }, XW{ 0.7699026741943047,0.1600783285433462 }, XW{ -0.9041172563704749,0.1069393259953184 }, XW{ 0.9041172563704749,0.1069393259953184 }, XW{ -0.9815606342467192,0.0471753363865118 }, XW{ 0.9815606342467192,0.0471753363865118 }),
		/* 13 */ std::make_tuple(XW{ 0.0000000000000000,0.2325515532308739 }, XW{ -0.2304583159551348,0.2262831802628972 }, XW{ 0.2304583159551348,0.2262831802628972 }, XW{ -0.4484927510364469,0.2078160475368885 }, XW{ 0.4484927510364469,0.2078160475368885 }, XW{ -0.6423493394403402,0.1781459807619457 }, XW{ 0.6423493394403402,0.1781459807619457 }, XW{ -0.8015780907333099,0.1388735102197872 }, XW{ 0.8015780907333099,0.1388735102197872 }, XW{ -0.9175983992229779,0.0921214998377285 }, XW{ 0.9175983992229779,0.0921214998377285 }, XW{ -0.9841830547185881,0.0404840047653159 }, XW{ 0.9841830547185881,0.0404840047653159 }),
		/* 14 */ std::make_tuple(XW{ -0.1080549487073437,0.2152638534631578 }, XW{ 0.1080549487073437,0.2152638534631578 }, XW{ -0.3191123689278897,0.2051984637212956 }, XW{ 0.3191123689278897,0.2051984637212956 }, XW{ -0.5152486363581541,0.1855383974779378 }, XW{ 0.5152486363581541,0.1855383974779378 }, XW{ -0.6872929048116855,0.1572031671581935 }, XW{ 0.6872929048116855,0.1572031671581935 }, XW{ -0.8272013150697650,0.1215185706879032 }, XW{ 0.8272013150697650,0.1215185706879032 }, XW{ -0.9284348836635735,0.0801580871597602 }, XW{ 0.9284348836635735,0.0801580871597602 }, XW{ -0.9862838086968123,0.0351194603317519 }, XW{ 0.9862838086968123,0.0351194603317519 }),
		/* 15 */ std::make_tuple(XW{ 0.0000000000000000,0.2025782419255613 }, XW{ -0.2011940939974345,0.1984314853271116 }, XW{ 0.2011940939974345,0.1984314853271116 }, XW{ -0.3941513470775634,0.1861610000155622 }, XW{ 0.3941513470775634,0.1861610000155622 }, XW{ -0.5709721726085388,0.1662692058169939 }, XW{ 0.5709721726085388,0.1662692058169939 }, XW{ -0.7244177313601701,0.1395706779261543 }, XW{ 0.7244177313601701,0.1395706779261543 }, XW{ -0.8482065834104272,0.1071592204671719 }, XW{ 0.8482065834104272,0.1071592204671719 }, XW{ -0.9372733924007060,0.0703660474881081 }, XW{ 0.9372733924007060,0.0703660474881081 }, XW{ -0.9879925180204854,0.0307532419961173 }, XW{ 0.9879925180204854,0.0307532419961173 }),
		/* 16 */ std::make_tuple(XW{ -0.0950125098376374,0.1894506104550685 }, XW{ 0.0950125098376374,0.1894506104550685 }, XW{ -0.2816035507792589,0.1826034150449236 }, XW{ 0.2816035507792589,0.1826034150449236 }, XW{ -0.4580167776572274,0.1691565193950025 }, XW{ 0.4580167776572274,0.1691565193950025 }, XW{ -0.6178762444026438,0.1495959888165767 }, XW{ 0.6178762444026438,0.1495959888165767 }, XW{ -0.7554044083550030,0.1246289712555339 }, XW{ 0.7554044083550030,0.1246289712555339 }, XW{ -0.8656312023878318,0.0951585116824928 }, XW{ 0.8656312023878318,0.0951585116824928 }, XW{ -0.9445750230732326,0.0622535239386479 }, XW{ 0.9445750230732326,0.0622535239386479 }, XW{ -0.9894009349916499,0.0271524594117541 }, XW{ 0.9894009349916499,0.0271524594117541 }),
		/* 17 */ std::make_tuple(XW{ 0.0000000000000000,0.1794464703562065 }, XW{ -0.1784841814958479,0.1765627053669926 }, XW{ 0.1784841814958479,0.1765627053669926 }, XW{ -0.3512317634538763,0.1680041021564500 }, XW{ 0.3512317634538763,0.1680041021564500 }, XW{ -0.5126905370864769,0.1540457610768103 }, XW{ 0.5126905370864769,0.1540457610768103 }, XW{ -0.6576711592166907,0.1351363684685255 }, XW{ 0.6576711592166907,0.1351363684685255 }, XW{ -0.7815140038968014,0.1118838471934040 }, XW{ 0.7815140038968014,0.1118838471934040 }, XW{ -0.8802391537269859,0.0850361483171792 }, XW{ 0.8802391537269859,0.0850361483171792 }, XW{ -0.9506755217687678,0.0554595293739872 }, XW{ 0.9506755217687678,0.0554595293739872 }, XW{ -0.9905754753144174,0.0241483028685479 }, XW{ 0.9905754753144174,0.0241483028685479 }),
		/* 18 */ std::make_tuple(XW{ -0.0847750130417353,0.1691423829631436 }, XW{ 0.0847750130417353,0.1691423829631436 }, XW{ -0.2518862256915055,0.1642764837458327 }, XW{ 0.2518862256915055,0.1642764837458327 }, XW{ -0.4117511614628426,0.1546846751262652 }, XW{ 0.4117511614628426,0.1546846751262652 }, XW{ -0.5597708310739475,0.1406429146706507 }, XW{ 0.5597708310739475,0.1406429146706507 }, XW{ -0.6916870430603532,0.1225552067114785 }, XW{ 0.6916870430603532,0.1225552067114785 }, XW{ -0.8037049589725231,0.1009420441062872 }, XW{ 0.8037049589725231,0.1009420441062872 }, XW{ -0.8926024664975557,0.0764257302548891 }, XW{ 0.8926024664975557,0.0764257302548891 }, XW{ -0.9558239495713977,0.0497145488949698 }, XW{ 0.9558239495713977,0.0497145488949698 }, XW{ -0.9915651684209309,0.0216160135264833 }, XW{ 0.9915651684209309,0.0216160135264833 }),
		/* 19 */ std::make_tuple(XW{ 0.0000000000000000,0.1610544498487837 }, XW{ -0.1603586456402254,0.1589688433939543 }, XW{ 0.1603586456402254,0.1589688433939543 }, XW{ -0.3165640999636298,0.1527660420658597 }, XW{ 0.3165640999636298,0.1527660420658597 }, XW{ -0.4645707413759609,0.1426067021736066 }, XW{ 0.4645707413759609,0.1426067021736066 }, XW{ -0.6005453046616810,0.1287539625393362 }, XW{ 0.6005453046616810,0.1287539625393362 }, XW{ -0.7209661773352294,0.1115666455473340 }, XW{ 0.7209661773352294,0.1115666455473340 }, XW{ -0.8227146565371428,0.0914900216224500 }, XW{ 0.8227146565371428,0.0914900216224500 }, XW{ -0.9031559036148179,0.0690445427376412 }, XW{ 0.9031559036148179,0.0690445427376412 }, XW{ -0.9602081521348300,0.0448142267656996 }, XW{ 0.9602081521348300,0.0448142267656996 }, XW{ -0.9924068438435844,0.0194617882297265 }, XW{ 0.9924068438435844,0.0194617882297265 }),
		/* 20 */ std::make_tuple(XW{ -0.0765265211334973,0.1527533871307258 }, XW{ 0.0765265211334973,0.1527533871307258 }, XW{ -0.2277858511416451,0.1491729864726037 }, XW{ 0.2277858511416451,0.1491729864726037 }, XW{ -0.3737060887154195,0.1420961093183820 }, XW{ 0.3737060887154195,0.1420961093183820 }, XW{ -0.5108670019508271,0.1316886384491766 }, XW{ 0.5108670019508271,0.1316886384491766 }, XW{ -0.6360536807265150,0.1181945319615184 }, XW{ 0.6360536807265150,0.1181945319615184 }, XW{ -0.7463319064601508,0.1019301198172404 }, XW{ 0.7463319064601508,0.1019301198172404 }, XW{ -0.8391169718222188,0.0832767415767048 }, XW{ 0.8391169718222188,0.0832767415767048 }, XW{ -0.9122344282513259,0.0626720483341091 }, XW{ 0.9122344282513259,0.0626720483341091 }, XW{ -0.9639719272779138,0.0406014298003869 }, XW{ 0.9639719272779138,0.0406014298003869 }, XW{ -0.9931285991850949,0.0176140071391521 }, XW{ 0.9931285991850949,0.0176140071391521 })
		// ... 21次以降は省略 (参照 → https://pomax.github.io/bezierinfo/legendre-gauss.html)
	);

	const double c = 0.5 * (b - a);
	const double d = 0.5 * (a + b);

	return c * std::apply(
		[&](const auto&...xw) { return ((xw.w * func(c * xw.x + d)) + ...); },
		std::get<Degree - 1>(coeffs));
}

使い方

テンプレート引数に分割点の個数、引数に関数と始点と終点を渡します。分割点の数は多いほど結果の精度が高くなります。

const double result = Integrate<20>([](double x) { return 4.0 / (1.0 + x * x); }, 0, 5);

曲線の長さを折れ線近似で計算した場合との比較です。

// |dBezier3(t) / dt|
double dNormBezier3(const std::array<Vec2, 4>& ps, double t)
{
	const double t1 = 1.0 - t;
	const Vec2 d = 3.0 * (ps[1] - ps[0]) * t1 * t1 +
		6.0 * (ps[2] - ps[1]) * t1 * t + 3.0 * (ps[3] - ps[2]) * t * t;
	return d.length();
}

void Main()
{
	std::array<Vec2, 4> ps{ Vec2(160,180),Vec2(290,460),Vec2(400,120),Vec2(600,400) };

	Bezier3 bezier(ps[0], ps[1], ps[2], ps[3]);

	const auto lengthLinear = bezier.getLineString(100000).calculateLength();
	const auto lengthIntegrate = Integrate<20>([&](double t) { return dNormBezier3(ps, t); }, 0, 1);

	Print << U"{:.16} | Linear"_fmt(lengthLinear);
	Print << U"{:.16} | Integrate"_fmt(lengthIntegrate);

	while (System::Update())
	{
		for (auto& p : ps)
		{
			p.asCircle(5).draw();
		}

		bezier.draw();
	}
}

ちゃんとした比較にはなっていませんが、簡単な曲線なら分割数をそんなに増やさなくても十分使えそうということがわかります。
20211212-131705-876.png


以上です。もうちょっと書きたいことあったけどまとめる時間が足りなかった。。

9
5
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
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?