diff --git a/Data/FlowCurves/EastKorea.tsv b/Data/FlowCurves/EastKorea.tsv new file mode 100644 index 000000000..faffd07eb --- /dev/null +++ b/Data/FlowCurves/EastKorea.tsv @@ -0,0 +1,24 @@ +longitude latitude +125.56 35.04 +125.79 34.43 +126.31 34.05 +127.19 33.96 +128.11 33.90 +128.79 34.18 +129.03 34.55 +129.27 35.02 +129.60 35.42 +129.75 35.96 +129.79 36.72 +129.91 37.53 +130.03 38.22 +130.52 38.55 +131.71 38.72 +132.02 39.15 +133.35 39.35 +133.90 38.93 +134.47 38.29 +134.84 37.78 +135.33 37.54 +135.5961 37.7112 +135.8521 37.873 \ No newline at end of file diff --git a/Data/FlowCurves/GulfStream.tsv b/Data/FlowCurves/GulfStream.tsv new file mode 100644 index 000000000..f67e46627 --- /dev/null +++ b/Data/FlowCurves/GulfStream.tsv @@ -0,0 +1,30 @@ +longitude latitude +-81.9583 24.099 +-81.0417 24.2241 +-80.3333 24.4742 +-80 24.9745 +-79.8333 25.6415 +-79.7917 26.7254 +-79.6667 27.6009 +-79.8333 28.6015 +-80 29.1435 +-80.125 29.8105 +-79.7917 30.7694 +-79.2917 31.4364 +-78.625 31.8116 +-77.7917 31.895 +-76.9583 32.0618 +-76 32.7705 +-75.7083 33.4792 +-75.1667 34.4381 +-74.5417 35.2719 +-73.625 36.606 +-72.5833 37.5231 +-71.5417 38.2319 +-70.2917 37.94 +-68.6667 37.2313 +-67.75 37.6482 +-66.8333 38.482 +-65.8333 38.4403 +-65.0417 38.7321 +-64.1667 38.8155 diff --git a/Data/FlowCurves/Kuroshio.tsv b/Data/FlowCurves/Kuroshio.tsv new file mode 100644 index 000000000..5bf92e2c8 --- /dev/null +++ b/Data/FlowCurves/Kuroshio.tsv @@ -0,0 +1,79 @@ +longitude latitude +126.17 11.24 +125.72 12.12 +125.48 12.69 +124.74 13.61 +124.44 14.35 +123.30 15.20 +122.64 16.07 +122.95 17.00 +122.80 18.20 +122.40 19.02 +121.38 19.86 +120.64 20.38 +119.92 20.67 +119.35 21.20 +119.47 21.78 +120.13 21.99 +120.78 21.75 +121.26 21.91 +121.65 22.80 +121.9007 23.4462 +122.2527 24.363 +122.5727 25.0102 +122.8927 25.199 +123.5646 25.3877 +124.2686 25.5226 +124.7166 25.6844 +125.1646 25.981 +125.5166 26.2776 +125.9645 26.6551 +126.3485 27.3023 +126.8925 27.7607 +127.5005 28.4618 +128.0764 29.0821 +128.3964 29.6753 +128.6524 30.0259 +129.1324 30.1337 +129.7084 30.0798 +129.9963 29.6753 +130.4443 29.4596 +131.1163 29.4866 +131.5963 29.891 +131.7883 30.5652 +131.8843 31.1045 +132.2042 31.509 +132.8122 31.9405 +133.6442 32.264 +134.2841 32.6416 +135.1801 32.7494 +136.1081 32.8573 +136.876 32.7764 +137.9 32.8303 +138.9559 32.9652 +139.5959 33.1809 +139.8839 33.5854 +140.3319 34.2056 +140.7798 34.691 +141.3558 35.1224 +142.0278 35.5269 +142.6038 35.8775 +142.7958 36.2011 +142.8598 36.6595 +143.0837 37.037 +143.7557 37.2258 +144.4917 37.1449 +145.3236 36.9561 +145.8356 36.6595 +145.9956 36.1471 +146.0596 35.4191 +146.1556 34.8797 +146.4116 34.6101 +146.7316 34.5831 +147.1795 34.7449 +147.4355 35.0955 +147.5635 35.5 +147.8835 35.9584 +148.2995 36.4438 +148.7475 36.7404 +149.5154 37.091 diff --git a/Data/FlowCurves/Tsushima.tsv b/Data/FlowCurves/Tsushima.tsv new file mode 100644 index 000000000..84d6bc1ff --- /dev/null +++ b/Data/FlowCurves/Tsushima.tsv @@ -0,0 +1,55 @@ +longitude latitude +127.19 33.96 +128.11 33.90 +128.67 33.88 +129.32 33.97 +130.1243 34.2595 +130.6043 34.5292 +130.9563 34.8528 +131.2443 35.1224 +131.5323 35.2842 +131.9803 35.3651 +132.3962 35.4191 +132.6202 35.5539 +132.7802 35.9314 +132.9402 36.2011 +133.3242 36.4438 +133.5802 36.4977 +133.8682 36.3629 +134.2202 36.228 +134.4761 36.255 +134.8281 36.4438 +135.1481 36.4977 +135.5321 36.5516 +135.7241 36.7134 +135.7561 36.9022 +135.5641 37.064 +135.4361 37.2258 +135.4361 37.4685 +135.5961 37.7112 +135.8521 37.873 +136.0441 38.0887 +136.1081 38.4392 +136.1721 38.8437 +136.3001 39.1673 +136.62 39.3291 +137.004 39.2752 +137.388 39.1134 +137.644 38.9516 +137.868 38.9246 +138.028 39.1134 +138.028 39.3831 +137.74 39.5718 +137.324 39.7606 +137.196 39.9763 +137.292 40.246 +137.516 40.5426 +137.964 40.7314 +138.6359 40.7853 +139.1799 40.8392 +139.4359 41.0549 +139.7239 41.3246 +140.20 41.27 +140.80 41.56 +141.26 41.60 +141.99 41.26 diff --git a/Data/Settings.tsv b/Data/Settings.tsv index 040d8b625..f0bc08d31 100644 --- a/Data/Settings.tsv +++ b/Data/Settings.tsv @@ -5,6 +5,7 @@ MapIcons Data/Settings/MapIcons.tsv MenuIcons Data/Settings/MenuIcons.tsv Persons Data/Settings/Persons.tsv Territories Data/Settings/Territories.tsv +FlowCurves Data/Settings/FlowCurves.tsv PlaceNames Data/Settings/PlaceNames.tsv GeographicFeatures Data/Settings/GeographicFeatures.tsv Genomes Data/Settings/Genomes.tsv diff --git a/Data/Settings/FlowCurves.tsv b/Data/Settings/FlowCurves.tsv new file mode 100644 index 000000000..ab1d8a613 --- /dev/null +++ b/Data/Settings/FlowCurves.tsv @@ -0,0 +1,5 @@ +key file_path type description min_size max_size first_year last_year color line_width +Test1 Data/FlowCurves/Kuroshio.tsv view_flow_curve 黒潮 0 200 -2500 1500 black 55.5 +Test2 Data/FlowCurves/Tsushima.tsv view_flow_curve 対馬海流 0 200 -2500 1500 black 55.5 +Test3 Data/FlowCurves/EastKorea.tsv view_flow_curve 東朝鮮暖流 0 200 -2500 1500 black 55.5 +Test4 Data/FlowCurves/GulfStream.tsv view_flow_curve メキシコ湾流 0 200 -2500 1500 black 55.5 \ No newline at end of file diff --git a/Data/Settings/Territories.tsv b/Data/Settings/Territories.tsv index 22bc994a6..e4b54d17b 100644 --- a/Data/Settings/Territories.tsv +++ b/Data/Settings/Territories.tsv @@ -1,3 +1,6 @@ key file_path type description min_size max_size first_year last_year color line_width -TestTerritories Data/Territories/TestTerritories.tsv view_territory ??? 0 100 100 1000 black 55.5 -TestTerritories Data/Territories/TestTerritories2.tsv view_territory ??? 0 100 100 1000 black 55.5 +Territories1 Data/Territories/Sakishima/Shimotabaru.tsv view_territory 下田原文化 0 100 -2500 -1500 black 55.5 +Territories2 Data/Territories/Sakishima/Mudoki.tsv view_territory 先島無土器文化 0 100 -200 1200 black 55.5 +Territories3 Data/Territories/Ryukyu/Kaizuka.tsv view_territory 沖縄貝塚文化 0 100 -4400 1200 black 55.5 +Territories4 Data/Territories/Lapita.tsv view_territory ラピタ文化 0 100 -1600 -500 black 55.5 +Territories4 Data/Territories/Afanasievo.tsv view_territory ??? 0 200 -3300 -2500 black 55.5 \ No newline at end of file diff --git a/Data/Territories/Afanasievo.tsv b/Data/Territories/Afanasievo.tsv new file mode 100644 index 000000000..948dbcfef --- /dev/null +++ b/Data/Territories/Afanasievo.tsv @@ -0,0 +1,10 @@ +longitude latitude +90 53 +102 50 +108 48 +90 44 +85 44 +80 46 +82 50 +86 52 +90 53 diff --git a/Data/Territories/Lapita.tsv b/Data/Territories/Lapita.tsv new file mode 100644 index 000000000..338ecbce1 --- /dev/null +++ b/Data/Territories/Lapita.tsv @@ -0,0 +1,10 @@ +longitude latitude +166 -4 +-171.8 -8 +-171.8 -13.8 +179 -17.8 +170 -22 +160 -22 +152 -10 +152 -4 +166 -4 diff --git a/Data/Territories/Ryukyu/Kaizuka.tsv b/Data/Territories/Ryukyu/Kaizuka.tsv new file mode 100644 index 000000000..70bb8b457 --- /dev/null +++ b/Data/Territories/Ryukyu/Kaizuka.tsv @@ -0,0 +1,6 @@ +longitude latitude +126.9984881 27.34026222 +126.4740218 25.89157499 +129.8427092 26.81341226 +130.2125251 28.67591147 +126.9984881 27.34026222 diff --git a/Data/Territories/Sakishima/Mudoki.tsv b/Data/Territories/Sakishima/Mudoki.tsv new file mode 100644 index 000000000..44ed781c1 --- /dev/null +++ b/Data/Territories/Sakishima/Mudoki.tsv @@ -0,0 +1,6 @@ +longitude latitude +124.6292555 24.96411605 +125.6646583 24.79521803 +123.789828 23.97415958 +122.8360816 24.45970535 +124.6292555 24.96411605 diff --git a/Data/Territories/Sakishima/Shimotabaru.tsv b/Data/Territories/Sakishima/Shimotabaru.tsv new file mode 100644 index 000000000..108773d3e --- /dev/null +++ b/Data/Territories/Sakishima/Shimotabaru.tsv @@ -0,0 +1,6 @@ +longitude latitude +123.9400757 24.80411315 +124.8872897 24.71513324 +123.789828 23.97415958 +122.8360816 24.45970535 +123.9400757 24.80411315 diff --git a/Data/Territories/TestTerritories.tsv b/Data/Territories/TestTerritories.tsv deleted file mode 100644 index 8cf142ffa..000000000 --- a/Data/Territories/TestTerritories.tsv +++ /dev/null @@ -1,7 +0,0 @@ -longitude latitude -135.0 5.0 -135.5 5.3 -137.0 6.0 -137.5 4.0 -134.0 3.0 -135.0 5.0 \ No newline at end of file diff --git a/Data/Territories/TestTerritories2.tsv b/Data/Territories/TestTerritories2.tsv deleted file mode 100644 index 8f5dbc68f..000000000 --- a/Data/Territories/TestTerritories2.tsv +++ /dev/null @@ -1,6 +0,0 @@ -longitude latitude -135.0 0.0 -137.0 0.0 -137.0 2.0 -135.0 2.0 -135.0 0.0 \ No newline at end of file diff --git a/Library/PAX_GRAPHICA/Key.hpp b/Library/PAX_GRAPHICA/Key.hpp index 7035064bd..4119c04f8 100644 --- a/Library/PAX_GRAPHICA/Key.hpp +++ b/Library/PAX_GRAPHICA/Key.hpp @@ -258,47 +258,6 @@ namespace paxg { return false; } - class Coordinate { - public: - Coordinate(const paxs::MercatorDeg& coordinate_) - : coordinate(coordinate_){} - - double getX() const { - return coordinate.x; - } - double getY() const { - return coordinate.y; - } - - /// @brief Get coordinate as Vector2 - /// @brief 座標をVector2として取得 - /// @return Vector2 containing x and y coordinates - paxs::Vector2 getVector2() const { - return paxs::Vector2(coordinate.x, coordinate.y); - } - - void setX(const double x_) { - coordinate.x = x_; - } - void setY(const double y_) { - coordinate.y = y_; - } - void set(paxs::Vector2 position) { - coordinate.x = position.x; - coordinate.y = position.y; - } - - double toEquirectangularRadY() const { - return coordinate.toEquirectangularRadY(); - } - double toEquirectangularDegY() const { - return coordinate.toEquirectangularDegY(); - } - - private: - paxs::MercatorDeg coordinate; // マップ座標の中央 - }; - } #endif // !PAX_GRAPHICA_KEY_HPP diff --git a/Library/PAX_GRAPHICA/Spline2D.hpp b/Library/PAX_GRAPHICA/Spline2D.hpp index 4f5a28ce2..da1cad2ea 100644 --- a/Library/PAX_GRAPHICA/Spline2D.hpp +++ b/Library/PAX_GRAPHICA/Spline2D.hpp @@ -36,6 +36,7 @@ namespace paxg { s3d::Spline2D spline; #else std::vector points_; + bool is_closed_ = false; // 閉じたループかどうか / Whether it's a closed loop #endif static Vec2f calculateCatmullRom(const Vec2f& p0, const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, float t) { @@ -53,10 +54,36 @@ namespace paxg { return Vec2f{ x, y }; } + /// @brief 閉じたループかどうかを自動判定し、重複する最後の点を除去 + /// @brief Automatically detect closed loop and remove duplicate last point + /// @return true if closed loop detected (and duplicate removed) + bool detectAndFixClosedLoop(std::vector& points) const { + if (points.size() < 3) return false; + + const Vec2f& first = points.front(); + const Vec2f& last = points.back(); + + // 最初と最後の点が十分近ければ閉じたループとみなす + // Consider it a closed loop if first and last points are close enough + constexpr float threshold = 0.1f; + float dx = first.x() - last.x(); + float dy = first.y() - last.y(); + float dist_sq = dx * dx + dy * dy; + + if (dist_sq < threshold * threshold) { + // 閉じたループの場合、最後の重複点を除去 + // Remove duplicate last point for closed loops + points.pop_back(); + return true; + } + + return false; + } + public: Spline2D() = default; - explicit Spline2D(const std::vector& points) { + explicit Spline2D(const std::vector& points, bool is_closed = false) { #ifdef PAXS_USING_SIV3D s3d::Array siv3d_points; for (const auto& p : points) { @@ -65,10 +92,31 @@ namespace paxg { spline = s3d::Spline2D(siv3d_points); #else points_ = points; + // 閉じたループを自動判定し、重複する最後の点を除去 + // Auto-detect closed loop and remove duplicate last point + if (!is_closed) { + is_closed_ = detectAndFixClosedLoop(points_); + } else { + is_closed_ = true; + // 明示的に閉じたループが指定された場合も重複チェック + // Also check for duplicates when explicitly specified as closed + if (points_.size() >= 2) { + const Vec2f& first = points_.front(); + const Vec2f& last = points_.back(); + constexpr float threshold = 0.1f; + float dx = first.x() - last.x(); + float dy = first.y() - last.y(); + float dist_sq = dx * dx + dy * dy; + + if (dist_sq < threshold * threshold) { + points_.pop_back(); + } + } + } #endif } - explicit Spline2D(const std::vector& points) { + explicit Spline2D(const std::vector& points, bool is_closed = false) { #ifdef PAXS_USING_SIV3D s3d::Array siv3d_points; for (const auto& p : points) { @@ -79,6 +127,28 @@ namespace paxg { for (const auto& p : points) { points_.emplace_back(static_cast(p.x()), static_cast(p.y())); } + + // 閉じたループを自動判定し、重複する最後の点を除去 + // Auto-detect closed loop and remove duplicate last point + if (!is_closed) { + is_closed_ = detectAndFixClosedLoop(points_); + } else { + is_closed_ = true; + // 明示的に閉じたループが指定された場合も重複チェック + // Also check for duplicates when explicitly specified as closed + if (points_.size() >= 2) { + const Vec2f& first = points_.front(); + const Vec2f& last = points_.back(); + constexpr float threshold = 0.1f; + float dx = first.x() - last.x(); + float dy = first.y() - last.y(); + float dist_sq = dx * dx + dy * dy; + + if (dist_sq < threshold * threshold) { + points_.pop_back(); + } + } + } #endif } @@ -89,12 +159,32 @@ namespace paxg { if (points_.size() < 2) return; const int divisions = 20; - - for (std::size_t i = 0; i < points_.size() - 1; ++i) { - Vec2f p0 = (i == 0) ? points_[0] : points_[i - 1]; - Vec2f p1 = points_[i]; - Vec2f p2 = points_[i + 1]; - Vec2f p3 = (i + 2 >= points_.size()) ? points_[i + 1] : points_[i + 2]; + const std::size_t point_count = points_.size(); + + // 閉じたループの場合、最後の点から最初の点への繋ぎも描画 + // For closed loops, also draw the connection from the last point to the first + const std::size_t loop_count = is_closed_ ? point_count : (point_count - 1); + + for (std::size_t i = 0; i < loop_count; ++i) { + // 閉じたループの場合、インデックスを循環させる + // For closed loops, wrap indices around + Vec2f p0, p1, p2, p3; + + if (is_closed_) { + // 閉じたループ:全ての点でモジュロ演算を使用 + // Closed loop: use modulo for all point indices + p0 = points_[(i + point_count - 1) % point_count]; + p1 = points_[i]; + p2 = points_[(i + 1) % point_count]; + p3 = points_[(i + 2) % point_count]; + } else { + // 開いた曲線:既存のロジック + // Open curve: existing logic + p0 = (i == 0) ? points_[0] : points_[i - 1]; + p1 = points_[i]; + p2 = points_[i + 1]; + p3 = (i + 2 >= point_count) ? points_[i + 1] : points_[i + 2]; + } Vec2f current_pos = p1; @@ -102,7 +192,6 @@ namespace paxg { float t = static_cast(j) / divisions; Vec2f next_pos = calculateCatmullRom(p0, p1, p2, p3, t); - // 修正: Lineのコンストラクタに float x, y を明示的に渡すことで曖昧さを回避 paxg::Line(current_pos.x(), current_pos.y(), next_pos.x(), next_pos.y()).draw(thickness, color); current_pos = next_pos; diff --git a/Library/PAX_GRAPHICA/ToggleButton.hpp b/Library/PAX_GRAPHICA/ToggleButton.hpp new file mode 100644 index 000000000..f0f63d077 --- /dev/null +++ b/Library/PAX_GRAPHICA/ToggleButton.hpp @@ -0,0 +1,336 @@ +/*########################################################################################## + + PAX SAPIENTICA Library 💀🌿🌏 + + [Planning] 2023-2024 As Project + [Production] 2023-2024 As Project + [Contact Us] wanotaitei@gmail.com https://github.com/AsPJT/PAX_SAPIENTICA + [License] Distributed under the CC0 1.0. https://creativecommons.org/publicdomain/zero/1.0/ + +##########################################################################################*/ + +#ifndef PAX_GRAPHICA_TOGGLE_BUTTON_HPP +#define PAX_GRAPHICA_TOGGLE_BUTTON_HPP + +#if defined(PAXS_USING_SIV3D) +#include +#elif defined(PAXS_USING_DXLIB) +#include +#elif defined(PAXS_USING_SFML) +#include +#endif + +#include +#include + +#include +#include + +namespace paxg { + + /// @brief Toggle button component with sliding knob (inspired by CSS switch design) + /// @brief スライドするつまみ付きトグルボタンコンポーネント(CSSスイッチデザインに触発) + /// + /// Features / 機能: + /// - Rounded rectangle background (pill shape) / 角丸長方形の背景(ピル型) + /// - Sliding circular knob / スライドする円形のつまみ + /// - Smooth animation / スムーズなアニメーション + /// - Color changes based on state (green=ON, gray=OFF) / 状態に応じた色の変化(緑=ON、灰色=OFF) + /// - Shadow effect on knob / つまみに影効果 + class ToggleButton { + private: + float x_{}, y_{}; // Position (位置) + float width_{50.0f}; // Width (幅) + float height_{30.0f}; // Height (高さ) + bool is_on_{false}; // Toggle state (トグル状態) + + // Colors (色) + static constexpr paxg::Color color_on_{0, 222, 0}; // Green when ON #00de00 (ONの時の緑) + static constexpr paxg::Color color_off_{255, 255, 255}; // White when OFF (OFFの時の白色) + static constexpr paxg::Color color_knob_{255, 255, 255}; // White knob (白いつまみ) + static constexpr paxg::Color color_shadow_{187, 187, 187};// Shadow color (#bbbbbb) (影の色) + + // Animation (アニメーション) + float knob_position_{0.0f}; // 0.0 (left) to 1.0 (right) + static constexpr float animation_speed_{0.2f}; // Smooth transition speed + + public: + /// @brief Default constructor + /// @brief デフォルトコンストラクタ + ToggleButton() = default; + + /// @brief Constructor with position + /// @brief 位置を指定するコンストラクタ + /// @param x X position (X座標) + /// @param y Y position (Y座標) + constexpr ToggleButton(float x, float y) : x_(x), y_(y) {} + + /// @brief Constructor with position and size + /// @brief 位置とサイズを指定するコンストラクタ + /// @param x X position (X座標) + /// @param y Y position (Y座標) + /// @param width Width (幅) + /// @param height Height (高さ) + constexpr ToggleButton(float x, float y, float width, float height) + : x_(x), y_(y), width_(width), height_(height) {} + + /// @brief Set position + /// @brief 位置を設定 + /// @param x X position (X座標) + /// @param y Y position (Y座標) + void setPosition(float x, float y) { + x_ = x; + y_ = y; + } + + /// @brief Set size + /// @brief サイズを設定 + /// @param width Width (幅) + /// @param height Height (高さ) + void setSize(float width, float height) { + width_ = width; + height_ = height; + } + + /// @brief Get toggle state + /// @brief トグル状態を取得 + /// @return true if ON, false if OFF (ONの場合true、OFFの場合false) + bool isOn() const { + return is_on_; + } + + /// @brief Set toggle state + /// @brief トグル状態を設定 + /// @param on New state (新しい状態) + void setOn(bool on) { + is_on_ = on; + // Immediately update knob position to match state (no animation on manual set) + knob_position_ = on ? 1.0f : 0.0f; + } + + /// @brief Toggle the state (ON→OFF or OFF→ON) + /// @brief 状態を切り替え(ON→OFFまたはOFF→ON) + void toggle() { + is_on_ = !is_on_; + } + + /// @brief Update button state (call every frame in main loop) + /// @brief ボタンの状態を更新(メインループで毎フレーム呼び出す) + /// @details Handles animation and click detection / アニメーションとクリック判定を処理 + void update() { + // Animate knob position (smooth transition) + const float target_position = is_on_ ? 1.0f : 0.0f; + if (knob_position_ < target_position) { + knob_position_ += animation_speed_; + if (knob_position_ > target_position) knob_position_ = target_position; + } else if (knob_position_ > target_position) { + knob_position_ -= animation_speed_; + if (knob_position_ < target_position) knob_position_ = target_position; + } + } + + /// @brief Get interpolated background color based on knob position + /// @brief つまみの位置に基づいて補間された背景色を取得 + /// @return Interpolated color between OFF and ON / OFFとONの間で補間された色 + paxg::Color getInterpolatedColor() const { + // Linear interpolation between color_off_ (white: 255,255,255) and color_on_ (green: 0,222,0) + const unsigned char r = static_cast( + 255 + (0 - 255) * knob_position_ + ); + const unsigned char g = static_cast( + 255 + (222 - 255) * knob_position_ + ); + const unsigned char b = static_cast( + 255 + (0 - 255) * knob_position_ + ); + return paxg::Color{r, g, b}; + } + + /// @brief Draw the toggle button + /// @brief トグルボタンを描画 + /// @details Draws background, knob, and shadow / 背景、つまみ、影を描画 + void draw() const { + // Background: two circles + center rectangle for pill shape + const float radius = height_ / 2.0f; + const paxg::Color bg_color = getInterpolatedColor(); + +#if defined(PAXS_USING_SIV3D) + // Left circle + s3d::Circle(x_ + radius, y_ + radius, radius).draw(bg_color.color); + + // Center rectangle + s3d::Rect(x_ + radius, y_, width_ - height_, height_).draw(bg_color.color); + + // Right circle + s3d::Circle(x_ + width_ - radius, y_ + radius, radius).draw(bg_color.color); + + // Knob + const float knob_radius = height_ * 0.4f; + const float knob_x = x_ + radius + (width_ - height_) * knob_position_; + const float knob_y = y_ + radius; + + // Shadow + s3d::Circle(knob_x + 2, knob_y + 2, knob_radius).draw(s3d::ColorF(0, 0, 0, 0.2)); + + // Knob circle + s3d::Circle(knob_x, knob_y, knob_radius).draw(color_knob_.color); + +#elif defined(PAXS_USING_DXLIB) + // Get RGB values from bg_color for DxLib + const unsigned char bg_r = static_cast(255 + (0 - 255) * knob_position_); + const unsigned char bg_g = static_cast(255 + (222 - 255) * knob_position_); + const unsigned char bg_b = static_cast(255 + (0 - 255) * knob_position_); + + // Left circle + DxLib::DrawCircle( + static_cast(x_ + radius), + static_cast(y_ + radius), + static_cast(radius), + DxLib::GetColor(bg_r, bg_g, bg_b), + TRUE + ); + + // Center rectangle + DxLib::DrawBox( + static_cast(x_ + radius), + static_cast(y_), + static_cast(x_ + width_ - radius), + static_cast(y_ + height_), + DxLib::GetColor(bg_r, bg_g, bg_b), + TRUE + ); + + // Right circle + DxLib::DrawCircle( + static_cast(x_ + width_ - radius), + static_cast(y_ + radius), + static_cast(radius), + DxLib::GetColor(bg_r, bg_g, bg_b), + TRUE + ); + + // Border for mostly OFF state + if (knob_position_ < 0.5f) { + const unsigned char border_alpha = static_cast(214 * (1.0f - knob_position_ * 2.0f)); + // Left circle border + DxLib::DrawCircle( + static_cast(x_ + radius), + static_cast(y_ + radius), + static_cast(radius), + DxLib::GetColor(border_alpha, border_alpha, border_alpha), + FALSE + ); + // Right circle border + DxLib::DrawCircle( + static_cast(x_ + width_ - radius), + static_cast(y_ + radius), + static_cast(radius), + DxLib::GetColor(border_alpha, border_alpha, border_alpha), + FALSE + ); + // Top and bottom borders + DxLib::DrawLine( + static_cast(x_ + radius), + static_cast(y_), + static_cast(x_ + width_ - radius), + static_cast(y_), + DxLib::GetColor(border_alpha, border_alpha, border_alpha) + ); + DxLib::DrawLine( + static_cast(x_ + radius), + static_cast(y_ + height_), + static_cast(x_ + width_ - radius), + static_cast(y_ + height_), + DxLib::GetColor(border_alpha, border_alpha, border_alpha) + ); + } + + // Knob + const float knob_radius = height_ * 0.4f; + const float knob_x = x_ + radius + (width_ - height_) * knob_position_; + const float knob_y = y_ + radius; + + // Shadow + DxLib::SetDrawBlendMode(DX_BLENDMODE_ALPHA, 51); + DxLib::DrawCircle( + static_cast(knob_x + 2), + static_cast(knob_y + 2), + static_cast(knob_radius), + DxLib::GetColor(187, 187, 187), + TRUE + ); + DxLib::SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0); + + // Knob circle + DxLib::DrawCircle( + static_cast(knob_x), + static_cast(knob_y), + static_cast(knob_radius), + DxLib::GetColor(255, 255, 255), + TRUE + ); + +#elif defined(PAXS_USING_SFML) + // Left circle + sf::CircleShape left_circle(radius); + left_circle.setOrigin(sf::Vector2f(radius, radius)); + left_circle.setPosition(sf::Vector2f(x_ + radius, y_ + radius)); + left_circle.setFillColor(bg_color.color); + paxg::Window::window().draw(left_circle); + + // Center rectangle + sf::RectangleShape center_rect(sf::Vector2f(width_ - height_, height_)); + center_rect.setPosition(sf::Vector2f(x_ + radius, y_)); + center_rect.setFillColor(bg_color.color); + paxg::Window::window().draw(center_rect); + + // Right circle + sf::CircleShape right_circle(radius); + right_circle.setOrigin(sf::Vector2f(radius, radius)); + right_circle.setPosition(sf::Vector2f(x_ + width_ - radius, y_ + radius)); + right_circle.setFillColor(bg_color.color); + paxg::Window::window().draw(right_circle); + + // Knob + const float knob_radius = height_ * 0.4f; + const float knob_x = x_ + radius + (width_ - height_) * knob_position_; + const float knob_y = y_ + radius; + + // Shadow + sf::CircleShape shadow(knob_radius); + shadow.setOrigin(sf::Vector2f(knob_radius, knob_radius)); + shadow.setPosition(sf::Vector2f(knob_x + 2, knob_y + 2)); + shadow.setFillColor(sf::Color(0, 0, 0, 51)); + paxg::Window::window().draw(shadow); + + // Knob circle + sf::CircleShape knob(knob_radius); + knob.setOrigin(sf::Vector2f(knob_radius, knob_radius)); + knob.setPosition(sf::Vector2f(knob_x, knob_y)); + knob.setFillColor(color_knob_.color); + paxg::Window::window().draw(knob); +#else + (void)radius; + (void)bg_color; +#endif + } + + /// @brief Get position + paxs::Vector2 getPosition() const { + return paxs::Vector2{x_, y_}; + } + + /// @brief Get size + paxs::Vector2 getSize() const { + return paxs::Vector2{width_, height_}; + } + + /// @brief Get bounding rectangle + paxs::Rect getRect() const { + return paxs::Rect{x_, y_, width_, height_}; + } + }; + +} // namespace paxg + +#endif // !PAX_GRAPHICA_TOGGLE_BUTTON_HPP diff --git a/Library/PAX_GRAPHICA/Triangle.hpp b/Library/PAX_GRAPHICA/Triangle.hpp index b37bfd00a..f5398a1cc 100644 --- a/Library/PAX_GRAPHICA/Triangle.hpp +++ b/Library/PAX_GRAPHICA/Triangle.hpp @@ -120,7 +120,7 @@ namespace paxg { #if defined(PAXS_USING_SIV3D) static constexpr float circumRadiusToSiv3dSides(const float circumRadius) { // Siv3Dは sides を受け取って内部で ×(1/√3) してるので、逆にこっちは ×√3 する - return circumRadius * 1.7320508075688772f; // std::sqrt(3.0f) の定数化 + return circumRadius * paxs::Math::sqrt3(); } #endif diff --git a/Library/PAX_MAHOROBA/Map/Content/Feature/FeatureType.hpp b/Library/PAX_MAHOROBA/Map/Content/Feature/FeatureType.hpp index 9681e7880..69e9fe1f2 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Feature/FeatureType.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Feature/FeatureType.hpp @@ -26,6 +26,7 @@ enum class FeatureType : std::uint_least8_t { Settlement, ///< 集落 / Settlement Model3D, ///< 3Dモデル(古墳の石室など) / 3D model (burial chamber, etc.) Territory, ///< 領域(国や文化圏などのスプライン曲線) / Territory (spline curves for countries, cultural areas, etc.) + FlowCurve, ///< フロー曲線(移動や交易などを示すスプライン曲線) / Flow curve (spline curves for movement, trade, etc.) }; } // namespace paxs diff --git a/Library/PAX_MAHOROBA/Map/Content/Feature/FlowCurveFeature.hpp b/Library/PAX_MAHOROBA/Map/Content/Feature/FlowCurveFeature.hpp new file mode 100644 index 000000000..a6d38bf1a --- /dev/null +++ b/Library/PAX_MAHOROBA/Map/Content/Feature/FlowCurveFeature.hpp @@ -0,0 +1,532 @@ +/*########################################################################################## + + PAX SAPIENTICA Library 💀🌿🌏 + + [Planning] 2023-2024 As Project + [Production] 2023-2024 As Project + [Contact Us] wanotaitei@gmail.com https://github.com/AsPJT/PAX_SAPIENTICA + [License] Distributed under the CC0 1.0. https://creativecommons.org/publicdomain/zero/1.0/ + +##########################################################################################*/ + +#ifndef PAX_MAHOROBA_FLOW_CURVE_FEATURE_HPP +#define PAX_MAHOROBA_FLOW_CURVE_FEATURE_HPP + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace paxs { + +/// @brief フロー曲線(移動や交易などを示すスプライン曲線)を表す地物クラス +/// @brief Feature class representing a flow curve (spline curves for movement, trade, etc.) +class FlowCurveFeature : public MapFeature, public ISpatiallyUpdatable { +public: + /// @param data フロー曲線の位置データ / Flow curve location data + /// @param group_data フロー曲線グループデータ / Flow curve group data + FlowCurveFeature(const FlowCurveLocationData& data, const FlowCurveLocationGroup& group_data) + : data_(data) + , group_data_(group_data) + , cached_screen_points_() + , color_(0, 0, 0, 255) // デフォルト色(黒) / Default color (black) + , animation_offset_(0.0f) + , animation_speed_(0.002f) // アニメーション速度を遅く / Slower animation speed + { + // 色情報からRGBAを取得(将来的にはAppConfigから色マッピングを読み込む) + // TODO: 色情報の管理システムを実装 + } + + FeatureType getType() const override { + return FeatureType::FlowCurve; + } + + std::uint_least32_t getId() const override { + if (!data_.key.empty()) { + return MurMur3::calcHash(data_.key.c_str()); + } + return 0; + } + + std::string getName() const override { + return data_.key; + } + + std::uint_least32_t getFeatureTypeHash() const override { + return data_.feature_type_hash; + } + + /// @brief アニメーションの更新 + /// @brief Update animation + void updateAnimation() const { // constにして、mutableなメンバーを更新 + // アニメーションオフセットを更新 + // Update animation offset + animation_offset_ += animation_speed_; + + // オフセットが1.0を超えたらリセット(ループアニメーション) + // Reset when offset exceeds 1.0 (loop animation) + if (animation_offset_ >= 1.0f) { + animation_offset_ -= 1.0f; + } + } + + /// @brief 空間的更新(ISpatiallyUpdatableの実装) + /// @brief Spatial update (ISpatiallyUpdatable implementation) + void updateSpatial(const SpatialContext& context) override { + // 地物種別の可視性チェック(最優先) + if ((context.visibility_manager != nullptr) && !context.visibility_manager->isVisible(data_.feature_type_hash)) { + cached_screen_points_.clear(); + visible_ = false; + return; + } + + // データが空の場合は早期リターン + if (data_.coordinates.empty()) { + cached_screen_points_.clear(); + visible_ = false; + return; + } + + // フロー曲線のバウンディングボックスを計算(最小・最大経度/緯度) + // Calculate bounding box of the flow curve (min/max longitude/latitude) + double min_lon = data_.coordinates[0].x; + double max_lon = data_.coordinates[0].x; + double min_lat = data_.coordinates[0].y; + double max_lat = data_.coordinates[0].y; + + for (const auto& coord : data_.coordinates) { + min_lon = (std::min)(min_lon, coord.x); + max_lon = (std::max)(max_lon, coord.x); + min_lat = (std::min)(min_lat, coord.y); + max_lat = (std::max)(max_lat, coord.y); + } + + // 日付変更線をまたぐかチェック(経度差が180°以上) + // Check if flow curve crosses date line (longitude difference >= 180°) + const bool crosses_dateline = (max_lon - min_lon) > 180.0; + + // ±3°のマージンを追加 + // Add ±3° margin + constexpr double bbox_margin = 3.0; + min_lon -= bbox_margin; + max_lon += bbox_margin; + min_lat -= bbox_margin; + max_lat += bbox_margin; + + // 画面の範囲を計算 + // Calculate screen bounds + const double view_half_width = context.map_view_size.x / 2.0; + const double view_half_height = context.map_view_size.y / 2.0; + const double view_min_lon = context.map_view_center.x - view_half_width; + const double view_max_lon = context.map_view_center.x + view_half_width; + const double view_min_lat = context.map_view_center.y - view_half_height; + const double view_max_lat = context.map_view_center.y + view_half_height; + + // 経度ラップ処理:3つのオフセット候補(-360°, 0°, +360°)で試す + // Longitude wrapping: try 3 offset candidates (-360°, 0°, +360°) + int best_offset_mult = 0; + bool found_overlap = false; + + for (int offset_mult = -1; offset_mult <= 1; ++offset_mult) { + const double offset_min_lon = min_lon + (offset_mult * 360.0); + const double offset_max_lon = max_lon + (offset_mult * 360.0); + + // バウンディングボックスが画面範囲と重なるかチェック + // Check if bounding box overlaps with screen bounds + const bool lon_overlap = (offset_min_lon <= view_max_lon) && (offset_max_lon >= view_min_lon); + const bool lat_overlap = (min_lat <= view_max_lat) && (max_lat >= view_min_lat); + + if (lon_overlap && lat_overlap) { + best_offset_mult = offset_mult; + found_overlap = true; + break; // 最初に見つかったオフセットを使用 + } + } + + // 画面範囲と重ならない場合は非表示 + // Hide if no overlap with screen bounds + if (!found_overlap) { + cached_screen_points_.clear(); + visible_ = false; + return; + } + + // 最適なオフセットを適用した後、日付変更線をまたぐ場合の経度正規化 + // Apply best offset, then normalize longitude for date line wrapping + std::vector normalized_coords; + normalized_coords.reserve(data_.coordinates.size()); + + // 最初の点にオフセットを適用 + const double first_x = data_.coordinates[0].x + (best_offset_mult * 360.0); + normalized_coords.push_back(paxs::WebMercatorDeg(paxs::Vector2(first_x, data_.coordinates[0].y))); + + // 2点目以降は、日付変更線をまたぐ場合のみ正規化 + // For subsequent points, normalize only if crossing date line + if (crosses_dateline) { + // 日付変更線をまたぐ場合:前の点との経度差が180°を超えないように正規化 + // Date line crossing: normalize so longitude difference from previous point doesn't exceed 180° + for (size_t i = 1; i < data_.coordinates.size(); ++i) { + const double prev_lon = normalized_coords[i - 1].x; + double current_lon = data_.coordinates[i].x + (best_offset_mult * 360.0); + + // 経度差を計算 + double lon_diff = current_lon - prev_lon; + + // 差が180°を超える場合、短い方の経路を選択 + // If difference exceeds 180°, choose shorter path + if (lon_diff > 180.0) { + current_lon -= 360.0; + } else if (lon_diff < -180.0) { + current_lon += 360.0; + } + + normalized_coords.push_back(paxs::WebMercatorDeg( + paxs::Vector2(current_lon, data_.coordinates[i].y) + )); + } + } else { + // 日付変更線をまたがない場合:そのままオフセットを適用 + // Non-crossing: simply apply offset + for (size_t i = 1; i < data_.coordinates.size(); ++i) { + const double current_lon = data_.coordinates[i].x + (best_offset_mult * 360.0); + normalized_coords.push_back(paxs::WebMercatorDeg( + paxs::Vector2(current_lon, data_.coordinates[i].y) + )); + } + } + + // 正規化された座標をスクリーン座標に変換 + // Convert normalized coordinates to screen coordinates + cached_screen_points_.clear(); + cached_screen_points_.reserve(normalized_coords.size()); + + for (const auto& coord : normalized_coords) { + // スクリーン座標に変換 + const paxg::Vec2 screen_pos = MapCoordinateConverter::toScreenPos( + Vector2(coord.x, coord.y), + context.map_view_size, + context.map_view_center + ); + + cached_screen_points_.emplace_back( + static_cast(screen_pos.x()), + static_cast(screen_pos.y()) + ); + } + + visible_ = (cached_screen_points_.size() >= 2); + } + + bool isVisible() const override { + return visible_; + } + + bool isInTimeRange(double jdn) const override { + return data_.year_range.contains(jdn); + } + + const WrappedScreenPositions& getScreenPositions() const override { + // FlowCurveは複数点を持つため、WrappedScreenPositionsは使用しない + // Use empty positions since flow curves use multiple points + static const WrappedScreenPositions empty_positions; + return empty_positions; + } + + int getDisplaySize() const override { + return 0; // FlowCurveは点ではなく線なのでサイズは0 + } + + bool isHit(const paxs::Vector2& mouse_pos) const override { + // フロー曲線のクリック判定は実装しない(将来的には線の近傍判定を実装可能) + // Click detection for flow curves is not implemented (can be added in the future) + (void)mouse_pos; + return false; + } + + void onClick(const ClickContext& context) override { + // クリック時の処理(デバッグ出力) + std::cout << "FlowCurve clicked: " << getName() << std::endl; + (void)context; + } + + /// @brief スプライン曲線+矢印を描画(アニメーション付き) + /// @brief Draw spline curve with animated arrow + void renderSpline() const { + if (!visible_ || cached_screen_points_.size() < 2) { + return; + } + + // アニメーションを更新(毎フレーム呼び出す) + // Update animation (called every frame) + updateAnimation(); + + // グループデータから線幅を取得 + const float line_width = group_data_.line_width; + + // 背景として全体のスプライン曲線を薄く描画 + // Draw full spline curve as background (lighter color) + paxg::Color bg_color(80, 80, 80, 80); // グレー、透明度を下げる / Gray with reduced opacity + paxg::Spline2D(cached_screen_points_).draw(line_width * 0.5f, bg_color); + + // 複数のアニメーションする移動曲線を描画(7本の流路) + // Draw multiple animated moving curve segments (7 flow paths) + // 各線の開始位置と開始時間をずらしてバラつきを出す + // Stagger start position and timing for each line to create variation + + // 流路定義:{lateral_offset, time_offset, start_position_offset} + // Flow path definition: {lateral_offset, time_offset, start_position_offset} + struct FlowPath { + float lateral_offset; // 左右のオフセット(ピクセル) / Lateral offset in pixels + float time_offset; // 時間オフセット(0.0~1.0) / Time offset (0.0-1.0) + float start_pos_offset; // 開始位置オフセット(0.0~1.0) / Start position offset (0.0-1.0) + }; + + constexpr FlowPath flow_paths[] = { + {-6.0f, 0.0f, 0.0f}, // 左外側 / Left outer + {-4.0f, 0.14f * 1.5, 0.4f}, // 左中外 / Left mid-outer + {-2.0f, 0.28f * 1.5, 0.8f}, // 左中内 / Left mid-inner + { 0.0f, 0.42f * 1.5, 0.2f}, // 中央 / Center + { 2.0f, 0.56f * 1.5, 0.5f}, // 右中内 / Right mid-inner + { 4.0f, 0.70f * 1.5, 0.9f}, // 右中外 / Right mid-outer + { 6.0f, 0.84f * 1.5, 0.5f} // 右外側 / Right outer + }; + + constexpr int num_segments_per_line = 4; // 各流路あたりの動く曲線の本数 / Number of moving segments per flow path + + for (const auto& path : flow_paths) { + for (int i = 0; i < num_segments_per_line; ++i) { + // 各線内での位相オフセット + // Phase offset within each line + const float phase_offset = static_cast(i) / num_segments_per_line; + + // 時間オフセットを加算してバラつきを出す + // Add time offset for variation + const float total_offset = phase_offset + path.time_offset; + + drawAnimatedSegment(line_width, total_offset, path.lateral_offset, path.start_pos_offset); + } + } + } + + const FlowCurveLocationData& getData() const { + return data_; + } + + const FlowCurveLocationGroup& getGroupData() const { + return group_data_; + } + + const std::vector& getScreenPoints() const { + return cached_screen_points_; + } + +private: + FlowCurveLocationData data_; ///< フロー曲線位置データ / Flow curve location data + FlowCurveLocationGroup group_data_; ///< フロー曲線グループデータ / Flow curve group data + + std::vector cached_screen_points_; ///< キャッシュされたスクリーン座標列 / Cached screen points + paxg::Color color_; ///< 描画色 / Drawing color + bool visible_{true}; ///< 可視性 / Visibility + + mutable float animation_offset_; ///< アニメーションオフセット (0.0 ~ 1.0) / Animation offset + float animation_speed_; ///< アニメーション速度 / Animation speed + + /// @brief Catmull-Romスプライン補間 + /// @brief Catmull-Rom spline interpolation + static paxg::Vec2f calculateCatmullRom(const paxg::Vec2f& p0, const paxg::Vec2f& p1, + const paxg::Vec2f& p2, const paxg::Vec2f& p3, float t) { + float t2 = t * t; + float t3 = t2 * t; + + float v0 = (p2.x() - p0.x()) * 0.5f; + float v1 = (p3.x() - p1.x()) * 0.5f; + float x = (2 * p1.x() - 2 * p2.x() + v0 + v1) * t3 + (-3 * p1.x() + 3 * p2.x() - 2 * v0 - v1) * t2 + v0 * t + p1.x(); + + v0 = (p2.y() - p0.y()) * 0.5f; + v1 = (p3.y() - p1.y()) * 0.5f; + float y = (2 * p1.y() - 2 * p2.y() + v0 + v1) * t3 + (-3 * p1.y() + 3 * p2.y() - 2 * v0 - v1) * t2 + v0 * t + p1.y(); + + return paxg::Vec2f(x, y); + } + + /// @brief アニメーションする線分を描画 + /// @brief Draw animated curve segment + /// @param line_width 線幅 / Line width + /// @param phase_offset 位相オフセット (0.0 ~ 1.0) / Phase offset + /// @param fixed_lateral_offset 固定の左右オフセット(ピクセル単位) / Fixed lateral offset in pixels + /// @param start_position_offset 開始位置オフセット (0.0 ~ 1.0) / Start position offset + void drawAnimatedSegment(float line_width, float phase_offset = 0.0f, float fixed_lateral_offset = 0.0f, float start_position_offset = 0.0f) const { + if (cached_screen_points_.size() < 2) return; + + // 移動する線分の長さ(始点から約3点分の長さ) + // Length of moving segment (approximately 3 points from start) + constexpr float segment_length = 3.0f; + + const std::size_t point_count = cached_screen_points_.size(); + + // スプライン曲線全体の長さに基づいて、アニメーションオフセットを適用 + // Apply animation offset based on total spline curve length + const float total_segments = static_cast(point_count - 1); + + // 時間オフセットと開始位置オフセットを組み合わせる + // Combine time offset and start position offset + float animated_position = (animation_offset_ + phase_offset); + + // 位相オフセットが1.0を超える場合、0.0~1.0に正規化 + // Normalize phase offset to 0.0-1.0 range + while (animated_position >= 1.0f) { + animated_position -= 1.0f; + } + + // 開始位置オフセットを追加(各線の開始位置をずらす) + // Add start position offset (stagger start position for each line) + animated_position = animated_position + start_position_offset; + if (animated_position >= 1.0f) { + animated_position -= 1.0f; + } + + animated_position *= total_segments; + + // 移動する線分の開始点と終了点を計算 + // Calculate start and end points of moving segment + const float start_pos = animated_position; + const float end_pos = animated_position + segment_length; + + // 線分を構成する点を収集 + // Collect points for the segment + std::vector segment_points; + segment_points.reserve(static_cast(segment_length * 20 + 2)); + + // スプライン曲線上の点を補間して収集 + // Interpolate and collect points on spline curve + const int divisions = 20; // 各セグメント間の分割数 / Divisions per segment + + for (std::size_t i = 0; i < point_count - 1; ++i) { + const float segment_start = static_cast(i); + const float segment_end = static_cast(i + 1); + + // このセグメントが移動線分の範囲内にあるかチェック + // Check if this segment is within the moving segment range + if (segment_end < start_pos || segment_start > end_pos) { + continue; // 範囲外なのでスキップ / Skip if outside range + } + + // Catmull-Rom補間用の制御点を取得 + // Get control points for Catmull-Rom interpolation + paxg::Vec2f p0 = (i == 0) ? cached_screen_points_[0] : cached_screen_points_[i - 1]; + paxg::Vec2f p1 = cached_screen_points_[i]; + paxg::Vec2f p2 = cached_screen_points_[i + 1]; + paxg::Vec2f p3 = (i + 2 >= point_count) ? cached_screen_points_[i + 1] : cached_screen_points_[i + 2]; + + // このセグメント内で補間する範囲を計算 + // Calculate interpolation range within this segment + const float local_start = (std::max)(0.0f, start_pos - segment_start); + const float local_end = (std::min)(1.0f, end_pos - segment_start); + + const int start_div = static_cast(local_start * divisions); + const int end_div = static_cast(local_end * divisions); + + for (int j = start_div; j <= end_div; ++j) { + float t = static_cast(j) / divisions; + paxg::Vec2f point = calculateCatmullRom(p0, p1, p2, p3, t); + + // 固定の左右オフセットを適用 + // Apply fixed lateral offset + if (std::abs(fixed_lateral_offset) > 0.01f) { + // 曲線の向き(接線方向)を計算 + // Calculate tangent direction + paxg::Vec2f tangent = calculateTangent(p0, p1, p2, p3, t); + + // 接線に垂直な方向を計算(右向き) + // Calculate perpendicular direction (rightward) + paxg::Vec2f perpendicular(-tangent.y(), tangent.x()); + + // 垂直方向にオフセットを適用 + // Apply perpendicular offset + point = paxg::Vec2f( + point.x() + perpendicular.x() * fixed_lateral_offset, + point.y() + perpendicular.y() * fixed_lateral_offset + ); + } + + segment_points.push_back(point); + } + } + + // 収集した点で線分を描画 + // Draw segment with collected points + if (segment_points.size() >= 2) { + // 透明度を位相オフセットに基づいて調整 + // Adjust opacity based on phase offset + const float alpha_factor = 1.0f - (phase_offset * 0.4f); // 0.6 ~ 1.0 + const int alpha = static_cast(180 * alpha_factor + 50); // 50 ~ 230 + paxg::Color segment_color(0, 0, 0, alpha); // 黒色、透明度可変 / Black with variable opacity + + for (size_t i = 0; i < segment_points.size() - 1; ++i) { + paxg::Line(segment_points[i], segment_points[i + 1]).draw(line_width * 0.6f, segment_color); + } + + // 線分の終端に小さい矢印を描画(位相オフセットが小さく、中央付近の流路のみ) + // Draw small arrow at the end of segment (only for small phase offset and near-center paths) + if (segment_points.size() >= 2 && phase_offset < 0.3f && std::abs(fixed_lateral_offset) < 4.0f) { + const size_t last_idx = segment_points.size() - 1; + const paxg::Vec2f& prev = segment_points[last_idx - 1]; + const paxg::Vec2f& end = segment_points[last_idx]; + + const float arrow_length = line_width * 4.0f; + const float arrow_width = line_width * 2.5f; + + paxg::Line(prev, end).drawArrow(line_width * 0.6f, paxg::Vec2f(arrow_width, arrow_length), segment_color); + } + } + } + + /// @brief 接線ベクトルを計算(Catmull-Romスプラインの1次微分) + /// @brief Calculate tangent vector (first derivative of Catmull-Rom spline) + static paxg::Vec2f calculateTangent(const paxg::Vec2f& p0, const paxg::Vec2f& p1, + const paxg::Vec2f& p2, const paxg::Vec2f& p3, float t) { + const float t2 = t * t; + + // Catmull-Romスプラインの1次微分 + // First derivative of Catmull-Rom spline + const float v0_x = (p2.x() - p0.x()) * 0.5f; + const float v1_x = (p3.x() - p1.x()) * 0.5f; + const float dx = (2 * p1.x() - 2 * p2.x() + v0_x + v1_x) * 3.0f * t2 + + (-3 * p1.x() + 3 * p2.x() - 2 * v0_x - v1_x) * 2.0f * t + v0_x; + + const float v0_y = (p2.y() - p0.y()) * 0.5f; + const float v1_y = (p3.y() - p1.y()) * 0.5f; + const float dy = (2 * p1.y() - 2 * p2.y() + v0_y + v1_y) * 3.0f * t2 + + (-3 * p1.y() + 3 * p2.y() - 2 * v0_y - v1_y) * 2.0f * t + v0_y; + + // 正規化(単位ベクトル化) + // Normalize to unit vector + const float length = std::sqrt(dx * dx + dy * dy); + if (length > 0.0001f) { + return paxg::Vec2f(dx / length, dy / length); + } + return paxg::Vec2f(1.0f, 0.0f); // デフォルト方向 / Default direction + } +}; + +} // namespace paxs + +#endif // !PAX_MAHOROBA_FLOW_CURVE_FEATURE_HPP diff --git a/Library/PAX_MAHOROBA/Map/Content/Feature/GenomeFeature.hpp b/Library/PAX_MAHOROBA/Map/Content/Feature/GenomeFeature.hpp index e70fa2dcf..cc7673520 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Feature/GenomeFeature.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Feature/GenomeFeature.hpp @@ -37,8 +37,10 @@ namespace paxs { /// @brief ゲノム情報(人骨、mtDNA、Y-DNA等)を表す地物クラス /// @brief Feature class representing genome information (human bones, mtDNA, Y-DNA, etc.) -/// @details 空間更新とローカライゼーション更新が必要(時間更新は不要) -class GenomeFeature : public MapFeature, public ISpatiallyUpdatable, public ILocalizable { +class GenomeFeature : public MapFeature, + public ISpatiallyUpdatable, + public ITemporallyUpdatable, + public ILocalizable { private: static constexpr std::uint_least32_t place_info_domain_hash = MurMur3::calcHash("GenomeNames"); @@ -74,6 +76,23 @@ class GenomeFeature : public MapFeature, public ISpatiallyUpdatable, public ILoc return data_.feature_type_hash; } + /// @brief 時間的更新(ITemporallyUpdatableの実装) + /// @brief Temporal update (ITemporallyUpdatable implementation) + void updateTemporal(const TemporalContext& context) override { + const bool previous_in_time_range = in_time_range_; + in_time_range_ = isInTimeRange(context.jdn); + if (!in_time_range_) { + // 時間範囲外なら可視化しない + return; + } + if (previous_in_time_range == in_time_range_) { + // 可視性が変化した場合のみ更新 + return; + } + + updateSpatial(context.toSpatial()); + } + /// @brief 空間的更新(ISpatiallyUpdatableの実装) /// @brief Spatial update (ISpatiallyUpdatable implementation) void updateSpatial(const SpatialContext& context) override { @@ -197,6 +216,7 @@ class GenomeFeature : public MapFeature, public ISpatiallyUpdatable, public ILoc Vector2 cached_text_size_{0, 0}; ///< キャッシュされたテキストサイズ / Cached text size std::string cached_name_; ///< キャッシュされた名前 / Cached name bool visible_ = true; ///< 可視性 / Visibility + bool in_time_range_ = true; ///< 時間範囲内か / Whether within time range }; } // namespace paxs diff --git a/Library/PAX_MAHOROBA/Map/Content/Feature/GeographicFeature.hpp b/Library/PAX_MAHOROBA/Map/Content/Feature/GeographicFeature.hpp index c046e4e4a..49eab2496 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Feature/GeographicFeature.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Feature/GeographicFeature.hpp @@ -13,6 +13,7 @@ #define PAX_MAHOROBA_GEOGRAPHIC_FEATURE_HPP #include +#include #include #include @@ -37,14 +38,15 @@ namespace paxs { /// @brief 地理的地物(アイコン)を表す地物クラス /// @brief Feature class representing a geographic feature (icon) -/// @details 空間更新のみ必要(時間・ローカライゼーションは不要) -class GeographicFeature : public MapFeature, public ISpatiallyUpdatable { +class GeographicFeature : public MapFeature, + public ISpatiallyUpdatable, + public ITemporallyUpdatable { public: /// @brief コンストラクタ /// @brief Constructor /// @param data 地物の位置データ / Geographic feature location data - GeographicFeature(const LocationPoint& data) - : data_(data) { + GeographicFeature(LocationPoint data) + : data_(std::move(data)) { visible_ = true; } @@ -80,7 +82,22 @@ class GeographicFeature : public MapFeature, public ISpatiallyUpdatable { return data_.feature_type_hash; } - // ========== 状態管理 / State Management ========== + /// @brief 時間的更新(ITemporallyUpdatableの実装) + /// @brief Temporal update (ITemporallyUpdatable implementation) + void updateTemporal(const TemporalContext& context) override { + const bool previous_in_time_range = in_time_range_; + in_time_range_ = isInTimeRange(context.jdn); + if (!in_time_range_) { + // 時間範囲外なら可視化しない + return; + } + if (previous_in_time_range == in_time_range_) { + // 可視性が変化した場合のみ更新 + return; + } + + updateSpatial(context.toSpatial()); + } /// @brief 空間的更新(ISpatiallyUpdatableの実装) /// @brief Spatial update (ISpatiallyUpdatable implementation) @@ -193,6 +210,7 @@ class GeographicFeature : public MapFeature, public ISpatiallyUpdatable { int cached_display_size_ = 50; ///< 表示サイズ / Display size Vector2 cached_texture_size_{50, 50}; ///< キャッシュされたテクスチャサイズ / Cached texture size bool visible_ = true; ///< 可視性 / Visibility + bool in_time_range_ = true; ///< 時間範囲内か / Whether within time range }; } // namespace paxs diff --git a/Library/PAX_MAHOROBA/Map/Content/Feature/PersonFeature.hpp b/Library/PAX_MAHOROBA/Map/Content/Feature/PersonFeature.hpp index f42bcdac2..ab800fb8e 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Feature/PersonFeature.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Feature/PersonFeature.hpp @@ -13,6 +13,7 @@ #define PAX_MAHOROBA_PERSON_FEATURE_HPP #include +#include #include #include @@ -39,7 +40,6 @@ namespace paxs { /// @brief 人物(肖像画+名前)を表す地物クラス /// @brief Feature class representing a person (portrait + name) -/// @details 空間・時間・ローカライゼーション全ての更新が必要 class PersonFeature : public MapFeature, public ISpatiallyUpdatable, public ITemporallyUpdatable, @@ -47,10 +47,10 @@ class PersonFeature : public MapFeature, public: /// @param data 人物の位置データ / Person location data /// @param group_data 人物グループデータ / Person group data - PersonFeature(const PersonLocationPoint& data, const PersonLocationGroup& group_data) - : data_(data) - , group_data_(group_data) { - visible_ = true; + PersonFeature(PersonLocationPoint data, PersonLocationGroup group_data) + : data_(std::move(data)) + , group_data_(std::move(group_data)) { + } FeatureType getType() const override { @@ -83,12 +83,17 @@ class PersonFeature : public MapFeature, /// @brief 時間的更新(ITemporallyUpdatableの実装) /// @brief Temporal update (ITemporallyUpdatable implementation) void updateTemporal(const TemporalContext& context) override { + if (!isInTimeRange(context.jdn)) { + return; + } // 時間補間座標の計算 interpolated_pos_ = MapCoordinateConverter::interpolatePosition( data_.start_coordinate, data_.end_coordinate, context.jdn, data_.year_range.minimum, data_.year_range.maximum ); cached_jdn_ = context.jdn; + + updateSpatial(context.toSpatial()); } /// @brief 空間的更新(ISpatiallyUpdatableの実装) @@ -196,7 +201,7 @@ class PersonFeature : public MapFeature, } - MercatorDeg getInterpolatedPosition() const { + WebMercatorDeg getInterpolatedPosition() const { return interpolated_pos_; } @@ -214,7 +219,7 @@ class PersonFeature : public MapFeature, PersonLocationPoint data_; ///< 人物位置データ / Person location data PersonLocationGroup group_data_; ///< 人物グループデータ / Person group data - MercatorDeg interpolated_pos_; ///< 補間された座標 / Interpolated position + WebMercatorDeg interpolated_pos_; ///< 補間された座標 / Interpolated position WrappedScreenPositions cached_screen_positions_; ///< 経度ラップされたスクリーン座標 / Wrapped screen positions int cached_display_size_ = 60; ///< 表示サイズ / Display size Vector2 cached_texture_size_{60, 60}; ///< キャッシュされたテクスチャサイズ / Cached texture size diff --git a/Library/PAX_MAHOROBA/Map/Content/Feature/PlaceNameFeature.hpp b/Library/PAX_MAHOROBA/Map/Content/Feature/PlaceNameFeature.hpp index 88b727094..54728da13 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Feature/PlaceNameFeature.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Feature/PlaceNameFeature.hpp @@ -13,7 +13,7 @@ #define PAX_MAHOROBA_PLACE_NAME_FEATURE_HPP #include -#include +#include #include @@ -37,15 +37,17 @@ namespace paxs { /// @brief 地名(テキストのみ)を表す地物クラス /// @brief Feature class representing a place name (text only) -/// @details 空間更新とローカライゼーション更新が必要(時間更新は不要) -class PlaceNameFeature : public MapFeature, public ISpatiallyUpdatable, public ILocalizable { +class PlaceNameFeature : public MapFeature, + public ISpatiallyUpdatable, + public ITemporallyUpdatable, + public ILocalizable { private: static constexpr std::uint_least32_t place_names_domain_hash = MurMur3::calcHash("PlaceNames"); public: /// @param data 地名の位置データ / Place location data - PlaceNameFeature(const LocationPoint& data) - : data_(data) { + PlaceNameFeature(LocationPoint data) + : data_(std::move(data)) { } FeatureType getType() const override { @@ -75,7 +77,22 @@ class PlaceNameFeature : public MapFeature, public ISpatiallyUpdatable, public I return data_.feature_type_hash; } - // ========== 更新メソッド / Update Methods ========== + /// @brief 時間的更新(ITemporallyUpdatableの実装) + /// @brief Temporal update (ITemporallyUpdatable implementation) + void updateTemporal(const TemporalContext& context) override { + const bool previous_in_time_range = in_time_range_; + in_time_range_ = isInTimeRange(context.jdn); + if (!in_time_range_) { + // 時間範囲外なら可視化しない + return; + } + if (previous_in_time_range == in_time_range_) { + // 可視性が変化した場合のみ更新 + return; + } + + updateSpatial(context.toSpatial()); + } /// @brief 空間的更新(ISpatiallyUpdatableの実装) /// @brief Spatial update (ISpatiallyUpdatable implementation) @@ -175,6 +192,7 @@ class PlaceNameFeature : public MapFeature, public ISpatiallyUpdatable, public I Vector2 cached_text_size_{100, 20}; ///< キャッシュされたテキストサイズ / Cached text size std::string cached_name_; ///< キャッシュされた名前 / Cached name bool visible_ = true; ///< 可視性 / Visibility + bool in_time_range_ = true; ///< 時間範囲内か / Whether within time range }; } // namespace paxs diff --git a/Library/PAX_MAHOROBA/Map/Content/Feature/TerritoryFeature.hpp b/Library/PAX_MAHOROBA/Map/Content/Feature/TerritoryFeature.hpp index 3ed69f95d..fcf9f7019 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Feature/TerritoryFeature.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Feature/TerritoryFeature.hpp @@ -37,10 +37,7 @@ namespace paxs { /// @brief 領域(国や文化圏などのスプライン曲線)を表す地物クラス /// @brief Feature class representing a territory (spline curves for countries, cultural areas, etc.) -/// @details 空間・時間の更新が必要(ローカライゼーションはオプション) -class TerritoryFeature : public MapFeature, - public ISpatiallyUpdatable, - public ITemporallyUpdatable { +class TerritoryFeature : public MapFeature, public ISpatiallyUpdatable { public: /// @param data 領域の位置データ / Territory location data /// @param group_data 領域グループデータ / Territory group data @@ -49,7 +46,6 @@ class TerritoryFeature : public MapFeature, , group_data_(group_data) , cached_screen_points_() , color_(0, 0, 0, 255) // デフォルト色(黒) / Default color (black) - , visible_(true) { // 色情報からRGBAを取得(将来的にはAppConfigから色マッピングを読み込む) // TODO: 色情報の管理システムを実装 @@ -74,14 +70,6 @@ class TerritoryFeature : public MapFeature, return data_.feature_type_hash; } - /// @brief 時間的更新(ITemporallyUpdatableの実装) - /// @brief Temporal update (ITemporallyUpdatable implementation) - void updateTemporal(const TemporalContext& context) override { - // 領域は時間変化しないため、特に処理は不要 - // Territories don't change over time, so no processing needed - (void)context; - } - /// @brief 空間的更新(ISpatiallyUpdatableの実装) /// @brief Spatial update (ISpatiallyUpdatable implementation) void updateSpatial(const SpatialContext& context) override { @@ -92,18 +80,127 @@ class TerritoryFeature : public MapFeature, return; } - // スクリーン座標に変換 - cached_screen_points_.clear(); - cached_screen_points_.reserve(data_.coordinates.size()); - - bool any_in_view = false; + // データが空の場合は早期リターン + if (data_.coordinates.empty()) { + cached_screen_points_.clear(); + visible_ = false; + return; + } + // 領域のバウンディングボックスを計算(最小・最大経度/緯度) + // Calculate bounding box of the territory (min/max longitude/latitude) + double min_lon = data_.coordinates[0].x; + double max_lon = data_.coordinates[0].x; + double min_lat = data_.coordinates[0].y; + double max_lat = data_.coordinates[0].y; + for (const auto& coord : data_.coordinates) { - // ビューの範囲チェック(少なくとも1点が範囲内なら描画) - if (context.isInViewBounds(coord)) { - any_in_view = true; + min_lon = (std::min)(min_lon, coord.x); + max_lon = (std::max)(max_lon, coord.x); + min_lat = (std::min)(min_lat, coord.y); + max_lat = (std::max)(max_lat, coord.y); + } + + // 日付変更線をまたぐかチェック(経度差が180°以上) + // Check if territory crosses date line (longitude difference >= 180°) + const bool crosses_dateline = (max_lon - min_lon) > 180.0; + + // ±3°のマージンを追加 + // Add ±3° margin + constexpr double bbox_margin = 3.0; + min_lon -= bbox_margin; + max_lon += bbox_margin; + min_lat -= bbox_margin; + max_lat += bbox_margin; + + // 画面の範囲を計算 + // Calculate screen bounds + const double view_half_width = context.map_view_size.x / 2.0; + const double view_half_height = context.map_view_size.y / 2.0; + const double view_min_lon = context.map_view_center.x - view_half_width; + const double view_max_lon = context.map_view_center.x + view_half_width; + const double view_min_lat = context.map_view_center.y - view_half_height; + const double view_max_lat = context.map_view_center.y + view_half_height; + + // 経度ラップ処理:3つのオフセット候補(-360°, 0°, +360°)で試す + // Longitude wrapping: try 3 offset candidates (-360°, 0°, +360°) + int best_offset_mult = 0; + bool found_overlap = false; + + for (int offset_mult = -1; offset_mult <= 1; ++offset_mult) { + const double offset_min_lon = min_lon + (offset_mult * 360.0); + const double offset_max_lon = max_lon + (offset_mult * 360.0); + + // バウンディングボックスが画面範囲と重なるかチェック + // Check if bounding box overlaps with screen bounds + const bool lon_overlap = (offset_min_lon <= view_max_lon) && (offset_max_lon >= view_min_lon); + const bool lat_overlap = (min_lat <= view_max_lat) && (max_lat >= view_min_lat); + + if (lon_overlap && lat_overlap) { + best_offset_mult = offset_mult; + found_overlap = true; + break; // 最初に見つかったオフセットを使用 } + } + + // 画面範囲と重ならない場合は非表示 + // Hide if no overlap with screen bounds + if (!found_overlap) { + cached_screen_points_.clear(); + visible_ = false; + return; + } + // 最適なオフセットを適用した後、日付変更線をまたぐ場合の経度正規化 + // Apply best offset, then normalize longitude for date line wrapping + std::vector normalized_coords; + normalized_coords.reserve(data_.coordinates.size()); + + // 最初の点にオフセットを適用 + const double first_x = data_.coordinates[0].x + (best_offset_mult * 360.0); + normalized_coords.push_back(paxs::WebMercatorDeg(paxs::Vector2(first_x, data_.coordinates[0].y))); + + // 2点目以降は、日付変更線をまたぐ場合のみ正規化 + // For subsequent points, normalize only if crossing date line + if (crosses_dateline) { + // 日付変更線をまたぐ場合:前の点との経度差が180°を超えないように正規化 + // Date line crossing: normalize so longitude difference from previous point doesn't exceed 180° + for (size_t i = 1; i < data_.coordinates.size(); ++i) { + const double prev_lon = normalized_coords[i - 1].x; + double current_lon = data_.coordinates[i].x + (best_offset_mult * 360.0); + + // 経度差を計算 + double lon_diff = current_lon - prev_lon; + + // 差が180°を超える場合、短い方の経路を選択 + // If difference exceeds 180°, choose shorter path + if (lon_diff > 180.0) { + current_lon -= 360.0; + } else if (lon_diff < -180.0) { + current_lon += 360.0; + } + + normalized_coords.push_back(paxs::WebMercatorDeg( + paxs::Vector2(current_lon, data_.coordinates[i].y) + )); + } + } else { + // 日付変更線をまたがない場合:そのままオフセットを適用 + // Non-crossing: simply apply offset + for (size_t i = 1; i < data_.coordinates.size(); ++i) { + const double current_lon = data_.coordinates[i].x + (best_offset_mult * 360.0); + normalized_coords.push_back(paxs::WebMercatorDeg( + paxs::Vector2(current_lon, data_.coordinates[i].y) + )); + } + } + + // 正規化された座標をスクリーン座標に変換 + // Convert normalized coordinates to screen coordinates + cached_screen_points_.clear(); + cached_screen_points_.reserve(normalized_coords.size()); + + for (const auto& coord : normalized_coords) { // スクリーン座標に変換 const paxg::Vec2 screen_pos = MapCoordinateConverter::toScreenPos( Vector2(coord.x, coord.y), @@ -117,7 +214,7 @@ class TerritoryFeature : public MapFeature, ); } - visible_ = any_in_view && (cached_screen_points_.size() >= 2); + visible_ = (cached_screen_points_.size() >= 2); } bool isVisible() const override { @@ -162,7 +259,9 @@ class TerritoryFeature : public MapFeature, // グループデータから線幅を取得 const float line_width = group_data_.line_width; - // スプライン曲線で描画(Settlementと同じ方法) + // スプライン曲線で描画 + // Spline2Dは最初と最後の点が同じ場合、自動的に閉じたループとして綺麗に描画する + // Spline2D automatically detects closed loops when first and last points are the same paxg::Spline2D(cached_screen_points_).draw(line_width, color_); } @@ -184,7 +283,7 @@ class TerritoryFeature : public MapFeature, std::vector cached_screen_points_; ///< キャッシュされたスクリーン座標列 / Cached screen points paxg::Color color_; ///< 描画色 / Drawing color - bool visible_; ///< 可視性 / Visibility + bool visible_{true}; ///< 可視性 / Visibility }; } // namespace paxs diff --git a/Library/PAX_MAHOROBA/Map/Content/Manager/FeatureCollectionManager.hpp b/Library/PAX_MAHOROBA/Map/Content/Manager/FeatureCollectionManager.hpp index a1945f755..5ae6bcb3e 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Manager/FeatureCollectionManager.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Manager/FeatureCollectionManager.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,7 @@ #include #include +#include #include #include #include @@ -41,7 +43,7 @@ namespace paxs { /// /// 責務: /// - features_の管理 - /// - データ読み込み(Person, Geographic, PlaceName, Genome, Territory) + /// - データ読み込み(Person, Geographic, PlaceName, Genome, Territory, FlowCurve) /// - Feature検索(ID検索、座標検索) /// - asset_registry_の管理 class FeatureCollectionManager { @@ -137,6 +139,23 @@ namespace paxs { } } + /// @brief フロー曲線データを読み込み + /// @brief Load flow curve features + void loadFlowCurveFeatures() { + // 容量を事前確保 + const std::size_t current_capacity = features_.capacity(); + const std::size_t estimated_flow_arrow_count = 100; + features_.reserve(current_capacity + estimated_flow_arrow_count); + + // FlowCurveFeatureに変換 + auto all_flow_arrows = FlowCurveRepository::loadFlowCurveList(); + for (const auto& [flow_arrow_data, flow_arrow_group] : all_flow_arrows) { + features_.emplace_back( + std::make_unique(flow_arrow_data, flow_arrow_group) + ); + } + } + public: FeatureCollectionManager() = default; ~FeatureCollectionManager() = default; @@ -157,11 +176,14 @@ namespace paxs { asset_registry_.loadMapIcons(map_icons_path); } - // 描画順序: 領域 → 地名 → ゲノム → 地理 → 人物 - // Territories are drawn first (background layer) + // 描画順序: 領域 → フロー曲線 → 地名 → ゲノム → 地理 → 人物 + // Territories are drawn first (background layer), then flow curves loadTerritoryFeatures(); std::cout << "Territory features loaded: " << features_.size() << "\n"; + loadFlowCurveFeatures(); + std::cout << "Flow curve features loaded: " << features_.size() << " total\n"; + loadPlaceNameFeatures(); std::cout << "Place name features loaded: " << features_.size() << " total\n"; diff --git a/Library/PAX_MAHOROBA/Map/Content/Manager/UpdateCoordinator.hpp b/Library/PAX_MAHOROBA/Map/Content/Manager/UpdateCoordinator.hpp index eb3ee525f..4e46578f9 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Manager/UpdateCoordinator.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Manager/UpdateCoordinator.hpp @@ -131,6 +131,9 @@ namespace paxs { const SpatialContext ctx = unified_context_.toSpatial(); for (auto& feature : features) { + if (!feature || !feature->isInTimeRange(unified_context_.jdn)) { + continue; + } // ISpatiallyUpdatableを実装している地物のみ更新 if (auto* spatial = dynamic_cast(feature.get())) { spatial->updateSpatial(ctx); @@ -155,10 +158,7 @@ namespace paxs { // ITemporallyUpdatableを実装している地物のみ更新 if (auto* temporal = dynamic_cast(feature.get())) { - // 時間範囲チェックは更新前に行う - if (feature->isInTimeRange(unified_context_.jdn)) { - temporal->updateTemporal(ctx); - } + temporal->updateTemporal(ctx); } } } diff --git a/Library/PAX_MAHOROBA/Map/Content/Renderer/GeometryRenderer.hpp b/Library/PAX_MAHOROBA/Map/Content/Renderer/GeometryRenderer.hpp index 44e28040c..fe7dccae5 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Renderer/GeometryRenderer.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Renderer/GeometryRenderer.hpp @@ -31,7 +31,7 @@ class GeometryRenderer { /// @brief 座標が表示範囲内にあるかを判定 /// @brief Check if coordinate is within view bounds - /// @param coordinate メルカトル座標 / Mercator coordinate + /// @param coordinate EPSG:3857(Webメルカトル) 座標 / Web Mercator coordinate /// @param map_view_size マップビューのサイズ / Map view size /// @param map_view_center マップビューの中心座標 / Map view center /// @param margin_factor マージン係数(デフォルト: 1.6) / Margin factor (default: 1.6) diff --git a/Library/PAX_MAHOROBA/Map/Content/Renderer/MapFeatureRenderer.hpp b/Library/PAX_MAHOROBA/Map/Content/Renderer/MapFeatureRenderer.hpp index 2065b4cbd..64413317b 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Renderer/MapFeatureRenderer.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Renderer/MapFeatureRenderer.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -74,6 +75,9 @@ class MapFeatureRenderer : public GeometryRenderer { case FeatureType::Territory: drawTerritory(static_cast(*feature)); break; + case FeatureType::FlowCurve: + drawFlowCurve(static_cast(*feature)); + break; default: break; } @@ -307,6 +311,15 @@ class MapFeatureRenderer : public GeometryRenderer { feature.renderSpline(); } + /// @brief フロー曲線地物を描画(スプライン曲線) + /// @brief Draw flow curve feature (spline curve) + /// @param feature フロー曲線地物 / Flow curve feature + static void drawFlowCurve(const FlowCurveFeature& feature) { // constに戻す + // FlowCurveFeatureの描画メソッドを呼び出し(アニメーション更新含む) + // Call FlowCurveFeature's rendering method (includes animation update) + feature.renderSpline(); + } + }; } // namespace paxs diff --git a/Library/PAX_MAHOROBA/Map/Content/Renderer/Model3DRenderer.hpp b/Library/PAX_MAHOROBA/Map/Content/Renderer/Model3DRenderer.hpp index 6b639f722..46155cb08 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Renderer/Model3DRenderer.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Renderer/Model3DRenderer.hpp @@ -36,7 +36,7 @@ namespace paxs { /// auto feature = std::make_unique( /// "tomb_001", // ID /// "石室", // 名前 / Name - /// MercatorDeg(135.0, 35.0), // 座標 / Coordinate + /// WebMercatorDeg(135.0, 35.0), // 座標 / Coordinate /// 2440000.0, // 開始年 / Start year (JDN) /// 2450000.0, // 終了年 / End year (JDN) /// model_config // 3Dモデル設定 / 3D model config diff --git a/Library/PAX_MAHOROBA/Map/Content/Update/IUpdatable.hpp b/Library/PAX_MAHOROBA/Map/Content/Update/IUpdatable.hpp index 72c8832e4..b879efd0f 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Update/IUpdatable.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Update/IUpdatable.hpp @@ -49,7 +49,7 @@ class ITemporallyUpdatable { /// @param context 時間的コンテキスト / Temporal context /// @details 以下の処理を実行する: /// @details - 時間補間座標の計算(PersonFeatureなど移動する地物) - /// @details - 時間範囲チェック + /// @details - 時間範囲チェック(呼び出し側で除外しないので、地物側で処理する) virtual void updateTemporal(const TemporalContext& context) = 0; }; diff --git a/Library/PAX_MAHOROBA/Map/Content/Update/UpdateContext.hpp b/Library/PAX_MAHOROBA/Map/Content/Update/UpdateContext.hpp index e5d85e037..b350af92b 100644 --- a/Library/PAX_MAHOROBA/Map/Content/Update/UpdateContext.hpp +++ b/Library/PAX_MAHOROBA/Map/Content/Update/UpdateContext.hpp @@ -39,7 +39,7 @@ struct SpatialContext : BaseContext { /// @brief 座標がビューの範囲内にあるかチェック(日付変更線対応) /// @brief Check if coordinates are within view bounds (with date line wrapping) - /// @param mercator_pos メルカトル座標 / Mercator position + /// @param mercator_pos EPSG:3857(Webメルカトル) 座標 / Web Mercator position /// @param margin マージン倍率(デフォルト1.6) / Margin multiplier (default 1.6) /// @return 範囲内ならtrue / True if within bounds /// @details 経度が±180°で循環するため、3つのラップ座標(-360°, 0°, +360°)のいずれかが範囲内にあるかチェックします @@ -69,6 +69,47 @@ struct SpatialContext : BaseContext { /// @brief Temporal context - Date-related information struct TemporalContext : BaseContext { double jdn = 0.0; ///< ユリウス日 / Julian Day Number + + Vector2 map_view_size{0.0, 0.0}; ///< 地図ビューのサイズ(幅、高さ) / Map view size (width, height) + Vector2 map_view_center{0.0, 0.0}; ///< 地図ビューの中心座標 / Map view center + + /// @brief 座標がビューの範囲内にあるかチェック(日付変更線対応) + /// @brief Check if coordinates are within view bounds (with date line wrapping) + /// @param mercator_pos EPSG:3857(Webメルカトル) 座標 / Web Mercator position + /// @param margin マージン倍率(デフォルト1.6) / Margin multiplier (default 1.6) + /// @return 範囲内ならtrue / True if within bounds + /// @details 経度が±180°で循環するため、3つのラップ座標(-360°, 0°, +360°)のいずれかが範囲内にあるかチェックします + /// @details Checks if any of the three wrapped coordinates (-360°, 0°, +360°) are within bounds since longitude wraps at ±180° + [[nodiscard]] bool isInViewBounds(Vector2 mercator_pos, double margin = 1.6) const { + const Rect view_rect = Rect::fromCenter( + map_view_center, + map_view_size * margin + ); + + // 経度の3つのラップ座標(-360°, 0°, +360°)のいずれかが範囲内にあるかチェック + // Check if any of the three longitude-wrapped coordinates are within bounds + for (int offset_mult = -1; offset_mult <= 1; ++offset_mult) { + const Vector2 wrapped_pos( + mercator_pos.x + (offset_mult * 360.0), + mercator_pos.y + ); + if (view_rect.contains(wrapped_pos)) { + return true; + } + } + return false; + } + + /// @brief 空間的コンテキストに変換 + /// @brief Convert to spatial context + [[nodiscard]] SpatialContext toSpatial() const { + SpatialContext ctx; + ctx.visibility_manager = visibility_manager; + ctx.texture_map = texture_map; + ctx.map_view_size = map_view_size; + ctx.map_view_center = map_view_center; + return ctx; + } }; /// @brief ローカライゼーションコンテキスト - 言語とフォントに関する情報 @@ -110,6 +151,8 @@ struct UnifiedContext : BaseContext { ctx.visibility_manager = visibility_manager; ctx.texture_map = texture_map; ctx.jdn = jdn; + ctx.map_view_size = map_view_size; + ctx.map_view_center = map_view_center; return ctx; } diff --git a/Library/PAX_MAHOROBA/Map/Core/MapCoordinateConverter.hpp b/Library/PAX_MAHOROBA/Map/Core/MapCoordinateConverter.hpp index 25dcebe1f..3122830cf 100644 --- a/Library/PAX_MAHOROBA/Map/Core/MapCoordinateConverter.hpp +++ b/Library/PAX_MAHOROBA/Map/Core/MapCoordinateConverter.hpp @@ -24,9 +24,9 @@ namespace paxs { /// @brief 地図座標変換ヘルパークラス /// @brief Map coordinate conversion helper class struct MapCoordinateConverter { - /// @brief メルカトル座標→スクリーン座標変換(経度ラップ処理付き) + /// @brief EPSG:3857(Webメルカトル) 座標→スクリーン座標変換(経度ラップ処理付き) /// @brief Convert Mercator coordinates to screen positions (with longitude wrapping) - /// @param mercator_pos メルカトル座標 / Mercator position + /// @param mercator_pos EPSG:3857(Webメルカトル) 座標 / Web Mercator position /// @param map_view_size ビューポートサイズ / Viewport size /// @param map_view_center ビューポート中心 / Viewport center /// @param out_positions 出力先(経度ラップされた3つのスクリーン座標) / Output (three wrapped screen positions) @@ -55,9 +55,9 @@ struct MapCoordinateConverter { /// @param min_year 開始年 / Start year /// @param max_year 終了年 / End year /// @return 補間された座標 / Interpolated position - static MercatorDeg interpolatePosition( - const MercatorDeg& start, - const MercatorDeg& end, + static WebMercatorDeg interpolatePosition( + const WebMercatorDeg& start, + const WebMercatorDeg& end, double jdn, double min_year, double max_year @@ -70,12 +70,12 @@ struct MapCoordinateConverter { const Vector2 coordinate_displacement = end - start; - return MercatorDeg(start + coordinate_displacement * year_normalization); + return WebMercatorDeg(start + coordinate_displacement * year_normalization); } /// @brief 単一の座標をスクリーン座標に変換 /// @brief Convert single coordinate to screen position - /// @param mercator_pos メルカトル座標 / Mercator position + /// @param mercator_pos EPSG:3857(Webメルカトル) 座標 / Web Mercator position /// @param map_view_size ビューポートサイズ / Viewport size /// @param map_view_center ビューポート中心 / Viewport center /// @return スクリーン座標 / Screen position @@ -84,7 +84,7 @@ struct MapCoordinateConverter { const Vector2& map_view_size, const Vector2& map_view_center ) { - // メルカトル座標をスクリーン座標に変換 + // EPSG:3857(Webメルカトル) 座標をスクリーン座標に変換 return paxg::Vec2( (mercator_pos.x - (map_view_center.x - map_view_size.x / 2)) / map_view_size.x * double(paxg::Window::width()), diff --git a/Library/PAX_MAHOROBA/Map/Core/MapViewport.hpp b/Library/PAX_MAHOROBA/Map/Core/MapViewport.hpp index 8d1d185cc..d34623fe4 100644 --- a/Library/PAX_MAHOROBA/Map/Core/MapViewport.hpp +++ b/Library/PAX_MAHOROBA/Map/Core/MapViewport.hpp @@ -12,7 +12,6 @@ #ifndef PAX_MAHOROBA_MAP_VIEWPORT_HPP #define PAX_MAHOROBA_MAP_VIEWPORT_HPP -#include #include #include @@ -56,9 +55,7 @@ namespace paxs { class MapViewport { private: // 中央の座標を指定 - paxg::Coordinate center = paxg::Coordinate( - paxs::MercatorDeg(paxs::EquirectangularDeg(paxs::Vector2(145, 48))) // 韓国 128, 37 // 日本 135, 35 // 北海道 // 東アジア 127, 31, 75.0 // 全世界 100, 0 - ); // マップ座標の中央 + paxs::WebMercatorDeg center = paxs::WebMercatorDeg(paxs::EPSG4326_WGS84Deg(paxs::Vector2(145, 48))); // マップ座標の中央 double height = MapViewportConstants::default_height; // 各国 16.0; // 全世界 240.0 // マップの高さ // マップの最大高さ @@ -97,7 +94,7 @@ namespace paxs { void notifyViewportChanged() { const int zoom_level = static_cast(std::log2(MapViewportConstants::longitude_range / height)); paxs::EventBus::getInstance().publish(ViewportChangedEvent( - center.getVector2(), zoom_level)); + center, zoom_level)); } /// @brief ビューポートの境界制約を適用(Domain層の責任) @@ -111,7 +108,7 @@ namespace paxs { width = height / double(paxg::Window::height()) * double(paxg::Window::width()); // 境界制約適用前の座標を保存 - const Vector2 old_center = center.getVector2(); + const Vector2 old_center = center; #ifdef PAXS_MAHOROBA constexpr double west_max = (208.0 / MapViewportConstants::tile_size) * MapViewportConstants::longitude_range - MapViewportConstants::longitude_max; @@ -139,17 +136,16 @@ namespace paxs { constexpr double south_max = (1.0 - (MapViewportConstants::tile_size / MapViewportConstants::tile_size)) * MapViewportConstants::longitude_range - MapViewportConstants::longitude_max; // Y座標の位置調整のみ(PAXS_MAHOROBA以外) - if (center.getY() + height / 2 > north_max) { - center.setY(north_max - height / 2); + if (center.y + height / 2 > north_max) { + center.y = north_max - height / 2; } - if (center.getY() - height / 2 < south_max) { - center.setY(south_max + height / 2); + if (center.y - height / 2 < south_max) { + center.y = south_max + height / 2; } #endif // 座標が変更されたかチェック - const Vector2 new_center = center.getVector2(); - if (isDifferent(new_center.x, old_center.x) || isDifferent(new_center.y, old_center.y)) { + if (isDifferent(center.x, old_center.x) || isDifferent(center.y, old_center.y)) { changed = true; } @@ -162,17 +158,13 @@ namespace paxs { } } void setCenter(Vector2 position) { - const Vector2 current_center = center.getVector2(); - if (isDifferent(current_center.x, position.x) || isDifferent(current_center.y, position.y)) { - center.set(position); + if (isDifferent(center.x, position.x) || isDifferent(center.y, position.y)) { + center = paxs::WebMercatorDeg(position); } } - paxg::Coordinate& getCoordinate() { - return center; - } Vector2 getCenter() const { - return center.getVector2(); + return center; } Vector2 getSize() const { return Vector2(width, height); diff --git a/Library/PAX_MAHOROBA/Map/Simulation/SettlementRenderer.hpp b/Library/PAX_MAHOROBA/Map/Simulation/SettlementRenderer.hpp index 5bdea58cb..22b5d7217 100644 --- a/Library/PAX_MAHOROBA/Map/Simulation/SettlementRenderer.hpp +++ b/Library/PAX_MAHOROBA/Map/Simulation/SettlementRenderer.hpp @@ -98,12 +98,12 @@ namespace paxs { SettlementRenderer() = default; - /// @brief グリッド座標をメルカトル座標に変換 + /// @brief グリッド座標をEPSG:3857(Webメルカトル) 座標に変換 /// @brief Convert grid coordinate to Mercator coordinate /// @param position グリッド座標 / Grid coordinate - /// @return メルカトル座標 / Mercator coordinate - static paxs::MercatorDeg positionToMercator(const paxs::Vector2& position) { - return paxs::MercatorDeg(MapUtils::tilePixelToAngleSpace( + /// @return EPSG:3857(Webメルカトル) 座標 / Web Mercator coordinate + static paxs::WebMercatorDeg positionToMercator(const paxs::Vector2& position) { + return paxs::WebMercatorDeg(MapUtils::tilePixelToAngleSpace( SimulationConstants::getInstance().getStartArea(), position, ZOOM_LEVEL @@ -330,7 +330,7 @@ namespace paxs { const auto area_height = SimulationConstants::getInstance().getEndArea().y - SimulationConstants::getInstance().getStartArea().y; - const paxs::MercatorDeg start_coordinate = positionToMercator(paxs::Vector2(0, 0)); + const paxs::WebMercatorDeg start_coordinate = positionToMercator(paxs::Vector2(0, 0)); const paxg::Vec2f draw_start_pos = paxg::Vec2f{ static_cast((start_coordinate.x - (map_view_center.x - map_view_size.x / 2)) / map_view_size.x * double(paxg::Window::width())), @@ -339,7 +339,7 @@ namespace paxs { map_view_size.y * double(paxg::Window::height()))) }; - const paxs::MercatorDeg end_coordinate = positionToMercator( + const paxs::WebMercatorDeg end_coordinate = positionToMercator( paxs::Vector2(area_width * 256, area_height * 256)); const paxg::Vec2f draw_end_pos = paxg::Vec2f{ static_cast((end_coordinate.x - (map_view_center.x - map_view_size.x / 2)) / @@ -349,7 +349,7 @@ namespace paxs { map_view_size.y * double(paxg::Window::height()))) }; - const paxs::MercatorDeg tile_coordinate = positionToMercator( + const paxs::WebMercatorDeg tile_coordinate = positionToMercator( paxs::Vector2(SimulationConstants::getInstance().cell_group_length, SimulationConstants::getInstance().cell_group_length)); const paxg::Vec2f tile_pos = paxg::Vec2f{ diff --git a/Library/PAX_MAHOROBA/Map/Tile/XYZTile.hpp b/Library/PAX_MAHOROBA/Map/Tile/XYZTile.hpp index decb7400b..0c8772e92 100644 --- a/Library/PAX_MAHOROBA/Map/Tile/XYZTile.hpp +++ b/Library/PAX_MAHOROBA/Map/Tile/XYZTile.hpp @@ -106,7 +106,7 @@ namespace paxs { // 浮動小数点比較の許容誤差 static constexpr double VIEWPORT_EPSILON = 1e-9; - // XYZ タイルの 1 つのセルのメルカトル座標を保持 + // XYZ タイルの 1 つのセルのWebメルカトル座標を保持 // 基本的に Z = 19 は無い paxs::TileCache tile_cache_; diff --git a/Library/PAX_MAHOROBA/UI/Calendar/TimeControlButton.hpp b/Library/PAX_MAHOROBA/UI/Calendar/TimeControlButton.hpp index ad8ace16b..4ba33a357 100644 --- a/Library/PAX_MAHOROBA/UI/Calendar/TimeControlButton.hpp +++ b/Library/PAX_MAHOROBA/UI/Calendar/TimeControlButton.hpp @@ -22,6 +22,7 @@ #include #include +#include #include namespace paxs { @@ -286,9 +287,9 @@ namespace paxs { paxs::EventBus::getInstance().publish(TimePlaybackControlEvent(Action::Forward)); } else { // 日付移動系 - constexpr double day = 1.0; - constexpr double month = 365.2422 / 12.0; - constexpr double year = 365.2422; + constexpr double day = paxs::Calendar::daysInTropicalDay(); + constexpr double month = paxs::Calendar::daysInTropicalMonth(); + constexpr double year = paxs::Calendar::daysInTropicalYear(); // ボタンIDから日数を計算 const double days_map[] = { diff --git a/Library/PAX_MAHOROBA/UI/Debug/DebugConsole.hpp b/Library/PAX_MAHOROBA/UI/Debug/DebugConsole.hpp index 35d85c64e..a3cea5bbe 100644 --- a/Library/PAX_MAHOROBA/UI/Debug/DebugConsole.hpp +++ b/Library/PAX_MAHOROBA/UI/Debug/DebugConsole.hpp @@ -12,7 +12,6 @@ #ifndef PAX_MAHOROBA_UI_DEBUG_DEBUG_CONSOLE_HPP #define PAX_MAHOROBA_UI_DEBUG_DEBUG_CONSOLE_HPP -#include #include #include #include @@ -29,6 +28,7 @@ #include #include #include +#include namespace paxs { @@ -228,10 +228,7 @@ class DebugConsole { } double getCurrentTime() const { - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast(duration).count() / 1000.0; + return paxs::TimeUtils::getCurrentTimestampMillis() / 1000.0; } void toggleVisible() { diff --git a/Library/PAX_MAHOROBA/UI/Debug/DebugConsoleCommandRegistry.hpp b/Library/PAX_MAHOROBA/UI/Debug/DebugConsoleCommandRegistry.hpp index bf5d57cc4..423284330 100644 --- a/Library/PAX_MAHOROBA/UI/Debug/DebugConsoleCommandRegistry.hpp +++ b/Library/PAX_MAHOROBA/UI/Debug/DebugConsoleCommandRegistry.hpp @@ -61,11 +61,11 @@ class DebugConsoleCommandRegistry { PAXS_WARNING("Longitude must be between 0.0 and 180.0"); return; } - // メルカトル座標に変換 + // EPSG:3857(Webメルカトル) 座標に変換 const Vector2 map_viewport_center = app_state.getMapViewport().getCenter(); paxs::Vector2 equirect_coords(longitude, map_viewport_center.y); - paxg::Coordinate mercator_coords = paxs::MercatorDeg(paxs::EquirectangularDeg(equirect_coords)); - app_state.setViewportCenter(Vector2(mercator_coords.getX(), map_viewport_center.y)); + paxs::WebMercatorDeg mercator_coords = paxs::WebMercatorDeg(paxs::EPSG4326_WGS84Deg(equirect_coords)); + app_state.setViewportCenter(Vector2(mercator_coords.x, map_viewport_center.y)); }); // y : 緯度を設定(範囲: 0.0~90.0) @@ -84,11 +84,11 @@ class DebugConsoleCommandRegistry { PAXS_WARNING("Latitude must be between 0.0 and 90.0"); return; } - // メルカトル座標に変換 + // EPSG:3857(Webメルカトル) 座標に変換 const Vector2 map_viewport_center = app_state.getMapViewport().getCenter(); paxs::Vector2 equirect_coords(map_viewport_center.x, latitude); - paxg::Coordinate mercator_coords = paxs::MercatorDeg(paxs::EquirectangularDeg(equirect_coords)); - app_state.setViewportCenter(Vector2(map_viewport_center.x, mercator_coords.getY())); + paxs::WebMercatorDeg mercator_coords = paxs::WebMercatorDeg(paxs::EPSG4326_WGS84Deg(equirect_coords)); + app_state.setViewportCenter(Vector2(map_viewport_center.x, mercator_coords.y)); }); // z : 拡大率を設定 diff --git a/Library/PAX_MAHOROBA/UI/Debug/DebugNotificationManager.hpp b/Library/PAX_MAHOROBA/UI/Debug/DebugNotificationManager.hpp index 9084071f1..58fe73ffa 100644 --- a/Library/PAX_MAHOROBA/UI/Debug/DebugNotificationManager.hpp +++ b/Library/PAX_MAHOROBA/UI/Debug/DebugNotificationManager.hpp @@ -13,7 +13,6 @@ #define PAX_MAHOROBA_UI_DEBUG_DEBUG_NOTIFICATION_MANAGER_HPP #include -#include #include #include @@ -27,6 +26,7 @@ #include #include #include +#include namespace paxs { @@ -157,10 +157,7 @@ class DebugNotificationManager { } double getCurrentTime() const { - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast(duration).count() / 1000.0; + return paxs::TimeUtils::getCurrentTimestampMillis() / 1000.0; } private: diff --git a/Library/PAX_MAHOROBA/UI/Debug/DebugPerformanceMonitor.hpp b/Library/PAX_MAHOROBA/UI/Debug/DebugPerformanceMonitor.hpp index 2f260336a..e5b22e8df 100644 --- a/Library/PAX_MAHOROBA/UI/Debug/DebugPerformanceMonitor.hpp +++ b/Library/PAX_MAHOROBA/UI/Debug/DebugPerformanceMonitor.hpp @@ -13,7 +13,6 @@ #define PAX_MAHOROBA_UI_DEBUG_DEBUG_PERFORMANCE_MONITOR_HPP #include -#include #include #include #include @@ -27,6 +26,8 @@ #include #include +#include + namespace paxs { /// @brief パフォーマンスモニター(FPS、メモリ使用量などを表示) @@ -37,7 +38,7 @@ class DebugPerformanceMonitor { // FPS計測用 std::deque frame_times_; - std::chrono::steady_clock::time_point last_frame_time_; + paxs::TimeUtils::PerformanceTimePoint last_frame_time_; double fps_ = 0.0; double frame_time_ms_ = 0.0; @@ -46,7 +47,7 @@ class DebugPerformanceMonitor { static constexpr std::size_t MAX_FPS_HISTORY = 120; // 2秒分(60FPS想定) // 更新間隔 - std::chrono::steady_clock::time_point last_update_time_; + paxs::TimeUtils::PerformanceTimePoint last_update_time_; static constexpr double UPDATE_INTERVAL = 0.5; // 0.5秒ごとに更新 // 表示モード @@ -67,7 +68,7 @@ class DebugPerformanceMonitor { public: DebugPerformanceMonitor() { - last_frame_time_ = std::chrono::steady_clock::now(); + last_frame_time_ = paxs::TimeUtils::now(); last_update_time_ = last_frame_time_; } @@ -76,11 +77,10 @@ class DebugPerformanceMonitor { /// @brief 更新処理(毎フレーム呼び出し) /// @brief Update process (called every frame) void update() { - auto current_time = std::chrono::steady_clock::now(); + auto current_time = paxs::TimeUtils::now(); // フレーム時間を計測 - std::chrono::duration frame_duration = current_time - last_frame_time_; - double frame_time = frame_duration.count(); + double frame_time = paxs::TimeUtils::getDifferenceSeconds(last_frame_time_, current_time); last_frame_time_ = current_time; // フレーム時間を記録 @@ -90,8 +90,8 @@ class DebugPerformanceMonitor { } // 定期的にFPSを更新 - std::chrono::duration update_duration = current_time - last_update_time_; - if (update_duration.count() >= UPDATE_INTERVAL) { + double update_elapsed = paxs::TimeUtils::getDifferenceSeconds(last_update_time_, current_time); + if (update_elapsed >= UPDATE_INTERVAL) { calculateFPS(); last_update_time_ = current_time; diff --git a/Library/PAX_MAHOROBA/UI/Debug/PerformanceProfiler.hpp b/Library/PAX_MAHOROBA/UI/Debug/PerformanceProfiler.hpp index a23ac52fd..790971bbd 100644 --- a/Library/PAX_MAHOROBA/UI/Debug/PerformanceProfiler.hpp +++ b/Library/PAX_MAHOROBA/UI/Debug/PerformanceProfiler.hpp @@ -12,13 +12,14 @@ #ifndef PAX_MAHOROBA_UI_DEBUG_PERFORMANCE_PROFILER_HPP #define PAX_MAHOROBA_UI_DEBUG_PERFORMANCE_PROFILER_HPP -#include #include #include #include #include +#include + namespace paxs { /// @brief パフォーマンスプロファイラ(シングルトン) @@ -39,14 +40,14 @@ class PerformanceProfiler { bool enabled_ = true; /// @brief フレーム開始時刻 - std::chrono::steady_clock::time_point frame_start_time_; + TimeUtils::PerformanceTimePoint frame_start_time_; /// @brief 総フレーム時間(ミリ秒) double total_frame_time_ms_ = 0.0; // シングルトンパターン PerformanceProfiler() { - frame_start_time_ = std::chrono::steady_clock::now(); + frame_start_time_ = TimeUtils::now(); } ~PerformanceProfiler() = default; @@ -69,9 +70,9 @@ class PerformanceProfiler { /// @brief Begin scope /// @param scope_name スコープ名 / Scope name /// @return 開始時刻 / Start time - std::chrono::steady_clock::time_point beginScope(const std::string& scope_name) { + TimeUtils::PerformanceTimePoint beginScope(const std::string& scope_name) { if (!enabled_) { - return std::chrono::steady_clock::now(); + return TimeUtils::now(); } // 親スコープを取得 @@ -100,21 +101,20 @@ class PerformanceProfiler { scopes_[parent_name].addChild(scope_name); } - return std::chrono::steady_clock::now(); + return TimeUtils::now(); } /// @brief スコープの終了 /// @brief End scope /// @param scope_name スコープ名 / Scope name /// @param start_time 開始時刻 / Start time - void endScope(const std::string& scope_name, std::chrono::steady_clock::time_point start_time) { + void endScope(const std::string& scope_name, TimeUtils::PerformanceTimePoint start_time) { if (!enabled_) { return; } - auto end_time = std::chrono::steady_clock::now(); - std::chrono::duration duration = end_time - start_time; - double time_ms = duration.count(); + auto end_time = TimeUtils::now(); + double time_ms = TimeUtils::getDifferenceMilliseconds(start_time, end_time); // スコープデータを更新 auto it = scopes_.find(scope_name); @@ -135,7 +135,7 @@ class PerformanceProfiler { return; } - frame_start_time_ = std::chrono::steady_clock::now(); + frame_start_time_ = TimeUtils::now(); // 全スコープのフレームをリセット for (auto& pair : scopes_) { @@ -153,9 +153,8 @@ class PerformanceProfiler { return; } - auto end_time = std::chrono::steady_clock::now(); - std::chrono::duration duration = end_time - frame_start_time_; - total_frame_time_ms_ = duration.count(); + auto end_time = TimeUtils::now(); + total_frame_time_ms_ = TimeUtils::getDifferenceMilliseconds(frame_start_time_, end_time); } /// @brief スコープデータを取得 diff --git a/Library/PAX_MAHOROBA/UI/Debug/PerformanceScope.hpp b/Library/PAX_MAHOROBA/UI/Debug/PerformanceScope.hpp index e17ce6865..c86f84bda 100644 --- a/Library/PAX_MAHOROBA/UI/Debug/PerformanceScope.hpp +++ b/Library/PAX_MAHOROBA/UI/Debug/PerformanceScope.hpp @@ -13,11 +13,12 @@ #define PAX_MAHOROBA_UI_DEBUG_PERFORMANCE_SCOPE_HPP #ifdef PAXS_DEVELOPMENT -#include #include #include +#include + namespace paxs { /// @brief パフォーマンススコープ(RAIIパターン) @@ -25,9 +26,9 @@ namespace paxs { /// @details コンストラクタで計測開始、デストラクタで計測終了 class PerformanceScope { private: - std::string scope_name_; ///< スコープ名 / Scope name - std::chrono::steady_clock::time_point start_time_; ///< 開始時刻 / Start time - bool active_; ///< アクティブフラグ / Active flag + std::string scope_name_; ///< スコープ名 / Scope name + paxs::TimeUtils::PerformanceTimePoint start_time_; ///< 開始時刻 / Start time + bool active_; ///< アクティブフラグ / Active flag public: /// @brief コンストラクタ(計測開始) diff --git a/Library/PAX_MAHOROBA/UI/DebugInfoPanel.hpp b/Library/PAX_MAHOROBA/UI/DebugInfoPanel.hpp index 275afd7f8..239058fac 100644 --- a/Library/PAX_MAHOROBA/UI/DebugInfoPanel.hpp +++ b/Library/PAX_MAHOROBA/UI/DebugInfoPanel.hpp @@ -109,7 +109,7 @@ namespace paxs { paxg::Color(0, 0, 0) ); - // メルカトル経度 + // EPSG:3857(Webメルカトル) 経度 const std::string* const merc_lon_label_ptr = Fonts().getLocalesText( ui_map_domain_key, mercator_longitude_key @@ -127,7 +127,7 @@ namespace paxs { paxg::Color(0, 0, 0) ); - // メルカトル緯度 + // EPSG:3857(Webメルカトル) 緯度 const std::string* const merc_lat_label_ptr = Fonts().getLocalesText( ui_map_domain_key, mercator_latitude_key @@ -145,7 +145,7 @@ namespace paxs { paxg::Color(0, 0, 0) ); - // 緯度(Equirectangular) + // 緯度(EPSG4326_WGS84) const std::string* const lat_label_ptr = Fonts().getLocalesText( ui_map_domain_key, latitude_key @@ -157,10 +157,10 @@ namespace paxs { paxg::Color(0, 0, 0) ); } - // メルカトル座標から正距円筒図法の緯度に変換 - const paxs::MercatorDeg merc_coord(paxs::Vector2(map_viewport_center.x, map_viewport_center.y)); + // EPSG:3857(Webメルカトル) 座標から EPSG:4326(WGS84) の緯度に変換 + const paxs::WebMercatorDeg merc_coord(paxs::Vector2(map_viewport_center.x, map_viewport_center.y)); font->draw( - std::to_string(merc_coord.toEquirectangularDegY()), + std::to_string(merc_coord.toEPSG4326_WGS84DegY()), paxg::Vec2i(text_x + 10, text_y + line_height * current_line++), paxg::Color(0, 0, 0) ); diff --git a/Library/PAX_MAHOROBA/UI/MenuBar/MenuBar.hpp b/Library/PAX_MAHOROBA/UI/MenuBar/MenuBar.hpp index f967e0dc3..e9fd78507 100644 --- a/Library/PAX_MAHOROBA/UI/MenuBar/MenuBar.hpp +++ b/Library/PAX_MAHOROBA/UI/MenuBar/MenuBar.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -35,14 +36,14 @@ namespace paxs { /// @brief アプリ上部のメニューバー - class MenuBar : public InteractiveUIComponent{ + class MenuBar : public InteractiveUIComponent { private: static constexpr int github_button_margin = 8; public: MenuBar(const paxs::FeatureVisibilityManager& visible_manager) : language_selector_(paxs::MurMur3::calcHash("Language"), - paxg::Vec2i{3000, 0}, + paxg::Vec2i{ 3000, 0 }, paxs::PulldownDisplayType::SelectedValue, true ) { @@ -57,33 +58,33 @@ namespace paxs { language_selector_.setOnSelectionChanged([this](std::uint_least32_t key, bool is_selected) { (void)is_selected; handleLanguageChanged(key); - }); + }); // メニューバーにメニュー項目を追加(FontSystem経由) menu_system.add(paxs::MenuBarKeys::view_menu_hashes, - static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), - static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), - MenuBarType::view); + static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), + static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), + MenuBarType::view); menu_system.add(paxs::MenuBarKeys::place_names_menu_hashes, - static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), - static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), - MenuBarType::place_names); + static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), + static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), + MenuBarType::place_names); menu_system.add(paxs::MenuBarKeys::item_menu_hashes, - static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), - static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), - MenuBarType::item); + static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), + static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), + MenuBarType::item); menu_system.add(paxs::MenuBarKeys::structure_menu_hashes, - static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), - static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), - MenuBarType::structure); + static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), + static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), + MenuBarType::structure); menu_system.add(paxs::MenuBarKeys::genome_menu_hashes, - static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), - static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), - MenuBarType::genomes); + static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), + static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), + MenuBarType::genomes); menu_system.add(paxs::MenuBarKeys::map_menu_hashes, - static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), - static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), - MenuBarType::map); + static_cast(paxg::FontConfig::PULLDOWN_FONT_SIZE), + static_cast(paxg::FontConfig::PULLDOWN_FONT_BUFFER_THICKNESS), + MenuBarType::map); // FeatureVisibilityManagerから初期状態を読み込んでメニューに反映 initializeMenuFromVisibility(visible_manager); @@ -171,7 +172,7 @@ namespace paxs { if (view_menu != nullptr) { view_menu->setOnItemToggled([this](std::size_t index, bool is_checked) { handleMenuItemToggled(paxs::MenuBarType::view, index, is_checked); - }); + }); } // Place Names メニューのコールバック @@ -179,7 +180,7 @@ namespace paxs { if (place_names_menu != nullptr) { place_names_menu->setOnItemToggled([this](std::size_t index, bool is_checked) { handleMenuItemToggled(paxs::MenuBarType::place_names, index, is_checked); - }); + }); } // Item メニューのコールバック @@ -187,7 +188,7 @@ namespace paxs { if (item_menu != nullptr) { item_menu->setOnItemToggled([this](std::size_t index, bool is_checked) { handleMenuItemToggled(paxs::MenuBarType::item, index, is_checked); - }); + }); } // Structure メニューのコールバック @@ -195,7 +196,7 @@ namespace paxs { if (structure_menu != nullptr) { structure_menu->setOnItemToggled([this](std::size_t index, bool is_checked) { handleMenuItemToggled(paxs::MenuBarType::structure, index, is_checked); - }); + }); } // Genome メニューのコールバック @@ -203,7 +204,7 @@ namespace paxs { if (genome_menu != nullptr) { genome_menu->setOnItemToggled([this](std::size_t index, bool is_checked) { handleMenuItemToggled(paxs::MenuBarType::genomes, index, is_checked); - }); + }); } // Map メニューのコールバック @@ -211,7 +212,7 @@ namespace paxs { if (map_menu != nullptr) { map_menu->setOnItemToggled([this](std::size_t index, bool is_checked) { handleMenuItemToggled(paxs::MenuBarType::map, index, is_checked); - }); + }); } } @@ -229,6 +230,10 @@ namespace paxs { menu_system.render(); language_selector_.render(); + // トグルボタンを描画 + toggle_button_.update(); + toggle_button_.render(); + // GitHubボタンを描画 github_button_.render(); } @@ -258,6 +263,11 @@ namespace paxs { return language_selector_.handleEvent(event); } + // トグルボタンのマウス入力処理 + if (toggle_button_.isHit(event.pos)) { + return toggle_button_.handleEvent(event); + } + // GitHubボタンのマウス入力処理 if (github_button_.isHit(event.pos)) { return github_button_.handleEvent(event); @@ -279,6 +289,7 @@ namespace paxs { private: paxs::Pulldown language_selector_; paxs::MenuSystem menu_system; + mutable paxs::MenuBarToggleButton toggle_button_; paxs::GitHubButton github_button_; /// @brief イベント購読を設定 @@ -306,12 +317,18 @@ namespace paxs { // 言語選択プルダウンを右端に配置 language_selector_.setPos(Vector2{ static_cast(paxg::Window::width() - language_selector_.getRect().width()), - 0 + 0 }); github_button_.setPos(Vector2{ language_selector_.getRect().x() - github_button_.getRect().width() - github_button_margin, - (language_selector_.getRect().height() - github_button_.getRect().height()) / 2 + (language_selector_.getRect().height() - github_button_.getRect().height()) / 2 }); + + // トグルボタンをGitHubボタンの左隣に配置 + toggle_button_.init( + github_button_.getRect().x(), + language_selector_.getRect().height() + ); } /// @brief FeatureVisibilityManagerからメニューの初期状態を読み込む @@ -323,7 +340,8 @@ namespace paxs { for (std::size_t i = 0; i < paxs::MenuBarKeys::view_menu_items.size(); ++i) { view_menu->setIsItems(i, visible_manager.isVisible(paxs::MenuBarKeys::view_menu_items[i])); } - } else { + } + else { PAXS_WARNING("MenuBar::initializeMenuFromVisibility: 'view' menu not found."); } @@ -333,7 +351,8 @@ namespace paxs { for (std::size_t i = 0; i < paxs::MenuBarKeys::map_menu_items.size(); ++i) { map_menu->setIsItems(i, visible_manager.isVisible(paxs::MenuBarKeys::map_menu_items[i])); } - } else { + } + else { PAXS_WARNING("MenuBar::initializeMenuFromVisibility: 'map' menu not found."); } } diff --git a/Library/PAX_MAHOROBA/UI/MenuBar/MenuBarToggleButton.hpp b/Library/PAX_MAHOROBA/UI/MenuBar/MenuBarToggleButton.hpp new file mode 100644 index 000000000..5a1f4d6f3 --- /dev/null +++ b/Library/PAX_MAHOROBA/UI/MenuBar/MenuBarToggleButton.hpp @@ -0,0 +1,104 @@ +/*########################################################################################## + + PAX SAPIENTICA Library 💀🌿🌏 + + [Planning] 2023-2024 As Project + [Production] 2023-2024 As Project + [Contact Us] wanotaitei@gmail.com https://github.com/AsPJT/PAX_SAPIENTICA + [License] Distributed under the CC0 1.0. https://creativecommons.org/publicdomain/zero/1.0/ + +##########################################################################################*/ + +#ifndef PAX_MAHOROBA_UI_MENU_BAR_TOGGLE_BUTTON_HPP +#define PAX_MAHOROBA_UI_MENU_BAR_TOGGLE_BUTTON_HPP + +#include +#include +#include +#include +#include +#include + +namespace paxs { + + /// @brief メニューバーのTerritory表示切替トグルボタン + /// @brief Toggle button for switching Territory display in menu bar + class MenuBarToggleButton : public InteractiveUIComponent { + private: + paxg::ToggleButton toggle_button_; + static constexpr int BUTTON_HEIGHT = 24; + static constexpr int BUTTON_WIDTH = BUTTON_HEIGHT * 2; // 幅は高さの2倍 + + public: + MenuBarToggleButton() = default; + + /// @brief Initialize toggle button position + /// @brief トグルボタンの位置を初期化 + /// @param github_button_x GitHubボタンのX座標 + /// @param menu_bar_height メニューバーの高さ + void init(int github_button_x, int menu_bar_height) { + const int margin = 8; + const int x = github_button_x - BUTTON_WIDTH - margin; + const int y = (menu_bar_height - BUTTON_HEIGHT) / 2; + + toggle_button_.setPosition(static_cast(x), static_cast(y)); + toggle_button_.setSize(static_cast(BUTTON_WIDTH), static_cast(BUTTON_HEIGHT)); + + // デフォルトはON(Territory表示) + toggle_button_.setOn(true); + } + + void render() const override { + if (!isVisible()) return; + toggle_button_.draw(); + } + + /// @brief Update animation state + /// @brief アニメーション状態を更新 + void update() { + toggle_button_.update(); + } + + Rect getRect() const override { + return toggle_button_.getRect().toType(); + } + + const char* getName() const override { return "MenuBarToggleButton"; } + bool isVisible() const override { return true; } + void setPos(const Vector2& pos) override { + toggle_button_.setPosition(static_cast(pos.x), static_cast(pos.y)); + } + + EventHandlingResult handleEvent(const MouseEvent& event) override { + if (!isVisible()) { + return EventHandlingResult::Handled(); + } + + // クリックを押した瞬間(Pressed)のみトグルを切り替える + if (event.left_button_state == MouseButtonState::Pressed) { + + toggle_button_.toggle(); + + // TODO: 処理を削除 + // Publish feature visibility change event for Territory (map_line1) + paxs::EventBus::getInstance().publish( + FeatureVisibilityChangeCommandEvent( + static_cast(paxs::MapLayersMenu::line1), + toggle_button_.isOn() + ) + ); + + return EventHandlingResult::Handled(); + } + + return EventHandlingResult::Handled(); + } + + RenderLayer getLayer() const override { + return RenderLayer::MenuBar; + } + }; + +} // namespace paxs + +#endif // !PAX_MAHOROBA_UI_MENU_BAR_TOGGLE_BUTTON_HPP diff --git a/Library/PAX_SAPIENTICA/Calendar/JulianDayNumber.hpp b/Library/PAX_SAPIENTICA/Calendar/JulianDayNumber.hpp index c1ccc0f5a..13a0894bb 100644 --- a/Library/PAX_SAPIENTICA/Calendar/JulianDayNumber.hpp +++ b/Library/PAX_SAPIENTICA/Calendar/JulianDayNumber.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace paxs::cal { @@ -85,8 +86,8 @@ namespace paxs::cal { double L = static_cast(static_cast(day) + 1402); const double N = std::floor((L - 1) / 1461); L = L - 1461 * N; - const double I = std::floor((L - 1) / 365) - std::floor(L / 1461); - L = L - 365 * I + 30; + const double I = std::floor((L - 1) / paxs::Calendar::daysInYearCommon()) - std::floor(L / 1461); + L = L - (paxs::Calendar::daysInYearCommon() * I) + 30; const double J = std::floor(80 * L / 2447); ymd.setDay(static_cast(L - std::floor(2447 * J / 80))); L = std::floor(J / 11); diff --git a/Library/PAX_SAPIENTICA/Calendar/Koyomi.hpp b/Library/PAX_SAPIENTICA/Calendar/Koyomi.hpp index 05a58aece..47e7f89bf 100644 --- a/Library/PAX_SAPIENTICA/Calendar/Koyomi.hpp +++ b/Library/PAX_SAPIENTICA/Calendar/Koyomi.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -88,7 +89,7 @@ namespace paxs { private: // 暦の更新に関する定数 - static constexpr double days_per_year = 365.2425; // グレゴリオ暦の1年の平均日数 + static constexpr double days_per_year = paxs::Calendar::daysInTropicalYear(); // 平均太陽年の日数 static constexpr double time_scale_factor = 18.0; // 時間経過の速度調整係数 static constexpr int calendar_update_threshold = 0; // 暦更新のしきい値 diff --git a/Library/PAX_SAPIENTICA/Core/Calendar/Constants.hpp b/Library/PAX_SAPIENTICA/Core/Calendar/Constants.hpp index c45a01136..7ed951c0c 100644 --- a/Library/PAX_SAPIENTICA/Core/Calendar/Constants.hpp +++ b/Library/PAX_SAPIENTICA/Core/Calendar/Constants.hpp @@ -22,6 +22,10 @@ struct CalendarConstants { /// @brief Average days in a year in the Gregorian calendar (including leap years) constexpr static T daysInYearGregorian() noexcept { return static_cast(365.2425); } + /// @brief ユリウス暦における1年の平均日数(閏年を含む) + /// @brief Average days in a year in the Julian calendar (including leap years) + constexpr static T daysInYearJulian() noexcept { return static_cast(365.25); } + /// @brief 一般的な1年の日数(平年の日数) /// @brief Common days in a year (non-leap year) constexpr static T daysInYearCommon() noexcept { return static_cast(365); } @@ -30,6 +34,14 @@ struct CalendarConstants { /// @brief Length of the tropical year (mean solar year), in days constexpr static T daysInTropicalYear() noexcept { return static_cast(365.24219); } + /// @brief 平均太陽年(tropical year)の一月の長さ(日) + /// @brief Length of the tropical year (mean solar year), in days per month + constexpr static T daysInTropicalMonth() noexcept { return daysInTropicalYear() / static_cast(12); } + + /// @brief 平均太陽年(tropical year)の一日の長さ(日) + /// @brief Length of the tropical year (mean solar year), in days per day + constexpr static T daysInTropicalDay() noexcept { return static_cast(1); } + /// @brief 紀元1年1月1日 00:00 UT のユリウス日(プロレプティック・グレゴリオ暦) /// @brief Julian Date for 0001-01-01 00:00 UT (Proleptic Gregorian calendar) /// @note JD = 1721425.5 が標準的な値 diff --git a/Library/PAX_SAPIENTICA/Core/Calendar/Conversions.hpp b/Library/PAX_SAPIENTICA/Core/Calendar/Conversions.hpp index efa93c152..0f9285dd6 100644 --- a/Library/PAX_SAPIENTICA/Core/Calendar/Conversions.hpp +++ b/Library/PAX_SAPIENTICA/Core/Calendar/Conversions.hpp @@ -31,15 +31,6 @@ struct CalendarConversions { + (year - static_cast(1)) * CalendarConstants::daysInYearGregorian(); } - /// @brief 年をユリウス日に変換(平年 365 日を用いた簡易近似) - /// @brief Convert calendar year to Julian Date (simple approx., using 365-day common year) - /// @param year 西暦年 - /// @return ユリウス日(0001-01-01 00:00 UT を基準とした近似値) - constexpr static T yearToJulianDayCommon(const T year) noexcept { - return CalendarConstants::jdOfGregorianYear1Start() - + (year - static_cast(1)) * CalendarConstants::daysInYearCommon(); - } - /// @brief 年をユリウス日に変換(平均太陽年を使用した近似) /// @brief Convert calendar year to Julian Date (approx., using tropical year length) /// @param year 西暦年 @@ -60,16 +51,6 @@ struct CalendarConversions { / CalendarConstants::daysInYearGregorian(); } - /// @brief ユリウス日を年に変換(平年 365 日を用いた簡易近似) - /// @brief Convert Julian Date to calendar year (simple approx., using 365-day common year) - /// @param julianDay ユリウス日 - /// @return 近似的な西暦年 - constexpr static T julianDayToYearCommon(const T julianDay) noexcept { - return static_cast(1) - + (julianDay - CalendarConstants::jdOfGregorianYear1Start()) - / CalendarConstants::daysInYearCommon(); - } - /// @brief ユリウス日を年に変換(平均太陽年を使用した近似) /// @brief Convert Julian Date to calendar year (approx., using tropical year length) /// @param julianDay ユリウス日 diff --git a/Library/PAX_SAPIENTICA/Core/Math/Constants.hpp b/Library/PAX_SAPIENTICA/Core/Math/Constants.hpp index bcc7cea65..7d1cff362 100644 --- a/Library/PAX_SAPIENTICA/Core/Math/Constants.hpp +++ b/Library/PAX_SAPIENTICA/Core/Math/Constants.hpp @@ -34,6 +34,12 @@ namespace paxs { constexpr static T e() noexcept { return std::numbers::e_v; } // e constexpr static T log2e() noexcept { return std::numbers::log2e_v; } // log2(e) constexpr static T log10e() noexcept { return std::numbers::log10e_v; } // log10(e) + constexpr static T ln2() noexcept { return std::numbers::ln2_v; } // ln(2) + constexpr static T ln10() noexcept { return std::numbers::ln10_v; } // ln(10) + + constexpr static T sqrt2() noexcept { return std::numbers::sqrt2_v; } // √2 + constexpr static T sqrt3() noexcept { return std::numbers::sqrt3_v; } // √3 + constexpr static T invSqrt3() noexcept { return std::numbers::inv_sqrt3_v; } // 1/ }; } diff --git a/Library/PAX_SAPIENTICA/Core/Platform.hpp b/Library/PAX_SAPIENTICA/Core/Platform.hpp index 042119703..3efd95804 100644 --- a/Library/PAX_SAPIENTICA/Core/Platform.hpp +++ b/Library/PAX_SAPIENTICA/Core/Platform.hpp @@ -12,6 +12,7 @@ #ifndef PAX_SAPIENTICA_CORE_PLATFORM_HPP #define PAX_SAPIENTICA_CORE_PLATFORM_HPP +// OS判定 #if defined(_WIN32) #define PAXS_PLATFORM_WINDOWS #elif defined(__APPLE__) && defined(__MACH__) @@ -22,5 +23,14 @@ #define PAXS_PLATFORM_ANDROID #endif +// コンパイラ判定 +#if defined(_MSC_VER) +#define PAXS_COMPILER_MSVC +#elif defined(__GNUC__) +#define PAXS_COMPILER_GCC +#elif defined(__clang__) +#define PAXS_COMPILER_CLANG +#endif + #endif // !PAX_SAPIENTICA_CORE_PLATFORM_HPP diff --git a/Library/PAX_SAPIENTICA/Core/Type/Rect.hpp b/Library/PAX_SAPIENTICA/Core/Type/Rect.hpp index 5a8c07564..f969ac7bc 100644 --- a/Library/PAX_SAPIENTICA/Core/Type/Rect.hpp +++ b/Library/PAX_SAPIENTICA/Core/Type/Rect.hpp @@ -109,6 +109,17 @@ namespace paxs { auto operator<=>(const Rect& rect) const noexcept = default; + /// @brief Convert rectangle to another type + /// @brief 矩形を別の型に変換 + template + [[nodiscard]] Rect toType() const noexcept { + return Rect( + static_cast(position.x), + static_cast(position.y), + static_cast(size.x), + static_cast(size.y) + ); + } }; } // namespace paxs diff --git a/Library/PAX_SAPIENTICA/Geography/Coordinate/Projection.hpp b/Library/PAX_SAPIENTICA/Geography/Coordinate/Projection.hpp index 7a0185b06..5562bba76 100644 --- a/Library/PAX_SAPIENTICA/Geography/Coordinate/Projection.hpp +++ b/Library/PAX_SAPIENTICA/Geography/Coordinate/Projection.hpp @@ -21,76 +21,134 @@ namespace paxs { // | X min | X max | Y min | Y max | MapProjection 地図投影法 // | ---- | ---- | ---- | ---- | ------------------ - // | -180 | 180 | -90 | 90 | EquirectangularDeg 正距円筒図法(度) + // | -180 | 180 | -90 | 90 | EPSG:4326(WGS84) // | ---- | ---- | ---- | ---- | ------------------ - // | -180 | 180 | -∞ | ∞ | MercatorDeg メルカトル図法(度) - // | ---- | ---- | ---- | ---- | ------------------ - // | -180 | 180 | -180 | 180 | XYZTiles XYZ タイル(度) + // | -180 | 180 | -180 | 180 | この範囲を「EPSG:3857(Webメルカトル) 座標」と定義 (正方形、 EPSG:3857(Webメルカトル) の経度(度)基準の大きさ) + + //メルカトル図法において、緯度 $\phi$(ファイ)から地図上の縦方向の座標 $y$ を求める計算式、および「なぜ 85.051129度 なのか」という導出過程は以下の通りです。 + //この数値は、**Webメルカトルにおいて、地図全体を正方形にするために定義された上限**です。 + //### 1. メルカトル図法の計算式 + //メルカトル図法における緯度 $\phi$(ラジアン)の $y$ 座標は、以下の式で表されます。 + //$$ + //y = \ln\left(\tan\left(\frac{\pi}{4} + \frac{\phi}{2}\right)\right) + //$$ + //ここで、 + //* $\ln$ は自然対数(ネイピア数 $e$ を底とする対数) + //* $\tan$ は正接関数 + //* $\phi$ は緯度(ラジアン表記) + //* $\pi$ は円周率 + //この式は、地球を半径1の球体とした場合の座標です。 + //### 2. なぜ「180」になるのか(導出) + //「経度の半分の大きさ(180)と同じになる」という条件は、**「赤道から極限までの描画距離($y$)を、赤道から経度180度までの距離($x=\pi$)と等しくする」** という意味です。 + //つまり、上記公式の $y$ が $\pi$ (度数法での180度に相当するラジアン値)になる時の $\phi$ を逆算することで求められます。 + //#### 計算ステップ + //**① 式を立てる** + //$y$ に $\pi$ を代入します。 + //$$ + //\pi = \ln\left(\tan\left(\frac{\pi}{4} + \frac{\phi}{2}\right)\right) + //$$ + //**② 対数を外す** + //両辺の指数をとります($\ln$ の逆関数は $e^x$)。 + //$$ + //e^\pi = \tan\left(\frac{\pi}{4} + \frac{\phi}{2}\right) + //$$ + //**③ $\tan$ を外す** + //逆正接(アークタンジェント)をとります。 + //$$ + //\arctan(e^\pi) = \frac{\pi}{4} + \frac{\phi}{2} + //$$ + //**④ $\phi$ について解く** + //$$ + //\frac{\phi}{2} = \arctan(e^\pi) - \frac{\pi}{4} + //$$ + //$$ + //\phi = 2 \arctan(e^\pi) - \frac{\pi}{2} + //$$ + //**⑤ 値を計算する** + //* $e^\pi \approx 23.14069$ + //* $\arctan(23.14069)$ を計算すると、約 $1.5276$ ラジアン(約 $87.52557^\circ$) + //* ここから $\frac{\pi}{2}$ (90度)を引いて2倍する計算になりますが、度数法で計算したほうが直感的です。 + //$$ + //\phi_{\text{deg}} = 2 \times (87.52557^\circ) - 90^\circ + //$$ + //$$ + //\phi_{\text{deg}} = 175.05114^\circ - 90^\circ + //$$ + //$$ + //\phi_{\text{deg}} \approx 85.05114^\circ + //$$ + //より精密な値が、**85.05112878...度** となります。 + //### まとめ + //計算式は以下の等式です。 + //$$ + //\phi = 2 \arctan(e^\pi) - \frac{\pi}{2} + //$$ + //この緯度で地図を切り取ることにより、メルカトル図法で描画される世界地図は**横幅(経度360度分)と縦幅(北緯約85度〜南緯約85度)が1:1の正方形**になります。これにより、Web上のタイル画像処理(256x256ピクセルの正方形タイルの組み合わせ)が非常に効率的になるため、多くのWeb地図でこの緯度が「世界の果て」として採用されています。 // 前方宣言 // forward declaration - struct MercatorDeg; // メルカトル図法(度) - struct EquirectangularDeg; // 正距円筒図法(度) + struct WebMercatorDeg; // EPSG:3857(Webメルカトル) (度) + struct EPSG4326_WGS84Deg; // EPSG:4326(WGS84) - // メルカトル図法(度) - struct MercatorDeg : paxs::Vector2 { - constexpr explicit MercatorDeg() : Vector2() {} - constexpr explicit MercatorDeg(const paxs::Vector2& v) noexcept : paxs::Vector2(v) {} - // Y軸を正距円筒図法(ラジアン)へ変換した値を返す - double toEquirectangularRadY() const noexcept { + // EPSG:3857(Webメルカトル) (度) + struct WebMercatorDeg : paxs::Vector2 { + constexpr explicit WebMercatorDeg() : Vector2() {} + constexpr explicit WebMercatorDeg(const paxs::Vector2& v) noexcept : paxs::Vector2(v) {} + // Y軸を EPSG:4326(WGS84) (ラジアン)へ変換した値を返す + double toEPSG4326_WGS84RadY() const noexcept { return std::asin(std::tanh(paxs::Math::degToRad(this->y))); } - // Y軸を正距円筒図法(度)へ変換した値を返す - double toEquirectangularDegY() const noexcept { - return paxs::Math::radToDeg(toEquirectangularRadY()); + // Y軸を EPSG:4326(WGS84) (度)へ変換した値を返す + double toEPSG4326_WGS84DegY() const noexcept { + return paxs::Math::radToDeg(toEPSG4326_WGS84RadY()); } - // 正距円筒図法(度)へ変換した値を返す - operator EquirectangularDeg() const noexcept; + // EPSG:4326(WGS84) (度)へ変換した値を返す + operator EPSG4326_WGS84Deg() const noexcept; }; - // 正距円筒図法(度) - struct EquirectangularDeg : paxs::Vector2 { - constexpr explicit EquirectangularDeg(const paxs::Vector2& v) noexcept : paxs::Vector2(v) {} - // Y軸をメルカトル図法(ラジアン)へ変換した値を返す + // EPSG:4326(WGS84) (度) + struct EPSG4326_WGS84Deg : paxs::Vector2 { + constexpr explicit EPSG4326_WGS84Deg(const paxs::Vector2& v) noexcept : paxs::Vector2(v) {} + // Y軸をEPSG:3857(Webメルカトル) (ラジアン)へ変換した値を返す double toMercatorRadY() const noexcept { return static_cast((y >= 0 ? 1 : -1) * std::abs(std::log(std::abs(std::tan(paxs::Math::pi() / 4.0 - paxs::Math::degToRad(y) / 2.0))))); } - // Y軸をメルカトル図法(度)へ変換した値を返す - double toMercatorDegY() const noexcept { + // Y軸をEPSG:3857(Webメルカトル) (度)へ変換した値を返す + double toWebMercatorDegY() const noexcept { return paxs::Math::radToDeg(toMercatorRadY()); } - // メルカトル図法(度)へ変換した値を返す - operator MercatorDeg() const noexcept { - return MercatorDeg(paxs::Vector2(x, toMercatorDegY())); + // EPSG:3857(Webメルカトル) (度)へ変換した値を返す + operator WebMercatorDeg() const noexcept { + return WebMercatorDeg(paxs::Vector2(x, toWebMercatorDegY())); } - // メルカトル図法(度)へ変換した値を返す - MercatorDeg toMercatorDeg() const noexcept { - return MercatorDeg(paxs::Vector2(x, toMercatorDegY())); + // EPSG:3857(Webメルカトル) (度)へ変換した値を返す + WebMercatorDeg toWebMercatorDeg() const noexcept { + return WebMercatorDeg(paxs::Vector2(x, toWebMercatorDegY())); } }; - // メルカトル図法(度)を正距円筒図法(度)へ変換した値を返す - MercatorDeg::operator EquirectangularDeg() const noexcept { - return EquirectangularDeg(paxs::Vector2(x, toEquirectangularDegY())); + // EPSG:3857(Webメルカトル) (度)を EPSG:4326(WGS84) (度)へ変換した値を返す + WebMercatorDeg::operator EPSG4326_WGS84Deg() const noexcept { + return EPSG4326_WGS84Deg(paxs::Vector2(x, toEPSG4326_WGS84DegY())); } // 個別の変換 template struct MapProjection { - // 正距円筒図法(ラジアン)からメルカトル図法へ変換(ラジアン) - constexpr static T equirectangularRadYToMercatorRadY(const T value) noexcept { + // EPSG:4326(WGS84) (ラジアン)から EPSG:3857(Webメルカトル) へ変換(ラジアン) + constexpr static T EPSG4326_WGS84RadYToMercatorRadY(const T value) noexcept { return static_cast((value >= 0 ? 1 : -1) * std::abs(std::log(std::abs(std::tan(paxs::Math::pi() / 4.0 - double(value) / 2.0))))); } - // 正距円筒図法(度)からメルカトル図法へ変換(ラジアン) - constexpr static T equirectangularDegYToMercatorRadY(const T value) noexcept { - return static_cast(equirectangularRadYToMercatorRadY(paxs::Math::degToRad(value))); + // EPSG:4326(WGS84) (度)から EPSG:3857(Webメルカトル) へ変換(ラジアン) + constexpr static T EPSG4326_WGS84DegYToMercatorRadY(const T value) noexcept { + return static_cast(EPSG4326_WGS84RadYToMercatorRadY(paxs::Math::degToRad(value))); } - // 正距円筒図法(ラジアン)からメルカトル図法へ変換(度) - constexpr static T equirectangularRadYToMercatorDegY(const T value) noexcept { - return static_cast(paxs::Math::radToDeg(equirectangularRadYToMercatorRadY(value))); + // EPSG:4326(WGS84) (ラジアン)から EPSG:3857(Webメルカトル) へ変換(度) + constexpr static T EPSG4326_WGS84RadYToWebMercatorDegY(const T value) noexcept { + return static_cast(paxs::Math::radToDeg(EPSG4326_WGS84RadYToMercatorRadY(value))); } - // 正距円筒図法(度)からメルカトル図法へ変換(度) - constexpr static T equirectangularDegYToMercatorDegY(const T value) noexcept { - return static_cast(paxs::Math::radToDeg(equirectangularRadYToMercatorRadY(paxs::Math::degToRad(value)))); + // EPSG:4326(WGS84) (度)から EPSG:3857(Webメルカトル) へ変換(度) + constexpr static T EPSG4326_WGS84DegYToWebMercatorDegY(const T value) noexcept { + return static_cast(paxs::Math::radToDeg(EPSG4326_WGS84RadYToMercatorRadY(paxs::Math::degToRad(value)))); } }; } diff --git a/Library/PAX_SAPIENTICA/Map/FlowCurveLocationPoint.hpp b/Library/PAX_SAPIENTICA/Map/FlowCurveLocationPoint.hpp new file mode 100644 index 000000000..4bbadcf09 --- /dev/null +++ b/Library/PAX_SAPIENTICA/Map/FlowCurveLocationPoint.hpp @@ -0,0 +1,80 @@ +/*########################################################################################## + + PAX SAPIENTICA Library 💀🌿🌏 + + [Planning] 2023-2024 As Project + [Production] 2023-2024 As Project + [Contact Us] wanotaitei@gmail.com https://github.com/AsPJT/PAX_SAPIENTICA + [License] Distributed under the CC0 1.0. https://creativecommons.org/publicdomain/zero/1.0/ + +##########################################################################################*/ + +#ifndef PAX_SAPIENTICA_MAP_FLOW_CURVE_LOCATION_POINT_HPP +#define PAX_SAPIENTICA_MAP_FLOW_CURVE_LOCATION_POINT_HPP + +#include +#include +#include +#include + +#include +#include +#include + +namespace paxs { + + /// @brief フロー曲線の位置情報(スプライン曲線用の点列) + /// @brief Flow curve location information (points for spline curves) + struct FlowCurveLocationData { + explicit FlowCurveLocationData() = default; + explicit FlowCurveLocationData( + std::string key_, // フロー曲線の一意キー / Unique key for flow curve + std::vector&& coordinates_, // スプライン曲線の座標列 / Coordinates for spline curve + const Range& zoom_range_, + const Range& year_range_, + const std::uint_least32_t feature_type_hash_ + ) noexcept + : key(std::move(key_)), + coordinates(std::move(coordinates_)), + zoom_range(zoom_range_), + year_range(year_range_), + feature_type_hash(feature_type_hash_) {} + + std::string key; // フロー曲線の一意キー / Unique key for flow curve + std::vector coordinates; // スプライン曲線の座標列 / Coordinates for spline curve + Range zoom_range{0.0, 9999.0}; // 表示するズームレベル範囲 / Zoom level range + Range year_range{-99999999.0, 99999999.0}; // 可視化する時代 / Time range + std::uint_least32_t feature_type_hash = MurMur3::calcHash("flow_arrow"); // 地物の種別 / Feature type hash + }; + + /// @brief フロー曲線のグループ + /// @brief Flow curve group + struct FlowCurveLocationGroup { + std::vector flow_arrow_list; // フロー曲線の一覧 / List of flow curves + + Range zoom_range{0.0, 9999.0}; // 表示するズームレベル範囲 / Zoom level range + Range year_range{-99999999.0, 99999999.0}; // 可視化する時代範囲 / Time range + std::uint_least32_t feature_type_hash = MurMur3::calcHash("flow_arrow"); // 地物の種別 / Feature type hash + std::uint_least32_t color_hash = 0; // 色情報のハッシュ / Color hash + float line_width = 2.0f; // 線の太さ / Line width + + explicit FlowCurveLocationGroup() = default; + explicit FlowCurveLocationGroup( + const std::vector& flow_arrow_list_, // フロー曲線リスト / Flow curve list + const Range& zoom_range_, // 表示するズームレベル範囲 / Zoom level range + const Range& year_range_, // 可視化する時代範囲 / Time range + const std::uint_least32_t feature_type_hash_, // 地物の種別 / Feature type hash + const std::uint_least32_t color_hash_, // 色情報のハッシュ / Color hash + float line_width_ // 線の太さ / Line width + ) noexcept + : flow_arrow_list(flow_arrow_list_), + zoom_range(zoom_range_), + year_range(year_range_), + feature_type_hash(feature_type_hash_), + color_hash(color_hash_), + line_width(line_width_) {} + }; + +} + +#endif // !PAX_SAPIENTICA_MAP_FLOW_CURVE_LOCATION_POINT_HPP diff --git a/Library/PAX_SAPIENTICA/Map/LocationPoint.hpp b/Library/PAX_SAPIENTICA/Map/LocationPoint.hpp index 5fa0ba8b1..6b99fff4b 100644 --- a/Library/PAX_SAPIENTICA/Map/LocationPoint.hpp +++ b/Library/PAX_SAPIENTICA/Map/LocationPoint.hpp @@ -24,15 +24,12 @@ namespace paxs { - constexpr double days_in_a_year = 365.2425; - constexpr double julian_day_on_m1_1_1 = 1721060.0; - // 地物の位置情報 struct LocationPoint { explicit LocationPoint() = default; explicit LocationPoint( std::string key_, // 地物の一意キー - const paxs::MercatorDeg& coordinate_, // 経緯度 + const paxs::WebMercatorDeg& coordinate_, // 経緯度 const double overall_length_, // 全長 const Range& zoom_range_, // 表示するズームレベル範囲 const Range& year_range_, // 可視化する時代範囲 @@ -47,7 +44,7 @@ namespace paxs { extra_data(extra_data_){} std::string key; // 地物の一意キー(TSVのkeyカラムから) - paxs::MercatorDeg coordinate; // 経緯度 + paxs::WebMercatorDeg coordinate; // 経緯度 double overall_length = 10; // 全長 Range zoom_range{0.0, 9999.0}; // 表示するズームレベル範囲 Range year_range{-99999999.0, 99999999.0}; // 可視化する時代(古い年~新しい年) @@ -77,8 +74,8 @@ namespace paxs { struct LocationPointGroup { std::vector location_point_list; // 地物の一覧 - paxs::MercatorDeg start_coordinate; // 経緯度 - paxs::MercatorDeg end_coordinate; // 経緯度 + paxs::WebMercatorDeg start_coordinate; // 経緯度 + paxs::WebMercatorDeg end_coordinate; // 経緯度 Range zoom_range{0.0, 9999.0}; // 表示するズームレベル範囲 Range year_range{-99999999.0, 99999999.0}; // 可視化する時代(古い年~新しい年) std::uint_least32_t feature_type_hash = MurMur3::calcHash("place_name"); // 地物の種別を識別するハッシュ値(例: "tomb", "site", "person") @@ -87,8 +84,8 @@ namespace paxs { explicit LocationPointGroup() = default; explicit LocationPointGroup( const std::vector& location_point_list_, // 地物 - paxs::MercatorDeg start_coordinate_, // 経緯度 - paxs::MercatorDeg end_coordinate_, // 経緯度 + paxs::WebMercatorDeg start_coordinate_, // 経緯度 + paxs::WebMercatorDeg end_coordinate_, // 経緯度 const Range& zoom_range_, // 表示するズームレベル範囲 const Range& year_range_, // 可視化する時代範囲 const std::uint_least32_t feature_type_hash_, // 地物の種別を識別するハッシュ値 diff --git a/Library/PAX_SAPIENTICA/Map/PersonLocationPoint.hpp b/Library/PAX_SAPIENTICA/Map/PersonLocationPoint.hpp index 14f3aa4c9..b3ee50294 100644 --- a/Library/PAX_SAPIENTICA/Map/PersonLocationPoint.hpp +++ b/Library/PAX_SAPIENTICA/Map/PersonLocationPoint.hpp @@ -28,8 +28,8 @@ namespace paxs { explicit PersonLocationPoint() = default; explicit PersonLocationPoint( std::string key_, // 人物の一意キー - const paxs::MercatorDeg& start_coordinate_, - const paxs::MercatorDeg& end_coordinate_, + const paxs::WebMercatorDeg& start_coordinate_, + const paxs::WebMercatorDeg& end_coordinate_, const Range& zoom_range_, const Range& year_range_, const std::uint_least32_t feature_type_hash_, @@ -39,8 +39,8 @@ namespace paxs { zoom_range(zoom_range_), year_range(year_range_), feature_type_hash(feature_type_hash_), texture_key(texture_key_) {} std::string key; // 人物の一意キー - paxs::MercatorDeg start_coordinate; // 開始時の経緯度 - paxs::MercatorDeg end_coordinate; // 終了時の経緯度 + paxs::WebMercatorDeg start_coordinate; // 開始時の経緯度 + paxs::WebMercatorDeg end_coordinate; // 終了時の経緯度 Range zoom_range{0.0, 9999.0}; // 表示するズームレベル範囲 Range year_range{-99999999.0, 99999999.0}; // 可視化する時代(古い年~新しい年) std::uint_least32_t feature_type_hash = MurMur3::calcHash("person"); // 地物の種別を識別するハッシュ値 diff --git a/Library/PAX_SAPIENTICA/Map/Repository/FeatureListLoader.hpp b/Library/PAX_SAPIENTICA/Map/Repository/FeatureListLoader.hpp index 92627c024..489893849 100644 --- a/Library/PAX_SAPIENTICA/Map/Repository/FeatureListLoader.hpp +++ b/Library/PAX_SAPIENTICA/Map/Repository/FeatureListLoader.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -91,9 +92,8 @@ namespace paxs { const bool has_first_year = table.hasColumn(first_year_hash); const bool has_last_year = table.hasColumn(last_year_hash); - // 定数 - constexpr double local_days_in_a_year = 365.24; - constexpr double local_julian_day_on_m1_1_1 = 1721423.5; + const double days_in_a_year = Calendar::daysInYearGregorian(); + const double julian_day_on_m1_1_1 = Calendar::jdOfGregorianYear1Start(); // 1 行ずつ読み込み table.forEachRow([&](std::size_t row_index, const std::vector& row) { @@ -131,7 +131,7 @@ namespace paxs { const int min_year = !first_jd_str.empty() ? StringUtils::safeStoi(first_jd_str, -99999999, true) : (!first_year_str.empty() ? - static_cast(StringUtils::safeStod(first_year_str, -99999999.0, true) * local_days_in_a_year + local_julian_day_on_m1_1_1) : + static_cast(StringUtils::safeStod(first_year_str, -99999999.0, true) * days_in_a_year + julian_day_on_m1_1_1) : -99999999); // 可視化する時代(~新しい年) @@ -140,7 +140,7 @@ namespace paxs { const int max_year = !last_jd_str.empty() ? StringUtils::safeStoi(last_jd_str, 99999999, true) : (!last_year_str.empty() ? - static_cast(StringUtils::safeStod(last_year_str, 99999999.0, true) * local_days_in_a_year + local_julian_day_on_m1_1_1) : + static_cast(StringUtils::safeStod(last_year_str, 99999999.0, true) * days_in_a_year + julian_day_on_m1_1_1) : 99999999); // 画像 diff --git a/Library/PAX_SAPIENTICA/Map/Repository/FlowCurveRepository.hpp b/Library/PAX_SAPIENTICA/Map/Repository/FlowCurveRepository.hpp new file mode 100644 index 000000000..ef9081013 --- /dev/null +++ b/Library/PAX_SAPIENTICA/Map/Repository/FlowCurveRepository.hpp @@ -0,0 +1,153 @@ +/*########################################################################################## + + PAX SAPIENTICA Library 💀🌿🌏 + + [Planning] 2023-2024 As Project + [Production] 2023-2024 As Project + [Contact Us] wanotaitei@gmail.com https://github.com/AsPJT/PAX_SAPIENTICA + [License] Distributed under the CC0 1.0. https://creativecommons.org/publicdomain/zero/1.0/ + +##########################################################################################*/ + +#ifndef PAX_SAPIENTICA_MAP_REPOSITORY_FLOW_CURVE_REPOSITORY_HPP +#define PAX_SAPIENTICA_MAP_REPOSITORY_FLOW_CURVE_REPOSITORY_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace paxs { + + /// @brief フロー曲線データの読み込みを担当する構造体 (Infrastructure Layer) + /// @brief Repository for loading flow curve data (Infrastructure Layer) + struct FlowCurveRepository { + /// @brief フロー曲線リストを読み込み、全てのFlowCurveLocationDataを返す + /// @brief Load flow curve data file list and return all FlowCurveLocationData + static std::vector> loadFlowCurveList() { + std::vector> all_flow_arrows; + + FeatureListLoader::loadFeatureList("FlowCurves", [&all_flow_arrows](const FeatureListParams& params) { + auto loaded = loadFlowCurveFromFile(params); + for (const auto& flow_arrow_data : loaded.flow_arrow_list) { + all_flow_arrows.emplace_back(flow_arrow_data, loaded); + } + }); + + return all_flow_arrows; + } + + /// @brief 個別ファイルからフロー曲線データを読み込み + /// @brief Load flow curve data from individual file + static FlowCurveLocationGroup loadFlowCurveFromFile(const FeatureListParams& params) { + std::vector flow_arrow_list{}; // フロー曲線の一覧 / List of flow curves + const std::uint_least32_t feature_type_hash = MurMur3::calcHash(params.type.size(), params.type.c_str()); + + const std::string flow_arrow_key = params.key; + if (flow_arrow_key.empty()) { + PAXS_WARNING("FlowCurves.tsv key is empty for file: " + params.file_path); + return FlowCurveLocationGroup(); + } + + paxs::TsvTable table(params.file_path); + if (!table.isSuccessfullyLoaded()) { + PAXS_WARNING("Failed to load flow curve file: " + params.file_path); + return FlowCurveLocationGroup(); + } + + // 必須カラムの検証 + if (!table.hasColumn("longitude")) { + PAXS_ERROR("FlowCurveFile is missing required column: longitude"); + return FlowCurveLocationGroup(); + } + if (!table.hasColumn("latitude")) { + PAXS_ERROR("FlowCurveFile is missing required column: latitude"); + return FlowCurveLocationGroup(); + } + + // カラムハッシュの設定 + const std::uint_least32_t longitude_hash = MurMur3::calcHash("longitude"); + const std::uint_least32_t latitude_hash = MurMur3::calcHash("latitude"); + + // 座標列を読み込み + std::vector coordinates; + + // 1 行ずつ読み込み + table.forEachRow([&](std::size_t row_index, const std::vector&) { + const std::string& longitude_str = table.get(row_index, longitude_hash); + const std::string& latitude_str = table.get(row_index, latitude_hash); + + if (longitude_str.empty() || latitude_str.empty()) { + PAXS_WARNING("Skipping row " + std::to_string(row_index) + " in " + params.file_path + ": missing coordinates"); + return; + } + + // 経度の変換 + auto lon_opt = StringUtils::toDouble(longitude_str); + if (!lon_opt) { + PAXS_WARNING("Invalid longitude at row " + std::to_string(row_index) + " in " + params.file_path + ": \"" + longitude_str + "\""); + return; + } + double point_longitude = *lon_opt; + + // 緯度の変換 + auto lat_opt = StringUtils::toDouble(latitude_str); + if (!lat_opt) { + PAXS_WARNING("Invalid latitude at row " + std::to_string(row_index) + " in " + params.file_path + ": \"" + latitude_str + "\""); + return; + } + double point_latitude = *lat_opt; + + // EPSG:3857(Webメルカトル) 座標に変換して格納 + coordinates.emplace_back( + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(point_longitude, point_latitude) + ).toWebMercatorDeg() + ); + }); + + // 座標を何も読み込んでいない場合は何もしないで終わる + if (coordinates.empty()) { + PAXS_WARNING("No valid coordinates loaded from file: " + params.file_path); + return FlowCurveLocationGroup(); + } + + // FlowCurveLocationDataを作成 + flow_arrow_list.emplace_back( + flow_arrow_key, + std::move(coordinates), + params.zoom_range, + params.year_range, + feature_type_hash + ); + + // FlowCurves.tsvからcolor_hashとline_widthを取得(paramsのtexture_hashを色として使用) + // TODO: 将来的にはcolorとline_width専用のカラムを追加 + const std::uint_least32_t flow_arrow_color_hash = params.texture_hash; // textureカラムを色として使用 + const float flow_arrow_line_width = static_cast(params.zoom); // zoomカラムを線幅として使用 + + // 読み込んだファイルを格納する + return FlowCurveLocationGroup( + flow_arrow_list, + params.zoom_range, + params.year_range, + feature_type_hash, + flow_arrow_color_hash, + flow_arrow_line_width + ); + } + }; + +} + +#endif // !PAX_SAPIENTICA_MAP_REPOSITORY_FLOW_CURVE_REPOSITORY_HPP diff --git a/Library/PAX_SAPIENTICA/Map/Repository/GenomeRepository.hpp b/Library/PAX_SAPIENTICA/Map/Repository/GenomeRepository.hpp index 3276f0128..51327c81a 100644 --- a/Library/PAX_SAPIENTICA/Map/Repository/GenomeRepository.hpp +++ b/Library/PAX_SAPIENTICA/Map/Repository/GenomeRepository.hpp @@ -89,8 +89,8 @@ namespace paxs { // LocationPointを構築 location_point_list.emplace_back( data.key, - paxs::EquirectangularDeg( - paxs::Vector2(data.longitude, data.latitude)).toMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(data.longitude, data.latitude)).toWebMercatorDeg(), data.overall_length, data.zoom_range, data.year_range, @@ -109,10 +109,10 @@ namespace paxs { // LocationPointGroupを構築して返す return LocationPointGroup(location_point_list, - paxs::EquirectangularDeg( - paxs::Vector2(bounds.start_longitude, bounds.start_latitude)).toMercatorDeg(), - paxs::EquirectangularDeg( - paxs::Vector2(bounds.end_longitude, bounds.end_latitude)).toMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(bounds.start_longitude, bounds.start_latitude)).toWebMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(bounds.end_longitude, bounds.end_latitude)).toWebMercatorDeg(), params.zoom_range, params.year_range, feature_type_hash, params.texture_hash); diff --git a/Library/PAX_SAPIENTICA/Map/Repository/GeographicFeatureRepository.hpp b/Library/PAX_SAPIENTICA/Map/Repository/GeographicFeatureRepository.hpp index 8b8565f99..0106bc633 100644 --- a/Library/PAX_SAPIENTICA/Map/Repository/GeographicFeatureRepository.hpp +++ b/Library/PAX_SAPIENTICA/Map/Repository/GeographicFeatureRepository.hpp @@ -88,8 +88,8 @@ namespace paxs { // LocationPointを構築 location_point_list.emplace_back( data.key, - paxs::EquirectangularDeg( - paxs::Vector2(data.longitude, data.latitude)).toMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(data.longitude, data.latitude)).toWebMercatorDeg(), data.overall_length, data.zoom_range, data.year_range, @@ -106,10 +106,10 @@ namespace paxs { } return LocationPointGroup(location_point_list, - paxs::EquirectangularDeg( - paxs::Vector2(bounds.start_longitude, bounds.start_latitude)).toMercatorDeg(), - paxs::EquirectangularDeg( - paxs::Vector2(bounds.end_longitude, bounds.end_latitude)).toMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(bounds.start_longitude, bounds.start_latitude)).toWebMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(bounds.end_longitude, bounds.end_latitude)).toWebMercatorDeg(), params.zoom_range, params.year_range, feature_type_hash, params.texture_hash); diff --git a/Library/PAX_SAPIENTICA/Map/Repository/LocationDataLoader.hpp b/Library/PAX_SAPIENTICA/Map/Repository/LocationDataLoader.hpp index 637d3c208..33ddc5301 100644 --- a/Library/PAX_SAPIENTICA/Map/Repository/LocationDataLoader.hpp +++ b/Library/PAX_SAPIENTICA/Map/Repository/LocationDataLoader.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -26,16 +27,6 @@ namespace paxs { - // 内部実装用の名前空間 - namespace location_data_loader_detail { - // 日付計算用の定数(Repository用の簡易版) - // Note: LocationPoint.hppには365.2425と1721060.0が定義されているが、 - // 既存のRepository実装との互換性のため、ここでは整数値を使用 - // TODO: 確認 - constexpr int days_in_a_year = 365; - constexpr int julian_day_on_m1_1_1 = 1721058; - } - /// @brief TSVカラムのハッシュ値を事前計算して保持する構造体 /// @brief Struct to hold pre-computed hash values for TSV columns struct ColumnHashes { @@ -175,9 +166,12 @@ namespace paxs { // 年が指定されている場合 if (!year_str.empty()) { + const double days_in_a_year = Calendar::daysInYearGregorian(); + const double julian_day_on_m1_1_1 = Calendar::jdOfGregorianYear1Start(); + auto year_opt = StringUtils::toDouble(year_str); if (year_opt) { - return (*year_opt * location_data_loader_detail::days_in_a_year) + location_data_loader_detail::julian_day_on_m1_1_1; + return (*year_opt * days_in_a_year) + julian_day_on_m1_1_1; } PAXS_WARNING("Invalid year value: \"" + year_str + "\", using default: " + std::to_string(default_value)); return default_value; diff --git a/Library/PAX_SAPIENTICA/Map/Repository/PersonRepository.hpp b/Library/PAX_SAPIENTICA/Map/Repository/PersonRepository.hpp index 8034e3168..879b0750e 100644 --- a/Library/PAX_SAPIENTICA/Map/Repository/PersonRepository.hpp +++ b/Library/PAX_SAPIENTICA/Map/Repository/PersonRepository.hpp @@ -151,14 +151,14 @@ namespace paxs { // 格納 person_location_list.emplace_back( person_key, - paxs::EquirectangularDeg( + paxs::EPSG4326_WGS84Deg( paxs::Vector2( start_point_longitude, // 経度 - start_point_latitude)).toMercatorDeg(), // 緯度 - paxs::EquirectangularDeg( + start_point_latitude)).toWebMercatorDeg(), // 緯度 + paxs::EPSG4326_WGS84Deg( paxs::Vector2( end_point_longitude, // 経度 - end_point_latitude)).toMercatorDeg(), // 緯度 + end_point_latitude)).toWebMercatorDeg(), // 緯度 Range( StringUtils::safeStod(min_size_str, params.zoom_range.minimum, true), StringUtils::safeStod(max_size_str, params.zoom_range.maximum, true) diff --git a/Library/PAX_SAPIENTICA/Map/Repository/PlaceNameRepository.hpp b/Library/PAX_SAPIENTICA/Map/Repository/PlaceNameRepository.hpp index 20304b504..32f093344 100644 --- a/Library/PAX_SAPIENTICA/Map/Repository/PlaceNameRepository.hpp +++ b/Library/PAX_SAPIENTICA/Map/Repository/PlaceNameRepository.hpp @@ -88,8 +88,8 @@ namespace paxs { // LocationPointを構築(地名は常にparams.texture_hashを使用) location_point_list.emplace_back( data.key, - paxs::EquirectangularDeg( - paxs::Vector2(data.longitude, data.latitude)).toMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(data.longitude, data.latitude)).toWebMercatorDeg(), data.overall_length, data.zoom_range, data.year_range, @@ -108,10 +108,10 @@ namespace paxs { // LocationPointGroupを構築して返す return LocationPointGroup(location_point_list, - paxs::EquirectangularDeg( - paxs::Vector2(bounds.start_longitude, bounds.start_latitude)).toMercatorDeg(), - paxs::EquirectangularDeg( - paxs::Vector2(bounds.end_longitude, bounds.end_latitude)).toMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(bounds.start_longitude, bounds.start_latitude)).toWebMercatorDeg(), + paxs::EPSG4326_WGS84Deg( + paxs::Vector2(bounds.end_longitude, bounds.end_latitude)).toWebMercatorDeg(), params.zoom_range, params.year_range, feature_type_hash, params.texture_hash); diff --git a/Library/PAX_SAPIENTICA/Map/Repository/TerritoryRepository.hpp b/Library/PAX_SAPIENTICA/Map/Repository/TerritoryRepository.hpp index 20687848b..bc9af4fe2 100644 --- a/Library/PAX_SAPIENTICA/Map/Repository/TerritoryRepository.hpp +++ b/Library/PAX_SAPIENTICA/Map/Repository/TerritoryRepository.hpp @@ -80,7 +80,7 @@ namespace paxs { const std::uint_least32_t latitude_hash = MurMur3::calcHash("latitude"); // 座標列を読み込み - std::vector coordinates; + std::vector coordinates; // 1 行ずつ読み込み table.forEachRow([&](std::size_t row_index, const std::vector&) { @@ -108,11 +108,11 @@ namespace paxs { } double point_latitude = *lat_opt; - // メルカトル座標に変換して格納 + // EPSG:3857(Webメルカトル) 座標に変換して格納 coordinates.emplace_back( - paxs::EquirectangularDeg( + paxs::EPSG4326_WGS84Deg( paxs::Vector2(point_longitude, point_latitude) - ).toMercatorDeg() + ).toWebMercatorDeg() ); }); diff --git a/Library/PAX_SAPIENTICA/Map/TerritoryLocationPoint.hpp b/Library/PAX_SAPIENTICA/Map/TerritoryLocationPoint.hpp index 52ea2cf93..ff7f5a9e0 100644 --- a/Library/PAX_SAPIENTICA/Map/TerritoryLocationPoint.hpp +++ b/Library/PAX_SAPIENTICA/Map/TerritoryLocationPoint.hpp @@ -29,7 +29,7 @@ namespace paxs { explicit TerritoryLocationData() = default; explicit TerritoryLocationData( std::string key_, // 領域の一意キー / Unique key for territory - std::vector&& coordinates_, // スプライン曲線の座標列 / Coordinates for spline curve + std::vector&& coordinates_, // スプライン曲線の座標列 / Coordinates for spline curve const Range& zoom_range_, const Range& year_range_, const std::uint_least32_t feature_type_hash_ @@ -41,7 +41,7 @@ namespace paxs { feature_type_hash(feature_type_hash_) {} std::string key; // 領域の一意キー / Unique key for territory - std::vector coordinates; // スプライン曲線の座標列 / Coordinates for spline curve + std::vector coordinates; // スプライン曲線の座標列 / Coordinates for spline curve Range zoom_range{0.0, 9999.0}; // 表示するズームレベル範囲 / Zoom level range Range year_range{-99999999.0, 99999999.0}; // 可視化する時代 / Time range std::uint_least32_t feature_type_hash = MurMur3::calcHash("territory"); // 地物の種別 / Feature type hash diff --git a/Library/PAX_SAPIENTICA/Simulation/Algorithm/AStar.hpp b/Library/PAX_SAPIENTICA/Simulation/Algorithm/AStar.hpp index 9945ddfb8..a439943cf 100644 --- a/Library/PAX_SAPIENTICA/Simulation/Algorithm/AStar.hpp +++ b/Library/PAX_SAPIENTICA/Simulation/Algorithm/AStar.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -145,7 +146,7 @@ namespace paxs { const AStarVec2 neighbour_z = neighbour * z; if (!search_range.contains(neighbour)) continue; // コスト計算 - const double node_cost = calcCost(environment, neighbour_z) * ((x <= 3 /* 斜め移動 */) ? 1.41421356237 : 1.0) + node_.cost; + const double node_cost = calcCost(environment, neighbour_z) * ((x <= 3 /* 斜め移動 */) ? MathConstants::sqrt2() : 1.0) + node_.cost; const GridType distance = calculateDistance(neighbour); if (existPoint(neighbour, node_cost + distance)) continue; open.emplace_back(AStarNode(neighbour, node_.position, distance, node_cost)); diff --git a/Library/PAX_SAPIENTICA/Simulation/Config/Object.hpp b/Library/PAX_SAPIENTICA/Simulation/Config/Object.hpp index d321cf61b..dd02c24ab 100644 --- a/Library/PAX_SAPIENTICA/Simulation/Config/Object.hpp +++ b/Library/PAX_SAPIENTICA/Simulation/Config/Object.hpp @@ -48,7 +48,7 @@ namespace paxs { constexpr void setPosition(const Vector2& pos) noexcept { position = pos; } /// @brief Get the mercator coordinate from the XYZTile coordinate. - /// @brief 座標をメルカトル座標で取得 + /// @brief 座標をWebメルカトル座標で取得 paxs::Vector2 getLocation(const paxs::Vector2& start_position, const int z) const noexcept { return MapUtils::tilePixelToAngleSpace(start_position, position, z); } diff --git a/Library/PAX_SAPIENTICA/Simulation/Manager/SettlementSimulator.hpp b/Library/PAX_SAPIENTICA/Simulation/Manager/SettlementSimulator.hpp index fddd6d7eb..996f49123 100644 --- a/Library/PAX_SAPIENTICA/Simulation/Manager/SettlementSimulator.hpp +++ b/Library/PAX_SAPIENTICA/Simulation/Manager/SettlementSimulator.hpp @@ -14,9 +14,7 @@ #include #include -#include #include -#include #include #include #include // for std::pair @@ -40,7 +38,7 @@ namespace paxs { // シミュレーション出力フォルダ用の時刻取得 std::string calcDateTime() { - return TimeUtils::getCurrentDateTime("%Y-%m-%d-%H-%M-%S"); + return TimeUtils::getFilenameSafeDateTime(); } class SettlementSimulator { @@ -237,8 +235,8 @@ namespace paxs { /// @brief Execute the simulation for the one step. /// @brief シミュレーションを1ステップ実行する void step() noexcept { - std::chrono::system_clock::time_point start_time, end_time, move_time, m_start_time, m_end_time; - start_time = std::chrono::system_clock::now(); // 計測開始 + // パフォーマンス計測開始(TimeUtilsを使用) + const auto start_time = TimeUtils::now(); // 指定したステップおきに出力する if (step_count % SimulationConstants::getInstance().output_step_frequency == 0) { @@ -372,8 +370,8 @@ namespace paxs { for (auto& move : move_list) { moveSettlement(std::get<0>(move), std::get<1>(move), std::get<2>(move)); } - move_time = std::chrono::system_clock::now(); // 移動 - move_processing_time = static_cast(std::chrono::duration_cast(move_time - start_time).count() / 1000.0); + const auto move_time = TimeUtils::now(); // 移動計測 + move_processing_time = TimeUtils::getDifferenceSeconds(start_time, move_time); for (auto& settlement_grid : settlement_grids) { for (auto& settlement : settlement_grid.second.getSettlements()) { @@ -388,7 +386,7 @@ namespace paxs { randomizeSettlements(false, true /* 渡来人 */, (step_count >= SimulationConstants::getInstance().bronze_start_steps)/*青銅*/); } - m_start_time = std::chrono::system_clock::now(); // 婚姻計測開始 + const auto m_start_time = TimeUtils::now(); // 婚姻計測開始 marriage_pos_list.clear(); bronze_share_list.clear(); // 青銅交換リストのクリア @@ -438,8 +436,8 @@ namespace paxs { } } - m_end_time = std::chrono::system_clock::now(); // 婚姻計測終了 - marriage_processing_time = static_cast(std::chrono::duration_cast(m_end_time - m_start_time).count() / 1000.0); + const auto m_end_time = TimeUtils::now(); // 婚姻計測終了 + marriage_processing_time = TimeUtils::getDifferenceSeconds(m_start_time, m_end_time); for (auto& settlement_grid : settlement_grids) { for (auto& settlement : settlement_grid.second.getSettlements()) { @@ -464,8 +462,8 @@ namespace paxs { japan_provinces->update(); } ++step_count; // ステップ数を増やす - end_time = std::chrono::system_clock::now(); // 計測終了 - processing_time = static_cast(std::chrono::duration_cast(end_time - start_time).count() / 1000.0); + const auto end_time = TimeUtils::now(); // 計測終了 + processing_time = TimeUtils::getDifferenceSeconds(start_time, end_time); } // 婚姻時に移動した位置一覧を取得する diff --git a/Library/PAX_SAPIENTICA/Simulation/Output/SimulationResultWriter.hpp b/Library/PAX_SAPIENTICA/Simulation/Output/SimulationResultWriter.hpp index 7f6f0ceb7..91cb891d3 100644 --- a/Library/PAX_SAPIENTICA/Simulation/Output/SimulationResultWriter.hpp +++ b/Library/PAX_SAPIENTICA/Simulation/Output/SimulationResultWriter.hpp @@ -90,7 +90,7 @@ namespace paxs { /// @brief Get current date and time as string /// @return YYYY-MM-DD-HH-MM-SS 形式の文字列 / String in YYYY-MM-DD-HH-MM-SS format std::string calcDateTime() const { - return TimeUtils::getCurrentDateTime("%Y-%m-%d-%H-%M-%S"); + return TimeUtils::getFilenameSafeDateTime(); } /// @brief ファイルストリームを閉じる diff --git a/Library/PAX_SAPIENTICA/Utility/ExecutablePath.hpp b/Library/PAX_SAPIENTICA/Utility/ExecutablePath.hpp index f6485d9ec..47f9aa340 100644 --- a/Library/PAX_SAPIENTICA/Utility/ExecutablePath.hpp +++ b/Library/PAX_SAPIENTICA/Utility/ExecutablePath.hpp @@ -70,7 +70,7 @@ namespace paxs { return ""; #elif defined(PAXS_PLATFORM_LINUX) - // Linux (Ubuntu) implementation + // Linux implementation char exe_path[PATH_MAX]; ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); if (len != -1) { @@ -136,7 +136,7 @@ namespace paxs { return ""; #elif defined(PAXS_PLATFORM_LINUX) - // Linux (Ubuntu) implementation + // Linux implementation char exe_path[PATH_MAX]; ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); if (len != -1) { diff --git a/Library/PAX_SAPIENTICA/Utility/Logger.hpp b/Library/PAX_SAPIENTICA/Utility/Logger.hpp index 81f92ae85..31a57fc1b 100644 --- a/Library/PAX_SAPIENTICA/Utility/Logger.hpp +++ b/Library/PAX_SAPIENTICA/Utility/Logger.hpp @@ -12,7 +12,6 @@ #ifndef PAX_SAPIENTICA_LOGGER_HPP #define PAX_SAPIENTICA_LOGGER_HPP -#include #include #include #include diff --git a/Library/PAX_SAPIENTICA/Utility/MapUtils.hpp b/Library/PAX_SAPIENTICA/Utility/MapUtils.hpp index 34778a55b..d578ba0f8 100644 --- a/Library/PAX_SAPIENTICA/Utility/MapUtils.hpp +++ b/Library/PAX_SAPIENTICA/Utility/MapUtils.hpp @@ -30,19 +30,19 @@ namespace paxs { * @param start_position タイルの開始位置(タイル座標系) * @param position ピクセル位置(ピクセル座標系) * @param z ズームレベル(0から始まる) - * @return Vector2(経度 longitude, 緯度 latitude) 度数法、-180~180度 + * @return Vector2(X座標, Y座標)、範囲:-180~180 */ - // TODO: これを使っている場所でメルカトル座標として扱っている箇所を確認する + // TODO: これを使っている場所でWebメルカトル座標として扱っている箇所を確認する static paxs::Vector2 tilePixelToAngleSpace(const paxs::Vector2& start_position, const paxs::Vector2& position, const int z) noexcept { const double n = std::pow(2, z); // ズームレベルzにおけるタイル数(2^z) const double pixel_sum = pixel_size * n; // 全体のピクセル数 const double u = (double)position.x / pixel_sum + start_position.x / n; // 正規化座標 u (0~1) const double v = (double)position.y / pixel_sum + start_position.y / n; // 正規化座標 v (0~1) - const double lon = u * Math::deg360() - Math::deg180(); // 経度 (-180~180度) - const double lat = -v * Math::deg360() + Math::deg180(); // 緯度 (-180~180度) + const double x = u * Math::deg360() - Math::deg180(); // X座標 (-180~180) + const double y = -v * Math::deg360() + Math::deg180(); // Y座標 (-180~180) - return {lon, lat}; + return {x, y}; } }; diff --git a/Library/PAX_SAPIENTICA/Utility/TimeUtils.hpp b/Library/PAX_SAPIENTICA/Utility/TimeUtils.hpp index 9f525864a..ae0b05aa2 100644 --- a/Library/PAX_SAPIENTICA/Utility/TimeUtils.hpp +++ b/Library/PAX_SAPIENTICA/Utility/TimeUtils.hpp @@ -12,63 +12,99 @@ #ifndef PAX_SAPIENTICA_UTILITY_TIME_UTILS_HPP #define PAX_SAPIENTICA_UTILITY_TIME_UTILS_HPP +#include #include +#include #include +#include + namespace paxs { /// @brief 時刻関連のユーティリティ関数 /// @brief Time-related utility functions - struct TimeUtils { - /// @brief time_t を std::tm に安全に変換する(プラットフォーム依存) - /// @brief Safely convert time_t to std::tm (platform-dependent) - /// @param time 変換する時刻 / Time to convert - /// @param result 結果を格納する std::tm 構造体 / std::tm structure to store the result - /// @return true if successful, false otherwise - static bool localTimeSafe(const std::time_t& time, std::tm& result) noexcept { -#ifdef _MSC_VER - // Windows: localtime_s を使用 - return localtime_s(&result, &time) == 0; -#else - // POSIX: localtime_r を使用 - return localtime_r(&time, &result) != nullptr; -#endif - } - + class TimeUtils { + public: /// @brief 現在の日時を指定されたフォーマットで文字列として取得する /// @brief Get the current date and time as a string in the specified format - /// @param format strftime フォーマット文字列 / strftime format string (default: "%Y-%m-%d %X") - /// @param buffer_size バッファサイズ / Buffer size (default: 128) + /// @param format strftime互換フォーマット文字列 / strftime-compatible format string + /// @param buffer_size バッファサイズ(内部用) / Buffer size (for internal use) /// @return フォーマットされた日時文字列 / Formatted date-time string static std::string getCurrentDateTime(const char* format = "%Y-%m-%d %X", std::size_t buffer_size = 128) noexcept { - const std::time_t now = std::time(nullptr); - std::tm tm_buf; + using namespace std::chrono; - if (!localTimeSafe(now, tm_buf)) { - return ""; // エラー時は空文字列を返す - } + try { + // システムクロックから現在時刻を取得 + auto now = system_clock::now(); - std::string buffer(buffer_size, '\0'); - const std::size_t written = std::strftime(&buffer[0], buffer_size, format, &tm_buf); + // time_tに変換 + std::time_t now_time_t = system_clock::to_time_t(now); - if (written == 0) { - return ""; // フォーマット失敗時は空文字列を返す + // ローカル時刻に変換 + std::tm tm_buf{}; + if (!toLocalTimeSafe(now_time_t, tm_buf)) { + return ""; // エラー時は空文字列を返す + } + + // strftimeでフォーマット + std::string buffer(buffer_size, '\0'); + const std::size_t written = std::strftime(&buffer[0], buffer_size, format, &tm_buf); + + if (written == 0) { + return ""; // フォーマット失敗時は空文字列を返す + } + + buffer.resize(written); + return buffer; } + catch (...) { + // 例外が発生した場合は空文字列を返す + return ""; + } + } - buffer.resize(written); - return buffer; + /// @brief 指定された time_point を文字列に変換する + /// @brief Convert the specified time_point to a string + /// @param time_point 変換する時刻 / Time point to convert + /// @param format strftime互換フォーマット文字列 / strftime-compatible format string + /// @param buffer_size バッファサイズ / Buffer size + /// @return フォーマットされた日時文字列 / Formatted date-time string + template + static std::string formatTimePoint( + const std::chrono::time_point& time_point, + const char* format = "%Y-%m-%d-%H-%M-%S", + std::size_t buffer_size = 64 + ) noexcept { + using namespace std::chrono; + + try { + // system_clockに変換 + auto sys_time = time_point_cast( + time_point - Clock::now() + system_clock::now() + ); + + std::time_t time_t_val = system_clock::to_time_t(sys_time); + return formatTime(time_t_val, format, buffer_size); + } + catch (...) { + return ""; + } } /// @brief 指定された time_t を文字列に変換する /// @brief Convert the specified time_t to a string /// @param time 変換する時刻 / Time to convert - /// @param format strftime フォーマット文字列 / strftime format string (default: "%Y-%m-%d-%H-%M-%S") - /// @param buffer_size バッファサイズ / Buffer size (default: 64) + /// @param format strftime互換フォーマット文字列 / strftime-compatible format string + /// @param buffer_size バッファサイズ / Buffer size /// @return フォーマットされた日時文字列 / Formatted date-time string - static std::string formatTime(const std::time_t& time, const char* format = "%Y-%m-%d-%H-%M-%S", std::size_t buffer_size = 64) noexcept { - std::tm tm_buf; + static std::string formatTime( + const std::time_t& time, + const char* format = "%Y-%m-%d-%H-%M-%S", + std::size_t buffer_size = 64 + ) noexcept { + std::tm tm_buf{}; - if (!localTimeSafe(time, tm_buf)) { + if (!toLocalTimeSafe(time, tm_buf)) { return ""; // エラー時は空文字列を返す } @@ -82,6 +118,165 @@ namespace paxs { buffer.resize(written); return buffer; } + + /// @brief 現在のUNIXタイムスタンプを取得(秒単位) + /// @brief Get current UNIX timestamp in seconds + /// @return 秒単位のタイムスタンプ / Timestamp in seconds + static std::int64_t getCurrentTimestamp() noexcept { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); + } + + /// @brief 現在のタイムスタンプを取得(ミリ秒単位) + /// @brief Get current timestamp in milliseconds + /// @return ミリ秒単位のタイムスタンプ / Timestamp in milliseconds + static std::int64_t getCurrentTimestampMillis() noexcept { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); + } + + // ======================================================================== + // パフォーマンス計測用ユーティリティ + // Performance Measurement Utilities + // ======================================================================== + + /// @brief パフォーマンス計測用の時刻型(単調増加保証) + /// @brief Time point type for performance measurement (monotonic) + using PerformanceTimePoint = std::chrono::steady_clock::time_point; + + /// @brief パフォーマンス計測用の現在時刻を取得 + /// @brief Get current time for performance measurement + /// @return 現在の時刻(単調増加保証) / Current time point (monotonic) + static PerformanceTimePoint now() noexcept { + return std::chrono::steady_clock::now(); + } + + /// @brief 2つの時刻の差を計算(秒) + /// @brief Calculate time difference in seconds + /// @tparam TimePoint 時刻の型 / Type of time point + /// @param start 開始時刻 / Start time + /// @param end 終了時刻 / End time + /// @return 秒単位の差 / Difference in seconds + template + static double getDifferenceSeconds(const TimePoint& start, const TimePoint& end) noexcept { + using namespace std::chrono; + return duration_cast>(end - start).count(); + } + + /// @brief 2つの時刻の差を計算(ミリ秒) + /// @brief Calculate time difference in milliseconds + /// @tparam TimePoint 時刻の型 / Type of time point + /// @param start 開始時刻 / Start time + /// @param end 終了時刻 / End time + /// @return ミリ秒単位の差 / Difference in milliseconds + template + static double getDifferenceMilliseconds(const TimePoint& start, const TimePoint& end) noexcept { + using namespace std::chrono; + return duration_cast>(end - start).count(); + } + + /// @brief 開始時刻からの経過時間を計算(秒) + /// @brief Calculate elapsed time from start (seconds) + /// @tparam TimePoint 時刻の型 / Type of time point + /// @param start 開始時刻 / Start time + /// @return 経過時間(秒) / Elapsed time in seconds + template + static double getElapsedSeconds(const TimePoint& start) noexcept { + return getDifferenceSeconds(start, now()); + } + + /// @brief 開始時刻からの経過時間を計算(ミリ秒) + /// @brief Calculate elapsed time from start (milliseconds) + /// @tparam TimePoint 時刻の型 / Type of time point + /// @param start 開始時刻 / Start time + /// @return 経過時間(ミリ秒) / Elapsed time in milliseconds + template + static double getElapsedMilliseconds(const TimePoint& start) noexcept { + return getDifferenceMilliseconds(start, now()); + } + + /// @brief ISO 8601形式の日時文字列を取得 + /// @brief Get ISO 8601 formatted date-time string + /// @return ISO 8601形式の文字列(例: "2024-11-25T12:34:56") + static std::string getISO8601DateTime() noexcept { + return getCurrentDateTime("%Y-%m-%dT%H:%M:%S"); + } + + /// @brief ファイル名に適した日時文字列を取得 + /// @brief Get filename-safe date-time string + /// @return ファイル名用の文字列(例: "2024-11-25-12-34-56") + static std::string getFilenameSafeDateTime() noexcept { + return getCurrentDateTime("%Y-%m-%d-%H-%M-%S"); + } + + private: + + /// @brief time_t を std::tm に変換する + /// @brief Convert time_t to std::tm + /// @param time 変換する時刻 / Time to convert + /// @param result 結果を格納する std::tm 構造体 / std::tm structure to store the result + /// @return true if successful, false otherwise + static bool toLocalTimeSafe(const std::time_t& time, std::tm& result) noexcept { + try { + #if defined(PAXS_COMPILER_MSVC) + // localtime_s (スレッドセーフ) + // 第1引数: 出力バッファ, 第2引数: 入力 time_t + return ::localtime_s(&result, &time) == 0; + #else + // localtime_r (スレッドセーフ) + // 第1引数: 入力 time_t, 第2引数: 出力バッファ + return ::localtime_r(&time, &result) != nullptr; + #endif + } + catch (...) { + return false; + } + } + + /// @brief durationを人間が読みやすい文字列に変換 + /// @brief Convert duration to human-readable string + /// @tparam Rep duration の値型 / Value type of duration + /// @tparam Period duration の周期 / Period of duration + /// @param duration 変換する時間 / Duration to convert + /// @return 読みやすい文字列(例: "1h 23m 45s") / Human-readable string + template + static std::string formatDuration(const std::chrono::duration& duration) noexcept { + using namespace std::chrono; + + try { + auto h = duration_cast(duration); + auto m = duration_cast(duration - h); + auto s = duration_cast(duration - h - m); + auto ms = duration_cast(duration - h - m - s); + + std::ostringstream oss; + bool has_value = false; + + if (h.count() > 0) { + oss << h.count() << "h"; + has_value = true; + } + if (m.count() > 0 || has_value) { + if (has_value) oss << " "; + oss << m.count() << "m"; + has_value = true; + } + if (s.count() > 0 || has_value) { + if (has_value) oss << " "; + oss << s.count() << "s"; + has_value = true; + } + if (ms.count() > 0 || !has_value) { + if (has_value) oss << " "; + oss << ms.count() << "ms"; + } + + return oss.str(); + } + catch (...) { + return "0ms"; + } + } }; } // namespace paxs diff --git a/Projects/CMakeLists.txt b/Projects/CMakeLists.txt index 36533125a..04ae3c105 100644 --- a/Projects/CMakeLists.txt +++ b/Projects/CMakeLists.txt @@ -23,7 +23,7 @@ if(NOT DEFINED OS) if(APPLE) set(OS "macOS" CACHE STRING "Operating System name") elseif(UNIX) - set(OS "Ubuntu" CACHE STRING "Operating System name") + set(OS "Linux" CACHE STRING "Operating System name") elseif(WIN32) set(OS "Windows" CACHE STRING "Operating System name") else() diff --git a/Projects/IncludeTest/source/PAX_GRAPHICA/ToggleButtonIncludeTest.cpp b/Projects/IncludeTest/source/PAX_GRAPHICA/ToggleButtonIncludeTest.cpp new file mode 100644 index 000000000..848bd02d9 --- /dev/null +++ b/Projects/IncludeTest/source/PAX_GRAPHICA/ToggleButtonIncludeTest.cpp @@ -0,0 +1,3 @@ +#include + +int main(){} diff --git a/Projects/IncludeTest/source/PAX_MAHOROBA/UI/MenuBar/MenuBarToggleButtonIncludeTest.cpp b/Projects/IncludeTest/source/PAX_MAHOROBA/UI/MenuBar/MenuBarToggleButtonIncludeTest.cpp new file mode 100644 index 000000000..e421b4c4f --- /dev/null +++ b/Projects/IncludeTest/source/PAX_MAHOROBA/UI/MenuBar/MenuBarToggleButtonIncludeTest.cpp @@ -0,0 +1,3 @@ +#include + +int main(){} diff --git a/Projects/MapViewer/CMakeLists.txt b/Projects/MapViewer/CMakeLists.txt index dd37cdb27..f4982db90 100644 --- a/Projects/MapViewer/CMakeLists.txt +++ b/Projects/MapViewer/CMakeLists.txt @@ -18,7 +18,7 @@ if(NOT DEFINED OS) if(APPLE) set(OS "macOS" CACHE STRING "Operating System name") elseif(UNIX) - set(OS "Ubuntu" CACHE STRING "Operating System name") + set(OS "Linux" CACHE STRING "Operating System name") elseif(WIN32) set(OS "Windows" CACHE STRING "Operating System name") endif() diff --git a/Projects/MapViewer/Ubuntu/.gitignore b/Projects/MapViewer/Linux/.gitignore similarity index 100% rename from Projects/MapViewer/Ubuntu/.gitignore rename to Projects/MapViewer/Linux/.gitignore diff --git a/Projects/MapViewer/Ubuntu/Config.tsv b/Projects/MapViewer/Linux/Config.tsv similarity index 100% rename from Projects/MapViewer/Ubuntu/Config.tsv rename to Projects/MapViewer/Linux/Config.tsv diff --git a/Projects/MapViewer/Ubuntu/README.md b/Projects/MapViewer/Linux/README.md similarity index 100% rename from Projects/MapViewer/Ubuntu/README.md rename to Projects/MapViewer/Linux/README.md diff --git a/Projects/MapViewer/Ubuntu/SFML/Main.cpp b/Projects/MapViewer/Linux/SFML/Main.cpp similarity index 100% rename from Projects/MapViewer/Ubuntu/SFML/Main.cpp rename to Projects/MapViewer/Linux/SFML/Main.cpp diff --git a/Projects/MapViewer/Ubuntu/SFML/README.md b/Projects/MapViewer/Linux/SFML/README.md similarity index 100% rename from Projects/MapViewer/Ubuntu/SFML/README.md rename to Projects/MapViewer/Linux/SFML/README.md diff --git a/Projects/UnitTest/Source/PAX_SAPIENTICA/MapProjectionUnitTest.cpp b/Projects/UnitTest/Source/PAX_SAPIENTICA/MapProjectionUnitTest.cpp deleted file mode 100644 index 926ec0931..000000000 --- a/Projects/UnitTest/Source/PAX_SAPIENTICA/MapProjectionUnitTest.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/*########################################################################################## - - PAX SAPIENTICA Library 💀🌿🌏 - - [Planning] 2023-2024 As Project - [Production] 2023-2024 As Project - [Contact Us] wanotaitei@gmail.com https://github.com/AsPJT/PAX_SAPIENTICA - [License] Distributed under the CC0 1.0. https://creativecommons.org/publicdomain/zero/1.0/ - -##########################################################################################*/ - -#include - -#include - -const paxs::MercatorDeg mercatorDeg({0.0, 0.0}); - -TEST (MapProjectionUnitTest, toEquirectangularRadY) { - double expected = 0.0; - double actual = mercatorDeg.toEquirectangularRadY(); - ASSERT_EQ(expected, actual); -} - -