mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-07 06:32:08 +08:00
cabana/chart: populate the points incrementally (#30326)
* populate the points incrementally * less spacing old-commit-hash: 57621afcb0f4a95f5fd36cc47a7801a776383af5
This commit is contained in:
+46
-46
@@ -279,38 +279,50 @@ void ChartView::updateSeriesPoints() {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::updateSeries(const cabana::Signal *sig, bool clear) {
|
||||
void ChartView::appendCanEvents(const cabana::Signal *sig, const std::vector<const CanEvent *> &events,
|
||||
std::vector<QPointF> &vals, std::vector<QPointF> &step_vals) {
|
||||
vals.reserve(vals.size() + events.capacity());
|
||||
step_vals.reserve(step_vals.size() + events.capacity() * 2);
|
||||
|
||||
double value = 0;
|
||||
const uint64_t begin_mono_time = can->routeStartTime() * 1e9;
|
||||
for (const CanEvent *e : events) {
|
||||
if (sig->getValue(e->dat, e->size, &value)) {
|
||||
const double ts = (e->mono_time - std::min(e->mono_time, begin_mono_time)) / 1e9;
|
||||
vals.emplace_back(ts, value);
|
||||
if (!step_vals.empty())
|
||||
step_vals.emplace_back(ts, step_vals.back().y());
|
||||
step_vals.emplace_back(ts, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChartView::updateSeries(const cabana::Signal *sig, const MessageEventsMap *msg_new_events) {
|
||||
for (auto &s : sigs) {
|
||||
if (!sig || s.sig == sig) {
|
||||
if (clear) {
|
||||
if (!msg_new_events) {
|
||||
s.vals.clear();
|
||||
s.step_vals.clear();
|
||||
s.last_value_mono_time = 0;
|
||||
}
|
||||
auto events = msg_new_events ? msg_new_events : &can->eventsMap();
|
||||
auto it = events->find(s.msg_id);
|
||||
if (it == events->end() || it->second.empty()) continue;
|
||||
|
||||
if (s.vals.empty() || (it->second.back()->mono_time / 1e9 - can->routeStartTime()) > s.vals.back().x()) {
|
||||
appendCanEvents(s.sig, it->second, s.vals, s.step_vals);
|
||||
} else {
|
||||
std::vector<QPointF> vals, step_vals;
|
||||
appendCanEvents(s.sig, it->second, vals, step_vals);
|
||||
s.vals.insert(std::lower_bound(s.vals.begin(), s.vals.end(), vals.front().x(), xLessThan),
|
||||
vals.begin(), vals.end());
|
||||
s.step_vals.insert(std::lower_bound(s.step_vals.begin(), s.step_vals.end(), step_vals.front().x(), xLessThan),
|
||||
step_vals.begin(), step_vals.end());
|
||||
}
|
||||
|
||||
const auto &msgs = can->events(s.msg_id);
|
||||
s.vals.reserve(msgs.capacity());
|
||||
s.step_vals.reserve(msgs.capacity() * 2);
|
||||
|
||||
auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), s.last_value_mono_time, CompareCanEvent());
|
||||
const double route_start_time = can->routeStartTime();
|
||||
for (auto end = msgs.cend(); first != end; ++first) {
|
||||
const CanEvent *e = *first;
|
||||
double value = 0;
|
||||
if (s.sig->getValue(e->dat, e->size, &value)) {
|
||||
double ts = e->mono_time / 1e9 - route_start_time; // seconds
|
||||
s.vals.append({ts, value});
|
||||
if (!s.step_vals.empty()) {
|
||||
s.step_vals.append({ts, s.step_vals.back().y()});
|
||||
}
|
||||
s.step_vals.append({ts, value});
|
||||
s.last_value_mono_time = e->mono_time;
|
||||
}
|
||||
}
|
||||
if (!can->liveStreaming()) {
|
||||
s.segment_tree.build(s.vals);
|
||||
}
|
||||
s.series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals);
|
||||
s.series->replace(QVector<QPointF>::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals));
|
||||
}
|
||||
}
|
||||
updateAxisY();
|
||||
@@ -320,7 +332,7 @@ void ChartView::updateSeries(const cabana::Signal *sig, bool clear) {
|
||||
|
||||
// auto zoom on yaxis
|
||||
void ChartView::updateAxisY() {
|
||||
if (sigs.isEmpty()) return;
|
||||
if (sigs.empty()) return;
|
||||
|
||||
double min = std::numeric_limits<double>::max();
|
||||
double max = std::numeric_limits<double>::lowest();
|
||||
@@ -344,9 +356,7 @@ void ChartView::updateAxisY() {
|
||||
if (it->y() > s.max) s.max = it->y();
|
||||
}
|
||||
} else {
|
||||
auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last));
|
||||
s.min = min_y;
|
||||
s.max = max_y;
|
||||
std::tie(s.min, s.max) = s.segment_tree.minmax(std::distance(s.vals.cbegin(), first), std::distance(s.vals.cbegin(), last));
|
||||
}
|
||||
min = std::min(min, s.min);
|
||||
max = std::max(max, s.max);
|
||||
@@ -365,7 +375,7 @@ void ChartView::updateAxisY() {
|
||||
axis_y->setRange(min_y, max_y);
|
||||
axis_y->setTickCount(tick_count);
|
||||
|
||||
int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1;
|
||||
int n = std::max(int(-std::floor(std::log10((max_y - min_y) / (tick_count - 1)))), 0);
|
||||
int max_label_width = 0;
|
||||
QFontMetrics fm(axis_y->labelsFont());
|
||||
for (int i = 0; i < tick_count; i++) {
|
||||
@@ -453,7 +463,7 @@ static QPixmap getDropPixmap(const QPixmap &src) {
|
||||
return px;
|
||||
}
|
||||
|
||||
void ChartView::contextMenuEvent(QContextMenuEvent *event) {
|
||||
void ChartView::contextMenuEvent(QContextMenuEvent *event) {
|
||||
QMenu context_menu(this);
|
||||
context_menu.addActions(menu->actions());
|
||||
context_menu.addSeparator();
|
||||
@@ -492,13 +502,9 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) {
|
||||
rubber->hide();
|
||||
auto rect = rubber->geometry().normalized();
|
||||
double min = chart()->mapToValue(rect.topLeft()).x();
|
||||
double max = chart()->mapToValue(rect.bottomRight()).x();
|
||||
|
||||
// Prevent zooming/seeking past the end of the route
|
||||
min = std::clamp(min, 0., can->totalSeconds());
|
||||
max = std::clamp(max, 0., can->totalSeconds());
|
||||
|
||||
double min = std::clamp(chart()->mapToValue(rect.topLeft()).x(), 0., can->totalSeconds());
|
||||
double max = std::clamp(chart()->mapToValue(rect.bottomRight()).x(), 0., can->totalSeconds());
|
||||
if (rubber->width() <= 0) {
|
||||
// no rubber dragged, seek to mouse position
|
||||
can->seekTo(min);
|
||||
@@ -623,7 +629,7 @@ void ChartView::dropEvent(QDropEvent *event) {
|
||||
source_chart->chart()->removeSeries(s.series);
|
||||
addSeries(s.series);
|
||||
}
|
||||
sigs.append(source_chart->sigs);
|
||||
sigs.insert(sigs.end(), std::move_iterator(source_chart->sigs.begin()), std::move_iterator(source_chart->sigs.end()));
|
||||
updateAxisY();
|
||||
updateTitle();
|
||||
startAnimation();
|
||||
@@ -763,13 +769,8 @@ void ChartView::drawSignalValue(QPainter *painter) {
|
||||
painter->setPen(chart()->legend()->labelColor());
|
||||
int i = 0;
|
||||
for (auto &s : sigs) {
|
||||
QString value = "--";
|
||||
if (s.series->isVisible()) {
|
||||
auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; });
|
||||
if (it != s.vals.crend() && it->x() >= axis_x->min()) {
|
||||
value = s.sig->formatValue(it->y());
|
||||
}
|
||||
}
|
||||
auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; });
|
||||
QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? s.sig->formatValue(it->y()) : "--";
|
||||
QRectF marker_rect = legend_markers[i++]->sceneBoundingRect();
|
||||
QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size());
|
||||
QString elided_val = painter->fontMetrics().elidedText(value, Qt::ElideRight, value_rect.width());
|
||||
@@ -841,9 +842,8 @@ void ChartView::setSeriesType(SeriesType type) {
|
||||
s.series->deleteLater();
|
||||
}
|
||||
for (auto &s : sigs) {
|
||||
auto series = createSeries(series_type, s.sig->color);
|
||||
series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals);
|
||||
s.series = series;
|
||||
s.series = createSeries(series_type, s.sig->color);
|
||||
s.series->replace(QVector<QPointF>::fromStdVector(series_type == SeriesType::StepLine ? s.step_vals : s.vals));
|
||||
}
|
||||
updateSeriesPoints();
|
||||
updateTitle();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QGraphicsPixmapItem>
|
||||
@@ -31,7 +32,7 @@ public:
|
||||
ChartView(const std::pair<double, double> &x_range, ChartsWidget *parent = nullptr);
|
||||
void addSignal(const MessageId &msg_id, const cabana::Signal *sig);
|
||||
bool hasSignal(const MessageId &msg_id, const cabana::Signal *sig) const;
|
||||
void updateSeries(const cabana::Signal *sig = nullptr, bool clear = true);
|
||||
void updateSeries(const cabana::Signal *sig = nullptr, const MessageEventsMap *msg_new_events = nullptr);
|
||||
void updatePlot(double cur, double min, double max);
|
||||
void setSeriesType(SeriesType type);
|
||||
void updatePlotArea(int left, bool force = false);
|
||||
@@ -43,9 +44,8 @@ public:
|
||||
MessageId msg_id;
|
||||
const cabana::Signal *sig = nullptr;
|
||||
QXYSeries *series = nullptr;
|
||||
QVector<QPointF> vals;
|
||||
QVector<QPointF> step_vals;
|
||||
uint64_t last_value_mono_time = 0;
|
||||
std::vector<QPointF> vals;
|
||||
std::vector<QPointF> step_vals;
|
||||
QPointF track_pt{};
|
||||
SegmentTree segment_tree;
|
||||
double min = 0;
|
||||
@@ -64,6 +64,8 @@ private slots:
|
||||
void signalRemoved(const cabana::Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); }
|
||||
|
||||
private:
|
||||
void appendCanEvents(const cabana::Signal *sig, const std::vector<const CanEvent *> &events,
|
||||
std::vector<QPointF> &vals, std::vector<QPointF> &step_vals);
|
||||
void createToolButtons();
|
||||
void addSeries(QXYSeries *series);
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
@@ -107,7 +109,7 @@ private:
|
||||
QGraphicsProxyWidget *close_btn_proxy;
|
||||
QGraphicsProxyWidget *manage_btn_proxy;
|
||||
TipLabel tip_label;
|
||||
QList<SigItem> sigs;
|
||||
std::vector<SigItem> sigs;
|
||||
double cur_sec = 0;
|
||||
SeriesType series_type = SeriesType::Line;
|
||||
bool is_scrubbing = false;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "tools/cabana/chart/chart.h"
|
||||
|
||||
const int MAX_COLUMN_COUNT = 4;
|
||||
const int CHART_SPACING = 10;
|
||||
const int CHART_SPACING = 4;
|
||||
|
||||
ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_timer(this), QFrame(parent) {
|
||||
setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
|
||||
@@ -78,8 +78,9 @@ ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), auto_scroll_tim
|
||||
|
||||
// charts
|
||||
charts_container = new ChartsContainer(this);
|
||||
|
||||
charts_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
charts_scroll = new QScrollArea(this);
|
||||
charts_scroll->viewport()->setBackgroundRole(QPalette::Base);
|
||||
charts_scroll->setFrameStyle(QFrame::NoFrame);
|
||||
charts_scroll->setWidgetResizable(true);
|
||||
charts_scroll->setWidget(charts_container);
|
||||
@@ -149,11 +150,10 @@ void ChartsWidget::updateTabBar() {
|
||||
}
|
||||
}
|
||||
|
||||
void ChartsWidget::eventsMerged() {
|
||||
void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) {
|
||||
QFutureSynchronizer<void> future_synchronizer;
|
||||
bool clear = !can->liveStreaming();
|
||||
for (auto c : charts) {
|
||||
future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, clear));
|
||||
future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, &new_events));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ void ChartsWidget::splitChart(ChartView *src_chart) {
|
||||
it->series->setColor(it->sig->color);
|
||||
|
||||
c->addSeries(it->series);
|
||||
c->sigs.push_back(*it);
|
||||
c->sigs.emplace_back(std::move(*it));
|
||||
c->updateAxisY();
|
||||
c->updateTitle();
|
||||
it = src_chart->sigs.erase(it);
|
||||
@@ -322,7 +322,7 @@ void ChartsWidget::updateLayout(bool force) {
|
||||
}
|
||||
for (int i = 0; i < current_charts.size(); ++i) {
|
||||
charts_layout->addWidget(current_charts[i], i / n, i % n);
|
||||
if (current_charts[i]->sigs.isEmpty()) {
|
||||
if (current_charts[i]->sigs.empty()) {
|
||||
// the chart will be resized after add signal. delay setVisible to reduce flicker.
|
||||
QTimer::singleShot(0, [c = current_charts[i]]() { c->setVisible(true); });
|
||||
} else {
|
||||
@@ -474,8 +474,9 @@ bool ChartsWidget::event(QEvent *event) {
|
||||
|
||||
ChartsContainer::ChartsContainer(ChartsWidget *parent) : charts_widget(parent), QWidget(parent) {
|
||||
setAcceptDrops(true);
|
||||
setBackgroundRole(QPalette::Window);
|
||||
QVBoxLayout *charts_main_layout = new QVBoxLayout(this);
|
||||
charts_main_layout->setContentsMargins(0, 10, 0, 0);
|
||||
charts_main_layout->setContentsMargins(0, CHART_SPACING, 0, CHART_SPACING);
|
||||
charts_layout = new QGridLayout();
|
||||
charts_layout->setSpacing(CHART_SPACING);
|
||||
charts_main_layout->addLayout(charts_layout);
|
||||
@@ -519,15 +520,11 @@ void ChartsContainer::paintEvent(QPaintEvent *ev) {
|
||||
r.setHeight(CHART_SPACING);
|
||||
}
|
||||
|
||||
const int margin = (CHART_SPACING - 2) / 2;
|
||||
QPainterPath path;
|
||||
path.addPolygon(QPolygonF({r.topLeft(), QPointF(r.left() + CHART_SPACING, r.top() + r.height() / 2), r.bottomLeft()}));
|
||||
path.addPolygon(QPolygonF({r.topRight(), QPointF(r.right() - CHART_SPACING, r.top() + r.height() / 2), r.bottomRight()}));
|
||||
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.fillPath(path, palette().highlight());
|
||||
p.fillRect(r.adjusted(2, margin, -2, -margin), palette().highlight());
|
||||
p.setPen(QPen(palette().highlight(), 2));
|
||||
p.drawLine(r.topLeft() + QPoint(1, 0), r.bottomLeft() + QPoint(1, 0));
|
||||
p.drawLine(r.topLeft() + QPoint(0, r.height() / 2), r.topRight() + QPoint(0, r.height() / 2));
|
||||
p.drawLine(r.topRight(), r.bottomRight());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ private:
|
||||
void removeChart(ChartView *chart);
|
||||
void splitChart(ChartView *chart);
|
||||
QRect chartVisibleRect(ChartView *chart);
|
||||
void eventsMerged();
|
||||
void eventsMerged(const MessageEventsMap &new_events);
|
||||
void updateState();
|
||||
void zoomReset();
|
||||
void startAutoScroll();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
@@ -139,9 +138,10 @@ void AbstractStream::updateLastMsgsTo(double sec) {
|
||||
}
|
||||
|
||||
void AbstractStream::mergeEvents(std::vector<Event *>::const_iterator first, std::vector<Event *>::const_iterator last) {
|
||||
static std::unordered_map<MessageId, std::deque<const CanEvent *>> new_events_map;
|
||||
static MessageEventsMap msg_events;
|
||||
static std::vector<const CanEvent *> new_events;
|
||||
new_events_map.clear();
|
||||
|
||||
std::for_each(msg_events.begin(), msg_events.end(), [](auto &e) { e.second.clear(); });
|
||||
new_events.clear();
|
||||
|
||||
for (auto it = first; it != last; ++it) {
|
||||
@@ -156,25 +156,25 @@ void AbstractStream::mergeEvents(std::vector<Event *>::const_iterator first, std
|
||||
e->size = dat.size();
|
||||
memcpy(e->dat, (uint8_t *)dat.begin(), e->size);
|
||||
|
||||
new_events_map[{.source = e->src, .address = e->address}].push_back(e);
|
||||
msg_events[{.source = e->src, .address = e->address}].push_back(e);
|
||||
new_events.push_back(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &[id, new_e] : new_events_map) {
|
||||
auto &e = events_[id];
|
||||
auto insert_pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent());
|
||||
e.insert(insert_pos, new_e.cbegin(), new_e.cend());
|
||||
}
|
||||
|
||||
if (!new_events.empty()) {
|
||||
auto insert_pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent());
|
||||
all_events_.insert(insert_pos, new_events.cbegin(), new_events.cend());
|
||||
for (auto &[id, new_e] : msg_events) {
|
||||
if (!new_e.empty()) {
|
||||
auto &e = events_[id];
|
||||
auto pos = std::upper_bound(e.cbegin(), e.cend(), new_e.front()->mono_time, CompareCanEvent());
|
||||
e.insert(pos, new_e.cbegin(), new_e.cend());
|
||||
}
|
||||
}
|
||||
auto pos = std::upper_bound(all_events_.cbegin(), all_events_.cend(), new_events.front()->mono_time, CompareCanEvent());
|
||||
all_events_.insert(pos, new_events.cbegin(), new_events.cend());
|
||||
emit eventsMerged(msg_events);
|
||||
}
|
||||
|
||||
lastest_event_ts = all_events_.empty() ? 0 : all_events_.back()->mono_time;
|
||||
emit eventsMerged();
|
||||
}
|
||||
|
||||
// CanData
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
@@ -52,6 +51,8 @@ struct BusConfig {
|
||||
bool can_fd = false;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<MessageId, std::vector<const CanEvent *>> MessageEventsMap;
|
||||
|
||||
class AbstractStream : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -73,6 +74,7 @@ public:
|
||||
virtual double getSpeed() { return 1; }
|
||||
virtual bool isPaused() const { return false; }
|
||||
virtual void pause(bool pause) {}
|
||||
const MessageEventsMap &eventsMap() const { return events_; }
|
||||
const std::vector<const CanEvent *> &allEvents() const { return all_events_; }
|
||||
const std::vector<const CanEvent *> &events(const MessageId &id) const;
|
||||
virtual const std::vector<std::tuple<double, double, TimelineType>> getTimeline() { return {}; }
|
||||
@@ -82,7 +84,7 @@ signals:
|
||||
void resume();
|
||||
void seekedTo(double sec);
|
||||
void streamStarted();
|
||||
void eventsMerged();
|
||||
void eventsMerged(const MessageEventsMap &events_map);
|
||||
void updated();
|
||||
void msgsReceived(const QHash<MessageId, CanData> *new_msgs, bool has_new_ids);
|
||||
void sourcesUpdated(const SourceSet &s);
|
||||
@@ -104,7 +106,7 @@ protected:
|
||||
std::atomic<bool> processing = false;
|
||||
std::unique_ptr<QHash<MessageId, CanData>> new_msgs;
|
||||
QHash<MessageId, CanData> all_msgs;
|
||||
std::unordered_map<MessageId, std::vector<const CanEvent *>> events_;
|
||||
MessageEventsMap events_;
|
||||
std::vector<const CanEvent *> all_events_;
|
||||
std::unique_ptr<MonotonicBuffer> event_buffer;
|
||||
std::mutex mutex;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
// SegmentTree
|
||||
|
||||
void SegmentTree::build(const QVector<QPointF> &arr) {
|
||||
void SegmentTree::build(const std::vector<QPointF> &arr) {
|
||||
size = arr.size();
|
||||
tree.resize(4 * size); // size of the tree is 4 times the size of the array
|
||||
if (size > 0) {
|
||||
@@ -27,7 +27,7 @@ void SegmentTree::build(const QVector<QPointF> &arr) {
|
||||
}
|
||||
}
|
||||
|
||||
void SegmentTree::build_tree(const QVector<QPointF> &arr, int n, int left, int right) {
|
||||
void SegmentTree::build_tree(const std::vector<QPointF> &arr, int n, int left, int right) {
|
||||
if (left == right) {
|
||||
const double y = arr[left].y();
|
||||
tree[n] = {y, y};
|
||||
|
||||
+2
-2
@@ -57,12 +57,12 @@ enum {
|
||||
class SegmentTree {
|
||||
public:
|
||||
SegmentTree() = default;
|
||||
void build(const QVector<QPointF> &arr);
|
||||
void build(const std::vector<QPointF> &arr);
|
||||
inline std::pair<double, double> minmax(int left, int right) const { return get_minmax(1, 0, size - 1, left, right); }
|
||||
|
||||
private:
|
||||
std::pair<double, double> get_minmax(int n, int left, int right, int range_left, int range_right) const;
|
||||
void build_tree(const QVector<QPointF> &arr, int n, int left, int right);
|
||||
void build_tree(const std::vector<QPointF> &arr, int n, int left, int right);
|
||||
std::vector<std::pair<double, double>> tree;
|
||||
int size = 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user