Files
Renderive/render_2D/plottable/Afterglow.cpp
T
2026-08-12 00:06:44 +08:00

87 lines
4.4 KiB
C++

#include "Afterglow.h"
#include "Heatmap_Utils.h"
#include "Plottable_Real_Time_Data.h"
#include "../render/Blend2D_Cache.h"
#include <algorithm>
#include <deque>
namespace renderive {
namespace detail {
namespace {
using Afterglow_History = Plottable_History_Real_Time_Data<std::vector<double>, std::deque<std::vector<double>>>;
}
struct Afterglow_Control::Impl {
Impl(Afterglow_Control& owner, std::shared_ptr<Frequency_Axis> frequency, std::shared_ptr<Axis> power)
: frequency_axis(std::move(frequency)), power_axis(std::move(power)), history(owner) {}
std::shared_ptr<Frequency_Axis> frequency_axis;
std::shared_ptr<Axis> power_axis;
Afterglow_History history;
};
Afterglow_Control::Afterglow_Control(Plot_Core& plot, const Afterglow_Properties& properties, std::shared_ptr<Frequency_Axis> frequency_axis, std::shared_ptr<Axis> power_axis)
: Plottable_State(plot, properties), impl_(std::make_unique<Impl>(*this, std::move(frequency_axis), std::move(power_axis))) {}
Afterglow_Control::~Afterglow_Control() = default;
std::size_t Afterglow_Control::history_count() const {
return impl_->history.size();
}
std::size_t Afterglow_Control::latest_spectrum_point_count() const {
const auto history = impl_->history.snapshot();
return history.empty() ? 0 : history.back().size();
}
std::size_t Afterglow_Control::rendered_cell_count() const {
const auto state = properties();
const auto history = impl_->history.snapshot();
if (history.empty())
return 0;
const int width = std::min(state.frequency_point_size.get(), static_cast<int>(history.back().size()));
const int height = state.power_point_size.get() > 0
? state.power_point_size.get()
: std::max(1, static_cast<int>(impl_->power_axis->transform().pixel_length));
return width > 0 && height > 0 ? static_cast<std::size_t>(width) * static_cast<std::size_t>(height) : 0;
}
void Afterglow_Control::append_spectrum(std::span<const double> values) {
if (get<&Afterglow_Properties::frequency_point_size>() <= 0)
set<&Afterglow_Properties::frequency_point_size>(static_cast<int>(values.size()));
impl_->history.update({values.begin(), values.end()}, 64);
changed();
}
void Afterglow_Control::append_spectrum(std::pmr::vector<double>&& values) {
append_spectrum(std::span<const double>(values.data(), values.size()));
}
void Afterglow_Control::publish() {
publish_properties();
}
void Afterglow_Control::paint(Painter& painter, const Render_State_View& view) {
const auto& state = render_properties(view);
const auto& history = view.get(impl_->history);
if (history.empty())
return;
const int width = std::min(state.frequency_point_size.get(), static_cast<int>(history.back().size()));
const int height = state.power_point_size.get() > 0
? state.power_point_size.get()
: std::max(1, static_cast<int>(impl_->power_axis->transform(view).pixel_length));
if (width <= 0 || height <= 0)
return;
std::vector<double> intensity(static_cast<std::size_t>(width) * height);
double weight = 1.0;
const double decay = 1.0 - state.attenuation_rate.get();
for (auto iterator = history.rbegin(); iterator != history.rend(); ++iterator) {
const int count = std::min(width, static_cast<int>(iterator->size()));
for (int x = 0; x < count; ++x) {
const double normalized = normalized_value((*iterator)[static_cast<std::size_t>(x)], state.power_range);
const int y = std::clamp(height - 1 - static_cast<int>(normalized * (height - 1)), 0, height - 1);
intensity[static_cast<std::size_t>(y) * width + x] += weight;
if (state.interpolate && y + 1 < height)
intensity[static_cast<std::size_t>(y + 1) * width + x] += weight * 0.35;
}
weight *= decay;
if (weight < 0.01)
break;
}
const double maximum = std::max(1.0, *std::max_element(intensity.begin(), intensity.end()));
std::vector<Pixel> pixels(intensity.size());
for (std::size_t index = 0; index < pixels.size(); ++index)
pixels[index] = state.color_map.at_normalized(intensity[index] / maximum);
painter.heatmap(mapped_rect(impl_->frequency_axis->transform(view), impl_->power_axis->transform(view), state.frequency_range, state.power_range), width, height, pixels, Image_Interpolation_Mode::Bilinear);
}
}
}